/src/router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
export const Layout = () => import("@/layout/index.vue");
// 静态路由
export const constantRoutes: RouteRecordRaw[] = [
{
path: "/redirect",
component: Layout,
meta: { hidden: true },
children: [
{
path: "/redirect/:path(.*)",
component: () => import("@/views/redirect/index.vue"),
},
],
},
{
path: "/login",
component: () => import("@/views/login/index.vue"),
meta: { hidden: true },
},
{
path: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
component: () => import("@/views/dashboard/index.vue"),
name: "Dashboard",
meta: { title: "dashboard", icon: "homepage", affix: true },
},
{
path: "401",
component: () => import("@/views/error-page/401.vue"),
meta: { hidden: true },
},
{
path: "404",
component: () => import("@/views/error-page/404.vue"),
meta: { hidden: true },
},
],
},
// 外部链接
/*{
path: '/external-link',
component: Layout,
children: [
{
path: 'https://www.cnblogs.com/haoxianrui/',
meta: { title: '外部链接', icon: 'link' }
}
]
}*/
// 多级嵌套路由
/* {
path: '/nested',
component: Layout,
redirect: '/nested/level1/level2',
name: 'Nested',
meta: {title: '多级菜单', icon: 'nested'},
children: [
{
path: 'level1',
component: () => import('@/views/nested/level1/index.vue'),
name: 'Level1',
meta: {title: '菜单一级'},
redirect: '/nested/level1/level2',
children: [
{
path: 'level2',
component: () => import('@/views/nested/level1/level2/index.vue'),
name: 'Level2',
meta: {title: '菜单二级'},
redirect: '/nested/level1/level2/level3',
children: [
{
path: 'level3-1',
component: () => import('@/views/nested/level1/level2/level3/index1.vue'),
name: 'Level3-1',
meta: {title: '菜单三级-1'}
},
{
path: 'level3-2',
component: () => import('@/views/nested/level1/level2/level3/index2.vue'),
name: 'Level3-2',
meta: {title: '菜单三级-2'}
}
]
}
]
},
]
}*/
];
/**
* 创建路由
*/
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes as RouteRecordRaw[],
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 }),
});
/**
* 重置路由
*/
export function resetRouter() {
router.replace({ path: "/login" });
location.reload();
}
export default router;
/src/store/permission
import { RouteRecordRaw } from "vue-router";
import { defineStore } from "pinia";
import { constantRoutes } from "@/router";
import { store } from "@/store";
import { listRoutes } from "@/api/menu";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layout/index.vue");
/**
* Use meta.role to determine if the current user has permission
*
* @param roles 用户角色集合
* @param route 路由
* @returns
*/
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
if (route.meta && route.meta.roles) {
// 角色【超级管理员】拥有所有权限,忽略校验
if (roles.includes("ROOT")) {
return true;
}
return roles.some((role) => {
if (route.meta?.roles !== undefined) {
return (route.meta.roles as string[]).includes(role);
}
});
}
return false;
};
/**
* 递归过滤有权限的异步(动态)路由
*
* @param routes 接口返回的异步(动态)路由
* @param roles 用户角色集合
* @returns 返回用户有权限的异步(动态)路由
*/
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
const asyncRoutes: RouteRecordRaw[] = [];
routes.forEach((route) => {
const tmpRoute = { ...route }; // ES6扩展运算符复制新对象
// console.log(tmpRoute,route);
// 判断用户(角色)是否有该路由的访问权限
// console.log(modules,tmpRoute.component);
if (hasPermission(roles, tmpRoute)) {
if (tmpRoute.component?.toString() == "Layout") {
tmpRoute.component = Layout;
} else {
const component = modules[`../../views/${tmpRoute.component}.vue`];
// console.log(modules['../../views/system/menu/index.vue'],'../../views/system/menu/index.vue');
if (component) {
tmpRoute.component = component;
} else {
tmpRoute.component = modules[`../../views/error-page/404.vue`];
}
}
// debugger
if (tmpRoute.children) {
tmpRoute.children = filterAsyncRoutes(tmpRoute.children, roles);
}
// console.log(asyncRoutes);
asyncRoutes.push(tmpRoute);
// console.log(asyncRoutes)
}
});
return asyncRoutes;
};
// setup
export const usePermissionStore = defineStore("permission", () => {
// state
const routes = ref<RouteRecordRaw[]>([]);
// actions
function setRoutes(newRoutes: RouteRecordRaw[]) {
routes.value = constantRoutes.concat(newRoutes);
}
/**
* 生成动态路由
*
* @param roles 用户角色集合
* @returns
*/
function generateRoutes(roles: string[]) {
return new Promise<RouteRecordRaw[]>((resolve, reject) => {
// 接口获取所有路由
listRoutes()
.then(({ data: asyncRoutes }) => {
console.log(asyncRoutes,'asyncRoutes');
// 根据角色获取有访问权限的路由
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
console.log(accessedRoutes,'accessedRoutes');
setRoutes(accessedRoutes);
resolve(accessedRoutes);
})
.catch((error) => {
reject(error);
});
});
}
return { routes, setRoutes, generateRoutes };
});
// 非setup
export function usePermissionStoreHook() {
return usePermissionStore(store);
}
/src/permission
import router from "@/router";
import { useUserStoreHook } from "@/store/modules/user";
import { usePermissionStoreHook } from "@/store/modules/permission";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
NProgress.configure({ showSpinner: false }); // 进度条
const permissionStore = usePermissionStoreHook();
// 白名单路由
const whiteList = ["/login"];
router.beforeEach(async (to, from, next) => {
NProgress.start();
const hasToken = localStorage.getItem("accessToken");
if (hasToken) {
if (to.path === "/login") {
// 如果已登录,跳转首页
next({ path: "/" });
NProgress.done();
} else {
const userStore = useUserStoreHook();
const hasRoles = userStore.roles && userStore.roles.length > 0;
if (hasRoles) {
// 未匹配到任何路由,跳转404
if (to.matched.length === 0) {
from.name ? next({ name: from.name }) : next("/404");
} else {
next();
}
} else {
try {
const { roles,perms } = await userStore.getInfo();
console.log(roles,'roles');
const accessRoutes = await permissionStore.generateRoutes(roles);
accessRoutes.forEach((route) => {
router.addRoute(route);
});
next({ ...to, replace: true });
} catch (error) {
// 移除 token 并跳转登录页
await userStore.resetToken();
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
// 未登录可以访问白名单页面
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
});
main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from '@/router';
import { setupStore } from '@/store';
import { setupDirective } from '@/directive';
//引入权限过滤功能
import '@/permission';
// 本地SVG图标
import 'virtual:svg-icons-register';
// 国际化
import i18n from '@/lang/index';
// 样式
import 'element-plus/theme-chalk/dark/css-vars.css';
import '@/styles/index.scss';
import 'uno.css';
const app = createApp(App);
// 全局注册 自定义指令(directive)
setupDirective(app);
// 全局注册 状态管理(store)
setupStore(app);
app.use(router).use(i18n).mount('#app');
以上是路由菜单管理的权限
//指令权限 、src/directive/index
import { useUserStoreHook } from '@/store/modules/user';
import { Directive, DirectiveBinding } from 'vue';
/**
* 按钮权限
*/
export const hasPerm: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
// 「超级管理员」拥有所有的按钮权限
const { roles, perms } = useUserStoreHook();
if (roles.includes('ROOT')) {
return true;
}
// 「其他角色」按钮权限校验
const { value } = binding;
if (value) {
const requiredPerms = value; // DOM绑定需要的按钮权限标识
const hasPerm = perms?.some(perm => {
return requiredPerms.includes(perm);
});
if (!hasPerm) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(
"need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
);
}
}
};
/**
* 角色权限
*/
export const hasRole: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
if (value) {
const requiredRoles = value; // DOM绑定需要的角色编码
const { roles } = useUserStoreHook();
const hasRole = roles.some(perm => {
return requiredRoles.includes(perm);
});
if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error("need roles! Like v-has-role=\"['admin','test']\"");
}
}
};