import { cloneDeep } from 'lodash-es' import type { RouteMeta, RouteRecordRaw } from 'vue-router' import useSettingsStore from './settings' import { resolveRoutePath } from '@/utils' import { systemRoutes } from '@/router/routes' import apiApp from '@/api/modules/app' import type { Route } from '#/global' const useRouteStore = defineStore( // 唯一ID 'route', () => { const settingsStore = useSettingsStore() const isGenerate = ref(false) const routesRaw = ref([]) const filesystemRoutesRaw = ref([]) const currentRemoveRoutes = ref<(() => void)[]>([]) // 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平 function flatAsyncRoutes(route: T): T { if (route.children) { route.children = flatAsyncRoutesRecursive(route.children, [{ path: route.path, title: route.meta?.title, icon: route.meta?.icon, hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false, }], route.path, route.meta?.auth) } return route } function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = '', baseAuth: RouteMeta['auth']): RouteRecordRaw[] { const res: RouteRecordRaw[] = [] routes.forEach((route) => { if (route.children) { const childrenBaseUrl = resolveRoutePath(baseUrl, route.path) const childrenBaseAuth = !baseAuth || baseAuth === '' || baseAuth?.length === 0 ? route.meta?.auth : baseAuth const tmpBreadcrumb = cloneDeep(breadcrumb) tmpBreadcrumb.push({ path: childrenBaseUrl, title: route.meta?.title, icon: route.meta?.icon, hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false, }) const tmpRoute = cloneDeep(route) tmpRoute.path = childrenBaseUrl if (!tmpRoute.meta) { tmpRoute.meta = {} } tmpRoute.meta.auth = childrenBaseAuth tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb delete tmpRoute.children res.push(tmpRoute) const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl, childrenBaseAuth) childrenRoutes.forEach((item) => { // 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复 if (res.some(v => v.path === item.path)) { res.forEach((v, i) => { if (v.path === item.path) { res[i] = item } }) } else { res.push(item) } }) } else { const tmpRoute = cloneDeep(route) tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path) // 处理面包屑导航 const tmpBreadcrumb = cloneDeep(breadcrumb) tmpBreadcrumb.push({ path: tmpRoute.path, title: tmpRoute.meta?.title, icon: tmpRoute.meta?.icon, hide: !tmpRoute.meta?.breadcrumb && tmpRoute.meta?.breadcrumb === false, }) if (!tmpRoute.meta) { tmpRoute.meta = {} } tmpRoute.meta.auth = !baseAuth || baseAuth === '' || baseAuth?.length === 0 ? tmpRoute.meta?.auth : baseAuth tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb res.push(tmpRoute) } }) return res } // 扁平化路由(将三级及以上路由数据拍平成二级) const flatRoutes = computed(() => { const returnRoutes: RouteRecordRaw[] = [] if (settingsStore.settings.app.routeBaseOn !== 'filesystem') { if (routesRaw.value) { routesRaw.value.forEach((item) => { const tmpRoutes = cloneDeep(item.children) as RouteRecordRaw[] tmpRoutes.map((v) => { if (!v.meta) { v.meta = {} } v.meta.auth = item.meta?.auth ?? v.meta?.auth return v }) returnRoutes.push(...tmpRoutes) }) returnRoutes.forEach(item => flatAsyncRoutes(item)) } } else { returnRoutes.push(...cloneDeep(filesystemRoutesRaw.value) as RouteRecordRaw[]) } return returnRoutes }) const flatSystemRoutes = computed(() => { const routes = [...systemRoutes] routes.forEach(item => flatAsyncRoutes(item)) return routes }) // TODO 将设置 meta.sidebar 的属性转换成 meta.menu ,过渡处理,未来将被弃用 let isUsedDeprecatedAttribute = false function converDeprecatedAttribute(routes: T): T { routes.forEach((route) => { route.children = converDeprecatedAttributeRecursive(route.children) }) if (isUsedDeprecatedAttribute) { // turbo-console-disable-next-line console.warn('[Fantastic-admin] 路由配置中的 "sidebar" 属性即将被弃用, 请尽快替换为 "menu" 属性') } return routes } function converDeprecatedAttributeRecursive(routes: RouteRecordRaw[]) { if (routes) { routes.forEach((route) => { if (typeof route.meta?.sidebar === 'boolean') { isUsedDeprecatedAttribute = true route.meta.menu = route.meta.sidebar delete route.meta.sidebar } if (route.children) { converDeprecatedAttributeRecursive(route.children) } }) } return routes } // 生成路由(前端生成) function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) { // 设置 routes 数据 routesRaw.value = converDeprecatedAttribute(cloneDeep(asyncRoutes) as any) isGenerate.value = true } // 格式化后端路由数据 function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/*.vue')): Route.recordMainRaw[] { return routes.map((route: any) => { switch (route.component) { case 'Layout': route.component = () => import('@/layouts/index.vue') break default: if (route.component) { route.component = views[`../../views/${route.component}`] } else { delete route.component } } if (route.children) { route.children = formatBackRoutes(route.children, views) } return route }) } // 生成路由(后端获取) async function generateRoutesAtBack() { await apiApp.routeList().then((res) => { // 设置 routes 数据 routesRaw.value = converDeprecatedAttribute(formatBackRoutes(res.data) as any) isGenerate.value = true }).catch(() => {}) } // 生成路由(文件系统生成) function generateRoutesAtFilesystem(asyncRoutes: RouteRecordRaw[]) { // 设置 routes 数据 filesystemRoutesRaw.value = cloneDeep(asyncRoutes) as any isGenerate.value = true } // 记录 accessRoutes 路由,用于登出时删除路由 function setCurrentRemoveRoutes(routes: (() => void)[]) { currentRemoveRoutes.value = routes } // 清空动态路由 function removeRoutes() { isGenerate.value = false routesRaw.value = [] filesystemRoutesRaw.value = [] currentRemoveRoutes.value.forEach((removeRoute) => { removeRoute() }) currentRemoveRoutes.value = [] } return { isGenerate, routesRaw, currentRemoveRoutes, flatRoutes, flatSystemRoutes, generateRoutesAtFront, generateRoutesAtBack, generateRoutesAtFilesystem, setCurrentRemoveRoutes, removeRoutes, } }, ) export default useRouteStore