最近完成了个人后台管理系统权限功能的实现,同时以为后台系统全部的菜单都左置,会限制菜单的扩展,所以我改进了三级菜单的显示。前端
权限路由思路: 根据用户登陆的roles信息与路由中配置的roles信息进行比较过滤,生成能够访问的路由表,并经过router.addRoutes(store.getters.addRouters)动态添加可访问权限路由表,从而实现左侧和顶栏菜单的展现。vue
实现步骤:webpack
1.在router/index.js中,给相应的菜单设置默认的roles信息;ios
以下:给"权限设置"菜单设置的权限为:meta:{roles: ['admin', 'editor']},及不一样的角色均可以看到; 给其子菜单"页面权限",设置权限为:meta:{roles: ['admin']},及表示只有"admin"能够看到该菜单; 给其子菜单"按钮权限"设置权限为:meta:{roles: ['editor']},及表示只有"editor"能够看到该菜单。git
2.经过router.beforeEach()和router.afterEach()进行路由过滤和权限拦截;github
代码以下:web
// permission judge function
function hasPermission(roles, permissionRoles) {
if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
if (!permissionRoles) return true
return roles.some(role => permissionRoles.indexOf(role) >= 0)
}
const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
NProgress.start()
// 设置浏览器头部标题
const browserHeaderTitle = to.meta.title
store.commit('SET_BROWSERHEADERTITLE', {
browserHeaderTitle: browserHeaderTitle
})
// 点击登陆时,拿到了token并存入了vuex;
if (getToken()) {
/* has token*/
if (store.getters.isLock && to.path !== '/lock') {
next({
path: '/lock'
})
NProgress.done()
} else if (to.path === '/login') {
next({ path: '/' }) // 会匹配到path:'',后面的path:'*'尚未生成;
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(res => { // 拉取用户信息
const roles = res.roles
store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
router.addRoutes(store.getters.addRouters) // 动态添加可访问权限路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
if (hasPermission(store.getters.roles, to.meta.roles)) {
next()//
} else {
next({ path: '/401', replace: true, query: { noGoBack: true }})
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// 点击退出时,会定位到这里
next('/login')
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done() // 结束Progress
setTimeout(() => {
const browserHeaderTitle = store.getters.browserHeaderTitle
setTitle(browserHeaderTitle)
}, 0)
})
复制代码
用户点击登陆以后的业务逻辑分析:ajax
一、用户调取登陆接口,获取到token,进行路由跳转到首页;vuex
二、经过路由导航钩子router.beforeEach((to,from,next)=>{})函数肯定下一步的跳转逻辑,以下:npm
2.一、用户已经登陆成功并返回token值;
2.1.一、lock 锁屏场景;
2.1.二、用户从新定位到登陆页面;
2.1.三、根据用户是否有roles信息,进行不一样的业务逻辑,以下:
(1)、初始状况下,用户roles信息为空;
经过store.dispatch('GetInfo')调取接口,获取用户信息;
获取到roles信息后,将roles,name,avatar保存到vuex;
同时,经过store.dispatch('GenerateRoutes', { roles })去从新过滤和生成路由,并将从新生成以后的权限路由'routes'保存到vuex;
最后,经过router.addRoutes()合并路由表;
若是在获取用户信息接口时,出现错误,则调取store.dispatch('FedLogOut')接口,返回到login页面;
用户FedLogOut以后,须要状况vuex和localStorage中的token信息;
(2)、用户已经拥有roles信息;
点击页面路由,经过roles权限判断 hasPermission();
若是用户有该路由权限,直接跳转对应的页面;若是没有权限,则跳转至401提示页面;
2.二、用户没有获取到token值;
2.2.一、若是设置了白名单用户,则直接跳转到相应的页面;反之,则跳转至登陆页面;
复制代码
三、经过路由导航钩子函数router.afterEach(() => {}),作收尾工做,以下:
3.一、NProgress.done() // 结束Progress
3.二、获取到title并设置title;
复制代码
详细代码,请参考src/permission.js
四、权限演示说明
测试帐号:
(1). username: admin,password: 123456;admin拥有最高权限,能够查看全部的页面和按钮;
(2). username: editor,password: 123456;editor只有被赋予权限的页面和按钮才能够看到;
如图所示,在完成通常后台系统所具备的二级导航菜单功能以后,我发现其实不少的后台管理系统都有三级导航菜单,可是若是都把三级菜单放到左侧菜单作阶梯状排列,就会显得比较紧凑,所以我以为把全部的三级菜单放到顶部是一个不错的选择。
开发需求:点击左侧菜单,找到其对应的菜单(顶栏菜单)排放于顶部导航栏;
开发步骤:
经过element-ui,NavMenu 导航菜单来进行顶部菜单的展现,注意顶栏和侧栏设置的区别;同时将其引用于头部组件headNav.vue中;
格式以下:
export const topRouterMap = [
{
'parentName':'infoShow',
'topmenulist':[
{
path: 'infoShow1',
name: 'infoShow1',
meta: {
title: '我的信息子菜单1',
icon: 'fa-asterisk',
routerType: 'topmenu'
},
component: () => import('@/page/fundList/moneyData')
}
]
},
{
'parentName':'chinaTabsList',
'topmenulist':[
{
path:'chinaTabsList1',
name:'chinaTabsList1',
meta:{
title:'区域投资子菜单1',
icon:'fa-asterisk',
routerType:'topmenu'
},
component: () => import('@/page/fundList/moneyData')
}
]
}
]
复制代码
定义topRouterMap为路由总数组;经过parentName来与左侧路由创建联系;经过topmenulist表示该顶栏路由的值;经过meta.routerType的值为"topmenu"或"leftmenu"来区分是顶栏路由,仍是左侧路由;
思路:点击左侧菜单,须要显示顶部对应的菜单。由于左侧菜单要和顶部菜单创建联系。咱们知道导航菜单在用户登陆时,会根据用户的role信息进行权限过滤;那么,在过滤权限路由数据以前,咱们能够经过addTopRouter()将全部的三级菜单进行过滤添加,添加完成以后,继续进行角色过滤,能够保证将不具有权限的顶部菜单也过滤掉。
// 经过循环过滤,生成新的二级菜单
function addTopRouter(){
asyncRouterMap.forEach( (item) => {
if(item.children && item.children.length >= 1){
item.children.forEach((sitem) => {
topRouterMap.forEach((citem) => {
if(sitem.name === citem.parentName){
let newChildren = item.children.concat(citem.topmenulist);
item.children = newChildren;
}
})
})
}
})
return asyncRouterMap;
}
复制代码
4.点击左侧菜单过滤路由并显示对应数据;
在组件topMenu.vue中,用户默认进来或者点击左侧菜单,触发setLeftInnerMenu()函数,以下:
setLeftInnerMenu(){
if(this.$route.meta.routerType == 'leftmenu'){ // 点击的为 左侧的2级菜单
this.$store.dispatch(''ClickLeftInnerMenu,
{'name':this.$route.name}
);
}else{ // 点击顶部的菜单
this.$store.dispatch('ClickTopMenu',
{'title':this.$route.meta.title}
);
}
}
复制代码
经过当前路由this.store触发异步动做'ClickLeftInnerMenu'并传递参数'name',vuex中经过state.topRouters = filterTopRouters(state.routers,data)过滤当前路由信息;代码以下:
// 获取到当前路由对应顶部子菜单
function filterTopRouters(data){
let topRouters = topRouterMap.find((item)=>{
return item.parentName === data.name
})
if(!mutils.isEmpty(topRouters)){
return topRouters.topmenulist;
}
}
复制代码
topMenu.vue中,经过 computed:{ ...mapGetters(['topRouters'])}进行对应顶部路由数据的展现。用户每次点击左侧菜单时,顶部路由都进行了从新赋值并渲染,保证了数据的准确性。
5.顶部菜单完善;
当顶部菜单的数据量过大时,咱们须要设置横向滚动条并设置滚动条的样式。 如图:
Easy Mock介绍:
详细使用方法,包含新建项目,基础语法,数据占位符,Swagger等介绍和使用,请参考详细文档
easy-mock,在本项目中的使用:
根据项目须要,建立的接口有:用户登陆接口:"/user/login";获取用户信息接口:"/user/info";用户登出接口:"/user/logout";获取全部用户列表接口:"/user/getUserList";如图:
登陆接口在easy-mock端编写的逻辑以下:
{
code: function({
_req
}) {
if (_req.body.username === "admin" || _req.body.username === "editor" && _req.body.password === "123456") {
return 200
} else {
return -1
}
},
message: function({
_req
}) {
if (_req.body.username !== "admin" || _req.body.username !== "editor") {
return "帐号或密码有误!"
}
},
data: function({
_req
}) {
if (_req.body.username == "admin" && _req.body.password === "123456") {
return {
code: 0,
roles: ['admin'],
token: 'admin',
introduction: '我是超级管理员',
name: 'Super Admin'
}
} else if (_req.body.username === 'editor' && _req.body.password === "123456") {
return {
code: 0,
roles: ['editor'],
token: 'editor',
introduction: '我是编辑',
name: 'Normal Editor'
}
} else {
return "帐号或密码有误!"
}
}
}
复制代码
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
API_BASE_URL: '"https://easy-mock.com/mock/5cd03667adb0973be6a3d8d1/api"',
})
复制代码
3.接口封装实例;以下:
import request from '@/utils/axios'
export function login(username, password) {
return request({
url: process.env.API_BASE_URL+'/user/login',
method: 'post',
data: {
username,
password
}
})
}
复制代码
使用背景:
在使用easy-mock模拟数据的过程当中,发现其对表格固定数据不能实现增删改等功能,于是选择了使用mockjs;
介绍及功能:
Mock.js是一款模拟数据生成器,旨在帮助前端攻城师独立于后端进行开发,帮助编写单元测试。提供了如下模拟功能:
1.根据数据模板生成模拟数据,经过mockjs提供的方法,你能够轻松地创造大量随机的文本,数字,布尔值,日期,邮箱,连接,图片,颜色等.
2.模拟 Ajax 请求,生成并返回模拟数据,mockjs能够进行强大的ajax拦截.能判断请求类型,获取到url,请求参数等.而后能够返回mock的假数据,或者你本身编好的json文件.功能强大易上手.
3.基于 HTML 模板生成模拟数据
mockjs在本项目中使用:
npm install mockjs --save-dev
复制代码
2.建立mock文件夹结构并定义相关的功能模块;如图:
mockjs/index.js,负责定义相关的mock接口,以下:import Mock from 'mockjs'
import tableAPI from './money'
// 设置全局延时 没有延时的话有时候会检测不到数据变化 建议保留
Mock.setup({
timeout: '300-600'
})
// 资金相关
Mock.mock(/\/money\/get/, 'get', tableAPI.getMoneyList)
Mock.mock(/\/money\/remove/, 'get', tableAPI.deleteMoney)
Mock.mock(/\/money\/batchremove/, 'get', tableAPI.batchremoveMoney)
Mock.mock(/\/money\/add/, 'get', tableAPI.createMoney)
Mock.mock(/\/money\/edit/, 'get', tableAPI.updateMoney)
复制代码
mockjs/money.js,则定义相关的函数,实现模拟数据的业务逻辑,好比资金流水数据的增删改查等;数据的生成规则请参照mockjs官网文档,上面有详细的语法说明;
3.在main.js中引入定义好的mockjs;以下:
import './mockjs' //引用mock
复制代码
4.mockjs,api接口封装;
src/api/money.js中,进行了统一的接口封装,在页面中调用对应函数,便可获取到相应的模拟数据。代码以下:
import request from '@/utils/axios'
export function getMoneyIncomePay(params) {
return request({
url: '/money/get',
method: 'get',
params: params
})
}
export function addMoney(params) {
return request({
url: '/money/add',
method: 'get',
params: params
})
}
复制代码
5.组件中,接口调用,获取数据,渲染页面;
项目说明:
小爱ADMIN是彻底开源免费的管理系统集成方案,能够直接应用于相关后台管理系统模板;不少重点地方都作了详细的注释和解释。若是你也同样喜欢前端开发,欢迎加入咱们的讨论/学习群,群内能够提问答疑,分享学习资料; 欢迎加入答疑qq群:602515030