目前我做的是造(抄)一个vue的后台管理 demo, 登录、退出、左侧菜单导航、动态路由这些基本都搞定了. 问题如下:
我在router/index
中定义的静态路由和我用router.addRoutes
添加的动态路由到底有啥区别呢?
动态路由的思路大约如下:
1: 从后台拿到菜单, 这个菜单已经是后台构造好的树结构
2: 在vuex中递归这个菜单树, 顶级菜单就把component置为 Layout , 其他子路由就动态import
3: 在路由守卫中把路由放进去
package.json
{
"name": "bootsite-ui",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"dev": "vue-cli-service serve --mode dev",
"fat": "vue-cli-service serve --mode fat",
"local": "vue-cli-service serve --mode local",
"prod": "vue-cli-service serve --mode prod"
},
"dependencies": {
"axios": "^0.27.2",
"core-js": "^3.6.5",
"element-ui": "^2.4.5",
"js-cookie": "^3.0.1",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"vue": "^2.6.11",
"vue-fragment": "^1.5.2",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.13",
"@vue/cli-plugin-eslint": "~4.5.13",
"@vue/cli-plugin-router": "~4.5.13",
"@vue/cli-plugin-vuex": "~4.5.13",
"@vue/cli-service": "~4.5.13",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"vue-cli-plugin-element": "~1.0.1",
"vue-template-compiler": "^2.6.11"
}
}
路由vuex代码如下
import ApiAuth from '@/api/ApiAuth'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { constantRoutes } from '@/router'
import Layout from '@/layout'
import SystemUserIndex from '@/views/system/user'
const asyncRouteMap = new Map()
asyncRouteMap.set('SystemUserIndex', SystemUserIndex)
/**
* 构造路由名称
* @param {string} component 组件地址
* @returns 路由名称
*/
function buildRouteName (component) {
if (typeof component !== 'string' || component.length === 0) {
return []
}
const arr = component.split('/')
if (component.startsWith('/')) {
arr.shift()
}
if (arr.length === 0) {
return arr
}
let routeName = ''
arr.forEach(e => {
routeName = routeName + e.slice(0, 1).toUpperCase() + e.slice(1)
})
return routeName
}
/**
* 构造动态路由
* @param {*} routes 动态路由数组
* @param {*} menus 用户菜单
*/
function filterAsyncRoutes (routes, menus) {
menus.forEach(menu => {
const route = {
name: buildRouteName(menu.component),
path: menu.path,
// component: (resolve) => require([`@/views${menu.component}`], resolve),
component: () => import('@/views' + menu.component),
meta: {
icon: menu.icon,
title: menu.name
}
}
if (asyncRouteMap.has(route.name)) {
route.component = asyncRouteMap.get(route.name)
}
if (menu.children) {
route.children = []
filterAsyncRoutes(route.children, menu.children)
}
routes.push(route)
})
}
const auth = {
state: {
token: getToken(),
name: '',
avatar: '',
roles: [],
permissions: [],
menus: [],
addRoutes: [],
routes: []
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
},
SET_MENUS: (state, menus) => {
state.menus = menus
},
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
},
actions: {
// 登录
Login ({ commit }, data) {},
// 获取用户信息
GetInfo ({ commit, state }) {},
// 退出
LogOut ({ commit, state }) {},
// 生成动态路由
GenerateRoutes ({ commit, state }) {
return new Promise((resolve, reject) => {
ApiAuth.getMenuTree().then(res => {
commit('SET_MENUS', res.body)
const accessedRoutes = []
state.menus.forEach(e => {
e.parentId = 0
})
filterAsyncRoutes(accessedRoutes, state.menus)
commit('SET_ROUTES', accessedRoutes)
accessedRoutes.forEach(e => {
e.component = Layout
})
accessedRoutes.push({
path: '/*',
redirect: '/404'
})
resolve(accessedRoutes)
}).catch(error => {
reject(error)
})
})
}
}
}
export default auth
路由守卫
import router from './router'
import { Message } from 'element-ui'
import store from './store'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
NProgress.configure({ showSpinner: false })
// 前端路由白名单
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
NProgress.start()
/* 已登录 */
if (getToken()) {
// TODO 设置标题
// 已登录过不需要再访问登录页
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
// 如果用户信息为空, 则获取用户信息
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(() => {
// 获取用户路由列表
store.dispatch('GenerateRoutes').then(accessRoutes => {
router.addRoutes(accessRoutes)
router.options.routes.push(...accessRoutes)
console.log(JSON.stringify(router.options.routes))
next({ ...to, replace: true })
})
}).catch(error => {
console.log(error)
store.dispatch('LogOut').then(() => {
Message.error(error)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
/* 没有登录过 */
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
其中一级路由的component是 Layout
,二级路由中 /system/user
使用的是import的组件, 其他的二级路由都是动态的import
第一个是路由数据的区别
我打印了路由之后发现有如下两个区别
第二个算是 vue 开发者工具的问题和第一个问题类似
为什么在 vue 开发者工具里面的vuex, 提前import的可以显示路径, 动态import的就不行
最后还有几个小问题
- 之前动态import一直在报错:
vue Cannot read property 'range' of null
, 然后降级babel-eslint
好像也没什么用, 重新下载依赖也没用, 今天又重新下载之后又好了. 如果确实是某些依赖没有下载完整或者某些依赖版本不正确, 那有没有比较好的办法解决而不是看运气或者直接不用动态import - vue 开发者工具中的路由的 options 是干啥的, 为啥一级路由有, 其他的子路由没有
- 三级路由激活的时候, vue 开发者工具中的路由怎么没有 active 了
以上