前言:

在“VUE3后台管理系统【模板构建】”文章中,详细的介绍了我使用vue3.0和vite2.0构建的后台管理系统,虽然只是简单的一个后台管理系统,其中涉及的技术基本都覆盖了,基于vue3的vue-router和vuex,以及借助第三方开源插件来实现vuex数据持久化。前边只是介绍了vue后台管理系统的页面布局,以及一些常用的插件的使用,如:富文本编辑器、视频播放器、页面滚动条美化(前边忘记介绍了,此次文章中将会进行添加和补充)。

本次文章主要介绍的是vue-router的动态匹配和动态校验,来实现不同账号不同权限,通过前端来对用户权限进行相应的限制;在一些没有访问权限的路径下访问时给予相应的提示以及后续相应的跳转复原等逻辑操作。用户鉴权,前端可以进行限制,也可以通过后台接口数据进行限制,之前开发过程中遇到过通过后台接口来动态渲染路由的,接下来介绍的是纯前端来做路由访问的限制。

路由配置:

import Layout from "../layout/Index.vue";
import RouteView from "../components/RouteView.vue"; const layoutMap = [
{
path: "/",
name: "Index",
meta: { title: "控制台", icon: "home" },
component: () => import("../views/Index.vue")
},
{
path: "/data",
meta: { title: "数据管理", icon: "database" },
component: RouteView,
children: [
{
path: "/data/list",
name: "DataList",
meta: { title: "数据列表", roles: ["admin"] },
component: () => import("../views/data/List.vue")
},
{
path: "/data/table",
name: "DataTable",
meta: { title: "数据表格" },
component: () => import("../views/data/Table.vue")
}
]
},
{
path: "/admin",
meta: { title: "用户管理", icon: "user" },
component: RouteView,
children: [
{
path: "/admin/user",
name: "AdminAuth",
meta: { title: "用户列表", roles: ["admin"] },
component: () => import("../views/admin/AuthList.vue")
},
{
path: "/admin/role",
name: "AdminRole",
meta: { title: "角色列表" },
component: () => import("../views/admin/RoleList.vue")
}
]
},
{
path: "user",
name: "User",
hidden: true /* 不在侧边导航展示 */,
meta: { title: "个人中心" },
component: () => import("../views/admin/User.vue")
},
{
path: "/error",
name: "NotFound",
hidden: true,
meta: { title: "Not Found" },
component: () => import("../components/NotFound.vue")
}
]; const routes = [
{
path: "/login",
name: "Login",
meta: { title: "用户登录" },
component: () => import("../views/Login.vue")
},
{
path: "/",
component: Layout,
children: [...layoutMap]
},
{ path: "/*", redirect: { name: "NotFound" } }
]; export { routes, layoutMap };

注:

  • 此次路由列表分为两部分,其中一部分是默认路由,即无需权限校验的路由路径(如:Login登录页);
  • 其中layoutMap中的路由元素是全部与路由路径相关的配置信息,即包裹所有用户权限的路径路由信息;
  • 路由鉴权最终限制的就是layoutMap数组中的数据元素,并且进行相应的筛选限制来达到限制路由访问的目的。

路由拦截:

// vue-router4.0版写法
import { createRouter, createWebHistory } from "vue-router";
import { decode } from "js-base64";
import { routes } from "./router";
import NProgress from "nprogress";
import "nprogress/nprogress.css"; NProgress.configure({ showSpinner: false }); const router = createRouter({
history: createWebHistory(),
routes: [...routes],
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
}
}); // 路由拦截与下方vue-router3.x写法相同
// vue-router3.x版写法
import Vue from "vue";
import VueRouter from "vue-router";
import { decode } from "js-base64";
import { routes } from "./router";
import NProgress from "nprogress";
import "nprogress/nprogress.css"; NProgress.configure({ showSpinner: false }); Vue.use(VueRouter); const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes: [...routes],
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
}
}); router.beforeEach((to, from, next) => {
NProgress.start();
const jwt = sessionStorage.getItem("jwt") || ""; document.title = jwt ? (to.meta.title ? to.meta.title + " - 管理应用" : "管理系统") : "系统登录";
if (to.path === "/login") {
!!jwt ? next("/") : next();
} else {
if (from.path === "/login" && !jwt) {
NProgress.done(true);
next(false);
return;
}
if (!!jwt) {
if (to.meta.hasOwnProperty("roles")) {
let roles = to.meta.roles || [],
{ role } = jwt && JSON.parse(decode(jwt));
roles.includes(role) ? next() : next("/error");
return;
}
next();
} else {
next("/login");
}
}
}); router.afterEach(() => {
NProgress.done();
}); export default router;

注:

  • 依据访问的路由节点的信息,进行动态的路由权限校验,有访问权限的放过,没有访问权限的路由进行相应的拦截处理;
  • nprogress为路由访问的进度条,访问时有相应的进度条指示,也有转动的小菊花(即路由加载指示器)可通过相关配置进行相关的配置;
  • 当有用户信息时访问“/login”时则默认重定向到系统控制台页,反之则不进行拦截,让其跳转至登录页面;
  • 当访问非登录页面时,要进行role管理员权限的校验,有权限则放过,继续向后执行,反之则重定向到“/error”页面提示其无权访问当前路径。

路由过滤:

/* 处理权限 */
export const hasPermission = (route, role) => {
if (route["meta"] && route.meta.hasOwnProperty("roles")) {
return route.meta.roles.includes(role);
}
return true;
}; /* 过滤数组 */
export const filterAsyncRouter = (routers, role) => {
let tmp = [];
tmp = routers.filter(el => {
if (hasPermission(el, role)) {
if (el["children"] && el.children.length) {
el.children = filterAsyncRouter(el.children, role);
}
return true;
}
return false;
});
return tmp;
};

注:此两函数为封装的过滤指定权限的路由数据,返回过滤后的数据(即当前账号有权访问的页面);

vuex存储和过滤路由信息

import Vue from "vue";
import Vuex from "vuex";
import { layoutMap } from "../router/router";
import { filterAsyncRouter } from "../utils/tool";
import createPersistedState from "vuex-persistedstate";
import SecureLS from "secure-ls";
import { CLEAR_USER, SET_USER, SET_ROUTES } from "./mutation-types"; Vue.use(Vuex); const state = {
users: null,
routers: []
}; const getters = {}; const mutations = {
[CLEAR_USER](state) {
state.users = null;
state.routers.length = 0;
},
[SET_USER](state, payload) {
state.users = payload;
},
[SET_ROUTES](state, payload) {
state.routers = payload;
}
}; const ls = new SecureLS({
encodingType: "aes" /* 加密方式 */,
isCompression: false /* 压缩数据 */,
encryptionSecret: "vue" /* 加密密钥 */
}); const actions = {
clearUser({ commit }) {
commit(CLEAR_USER);
},
setUser({ commit }, payload) {
let deepCopy = JSON.parse(JSON.stringify(layoutMap)),
accessedRouters = filterAsyncRouter(deepCopy, payload.role);
commit(SET_USER, payload);
commit(SET_ROUTES, accessedRouters);
}
}; const myPersistedState = createPersistedState({
key: "store",
storage: window.sessionStorage,
// storage: {
// getItem: state => ls.get(state),
// setItem: (state, value) => ls.set(state, value),
// removeItem: state => ls.remove(state)
// } /* 永久存储 */
reducer(state) {
return { ...state };
}
}); export default new Vuex.Store({
state,
getters,
mutations,
actions
// plugins: [myPersistedState]
});

注:

  • secure-ls 为加密工具函数,加密级别比较高,一般不可破解,基于密钥和私钥进行加密和解密,使用规则请参考github;
  • vuex-persistedstate 为持久化处理vuex状态使用的,存储方式主要有sessionStorage、localStorage以cookies,一般常用前两种方式;
  • 借助vuex来遍历过滤指定权限的路由,然后在Menu.vue中进行渲染和遍历。

路由列表渲染:

<template>
<a-layout-sider class="sider" v-model="collapsed" collapsible :collapsedWidth="56">
<div class="logo">
<a-icon type="ant-design" />
</div>
<a-menu
class="menu"
theme="dark"
mode="inline"
:defaultOpenKeys="[defaultOpenKeys]"
:selectedKeys="[$route.path]"
:inlineIndent="16"
>
<template v-for="route in routers">
<template v-if="!route['hidden']">
<a-sub-menu v-if="route.children && route.children.length" :key="route.path">
<span slot="title">
<a-icon :type="route.meta['icon']" />
<span>{{ route.meta.title }}</span>
</span>
<a-menu-item v-for="sub in route.children" :key="sub.path">
<router-link :to="{ path: sub.path }">
<a-icon v-if="sub.meta['icon']" :type="sub.meta['icon']" />
<span>{{ sub.meta.title }}</span>
</router-link>
</a-menu-item>
</a-sub-menu>
<a-menu-item v-else :key="route.path">
<router-link :to="{ path: route.path }">
<a-icon :type="route.meta['icon']" />
<span>{{ route.meta.title }}</span>
</router-link>
</a-menu-item>
</template>
</template>
</a-menu>
</a-layout-sider>
</template> <script>
import { mapState } from "vuex"; export default {
name: "Sider",
data() {
return {
collapsed: false,
defaultOpenKeys: ""
};
},
computed: {
...mapState(["routers"])
},
created() {
this.defaultOpenKeys = "/" + this.$route.path.split("/")[1];
}
};
</script> <style lang="less" scoped>
.sider {
height: 100vh;
overflow: hidden;
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
} .logo {
height: 56px;
line-height: 56px;
font-size: 30px;
color: #fff;
text-align: center;
background-color: #002140;
} .menu {
width: auto;
}
}
</style> <style>
ul.ant-menu-inline-collapsed > li.ant-menu-item,
ul.ant-menu-inline-collapsed > li.ant-menu-submenu > div.ant-menu-submenu-title {
padding: 0 16px !important;
text-align: center;
}
</style>

注:该菜单渲染是基于Vue2.x和Ant Design Vue来编辑实现的。

<template>
<el-aside :width="isCollapse ? `64px` : `200px`">
<div class="logo">
<img src="@/assets/img/avatar.png" alt="logo" draggable="false" />
<p>Vite2 Admin</p>
</div>
<el-menu
background-color="#001529"
text-color="#eee"
active-text-color="#fff"
router
unique-opened
:default-active="route.path"
:collapse="isCollapse"
>
<template v-for="item in routers" :key="item.name">
<template v-if="!item['hidden']">
<el-submenu v-if="item.children && item.children.length" :index="concatPath(item.path)">
<template #title>
<i :class="item.meta.icon"></i>
<span>{{ item.meta.title }}</span>
</template>
<template v-for="sub in item.children" :key="sub.name">
<el-menu-item :index="concatPath(item.path, sub.path)">
<i :class="sub.meta['icon']"></i>
<template #title>{{ sub.meta.title }}</template>
</el-menu-item>
</template>
</el-submenu>
<el-menu-item v-else :index="concatPath(item.path)">
<i :class="item.meta['icon']"></i>
<template #title>{{ item.meta.title }}</template>
</el-menu-item>
</template>
</template>
</el-menu>
<div class="fold" @click="changeCollapse">
<i v-show="!isCollapse" class="el-icon-d-arrow-left"></i>
<i v-show="isCollapse" class="el-icon-d-arrow-right"></i>
</div>
</el-aside>
</template> <script>
import { computed, reactive, toRefs } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex"; export default {
setup() {
const route = useRoute();
const store = useStore();
const state = reactive({ isCollapse: false });
const routers = computed(() => store.state.routers); const changeCollapse = () => {
state.isCollapse = !state.isCollapse;
}; const concatPath = (p_path, c_path = "") => {
return `${p_path !== "" ? "/" + p_path : "/"}${c_path !== "" ? "/" + c_path : ""}`;
}; return {
route,
routers,
concatPath,
changeCollapse,
...toRefs(state)
};
}
};
</script>

注:

  • 该菜单导航是基于vue3和支持Vue3版本的Element-Plus实现的,详细参数配置请参考Element-plus官网;
  • 此处获取的路由数组即鉴权过滤后的路由数组数据;此菜单将会依据登录信息动态遍历生成指定菜单数据。

总结:

结合之前的模板代码,就可以完整的搭建出一个带有前端权限校验的vue后台管理系统,主要是梳理清路由数据和过滤后的路由鉴权后的路由数据信息。主要代码就是上述封装的过滤和权限校验函数。后续将放开后台模板代码,模板代码完善中......

VUE3后台管理系统【路由鉴权】的更多相关文章

  1. vue3后台管理系统(模板)

    系统简介 此管理系统是基于Vite2和Vue3.0构建生成的后台管理系统.目的在于学习vite和vue3等新技术,以便于后续用于实际开发工作中: 本文章将从管理系统页面布局.vue路由鉴权.vuex状 ...

  2. react后台管理系统路由方案及react-router原理解析

        最近做了一个后台管理系统主体框架是基于React进行开发的,因此系统的路由管理,选用了react-router(4.3.1)插件进行路由页面的管理配置. 实现原理剖析 1.hash的方式   ...

  3. 基于VUE实现的新闻后台管理系统-三

    开始coding啦 ¶分析项目 根据展示效果我们可以分析出,Web页面有两个,一个用于登录,一个用于系统内容控制,我们分别将其命名为Login和Cms,然后进行路由配置. 在src/page下新建Lo ...

  4. react-router 5.0 的鉴权

    react-router 5.0 的鉴权 当我们使用react-router 控制页面的路由时候,有些页面,是需要登录才能访问,有些不需要登录就可以访问,还有些页面,是根据用户的权限来限制访问的. 如 ...

  5. AOP自定义注解鉴权

    刚出来工作那会或者在学校的时候,经常听到说AOP(面向对象编程,熟称切面)的用途是日志.鉴权等.但是那会不会,后面学会了,又没有写博客记录,今天写给大伙,希望能帮到大家 一.学习目标:利用AOP+自定 ...

  6. Vue3 + Element ui 后台管理系统

    Vue3 + Element ui  后台管理系统 概述:这是一个用vue3.0和element搭建的后台管理系统界面. 项目git地址: https://github.com/whiskyma/vu ...

  7. vue3项目后台管理系统模板

    Vue3.0 发布第一个版本至今有一段时间了,到现在一直在更新优化,在性能方面,对比 Vue2.x ,性能的提升比较明显,打包后体积更小 来看下 Vue3.x 新增了哪些功能和特性. Performa ...

  8. Django学习(四) Django提供的后台管理系统以及如何定义URL路由

    一旦你建立了模型Models,那么Django就可以为你创建一个专业的,可以提供给生成用的后台管理站点.这个站点可以提供给有权限的人进行已有模型Models数据的增删改查. 将新建的模型Models是 ...

  9. koa2服务端使用jwt进行鉴权及路由权限分发

    大体思路 后端书写REST api时,有一些api是非常敏感的,比如获取用户个人信息,查看所有用户列表,修改密码等.如果不对这些api进行保护,那么别人就可以很容易地获取并调用这些 api 进行操作. ...

随机推荐

  1. css选择器中:first-child 与 :first-of-type的区别

    ## css选择器中:first-child 与 :first-of-type的区别 ---- :first-child选择器是css2中定义的选择器,从字面意思上来看也很好理解,就是第一个子元素.比 ...

  2. Laravel路由中不固定数量的参数如何实现?

    前言 laravel是个好框架,我也在学习和使用,并且在公司里推广,最近在读 Laravel 源码的时候,发现了一个段特别有趣的代码,大家请看: ... 这三个点是做什么用的呢?我查了 PHP 的手册 ...

  3. hdu4081 最小树+DFS或者次小树的变形

    题意:       给你一个全图,在里面找到一棵树,这棵树最多只有一条边可以不是最小树(也可以是), 要求 那对特殊的边的两个权值/除了这条边其他边的和最大. 思路:      方法有很多,最少有三种 ...

  4. Windows核心编程 第27章 硬件输入模型和局部输入状态

    第27章 硬件输入模型和局部输入状态 这章说的是按键和鼠标事件是如何进入系统并发送给适当的窗口过程的.微软设计输入模型的一个主要目标就是为了保证一个线程的动作不要对其他线程的动作产生不好的影响. 27 ...

  5. CVE-2018-0802:Microsoft office 公式编辑器 font name 字段二次溢出漏洞调试分析

    \x01 前言 CVE-2018-0802 是继 CVE-2017-11882 发现的又一个关于 font name 字段的溢出漏洞,又称之为 "第二代噩梦公式",巧合的是两个漏洞 ...

  6. js限制上传文件类型和大小

    <html> <head> <script type="text/javascript"> function fileChange(target ...

  7. src/main/resorces applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  8. MySQL|一文解决主库已有数据的主从复制

    主从复制配置方案和实际的场景有很多,在之前配置了主从库都是全新的配置方案 在这一篇会配置主库存在数据,然后配置主从复制 开始之前,先分享一套MySQL教程,小白入门或者学习巩固都可以看 MySQL基础 ...

  9. Hive企业级性能优化

    Hive作为大数据平台举足轻重的框架,以其稳定性和简单易用性也成为当前构建企业级数据仓库时使用最多的框架之一. 但是如果我们只局限于会使用Hive,而不考虑性能问题,就难搭建出一个完美的数仓,所以Hi ...

  10. python函数默认值只初始化一次

    当在函数中定义默认值时,值初始化只会进行一次,就是执行到def methodname时执行.看下面代码: from datetime import datetime def test(t=dateti ...