import router, { constantRoutes } from '@/router' import { getRouters } from '@/api/menu' import Layout from '@/layout/index' import ParentView from '@/components/ParentView' import auth from '@/utils/auth' import { getFrontInitialRouters } from '@/api/system/user' const permission = { state: { // 路由缓存 routeCache: {}, // 全部路由 = 本地路由 + 后台路由 routes: [], // 后台路由 addRoutes: [], // 显示在最顶部的路由 = 后台路由中的一级路由 topbarRouters: [], // 中部路由 = topRouters中选中的一级路由的二级子路由 middlebarRouters: [], // 边部路由 = middleRouters中选中的二级路由的三级子路由 sidebarRouters: [], // 当前路由 currentRoute: null, activeTopPath: null, activeMiddlePath: null, activeSidePath: null, // 宽屏 isWideScreen: false }, mutations: { REMOVE_ROUTES: (state, token) => { state.routes = [] state.addRoutes = [] state.routeCache = {} // 全部清除 }, SET_ROUTES: (state, { routes, token }) => { state.addRoutes = routes state.routes = constantRoutes.concat(routes) state.routeCache[token] = routes }, SET_TOPBAR_ROUTERS: (state, routes) => { state.topbarRouters = routes }, SET_MIDDLEBAR_ROUTERS: (state, routes) => { state.middlebarRouters = routes }, SET_SIDEBAR_ROUTERS: (state, routes) => { state.sidebarRouters = routes }, SET_IS_WIDESCREEN: (state, trueOrFalse) => { state.isWideScreen = trueOrFalse }, SET_CURRENT_ROUTE: (state, route) => { state.currentRoute = route }, SET_ACTIVE_TOP_PATH: (state, path) => { state.activeTopPath = path }, SET_ACTIVE_MIDDLE_PATH: (state, path) => { state.activeMiddlePath = path }, SET_ACTIVE_SIDE_PATH: (state, path) => { state.activeSidePath = path } }, actions: { // 生成路由 CheckRoutes({ commit, state }, force) { const token = auth.getToken() || '' const cache = state.routeCache[token] let routeAdded = false if (cache && !force) return Promise.resolve(routeAdded) // 缓存不需要返回,已经在外部被调用过store.addRoutes return new Promise(resolve => { // 向后端请求路由数据 const routeApi = token ? getRouters : getFrontInitialRouters routeApi().then(res => { const global404 = { path: '*', redirect: '/404', hidden: true } const topRoutes = res.data handleAsyncRouters(topRoutes) const addRoutes = topRoutes.concat([global404]) // 目前只能确定全局路由与top路由,middle/side需要等路由跳转 commit('SET_ROUTES', { routes: addRoutes, token }) commit('SET_TOPBAR_ROUTERS', topRoutes) router.addRoutes(addRoutes) routeAdded = !!addRoutes.length resolve(routeAdded) }) }) }, SetLocation({ commit, state }, { to, from }) { // 定位路由,显示合适的菜单 // console.log('receive set-location request', to, from) commit('SET_CURRENT_ROUTE', to) commit('SET_IS_WIDESCREEN', to.meta?.isWideScreen) // 现在开始设置top/middle/side三级的当前路由 // 有两项任务,1是要设置菜单集合,2是设置当前菜单 const topRoutes = state.topbarRouters const ancestors = { fullMatched: false, matches: [] } let targetPath = to.path traceAncestorRoutesInMenus(topRoutes, targetPath, ancestors) if (!ancestors.fullMatched && to.meta?.parentPath) { targetPath = to.meta.parentPath ancestors.matches.length = 0 // clear for re-trace traceAncestorRoutesInMenus(topRoutes, targetPath, ancestors) } // 完全匹配,开始操作菜单 if (ancestors.fullMatched) { const activeCommits = [ { name: 'SET_ACTIVE_TOP_PATH', getPath: (routes, idx) => routes[idx]?.path || '' }, { name: 'SET_ACTIVE_MIDDLE_PATH', getPath: (routes, idx) => routes[idx]?.path || '' }, { name: 'SET_ACTIVE_SIDE_PATH', getPath: (routes, idx) => (routes[idx] && routes.last())?.path || '' }] // 设置高亮菜单 activeCommits.forEach((config, idx) => { commit(config.name, config.getPath(ancestors.matches, idx)) }) // 设置middle/side菜单集合 const topMatch = ancestors.matches[0] if (topMatch) { commit('SET_MIDDLEBAR_ROUTERS', topMatch.children || []) commit('SET_SIDEBAR_ROUTERS', []) // 先清空,可能立即又会被后续代码赋值 } else { commit('SET_MIDDLEBAR_ROUTERS', []) commit('SET_SIDEBAR_ROUTERS', []) } const middleMatch = ancestors.matches[1] if (middleMatch && ancestors.matches.length > 2 // NOTE: 有部分路由强制只匹配至2级菜单,如报告想全屏展示 ) { commit('SET_SIDEBAR_ROUTERS', middleMatch.children || []) } } }, RemoveRoutes({ commit }) { commit('REMOVE_ROUTES') }, AccessDeepMenu({ commit }, route) { const path = route.meta?.firstLeafPath || route.path router.push(path) } } } function handleAsyncRouters(routes = [], parent = null, level = 0) { // NOTE: hht 22.8.29 解析思路,因为系统菜单全由后台配置,本地静态路由多是子页面等 // 1 asyncRoutes尽可能提供多的便利强引用,以用来在匹配到路由之后,快速锁定top/middle/side三级菜单 // 2 constantRoutes可以手动去挂载一个弱关联,表示它与后台配置的某个菜单是有关联的,这样在跳转后能正确定位菜单 // 其中,1是必要处理步骤;2是充要处理步骤,没有关联即不更改菜单定位(效果不会比现在更差) routes.forEach(route => { // 递归处理所有的path, 保证每级路由的path都是绝对地址,方便跳转操作与路由path匹配 handleAsyncRouterPath(route, parent) // 递归处理所有的redirect, 让每个非叶子结点路由能redirect到叶子结点 handleAsyncRouterRedirect(route) // 递归处理所有的组件属性,从字符串转化为VUE组件 handleAsyncRouterComponent(route) // 递归挂载本地强引用 handleAsyncRouterLocalReference(route, parent, level) }) } function handleAsyncRouterPath(route, parent) { if (!route.path) { console.log('handleAsyncRouterPath - route must have `path` value', route.name) return } if (!route.path.startsWith('/')) { route.path = (parent?.path || '') + '/' + route.path } if (route.children?.length) { route.children.forEach(child => handleAsyncRouterPath(child, route)) } else { delete route.children delete route.redirect } } function handleAsyncRouterRedirect(route) { // 22.8.30 本来准备利用redirect属性来自动跳转,但后台路由可能会和本地路由重合 // 比如`/user`, 后台路由会配置它跳'/user/info',再跳`/user/info/userinfo` // 但因为本地也配置了`/user`,本地静态路由优先级更高,它是没有配置redirect的,结果就是会404 // 所以这里直接挂了`_firstLeafPath`, 提供了AccessDeepMenu方法来做跳转 // 挂载两个后代path, 方便定制菜单 const getFirstLeafPath = (routes, deep) => { const firstChild = routes?.first(c => !c.hidden) if (!firstChild) return '' if (!deep) return firstChild.path if (!firstChild.children?.length) return firstChild.path return getFirstLeafPath(firstChild.children, deep) } // 属性挂在meta上可以不受匹配方法等因素的影响,最大限度保留设置 route.meta = route.meta || {} route.meta.firstChildPath = getFirstLeafPath(route.children, false) route.meta.firstLeafPath = getFirstLeafPath(route.children, true) route.children?.forEach(child => handleAsyncRouterRedirect(child)) } function handleAsyncRouterComponent(route) { if (typeof route.component === 'string') { const sharedComponents = { Layout, ParentView } const sharedComponent = sharedComponents[route.component] route.component = sharedComponent || loadView(route.component) } route.children?.forEach(child => handleAsyncRouterComponent(child)) } function handleAsyncRouterLocalReference(route, parent, level) { // 属性挂在meta上可以不受匹配方法等因素的影响,最大限度保留设置 route.meta = route.meta || {} route.name = route.meta.routeName || route.name // overwrite route name by server meta config // 挂parent可逆向追溯, 不能挂反向引用vuex内部会深拷贝! // route.parent = parent route.meta.parentPath = parent?.path || '' // 挂level可直接定位top/middle/side route.meta.level = level route.children?.forEach(child => handleAsyncRouterLocalReference(child, route, level + 1)) } function traceAncestorRoutesInMenus(routes, path, ancestors) { // 从routes中找到目标路径,才算完全成功 const fullMath = routes.first(r => r.path == path) if (fullMath) { ancestors.fullMatched = true ancestors.matches.push(fullMath) return } const partialMath = routes.first(r => path.startsWith(r.path)) if (partialMath) { ancestors.fullMatched = false ancestors.matches.push(partialMath) // 继续往下找 if (partialMath.children?.length) { traceAncestorRoutesInMenus(partialMath.children, path, ancestors) } } } export const loadView = (view) => { // 路由懒加载 return (resolve) => require([`@/views/${view}`], resolve) } export default permission