(獲取本節(jié)完整代碼 GitHub/chizijijiadami/vue-elementui-5)
0击狮、寫在前面
管理后臺該有的基本功能前面文章已經(jīng)寫完了,現(xiàn)在就寫最后一個很重要的權限控制益老。
說到權限肯定是要有登錄系統(tǒng)的彪蓬,那就涉及到驗證使用者的登錄有效性問題,一般系統(tǒng)服務壓力可控的項目是由后端直接進行 sessionid 操作來識別用戶登錄的捺萌,但若需要進行多業(yè)務系統(tǒng)整合档冬,或者用戶量龐大涉及分發(fā)服務器等情況時后端直接操作 sessionid 就顯得捉襟見肘了,這時token機制就該上場了桃纯。
這篇文章主要內容包括:
● Token驗證酷誓,登錄 / 退出
● 頁面+按鈕權限控制
1、Token驗證态坦,登錄 / 退出
這里從一個普通的SAP登錄退出功能開始盐数。
(1)添加接口和Mock數(shù)據(jù)
src>data>api>Login>index.js
import axiosApi from '@/common/utils/axiosApi'
import * as filter from './filter'
export function toLogin(params) {
return axiosApi({
url: '/toLogin',
method: 'post',
filter: filter.toLogin,
params: params
})
}
src>data>api>Login>filter.js
export const toLogin = {
request(params) {
return params
},
response(data) {
return data
}
}
攔截接口 src>data>mock>index.js
......
+ Mock.mock("/toLogin", "post", () => {
+ return {
+ status: 0,
+ data:{
+ token:"123"
+ },
+ message: "成功"
+ };
+ });
(2)新建登錄狀態(tài)記錄
src>common>utils>index.js
const TokenKey = 'Admin-Token'
const err = 'Error:保存到本地存儲失敗!'
const errlimt = 'Error:本地存儲超過限制!'
export function setStorage(key, value, exprise, type) {
return new Promise(resolve => {
// 默認7天過期(毫秒)
let valueDate = JSON.stringify({
value: value,
time: new Date().getTime(),
exprise: exprise || 60 * 60 * 24 * 7 * 1000,
type: type || ''
})
try {
window.localStorage.setItem(key || TokenKey, valueDate)
} catch (e) {
if (isQuotaExceeded(e)) {
window.localStorage.clear()
throw errlimt
} else {
throw err
}
}
resolve()
})
}
export function getStorage(key) {
if (window.localStorage.getItem(key || TokenKey)) {
let dataObj = JSON.parse(window.localStorage.getItem(key || TokenKey))
let isTimed = new Date().getTime() - dataObj.time > dataObj.exprise
if (isTimed) {
window.localStorage.removeItem(key || TokenKey)
return null
} else {
return dataObj.value
}
} else {
return null
}
}
// 非空判斷
export function isNotEmpty(value) {
return value !== undefined && value !== '' && value !== null
}
function isQuotaExceeded(e) {
let flag = false
if (e) {
if (e.code) {
switch (e.code) {
case 22:
flag = true
break
// fireFox
case 1014:
if (e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
flag = true
}
break
}
} else if (e.number === -2147024882) {
// ie
flag = true
}
}
return flag
}
(3)添加登錄 / 退出 按鈕
src>pages>Layout>Header.vue
<template>
<div class="app-header">
<Menu v-if="menuLocation==='H'" />
<el-button
v-if="menuLocation!=='H'"
type="primary"
plain
@click="setMenuIsCollapse"
:icon="isCollapse?'el-icon-s-fold':'el-icon-s-unfold'"
></el-button>
+ <el-button type="primary" v-if="!getStorage">
+ <router-link to="/login">登錄</router-link>
+ </el-button>
+ <el-button type="primary" v-else @click="quit">退出</el-button>
</div>
</template>
<script>
import Menu from "./Menu";
import { mapGetters } from "vuex";
+ import { getStorage, setStorage } from "common/utils";
export default {
components: {
Menu
},
computed: {
...mapGetters(["app"]),
isCollapse() {
return this.app.menu.isCollapse;
},
menuLocation() {
return this.app.menu.location;
},
+ getStorage() {
+ return getStorage();
+ }
},
methods: {
setMenuIsCollapse() {
this.$store.dispatch("setMenuIsCollapse");
},
+ quit() {
+ setStorage().then(() => {
+ this.$router.push({
+ path: "/login?redirect="+this.$router.history.current.fullPath
+ });
+ });
+ }
}
};
</script>
(4)新建登錄頁面
<template>
<div class="app-login">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="用戶名" prop="name" :rules="regCheck({required:true,min:2,max:30})">
<el-input v-model.trim="form.name" maxlength="30"></el-input>
</el-form-item>
<el-form-item label="密碼" prop="pwd" :rules="regCheck({required:true,min:6})">
<el-input v-model.trim="form.pwd" type="password" maxlength="20"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('form')">登錄</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as api from "data/api/Login";
export default {
name: "LoginIndex",
data() {
return {
form: {
name: "",
pwd: ""
}
};
},
methods: {
onSubmit(form) {
this.$refs[form].validate(valid => {
if (valid) {
api.toLogin(form).then(data => {
if (data.status === 0) {
setStorage(null, data.data.token).then(() => {
this.$router.push({
path: this.$route.query.redirect
? this.$route.query.redirect
: "/"
});
}
});
} else {
console.log("error submit!!");
return false;
}
});
}
}
};
</script>
<style lang="stylus" scoped>
.app-login
width 500px
margin 50px auto
</style>
(5)修改路由
src>router>index.js
......
+ {
+ path: '/login',
+ component: _import('Login/index')
+ },
{
path: '/404',
component: _import('ErrorPages/404')
},
(6)添加校驗規(guī)則
src>common>validate>index.js
if (_required) {
rules.push({
required: true,
validator: validatorCode.checkNotNull.bind(item),
trigger: _trigger
})
}
+ if (isNotEmpty(item.min) && (isNotEmpty(item.max) || isNotEmpty(item.maxLength))) {
+ rules.push({
+ min: item.min,
+ max: isNotEmpty(item.max) ? item.max : isNotEmpty(item.maxLength),
+ message: '字符長度在' + item.min + '至' + item.max + '之間!',
+ trigger: _trigger
+ })
+ } else if (isNotEmpty(item.min)) {
+ rules.push({
+ min: item.min,
+ message: '至少' + item.min + '個字符',
+ trigger: _trigger
+ })
+ } else if (isNotEmpty(item.max) || isNotEmpty(item.maxLength)) {
+ rules.push({
+ max: isNotEmpty(item.max) ? item.max : isNotEmpty(item.maxLength),
+ message: '至多' + item.max + '個字符',
+ trigger: _trigger
+ })
+ }
if (_type) {
添加一個工具文件 src>common>utils>index.js
// 非空判斷
export function isNotEmpty(value) {
return value !== undefined && value !== '' && value !== null
}
(7)修改全局路由守衛(wèi)
+ import { getStorage } from '../utils'
......
router.beforeEach(async (to, from, next) => {
document.title = getPageTitle(to.meta.title)
+ if (getStorage()) {
if (store.getters.app.menu.list.length === 0) {
store.dispatch("setMenuList", filterRouter(pagesRouterList))
next({ ...to, replace: true })
} else {
- next()
+ if (to.path === '/login') {
next('/')
+ } else {
+ next()
+ }
}
+ } else {
+ if (to.path === '/login') {
+ next()
+ } else {
+ next('/login')
+ }
+ }
})
2、權限對接
(1)頁面權限
a. 新建權限接口
src>data>api>Permission>index.js
import axiosApi from '@/common/utils/axiosApi'
import * as filter from './filter'
export function getPermission(params) {
return axiosApi({
url: '/permission',
method: 'get',
filter: filter.getPermission,
params: params
})
}
src>data>api>Permission>filter.js
export const getPermission = {
request(params) {
return params
},
response(data) {
return data
}
}
b. 新建權限接口Mock數(shù)據(jù)
每個路徑頁面都需要唯一標識符去識別伞梯,接口返回的 code 就是路由文件中的 name玫氢。
src>data>mock>permission>index.js
const Mock = require("mockjs");
Mock.mock("/permission", "get", () => {
return {
status: 0,
data: {
name: '測試',
code: 'test',
permission: {
page: {
code: 'pagePermission',
name: "頁面權限",
children: [
{
code: 'Index',
name: "首頁",
children: [
{
code: 'IndexIndex',
name: '首頁',
children: [
{
code: 'IndexIndex_save',
name: '保存'
}
]
}
]
},
{
code:'List',
name:'列表',
children:[
{
code:'ListDetai',
name:'詳情'
},
{
code:'ListFeature',
name:'特性'
}
]
}
]
},
api: {
code: 'apiPermission',
name: "接口權限",
children: []
}
}
},
message: "成功"
};
});
c. 修改mock引入文件,src>data>mock>index.js
+ import './permission'
......
const Mock = require("mockjs");
// 使用mockjs模擬數(shù)據(jù)
let dataList = Mock.mock({
......
d. 修改路由定義
這里要將 pagesRouterList 中不需要權限控制的路由提取出來賦值給 constantRouterList谜诫,這個 constantRouterList 會進行初始化漾峡,而由權限控制的路由則會從接口獲取后通過vue-router的方法 router-addroutes 動態(tài)添加到路由。
src>router>index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const _import = file => () => import('@/pages/' + file + '.vue')
export const constantRouterList = [
{
path: '/login',
component: _import('Login/index')
},
{
path: '/404',
component: _import('ErrorPages/404')
},
{
path: '*',
redirect: '/404'
},
{
path: '',
redirect: '/index/index'
}
]
export const pagesRouterList = [
{
path: '/index',
component: _import('Layout/index'),
redirect: '/index/index',
name: "Index",
meta: {
title: "首頁",
icon: "user",
isShow: true
},
children: [
{
path: 'index',
component: _import('Index/index'),
name: "IndexIndex",
meta: {
title: "首頁",
icon: "user",
isShow: false
}
}
]
},
{
path: '/list',
component: _import('Layout/index'),
name: "List",
meta: {
title: "列表",
icon: "document",
isShow: true
},
children: [
{
path: 'detail',
component: _import('List/Detail/index'),
name: "ListDetai",
meta: {
title: "詳情",
icon: "document",
isShow: true
}
},
{
path: 'feature',
component: _import('List/Feature/index'),
name: "ListFeature",
meta: {
title: "特性",
icon: "document",
isShow: true
}
}
]
}
]
export default new Router({
scrollBehavior() {
return { x: 0, y: 0 }
},
routes: constantRouterList
})
e. 添加權限狀態(tài)值
修改 src>data>store>modules>app.js
const app = {
state: {
system: {
title: "大米工廠",
},
+ auth: {
+ page: [],
+ btn: []
+ },
menu: {
isCollapse: false,
location: "V", //V喻旷、VH灰殴、H三個值,V表示在左側,VH表示橫跨頭部牺陶,H表示在頭部
list: [],
obj: {}
},
tabs: {
isShow: false
},
crumbs: {
isShow: false
},
footer: {
isShow: false
}
},
mutations: {
SET_MENU_ISCOLLAPSE: state => {
state.menu.isCollapse = !state.menu.isCollapse
},
SETMENU_LIST: (state, menuList) => {
state.menu.list = menuList
},
+ SET_AUTH: (state, auth) => {
+ state.auth.page = auth.page
+ state.auth.btn = auth.btn
+ }
},
actions: {
setMenuIsCollapse({ commit }) {
commit('SET_MENU_ISCOLLAPSE')
},
setMenuList({ commit }, menuList) {
commit('SETMENU_LIST', menuList)
},
+ setAuth({ commit }, auth) {
+ commit('SET_AUTH', auth)
+ }
}
}
export default app
f. 修改全局路由守衛(wèi)
src>common>routerFilter>index.js
- import { getStorage } from '../utils'
+ import { getStorage, setStorage } from '../utils'
......
router.beforeEach(async (to, from, next) => {
document.title = getPageTitle(to.meta.title)
if (getStorage()) {
if (store.getters.app.menu.list.length === 0) {
- store.dispatch("setMenuList", filterRouter(pagesRouterList))
- next({ ...to, replace: true })
+ filterRouter(pagesRouterList).then(data => {
+ if (data) {
+ store.dispatch("setAuth", data.auth)
+ store.dispatch("setMenuList", data.menuList).then(() => {
+ router.addRoutes(data.menuList)
+ console.log(to.path);
+ if (to.path === '/404') {
+ next('/')
+ } else {
+ next({ ...to, replace: true })
+ }
+ })
+ } else {
+ setStorage()
+ next('/login')
+ }
+ })
} else {
if (to.path === '/login') {
next('/')
} else {
next()
}
}
} else {
if (to.path === '/login') {
next()
} else {
next('/login')
}
}
})
src>common>routerFilter>filter.js
import { MessageBox } from 'element-ui'
import store from 'store'
+ import * as api from 'data/api/Permission'
export function filterRouter(pagesRouterList) {
- let mennuList = pagesRouterList.filter(ele => ele.meta && ele.meta.isShow)
- try {
- if (mennuList.length <= 0) throw "沒有可用菜單";
- filterPage(mennuList)
- return mennuList;
- } catch (err) {
- MessageBox({
- message: err,
- showCancelButton: false,
- confirmButtonText: '確定',
- type: 'error'
- })
- }
+ return api.getPermission().then(data => {
+ let auth = {
+ page: [],
+ btn: []
+ }
+ let permissionPage = data.data.permission.page;
+ try {
+ if (permissionPage.children && permissionPage.children.length <= 0) throw "您暫無權限請聯(lián)系管理員";
+ authArrFilter(permissionPage, auth)
+ let authRouter = authRouterFilter(pagesRouterList, auth)
+ let menuList = authRouter;
+ filterPage(menuList)
+ return { auth: auth, menuList: menuList };
+ } catch (err) {
+ MessageBox({
+ message: err,
+ showCancelButton: false,
+ confirmButtonText: '確定',
+ type: 'error'
+ })
+ }
+ })
}
+ function authArrFilter(page, auth) {
+ if (page.children) {
+ page.children.forEach(function (item) {
+ if (item.code.match(/_/)) {
+ auth.btn.push(item.code)
+ } else {
+ auth.page.push(item.code)
+ }
+ authArrFilter(item, auth)
+ })
+ }
+ }
+ function authRouterFilter(pagesRouterList, auth) {
+ function _filter(list) {
+ return list.filter(item => {
+ if (item.children && item.children.length) {
+ item.children = _filter(item.children)
+ }
+ return auth.page.includes(item.name)
+ })
+ }
+ return _filter(pagesRouterList)
+ }
function filterPage(menuList, pathFull, joinSign) {
let pathFullCurrent = pathFull || ""
let joinSignCurrent = joinSign || ""
for (let i = 0; i < menuList.length; i++) {
const ele = menuList[i];
ele.pathFull = pathFullCurrent + joinSignCurrent + ele.path
ele.showChildren = []
store.getters.app.menu.obj[ele.name] = ele
if (ele.children && ele.meta.isShow) {
ele.showChildren = ele.children.filter(ele2 => ele2.meta.isShow)
filterPage(ele.children, ele.pathFull, "/")
}
}
}
到這里頁面權限就好了伟阔,運行修改 權限Mock數(shù)據(jù),src>data>mock>permission>index.js
page: {
code: 'pagePermission',
name: "頁面權限",
children: [
......
{
code:'List',
name:'列表',
children:[
- {
- code:'ListDetai',
- name:'詳情'
- },
{
code:'ListFeature',
name:'特性'
}
]
}
]
},
g. 運行如下圖掰伸,詳情菜單沒有顯示皱炉,如果直接輸入 /list/detail 路徑會發(fā)現(xiàn)跳轉到404去了,因為沒有權限狮鸭。
(2)按鈕權限
這里要用到 Vue-directive 的知識合搅,前面已經(jīng)有了按鈕權限數(shù)組還是比較簡單的。
● 新建全局指令文件
src>common>directives>index.js
import store from 'data/store'
export default {
// 是否有按鈕權限判定
btnHas: {
inserted(el, binding) {
if (
!store.getters.app.auth.btn.includes(binding.value)
) {
if (!!window.ActiveXObject || 'ActiveXObject' in window) {
el.parentNode.removeChild(el)
} else {
el.remove()
}
}
}
}
}
● main.js 引入
......
//mockj數(shù)據(jù)
import 'data/mock'
+ // 全局directive指令
+ import directives from './common/directives'
+ // 注冊本頁全局指令方法
+ Object.keys(directives).forEach(key => {
+ Vue.directive(key, directives[key])
+ })
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
● 頁面使用
我們那下面圖上的兩個按鈕試一下
修改 src>pages>Index>index.vue
......
- <el-button type="primary" @click="submitForm('form')">提交</el-button>
- <el-button @click="resetForm('form')">重置</el-button>
+ <el-button type="primary" v-btnHas="'IndexIndex_save'" @click="submitForm('form')">提交</el-button>
+ <el-button v-btnHas="'IndexIndex_reset'" @click="resetForm('form')">重置</el-button>
.....
運行如下圖歧蕉,重置按鈕因為不在權限接口中所以不顯示了灾部。
我們再修改Mock權限接口
......
{
code: 'IndexIndex',
name: '首頁',
children: [
{
code: 'IndexIndex_save',
name: '保存'
},
+ {
+ code: 'IndexIndex_reset',
+ name: '重置'
+ }
]
}
......
可以看到因為賦權了,重置按鈕又出來了惯退。
到這里完整的管理后臺就寫好了赌髓,我們后續(xù)見。
感謝閱讀催跪,喜歡的話點個贊吧:)
更多內容請關注后續(xù)文章锁蠕。。。
一、vue入門基礎開發(fā)—手把手教你用vue開發(fā)
二溪掀、vue+ElementUI開發(fā)后臺管理模板—布局
三喻喳、vue+ElementUI開發(fā)后臺管理模板—功能、資源、全局組件
四、vue+ElementUI開發(fā)后臺管理模板—方法指令、接口數(shù)據(jù)