element admin
1.安装依赖
package.json
“tui-editor”: “1.3.3”, “core-js”: “3.6.5”,删掉
把\src\views\components-demo\markdown.vue里面的markdown-editor组件还有引用全都注释掉
2.从入口开始
src/main.js
// 引入vue
import Vue from 'vue'
// 引入js-cookie
// 是用来操作cookie的,后端返回的token就存储在cookie中
// 之前我们是存储在loccalstroage中
import Cookies from 'js-cookie'
// 引入重置样式
import 'normalize.css/normalize.css' // a modern alternative to CSS resets
// Element是PC端的UI组件库
import Element from 'element-ui'
import './styles/element-variables.scss'
// 引入国际包 国际化
// import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖
// 引入自已写的CSS全局样式
import '@/styles/index.scss' // global css
// 引入APP组件
import App from './App'
// 引入仓库
import store from './store'
// 引入路由
import router from './router'
// 引入自己封装的icon图标
import './icons' // icon
// 引入权限模块,非常重要,面试必问
import './permission' // permission control
import './utils/error-log' // error log
// 引入过滤器
import * as filters from './filters' // global filters
/**
* If you don't want to use mock-server
* you want to use MockJs for mock api
* you can execute: mockXHR()
*
* Currently MockJs will be used in the production environment,
* please remove it before going online ! ! !
*/
// process是node中的全局变量
// 当通过npm run build时,node_env就是production
// 当通过npm run dev时,node_env就是development
// process.env.NODE_ENV === 'production' 表示是生成环境
if (process.env.NODE_ENV === 'production') {
// 导入mock模块,模拟接口中的,本项目中的接口,都是模拟的,都是假的
const { mockXHR } = require('../mock')
mockXHR()
}
Vue.use(Element, {
// Button 可以设置 如果不设置默认是medium
size: Cookies.get('size') || 'medium', // set element-ui default size
// locale: enLang 表示组件使用英文
// locale: enLang // 如果使用中文,无需设置,请删除
})
// 注册全局过滤器
// register global utility filters
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
// 关闭生成环境下,vue提示
Vue.config.productionTip = false
// new一个Vue实例
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
3.路由模块
src/router/index.js
// 引入vue
import Vue from 'vue'
// 引入vue-router
import Router from 'vue-router'
// 路由就是一个插件,需要use
Vue.use(Router)
// 引入layout组件
// layout组件非常重要
// 一级路由出口中,匹配layout组件
import Layout from '@/layout'
// 引入其它四个路由模块
import componentsRouter from './modules/components'
import chartsRouter from './modules/charts'
import tableRouter from './modules/table'
import nestedRouter from './modules/nested'
/**
* Note: sub-menu only appear when route children.length >= 1
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
*
* hidden: true if set true, item will not show in the sidebar(default is false)
* alwaysShow: true if set true, will always show the root menu
* if not set alwaysShow, when item has more than one children route,
* it will becomes nested mode, otherwise not show the root menu
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
* name:'router-name' the name is used by <keep-alive> (must set!!!)
* meta : {
roles: ['admin','editor'] control the page roles (you can set multiple roles)
title: 'title' the name show in sidebar and breadcrumb (recommend set)
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
noCache: true if set true, the page will no be cached(default is false)
affix: true if set true, the tag will affix in the tags-view
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
}
*/
/**
* constantRoutes
* a base page that does not have permission requirements
* all roles can be accessed
*/
// constantRoutes是静态路由
// 本项目,路由分成了两大模块:静态路由 和 动态路由
// 静态路由:所有的用户可以访问,不需要权限
// 动态路由:需要权限,如果有权限,就可以访问,如果没有权限,就不能访问
// 路则规则:就是一个对象
// path: '/redirect', 访问的url
// component: Layout, 访问出口中放什么组件 在一级中币出口中放 Layout 组件
// hidden: true, 隐藏 把侧边栏中不能看到声明式导航
// children: [ 配置二级路由
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
// 在二级路由出口中放@/views/redirect/index组件
component: () => import('@/views/redirect/index')
}
]
},
{
// 当访问 /login时, 在一级路由出口放登录页面 一级路由出口是在APP组件中
path: '/login',
component: () => import('@/views/login/index'),
// 把侧边栏中不能看到声明式导航
hidden: true
},
{
path: '/auth-redirect',
component: () => import('@/views/login/auth-redirect'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error-page/401'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
}
]
},
{
path: '/documentation',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/documentation/index'),
name: 'Documentation',
meta: { title: 'Documentation', icon: 'documentation', affix: true }
}
]
},
{
path: '/guide',
component: Layout,
redirect: '/guide/index',
children: [
{
path: 'index',
component: () => import('@/views/guide/index'),
name: 'Guide',
meta: { title: 'Guide', icon: 'guide', noCache: true }
}
]
},
{
path: '/profile',
component: Layout,
redirect: '/profile/index',
hidden: true,
children: [
{
path: 'index',
component: () => import('@/views/profile/index'),
name: 'Profile',
meta: { title: 'Profile', icon: 'user', noCache: true }
}
]
}
]
/**
* asyncRoutes
* the routes that need to be dynamically loaded based on user roles
*/
// asyncRoutes是动态路由
// 页面级(路由级)权限:
// 不同用户,登录到系统,看到的侧边栏是不一样,也就是有不同的页面
// 同一个页面,有的用户可以访问,有的用户不能访问
// 并不是说,你在下面配置完就OK,背后还有很多代码
export const asyncRoutes = [
{
path: '/permission',
component: Layout,
redirect: '/permission/page',
alwaysShow: true, // will always show the root menu
name: 'Permission',
meta: {
title: 'Permission',
icon: 'lock',
// roles表示什么样的用户可以访问permission
// 不同的用户有不同的角色
// 本项目就两个角色:admin editor
// roles: ['admin', 'editor'] 表示amdin可以访问persmisson editor也可以访问persmisson
roles: ['admin', 'editor'] // you can set roles in root nav
// roles: ['admin'] 表示只能admin用户可以访问persmisson
// roles: ['admin'] // you can set roles in root nav
},
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
roles: ['admin'] // or you can only set roles in sub nav
}
},
{
path: 'directive',
component: () => import('@/views/permission/directive'),
name: 'DirectivePermission',
meta: {
title: 'Directive Permission'
// 没有roles,表示此路由规则,不需要校验
// if do not set roles, means: this page does not require permission
}
},
{
path: 'role',
component: () => import('@/views/permission/role'),
name: 'RolePermission',
meta: {
title: 'Role Permission',
roles: ['admin']
}
}
]
},
{
path: '/icon',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/icons/index'),
name: 'Icons',
meta: { title: 'Icons', icon: 'icon', noCache: true }
}
]
},
/** when your routing map is too long, you can split it into small modules **/
componentsRouter,
chartsRouter,
nestedRouter,
tableRouter,
{
path: '/example',
component: Layout,
redirect: '/example/list',
name: 'Example',
meta: {
title: 'Example',
icon: 'el-icon-s-help'
},
children: [
{
path: 'create',
component: () => import('@/views/example/create'),
name: 'CreateArticle',
meta: { title: 'Create Article', icon: 'edit' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/example/edit'),
name: 'EditArticle',
meta: { title: 'Edit Article', noCache: true, activeMenu: '/example/list' },
hidden: true
},
{
path: 'list',
component: () => import('@/views/example/list'),
name: 'ArticleList',
meta: { title: 'Article List', icon: 'list' }
}
]
},
{
path: '/tab',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/tab/index'),
name: 'Tab',
meta: { title: 'Tab', icon: 'tab' }
}
]
},
{
path: '/error',
component: Layout,
redirect: 'noRedirect',
name: 'ErrorPages',
meta: {
title: 'Error Pages',
icon: '404'
},
children: [
{
path: '401',
component: () => import('@/views/error-page/401'),
name: 'Page401',
meta: { title: '401', noCache: true }
},
{
path: '404',
component: () => import('@/views/error-page/404'),
name: 'Page404',
meta: { title: '404', noCache: true }
}
]
},
{
path: '/error-log',
component: Layout,
children: [
{
path: 'log',
component: () => import('@/views/error-log/index'),
name: 'ErrorLog',
meta: { title: 'Error Log', icon: 'bug' }
}
]
},
{
path: '/excel',
component: Layout,
redirect: '/excel/export-excel',
name: 'Excel',
meta: {
title: 'Excel',
icon: 'excel'
},
children: [
{
path: 'export-excel',
component: () => import('@/views/excel/export-excel'),
name: 'ExportExcel',
meta: { title: 'Export Excel' }
},
{
path: 'export-selected-excel',
component: () => import('@/views/excel/select-excel'),
name: 'SelectExcel',
meta: { title: 'Export Selected' }
},
{
path: 'export-merge-header',
component: () => import('@/views/excel/merge-header'),
name: 'MergeHeader',
meta: { title: 'Merge Header' }
},
{
path: 'upload-excel',
component: () => import('@/views/excel/upload-excel'),
name: 'UploadExcel',
meta: { title: 'Upload Excel' }
}
]
},
{
path: '/zip',
component: Layout,
redirect: '/zip/download',
alwaysShow: true,
name: 'Zip',
meta: { title: 'Zip', icon: 'zip' },
children: [
{
path: 'download',
component: () => import('@/views/zip/index'),
name: 'ExportZip',
meta: { title: 'Export Zip' }
}
]
},
{
path: '/pdf',
component: Layout,
redirect: '/pdf/index',
children: [
{
path: 'index',
component: () => import('@/views/pdf/index'),
name: 'PDF',
meta: { title: 'PDF', icon: 'pdf' }
}
]
},
{
path: '/pdf/download',
component: () => import('@/views/pdf/download'),
hidden: true
},
{
path: '/theme',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/theme/index'),
name: 'Theme',
meta: { title: 'Theme', icon: 'theme' }
}
]
},
{
path: '/clipboard',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/clipboard/index'),
name: 'ClipboardDemo',
meta: { title: 'Clipboard', icon: 'clipboard' }
}
]
},
{
path: 'external-link',
component: Layout,
children: [
{
path: 'https://github.com/PanJiaChen/vue-element-admin',
meta: { title: 'External Link', icon: 'link' }
}
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
4.permission模块
src/permission.js
// 引入路由器
import router from './router'
// 引入仓库
import store from './store'
// 引入elementui中的提示性组件
import { Message } from 'element-ui'
// 引入nprogress 进度条
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
// getToken就是从cookie中获取token
// 如果登录了,是有token,如果没有登录,就没有token
import { getToken } from '@/utils/auth' // get token from cookie
// 得到页面中的title
import getPageTitle from '@/utils/get-page-title'
// 配置NProgress
NProgress.configure({ showSpinner: false }) // NProgress Configuration
// whiteList是白名单 不需要登录,就可以直接访问
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
// 全局前置路由守卫
// 核心代码都是在守卫中
router.beforeEach(async(to, from, next) => {
// 开启进度条
NProgress.start()
// 给页面的title赋值
document.title = getPageTitle(to.meta.title)
// 从cookie中获取token 如果有token表示登录了
const hasToken = getToken()
if (hasToken) {
// 有token,说明已经登录了
if (to.path === '/login') {
// 已经登录了,你又去登录,放行到后面首页
next({ path: '/' })
// 关闭进度条
NProgress.done()
} else {
// 已经登录了,去其它页面
// store.getters.roles得到vuex中的角色
// 如果登录了,我们会调用一个接口,去拿用户信息,在用户信息中,有当前用户的角色
// 点击登录,先发一个登录请求,服务器响应一个token,前端把token存储到cookie
// 紧接着发第二个请求,是用来获取用户信息的,前端把用户信息存储到了vuex中,用户信息中有一个角色
// 也就是说,在vuex中是可以获取角色的 通过store.getters.roles
// store.getters.roles.length > 0 表示vuex是有角色
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
// 从vuex中获取角色,直接放行
next()
} else {
// else表示vuex中没有角色 当你又去刷新浏览器时,vuex中就没有角色,vuex中的数据也是存储在内存
try {
// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
// store.dispatch('user/getInfo') 重新获取用户信息 肯定是在vuex中发送ajax请求
// roles 表示用户信息,用户信息中包含用户角色
const { roles } = await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
// dispatch('permission/generateRoutes', roles) 根据用户角色,生成路由规则
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// dynamically add accessible routes
// 一个路由器中,可以有很多的规则,计算了当前用户角色有18个规则
// 利用addRoutes,把这18个规则,添加到路由器
router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
// 上面已经把规则添加到路由器中,放行,此时,你就可以看到,你有权限看到的页面了
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
// 如果在生成规则时,出问题了
// store.dispatch('user/resetToken') 清除token
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
// 重新回到登录页面
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
// 没有token
// 没有token看一下,你访问的路径有没有在白名单中
if (whiteList.indexOf(to.path) !== -1) {
// 如果在白名单中,就放行
next()
} else {
// 如果没有在白名单中,表示你访问的路由规则,需要登录
// 需要登录,放行到登录页面
next(`/login?redirect=${to.path}`)
// 关闭进度条
NProgress.done()
}
}
})
// 全局后置路由守卫
router.afterEach(() => {
// 关闭进度条
NProgress.done()
})
5.状态管理
src/store/modules/user.js
import {
login,
logout,
getInfo
} from '@/api/user'
import {
getToken,
setToken,
removeToken
} from '@/utils/auth'
import router, {
resetRouter
} from '@/router'
const state = {
token: getToken(), // token
name: '', // 用户名
avatar: '', // 用户头像
introduction: '', // 用户简介
roles: [] // 当前用户所具有的角色
}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_INTRODUCTION: (state, introduction) => {
state.introduction = introduction
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
}
const actions = {
// 用户登录
login({
commit
}, userInfo) {
// 组件中派发action
const {
username,
password
} = userInfo
return new Promise((resolve, reject) => {
// login是接口
login({
username: username.trim(),
password: password
}).then(response => {
const {
data
} = response
// commit('SET_TOKEN', data.token) commit一个mutaion,给token赋值
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
getInfo({
commit,
state
}) {
return new Promise((resolve, reject) => {
// getInfo 也是接口
getInfo(state.token).then(response => {
const {
data
} = response
if (!data) {
reject('Verification failed, please Login again.')
}
const {
roles,
name,
avatar,
introduction
} = data
// roles must be a non-empty array
if (!roles || roles.length <= 0) {
reject('getInfo: roles must be a non-null array!')
}
// 把用户信息存储到vuex
commit('SET_ROLES', roles)
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_INTRODUCTION', introduction)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// 用户退出登录
logout({
commit,
state,
dispatch
}) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resetRouter()
// reset visited views and cached views
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
dispatch('tagsView/delAllViews', null, {
root: true
})
resolve()
}).catch(error => {
reject(error)
})
})
},
// 清除token
resetToken({
commit
}) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
})
},
// 动态修改角色
async changeRoles({
commit,
dispatch
}, role) {
const token = role + '-token'
commit('SET_TOKEN', token)
setToken(token)
const {
roles
} = await dispatch('getInfo')
resetRouter()
// generate accessible routes map based on roles
const accessRoutes = await dispatch('permission/generateRoutes', roles, {
root: true
})
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// reset visited views and cached views
dispatch('tagsView/delAllViews', null, {
root: true
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
src/store/modules/permission.js
import {
asyncRoutes,
constantRoutes
} from '@/router'
// 使用roles:Array<string> 和 route:{path,component,meta:{roles:[]}}
// 判断当前用户能不能访问当前路由规则,返回布尔值。
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
// 如果路由规则上有meta,并且meta上有roles这个自定义字段,说明这条路由是有权限的,要进行过滤权限。
return roles.some(role => route.meta.roles.includes(role))
} else {
// 如果路由规则上没有meta这个属性,或者meta上没有roles这个自定义字段,任何用户都可以访问。
return true
}
}
// 使用roles:Array<string>, 和 routes:Array<route> routes是所有的动态路由,roles是当前的角色
// 生成当前用户可访问的路由规则。
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
// 深复制(深拷贝)仅对那些比较简单对象进行深拷贝
const tmp = {
...route
}
// hasPermission(['admin', 'editor'], {path,component,meta:{roles:['editor']}}): boolean
if (hasPermission(roles, tmp)) {
// 有没有嵌套视图,如果进行递归
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [], // 静态路由规则 + 当前用户可访问的动态路由规则
addRoutes: [] // 只是当前用户可访问的动态路由规则
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
// 用于生成当前用户可访问的路由规则
// roles = ['admin', 'editor']
generateRoutes({
commit
}, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
// admin用户,不需要计算可访问的动态路由,把所有的动态路由都赋值给accessedRoutes
// 工作经验:工作中的管理系统,admin一般只用看到功能性页面,不需要看那些业务性页面。
// accessedRoutes = asyncRoutes || []
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
} else {
// 非admin用户,filterAsyncRoutes会计算当前用户可以访问当前用户可以访问那些动态路由,asyncRoutes是所有路由,roles是当前的角色
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
// 把当前用户可访问的路由规则放在vuex中
commit('SET_ROUTES', accessedRoutes)
// 把当前用户可访问的路由规则给到.then()
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
6.前端权限设计总结
不同的公司,权限设计是不一样的,从前后端协作的角度说,权限设计大体上有两种思路,第一种是单纯由前端来实现,第二种由后端来实现。后端实现,我们后面会讲一个案例。由前端来设计管理系统的权限,开发成本相对较低。
中小型公司:前端做权限设计,本开源项目主要前端做的权限设计。
大公司:后端做权限设计,后面再讲一下后端的权限设计。
前端实现思路:前端用token调接口换取当前用户信息,再使用用户的角色信息动态生成有权访问的菜单和路由。核心知识:
- vue-router 中的全局守卫、路由元信息
- router.addRoutes() 动态添加路由匹配规则
- 使用 vuex 管理用户信息、路由菜单信息等
核心逻辑:
- 全局路由守卫,用于拦截用户对系统内部页面的访问
- 当权限路由动态生成后,才能进入系统内部渲染 Menu 菜单
- 在登录流程中,先用 token 调接口获取用户信息,再根据用户信息生成权限路由,并保存在状态管理工具中去,后面就可以正常访问权限路由了。
- 当用户在系统内部刷新页面时,这就是刷新流程。当我们刷新时,Vuex中的用户信息和权限菜单信息会丢失。所以,在刷新流程中,会重新使用 token 换取用户信息,再使用用户角色来生成权限路由。
面试题:你做的管理系统,你是如何处理权限的问题的?
答:我们之前的公司中小公司,做的项目,大概有30个左右的模块,所以权限这一块,就前端去处理的。我们处理的流程大致是这样的:前端登录换取token,在导航守卫中,实现权限设计,首先判断有没有token,没有token,直接跳到登录页面。有token会进一步判断vuex中有没有用户信息。如果没有用户信息,拿着token,调用接口获取用户信息,用户信息中保存了最重要的字段,就是角色,有了角色后,通过算法生成当前用户可访问的动态路由规则(算法大至是使用后端返回的角色和路由元信息中的角色进行对比,得到可以访问的动态路由规则),有了动态访问的路由规则,再通过addRoutes方法,把得到的动态访问的路由规则添加到路由系统。
面试题:前端做权限这一块,你感觉有什么不足?
答:如果要修改权限,必须要去修改前端代码,重新打包,重新上线。前端处理权限,只适合中小项目(模块少,角色少),一般中小公司权限处理都是前端实现。
面试题:管理系统左侧的菜单是什么时候生成的?
答:在登录流程中,登录成功后,得到的token,根据token获取用户信息,用户信息中包含角色,根据角色生成可访问的动态路由规则(accessRoutes),把路由规则,也保存到了Vuex中,跳到系统内部页面,渲染Layout组件,在渲染Layout组件时,会渲染菜单。在刷新流程中,使用token换取用户信息,生成可访问的动态路由规则,保存到Vuex中,再次渲染Layout时,生成左侧菜单。
7.后端权限设计总结
后端处理权限,一般在管理系统中,都有两个模块,用户管理,角色管理。
后端实现思路:前端用token换取用户信息,用户信息中就已经包含了与路由渲染相关的菜单字段。后端可以在数据库中手动配置菜单数据,还可以开发专门的权限管理模块来动态管理菜单数据。如果要动态地通过增删改查来管理菜单数据,常常得有角色管理、用户管理和菜单管理等功能。
8.axios封装
src/utils/request.js
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// create an axios instance
// baseURL是服务器的地址
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// 请求拦截器
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
// res是服务器响应的结果
const res = response.data
// if the custom code is not 20000, it is judged as an error.
// 状态码分两类:http状态码,业务状态码
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// token过期
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
// 如果没有给出响应,服务器没有响应就触发下面的代码
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service