/* * @Author: levi7754 levi7754@163.com * @Date: 2024-08-01 21:42:39 * @LastEditors: levi7754 levi7754@163.com * @LastEditTime: 2025-12-02 18:21:41 * @FilePath: \udp-front\packages\udp-core\src\router\utils.ts * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE */ import type { RouteRecordRaw, RouteComponent } from 'vue-router'; import { isProxy, toRaw, defineAsyncComponent } from 'vue'; import { useTimeoutFn } from '@vueuse/core'; import { storageLocal, buildHierarchyTree, dbstorage } from '@utogether/utils'; import { clone } from 'xe-utils'; import { router, getViews } from './index'; import { getConfig } from '../config'; import { geti18n } from '../plugins/i18n'; import { routerArrays } from '../layout/types'; import { usePermissionStoreHook } from '../store/modules/permission'; import { useSystemStoreHook } from '../store/modules/system'; import { useMultiTagsStoreHook } from '../store/modules/multiTags'; import { getServiceApi } from '../api'; import createComponent from './createComponent'; const Layout = () => import('../layout/layoutView.vue'); const IFrame = () => import('../layout/frameView.vue'); // 动态路由 import { initDict, initSystemInfo, getAsyncRoutes } from '../api/user'; // https://cn.vitejs.dev/guide/features.html#glob-import // const modulesRoutes = import.meta.glob('/src/views/**/*.{vue,tsx}'); // 菜单类型 enum MenuCategoty { Menu = '5', // 菜单 Lowcode = '10' // 低代码 } /** 按照路由中meta下的rank等级升序来排序路由 */ function ascending(arr: any[]) { arr.forEach(v => { if (v?.meta?.rank === null) v.meta.rank = undefined; if (v?.meta?.rank === 0) { if (v.name !== 'home' && v.path !== '/') { console.warn('rank only the home page can be 0'); } } }); return arr.sort((a: { meta: { rank: number } }, b: { meta: { rank: number } }) => { return a?.meta?.rank - b?.meta?.rank; }); } /** 过滤meta中showLink为false的菜单 */ function filterTree(data: RouteComponent[]) { const newTree = clone(data, true).filter((v: { meta: { showLink: boolean } }) => v && v.meta?.showLink !== false); newTree.forEach((v: { children }) => v.children && (v.children = filterTree(v.children))); return newTree; } /** 通过指定 `key` 获取父级路径集合,默认 `key` 为 `path` */ function getParentPaths(value: string, routes: RouteRecordRaw[], key = 'path') { // 深度遍历查找 function dfs(routes: RouteRecordRaw[], value: string, parents: string[]) { for (let i = 0; i < routes.length; i++) { const item = routes[i]; // 找到path则返回父级path // if (item[key] === value) { // return parents.length ? parents : routes[0]; // } if (item[key] === value) return parents; // children不存在或为空则不递归 if (!item.children || !item.children.length) continue; // 往下查找时将当前path入栈 parents.push(item.path); // @ts-ignore if (dfs(item.children, value, parents).length) return parents; // 深度遍历查找未找到时当前path 出栈 parents.pop(); } // 未找到时返回空数组 return []; } return dfs(routes, value, []); } /** 查找对应 `path` 的路由信息 */ function findRouteByPath(path: string, routes: RouteRecordRaw[]) { let res = routes.find((item: { path: string }) => item.path == path); if (res) { return isProxy(res) ? toRaw(res) : res; } else { for (let i = 0; i < routes.length; i++) { if (routes[i].children instanceof Array && routes[i].children.length > 0) { res = findRouteByPath(path, routes[i].children); if (res) { return isProxy(res) ? toRaw(res) : res; } } } return null; } } function addPathMatch() { if (!router.hasRoute('pathMatch')) { router.addRoute({ path: '/:pathMatch(.*)', name: 'pathMatch', redirect: '/error/404' }); } } /** * @description: 添加审批动态路由 * @param {*} syncRoutes 异步获取的路由表数据 * @param {*} routes 格式化后的路由表数据 * @return {*} 格式化后的路由表数据 */ // function addWorkflowRouter(syncRoutes, routes) { // const flowIdx = syncRoutes.findIndex(f => f.menuCode === 'Workflow'); // const isExit = flowIdx !== -1 && syncRoutes[flowIdx].children?.some(s => s.name === 'workflowApprove'); // if (!isExit) { // flowIdx !== -1 && routes[flowIdx].children?.push(workflowRouter); // } // return routes; // } // 菜单国家化 function setMenui18n() { const i18n = geti18n(); i18n.global?.mergeLocaleMessage('zh', menuI18n.zh); i18n.global?.mergeLocaleMessage('en', menuI18n.en); storageLocal.setItem('menu_zh', menuI18n.zh); storageLocal.setItem('menu_en', menuI18n.en); } function handleAsyncRoutes(routesList) { if (routesList?.length) { const idx = routesList.findIndex(f => f.menuCode === 'SysHome'); if (idx !== -1) { // 仪表盘报表 const dashboardTabs = []; const homeMenus = []; routesList[idx].children.forEach(child => { if (child.menuCategory !== '1') { dashboardTabs.push(child); } else { homeMenus.push(child); } }); routesList[idx].children = homeMenus; useSystemStoreHook().setHomeMenus(dashboardTabs); } const routes = clone(addAsyncRoutes(routesList), true); // routes = addWorkflowRouter(routesList, routes); setMenui18n(); formatFlatteningRoutes(routes).map((v: RouteRecordRaw) => { // 菜单国际化 // 防止重复添加路由 const { options } = router; if (options.routes[0].children.findIndex(value => value.path === v.path) !== -1) { return; } else { // 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转 router.options.routes[0].children.push(v); // 最终路由进行升序 ascending(router.options.routes[0].children); if (!router.hasRoute(v?.name)) router.addRoute(v); } }); } usePermissionStoreHook().changeSetting(routesList); if (!useMultiTagsStoreHook().getMultiTagsCache) { useMultiTagsStoreHook().handleTags('equal', [ ...routerArrays, ...usePermissionStoreHook().flatteningRoutes.filter(v => v?.meta?.fixedTag) ]); } addPathMatch(); } /** 初始化路由(`new Promise` 写法防止在异步请求中造成无限循环)*/ async function initRouter(userName) { let asyncRouteList = []; const key = `U-${userName}-ROUTES`; // 开启动态路由缓存本地 if (getConfig().CachingAsyncRoutes) { await dbstorage.getItem(key).then(data => (asyncRouteList = data)); } initDict().then(res => useSystemStoreHook().setDict(res)); await initSystemInfo().then(async (res: IRecord) => { useSystemStoreHook().setSystemInfo(res); await getServiceApi() .get('/uums/cusOrganization', { pageSize: 100, pageNum: 1, orgId: res.orgId }) .then((res: IResponseData) => useSystemStoreHook().setInvOrgList(res?.list || [])); }); if (asyncRouteList?.length) { return new Promise(resolve => { handleAsyncRoutes(asyncRouteList); // 缓存最新的路由 getAsyncRoutes({ userName }).then((data: any) => { dbstorage.setItem(key, data); }); resolve(router); }); } else { return new Promise(resolve => { getAsyncRoutes({ userName }).then((data: any) => { handleAsyncRoutes(clone(data, true)); dbstorage.setItem(key, data); resolve(router); }); }); } } /** * 将多级嵌套路由处理成一维数组 * @param routesList 传入路由 * @returns 返回处理后的一维路由 */ function formatFlatteningRoutes(routesList: RouteRecordRaw[]) { if (routesList.length === 0) return routesList; let hierarchyList = buildHierarchyTree(routesList); for (let i = 0; i < hierarchyList.length; i++) { if (hierarchyList[i].children) { hierarchyList = hierarchyList.slice(0, i + 1).concat(hierarchyList[i].children, hierarchyList.slice(i + 1)); } } return hierarchyList; } /** * 一维数组处理成多级嵌套数组(三级及以上的路由全部拍成二级,keep-alive 只支持到二级缓存) * @param routesList 处理后的一维路由菜单数组 * @returns 返回将一维数组重新处理成规定路由的格式 */ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) { if (routesList.length === 0) return routesList; const newRoutesList: RouteRecordRaw[] = []; routesList.forEach((v: RouteRecordRaw) => { if (v.path === '/') { newRoutesList.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] }); } else { newRoutesList[0].children.push({ ...v }); } }); return newRoutesList; } /** 处理缓存路由(添加、删除、刷新) */ function handleAliveRoute({ name }: ToRouteType, mode?: string) { switch (mode) { case 'add': usePermissionStoreHook().cacheOperate({ mode: 'add', name }); break; case 'delete': usePermissionStoreHook().cacheOperate({ mode: 'delete', name }); break; case 'refresh': usePermissionStoreHook().cacheOperate({ mode: 'refresh', name }); break; default: usePermissionStoreHook().cacheOperate({ mode: 'delete', name }); useTimeoutFn(() => { usePermissionStoreHook().cacheOperate({ mode: 'add', name }); }, 100); } } /** 过滤后端传来的动态路由 重新生成规范路由 */ const menuI18n = { zh: {}, en: {} }; const processMenus = []; const addAsyncRoutes = (arrRoutes: Array) => { const modulesRoutes = getViews(); if (!arrRoutes || !arrRoutes.length) return; const modulesRoutesKeys = Object.keys(modulesRoutes); for (let i = 0; i < arrRoutes.length; i++) { const v = arrRoutes[i]; if (v?.children && v.children.length && !v.redirect) { // 父级的redirect属性取值:如果子级存在且父级的redirect属性不存在,默认取第一个子级的path;如果子级存在且父级的redirect属性存在,取存在的redirect属性,会覆盖默认值 v.redirect = v.children[0].path; } const { icon, permissionCode, extraIcon, frameSrc } = v; // 流程菜单保存 v.isApprovalPage === 'Y' && processMenus.push(v); v.meta = { keepAlive: true, rank: v.sort, title: `${v.i18nField}`, // 审批流设计页面和审批页面的code写死,只需配置页面即可(保底方式) // showLink: !['UDPFlowTask'].includes(v.menuCode) ? null : false showLink: v.showLink !== 'N' }; v.hiddenTag = null; Object.assign(v.meta, { icon, permissionCode, extraIcon, frameSrc }); menuI18n.zh[v.i18nField] = v.menuName || v.menuNameEn; menuI18n.en[v.i18nField] = v.menuNameEn || v.menuName; v.name = v.menuCode || v.name; v.path = v.menuPath || v.path; if (!v.parentId) { v.component = Layout; } else if (v.meta?.frameSrc) { v.component = IFrame; } else if (v.menuCategory === MenuCategoty.Lowcode) { // 低开路由处理 const index = modulesRoutesKeys.findIndex(ev => ev.includes('lowcode-contain')); v.component = createComponent( v.name, defineAsyncComponent(() => import(/* @vite-ignore */ modulesRoutesKeys[index])) ); // 将模块类型赋值到meta中 Object.assign(v.meta, { moduleType: v.moduleType }); } else if (v.menuCategory !== MenuCategoty.Menu) { const index = modulesRoutesKeys.findIndex(ev => ev.includes(v.path)); v.component = modulesRoutes[modulesRoutesKeys[index]]; } if (v?.children && v.children.length) { addAsyncRoutes(v.children); } else { delete v.children; } } return arrRoutes; }; // 是否有权限 function hasPermissions(value: Array): boolean { if (value && value instanceof Array && value.length > 0) { const roles = usePermissionStoreHook().buttonAuth; const permissionRoles = value; const hasPermission = roles.some(role => { return permissionRoles.includes(role); }); if (!hasPermission) { return false; } return true; } else { return false; } } // 删除当前路由 function delCurrentRoute(current: any, toNext = true) { const startIndex: number = useMultiTagsStoreHook().multiTags.findIndex(tag => { if (current.query) { if (current.path === tag.path) { return current.query === tag.query; } } else { return current.path === tag.path; } }); useMultiTagsStoreHook().handleTags('splice', '', { startIndex, length: 1 }); handleAliveRoute(current.matched, 'delete'); if (toNext) { const newRoute = useMultiTagsStoreHook().handleTags('slice'); router.push({ path: newRoute[0].path, query: newRoute[0].query }); } } function handleTopMenu(route) { if (route?.children && route.children.length > 1) { if (route.redirect) { return route.children.filter(cur => cur.path === route.redirect)[0]; } else { return route.children[0]; } } else { return route; } } /** 获取所有菜单中的第一个菜单(顶级菜单)*/ function getTopMenu(tag = false) { const topMenu = handleTopMenu(usePermissionStoreHook().wholeMenus[0]?.children[0]); tag && useMultiTagsStoreHook().handleTags('push', topMenu); return topMenu; } function getProcessMenu() { return processMenus; } export { ascending, filterTree, initRouter, getTopMenu, getProcessMenu, hasPermissions, addAsyncRoutes, getParentPaths, findRouteByPath, handleAliveRoute, formatTwoStageRoutes, formatFlatteningRoutes, delCurrentRoute };