import auth from '@/plugins/auth' import router, { constantRoutes, dynamicRoutes } from '@/router' import { getRouters } from '@/api/menu' import Layout from '@/layout/index' import ParentView from '@/components/ParentView' import InnerLink from '@/layout/components/InnerLink' // 匹配views里面所有的.vue文件 const modules = import.meta.glob('./../../views/**/*.vue') const usePermissionStore = defineStore( 'permission', { state: () => ({ routes: [], addRoutes: [], defaultRoutes: [], topbarRouters: [], sidebarRouters: [] }), actions: { setRoutes(routes) { this.addRoutes = routes this.routes = constantRoutes.concat(routes) }, setDefaultRoutes(routes) { this.defaultRoutes = constantRoutes.concat(routes) }, setTopbarRoutes(routes) { this.topbarRouters = routes }, setSidebarRouters(routes) { this.sidebarRouters = routes }, generateRoutes(roles) { return new Promise(resolve => { // 向后端请求路由数据 getRouters().then(res => { // 三份数据各自深拷贝后分别构建,normalizeRouteNames 是确定性的, // 因此三处得到的 name 完全一致,页签缓存不会对不上。 const sdata = normalizeRouteNames(JSON.parse(JSON.stringify(res.data))) const rdata = normalizeRouteNames(JSON.parse(JSON.stringify(res.data))) const defaultData = normalizeRouteNames(JSON.parse(JSON.stringify(res.data))) const sidebarRoutes = filterAsyncRouter(sdata) const rewriteRoutes = filterAsyncRouter(rdata, false, true) const defaultRoutes = filterAsyncRouter(defaultData) const asyncRoutes = filterDynamicRoutes(dynamicRoutes) asyncRoutes.forEach(route => { router.addRoute(route) }) this.setRoutes(rewriteRoutes) this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)) this.setDefaultRoutes(sidebarRoutes) this.setTopbarRoutes(defaultRoutes) resolve(rewriteRoutes) }) }) } } }) const isContainerRoute = (route) => route.component === 'Layout' || route.component === 'ParentView' || route.component === 'InnerLink' /** * 修正后端菜单里的路由重名。 * * 两种情况 vue-router 4 会因重名丢路由或直接报错: * 1. 父容器与后代页面同名(如 /standardMaterial 容器与其下的标准物质清单页都叫 * StandardMaterial)——vue-router 3 允许,vue-router 4 抛 * "A route named X has been added as a child of a route with the same name"; * 2. 两个不同菜单项取了同一个 name(如两个「报告结果」都叫 ReportResults)—— * addRoute 时后者会把前者挤掉,被挤掉的那条路径就点不开了。 * * 处理策略:先保留第一次出现的 name,后续重名一律加数字后缀。容器与后代叶子重名 * 时优先改容器名(页面 name 与组件 name 一致,是页签缓存的键,尽量不动)。 */ function normalizeRouteNames(routes) { const leafNames = new Set() const collectLeafNames = (list) => { list.forEach(route => { if (route.name && !isContainerRoute(route)) leafNames.add(route.name) if (route.children && route.children.length) collectLeafNames(route.children) }) } collectLeafNames(routes) const used = new Set() const walk = (list) => { list.forEach(route => { if (route.name) { const conflict = used.has(route.name) || (isContainerRoute(route) && leafNames.has(route.name)) if (conflict) { let suffix = 2 while (leafNames.has(`${route.name}${suffix}`) || used.has(`${route.name}${suffix}`)) suffix++ route.name = `${route.name}${suffix}` } used.add(route.name) } if (route.children && route.children.length) walk(route.children) }) } walk(routes) return routes } // 遍历后台传来的路由字符串,转换为组件对象 function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { return asyncRouterMap.filter(route => { if (type && route.children) { route.children = filterChildren(route.children) } if (route.component) { // Layout ParentView 组件特殊处理 if (route.component === 'Layout') { route.component = Layout } else if (route.component === 'ParentView') { route.component = ParentView } else if (route.component === 'InnerLink') { route.component = InnerLink } else { route.component = loadView(route.component) } } if (route.children != null && route.children && route.children.length) { route.children = filterAsyncRouter(route.children, route, type) } else { delete route['children'] delete route['redirect'] } return true }) } function filterChildren(childrenMap, lastRouter = false) { var children = [] childrenMap.forEach(el => { el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path if (el.children && el.children.length && el.component === 'ParentView') { children = children.concat(filterChildren(el.children, el)) } else { children.push(el) } }) return children } // 动态路由遍历,验证是否具备权限 export function filterDynamicRoutes(routes) { const res = [] routes.forEach(route => { if (route.permissions) { if (auth.hasPermiOr(route.permissions)) { res.push(route) } } else if (route.roles) { if (auth.hasRoleOr(route.roles)) { res.push(route) } } }) return res } export const loadView = (view) => { let res for (const path in modules) { const dir = path.split('views/')[1].split('.vue')[0] if (dir === view || dir === `${view}/index`) { res = () => modules[path]() } } return res } export default usePermissionStore