基于RBAC的菜单权限控制

RBAC (Role-Based Access Control) 是一种基于角色的访问控制方式。在这种模式中,权限不是直接分配给用户,而是分配给角色,然后用户被分配到不同的角色。用户通过角色获得权限。这种方式使得权限管理变得更加灵活和方便。

在 RBAC 模型中,可以包含以下几个主要元素:

  • 用户(User):系统的操作者,可以是人,也可以是内部的程序或进程。
  • 角色(Role):用户的身份,它是权限的集合。一个用户可以拥有多个角色。
  • 权限(Permission):对一种操作或者资源的访问控制。权限通常是系统定义好的,例如读权限、写权限等。

RBAC 模型的主要优点是可以简化权限管理。在大型组织中,用户经常会更换部门,或者角色会随着时间而变化。使用 RBAC 模型,只需要更改用户的角色,而不需要单独更改每个用户的权限。

在企业服务中,权限一般分割为 页面访问权限按钮操作权限API 访问权限

一、权限受控的主体思路

  • 我们将路由模块分为静态路由和动态路由,静态路由是所有人都能访问的,而动态路由则是需要权限才能访问的
  • 在用户登录获取的用户信息中,携带有用户拥有的权限标识。我们可以将用户携带的权限标识和动态路由对应起来(比如对应到路由模块的 name),这样就可以从动态路由中筛选出用户所拥有的路由了
  • 静态路由和筛选出的路由合并得到用户完整路由。侧边栏根据完整路由渲染出菜单栏、路由守卫根据完整路由拦截或放行

二、拆分路由模块

拆分后的/src/router.index.js

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);
// 引入多个模块的规则
import approvalsRouter from "./modules/approvals";
import departmentsRouter from "./modules/departments";
import employeesRouter from "./modules/employees";
import permissionRouter from "./modules/permission";
import attendancesRouter from "./modules/attendances";
import salarysRouter from "./modules/salarys";
import settingRouter from "./modules/setting";
import socialRouter from "./modules/social";
import userRouter from "./modules/user";
/* Layout */
import Layout from "@/layout";
export const constantRoutes = [
{
path: "/login",
component: () => import("@/views/login/index"),
hidden: true,
},

{
path: "/404",
component: () => import("@/views/404"),
hidden: true,
},

{
path: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "dashboard",
component: () => import("@/views/dashboard/index"),
meta: { title: "首页", icon: "dashboard" },
},
],
},
{
path: "/import",
component: Layout,
hidden: true, // 该组件不在菜单中显示
children: [
{
path: "",
component: () => import("@/views/import"),
meta: {
title: "导入",
},
},
],
},
userRouter,
// 404 page must be placed at the end !!!
// { path: '*', redirect: '/404', hidden: true }
];
// 动态路由的变量
export const asyncRoutes = [
approvalsRouter,
departmentsRouter,
employeesRouter,
permissionRouter,
attendancesRouter,
salarysRouter,
settingRouter,
socialRouter,
];
const createRouter = () =>
new Router({
mode: "history", // require service support
base: "/hrsaas/",
scrollBehavior: () => ({ y: 0 }),
routes: [...constantRoutes],
});

const router = createRouter();

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}

export default router;

三、在 vuex 中新建 permission 模块

src/store/modules/permission.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// vuex的权限模块
import { constantRoutes } from "@/router";
// vuex中的permission模块用来存放当前的 静态路由 + 当前用户的 权限路由
const state = {
routes: constantRoutes, // 所有人默认拥有静态路由
};
const mutations = {
// newRoutes可以认为是 用户登录 通过权限所得到的动态路由的部分
setRoutes(state, newRoutes) {
// 下面这么写不对 不是语法不对 是业务不对
// state.routes = [...state.routes, ...newRoutes]
// 有一种情况 张三 登录 获取了动态路由 追加到路由上 李四登录 4个动态路由
// 应该是每次更新 都应该在静态路由的基础上进行追加
state.routes = [...constantRoutes, ...newRoutes];
},
};
const actions = {};
export default {
namespaced: true,
state,
mutations,
actions,
};

在 Vuex 管理模块中引入 permisson 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
import permission from "./modules/permission";

const store = new Vuex.Store({
modules: {
// 子模块 $store.state.app.
// mapGetters([])
app,
settings,
user,
permission,
},
getters,
});

四、vuex 筛选路由权限

我们可以按照这张图,做进一步的操作

image-20200815184407204

后端返回的用户信息

image-20200815185230597

接下来, 在 vuex 的 permission 中写一个 action,通过路由模块的 name 与用户信息中的权限标识进行关联,最终筛选出用户所拥有的权限路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { asyncRoutes, constantRoutes } from '@/router'

const actions = {
// 筛选权限路由
// 第二个参数为当前用户的所拥有的菜单权限
// menus: ["settings","permissions"]
// asyncRoutes是所有的动态路由
// asyncRoutes [{path: 'setting',name: 'setting'},{}]
filterRoutes(context, menus) {
const routes = []
// 筛选出 动态路由中和menus中能够对上的路由
menus.forEach(key => {
// key就是标识
// asyncRoutes 找 有没有对象中的name属性等于 key的 如果找不到就没权限 如果找到了 要筛选出来
routes.push(...asyncRoutes.filter(item => item.name === key)) // 得到一个数组 有可能 有元素 也有可能是空数组
})
// 得到的routes是所有模块中满足权限要求的路由数组
// routes就是当前用户所拥有的 动态路由的权限
context.commit('setRoutes', routes) // 将动态路由提交给mutations
return routes // 这里为什么还要return state数据 是用来 显示左侧菜单用的 return 是给路由addRoutes用的
}

五、权限拦截处调用筛选权限 Action

在拦截的位置,调用关联 action, 获取新增 routes,并且addRoutes

注意,由于内容较多,我们可以将拦截这块从/src/router/index.js 中抽离出来,形成一个单独的模块,/src/permission.js

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
// 权限拦截在路由跳转  导航守卫

import router from "@/router";
import store from "@/store"; // 引入store实例 和组件中的this.$store是一回事
import nprogress from "nprogress"; // 引入进度条
import "nprogress/nprogress.css"; // 引入进度条样式
// 不需要导出 因为只需要让代码执行即可
// 前置守卫
// next是前置守卫必须必须必须执行的钩子 next必须执行 如果不执行 页面就死了
// next() 放过
// next(false) 跳转终止
// next(地址) 跳转到某个地址
const whiteList = ["/login", "/404"]; // 定义白名单
router.beforeEach(async (to, from, next) => {
nprogress.start(); // 开启进度条的意思
if (store.getters.token) {
// 只有有token的情况下 才能获取资料
// 如果有token
if (to.path === "/login") {
// 如果要访问的是 登录页
next("/"); // 跳到主页 // 有token 用处理吗?不用
} else {
// 只有放过的时候才去获取用户资料
// 是每次都获取吗
// 如果当前vuex中有用户的资料的id 表示 已经有资料了 不需要获取了 如果没有id才需要获取
if (!store.getters.userId) {
// 如果没有id才表示当前用户资料没有获取过
// async 函数所return的内容 用 await就可以接收到
const { roles } = await store.dispatch("user/getUserInfo");
// 如果说后续 需要根据用户资料获取数据的话 这里必须改成 同步
// 筛选用户的可用路由
// actions中函数 默认是Promise对象 调用这个对象 想要获取返回的值话 必须 加 await或者是then
// actions是做异步操作的
const routes = await store.dispatch(
"permission/filterRoutes",
roles.menus
);
// routes就是筛选得到的动态路由
// 动态路由 添加到 路由表中 默认的路由表 只有静态路由 没有动态路由
// addRoutes 必须 用 next(地址) 不能用next()
router.addRoutes(routes); // 添加动态路由到路由表 铺路
// 添加完动态路由之后
next(to.path); // 相当于跳到对应的地址 相当于多做一次跳转 为什么要多做一次跳转
// 进门了,但是进门之后我要去的地方的路还没有铺好,直接走,掉坑里,多做一次跳转,再从门外往里进一次,跳转之前 把路铺好,再次进来的时候,路就铺好了
} else {
next();
}
}
} else {
// 没有token的情况下
if (whiteList.indexOf(to.path) > -1) {
// 表示要去的地址在白名单
next();
} else {
next("/login");
}
}
nprogress.done(); // 解决手动切换地址时 进度条不关闭的问题
});
// 后置守卫
router.afterEach(() => {
nprogress.done(); // 关闭进度条
});

在**src/store/getters.js**配置导出 routes

1
2
3
4
5
6
7
8
9
10
const getters = {
sidebar: (state) => state.app.sidebar,
device: (state) => state.app.device,
token: (state) => state.user.token,
name: (state) => state.user.userInfo.username, // 建立用户名称的映射
userId: (state) => state.user.userInfo.userId, // 建立用户id的映射
companyId: (state) => state.user.userInfo.companyId, // 建立用户的公司Id映射
routes: (state) => state.permission.routes, // 导出当前的路由
};
export default getters;

六、在左侧菜单组件中, 引入 routes

/src/layout/components/Siderbar/index.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
<template>
<div :class="{ 'has-logo': showLogo }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</div>
</template>

<script>
import { mapGetters } from "vuex";
import Logo from "./Logo";
import SidebarItem from "./SidebarItem";
import variables from "@/styles/variables.scss";

export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters(["sidebar", "routes"]),
// 路由 addRoutes之后 不会响应式的更新的组件
// 默认静态路由 5条 + addRoutes(6条) = 11条
// routes() {
// return this.$router.options.routes
// },
activeMenu() {
const route = this.$route;
const { meta, path } = route;
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
},
showLogo() {
return this.$store.state.settings.sidebarLogo;
},
variables() {
return variables;
},
isCollapse() {
return !this.sidebar.opened;
},
},
};
</script>

七、登出时重置路由权限和 404 问题

现在,我们看似完成了访问权限的功能,实则不然,因为当我们登出操作之后,虽然看不到菜单,但是用户实际上可以访问页面,直接在地址栏输入地址就能访问

这是怎么回事?

这是因为我们前面在addRoutes的时候,一直都是在,登出的时候,我们并没有删,也没有重置,也就是说,我们之前加的路由在登出之后一直在,这怎么处理?

router/index.js文件中提供一个重置路由方法

1
2
3
4
5
// 重置路由
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // 重新设置路由的可匹配路径
}

这个方法可以将路由重新实例化,相当于换了一个新的路由,之前**加的路由**自然不存在了,只需要在登出的时候, 调用此方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 登出的action
lgout(context) {
// 删除token
context.commit('removeToken') // 不仅仅删除了vuex中的 还删除了缓存中的
// 删除用户资料
context.commit('removeUserInfo') // 删除用户信息
// 重置路由
resetRouter()
// 还有一步 vuex中的数据是不是还在
// 要清空permission模块下的state数据
// vuex中 user子模块 permission子模块
// 子模块调用子模块的action 默认情况下 子模块的context是子模块的
// 父模块 调用 子模块的action
context.commit('permission/setRoutes', [], { root: true })
// 子模块调用子模块的action 可以 将 commit的第三个参数 设置成 { root: true } 就表示当前的context不是子模块了 而是父模块
}

除此之外,我们发现在页面刷新的时候,本来应该拥有权限的页面出现了 404,这是因为 404 的匹配权限放在了静态路由,而动态路由在没有 addRoutes 之前,找不到对应的地址,就会显示 404,所以我们需要将 404 放置到动态路由的最后

src/permission.js

1
router.addRoutes([...routes, { path: "*", redirect: "/404", hidden: true }]); // 添加到路由表

八、完整代码

1、路由模块

/src/router/index.js

路由模块提供了静态路由和动态路由(从/src/router 中的其他路由子模块导入)

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
74
75
76
77
78
79
80
81
82
83
84
85
86
import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);
// 引入多个模块的规则
import approvalsRouter from "./modules/approvals";
import departmentsRouter from "./modules/departments";
import employeesRouter from "./modules/employees";
import permissionRouter from "./modules/permission";
import attendancesRouter from "./modules/attendances";
import salarysRouter from "./modules/salarys";
import settingRouter from "./modules/setting";
import socialRouter from "./modules/social";
import userRouter from "./modules/user";
/* Layout */
import Layout from "@/layout";
export const constantRoutes = [
{
path: "/login",
component: () => import("@/views/login/index"),
hidden: true,
},

{
path: "/404",
component: () => import("@/views/404"),
hidden: true,
},

{
path: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "dashboard",
component: () => import("@/views/dashboard/index"),
meta: { title: "首页", icon: "dashboard" },
},
],
},
{
path: "/import",
component: Layout,
hidden: true, // 该组件不在菜单中显示
children: [
{
path: "",
component: () => import("@/views/import"),
meta: {
title: "导入",
},
},
],
},
userRouter,
];
// 动态路由的变量
export const asyncRoutes = [
approvalsRouter,
departmentsRouter,
employeesRouter,
permissionRouter,
attendancesRouter,
salarysRouter,
settingRouter,
socialRouter,
];
const createRouter = () =>
new Router({
mode: "history", // require service support
base: "/hrsaas/",
scrollBehavior: () => ({ y: 0 }),
routes: [...constantRoutes], // 动态路由和静态路由的临时合并
});

const router = createRouter();

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}

export default router;

2、store 模块

/src/store/index.js

它将其他 store 子模块汇总到一起导出供使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
import app from "./modules/app";
import settings from "./modules/settings";
import user from "./modules/user";
import permission from "./modules/permission";
import tagsView from "./modules/tagsView";

Vue.use(Vuex);

const store = new Vuex.Store({
modules: {
app,
settings,
user,
permission,
tagsView,
},
getters,
});

export default store;

/src/store/getter.js

getter 模块提供了其他 store 模块的快捷访问方式

1
2
3
4
5
6
7
8
9
10
11
const getters = {
sidebar: (state) => state.app.sidebar,
device: (state) => state.app.device,
token: (state) => state.user.token, // 将user模块下的token作为快捷方式放出来
name: (state) => state.user.userInfo.username, // 将子模块中的对象中的名称开放出来
userId: (state) => state.user.userInfo.userId,
staffPhoto: (state) => state.user.userInfo.staffPhoto, // 头像
companyId: (state) => state.user.userInfo.companyId,
routes: (state) => state.permission.routes,
};
export default getters;

/src/store/modules/user.js

user 模块主要用来存储 token 和用户信息

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
import { getToken, setToken, removeToken, setTimeStamp } from "@/utils/auth";
import { login, getUserInfo, getUserDetailById } from "@/api/user";
import { resetRouter } from "@/router";
const state = {
token: getToken(), // 设置token为共享
userInfo: {}, // 这里为什么不写null ?
};
const mutations = {
// 设置token的mutations
setToken(state, token) {
state.token = token; // 只是设置了vuex中的数据
// 需要将vuex中的数据同步到缓存
setToken(token);
},
removeToken(state) {
state.token = null; // 设置vuex中的token为null
removeToken(); // 同步删除缓存中的token
},
setUserInfo(state, userInfo) {
state.userInfo = userInfo;
// state.userInfo = { ...userInfo } // 浅拷贝 如果要去改属性里面的某一个值 可以用浅拷贝的方式
},
removeUserInfo(state) {
state.userInfo = {}; // 重置为空对象
},
};
const actions = {
// 封装一个登录的action
// data认为是 { mobile,password }
// 只要用async标记了函数 那么这个函数本身就是promise对象
async login(context, data) {
// 调用登录接口
const result = await login(data);
// result就是token
context.commit("setToken", result);

setTimeStamp(); // 设置时间戳
},
// 获取用户资料
async getUserInfo(context) {
const result = await getUserInfo();
// 此时result里面已经有userId
const baseInfo = await getUserDetailById(result.userId); // 用户的基本信息
context.commit("setUserInfo", { ...result, ...baseInfo }); // 修改state中的用户资料
return result;
},
// 登出action
lgout({ commit }) {
// 删除token
commit("removeToken");
// 删除用户资料
commit("removeUserInfo");
// 重置路由
resetRouter();
// 清空路由模块下的路由信息
// Vuex子模块 调用子模块的mutation
commit("permission/setRoutes", [], { root: true }); // commit默认是提交的当前子模块的mutations
// 如果加上 root: true 就表示commit此时是根级的commit
// this.$store.commit('permission/setRoutes')
},
};
export default {
namespaced: true,
state,
mutations,
actions,
};

/src/store/modules/permission.js

permission 模块主要用来筛选出用户所拥有的路由

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
// 引入了静态路由
// 权限模块的目的 是提供当前用户所拥有的路由权限的数据 静态路由 + 当前用户的自身的动态路由
import { constantRoutes, asyncRoutes } from "@/router";
const state = {
// routes表示当前人的路由权限
routes: constantRoutes, // 当前所有人都默认拥有静态路由
};
const mutations = {
// newRoutes认为是用户登录之后获取的新路由
setRoutes(state, newRoutes) {
state.routes = [...constantRoutes, ...newRoutes]; // 静态路由 + 动态路由
// 需要得到newRoutes 才能调用mutations
},
};
const actions = {
// 筛选路由模块 menus认为是当前用户资料的menus标识 代表用户所拥有的的权限
filterRoutes(context, menus) {
// menus ['setting','approvals]
// asyncRoutes [{ name: '' }]
var routes = [];
menus.forEach((item) => {
// item是字符串 去asyncRoutes里面找有没有路由的name叫这个字符串
routes.push(...asyncRoutes.filter((route) => route.name === item));
});
// routes就是当前用户的筛选之后的动态路由
context.commit("setRoutes", routes);
// 最后加一行代码
return routes; // 这里返回是因为后面调用action的时候 要使用
},
};
export default {
namespaced: true,
state,
mutations,
actions,
};

3、权限拦截模块

src/permission.js

这个模块主要是通过路由守卫进行页面权限控制

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
// 路由的拦截权限问题
import router from "@/router";
import store from "@/store";
import NProgress from "nprogress";
import "nprogress/nprogress.css"; // 引入进度条样式

// 前置守卫
const whileList = ["/login", "/404"];
router.beforeEach(async (to, from, next) => {
NProgress.start(); // 开启进度条
// next是一个必须执行的钩子 不执行就卡主了
if (store.getters.token) {
if (to.path === "/login") {
// next() 放行
// next(false) 终止
// next(地址) 跳到某个 地址
next("/"); // 跳到主页
} else {
// 要判断是不是已经获取过资料了
if (!store.getters.userId) {
// 如果id不存在 意味着当前没有用户资料 就要去获取用户资料
// vuex的action是一个promise
const { roles } = await store.dispatch("user/getUserInfo");
// 此时已经获取完资料
const routes = await store.dispatch(
"permission/filterRoutes",
roles.menus
);
// 此时得到的routes是当前用户的所拥有的的动态路由的权限
router.addRoutes([
...routes,
{ path: "*", redirect: "/404", hidden: true },
]); // 将当前动态路由加到当前路由规则上
// 加await的意思是 强制等待获取完用户资料之后 才去放行 就能保证 用户进到页面时候 有资料
// 添加完路由之后 不能用next() 要用next(to.path) 否则地址不能生效 这算是一个已知 的小缺陷
// 执行完addRoutes 必须执行next(to.path) 不能执行 next() 这是一个已知的问题缺陷
next(to.path); // 解决直接执行next()时的异常
} else {
next(); // 放行
}
}
} else {
if (whileList.indexOf(to.path) > -1) {
// 表示在白名单里面
next();
} else {
next("/login");
}
}
NProgress.done(); // 是为了解决手动输入地址时 进度条不关闭的问题
});

// 后置守卫
router.afterEach(() => {
NProgress.done();
});

4、侧边栏

/src/layout/components/Siderbar/index.vue

最终侧边栏组件根据 store 中的用户路由模块,渲染出用户可以访问的菜单

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
<template>
<div :class="{ 'has-logo': showLogo }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</div>
</template>

<script>
import { mapGetters } from "vuex";
import Logo from "./Logo";
import SidebarItem from "./SidebarItem";
import variables from "@/styles/variables.scss";

export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters(["sidebar", "routes"]),
// 路由 addRoutes之后 不会响应式的更新的组件
// 默认静态路由 5条 + addRoutes(6条) = 11条
// routes() {
// return this.$router.options.routes
// },
activeMenu() {
const route = this.$route;
const { meta, path } = route;
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
},
showLogo() {
return this.$store.state.settings.sidebarLogo;
},
variables() {
return variables;
},
isCollapse() {
return !this.sidebar.opened;
},
},
};
</script>
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 congtianfeng
  • 访问人数: | 浏览次数: