提交 | 用户 | 时间
820397 1 import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router'
H 2 import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
3 import { isUrl } from '@/utils/is'
4 import { cloneDeep, omit } from 'lodash-es'
5 import qs from 'qs'
6
7 const modules = import.meta.glob('../views/**/*.{vue,tsx}')
8 /**
9  * 注册一个异步组件
10  * @param componentPath 例:/bpm/oa/leave/detail
11  */
12 export const registerComponent = (componentPath: string) => {
13   for (const item in modules) {
14     if (item.includes(componentPath)) {
15       // 使用异步组件的方式来动态加载组件
16       // @ts-ignore
17       return defineAsyncComponent(modules[item])
18     }
19   }
20 }
21 /* Layout */
22 export const Layout = () => import('@/layout/Layout.vue')
23
24 export const getParentLayout = () => {
25   return () =>
26     new Promise((resolve) => {
27       resolve({
28         name: 'ParentLayout'
29       })
30     })
31 }
32
33 // 按照路由中meta下的rank等级升序来排序路由
34 export const ascending = (arr: any[]) => {
35   arr.forEach((v) => {
36     if (v?.meta?.rank === null) v.meta.rank = undefined
37     if (v?.meta?.rank === 0) {
38       if (v.name !== 'home' && v.path !== '/') {
39         console.warn('rank only the home page can be 0')
40       }
41     }
42   })
43   return arr.sort((a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {
44     return a?.meta?.rank - b?.meta?.rank
45   })
46 }
47
48 export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => {
49   if (!route) return route
50   const { matched, ...opt } = route
51   return {
52     ...opt,
53     matched: (matched
54       ? matched.map((item) => ({
55           meta: item.meta,
56           name: item.name,
57           path: item.path
58         }))
59       : undefined) as RouteRecordNormalized[]
60   }
61 }
62
63 // 后端控制路由生成
64 export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
65   const res: AppRouteRecordRaw[] = []
66   const modulesRoutesKeys = Object.keys(modules)
67   for (const route of routes) {
68     // 1. 生成 meta 菜单元数据
69     const meta = {
70       title: route.name,
71       icon: route.icon,
72       hidden: !route.visible,
73       noCache: !route.keepAlive,
74       alwaysShow:
75         route.children &&
76         route.children.length === 1 &&
77         (route.alwaysShow !== undefined ? route.alwaysShow : true)
78     } as any
79     // 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
80     // 此时,我们需要解析参数,并且将参数放到 meta.query 中
81     // 这样,后续在 Vue 文件中,可以通过 const { currentRoute } = useRouter() 中,通过 meta.query 获取到参数
82     if (route.component && route.component.indexOf('?') > -1) {
83       const query = route.component.split('?')[1]
84       route.component = route.component.split('?')[0]
85       meta.query = qs.parse(query)
86     }
87
88     // 2. 生成 data(AppRouteRecordRaw)
89     // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive
90     let data: AppRouteRecordRaw = {
3e359e 91       path:
H 92         route.path.indexOf('?') > -1 && !isUrl(route.path) ? route.path.split('?')[0] : route.path, // 注意,需要排除 http 这种 url,避免它带 ? 参数被截取掉
820397 93       name:
H 94         route.componentName && route.componentName.length > 0
95           ? route.componentName
96           : toCamelCase(route.path, true),
97       redirect: route.redirect,
98       meta: meta
99     }
100     //处理顶级非目录路由
101     if (!route.children && route.parentId == 0 && route.component) {
102       data.component = Layout
103       data.meta = {}
104       data.name = toCamelCase(route.path, true) + 'Parent'
105       data.redirect = ''
106       meta.alwaysShow = true
107       const childrenData: AppRouteRecordRaw = {
108         path: '',
109         name:
110           route.componentName && route.componentName.length > 0
111             ? route.componentName
112             : toCamelCase(route.path, true),
113         redirect: route.redirect,
114         meta: meta
115       }
116       const index = route?.component
117         ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))
118         : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))
119       childrenData.component = modules[modulesRoutesKeys[index]]
120       data.children = [childrenData]
121     } else {
122       // 目录
3e359e 123       if (route.children?.length) {
820397 124         data.component = Layout
H 125         data.redirect = getRedirect(route.path, route.children)
126         // 外链
127       } else if (isUrl(route.path)) {
128         data = {
129           path: '/external-link',
130           component: Layout,
131           meta: {
132             name: route.name
133           },
134           children: [data]
135         } as AppRouteRecordRaw
136         // 菜单
137       } else {
138         // 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会根path保持一致)
139         const index = route?.component
140           ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))
141           : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))
142         data.component = modules[modulesRoutesKeys[index]]
143       }
144       if (route.children) {
145         data.children = generateRoute(route.children)
146       }
147     }
148     res.push(data as AppRouteRecordRaw)
149   }
150   return res
151 }
152 export const getRedirect = (parentPath: string, children: AppCustomRouteRecordRaw[]) => {
153   if (!children || children.length == 0) {
154     return parentPath
155   }
156   const path = generateRoutePath(parentPath, children[0].path)
157   // 递归子节点
158   if (children[0].children) return getRedirect(path, children[0].children)
159 }
160 const generateRoutePath = (parentPath: string, path: string) => {
161   if (parentPath.endsWith('/')) {
162     parentPath = parentPath.slice(0, -1) // 移除默认的 /
163   }
164   if (!path.startsWith('/')) {
165     path = '/' + path
166   }
167   return parentPath + path
168 }
169 export const pathResolve = (parentPath: string, path: string) => {
170   if (isUrl(path)) return path
171   const childPath = path.startsWith('/') || !path ? path : `/${path}`
172   return `${parentPath}${childPath}`.replace(/\/\//g, '/')
173 }
174
175 // 路由降级
176 export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => {
177   const modules: AppRouteRecordRaw[] = cloneDeep(routes)
178   for (let index = 0; index < modules.length; index++) {
179     const route = modules[index]
180     if (!isMultipleRoute(route)) {
181       continue
182     }
183     promoteRouteLevel(route)
184   }
185   return modules
186 }
187
188 // 层级是否大于2
189 const isMultipleRoute = (route: AppRouteRecordRaw) => {
190   if (!route || !Reflect.has(route, 'children') || !route.children?.length) {
191     return false
192   }
193
194   const children = route.children
195
196   let flag = false
197   for (let index = 0; index < children.length; index++) {
198     const child = children[index]
199     if (child.children?.length) {
200       flag = true
201       break
202     }
203   }
204   return flag
205 }
206
207 // 生成二级路由
208 const promoteRouteLevel = (route: AppRouteRecordRaw) => {
209   let router: Router | null = createRouter({
210     routes: [route as RouteRecordRaw],
211     history: createWebHashHistory()
212   })
213
214   const routes = router.getRoutes()
215   addToChildren(routes, route.children || [], route)
216   router = null
217
218   route.children = route.children?.map((item) => omit(item, 'children'))
219 }
220
221 // 添加所有子菜单
222 const addToChildren = (
223   routes: RouteRecordNormalized[],
224   children: AppRouteRecordRaw[],
225   routeModule: AppRouteRecordRaw
226 ) => {
227   for (let index = 0; index < children.length; index++) {
228     const child = children[index]
229     const route = routes.find((item) => item.name === child.name)
230     if (!route) {
231       continue
232     }
233     routeModule.children = routeModule.children || []
234     if (!routeModule.children.find((item) => item.name === route.name)) {
235       routeModule.children?.push(route as unknown as AppRouteRecordRaw)
236     }
237     if (child.children?.length) {
238       addToChildren(routes, child.children, routeModule)
239     }
240   }
241 }
242 const toCamelCase = (str: string, upperCaseFirst: boolean) => {
243   str = (str || '')
244     .replace(/-(.)/g, function (group1: string) {
245       return group1.toUpperCase()
246     })
247     .replaceAll('-', '')
248
249   if (upperCaseFirst && str) {
250     str = str.charAt(0).toUpperCase() + str.slice(1)
251   }
252
253   return str
254 }