Vue3 权限管理从零开始:新手指南
前言
这篇文章是写给刚入门 Vue 框架开发或者刚入门的同学,权限管理是基本上每一个项目都会遇到问题,你有登陆,有游客等身份,你总要不同的身份会有不同的网页访问吧。下面就实现从想法到权限管理的构建。
必备条件
Vue3 项目开发语言基础了解
Vue-router 路由管理基础了解
Pina 状态管理器基础了解
首先执行创建工程语句
1
pnpm create vite my-vue-app --template vue-ts
这样就得到了一个 vue3+vite 项目。
安装路由、状态管理器以及持久化插件
1
pnpm install vue-router@4 pinia pinia-plugin-persistedstate
这个目的是存储我们的用户信息和当前的路由表
大致想法
graph TD
A[用户访问主页] --> B{是否已登录?}
B -- 否 --> C[仅可以查看公告页面]
B -- 是 --> D[记录身份信息]
D --> E[记录用户能访问的路由表]
E --> F{用户访问页面}
F -- 属于路由表 --> G[加载页面]
F -- 不属于路由表 --> H[跳转到 /error 页面]
这样的话就完成了用户的基础路由权限设计。
操作演示
实际的路由表的更新主要是通过 addRoute
和 removeRoute
来控制。
在
src/pages
下面新建admin.vue
custom.vue
error.vue
home.vue
user.vue
。分别写入页面名称就行。例如amdin.vue
页面内容。1
2
3
4
5
6<template>
<div>我是admin页面</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss"></style>在
src/store
中新建permission.ts
写入下面的内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import { defineStore } from "pinia";
import { publicRoutes, privateRoutes } from "../router";
import { RouteRecordRaw } from "vue-router";
export const useRoutesStore = defineStore("routes", {
state: () => ({
routes: publicRoutes,
}),
actions: {
setRoutes(newRoutes: RouteRecordRaw[]) {
this.routes = [...publicRoutes, ...newRoutes];
},
filterRoutes(role: string) {
this.routes = [
...publicRoutes,
...privateRoutes.filter((item) => item.meta.role === role),
];
return this.routes;
},
},
persist: {
storage: localStorage,
},
});在
src/store
中新建user.ts
写入下面的内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { defineStore } from "pinia";
export type Role = "admin" | "custom" | "user";
export const useUserRoleStore = defineStore("userInfo", {
state: (): { role: Role } => ({
role: "user",
}),
getters: {
getRole: (state) => state.role,
},
actions: {
changeRole(role: Role) {
this.role = role;
},
},
persist: {
storage: window.localStorage,
},
});可以对路由文件进行划分,这样会显得目录清晰。例如我建立了
admin
custom
user
。三个私有路由的文件,然后导入了一个文件中再合并成一个私有路由参数。当然你可以分的更细致,我这里只做个 demo
例如src/router/admin.ts
内容如下1
2
3
4
5
6
7
8
9
10const Admin = () => import("../pages/admin.vue");
export const adminRoutes = [
{
name: "admin",
component: Admin,
children: [],
path: "/admin",
meta: { role: "admin" },
},
];在路由总文件处注册路由
src/router/index.ts
。
这个文件大致要做 4 件事,当然你也可以细致的分划一下。- 导入私有路由的组合和公共路由
- 使用
createRouter
创建路由 - 编写全局路由功能:跳转前,看是路由是否存在
- 抛出修改路由表的方法
那么他的具体内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65import {
createWebHashHistory,
createRouter,
RouteRecordRaw,
} from "vue-router";
import { useUserRoleStore } from "../store/user";
import { userRoutes } from "./user";
import { adminRoutes } from "./admin";
import { customRoutes } from "./custom";
import { useRoutesStore } from "../store/permission";
// 导出所有的私密路由
export const privateRoutes = userRoutes
.concat(adminRoutes)
.concat(customRoutes);
const Home = () => import("../pages/home.vue");
const Error = () => import("../pages/error.vue");
export const publicRoutes: RouteRecordRaw[] = [
{ name: "home", component: Home, children: [], path: "/" },
{ name: "error", component: Error, children: [], path: "/error" },
];
export const router = createRouter({
history: createWebHashHistory(),
routes: publicRoutes,
});
//TODO 全局路由,跳转前,看是路由是否存在
// 假设用户时已经登陆的,这里只是做路由的动态修改操作
router.beforeEach(async (to, from, next) => {
const userRoleStore = useUserRoleStore();
const routeStore = useRoutesStore();
const filterRoutes = routeStore.filterRoutes(userRoleStore.role);
filterRoutes.forEach((item) => {
router.addRoute(item);
});
const exist = router.getRoutes().some((route) => route.path === to.path);
console.log("跳转前", router.getRoutes(), to.path, exist);
if (exist) {
next();
} else {
next(false);
router.push("/error");
}
});
//TODO 抛出添加路由的方法
export const filterRoleRoutes = async () => {
const userRoleStore = useUserRoleStore();
let preRoutes = router.getRoutes();
preRoutes.forEach((route) => {
if (
route.meta.role &&
route.meta.role !== userRoleStore.role &&
route.name
) {
router.removeRoute(route.name.toString());
}
});
};如果仅仅只是上面的操作的话,那么会出现用户切换了身份,路由更新了,但是点击对应的页面,他没法跳转。以及刷新的时候,路由表还没更新到当前用户的路由表,会无法访问具体的私有路由页面。
这里就是通过 App.vue 里的逻辑来改造上面描述的问题。当然里面的函数和方法你也可以封装和优化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73<template>
<div>
<span>切换身份:</span>
<button @click="changeRoleFn('user')">user</button>
<button @click="changeRoleFn('custom')">custom</button>
<button @click="changeRoleFn('admin')">admin</button>
</div>
<div>
<span>跳转路由</span>
<button
@click="
() => {
router.push('/user');
}
">
user
</button>
<button
@click="
() => {
router.push('/custom');
}
">
custom
</button>
<button
@click="
() => {
router.push('/admin');
}
">
admin
</button>
</div>
<div>
<RouterView></RouterView>
</div>
</template>
<script setup lang="ts">
import { filterRoleRoutes } from "./router";
import { Role, useUserRoleStore } from "./store/user";
import { useRouter } from "vue-router";
import { useRoutesStore } from "./store/permission";
const userRoleStore = useUserRoleStore();
const router = useRouter();
const routeStore = useRoutesStore();
const changeRoleFn = async (role: Role) => {
userRoleStore.changeRole(role);
// 内存添加路由表
const filterRoutes = routeStore.filterRoutes(userRoleStore.role);
filterRoutes.forEach((item) => {
router.addRoute(item);
});
// 路由更新,删除不符合当前身份的路由
filterRoleRoutes();
routeStore.filterRoutes(role);
};
// TODO每次刷新的时候检查下更新路由表
const filterRoutes = routeStore.filterRoutes(userRoleStore.role);
filterRoutes.forEach((item) => {
router.addRoute(item);
});
// TODO 这里如果不手动更新跳转的话,route.path的值实际上是'/' 而不是你浏览器上显示的网络地址
router.push(location.hash.replace(/#/, ""));
</script>
<style scoped></style>代码上有详细的解释。其实原理很简单,主要是踩的坑会比较麻烦。
功能权限控制
上面一块是大的页面权限的控制,那么同一个身份下的用户,有不同的功能。对于功能也是需要区分开的。
想法
通过指令来决定当前的这个 dom 是不是要渲染,如果身份不对就不渲染。不渲染你想点击也没法点。
那么下面就简单的给出部分代码
新建一个指令文件夹,编写permission
指令代码
1 | import { useUserRoleStore } from "../store/user"; |
后面就是注册到 app 上。当然了最好是弄一个单独的文件,然后统一的注册。单独注册的语法如下app.directive("permission", permission);
我这里就省略了,具体可以看源码
那么使用的话就只需像如下的admin.vue
页面一样
1 | <template> |
源码以及效果
效果就是,你切换对应的身份,那么他身份下的路由页面,你可以访问,相反的不是他的路由页面,你只能访问到/error
页面。
当然你可以不用动态路由的方法来实现,你也可以仅仅通过全局理由
beforeEach
来判断当前要去的页面是否能被访问