下面介紹兩種權限控制的方法:css
若是一個網站有不一樣的角色,好比管理員和普通用戶,要求不一樣的角色能訪問的頁面是不同的vue
這個時候咱們就能夠把全部的頁面都放在路由表裏,只要在訪問的時候判斷一下角色權限。若是有權限就讓訪問,沒有權限的話就拒絕訪問,跳轉到404頁面node
vue-router
在構建路由時提供了元信息meta
配置接口,咱們能夠在元信息中添加路由對應的權限,而後在路由守衛中檢查相關權限,控制其路由跳轉。ios
能夠在每個路由的 meta
屬性裏,將能訪問該路由的角色添加到 roles
裏。用戶每次登錄後,將用戶的角色返回。而後在訪問頁面時,把路由的 meta
屬性和用戶的角色進行對比,若是用戶的角色在路由的 roles
裏,那就是能訪問,若是不在就拒絕訪問。git
代碼示例1:vue-router
路由信息:vuex
routes: [
{
path: '/login',
name: 'login',
meta: {
roles: ['admin', 'user']
},
component: () => import('../components/Login.vue')
},
{
path: 'home',
name: 'home',
meta: {
roles: ['admin']
},
component: () => import('../views/Home.vue')
},
]
複製代碼
頁面控制:docker
//假設有兩種角色:admin 和 user
//從後臺獲取的用戶角色
const role = 'user'
//當進入一個頁面是會觸發導航守衛 router.beforeEach 事件
router.beforeEach((to,from,next)=>{
if(to.meta.roles.includes(role)){
next() //放行
}esle{
next({path:"/404"}) //跳到404頁面
}
})
複製代碼
代碼示例2express
固然也能夠用下面的一種方法:element-ui
// router.js
// 路由表元信息
[
{
path: '',
redirect: '/home'
},
{
path: '/home',
meta: {
title: 'Home',
icon: 'home'
}
},
{
path: '/userCenter',
meta: {
title: '我的中心',
requireAuth: true // 在須要登陸的路由的meta中添加響應的權限標識
}
}
]
// 在守衛中訪問元信息
router.beforeEach (to, from, next) {
let flag = to.matched.some(record=>record.meta.requireAuth);
//console.log(flag); //可本身打印出來看一下
}
複製代碼
能夠在多個路由下面添加這個權限標識,達到控制的目的
只要一切換頁面,就須要看有沒有這個權限,因此能夠在最大的路由下 main.js
中配置
存儲信息
通常的,用戶登陸後會在本地存儲用戶的認證信息,能夠用 token
、cookie
等,這裏咱們用 token
。
將用戶的token
保存到localStorage
裏,而用戶信息則存在內存store
中。這樣能夠在vuex
中存儲一個標記用戶登陸狀態的屬性auth
,方便權限控制。
代碼示例
// store.js
{
state: {
token: window.localStorage.getItem('token'),
auth: false,
userInfo: {}
},
mutations: {
setToken (state, token) {
state.token = token
window.localStorage.setItem('token', token)
},
clearToken (state) {
state.token = ''
window.localStorage.setItem('token', '')
},
setUserInfo (state, userInfo) {
state.userInfo = userInfo
state.auth = true // 獲取到用戶信息的同時將auth標記爲true,固然也能夠直接判斷userInfo
}
},
actions: {
async getUserInfo (ctx, token) {
return fetchUserInfo(token).then(response => {
if (response.code === 200) {
ctx.commit('setUserInfo', response.data)
}
return response
})
},
async login (ctx, account) {
return login(account).then(response => {
if (response.code === 200) {
ctx.commit('setUserInfo', response.data.userInfo)
ctx.commit('setToken', response.data.token)
}
})
}
}
}
複製代碼
寫好路由表和vuex以後,給全部路由設置一個全局守衛,在進入路由以前進行權限檢查,並導航到對應的路由。
// router.js
router.beforeEach(async (to, from, next) => {
if (to.matched.some(record => record.meta.requireAuth)) { // 檢查是否須要登陸權限
if (!store.state.auth) { // 檢查是否已登陸
if (store.state.token) { // 未登陸,可是有token,獲取用戶信息
try {
const data = await store.dispatch('getUserInfo', store.state.token)
if (data.code === 200) {
next()
} else {
window.alert('請登陸')
store.commit('clearToken')
next({ name: 'Login' })
}
} catch (err) {
window.alert('請登陸')
store.commit('clearToken')
next({ name: 'Login' })
}
} else {
window.alert('請登陸')
next({ name: 'Login' })
}
} else {
next()
}
} else {
next()
}
})
複製代碼
上述的方法是基於jwt
認證方式,本地不持久化用戶信息,只保存token
,當用戶刷新或者從新打開網頁時,進入須要登陸的頁面都會嘗試去請求用戶信息,該操做在整個訪問過程當中只進行一次,直到刷新或者從新打開,對於應用後期的開發維護和擴展支持都很好。
有時候爲了安全,咱們須要根據用戶權限或者是用戶屬性去動態的添加菜單和路由表,能夠實現對用戶的功能進行定製。vue-router
提供了addRoutes()
方法,能夠動態註冊路由,須要注意的是,動態添加路由是在路由表中push
路由,因爲路由是按順序匹配的,所以須要將諸如404頁面這樣的路由放在動態添加的最後。
代碼示例
// store.js
// 將須要動態註冊的路由提取到vuex中
const dynamicRoutes = [
{
path: '/manage',
name: 'Manage',
meta: {
requireAuth: true
},
component: () => import('./views/Manage')
},
{
path: '/userCenter',
name: 'UserCenter',
meta: {
requireAuth: true
},
component: () => import('./views/UserCenter')
}
]
複製代碼
在vuex
中添加userRoutes
數組用於存儲用戶的定製菜單。在setUserInfo中根據後端返回的菜單生成用戶的路由表。
// store.js
setUserInfo (state, userInfo) {
state.userInfo = userInfo
state.auth = true // 獲取到用戶信息的同時將auth標記爲true,固然也能夠直接判斷userInfo
// 生成用戶路由表
state.userRoutes = dynamicRoutes.filter(route => {
return userInfo.menus.some(menu => menu.name === route.name)
})
router.addRoutes(state.userRoutes) // 註冊路由
}
複製代碼
修改菜單渲染
// App.vue
<div id="nav">
<router-link to="/">主頁</router-link>
<router-link to="/login">登陸</router-link>
<template v-for="(menu, index) of $store.state.userInfo.menus">
<router-link :to="{ name: menu.name }" :key="index">{{menu.title}}</router-link>
</template>
</div>
複製代碼
若是前面的看完了,以爲本身瞭解的能夠了,其實就不須要往下面看了,固然,你也能夠看一下這個案例,嘗試本身寫一下。
友情提示:這個案例可能會有點難,有些邏輯比較很差理解,最好親自上手寫一下,下去本身慢慢慢慢消化
那麼就讓咱們開始吧:
新建一個項目,刪去不須要的文件和代碼
後端準備數據
在src同級目錄下,新建一個server.js
文件,以下:
代碼:
其中涉及到跨域問題,具體參考個人文章《常見的跨域解決方案(全)》
//Server.js
let express = require('express');
let app = express();
//在後端配置,讓全部人均可以訪問api接口 其中設計到跨域問題,具體參考個人文章《常見的跨域解決方案(全)》
app.use('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
//Access-Control-Allow-Headers ,可根據瀏覽器的F12查看,把對應的粘貼在這裏就行
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
app.get('/role',(req,res)=>{
res.json({
//假如這是後端給咱們的JSON數據
menuList:[
{pid:-1,path:'/cart',name:'購物車',id:1,auth:'cart'},
{pid:1,path:'/cart/cart-list',name:'購物車列表',id:4,auth:'cart-list'},
{pid:4,path:'/cart/cart-list/lottery',auth:'lottery',id:5,name:'彩票'},
{pid:4,path:'/cart/cart-list/product',auth:'product',id:6,name:'商品'},
{pid:-1,path:'/shop',name:'商店',id:2,auth:'shop'},
{pid:-1,path:'/profile',name:'我的中心',id:3,auth:'profile'},
],
buttonAuth:{
edit:true, // 可編輯
}
})
})
//監聽3000端口
app.listen(3000);
複製代碼
而後測試端口數據能不能得到(使用node指令):
使用瀏覽器訪問3000端口下的/role
若是能夠得到數據,則說明端口沒問題
而後把下面這些對應的頁面用vue的基本骨架寫出來
在router.js
中配置路由
import Vue from 'vue'
import Router from 'vue-router'
//引入組件
import Home from "./views/Home.vue"
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
//配置路由
routes: [
{
//訪問'/'時,重定向到home頁面
path:'/',
redirect:'/home'
},
{
path:'/home',
name:'home',
component:Home
},
{
path:'/cart',
name:'cart',
//使用懶加載,當使用這個組件的時候再加載資源,當組件資源較大時,不建議使用,可能會出現白屏現象
//並且最好使用絕對路徑,@是絕對路徑的意思,至關於src下
component:()=>import('@/components/menu/cart.vue'),
//配置子路由
children:[
{
//當配置子路由時,最好不要在前面加'/',好比:'/cart-list'
path:'cart-list',
name:'cart-list',
component:()=>import('@/components/menu/cart-list.vue'),
//配置子路由
children:[
{
path:'lottery',
name:'lottery',
component:()=>import('@/components/menu/lottery.vue')
},
{
path:'product',
name:'product',
component:()=>import('@/components/menu/product.vue')
}
]
}
]
},
{
path:'/profile',
name:'profile',
component:()=>import('@/components/menu/profile.vue')
},
{
path:'/shop',
name:'shop',
component:()=>import('@/components/menu/shop.vue')
},
{
path:'*',
component:()=>import('@/views/404.vue')
}
]
})
複製代碼
在瀏覽器地址欄中輸入路由,測試,確定是ok的,這裏就不貼圖了
路由配置完畢後,後端給咱們返回的數據如圖所示:
咱們須要把上面的數據轉換成咱們須要的數據格式,並保存在vuex中,(其中涉及到異步問題,具體參考個人文章《JS中的異步解決方案》),以下:
//store.js
//重難點代碼 很難理解 須要多看幾遍慢慢理解
let formatMenuList = (menuList) => {
function r(pid) {
//filter過濾數組,返回一個知足要求的數組
return menuList.filter(menu => {
//格式化菜單變成咱們須要的結果
if (menu.pid === pid) {
let children = r(menu.id);
menu.children = children.length ? children : null;
return true;
}
})
}
return r(-1);
}
actions: {
//異步 async await 參考個人文章<異步解決方案>
async getMenuList({ commit }) {
//去server.js(後端api)獲取數據
let { data } = await axios.get('http://localhost:3000/role');
let menuList = data.menuList
//把獲取的數據傳入上面寫的方法裏,轉換格式
menuList = formatMenuList(menuList)
//配置徹底局路由(main.js)後,可本身打印出來看一下
// console.log(menuList);
},
}
複製代碼
須要保存成的數據格式,拿我以前寫過的一個樹形數據來讓你們看看,只看結構就好,不用看數據:
treeData: {
title: "Web全棧架構師",
children: [
{
title: "Java架構師"
},
{
title: "JS高級",
children: [
{
title: "ES6"
},
{
title: "動效"
}
]
},
{
title: "Web全棧",
children: [
{
title: "Vue訓練營",
expand: true,
children: [
{
title: "組件化"
},
{
title: "源碼"
},
{
title: "docker部署"
}
]
},
{
title: "React",
children: [
{
title: "JSX"
},
{
title: "虛擬DOM"
}
]
},
{
title: "Node"
}
]
}
]
}
複製代碼
而後在main.js
中添加路由 (涉及到導航守衛問題,請參考個人文章Vue 導航守衛(路由的生命週期)) 以下:
//main.js
//只要頁面切換就執行的鉤子
//根據權限動態添加路由(咱們的路由要分紅兩部分:一部分是有權限的,另外一部分是沒有權限的)
router.beforeEach(async (to,from,next)=>{
//判斷當前有沒有獲取過權限,若是獲取過了,就不要再獲取了
if(!store.state.hasRules){
//獲取權限,調用獲取權限的接口,去action中獲取數據
await store.dispatch('getMenuList')
}else{
//若是已經獲取了權限就能夠訪問頁面了
next()
}
})
複製代碼
而後訪問主頁面,效果以下:
在上面的數據有個auth項,表明有沒有這個權限。若是沒有這個權限,到時候會把這個菜單從路由規則中刪除掉。把全部的auto都過濾出來,以下:
而後在主頁面測試以下(記的把上面的console.log代碼的註釋解開):
若是把後端數據(menuList)改變了,那麼從新刷新一下頁面,那麼就會動態的改變數據,這裏就不演示了,本身能夠註釋一個看一下
接下來渲染菜單數據,
在main.js
中 使用element-ui:
//main.js
//安裝 element-ui 並使用
import ElementUI from "element-ui"
//引入element-ui裏的樣式
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
複製代碼
把咱們剛纔獲得的數據渲染到Home上面:
在 Home 中使用mapState
方法:
當在咱們渲染的時候,咱們須要一個遞歸組件來動態的獲取傳遞過來的數據,動態的進行渲染,因此確定是不能寫死的,遞歸組件的使用請參考個人文章《Vue 遞歸組件(構建樹形菜單)》,這裏不作過多介紹,直接拿來用
在 views 下面添加一個 ReSubMenu
組件,代碼以下:
<template>
<el-submenu :index="data.path">
<template slot="title">
<router-link :to="data.path">{{data.name}}</router-link>
</template>
<template v-for="c in data.children">
<ReSubMenu :key="c.auth" v-if="data.children" :data="c">
</ReSubMenu>
<el-menu-item :key="c.auth" v-else :index="c.path">
{{c.name}}
</el-menu-item>
</template>
</el-submenu>
</template>
<script>
export default {
name:"ReSubMenu",
props:{
data:{
type:Object,
default:()=>({})
}
}
}
</script>
複製代碼
而後再 Home 中使用這個組件
template 中的代碼以下:
<template>
<div class='home'>
<!-- <router-view></router-view> -->
<el-menu default-active="2" class="el-menu-vertical-demo" :router="true">
<template v-for="m in menuList">
<!-- 渲染確定是不能寫死的,因此我使用以前封裝過的遞歸組件,具體內容參考個人文章<Vue 遞歸組件(構建樹形菜單)> -->
<ReSubMenu :data="m" :key="m.auth" v-if="m.children"></ReSubMenu>
<el-menu-item v-else :key="m.auth" :index="m.path">{{m.name}}</el-menu-item>
</template>
</el-menu>
<!-- <router-view></router-view> -->
</div>
</template>
複製代碼
效果圖:
這樣就能根據傳遞過來的數據動態的生成咱們須要的結構
可是若是後端沒有給咱們返回profile,也是攔不住的,以下:
這樣也是能夠訪問到 profile 的,以下:
這樣是不行的,由於後端並無給咱們返回 profile
如何解決?
答:根據權限動態添加路由 (咱們的路由要分紅兩部分 一部分是有權限 另外一部分是沒權限的)
修改 router.js
中的代碼以下:
import Vue from 'vue'
import Router from 'vue-router'
//引入組件
import Home from "./views/Home.vue"
Vue.use(Router)
let defaultRoutes = [
{
//訪問'/'時,重定向到home頁面
path:'/',
redirect:'/home'
},
{
path:'/home',
name:'home',
component:Home
},
{
path:'*',
component:()=>import('@/views/404.vue')
}
]
export let authRoutes = [
{
path:'/cart',
name:'cart',
//使用懶加載,當使用這個組件的時候再加載資源,當組件資源較大時,不建議使用,可能會出現白屏現象
//並且最好使用絕對路徑,@是絕對路徑的意思,至關於src下
component:()=>import('@/components/menu/cart.vue'),
//配置子路由
children:[
{
//當配置子路由時,最好不要在前面加'/',好比:'/cart-list'
path:'cart-list',
name:'cart-list',
component:()=>import('@/components/menu/cart-list.vue'),
//配置子路由
children:[
{
path:'lottery',
name:'lottery',
component:()=>import('@/components/menu/lottery.vue')
},
{
path:'product',
name:'product',
component:()=>import('@/components/menu/product.vue')
}
]
}
]
},
{
path:'/profile',
name:'profile',
component:()=>import('@/components/menu/profile.vue')
},
{
path:'/shop',
name:'shop',
component:()=>import('@/components/menu/shop.vue')
}
]
//須要查看用戶權限來動態添加路由
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
//默承認以訪問的路由
routes: defaultRoutes
})
複製代碼
在stroe.js
中導入:
在store.js
中添加代碼:
修改main.js
中的代碼(重點):
測試ok:
固然你也能夠再註釋一個測試一下
因此這個路由是由後端返回的,若是沒有權限,根本看不到
好了,這個案例就到此爲止了
案例源碼:源碼地址
本章就到此結束了哦