import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router' import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' import { isUrl } from '@/utils/is' import { cloneDeep, omit } from 'lodash-es' const modules = import.meta.glob('../views/**/*.{vue,tsx}') /** * 注册一个异步组件 * @param componentPath 例:/bpm/oa/leave/detail */ export const registerComponent = (componentPath: string) => { for (const item in modules) { if (item.includes(componentPath)) { // 使用异步组件的方式来动态加载组件 // @ts-ignore return defineAsyncComponent(modules[item]) } } } /* Layout */ export const Layout = () => import('@/layout/Layout.vue') export const getParentLayout = () => { return () => new Promise((resolve) => { resolve({ name: 'ParentLayout' }) }) } // 按照路由中meta下的rank等级升序来排序路由 export const 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 }) } export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => { if (!route) return route const { matched, ...opt } = route return { ...opt, matched: (matched ? matched.map((item) => ({ meta: item.meta, name: item.name, path: item.path })) : undefined) as RouteRecordNormalized[] } } // 后端控制路由生成 export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => { const res: AppRouteRecordRaw[] = [] const modulesRoutesKeys = Object.keys(modules) for (const route of routes) { const meta = { title: route.name, icon: route.icon, hidden: !route.visible, noCache: !route.keepAlive, alwaysShow: route.children && route.children.length === 1 && (route.alwaysShow !== undefined ? route.alwaysShow : true) } // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive let data: AppRouteRecordRaw = { path: route.path, name: route.componentName && route.componentName.length > 0 ? route.componentName : toCamelCase(route.path, true), redirect: route.redirect, meta: meta } //处理顶级非目录路由 if (!route.children && route.parentId == 0 && route.component) { data.component = Layout data.meta = {} data.name = toCamelCase(route.path, true) + 'Parent' data.redirect = '' meta.alwaysShow = true const childrenData: AppRouteRecordRaw = { path: '', name: route.componentName && route.componentName.length > 0 ? route.componentName : toCamelCase(route.path, true), redirect: route.redirect, meta: meta } const index = route?.component ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component)) : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path)) childrenData.component = modules[modulesRoutesKeys[index]] data.children = [childrenData] } else { // 目录 if (route.children) { data.component = Layout data.redirect = getRedirect(route.path, route.children) // 外链 } else if (isUrl(route.path)) { data = { path: '/external-link', component: Layout, meta: { name: route.name }, children: [data] } as AppRouteRecordRaw // 菜单 } else { // 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会根path保持一致) const index = route?.component ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component)) : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path)) data.component = modules[modulesRoutesKeys[index]] } if (route.children) { data.children = generateRoute(route.children) } } res.push(data as AppRouteRecordRaw) } return res } export const getRedirect = (parentPath: string, children: AppCustomRouteRecordRaw[]) => { if (!children || children.length == 0) { return parentPath } const path = generateRoutePath(parentPath, children[0].path) // 递归子节点 if (children[0].children) return getRedirect(path, children[0].children) } const generateRoutePath = (parentPath: string, path: string) => { if (parentPath.endsWith('/')) { parentPath = parentPath.slice(0, -1) // 移除默认的 / } if (!path.startsWith('/')) { path = '/' + path } return parentPath + path } export const pathResolve = (parentPath: string, path: string) => { if (isUrl(path)) return path const childPath = path.startsWith('/') || !path ? path : `/${path}` return `${parentPath}${childPath}`.replace(/\/\//g, '/') } // 路由降级 export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => { const modules: AppRouteRecordRaw[] = cloneDeep(routes) for (let index = 0; index < modules.length; index++) { const route = modules[index] if (!isMultipleRoute(route)) { continue } promoteRouteLevel(route) } return modules } // 层级是否大于2 const isMultipleRoute = (route: AppRouteRecordRaw) => { if (!route || !Reflect.has(route, 'children') || !route.children?.length) { return false } const children = route.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 } // 生成二级路由 const promoteRouteLevel = (route: AppRouteRecordRaw) => { let router: Router | null = createRouter({ routes: [route as RouteRecordRaw], history: createWebHashHistory() }) const routes = router.getRoutes() addToChildren(routes, route.children || [], route) router = null route.children = route.children?.map((item) => omit(item, 'children')) } // 添加所有子菜单 const addToChildren = ( routes: RouteRecordNormalized[], children: AppRouteRecordRaw[], routeModule: AppRouteRecordRaw ) => { 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 AppRouteRecordRaw) } if (child.children?.length) { addToChildren(routes, child.children, routeModule) } } } const toCamelCase = (str: string, upperCaseFirst: boolean) => { str = (str || '') .replace(/-(.)/g, function (group1: string) { return group1.toUpperCase() }) .replaceAll('-', '') if (upperCaseFirst && str) { str = str.charAt(0).toUpperCase() + str.slice(1) } return str }