前言:

在“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. lumen Rest API 起步

    lumen Rest API 起步 修改项目文件 .env DB_DATABASE=<数据库名> DB_USERNAME=<数据库用户名> DB_PASSWORD=<数据 ...

  2. Linux 究级基础入门命令整理

    Linux 究级基础入门命令整理 条条框框,三三两两,怎讷个这么多,哈哈!no zuo no die. 纯粹个人菜鸟笔记,望大神笑纳! 后续,未完!! 查看系统信息 uname -a - 查看内核/操 ...

  3. animation几个比较好玩的属性(alternate,及animation-fill-mode)

    <!DOCTYPE html> <html> <head> <style> div { width:100px; height:100px; backg ...

  4. 认识二进制安全与漏洞攻防技术 (Windows平台)

    二进制漏洞是指程序存在安全缺陷,导致攻击者恶意构造的数据(如Shellcode)进入程序相关处理代码时,改变程序原定的执行流程,从而实现破坏或获取超出原有的权限. 0Day漏洞 在计算机领域中,0da ...

  5. 技术面试问题汇总第004篇:猎豹移动反病毒工程师part4

    这次所讨论的三个问题,比如DLL以及HOOK,很容易被病毒木马所利用,因此必须要比较全面地进行了解.而异常处理机制,则往往与漏洞相关联.它们自身的概念并不难理解,只是由之引申而来的问题,在计算机安全领 ...

  6. hdu3870 基于最短路的最小割

    题意:      给你一个平面图,让你输出(1,1),(n ,n)的最小割.. 思路:       看完题想都没想直接最大流,结果TLE,想想也是 G<400*400,400*400*4> ...

  7. OWASP-ZAP扫描器的使用

    目录 OWASP-ZAP 更新 代理 目录扫描 主动扫描(Active  Scan) 扫描结果 生成报告 OWASP-ZAP OWASP Zed攻击代理(ZAP)是世界上最受欢迎的免费安全审计工具之一 ...

  8. (Py练习)判断101-200之间的素数个数并输出

    判断素数的方法之一:用一个数分别去除以2到squrt(这个数),如果能被整除,则不是素数. import math h = 0 leap = 1 for m in range(101, 201): k ...

  9. Git解决中文乱码问题

    git status 乱码 解决方法: git config --global core.quotepath false git commit 乱码 解决方法: git config --global ...

  10. 在局域网内知道计算机的名字查找计算机的IP

    第一步 nbtstat -a 计算机名字 第二步 nbtstat -c 可以看到计算机地址