предисловие
Чувствоватьпроект с открытым исходным кодом vue-element-adminЛогика повторного использования проверки разрешений на вход и динамической маршрутизации по-прежнему очень сильна и может использоваться непосредственно в ваших собственных проектах. Чтобы углубить память, напишите статью, чтобы записать конспекты исследования.
Статья в основном записывает логику в качестве мастера, затем сотрудничать с некоторым ключевым кодом, полный код может загрузить исходный код проекта напрямую, или я лично организуюЛайт(удалите другие коды, сохраните только функцию входа в систему)
Реализация динамической маршрутизации
логика
- Настройте два массива маршрутов:
- один публичный,
无需权限都可以加载
, такие как домашняя страница, страница входа, страница 404 и т. д.; - Один динамичный,
配置角色权限
, чтобы динамически выбирать, отображать ли;
- один публичный,
- После нажатия кнопки «Войти» будет возвращена информация о разрешениях пользователя в сочетании с разрешениями ролей массива динамической маршрутизации, а маршруты, к которым может получить доступ пользователь, будут отфильтрованы.
- Наконец, через vueaddRoutesМетод может динамически добавлять отфильтрованный массив к фактическому объекту маршрутизации.
Шаг 1. Настройте два массива маршрутов
Каталог: src/router/index.js
// 该 js 实现了逻辑的第一步,配置两个路由
// 公共的路由
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true // 因为无需在侧边栏显示,可以用这个字段来控制隐藏
},
{
path: '/',
component: Layout, // 这是一个框架组件,顶部和侧边栏会固定加载
redirect: '/dashboard', // 重定向的就是中间的内容部分
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: 'Dashboard', icon: 'dashboard'}
}
]
}
]
// 动态的路由 (最后通过 addRoutes 添加进去)
export const asyncRoutes = [
{
path: '/user',
component: Layout,
redirect: '/user/create',
children: [
{
path: 'create',
name: 'userCreate',
component: () => import('@/views/create/create'),
meta: { roles: ['admin'] } // 只有管理员才能访问
},
{
path: 'list',
name: 'userList',
component: () => import('@/views/user/list'),
meta: { roles: ['admin','editor'] } // 管理员和编辑可以访问
},
]
}
]
// 先把公共路由添加进路由实例,动态的路由手动添加
const createRouter = () => new Router({
// mode: 'history', // 是否使用 history 模式
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
Второй и третий этапы: реализация на маршрутизаторе
После нажатия входа вы войдете до того, как страница перейдет к路由守卫
.
- nprogress
- Первый вход фактически
发送两次请求
, первый раз — получить токен при нажатии на вход, а второй раз — в роутгарде获取用户拥有的权限
;- После получения текущих разрешений пользователя он будет фильтровать настроенный нами массив динамической маршрутизации, а затем использовать
addRoutes
добавлять динамически;- Ситуация перенаправления маршрута: когда нет токена и вы хотите перейти на другие маршруты, такие как «список пользователей», вы можете сначала назначить маршрут «список пользователей» параметру Реализовать переходы перенаправления.
Каталог: src/promission.js (будет представлен в main.js)
// 省略 import 引入的代码哦!
NProgress.configure({ showSpinner: false }) // 进度条设置,把转圈圈关掉
const whiteList = ['/login'] // 设置白名单
// 路由守卫
router.beforeEach(async (to, from, next) => {
// 进度条开始
NProgress.start()
// 设置标题
document.title = getPageTitle(to.meta.title)
// 第一次登录后会把 token 存在 cookie 中,此处就是通过 cookie 拿 token
const hasToken = getToken()
if (hasToken) {
/* 有 token,证明已成功登录 */
if (to.path === '/login') {
next({ path: '/' }) // 如果要去登录页,会自动跳转到首页,然后会再次进入这个路由守卫
NProgress.done()
} else {
// 在 vuex 中获取用户权限,因为第一次登录时会把请求回来的用户权限存在 vuex 中
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next() // 如果有 token 并且有权限,直接跳转
} else {
try {
// 有 token 无权限,这就是用户第一次登录的情况,需要发送请求获取
const { roles } = await store.dispatch('user/getInfo')
// 筛选动态路由数组
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// 动态添加筛选后的路由数组
router.addRoutes(accessRoutes)
// replace: true 表示不会记录到浏览器 history
next({ ...to, replace: true })
} catch (error) {
// 获取用户权限报错会要求重新登录,并且删掉 token
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`) // 重定向
NProgress.done()
}
}
}
} else {
/*没有 token,证明 token 过期了或者未登录,*/
if (whiteList.indexOf(to.path) !== -1) {
next() // 如果是白名单,直接跳转
} else {
// 要去其他路由,先把路由地址赋值给跳转参数,登陆成功后拿出来跳转。
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// 结束进度条
NProgress.done()
})
Перенаправление страницы входа
Как было сказано выше, может быть ситуация, что срок действия токена истек или вы не вошли в систему, но переходите на внутреннюю страницу по другим ссылкам, вам необходимо авторизоваться, прежде чем вы сможете перейти;
В этом случае он будет переходить на адрес маршрутизации в качестве параметра на страницу входа в систему, логин первым определяет, что нет параметра Redirect.
Каталог: src/views/login/index.vue
methods:{
// 点击登录调用的方法
handleLogin() {
// 字段校验(element-ui的东西)
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true // 登录时按钮里面会有个圈在转
this.$store.dispatch('user/login', this.loginForm) // 发送登录请求获取 token
.then(() => {
// 重定向,先判断有没有redirect参数
this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
this.loading = false
})
.catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
}
На данный момент реализована вся динамическая маршрутизация и проверка разрешений на вход, а что касается того, как фильтровать и объединять массивы маршрутизации в vuex, то логика кода отправки запросов здесь обсуждаться не будет.
На самом деле проверка разрешений и добавление динамической маршрутизации были реализованы и раньше, а потом уже с рендерингом представления,把我们筛选合并后的路由数组渲染到菜单上
Готово.
рендеринг меню
Рендеринг меню реализован с использованием компонента боковой панели element-ui, но детали все еще довольно сложны. Также обратитесь к документации курса на MOOC.com.История управления "Сяому Чтение"
Каталог: src\layout\components\Sidebar\SidebarItem.vue
// 该组件就是负责渲染侧边栏的每一个目录
<template>
<div v-if="!item.hidden">
<!-- template 部分渲染没有子目录的目录 -->
<!-- hasOneShowingChild:判断是否只有一个需要显示的子路由 -->
<!-- !onlyOneChild.children||onlyOneChild.noShowingChildren:判断需要展示的子菜单是否包含 children 属性,
如果包含,则说明子菜单可能存在孙子菜单,此时则需要再判断 noShowingChildren 属性 -->
<!-- !item.alwaysShow:判断路由中是否存在 alwaysShow 属性,只要配置了 alwaysShow 属性就会直接进入下面的那部分-->
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
</app-link>
</template>
<!-- el-submenu 部分是渲染含有子目录的目录 -->
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<!-- 子目录是嵌套本组件实现,所以可以理解为整个组件只会渲染一个目录,子目录是递归整个组件产生 -->
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
export default {
name: 'SidebarItem',
components: { Item, AppLink },
mixins: [FixiOSBug],
props: {
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
},
data() {
this.onlyOneChild = null
return {}
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
// 如果 children 中的路由包含 hidden 属性,则返回 false
if (item.hidden) {
return false
} else {
// 将子路由赋值给 onlyOneChild,用于只包含一个路由时展示
this.onlyOneChild = item
return true
}
})
// 如果过滤后,只包含展示一个路由,则返回 true
if (showingChildren.length === 1) {
return true
}
// 如果没有子路由需要展示,则将 onlyOneChild 的 path 设置空路由,并添加 noShowingChildren 属性,表示虽然有子路由,但是不需要展示子路由
if (showingChildren.length === 0) {
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
return true
}
// 返回 false,表示不需要展示子路由,或者超过一个需要展示的子路由
return false
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.basePath)) {
return this.basePath
}
return path.resolve(this.basePath, routePath)
}
}
}
</script>
Суммировать
Поместите документ курса MOOCИстория управления "Сяому Чтение"Внутри заканчивая изображение, хотя я думаю, что также посмотрите на код простой точки ... но после прочтения кода, чтобы посмотреть на этот рисунок, должно быть гораздо более глубокое понимание.
Логика кода защитной маршрутизации FIG: