Ниже описаны два метода контроля разрешений:
- Метаинформация о маршрутизации (мета)
- Динамическая загрузка меню и маршрутов (addRoutes)
Метаинформация о маршрутизации (мета)
Если у веб-сайта разные роли, напримерадминистратора такжеобычный пользователь, страницы, для доступа к которым требуются разные роли, отличаются
В это время мы можемПоместить все страницы в таблицу маршрутизации,если толькоОпределить права доступа при доступе. Если у вас есть разрешение, разрешите доступ, если у вас нет разрешения, запретите доступ и перейдите на страницу 404
vue-routerМетаинформация была предоставлена при построении маршрутаmetaЧтобы настроить интерфейс, мы можем добавить разрешения, соответствующие маршруту, в метаинформацию, а затем проверить соответствующие разрешения в блоке маршрутизации, чтобы контролировать переход маршрута.
доступен на каждом маршрутеmetaВ атрибуте добавьте роль, которая может получить доступ к маршруту доrolesвнутри. После того, как пользователь входит в систему каждый раз, роль пользователя возвращается. Потом при заходе на страницу проставить маршрутmetaАтрибут сравнивается с ролью пользователя, если роль пользователя указана в маршрутизации.rolesЕсли он есть, к нему можно получить доступ, а если его нет, доступ запрещен.
Пример кода 1:
Информация о маршрутизации:
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')
},
]
Управление страницей:
//假设有两种角色:admin 和 user
//从后台获取的用户角色
const role = 'user'
//当进入一个页面是会触发导航守卫 router.beforeEach 事件
router.beforeEach((to,from,next)=>{
if(to.meta.roles.includes(role)){
next() //放行
}esle{
next({path:"/404"}) //跳到404页面
}
})
Пример кода 2
Конечно, вы также можете использовать один из следующих методов:
// 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, когда пользователь обновляет или повторно открывает веб-страницу и входит на страницу, на которой необходимо войти в систему, он попытается запросить информацию о пользователе.Эта операция выполняется только один раз в течение всего процесса посещения до тех пор, пока она не будет обновлена или повторно открыта, что очень полезно для последующей разработки, обслуживания и поддержки расширения приложения.
Динамическая загрузка меню и маршрутов (addRoutes)
Иногда в целях безопасности нам необходимо динамически добавлять меню и таблицы маршрутизации в соответствии с разрешениями или атрибутами пользователя, которые могут настраивать пользовательские функции.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):
Используйте браузер для доступа к /role под портом 3000
Если данные доступны, порт в порядке
Затем напишите следующие соответствующие страницы с основным скелетом 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')
}
]
})
Введите маршрут в адресную строку браузера, проверьте, должно быть все в порядке, здесь нет карты
После завершения настройки маршрутизации данные, возвращаемые нам бэкендом, показаны на рисунке:
Нам нужно преобразовать приведенные выше данные в нужный нам формат данных и сохранить их в 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 Navigation Guard (жизненный цикл маршрутизатора)) следующим образом:
//main.js
//只要页面切换就执行的钩子
//根据权限动态添加路由(我们的路由要分成两部分:一部分是有权限的,另一部分是没有权限的)
router.beforeEach(async (to,from,next)=>{
//判断当前有没有获取过权限,如果获取过了,就不要再获取了
if(!store.state.hasRules){
//获取权限,调用获取权限的接口,去action中获取数据
await store.dispatch('getMenuList')
}else{
//如果已经获取了权限就可以访问页面了
next()
}
})
Затем посетите главную страницу, эффект выглядит следующим образом:
В приведенных выше данных есть элемент авторизации, который означает, есть ли у вас это разрешение или нет. Если у вас нет этого разрешения, в это время меню будет удалено из правил маршрутизации. Отфильтруйте все автомобили следующим образом:
Затем протестируйте на главной странице следующим образом (не забудьте раскомментировать приведенный выше код console.log):
Если бэкенд данные (menuList) изменились, то обновите страницу еще раз, тогда данные изменятся динамически, я не буду здесь это демонстрировать, вы можете закомментировать и посмотреть.
Затем визуализируйте данные меню,
существуетmain.jsсерединаиспользуя элемент-интерфейс:
//main.js
//安装 element-ui 并使用
import ElementUI from "element-ui"
//引入element-ui里的样式
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
Визуализируйте данные, которые мы только что получили, на Home:
Использование домаmapStateметод:
При рендеринге нам нуженрекурсивный компонентЧтобы динамически получать передаваемые данные и отображать их динамически, поэтому они не должны быть жестко закодированы.Пожалуйста, обратитесь к моей статье для использования рекурсивных компонентов."Рекурсивный компонент Vue (меню построения дерева)", я не буду вводить здесь слишком много, просто используйте его напрямую
добавить просмотры ниже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>
<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>
Изображение эффекта:
Таким образом, необходимая нам структура может быть динамически сгенерирована в соответствии с переданными данными.
Но если бэкэнд не вернет нам профиль, его нельзя будет остановить, следующим образом:
Так же есть доступ к профилю, а именно:
Это не сработает, потому что бэкэнд не возвращает нам профиль
Как решить?
A: Динамически добавлять маршруты в соответствии с разрешениями (наши маршруты должны быть разделены на две части, одна часть авторизована, а другая не авторизована)
Исправлять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Код в (выделено):
тест в порядке:
Конечно, вы также можете аннотировать тест
Таким образом, этот маршрут возвращается бэкэндом, если у вас нет разрешения, вы вообще его не видите.
Хорошо, это дело здесь.
Исходный код кейса:Адрес источника
Эта глава закончилась