routerHelper.ts 7.1 KB

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