vue實(shí)現(xiàn)后臺(tái)管理權(quán)限系統(tǒng)及頂欄三級(jí)菜單顯示功能
項(xiàng)目demo展示


重要功能總結(jié)
權(quán)限功能的實(shí)現(xiàn)
權(quán)限路由思路:
根據(jù)用戶(hù)登錄的roles信息與路由中配置的roles信息進(jìn)行比較過(guò)濾,生成可以訪問(wèn)的路由表,并通過(guò)router.addRoutes(store.getters.addRouters)動(dòng)態(tài)添加可訪問(wèn)權(quán)限路由表,從而實(shí)現(xiàn)左側(cè)和頂欄菜單的展示。
實(shí)現(xiàn)步驟:
1.在router/index.js中,給相應(yīng)的菜單設(shè)置默認(rèn)的roles信息;
如下:給"權(quán)限設(shè)置"菜單設(shè)置的權(quán)限為:meta:{roles: ['admin', 'editor']},及不同的角色都可以看到; 給其子菜單"頁(yè)面權(quán)限",設(shè)置權(quán)限為:meta:{roles: ['admin']},及表示只有"admin"可以看到該菜單; 給其子菜單"按鈕權(quán)限"設(shè)置權(quán)限為:meta:{roles: ['editor']},及表示只有"editor"可以看到該菜單。
2.通過(guò)router.beforeEach()和router.afterEach()進(jìn)行路由過(guò)濾和權(quán)限攔截;
代碼如下:
// 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()
// 設(shè)置瀏覽器頭部標(biāo)題
const browserHeaderTitle = to.meta.title
store.commit('SET_BROWSERHEADERTITLE', {
browserHeaderTitle: browserHeaderTitle
})
// 點(diǎn)擊登錄時(shí),拿到了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: '/' }) // 會(huì)匹配到path:'',后面的path:'*'還沒(méi)有生成;
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(res => { // 拉取用戶(hù)信息
const roles = res.roles
store.dispatch('GenerateRoutes', { roles }).then(() => { // 根據(jù)roles權(quán)限生成可訪問(wèn)的路由表
router.addRoutes(store.getters.addRouters) // 動(dòng)態(tài)添加可訪問(wèn)權(quán)限路由表
next({ ...to, replace: true }) // hack方法 確保addRoutes已完成
})
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
// 沒(méi)有動(dòng)態(tài)改變權(quán)限的需求可直接next() 刪除下方權(quán)限判斷 ↓
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 {
// 點(diǎn)擊退出時(shí),會(huì)定位到這里
next('/login')
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done() // 結(jié)束Progress
setTimeout(() => {
const browserHeaderTitle = store.getters.browserHeaderTitle
setTitle(browserHeaderTitle)
}, 0)
})
用戶(hù)點(diǎn)擊登錄之后的業(yè)務(wù)邏輯分析:
1、用戶(hù)調(diào)取登錄接口,獲取到token,進(jìn)行路由跳轉(zhuǎn)到首頁(yè);
2、通過(guò)路由導(dǎo)航鉤子router.beforeEach((to,from,next)=>{})函數(shù)確定下一步的跳轉(zhuǎn)邏輯,如下:
2.1、用戶(hù)已經(jīng)登錄成功并返回token值;
2.1.1、lock 鎖屏場(chǎng)景;
2.1.2、用戶(hù)重新定位到登錄頁(yè)面;
2.1.3、根據(jù)用戶(hù)是否有roles信息,進(jìn)行不同的業(yè)務(wù)邏輯,如下:
(1)、初始情況下,用戶(hù)roles信息為空;
通過(guò)store.dispatch('GetInfo')調(diào)取接口,獲取用戶(hù)信息;
獲取到roles信息后,將roles,name,avatar保存到vuex;
同時(shí),通過(guò)store.dispatch('GenerateRoutes', { roles })去重新過(guò)濾和生成路由,并將重新生成之后的權(quán)限路由'routes'保存到vuex;
最后,通過(guò)router.addRoutes()合并路由表;
如果在獲取用戶(hù)信息接口時(shí),出現(xiàn)錯(cuò)誤,則調(diào)取store.dispatch('FedLogOut')接口,返回到login頁(yè)面;
用戶(hù)FedLogOut之后,需要情況vuex和localStorage中的token信息;
(2)、用戶(hù)已經(jīng)擁有roles信息;
點(diǎn)擊頁(yè)面路由,通過(guò)roles權(quán)限判斷 hasPermission();
如果用戶(hù)有該路由權(quán)限,直接跳轉(zhuǎn)對(duì)應(yīng)的頁(yè)面;如果沒(méi)有權(quán)限,則跳轉(zhuǎn)至401提示頁(yè)面;
2.2、用戶(hù)沒(méi)有獲取到token值;
2.2.1、如果設(shè)置了白名單用戶(hù),則直接跳轉(zhuǎn)到相應(yīng)的頁(yè)面;反之,則跳轉(zhuǎn)至登錄頁(yè)面;
3、通過(guò)路由導(dǎo)航鉤子函數(shù)router.afterEach(() => {}),做收尾工作,如下:
3.1、NProgress.done() // 結(jié)束Progress
3.2、獲取到title并設(shè)置title;
詳細(xì)代碼,請(qǐng)參考src/permission.js
4、權(quán)限演示說(shuō)明
測(cè)試賬號(hào):
(1). username: admin,password: 123456;admin擁有最高權(quán)限,可以查看所有的頁(yè)面和按鈕;
(2). username: editor,password: 123456;editor只有被賦予權(quán)限的頁(yè)面和按鈕才可以看到;
三級(jí)導(dǎo)航菜單頂部欄展示

如圖所示,在完成一般后臺(tái)系統(tǒng)所具有的二級(jí)導(dǎo)航菜單功能之后,我發(fā)現(xiàn)其實(shí)很多的后臺(tái)管理系統(tǒng)都有三級(jí)導(dǎo)航菜單,但是如果都把三級(jí)菜單放到左側(cè)菜單做階梯狀排列,就會(huì)顯得比較緊湊,因此我覺(jué)得把所有的三級(jí)菜單放到頂部是一個(gè)不錯(cuò)的選擇。
開(kāi)發(fā)需求:點(diǎn)擊左側(cè)菜單,找到其對(duì)應(yīng)的菜單(頂欄菜單)排放于頂部導(dǎo)航欄;
開(kāi)發(fā)步驟:
1、 定義頂部導(dǎo)航組件topMenu.vue;
通過(guò)element-ui,NavMenu 導(dǎo)航菜單來(lái)進(jìn)行頂部菜單的展示,注意頂欄和側(cè)欄設(shè)置的區(qū)別;同時(shí)將其引用于頭部組件headNav.vue中;
2、定義頂欄路由數(shù)據(jù)router/topRouter.js;
格式如下:
export const topRouterMap = [
{
'parentName':'infoShow',
'topmenulist':[
{
path: 'infoShow1',
name: 'infoShow1',
meta: {
title: '個(gè)人信息子菜單1',
icon: 'fa-asterisk',
routerType: 'topmenu'
},
component: () => import('@/page/fundList/moneyData')
}
]
},
{
'parentName':'chinaTabsList',
'topmenulist':[
{
path:'chinaTabsList1',
name:'chinaTabsList1',
meta:{
title:'區(qū)域投資子菜單1',
icon:'fa-asterisk',
routerType:'topmenu'
},
component: () => import('@/page/fundList/moneyData')
}
]
}
]
定義topRouterMap為路由總數(shù)組;通過(guò)parentName來(lái)與左側(cè)路由建立聯(lián)系;通過(guò)topmenulist表示該頂欄路由的值;通過(guò)meta.routerType的值為"topmenu"或"leftmenu"來(lái)區(qū)分是頂欄路由,還是左側(cè)路由;
3、 準(zhǔn)備headNav.vue中渲染數(shù)據(jù);
思路:點(diǎn)擊左側(cè)菜單,需要顯示頂部對(duì)應(yīng)的菜單。因?yàn)樽髠?cè)菜單要和頂部菜單建立聯(lián)系。我們知道導(dǎo)航菜單在用戶(hù)登錄時(shí),會(huì)根據(jù)用戶(hù)的role信息進(jìn)行權(quán)限過(guò)濾;那么,在過(guò)濾權(quán)限路由數(shù)據(jù)之前,我們可以通過(guò)addTopRouter()將所有的三級(jí)菜單進(jìn)行過(guò)濾添加,添加完成之后,繼續(xù)進(jìn)行角色過(guò)濾,可以保證將不具備權(quán)限的頂部菜單也過(guò)濾掉。
// 通過(guò)循環(huán)過(guò)濾,生成新的二級(jí)菜單
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、點(diǎn)擊左側(cè)菜單過(guò)濾路由并顯示對(duì)應(yīng)數(shù)據(jù);
在組件topMenu.vue中,用戶(hù)默認(rèn)進(jìn)來(lái)或者點(diǎn)擊左側(cè)菜單,觸發(fā)setLeftInnerMenu()函數(shù),如下:
setLeftInnerMenu(){
if(this.$route.meta.routerType == 'leftmenu'){ // 點(diǎn)擊的為 左側(cè)的2級(jí)菜單
this.$store.dispatch(''ClickLeftInnerMenu,
{'name':this.$route.name}
);
}else{ // 點(diǎn)擊頂部的菜單
this.$store.dispatch('ClickTopMenu',
{'title':this.$route.meta.title}
);
}
}
通過(guò)當(dāng)前路由this.$route.meta.routerType的值判斷,用戶(hù)是點(diǎn)擊頂部菜單還是左側(cè)菜單。如果點(diǎn)擊頂部菜單,通過(guò)this.$store觸發(fā)異步動(dòng)作'ClickLeftInnerMenu'并傳遞參數(shù)'name',vuex中通過(guò)state.topRouters = filterTopRouters(state.routers,data)過(guò)濾當(dāng)前路由信息;代碼如下:
// 獲取到當(dāng)前路由對(duì)應(yīng)頂部子菜單
function filterTopRouters(data){
let topRouters = topRouterMap.find((item)=>{
return item.parentName === data.name
})
if(!mutils.isEmpty(topRouters)){
return topRouters.topmenulist;
}
}
topMenu.vue中,通過(guò) computed:{ ...mapGetters(['topRouters'])}進(jìn)行對(duì)應(yīng)頂部路由數(shù)據(jù)的展示。用戶(hù)每次點(diǎn)擊左側(cè)菜單時(shí),頂部路由都進(jìn)行了重新賦值并渲染,保證了數(shù)據(jù)的準(zhǔn)確性。
5、頂部菜單完善;
當(dāng)頂部菜單的數(shù)據(jù)量過(guò)大時(shí),我們需要設(shè)置橫向滾動(dòng)條并設(shè)置滾動(dòng)條的樣式。
如圖:

mock數(shù)據(jù)詳解
easy-mock使用
Easy Mock介紹:
•Easy Mock 是一個(gè)可視化,并且能快速生成 模擬數(shù)據(jù) 的持久化服務(wù),
•Easy Mock 支持基于 Swagger 創(chuàng)建項(xiàng)目,以節(jié)省手動(dòng)創(chuàng)建接口的時(shí)間;
•簡(jiǎn)單點(diǎn)說(shuō):Easy Mock就是一個(gè)在線創(chuàng)建mock的服務(wù)平臺(tái),幫你省去你 配置、安裝、起服務(wù)、維護(hù)、多人協(xié)作Mock數(shù)據(jù)不互通等一系列繁瑣的操作, 它能在不用1秒鐘的時(shí)間內(nèi)給你所要的一切。
詳細(xì)使用方法,包含新建項(xiàng)目,基礎(chǔ)語(yǔ)法,數(shù)據(jù)占位符,Swagger等介紹和使用,請(qǐng)參考詳細(xì)文檔
easy-mock,在本項(xiàng)目中的使用:
1.按照官方文檔,創(chuàng)建個(gè)人項(xiàng)目vue-touzi-admin;
根據(jù)項(xiàng)目需要,創(chuàng)建的接口有:用戶(hù)登錄接口:"/user/login";獲取用戶(hù)信息接口:"/user/info";用戶(hù)登出接口:"/user/logout";獲取所有用戶(hù)列表接口:"/user/getUserList";如圖:

登錄接口在easy-mock端編寫(xiě)的邏輯如下:
{
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 "賬號(hào)或密碼有誤!"
}
},
data: function({
_req
}) {
if (_req.body.username == "admin" && _req.body.password === "123456") {
return {
code: 0,
roles: ['admin'],
token: 'admin',
introduction: '我是超級(jí)管理員',
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 "賬號(hào)或密碼有誤!"
}
}
}
1.webpack中,開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境地址配置;生產(chǎn)環(huán)境,NODE_ENV: '"production"';如下:
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
API_BASE_URL: '"https://easy-mock.com/mock/5cd03667adb0973be6a3d8d1/api"',
})
3.接口封裝實(shí)例;如下:
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
}
})
}
mockjs使用
使用背景:
在使用easy-mock模擬數(shù)據(jù)的過(guò)程中,發(fā)現(xiàn)其對(duì)表格固定數(shù)據(jù)不能實(shí)現(xiàn)增刪改等功能,因而選擇了使用mockjs;
介紹及功能:
Mock.js是一款模擬數(shù)據(jù)生成器,旨在幫助前端攻城師獨(dú)立于后端進(jìn)行開(kāi)發(fā),幫助編寫(xiě)單元測(cè)試。提供了以下模擬功能:
1.根據(jù)數(shù)據(jù)模板生成模擬數(shù)據(jù),通過(guò)mockjs提供的方法,你可以輕松地創(chuàng)造大量隨機(jī)的文本,數(shù)字,布爾值,日期,郵箱,鏈接,圖片,顏色等.
2.模擬 Ajax 請(qǐng)求,生成并返回模擬數(shù)據(jù),mockjs可以進(jìn)行強(qiáng)大的ajax攔截.能判斷請(qǐng)求類(lèi)型,獲取到url,請(qǐng)求參數(shù)等.然后可以返回mock的假數(shù)據(jù),或者你自己編好的json文件.功能強(qiáng)大易上手.
3.基于 HTML 模板生成模擬數(shù)據(jù)
mockjs在本項(xiàng)目中使用:
1.安裝mockjs
npm install mockjs --save-dev
2.創(chuàng)建mock文件夾結(jié)構(gòu)并定義相關(guān)的功能模塊;如圖:

mockjs/index.js,負(fù)責(zé)定義相關(guān)的mock接口,如下:
import Mock from 'mockjs'
import tableAPI from './money'
// 設(shè)置全局延時(shí) 沒(méi)有延時(shí)的話有時(shí)候會(huì)檢測(cè)不到數(shù)據(jù)變化 建議保留
Mock.setup({
timeout: '300-600'
})
// 資金相關(guān)
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,則定義相關(guān)的函數(shù),實(shí)現(xiàn)模擬數(shù)據(jù)的業(yè)務(wù)邏輯,比如資金流水?dāng)?shù)據(jù)的增刪改查等;數(shù)據(jù)的生成規(guī)則請(qǐng)參照mockjs官網(wǎng)文檔,上面有詳細(xì)的語(yǔ)法說(shuō)明;
3.在main.js中引入定義好的mockjs;如下:
import './mockjs' //引用mock
4.mockjs,api接口封裝;
src/api/money.js中,進(jìn)行了統(tǒng)一的接口封裝,在頁(yè)面中調(diào)用對(duì)應(yīng)函數(shù),即可獲取到相應(yīng)的模擬數(shù)據(jù)。代碼如下:
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.組件中,接口調(diào)用,獲取數(shù)據(jù),渲染頁(yè)面;
總結(jié)
以上所述是小編給大家介紹的vue實(shí)現(xiàn)后臺(tái)管理權(quán)限系統(tǒng)及頂欄三級(jí)菜單顯示功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
相關(guān)文章
vue前端實(shí)現(xiàn)驗(yàn)證碼登錄功能
這篇文章主要介紹了vue前端實(shí)現(xiàn)驗(yàn)證碼登錄功能,登錄時(shí)圖形驗(yàn)證通過(guò)三種方法結(jié)合實(shí)例代碼給大家講解的非常詳細(xì), 通過(guò)實(shí)例代碼介紹了vue登錄時(shí)圖形驗(yàn)證碼功能的實(shí)現(xiàn),感興趣的朋友一起看看吧2023-12-12
VuePress 靜態(tài)網(wǎng)站生成方法步驟
這篇文章主要介紹了VuePress 靜態(tài)網(wǎng)站生成方法步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02
vue單頁(yè)應(yīng)用在頁(yè)面刷新時(shí)保留狀態(tài)數(shù)據(jù)的方法
今天小編就為大家分享一篇vue單頁(yè)應(yīng)用在頁(yè)面刷新時(shí)保留狀態(tài)數(shù)據(jù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
vue的路由守衛(wèi)和keep-alive后生命周期詳解
這篇文章主要為大家詳細(xì)介紹了vue路由守衛(wèi)和keep-alive,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
Vue.extend 編程式插入組件的實(shí)現(xiàn)
這篇文章主要介紹了Vue.extend 編程式插入組件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
vue實(shí)現(xiàn)word,pdf文件的導(dǎo)出功能
這篇文章給大家介紹了vue實(shí)現(xiàn)word或pdf文檔導(dǎo)出的功能,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-07-07
vue打包后dist目錄下的index.html網(wǎng)頁(yè)顯示空白的問(wèn)題(兩種方案)
本文主要介紹了vue打包后dist目錄下的index.html網(wǎng)頁(yè)顯示空白的問(wèn)題,主要介紹了兩種方式,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11

