Browse Source

🚀 菜单大调整,支持外链 内嵌 多级菜单

master
wangxiang 3 years ago
parent
commit
26090174d1
  1. 3
      src/components/SimpleMenu/src/SimpleMenu.vue
  2. 95
      src/router/helper/routeHelper.ts
  3. 5
      src/store/modules/permission.ts
  4. 0
      src/views/common/amap/index.vue
  5. 12
      src/views/level/Menu111.vue
  6. 12
      src/views/level/Menu12.vue
  7. 15
      src/views/level/Menu2.vue
  8. 42
      src/views/system/menu/menu.data.ts

3
src/components/SimpleMenu/src/SimpleMenu.vue

@ -129,6 +129,9 @@ @@ -129,6 +129,9 @@
async function handleSelect(key: string) {
if (isUrl(key)) {
// /
const url = String(key);
key = url.startsWith('/') ? url.substr(1) : url;
openWindow(key);
return;
}

95
src/router/helper/routeHelper.ts

@ -6,15 +6,20 @@ @@ -6,15 +6,20 @@
* @create: 2022/4/9
*/
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types';
import type { Router, RouteRecordNormalized } from 'vue-router';
import { getParentLayout, LAYOUT, PARENT_LAYOUT_NAME } from '/@/router/constant';
import { warn } from '/@/utils/log';
import { cloneDeep, omit } from 'lodash-es';
import { createRouter, createWebHashHistory } from 'vue-router';
const IFRAME = () => import('/@/views/core/iframe/FrameBlank.vue');
/** 布局组件Map */
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
LayoutMap.set('Layout', LAYOUT);
LayoutMap.set('Iframe', IFRAME);
/** 性能优化,避免多次加载views下的组件 */
let dynamicViewsModules: Record<string, () => Promise<Recordable>>;
@ -42,20 +47,92 @@ function findImportComponent(dynamicViewsModules: Record<string, () => Promise<R @@ -42,20 +47,92 @@ function findImportComponent(dynamicViewsModules: Record<string, () => Promise<R
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}');
routeList.forEach((item) => {
if (!item.component && item.meta?.frameSrc) {
item.component = 'Iframe';
}
const { component, name, children } = item;
const { component, children } = item;
if (component) {
const layoutFound = LayoutMap.get(component as string);
if (layoutFound) {
item.component = layoutFound;
} else {
} else if (component == PARENT_LAYOUT_NAME) {
item.component = getParentLayout();
}else {
item.component = findImportComponent(dynamicViewsModules, component as string);
}
} else if (name) {
item.component = getParentLayout();
}
children && transformObjToRoute(children);
});
}
/**
* 2 ,LAYOUT布局组件中加入了RouterView可以显示组件
* (getParentLayout)RouterView的,
* 2,LAYOUT,
*/
export function flatMultiLevelRoutes(routeModules: AppRouteModule[]) {
const modules: AppRouteModule[] = cloneDeep(routeModules);
for (let index = 0; index < modules.length; index++) {
const routeModule = modules[index];
// 判断级别是否 多级 路由
if (!isMultipleRoute(routeModule)) {
// 声明终止当前循环,即跳过此次循环,进行下一轮
continue;
}
// 路由等级提升
promoteRouteLevel(routeModule);
}
return modules;
}
/** 将所有多级路由全部提升为2级路由 */
function promoteRouteLevel(routeModule: AppRouteModule) {
// 使用vue-router拼接菜单
// createRouter 创建一个可以被 Vue 应用程序使用的路由实例
let router: Router | null = createRouter({
routes: [routeModule as unknown as RouteRecordNormalized],
history: createWebHashHistory(),
});
// 获取所有 路由记录的完整列表。
const routes = router.getRoutes();
// 将所有子路由添加到二级路由
addToChildren(routes, routeModule.children || [], routeModule);
router = null;
// omit lodash的函数 对传入的item对象的children进行删除
routeModule.children = routeModule.children?.map((item) => omit(item, 'children'));
}
/** 将所有子路由添加到二级路由 */
function addToChildren(routes: RouteRecordNormalized[], children: AppRouteRecordRaw[], routeModule: AppRouteModule ) {
for (let index = 0; index < children.length; index++) {
const child = children[index];
const route = routes.find((item) => item.name === child.name);
if (!route) {
continue;
}
routeModule.children = routeModule.children || [];
if (!routeModule.children.find((item) => item.name === route.name)) {
routeModule.children?.push(route as unknown as AppRouteModule);
}
if (child.children?.length) {
addToChildren(routes, child.children, routeModule);
}
}
}
/** 判断级别是否超过2级,超过两级就是多级路由 */
function isMultipleRoute(routeModule: AppRouteModule) {
// Reflect.has 与 in 操作符 相同,用于检查一个对象(包括它原型链上)是否拥有某个属性
if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) {
return false;
}
const children = routeModule.children;
let flag = false;
for (let index = 0; index < children.length; index++) {
const child = children[index];
if (child.children?.length) {
flag = true;
break;
}
}
return flag;
}

5
src/store/modules/permission.ts

@ -11,7 +11,7 @@ import { defineStore } from 'pinia'; @@ -11,7 +11,7 @@ import { defineStore } from 'pinia';
import { store } from '/@/store';
import { useI18n } from '/@/hooks/web/useI18n';
import { useUserStore } from './user';
import { transformObjToRoute } from '/@/router/helper/routeHelper';
import { transformObjToRoute, flatMultiLevelRoutes } from '/@/router/helper/routeHelper';
import { transformRouteToMenu } from '/@/router/helper/menuHelper';
import { filter } from '/@/utils/helper/treeHelper';
import { listMenuRoute } from '/@/api/platform/core/controller/menu';
@ -116,7 +116,8 @@ export const usePermissionStore = defineStore({ @@ -116,7 +116,8 @@ export const usePermissionStore = defineStore({
const menuList = transformRouteToMenu(routeList);
this.setMenuList(menuList);
// 过滤忽略路由配置项,只构建菜单不构建路由
routeList = filter(routeList, routeRemoveIgnoreFilter);
routeList = filter(flatMultiLevelRoutes(routeList), routeRemoveIgnoreFilter);
console.log('提升等级', routeList);
patchHomeAffix(routeList);
} catch (error) { console.error(error); }
return routeList;

0
src/views/map/index.vue → src/views/common/amap/index.vue

12
src/views/level/Menu111.vue

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
<template>
<div class="p-5">
多层级缓存-页面1-1-1
<br>
<Input/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Input } from 'ant-design-vue';
export default defineComponent({ name: 'Menu111Demo', components: { Input } });
</script>

12
src/views/level/Menu12.vue

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
<template>
<div class="p-5">
多层级缓存-页面1-2
<br>
<Input/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Input } from 'ant-design-vue';
export default defineComponent({ name: 'Menu12Demo', components: { Input } });
</script>

15
src/views/level/Menu2.vue

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
<template>
<div class="p-5">
多层级缓存-页面2
<br>
<Input/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Input } from 'ant-design-vue';
export default defineComponent({
name: 'Menu2Demo',
components: { Input },
});
</script>

42
src/views/system/menu/menu.data.ts

@ -10,8 +10,10 @@ import { FormSchema } from '/@/components/Table'; @@ -10,8 +10,10 @@ import { FormSchema } from '/@/components/Table';
import { h } from 'vue';
import { Tag } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
import { getMenu } from '/@/api/platform/system/controller/menu';
/** 通用变量统一声明区域 */
const isModule = (type: string) => type === 'M';
const isMenu = (type: string) => type === 'C';
const isButton = (type: string) => type === 'F';
@ -138,13 +140,33 @@ export const formSchema: FormSchema[] = [ @@ -138,13 +140,33 @@ export const formSchema: FormSchema[] = [
{ label: '菜单', value: 'C' },
{ label: '按钮', value: 'F' }
]
},
dynamicRules: ({ values }) => {
return [{
validator: async (rule, value) => {
if (~~values?.parentId != 0) {
const menu = await getMenu(values.parentId);
switch (menu?.type) {
case 'M':
if (value.type == 'F') return Promise.reject('检测到上级菜单为目录类型子项不允许选择按钮类型!');
break;
case 'C':
if (value?.type != 'F') return Promise.reject('检测到上级菜单为菜单类型子项只允许选择按钮类型!');
break;
}
} else {
if (value == 'F') return Promise.reject('根级菜单子项不允许选择按钮类型!');
}
return Promise.resolve();
},
validateTrigger: 'change'
}];
}
},
{
field: 'icon',
label: '菜单图标',
component: 'IconPicker',
required: true,
ifShow: ({ values }) => !isButton(values.type),
},
{
@ -160,7 +182,6 @@ export const formSchema: FormSchema[] = [ @@ -160,7 +182,6 @@ export const formSchema: FormSchema[] = [
field: 'path',
label: '路由地址',
component: 'Input',
required: true,
colProps: {
span: 12
},
@ -170,7 +191,6 @@ export const formSchema: FormSchema[] = [ @@ -170,7 +191,6 @@ export const formSchema: FormSchema[] = [
field: 'component',
label: '组件路径',
component: 'Input',
required: true,
colProps: {
span: 12
},
@ -230,5 +250,21 @@ export const formSchema: FormSchema[] = [ @@ -230,5 +250,21 @@ export const formSchema: FormSchema[] = [
span: 12
},
ifShow: ({ values }) => !isButton(values.type)
},
{
field: 'hideChildrenMenu',
label: '是否隐藏子菜单',
component: 'RadioButtonGroup',
defaultValue: '0',
componentProps: {
options: [
{ label: '否', value: '0' },
{ label: '是', value: '1' },
]
},
colProps: {
span: 12
},
ifShow: ({ values }) => isModule(values.type)
}
];

Loading…
Cancel
Save