最近完成了個人小愛ADMIN後臺管理系統基本功能,同時進行了頁面總體佈局和樣式的全新改版。新增了系統權限功能的實現,同時以爲後臺系統全部的菜單都左置,會限制菜單的擴展,所以我改進了三級菜單的顯示。前端
權限路由思路: 根據用戶登陸的roles信息與路由中配置的roles信息進行比較過濾,生成能夠訪問的路由表,並經過router.addRoutes(store.getters.addRouters)動態添加可訪問權限路由表,從而實現左側和頂欄菜單的展現。vue
在router/index.js中,給相應的菜單設置默認的roles信息; 以下:node
給"權限設置"菜單設置的權限爲:webpack
{
path: '/permission',
name: 'permission',
meta: {
title: '權限設置',
roles: ['admin', 'editor'] //不一樣的角色均可以看到
}
}
複製代碼
給其子菜單"頁面權限",設置權限爲:ios
{
path: 'page',
name: 'pagePer',
meta: {
title: '頁面權限',
roles: ['admin'] //只有"admin"能夠看到該菜單
},
component: () => import('@/page/permission/page'),
}
複製代碼
給其子菜單"按鈕權限"設置權限爲:git
{
path: 'directive',
name: 'directivePer',
meta: {
title: '按鈕權限',
roles:['editor'] //只有"editor"能夠看到該菜單
},
component: () => import('@/page/permission/directive'),
}
複製代碼
代碼以下:github
function hasPermission(roles, permissionRoles) {
if (roles.indexOf('admin') >= 0) return true
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並存入了cookie,保證頁面刷新時,始終能夠拿到token
if (getToken('Token')) {
if(to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
// 用戶登陸成功以後,每次點擊路由都進行了角色的判斷;
if (store.getters.roles.length === 0) {
let token = getToken('Token');
getUserInfo({"token":token}).then().then(res => { // 根據token拉取用戶信息
let userList = res.data.userList;
store.commit("SET_ROLES",userList.roles);
store.commit("SET_NAME",userList.name);
store.commit("SET_AVATAR",userList.avatar);
store.dispatch('GenerateRoutes', { "roles":userList.roles }).then(() => { // 根據roles權限生成可訪問的路由表
router.addRoutes(store.getters.addRouters) // 動態添加可訪問權限路由表
next({ ...to, replace: true }) // hack方法 確保addRoutes已完成
})
}).catch((err) => {
store.dispatch('LogOut').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)
})
複製代碼
一、路由對象區分權限路由對象和非權限路由對象;初始化時,將非權限路由對象賦值給Router;同時設置權限路由中的meta對象,如:meta:{roles:['admin','editor']},表示該roles所擁有的路由權限;web
二、經過用戶登陸成功以後返回的roles值,進行路由的匹配並生成新的路由對象;ajax
三、用戶成功登陸並跳轉到首頁時,根據剛剛生成的路由對象,渲染左側的菜單;即,不一樣的用戶看到的菜單是不同的;vuex
一、用戶點擊登陸按鈕,經過路由導航鉤子router.beforeEach()函數肯定下一步的跳轉邏輯,以下:
1.一、用戶已經登陸成功過,並從cookie中拿到了token值;
1.1.一、用戶訪問登陸頁面,直接定位到登陸頁面;
1.1.一、用戶訪問非登陸頁面,須要根據用戶是否有roles信息,進行不一樣的業務邏輯,以下:
(1)、初始狀況下,用戶roles信息爲空;
1.經過getUserInfo()函數,根據token拉取用戶信息;並經過store將該用戶roles,name,avatar信息存儲於vuex;
2.經過store.dispatch('GenerateRoutes', { roles })去從新過濾和生成路由,經過router.addRoutes()合併路由表;
3.若是在獲取用戶信息接口時出現錯誤,則調取store.dispatch('LogOut')接口,返回到login頁面;
(2)、用戶已經擁有roles信息;
1.點擊頁面路由,經過roles權限判斷 hasPermission()。若是用戶有該路由權限,直接跳轉對應的頁面;若是沒有權限,則跳轉至401提示頁面;
複製代碼
2.用戶點擊退出,token已被清空
1.若是設置了白名單用戶,則直接跳轉到相應的頁面;
2.反之,則跳轉至登陸頁面;
複製代碼
詳細代碼,請參考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()將全部的三級菜單進行過濾添加,添加完成以後,繼續進行角色過濾,能夠保證將不具有權限的頂部菜單也過濾掉。
// src/store/permission.js,經過循環過濾,生成新的二級菜單
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;
}
複製代碼
在組件topMenu.vue中,用戶默認進來或者點擊左側菜單,觸發setLeftInnerMenu()函數,以下:
setLeftInnerMenu(){
const titleList = this.$route.matched[1].meta.titleList;
const currentTitle = titleList && this.$route.matched[2].meta.title;
if( titleList && this.$route.matched[1].meta.routerType === 'leftmenu'){ // 點擊的爲 左側的2級菜單
this.$store.dispatch('ClickLeftInnerMenu',{'titleList':titleList});
this.$store.dispatch('ClickTopMenu',{'title':currentTitle});
}else{ // 點擊左側1級菜單
this.$store.dispatch('ClickLeftInnerMenu',{'titleList':[]});
this.$store.dispatch('ClickTopMenu',{'title':''});
}
}
複製代碼
經過當前路由this.store觸發異步動做'ClickLeftInnerMenu'並傳遞參數'name',vuex中經過state.topRouters = filterTopRouters(state.routers,data)過濾當前路由信息;代碼以下:
// src/store/permission.js,獲取到當前路由對應頂部子菜單
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'])}進行對應頂部路由數據的展現。用戶每次點擊左側菜單時,頂部路由都進行了從新賦值並渲染,保證了數據的準確性。
當頂部菜單的數據量過大時,咱們須要設置橫向滾動條並設置滾動條的樣式。 如圖:
在使用easy-mock模擬數據的過程當中,發現其對錶格固定數據不能實現增刪改等功能,而且因爲它們是免費提供服務,致使用戶量較大時,服務器常常沒法訪問,於是選擇了使用mockjs進行本地數據模擬。
Mock.js是一款模擬數據生成器,旨在幫助前端攻城師獨立於後端進行開發,幫助編寫單元測試。提供瞭如下模擬功能:
1.根據數據模板生成模擬數據,經過mockjs提供的方法,你能夠輕鬆地創造大量隨機的文本,數字,布爾值,日期,郵箱,連接,圖片,顏色等.
2.模擬 Ajax 請求,生成並返回模擬數據,mockjs能夠進行強大的ajax攔截.能判斷請求類型,獲取到url,請求參數等.而後能夠返回mock的假數據,或者你本身編好的json文件.功能強大易上手.
3.基於 HTML 模板生成模擬數據
npm install mockjs --save-dev
複製代碼
如圖:
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官網文檔,上面有詳細的語法說明;
以下:
import './mockjs' //引用mock
複製代碼
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
})
}
複製代碼
因爲項目早期使用vue-cli2.0構建項目,須要進行繁瑣的webpack配置;vue-cli 3.0集成了webpack配置並在性能提高上作了很大優化。由於本項目使用vue-cli3.0進行構建和升級。現將相關注意事項總結以下,詳細文檔,請參考官網介紹。
Vue CLI 的包名稱由 vue-cli 改爲了 @vue/cli。 若是你已經全局安裝了舊版本的 vue-cli (1.x 或 2.x),你須要先經過
npm uninstall vue-cli -g 或 yarn global remove vue-cli
複製代碼
卸載它。 Vue CLI 須要 Node.js 8.9 或更高版本 (推薦 8.11.0+)。你可使用 nvm 或 nvm-windows 在同一臺電腦中管理多個 Node 版本。
npm install -g @vue/cli
# OR
yarn global add @vue/cli
複製代碼
若是但願還保留 vue-cli2.x 的語法或使用 2.x 的模板,建議安裝 cli-init
npm install -g @vue/cli-init
# OR
yarn global add @vue/cli-init
複製代碼
vue create 項目名稱;
安裝步驟選取相關的配置信息便可,直到完成。
在根目錄下新建文件.env.development和.env.production,分別表示開發環境和生成環境配置;主要用於定義環境變量,並經過npm run serve或npm run build集成到不一樣的環境中,供接口調用。代碼以下:
.env.development
NODE_ENV = development
VUE_APP_URL = "https://easy-mock.com/mock/5cd03667adb0973be6a3d8d1/api"
複製代碼
.env.production
NODE_ENV = production
VUE_APP_URL = "https://easy-mock.com/mock/5cd03667adb0973be6a3d8d1/api"
複製代碼
使用方法,如本項目中,配置在utils/env.js中,代碼以下:
// development和production環境是不一樣的
let app_url = process.env.VUE_APP_URL
export default {
app_url
}
複製代碼
因爲使用vue-cli3.x生成項目,webpack相關配置已經集成到node_module中,若是但願對 webpack 等進行細緻化配置,須要在項目根目錄下新建文件vue.config.js,具體配置可參考文檔,下面是一份基本配置。
const TerserPlugin = require('terser-webpack-plugin') // 用於在生成環境剔除debuger和console
const path = require('path');
const resolve = dir => {
return path.join(__dirname, dir);
};
const env = process.env.NODE_ENV
let target = process.env.VUE_APP_URL // development和production環境是不一樣的
module.exports = {
publicPath: '/',
outputDir: './dist',
lintOnSave: false, // 關閉eslint
// 打包時不生成.map文件
productionSourceMap: false,
devServer: {
open: true,
host: '0.0.0.0',
port: 8808
// 因爲本項目數據經過easy-mock和mockjs模擬,不存在跨域問題,無需配置代理;
// proxy: {
// '/v2': {
// target: target,
// changeOrigin: true
// }
// }
},
// webpack相關配置
chainWebpack: (config) => {
config.entry.app = ['./src/main.js'];
config.resolve.alias
.set('@', resolve('src'))
.set('cps', resolve('src/components'))
},
configureWebpack:config => {
// 爲生產環境修改配置...
if (process.env.NODE_ENV === 'production') {
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
})
} else {
// 爲開發環境修改配置...
}
},
// 第三方插件配置
pluginOptions: {
}
}
複製代碼
項目配置完成好,安裝好全部的依賴包。執行開發環境打包命令:npm run serve,便可運行項目;執行生成環境打包命令:npm run build,便可生成生產環境文件。
項目開發至此,一些基本的功能都已經完成,基本上可以知足項目須要。下篇文章會繼續介紹"項目分享功能的實現細節"、"項目部署細節及注意事項(包括如何部署子目錄)"、"項目性能優化細節",但願你們敬請期待~
項目說明:
小愛ADMIN是徹底開源免費的管理系統集成方案,能夠直接應用於相關後臺管理系統模板;不少重點地方都作了詳細的註釋和解釋。若是你也同樣喜歡前端開發,歡迎加入咱們的討論/學習羣,羣內能夠提問答疑,分享學習資料; 歡迎加入答疑qq羣。