250 lines
7.4 KiB
TypeScript
250 lines
7.4 KiB
TypeScript
import { defineStore } from 'pinia';
|
||
import type {
|
||
RouteComponent,
|
||
RouteLocationRaw,
|
||
RouteMeta,
|
||
RouteRecord,
|
||
RouteRecordRaw,
|
||
} from 'vue-router';
|
||
import { getRouters } from '@/api/router';
|
||
import useNeInfoStore from '@/store/modules/neinfo';
|
||
import BasicLayout from '@/layouts/BasicLayout.vue';
|
||
import BlankLayout from '@/layouts/BlankLayout.vue';
|
||
import LinkLayout from '@/layouts/LinkLayout.vue';
|
||
import {
|
||
MENU_COMPONENT_LAYOUT_BASIC,
|
||
MENU_COMPONENT_LAYOUT_BLANK,
|
||
MENU_COMPONENT_LAYOUT_LINK,
|
||
} from '@/constants/menu-constants';
|
||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||
|
||
/**路由构建参数类型 */
|
||
type RouterStore = {
|
||
/**初始的根路由数据 */
|
||
rootRouterData: RouteRecordRaw[];
|
||
/**动态路由数据 */
|
||
buildRouterData: RouteRecordRaw[];
|
||
};
|
||
|
||
const useRouterStore = defineStore('router', {
|
||
state: (): RouterStore => ({
|
||
rootRouterData: [],
|
||
buildRouterData: [],
|
||
}),
|
||
actions: {
|
||
/**
|
||
* 记录初始根节点菜单数据
|
||
* @param data 初始数据
|
||
* @returns 初始数据
|
||
*/
|
||
setRootRouterData(data: RouteRecordRaw[]) {
|
||
if (this.rootRouterData.length <= 0) {
|
||
this.rootRouterData = data;
|
||
}
|
||
return this.rootRouterData;
|
||
},
|
||
/**
|
||
* 动态路由列表数据生成
|
||
* @returns 生成的路由菜单
|
||
*/
|
||
async generateRoutes() {
|
||
const res = await getRouters();
|
||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||
// 获取当前网元类型
|
||
const neTypes = useNeInfoStore().getNeSelectOtions.map(v => v.value);
|
||
// 先过滤
|
||
const filteredRoutes = this.clearMenuItemByNeList(res.data.concat(), neTypes);
|
||
// 再 build
|
||
const buildRoutes = buildRouters(filteredRoutes);
|
||
this.buildRouterData = buildRoutes;
|
||
return buildRoutes;
|
||
}
|
||
return [];
|
||
},
|
||
/**
|
||
* 根据网元类型过滤菜单
|
||
* @param routes 经过clearMenuItem(router.getRoutes())处理
|
||
* @param neTypes 网元类型
|
||
* @returns 过滤后的菜单
|
||
*/
|
||
clearMenuItemByNeList(
|
||
routes: RouteRecord[] | RouteRecordRaw[],
|
||
neTypes: string[]
|
||
): RouteRecordRaw[] {
|
||
return routes
|
||
.map((item: RouteRecord | RouteRecordRaw) => {
|
||
const finalItem = { ...item };
|
||
// 过滤网元类型
|
||
if (
|
||
Array.isArray(finalItem.meta?.neType) &&
|
||
finalItem.meta?.neType.length > 0
|
||
) {
|
||
let metaNeType: string[] = finalItem.meta.neType;
|
||
let match = false; // 匹配
|
||
for (const netype of metaNeType) {
|
||
if (netype.indexOf('+') > -1) {
|
||
metaNeType = netype.split('+');
|
||
match = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (match && !metaNeType.every(item => neTypes.includes(item))) {
|
||
// 同时匹配
|
||
return null;
|
||
} else if (!metaNeType.some(item => neTypes.includes(item))) {
|
||
// 有一种
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 有子菜单进行递归
|
||
if (finalItem.children && finalItem.children.length > 0) {
|
||
const children = this.clearMenuItemByNeList(
|
||
finalItem.children,
|
||
neTypes
|
||
);
|
||
// 如果子菜单都被过滤掉了,就不显示
|
||
if (children.length === 0) {
|
||
return null;
|
||
}
|
||
finalItem.children = children;
|
||
|
||
// 只重定向到第一个可用的子菜单
|
||
// finalItem.redirect = finalItem.children[0].path;
|
||
// console.log(`finalItem.redirect`, finalItem.redirect);
|
||
// 如果有子菜单,且没有重定向,则设置重定向到第一个子菜单
|
||
if (children.length > 0) {
|
||
let childPath = children[0].path;
|
||
if (!childPath.startsWith('/')) {
|
||
// 确保父路径以 / 结尾,子路径不以 / 开头
|
||
const parentPath = finalItem.path.replace(/\/$/, '');
|
||
childPath = parentPath + '/' + childPath.replace(/^\//, '');
|
||
}
|
||
finalItem.redirect = childPath;
|
||
}
|
||
|
||
return finalItem;
|
||
}
|
||
|
||
delete finalItem.children;
|
||
return finalItem;
|
||
})
|
||
.filter(item => item) as RouteRecordRaw[];
|
||
},
|
||
},
|
||
});
|
||
|
||
/**异步路由类型 */
|
||
type RecordRaws = {
|
||
path: string;
|
||
name?: string;
|
||
meta: RouteMeta;
|
||
redirect: RouteLocationRaw;
|
||
component: string;
|
||
children: RecordRaws[];
|
||
};
|
||
|
||
/**
|
||
* 构建动态路由
|
||
*
|
||
* 遍历后台配置的路由菜单,转换为组件路由菜单
|
||
*
|
||
* @param recordRaws 异步路由列表
|
||
* @returns 可添加的路由列表
|
||
*/
|
||
function buildRouters(recordRaws: RouteRecordRaw[]): RouteRecordRaw[] {
|
||
const routers: RouteRecordRaw[] = [];
|
||
for (const item of recordRaws) {
|
||
// 过滤旧前端菜单 是layui的菜单跳过
|
||
if (['', '/page"'].includes(item.path)) {
|
||
continue;
|
||
}
|
||
|
||
// 路由页面组件
|
||
let component: RouteComponent = {};
|
||
if (item.component) {
|
||
const comp = item.component as unknown as string; // 添加类型断言
|
||
if (comp === MENU_COMPONENT_LAYOUT_BASIC) {
|
||
component = BasicLayout;
|
||
} else if (comp === MENU_COMPONENT_LAYOUT_BLANK) {
|
||
component = BlankLayout;
|
||
} else if (comp === MENU_COMPONENT_LAYOUT_LINK) {
|
||
component = LinkLayout;
|
||
} else {
|
||
// 指定页面视图,一般用于显示子菜单
|
||
component = findView(comp);
|
||
}
|
||
}
|
||
|
||
// 有子菜单进行递归
|
||
let children: RouteRecordRaw[] = [];
|
||
if (item.children && item.children.length > 0) {
|
||
children = buildRouters(item.children);
|
||
|
||
// 如果没有 redirect 但有子菜单,设置 redirect 到第一个子菜单
|
||
if (!item.redirect && children.length > 0) {
|
||
let childPath = children[0].path;
|
||
if (!childPath?.startsWith('/')) {
|
||
childPath = item.path.replace(/\/$/, '') + '/' + (childPath || '').replace(/^\//, '');
|
||
}
|
||
|
||
// 修改 item 的 redirect(需要类型断言)
|
||
(item as any).redirect = childPath;
|
||
}
|
||
}
|
||
|
||
// 对元数据特殊参数进行处理
|
||
let metaIcon = (item.meta?.icon as string) || '';
|
||
if (!metaIcon.startsWith('icon-')) {
|
||
metaIcon = '';
|
||
}
|
||
|
||
// 更新 meta(需要类型断言)
|
||
(item as any).meta = Object.assign(item.meta || {}, {
|
||
icon: metaIcon,
|
||
});
|
||
|
||
// 构建路由
|
||
const router: RouteRecordRaw = {
|
||
path: item.path,
|
||
name: item.name,
|
||
meta: item.meta,
|
||
redirect: item.redirect,
|
||
component: component,
|
||
children: children,
|
||
};
|
||
routers.push(router);
|
||
}
|
||
return routers;
|
||
}
|
||
|
||
/**匹配views里面所有的.vue或.tsx文件 */
|
||
const views = import.meta.glob('./../../views/**/*.{vue,tsx}');
|
||
|
||
/**
|
||
* 查找页面模块
|
||
*
|
||
* 查找 `/views/system/menu/index.vue` 或 `/views/system/menu/index.tsx`
|
||
*
|
||
* 参数值为 `system/menu/index`
|
||
*
|
||
* @param dirName 组件路径
|
||
* @returns 路由懒加载函数
|
||
*/
|
||
function findView(dirName: string) {
|
||
for (const dir in views) {
|
||
let viewDirName = '';
|
||
const component = dir.match(/views\/(.+)\.(vue|tsx)/);
|
||
if (component && component.length === 3) {
|
||
viewDirName = component[1];
|
||
}
|
||
if (viewDirName === dirName) {
|
||
return () => views[dir]();
|
||
}
|
||
}
|
||
return () => import('@/views/error/404.vue');
|
||
}
|
||
|
||
export default useRouterStore;
|