手摸手,帶你用vue實現後臺管理許可權系統及頂欄三級選單顯示
手摸手,帶你用vue實現後臺管理許可權系統及頂欄三級選單顯示
- 效果演示地址
專案demo展示
重要功能總結
許可權功能的實現
許可權路由思路:
根據使用者登入的roles資訊與路由中配置的roles資訊進行比較過濾,生成可以訪問的路由表,並通過router.addRoutes(store.getters.addRouters)動態新增可訪問許可權路由表,從而實現左側和頂欄選單的展示。
實現步驟:
1.在router/index.js中,給相應的選單設定預設的roles資訊;
如下:給"許可權設定"選單設定的許可權為:meta:{roles: ['admin', 'editor']},及不同的角色都可以看到; 給其子選單"頁面許可權",設定許可權為:meta:{roles: ['admin']},及表示只有"admin"可以看到該選單; 給其子選單"按鈕許可權"設定許可權為:meta:{roles: ['editor']},及表示只有"editor"可以看到該選單。
2.通過router.beforeEach()和router.afterEach()進行路由過濾和許可權攔截;
程式碼如下:
// 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) })
使用者點選登入之後的業務邏輯分析:
1、使用者調取登入介面,獲取到token,進行路由跳轉到首頁;
2、通過路由導航鉤子router.beforeEach((to,from,next)=>{})函式確定下一步的跳轉邏輯,如下:
2.1、使用者已經登入成功並返回token值; 2.1.1、lock 鎖屏場景; 2.1.2、使用者重新定位到登入頁面; 2.1.3、根據使用者是否有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.2、使用者沒有獲取到token值; 2.2.1、如果設定了白名單使用者,則直接跳轉到相應的頁面;反之,則跳轉至登入頁面;
3、通過路由導航鉤子函式router.afterEach(() => {}),做收尾工作,如下:
3.1、NProgress.done() // 結束Progress
3.2、獲取到title並設定title;
詳細程式碼,請參考src/permission.js
4、許可權演示說明
測試賬號:
(1). username: admin,password: 123456;admin擁有最高許可權,可以檢視所有的頁面和按鈕;
(2). username: editor,password: 123456;editor只有被賦予許可權的頁面和按鈕才可以看到;
三級導航選單頂部欄展示
如圖所示,在完成一般後臺系統所具有的二級導航選單功能之後,我發現其實很多的後臺管理系統都有三級導航選單,但是如果都把三級選單放到左側選單做階梯狀排列,就會顯得比較緊湊,因此我覺得把所有的三級選單放到頂部是一個不錯的選擇。
開發需求:點選左側選單,找到其對應的選單(頂欄選單)排放於頂部導航欄;
開發步驟:
1、 定義頂部導航元件topMenu.vue;
通過element-ui,NavMenu 導航選單來進行頂部選單的展示,注意頂欄和側欄設定的區別;同時將其引用於頭部元件headNav.vue中;
2、定義頂欄路由資料router/topRouter.js;
格式如下:
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"來區分是頂欄路由,還是左側路由;
3、 準備headNav.vue中渲染資料;
思路:點選左側選單,需要顯示頂部對應的選單。因為左側選單要和頂部選單建立聯絡。我們知道導航選單在使用者登入時,會根據使用者的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.$route.meta.routerType的值判斷,使用者是點選頂部選單還是左側選單。如果點選頂部選單,通過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、頂部選單完善;
當頂部選單的資料量過大時,我們需要設定橫向滾動條並設定滾動條的樣式。
如圖:
mock資料詳解
easy-mock使用
Easy Mock介紹:
- Easy Mock 是一個視覺化,並且能快速生成 模擬資料 的持久化服務,
- Easy Mock 支援基於 Swagger 建立專案,以節省手動建立介面的時間;
- 簡單點說:Easy Mock就是一個線上建立mock的服務平臺,幫你省去你 配置、安裝、起服務、維護、多人協作Mock資料不互通等一系列繁瑣的操作, 它能在不用1秒鐘的時間內給你所要的一切。
詳細使用方法,包含新建專案,基礎語法,資料佔位符,Swagger等介紹和使用,請參考詳細文件
easy-mock,在本專案中的使用:
- 按照官方文件,建立個人專案vue-touzi-admin;
根據專案需要,建立的介面有:使用者登入介面:"/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 "賬號或密碼有誤!"
}
}
}
- webpack中,開發環境和生產環境地址配置;生產環境,NODE_ENV: '"production"';如下:
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
}
})
}
mockjs使用
使用背景:
在使用easy-mock模擬資料的過程中,發現其對錶格固定資料不能實現增刪改等功能,因而選擇了使用mockjs;
介紹及功能:
Mock.js是一款模擬資料生成器,旨在幫助前端攻城師獨立於後端進行開發,幫助編寫單元測試。提供了以下模擬功能:
1.根據資料模板生成模擬資料,通過mockjs提供的方法,你可以輕鬆地創造大量隨機的文字,數字,布林值,日期,郵箱,連結,圖片,顏色等.
2.模擬 Ajax 請求,生成並返回模擬資料,mockjs可以進行強大的ajax攔截.能判斷請求型別,獲取到url,請求引數等.然後可以返回mock的假資料,或者你自己編好的json檔案.功能強大易上手.
3.基於 HTML 模板生成模擬資料
mockjs在本專案中使用:
- 安裝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群:602515