feat: 菜单新窗口打开.

This commit is contained in:
lijiahang
2023-12-05 14:44:46 +08:00
parent c5120463d9
commit 9b3e5eef48
15 changed files with 108 additions and 89 deletions

View File

@@ -13,7 +13,8 @@ Authorization: {{token}}
"cache": "",
"icon": "",
"path": "",
"component": ""
"component": "",
"newWindow": ""
}
@@ -33,7 +34,8 @@ Authorization: {{token}}
"cache": "",
"icon": "",
"path": "",
"component": ""
"component": "",
"newWindow": ""
}

View File

@@ -62,6 +62,10 @@ public class SystemMenuDO extends BaseDO {
@TableField("cache")
private Integer cache;
@Schema(description = "新窗口打开 0关闭 1开启")
@TableField("new_window")
private Integer newWindow;
@Schema(description = "菜单图标")
@TableField("icon")
private String icon;

View File

@@ -41,6 +41,9 @@ public class SystemMenuCacheDTO {
@Schema(description = "菜单缓存 0不缓存 1缓存")
private Integer cache;
@Schema(description = "新窗口打开 0关闭 1开启")
private Integer newWindow;
@Schema(description = "菜单图标")
private String icon;

View File

@@ -46,6 +46,9 @@ public class SystemMenuCreateRequest implements Serializable {
@Schema(description = "菜单缓存 0不缓存 1缓存")
private Integer cache;
@Schema(description = "新窗口打开 0关闭 1开启")
private Integer newWindow;
@Schema(description = "菜单图标")
private String icon;

View File

@@ -46,6 +46,9 @@ public class SystemMenuUpdateRequest implements Serializable {
@Schema(description = "菜单缓存 0不缓存 1缓存")
private Integer cache;
@Schema(description = "新窗口打开 0关闭 1开启")
private Integer newWindow;
@Schema(description = "菜单图标")
private String icon;

View File

@@ -52,6 +52,9 @@ public class SystemMenuVO implements Serializable {
@Schema(description = "菜单缓存 0不缓存 1缓存")
private Integer cache;
@Schema(description = "新窗口打开 0关闭 1开启")
private Integer newWindow;
@Schema(description = "菜单图标")
private String icon;

View File

@@ -18,6 +18,7 @@
<result column="visible" property="visible"/>
<result column="status" property="status"/>
<result column="cache" property="cache"/>
<result column="new_window" property="newWindow"/>
<result column="icon" property="icon"/>
<result column="path" property="path"/>
<result column="component" property="component"/>
@@ -25,7 +26,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, parent_id, name, permission, type, sort, visible, status, cache, icon, path, component, create_time, update_time, creator, updater, deleted
id, parent_id, name, permission, type, sort, visible, status, cache, new_window, icon, path, component, create_time, update_time, creator, updater, deleted
</sql>
</mapper>

View File

@@ -12,6 +12,7 @@ export interface MenuCreateRequest {
sort?: number;
visible?: number;
cache?: number;
newWindow?: number;
icon?: string;
path?: string;
component?: string;
@@ -46,6 +47,7 @@ export interface MenuQueryResponse extends TableData {
visible: number;
status: number;
cache: number;
newWindow: number,
icon: string;
path: string;
component: string;

View File

@@ -1,7 +1,7 @@
<script lang="tsx">
import type { RouteMeta, RouteRecordRaw } from 'vue-router';
import { compile, computed, defineComponent, h, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { compile, computed, defineComponent, h, ref } from 'vue';
import { useAppStore } from '@/store';
import { listenerRouteChange } from '@/utils/route-listener';
import { openWindow, regexUrl } from '@/utils';
@@ -28,27 +28,23 @@
const openKeys = ref<string[]>([]);
const selectedKey = ref<string[]>([]);
/**
* 跳转
*/
// 跳转路由
const goto = (e: any, item: RouteRecordRaw) => {
// 打开外链
if (regexUrl.test(item.path)) {
openWindow(item.path);
selectedKey.value = [item.name as string];
return;
}
const { hideInMenu, activeMenu, newWindow } = item.meta as RouteMeta;
// 新页面打开
if (e.ctrlKey) {
if (newWindow || e.ctrlKey) {
const { href } = router.resolve({
name: item.name,
});
openWindow(href);
selectedKey.value = [item.name as string];
return;
}
// 设置 selectedKey
const { hideInMenu, activeMenu } = item.meta as RouteMeta;
if (route.name === item.name && !hideInMenu && !activeMenu) {
selectedKey.value = [item.name as string];
return;
@@ -81,9 +77,7 @@
return result;
};
/**
* 监听路由 设置打开的 key
*/
// 监听路由 设置打开的 key
listenerRouteChange((newRoute) => {
const { activeMenu, hideInMenu } = newRoute.meta;
if (!hideInMenu || activeMenu) {

View File

@@ -1,4 +1,4 @@
import type { RouteRecordRaw, RouteRecordNormalized } from 'vue-router';
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
import { computed } from 'vue';
import { useMenuStore } from '@/store';
import { cloneDeep } from 'lodash';
@@ -19,9 +19,8 @@ export default function useMenuTree() {
const collector: any = _routes.map((element) => {
// 隐藏子目录
if (element.meta?.hideChildrenInMenu || !element.children) {
if (element.meta?.hideInMenu || !element.children) {
element.children = [];
if (element.meta?.hideInMenu) {
// 如果隐藏菜单 则不显示
return null;

View File

@@ -1,5 +1,5 @@
<template>
<a-layout class="host-workspace-layout">
<a-layout class="host-layout">
<!-- 页面 -->
<a-layout-content>
<!-- 水印 -->
@@ -16,7 +16,7 @@
<script lang="ts">
export default {
name: 'hos-workspace-layout'
name: 'host-layout'
};
</script>
@@ -29,7 +29,7 @@
</script>
<style lang="less" scoped>
.host-workspace-layout {
.host-layout {
height: 100vh;
width: 100%;

View File

@@ -13,12 +13,12 @@ declare module 'vue-router' {
order?: number;
// 是否隐藏菜单
hideInMenu?: boolean;
// 是否隐藏子菜单
hideChildrenInMenu?: boolean;
// 是否添加到 tab
noAffix?: boolean;
// 是否忽略缓存
ignoreCache?: boolean;
// 是否新窗口打开
newWindow?: boolean;
// 是否活跃
activeMenu?: string;
}

View File

@@ -1,9 +1,11 @@
import type { RouteRecordNormalized } from 'vue-router';
import type { RouteMeta, RouteRecordNormalized } from 'vue-router';
import type { MenuState } from './types';
import type { MenuQueryResponse } from '@/api/system/menu';
import { defineStore } from 'pinia';
import { Notification } from '@arco-design/web-vue';
import { getMenuList } from '@/api/user/auth';
import router from '@/router';
import { EnabledStatus } from '@/types/const';
export default defineStore('menu', {
state: (): MenuState => ({
@@ -18,49 +20,51 @@ export default defineStore('menu', {
},
actions: {
// 转换菜单
convert(item: MenuQueryResponse): RouteRecordNormalized {
// 设置路由属性
const meta: RouteMeta = {
locale: item.name,
icon: item.icon,
order: item.sort,
hideInMenu: item.visible === EnabledStatus.DISABLED,
noAffix: item.visible === EnabledStatus.DISABLED,
ignoreCache: item.cache === EnabledStatus.DISABLED,
newWindow: item.newWindow === EnabledStatus.ENABLED,
};
// 获取 router
const route = router.getRoutes().find(r => {
return r.name === item.component;
});
// 设置 router meta
if (route) {
// 路由配置覆盖菜单配置
route.meta = { ...meta, ...route.meta };
}
// 返回
return {
name: item.component,
path: item.path,
meta: meta,
children: undefined
} as unknown as RouteRecordNormalized;
},
// 加载菜单
async fetchMenu() {
try {
// 查询菜单
const { data } = await getMenuList();
// @ts-ignore
this.serverMenus = (data as Array<any>).map(s => {
// 转换
const convert = (item: any) => {
// 设置路由属性
const meta = {
locale: item.name,
icon: item.icon,
order: item.sort,
hideInMenu: item.visible === 0,
hideChildrenInMenu: item.visible === 0,
noAffix: item.visible === 0,
ignoreCache: item.cache === 0,
};
// 获取 router
const route = router.getRoutes().find(r => {
return r.name === item.component;
});
// 设置 router meta
if (route) {
// 路由配置覆盖菜单配置
route.meta = { ...meta, ...route.meta };
}
// 返回
return {
name: item.component,
path: item.path,
meta: meta,
children: undefined as unknown
};
};
// 转换菜单
this.serverMenus = data.map(s => {
// 构建父目录
const menu = convert(s);
const menu = this.convert(s);
// 构建子目录
if (s.children) {
menu.children = (s.children as Array<any>).map(convert);
menu.children = s.children.map(this.convert);
}
return menu;
});
}) as RouteRecordNormalized[];
// 是否已加载过
this.menuFetched = true;
// 未配置菜单
@@ -72,7 +76,7 @@ export default defineStore('menu', {
}
} catch (error) {
Notification.error({
content: '加载菜单失败',
content: '加载菜单失败, 请刷新后重试',
closable: true,
});
}

View File

@@ -3,7 +3,7 @@
body-class="modal-form"
title-align="start"
:title="title"
:top="80"
:top="30"
:align-center="false"
:draggable="true"
:mask-closable="false"
@@ -17,8 +17,8 @@
ref="formRef"
label-align="right"
:style="{ width: '460px' }"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:label-col-props="{ span: 7 }"
:wrapper-col-props="{ span: 17 }"
:rules="formRules">
<!-- 上级菜单 -->
<a-form-item field="parentId" label="上级菜单">
@@ -88,10 +88,23 @@
<a-switch type="round"
size="large"
v-model="formModel.visible"
:checked-text="getDictValue(menuVisibleKey, MenuVisible.SHOW)"
:unchecked-text="getDictValue(menuVisibleKey, MenuVisible.HIDE)"
:checked-value="MenuVisible.SHOW"
:unchecked-value="MenuVisible.HIDE" />
:checked-text="getDictValue(menuVisibleKey, EnabledStatus.ENABLED)"
:unchecked-text="getDictValue(menuVisibleKey, EnabledStatus.DISABLED)"
:checked-value="EnabledStatus.ENABLED"
:unchecked-value="EnabledStatus.DISABLED" />
</a-form-item>
<!-- 是否新窗口打开 -->
<a-form-item v-if="formModel.type !== MenuType.FUNCTION"
field="type"
label="新窗口打开"
tooltip="选择后点击菜单会使用新页面打开">
<a-switch type="round"
size="large"
v-model="formModel.newWindow"
:checked-text="getDictValue(menuNewWindowKey, EnabledStatus.ENABLED)"
:unchecked-text="getDictValue(menuNewWindowKey, EnabledStatus.DISABLED)"
:checked-value="EnabledStatus.ENABLED"
:unchecked-value="EnabledStatus.DISABLED" />
</a-form-item>
<!-- 是否缓存 -->
<a-form-item v-if="formModel.type !== MenuType.FUNCTION"
@@ -101,10 +114,10 @@
<a-switch type="round"
size="large"
v-model="formModel.cache"
:checked-text="getDictValue(menuCacheKey, MenuCache.ENABLED)"
:unchecked-text="getDictValue(menuCacheKey, MenuCache.DISABLED)"
:checked-value="MenuCache.ENABLED"
:unchecked-value="MenuCache.DISABLED" />
:checked-text="getDictValue(menuCacheKey, EnabledStatus.ENABLED)"
:unchecked-text="getDictValue(menuCacheKey, EnabledStatus.DISABLED)"
:checked-value="EnabledStatus.ENABLED"
:unchecked-value="EnabledStatus.DISABLED" />
</a-form-item>
</a-form>
</a-spin>
@@ -123,8 +136,9 @@
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import formRules from '../types/form.rules';
import { menuCacheKey, sortStep } from '../types/const';
import { menuVisibleKey, menuTypeKey, MenuType, MenuVisible, MenuCache } from '../types/const';
import { menuCacheKey, menuNewWindowKey, sortStep } from '../types/const';
import { menuVisibleKey, menuTypeKey, MenuType } from '../types/const';
import { EnabledStatus } from '@/types/const';
import { createMenu, updateMenu } from '@/api/system/menu';
import { Message } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
@@ -147,7 +161,8 @@
permission: undefined,
sort: undefined,
visible: MenuVisible.SHOW,
cache: MenuCache.ENABLED,
cache: EnabledStatus.ENABLED,
newWindow: EnabledStatus.DISABLED,
icon: undefined,
path: undefined,
component: undefined,

View File

@@ -11,22 +11,6 @@ export const MenuType = {
FUNCTION: 3
};
// 菜单是否可见 值
export const MenuVisible = {
// 隐藏
HIDE: 0,
// 显示
SHOW: 1
};
// 菜单缓存状态 值
export const MenuCache = {
// 禁用
DISABLED: 0,
// 启用
ENABLED: 1
};
// 菜单类型 字典项
export const menuTypeKey = 'systemMenuType';
// 菜单状态 字典项
@@ -35,6 +19,8 @@ export const menuStatusKey = 'systemMenuStatus';
export const menuVisibleKey = 'systemMenuVisible';
// 是否缓存 字典项
export const menuCacheKey = 'systemMenuCache';
// 是否新窗口打开 字典项
export const menuNewWindowKey = 'systemMenuNewWindow';
// 加载的字典值
export const dictKeys = [menuTypeKey, menuStatusKey, menuVisibleKey, menuCacheKey];
export const dictKeys = [menuTypeKey, menuStatusKey, menuVisibleKey, menuCacheKey, menuNewWindowKey];