route.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import type { Route } from '#/global'
  2. import type { RouteRecordRaw } from 'vue-router'
  3. import { systemRoutes } from '@/router/routes'
  4. import { resolveRoutePath } from '@/utils'
  5. import { cloneDeep } from 'lodash-es'
  6. import useSettingsStore from './settings'
  7. import api from '@/api'
  8. import { ElMessage } from 'element-plus'
  9. const useRouteStore = defineStore(
  10. // 唯一ID
  11. 'route',
  12. () => {
  13. const settingsStore = useSettingsStore()
  14. const isGenerate = ref(false)
  15. const routesRaw = ref<Route.recordMainRaw[]>([])
  16. const filesystemRoutesRaw = ref<RouteRecordRaw[]>([])
  17. const currentRemoveRoutes = ref<(() => void)[]>([])
  18. // 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平
  19. function flatAsyncRoutes<T extends RouteRecordRaw>(route: T): T {
  20. if (route.children) {
  21. route.children = flatAsyncRoutesRecursive(route.children, [{
  22. path: route.path,
  23. title: route.meta?.title,
  24. icon: route.meta?.icon,
  25. hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
  26. }], route.path)
  27. }
  28. return route
  29. }
  30. function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = ''): RouteRecordRaw[] {
  31. const res: RouteRecordRaw[] = []
  32. routes.forEach((route) => {
  33. if (route.children) {
  34. const childrenBaseUrl = resolveRoutePath(baseUrl, route.path)
  35. const tmpBreadcrumb = cloneDeep(breadcrumb)
  36. tmpBreadcrumb.push({
  37. path: childrenBaseUrl,
  38. title: route.meta?.title,
  39. icon: route.meta?.icon,
  40. hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
  41. })
  42. const tmpRoute = cloneDeep(route)
  43. tmpRoute.path = childrenBaseUrl
  44. if (!tmpRoute.meta) {
  45. tmpRoute.meta = {}
  46. }
  47. tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
  48. delete tmpRoute.children
  49. res.push(tmpRoute)
  50. const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl)
  51. childrenRoutes.forEach((item) => {
  52. // 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复
  53. if (res.some(v => v.path === item.path)) {
  54. res.forEach((v, i) => {
  55. if (v.path === item.path) {
  56. res[i] = item
  57. }
  58. })
  59. }
  60. else {
  61. res.push(item)
  62. }
  63. })
  64. }
  65. else {
  66. const tmpRoute = cloneDeep(route)
  67. tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path)
  68. // 处理面包屑导航
  69. const tmpBreadcrumb = cloneDeep(breadcrumb)
  70. tmpBreadcrumb.push({
  71. path: tmpRoute.path,
  72. title: tmpRoute.meta?.title,
  73. icon: tmpRoute.meta?.icon,
  74. hide: !tmpRoute.meta?.breadcrumb && tmpRoute.meta?.breadcrumb === false,
  75. })
  76. if (!tmpRoute.meta) {
  77. tmpRoute.meta = {}
  78. }
  79. tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
  80. res.push(tmpRoute)
  81. }
  82. })
  83. return res
  84. }
  85. // 扁平化路由(将三级及以上路由数据拍平成二级)
  86. const flatRoutes = computed(() => {
  87. const returnRoutes: RouteRecordRaw[] = []
  88. if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
  89. if (routesRaw.value) {
  90. routesRaw.value.forEach((item) => {
  91. const tmpRoutes = cloneDeep(item.children) as RouteRecordRaw[]
  92. tmpRoutes.map((v) => {
  93. if (!v.meta) {
  94. v.meta = {}
  95. }
  96. v.meta.auth = item.meta?.auth ?? v.meta?.auth
  97. return v
  98. })
  99. returnRoutes.push(...tmpRoutes)
  100. })
  101. returnRoutes.forEach(item => flatAsyncRoutes(item))
  102. }
  103. }
  104. else {
  105. returnRoutes.push(...cloneDeep(filesystemRoutesRaw.value) as RouteRecordRaw[])
  106. }
  107. return returnRoutes
  108. })
  109. const flatSystemRoutes = computed(() => {
  110. const routes = [...systemRoutes]
  111. routes.forEach(item => flatAsyncRoutes(item))
  112. return routes
  113. })
  114. // TODO 将设置 meta.sidebar 的属性转换成 meta.menu ,过渡处理,未来将被弃用
  115. let isUsedDeprecatedAttribute = false
  116. function converDeprecatedAttribute<T extends Route.recordMainRaw[]>(routes: T): T {
  117. routes.forEach((route) => {
  118. route.children = converDeprecatedAttributeRecursive(route.children)
  119. })
  120. if (isUsedDeprecatedAttribute) {
  121. // turbo-console-disable-next-line
  122. console.warn('[Fantastic-admin] 路由配置中的 "sidebar" 属性即将被弃用, 请尽快替换为 "menu" 属性')
  123. }
  124. return routes
  125. }
  126. function converDeprecatedAttributeRecursive(routes: RouteRecordRaw[]) {
  127. if (routes) {
  128. routes.forEach((route) => {
  129. if (typeof route.meta?.sidebar === 'boolean') {
  130. isUsedDeprecatedAttribute = true
  131. route.meta.menu = route.meta.sidebar
  132. delete route.meta.sidebar
  133. }
  134. if (route.children) {
  135. converDeprecatedAttributeRecursive(route.children)
  136. }
  137. })
  138. }
  139. return routes
  140. }
  141. // 生成路由(前端生成)
  142. function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) {
  143. // 设置 routes 数据
  144. routesRaw.value = converDeprecatedAttribute(cloneDeep(asyncRoutes) as any)
  145. isGenerate.value = true
  146. }
  147. // 格式化后端路由数据
  148. // function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/*.vue')): Route.recordMainRaw[] {
  149. // return routes.map((route: any) => {
  150. // switch (route.component) {
  151. // case 'Layout':
  152. // route.component = () => import('@/layouts/index.vue')
  153. // break
  154. // default:
  155. // if (route.component) {
  156. // route.component = views[`../../views/${route.component}`]
  157. // }
  158. // else {
  159. // delete route.component
  160. // }
  161. // }
  162. // if (route.children) {
  163. // route.children = formatBackRoutes(route.children, views)
  164. // }
  165. // return route
  166. // })
  167. // }
  168. // 格式化后端路由数据
  169. function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/*.vue')): Route.recordMainRaw[] {
  170. // 需要从外层移动到meta对象的字段列表
  171. const metaFields = ['title', 'icon', 'auth', 'menu', 'breadcrumb', 'activeMenu', 'cache', 'noCache', 'link']// 从外层移动到meta对象的字段列表
  172. return routes.filter((route: any) => route.type != 2).map((route: any) => {
  173. // 处理组件引用
  174. switch (route.component) {
  175. case 'Layout':
  176. route.component = () => import('@/layouts/index.vue')
  177. break
  178. default:
  179. if (route.component) {
  180. route.component = views[`../../views${route.component}.vue`]
  181. if (route.menu ==0) {
  182. route.menu = false;
  183. }
  184. }
  185. else {
  186. delete route.component
  187. }
  188. }
  189. // 处理元数据格式转换:将指定字段从外层移动到meta对象中
  190. const hasMetaFields = metaFields.some(field => route[field] !== undefined)
  191. if (hasMetaFields) {
  192. // 确保meta对象存在
  193. if (!route.meta) {
  194. route.meta = {}
  195. }
  196. // 批量移动字段
  197. metaFields.forEach(field => {
  198. if (route[field] !== undefined) {
  199. route.meta[field] = route[field]
  200. delete route[field]
  201. }
  202. })
  203. }
  204. // 递归处理子路由
  205. if (route.children) {
  206. route.children = formatBackRoutes(route.children, views)
  207. }
  208. return route
  209. })
  210. }
  211. // 生成路由(后端获取)
  212. async function generateRoutesAtBack() {
  213. await api.menuApi.getRoleMenuTree().then((res) => {
  214. if(res.data.length > 0) {
  215. // 设置 routes 数据
  216. routesRaw.value = converDeprecatedAttribute(formatBackRoutes(res.data) as any)
  217. isGenerate.value = true
  218. }else{
  219. // 路由获取失败
  220. ElMessage.error('路由获取失败')
  221. }
  222. }).catch(() => {})
  223. }
  224. // 生成路由(文件系统生成)
  225. function generateRoutesAtFilesystem(asyncRoutes: RouteRecordRaw[]) {
  226. // 设置 routes 数据
  227. filesystemRoutesRaw.value = cloneDeep(asyncRoutes) as any
  228. isGenerate.value = true
  229. }
  230. // 记录 accessRoutes 路由,用于登出时删除路由
  231. function setCurrentRemoveRoutes(routes: (() => void)[]) {
  232. currentRemoveRoutes.value = routes
  233. }
  234. // 清空动态路由
  235. function removeRoutes() {
  236. isGenerate.value = false
  237. routesRaw.value = []
  238. filesystemRoutesRaw.value = []
  239. currentRemoveRoutes.value.forEach((removeRoute) => {
  240. removeRoute()
  241. })
  242. currentRemoveRoutes.value = []
  243. }
  244. return {
  245. isGenerate,
  246. routesRaw,
  247. currentRemoveRoutes,
  248. flatRoutes,
  249. flatSystemRoutes,
  250. generateRoutesAtFront,
  251. generateRoutesAtBack,
  252. generateRoutesAtFilesystem,
  253. setCurrentRemoveRoutes,
  254. removeRoutes,
  255. }
  256. },
  257. )
  258. export default useRouteStore