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;