routerHelper.ts 8.2 KB

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