序言
最近项目需求不是很多,想到公司后台管理项目有些过于臃肿,相帮它减减肥。
奈何君有意,了却她无情。
jQ+bootstrapt的非主流内衣、template-web的硬核外套、html+css+js的老实牌秋裤、cdn铭牌的各种包包,一眼看上去还不错是不是。跟着她回到家,一开门人傻了!......
若要爱请深爱,若不爱请拜拜。
显然我选择了后者,咱们步入正题哈 next()
项目架构-权限配置
分析
一般比较简单的后台管理系统就是系统登录用户角色较少,维护人员不是很多,可能就admin和editor两种,这种权限比较简单,在路由的meta标签加个role差不多就够了,举个栗子
{
path: '/dynamic',
component: Layout,
name: 'dynamic',
meta: {
title: '动态管理',
icon: 'icon-duihuakuang',
roles: ['admin', 'editor'] // you can set roles in root nav
},
children: [
{
path: 'dynamicList',
component: () => import(''),
name: 'dynamicList',
meta: { title: '动态列表', noCache: true }
},
{
path: 'rootdynamic',
component: () => import(''),
name: 'rootdynamic',
meta: { title: '机器人动态列表', noCache: true }
}
]
}
但是我这个有点不一样,复杂一点角色分权限等级,而且用户较多。所以说这样的显然无法实现。
思考
一般登录流程是
获取token--->拿token获取用户信息以及后端返回路由--->前端筛选权限路由--->登录成功--->动态显示该用户的路由
我们没有token,后端有定时任务,过期后端接口都返回403,前端就直接重定向到登录页,所以第一步要不要无所谓了,但是在这文里还是走正常流程(项目上大家也要灵活应变,不走寻常路才能走遍所有路!)。
实践
获取token
思考:你的数据究竟是存储到本地localStorage
还是用Vuex
?(期待你的看法)
我这里都写下怎么选择取决与个人(不要为了使用vuex而使用vuex,大多数项目真的没必要)
login({
commit
}, userInfo) {
const {
username,
password,
verification
} = userInfo
return new Promise((resolve, reject) => {
login({
userName: username.trim(),
passWord: password,
captcha: verification
}).then(response => {
if (response) {
//localStorage.setItem('info', JSON.stringify(response.data))
//假装有token,你有就把后端给的写上
commit('SET_TOKEN', new Date().getTime())
commit('SET_LEVEL', response.data.level)
setToken(new Date().getTime())
resolve(response)
} else {
this.$message({
type: 'error',
message: response.errMsg
})
}
}).catch(error => {
reject(error)
})
})
},
拿token获取用户信息以及后端返回路由
getInfo({
commit,
state
}) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
//路由信息存本地
localStorage.setItem('sidebars', JSON.stringify(response.data))
const data = JSON.parse(localStorage.getItem('info'))
const {
roleName,
userName,
department
} = data
commit('SET_ROLES', roleName)
commit('SET_NAME', userName)
commit('SET_AVATAR', '头像')
commit('SET_INTRODUCTION', department)
resolve(data)
}).catch(error => {
reject(error)
})
})
}
前端筛选权限路由
后端返回的路由是tree类型的数据,显然不能直接拿过来。
既然这样,自己动手吧。前端router.js
分constantRoutes
和asyncRoutes
两个路由菜单
- constantRoutes:每个用都有的路由,比方说登录页、首页、404...
- asyncRoutes:所有权限页面
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: '首页', icon: 'icon-shouye', affix: true }
}
]
}
]
export const asyncRoutes = [
]
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
在main.js
里面引入import './permission'
来看看permission.js
该怎么写
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// 开始进度条
NProgress.start()
// 设置页面标题
document.title = getPageTitle(to.meta.title)
// 确定用户是否已登录
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
// 确定用户是否已通过getInfo获得其权限角色
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// 拿到用户个人信息
const roles = await store.dispatch('user/getInfo')
// 权限路由匹配
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// 动态添加路由
router.addRoutes(accessRoutes)
// hack方法 确保addRoutes已完成
// 设置replace:true,这样导航就不会留下历史记录
next({ ...to, replace: true })
} catch (error) {
// 初始化
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// 在免费登录白名单中,直接进入
next()
} else {
// 其他没有访问权限的页面将重定向到登录页面。
next(`/login?redirect=${to.path}`)
// next()
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
看看generateRoutes
这里的roles其实就是用户信息,证明拿到了后端路由然后下一步
generateRoutes({
commit
}, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles) {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
/**功能权限点,为后文埋下伏笔
const data = JSON.parse(localStorage.getItem('sidebars'))
const permission = []
data.forEach(element => {
if (element.children) {
element.children.forEach(item => {
item.children.forEach(i => {
permission.push(i.menuId)
})
})
}
})
commit('SET_PERMISSION', permission)
*/
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
下面详细看看重点路由筛选,添加动态路由(完整文件见附录
)
/**
* 递归过滤异步路由表
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = {
...route
}
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
/**
* 通过和后端路由菜单比较确定当前用户是否有权限
* @param roles
* @param route
*/
function hasPermission(roles, route) {
//拿到后端返回的路由,将菜单名递归出来
const data = JSON.parse(localStorage.getItem('sidebars'))
const name = []
data.forEach(element => {
name.push(element.menuName)
if (element.children) {
element.children.forEach(item => {
name.push(item.menuName)
})
}
})
//将后端返回的和当前路由对比有的话就通过,进入动态路由无的话就pass
if (route.meta && route.meta.title) {
return name.some(item => {
return route.meta.title === item
})
} else {
return false
}
}
登录成功动态显示该用户的路由
既然有了页面权限,理论上应该有功能权限 其实应该还会有功能权限点,就是页面内他或许不能新增,只能看到列表,这种是页面内权限点配置,一般采用自定义指令的方式比较方便
权限配置-页面功能权限点配置
将后端返回的路由中的功能权限点通过递归提取出来
首先引入文件
mainjs
里面引入
import directives from '@/utils/directives.js'
Vue.use(directives)
然后编写逻辑
在directives.js
里面封装下指令
// 页面功能权限判断
function checkPermission(el, binding) {
const { value } = binding
if (value) {
//这个是我在筛选路由时顺便存下的
const permissionArr = store.getters && store.getters.permissionArr
const permissionRoles = Number(value)
const hasPermission = permissionArr.some(val => {
return permissionRoles === val
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`need roles! Like v-permission="['admin','editor']"`)
}
}
export default (Vue) => {
Vue.directive('permission', {
inserted(el, binding) {//初始化调用
checkPermission(el, binding)
}
// update(el, binding) {//这是在刷新的时候调用
// checkPermission(el, binding)
// }
})
}
最后页面使用
<el-button
v-for="(item) in dataSource.tool"
:key="item.key"
v-permission="item.permission"
class="filter-item"
type="primary"
@click="handleAdd(item.name)"
>
{{ item.name }}
</el-button>
结尾
谈谈架构框架选择
本来想用react+ant
的,但是奈何感觉react
都是js文件不太对我的胃口,
(个人感觉都是js文件层次不是很清晰,相对来讲vue的熟悉成本较低)
既然有的选,那咱们就用Vue
,
Vue3.0
虽然可以用,但是考虑到不确定因素,还需要时间去完善它
咱们选稳的2.x版本,然后UI库就Element UI
吧。
权限配置到这里就结束了,有什么问题大家可以留言一起讨论
现在正在重构页面coding,也遇到一些问题,高德地图结合echarts展示问题、角色页面角色等级问题、二次封装table组件。
等我攒够一波坑,再来和大家分享。
Vue重构项目之权限配置篇到此结束,谢谢你的阅读!!!咱们下期见
附录
vuex里面的permission部分
import {
asyncRoutes,
constantRoutes
} from '@/router'
/**
* 通过和后端路由菜单比较确定当前用户是否有权限
* @param roles
* @param route
*/
function hasPermission(roles, route) {
const data = JSON.parse(localStorage.getItem('sidebars'))
const name = []
data.forEach(element => {
name.push(element.menuName)
if (element.children) {
element.children.forEach(item => {
name.push(item.menuName)
})
}
})
if (route.meta && route.meta.title) {
return name.some(item => {
return route.meta.title === item
})
} else {
return false
}
}
/**
* 递归过滤异步路由表
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = {
...route
}
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: [],
permissionArr: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
},
SET_PERMISSION: (state, arr) => {
state.permissionArr = arr
}
}
const actions = {
generateRoutes({
commit
}, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles) {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
const data = JSON.parse(localStorage.getItem('sidebars'))
const permission = []
data.forEach(element => {
if (element.children) {
element.children.forEach(item => {
item.children.forEach(i => {
permission.push(i.menuId)
})
})
}
})
commit('SET_ROUTES', accessedRoutes)
commit('SET_PERMISSION', permission)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
写在最后
我是凉城a,一个前端,热爱技术也热爱生活。
与你相逢,我很开心。
-
文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
-
本文首发于掘金,未经许可禁止转载💌