123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- 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
|