前端權(quán)限:因?yàn)榍岸吮緛?lái)就是不安全的,真正的安全還是需要后端去把關(guān),所以后端也必須按做權(quán)限控制!我們前端的權(quán)限校驗(yàn)主要的目的是過(guò)濾不該有的請(qǐng)求和操作线梗,減少服務(wù)端壓力
一般來(lái)說(shuō)前端權(quán)限在三個(gè)方面:接口權(quán)限、按鈕權(quán)限怠益,路由權(quán)限(菜單權(quán)限)
菜單權(quán)限實(shí)現(xiàn)步驟:
1.拆分動(dòng)態(tài)路由和靜態(tài)路由
原因:由于不同用戶登錄以后應(yīng)該有不同的權(quán)限仪搔,可以看到不同的內(nèi)容,因此需要給不同的用戶在菜單上分配不同的權(quán)限溉痢。
菜單權(quán)限最重要的一點(diǎn)就是:當(dāng)獲取用戶信息的時(shí)候僻造,服務(wù)器會(huì)把響應(yīng)的用戶擁有菜單的權(quán)限信息返回,需要根據(jù)用戶身份對(duì)比出孩饼,當(dāng)前這個(gè)用戶需要展示哪些菜單髓削。
靜態(tài)路由:不管用戶是什么角色,都可以看到的路由镀娶,比如登錄立膛、首頁(yè)、404;
動(dòng)態(tài)路由:不同的用戶需要過(guò)濾選出的路由宝泵;
//靜態(tài)路由
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import Layout from '@/layout'
// 靜態(tài)路由:不管是什么角色都可以看見(jiàn):登錄頁(yè)面好啰,404,首頁(yè)
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
// 重定向
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首頁(yè)', icon: 'dashboard' }
}
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
// 動(dòng)態(tài)路由:不同角色需要過(guò)濾篩選出的路由
export const asyncRoutes = [
// 權(quán)限路由
{
name: 'Acl',
path: '/acl',
component: Layout,
redirect: '/acl/user/list',
meta: {
title: '權(quán)限管理',
icon: 'el-icon-lock'
},
children: [
{
name: 'User',
path: 'user/list',
component: () => import('@/views/acl/user/list'),
meta: {
title: '用戶管理'
}
}
]
},
{
path: '/product',
component: Layout,
name: 'Product',
// title:左側(cè)側(cè)邊欄名稱 icon小圖標(biāo)
meta: { title: '商品管理', icon: 'el-icon-goods' },
children: [
{
path: 'trademark',
name: 'TradeMark',
component: () => import('@/views/product/tradeMark'),
meta: { title: '品牌管理' }
},
]
}儿奶,
{
path: '/test',
component: Layout,
name: 'Test',
// title:左側(cè)側(cè)邊欄名稱 icon小圖標(biāo)
meta: { title: '測(cè)試管理', icon: 'el-icon-goods' },
children: [
{
path: 'test1',
name: 'test1',
component: () => import('@/views/Test/Test1'),
meta: { title: '測(cè)試管理1' }
},
2.分析登錄業(yè)務(wù)中的內(nèi)容
<el-button type="primary" @click.native.prevent="handleLogin">登錄</el-button>
// 登錄業(yè)務(wù):發(fā)請(qǐng)求框往,帶著用戶名和密碼給服務(wù)器(成功與失敗)
handleLogin() {
// 驗(yàn)證表單元素(用戶名與密碼)的是否符合規(guī)則
this.$refs.loginForm.validate(async valid => {
// 如果符合驗(yàn)證規(guī)則
if (valid) {
// 按鈕會(huì)有一個(gè)loading效果
this.loading = true
// 派發(fā)一個(gè)action:user/login闯捎,帶著用戶名和密碼的載荷
await this.$store.dispatch('user/login', this.loginForm)
// 登陸成功進(jìn)行路由跳轉(zhuǎn)
this.$router.push({ path: this.redirect || '/' })
// loading效果結(jié)束
this.loading = false
} else {
console.log('error submit!!')
return false
}
})
}
用戶信息模塊椰弊,主要功能:
1、登錄獲取token瓤鼻,存儲(chǔ)到Cookies中
2秉版、獲取用戶信息,存儲(chǔ)到Vuex中
import { login, logout, getInfo } from '@/api/user'
// 獲取token|設(shè)置token|刪除token的函數(shù)
import { getToken, setToken, removeToken } from '@/utils/auth'
// 引入路由中重置路由的方法
import { resetRouter, asyncRoutes, constantRoutes, anyRoutes } from '@/router'
import router from '@/router'
import cloneDeep from 'lodash/cloneDeep'
const getDefaultState = () => {
return {
// 獲取token
token: getToken(),
// 存儲(chǔ)用戶名
name: '',
// 存儲(chǔ)用戶頭像
avatar: '',
// 存儲(chǔ)菜單標(biāo)記(根據(jù)不同的角色茬祷,返回的標(biāo)記信息清焕,數(shù)組里面的元素是字符串)
routes: [],
// 存儲(chǔ)角色
roles: [],
// 按鈕權(quán)限
buttons: [],
// 項(xiàng)目中已有的異步路由和服務(wù)器返回的標(biāo)記信息對(duì)比之后,最終需要展示的路由
resultAsyncRoutes: [],
// 用戶最終需要展示的全部路由
resultAllRoutes: []
}
}
const state = getDefaultState()
const mutations = {
// 存儲(chǔ)token
SET_TOKEN: (state, token) => {
state.token = token
},
// 存儲(chǔ)用戶信息
SET_USERINFO: (state, userInfo) => {
// 用戶名
state.name = userInfo.name
// 用戶頭像
state.avatar = userInfo.avatar
// 菜單權(quán)限的標(biāo)記
state.routes = userInfo.routes
// 按鈕權(quán)限的標(biāo)記
state.button = userInfo.button
// 角色
state.roles = userInfo.roles
},
}
const actions = {
// user login
// 處理登陸的業(yè)務(wù)
async login({ commit }, userInfo) {
// 解構(gòu)出用戶名和密碼
const { username, password } = userInfo
const result = await login({ username: username.trim(), password: password })
// 注意:當(dāng)前登陸的請(qǐng)求 使用的是mock數(shù)據(jù)祭犯,mock數(shù)據(jù)code是20000
if (result.code === 20000) {
// vuex存儲(chǔ)token秸妥,修改state必須通過(guò)mutations
commit('SET_TOKEN', result.data.token)
// 本地持久化存儲(chǔ)token
setToken(result.data.token)
return 'ok'
} else {
return Promise.reject(new Error('faile'))
}
},
// 獲取用戶信息
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then((response) => {
// 獲取用戶信息:返回?cái)?shù)據(jù)包括用戶名,用戶頭像盹憎,routes(返回的標(biāo)志:不同的用戶應(yīng)該展示哪些菜單的標(biāo)記)
// (roles:用戶角色信息)(button:按鈕信息筛峭,按鈕權(quán)限使用的標(biāo)記)
const { data } = response
// vuex存儲(chǔ)用戶的全部信息
commit('SET_USERINFO', data)
// 提交數(shù)據(jù)
commit('SET_RESULTASYNCROUTES', computedAsyncRoutes(cloneDeep(asyncRoutes), data.routes))
resolve(data)
})
.catch((error) => {
reject(error)
})
})
}
import Cookies from 'js-cookie'
const TokenKey = 'vue_admin_template_token' // 設(shè)置一個(gè)獨(dú)一無(wú)二的key
export function getToken() {
return Cookies.get(TokenKey) //get獲取token
}
export function setToken(token) {
return Cookies.set(TokenKey, token) // set設(shè)置token
}
export function removeToken() {
return Cookies.remove(TokenKey) //remove移除token
}
3.在全局路由前置守衛(wèi)中判斷是否有token铐刘,要訪問(wèn)的是否是登錄頁(yè)面陪每,是否有用戶信息
// 全局前置守衛(wèi)
router.beforeEach(async(to, from, next) => {
// 開(kāi)啟進(jìn)度條
NProgress.start()
document.title = getPageTitle(to.meta.title)
const hasToken = getToken()
// 如果有token
if (hasToken) {
// 如果要訪問(wèn)的是登錄頁(yè)面
if (to.path === '/login') {
// 跳到主頁(yè),有token不用處理
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
// 如果有用戶信息
if (hasGetUserInfo) {
next()
} else {
try {
// 獲取用戶信息
await store.dispatch('user/getInfo')
next()
} catch (error) {
// 移除token
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
// 沒(méi)有token
if (whiteList.indexOf(to.path) !== -1) {
// 在白名單登錄頁(yè)面中镰吵,直接進(jìn)入
next()
} else {
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
4.對(duì)比異步路由檩禾,相同則展示對(duì)應(yīng)路由
// 定義一個(gè)函數(shù):兩個(gè)數(shù)組進(jìn)行對(duì)比,對(duì)比出當(dāng)前用戶顯示哪些異步路由
const computedAsyncRoutes = (asyncRoutes, routes) => {
// 過(guò)濾出當(dāng)前用戶(超級(jí)管理員疤祭,普通員工)需要展示的異步路由
return asyncRoutes.filter(item => {
// 數(shù)組中沒(méi)有這個(gè)元素返回索引值-1盼产,如果有,返回的索引值一定不是-1
if (routes.indexOf(item.name) !== -1) {
// 遞歸:還有2,3,4勺馆,級(jí)多級(jí)路由的情況
if (item.children && item.children.length) {
item.children = computedAsyncRoutes(item.children, routes)
}
return true
}
})
}
5.菜單權(quán)限設(shè)置成功
const mutations = {
// 最終計(jì)算出來(lái)的異步路由
SET_RESULTASYNCROUTES: (state, asyncRoutes) => {
// vuex保存當(dāng)前用戶的異步路由戏售,用戶需要展示的完整路由包括常量,異步草穆,任意路由
state.resultAsyncRoutes = asyncRoutes
// 計(jì)算出當(dāng)前用戶需要展示的所有路由,合并
state.resultAllRoutes = constantRoutes.concat(state.resultAsyncRoutes, anyRoutes)
// 給路由器添加新的路由
router.addRoutes(state.resultAllRoutes)
}
}