LinYeFangHuo/src/store/modules/permission.ts

495 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type { AppRouteRecordRaw, Menu } from '@/router/types';
import { defineStore } from 'pinia';
import { store } from '@/store';
import { useI18n } from '@/hooks/web/useI18n';
import { useUserStore } from './user';
import { useAppStoreWithOut } from './app';
import { toRaw } from 'vue';
import { transformObjToRoute, flatMultiLevelRoutes } from '@/router/helper/routeHelper';
import { transformRouteToMenu } from '@/router/helper/menuHelper';
import projectSetting from '@/settings/projectSetting';
import { PermissionModeEnum } from '@/enums/appEnum';
import { asyncRoutes } from '@/router/routes';
import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic';
import { filter, forEach } from '@/utils/helper/treeHelper';
import { getMenuList } from '@/api/sys/menu';
import { getPermCode } from '@/api/sys/user';
import { useMessage } from '@/hooks/web/useMessage';
import { PageEnum } from '@/enums/pageEnum';
import { router } from '@/router';
import { LAYOUT } from '@/router/constant';
import dayjs from 'dayjs';
import { getMainPage } from '@/api/path/project.api';
import { previewPath } from '@/utils';
interface PermissionState {
// Permission code list
// 权限代码列表
permCodeList: string[] | number[];
// Whether the route has been dynamically added
// 路由是否动态添加
isDynamicAddedRoute: boolean;
// To trigger a menu update
// 触发菜单更新
lastBuildMenuTime: number;
// Backstage menu list
// 后台菜单列表
backMenuList: Menu[];
// 菜单列表
frontMenuList: Menu[];
changeMenu: number;
homePage: string;
}
export const usePermissionStore = defineStore({
id: 'app-permission',
state: (): PermissionState => ({
// 权限代码列表
permCodeList: [],
// Whether the route has been dynamically added
// 路由是否动态添加
isDynamicAddedRoute: false,
// To trigger a menu update
// 触发菜单更新
lastBuildMenuTime: 0,
// Backstage menu list
// 后台菜单列表
backMenuList: [],
// menu List
// 菜单列表
frontMenuList: [],
changeMenu: dayjs().valueOf(),
// 主页面
homePage: '',
}),
getters: {
getChangeMenu(state): number {
return state.changeMenu;
},
getPermCodeList(state): string[] | number[] {
return state.permCodeList;
},
getBackMenuList(state): Menu[] {
return state.backMenuList;
},
getFrontMenuList(state): Menu[] {
return state.frontMenuList;
},
getLastBuildMenuTime(state): number {
return state.lastBuildMenuTime;
},
getIsDynamicAddedRoute(state): boolean {
return state.isDynamicAddedRoute;
},
getHomePage(state) {
return state.homePage;
},
},
actions: {
async setHomePage() {
this.homePage = await getMainPage();
},
setPermCodeList(codeList: string[]) {
this.permCodeList = codeList;
},
setBackMenuList(list: Menu[]) {
this.backMenuList = list;
list?.length > 0 && this.setLastBuildMenuTime();
},
setChangeMenu() {
this.changeMenu = dayjs().valueOf();
},
setFrontMenuList(list: Menu[]) {
this.frontMenuList = list;
},
setLastBuildMenuTime() {
this.lastBuildMenuTime = new Date().getTime();
},
setDynamicAddedRoute(added: boolean) {
this.isDynamicAddedRoute = added;
},
resetState(): void {
this.isDynamicAddedRoute = false;
this.permCodeList = [];
this.backMenuList = [];
this.lastBuildMenuTime = 0;
},
async changePermissionCode() {
const codeList = await getPermCode();
this.setPermCodeList(codeList);
},
// 构建路由
async buildRoutesAction(): Promise<AppRouteRecordRaw[]> {
const { t } = useI18n();
const userStore = useUserStore();
const appStore = useAppStoreWithOut();
let routes: AppRouteRecordRaw[] = [];
const roleList = toRaw(userStore.getRoleList) || [];
const { permissionMode = projectSetting.permissionMode } = appStore.getProjectConfig;
const normalizeMenu = (data) => {
let path = '';
if (data.item.url.indexOf('@') > 0) {
path = data.item.url.replace('@', '/');
} else {
path =
data.item.url ||
'/formCallPage?id=' +
data.item.id +
'&name=' +
data.item.name +
'&code=' +
data.item.code;
}
const newPath = {
orderNo: data.item.sortNo,
icon: data.item.iconName,
title: data.item.name,
meta: {
orderNo: data.item.sortNo,
icon: data.item.iconName,
title: data.item.name,
elements: (data.item && data.item.elements) || '',
},
name: data.item.name,
hideMenu: data.item.status == 1 ? false : true,
path: path,
id: data.item.id || '',
children: [],
};
if (data.children && data.children.length > 0) {
data.children.forEach((element) => {
newPath.children.push(normalizeMenu(element));
});
}
return newPath;
};
const childRoute = (childData) => {
const modules = import.meta.glob('../../views/**/**/**/index.vue');
childData.forEach((element) => {
// 菜单里配置带参数,但没有单独的主路由,先注册主路由
if (element.item.status == 1 && element.item.url.indexOf('@') > 0) {
if (router.hasRoute(element.item.code)) {
const path = element.item.url.substring(0, element.item.url.indexOf('@'));
router.addRoute(element.item.code, {
path: path + '/:id',
name: element.item.url.replaceAll('/', ''),
meta: {
title: element.item.name,
icon: element.item.iconName,
elements: element.item.elements,
},
component: modules['../../views/demo' + path + '/index.vue'],
});
} else {
const path = element.item.url.substring(0, element.item.url.indexOf('@'));
router.addRoute('Root', {
path: path,
name: element.item.code,
meta: {
title: element.item.name,
icon: element.item.iconName,
hideChildrenInMenu: true,
elements: element.item.elements,
},
component: LAYOUT,
children: [
{
path: path + '/:id',
name: element.item.url.replaceAll('/', ''),
meta: {
title: element.item.name,
icon: element.item.iconName,
elements: element.item.elements,
},
component: modules['../../views/demo' + path + '/index.vue'],
},
],
});
}
} else if (element.item.code && element.item.status == 1) {
router.addRoute(element.item.code, {
path: element.item.url,
name: element.item.url.replaceAll('/', ''),
meta: {
title: element.item.name,
icon: element.item.iconName,
elements: element.item.elements,
},
// component: () => import('../../views/demo' + element.item.url + '/index.vue')
component: modules['../../views/demo' + element.item.url + '/index.vue'],
});
}
if (element.children && element.children.length > 0) {
childRoute(element.children);
}
});
};
// 注册路由
const registeredRoute = (data) => {
const modules = import.meta.glob('../../views/**/**/**/index.vue');
if (data.item.url.split('/')[1] == 'map') {
// 大屏的情况下不继承layout
router.addRoute('Root', {
path: data.item.url,
name: data.item.code,
meta: {
title: data.item.name,
icon: data.item.iconName,
elements: data.item.elements,
},
component: modules['../../views' + data.item.url + '/index.vue'],
});
} else {
// 正常菜单
if (data.children.length == 0 && data.item.status == 1) {
// 没有子菜单
if (data.item.url.indexOf('@') > 0) {
//给带参数的路由先注册主路由
const path = data.item.url.substring(0, data.item.url.indexOf('@'));
router.addRoute('Root', {
path: path,
name: data.item.code,
meta: {
title: data.item.name,
icon: data.item.iconName,
hideChildrenInMenu: true,
elements: data.item.elements,
},
component: LAYOUT,
children: [
{
path: path + '/:id',
name: data.item.url.replaceAll('/', ''),
meta: {
title: data.item.name,
icon: data.item.iconName,
elements: data.item.elements,
},
component: modules['../../views/demo' + path + '/index.vue'],
},
],
});
} else {
router.addRoute('Root', {
path: data.item.url,
name: data.item.code,
meta: {
title: data.item.name,
icon: data.item.iconName,
hideChildrenInMenu: true,
elements: data.item.elements,
},
component: LAYOUT,
children: [
{
path: data.item.url,
name: data.item.url.replaceAll('/', ''),
meta: {
title: data.item.name,
icon: data.item.iconName,
elements: data.item.elements,
},
component: modules['../../views/demo' + data.item.url + '/index.vue'],
},
],
});
}
} else {
// 有子菜单
if (data.item.status == 0) {
return;
}
router.addRoute('Root', {
path: data.item.url,
name: data.item.code,
meta: {
title: data.item.name,
icon: data.item.iconName,
elements: data.item.elements,
},
component:
data.item.parentId == 0
? LAYOUT
: modules['../../views/demo' + data.item.url + '/index.vue'],
});
if (data.children && data.children.length > 0) {
childRoute(data.children);
}
}
}
// console.log(router.getRoutes());
};
//通过后端获取菜单
const _this = this;
async function buildMenusByServer(list) {
const data = await getMenuList({ typeId: userStore.getSubject });
const moduleRoutes = list;
await data.forEach((value) => {
moduleRoutes.push(normalizeMenu(value));
registeredRoute(value);
});
await Promise.all(moduleRoutes);
// 设置菜单列表
_this.setFrontMenuList(moduleRoutes);
}
// 路由过滤器 在 函数filter 作为回调传入遍历使用
const routeFilter = (route: AppRouteRecordRaw) => {
const { meta } = route;
// 抽出角色
const { roles } = meta || {};
if (!roles) return true;
// 进行角色权限判断
return roleList.some((role) => roles.includes(role));
};
const routeRemoveIgnoreFilter = (route: AppRouteRecordRaw) => {
const { meta } = route;
// ignoreRoute 为true 则路由仅用于菜单生成,不会在实际的路由表中出现
const { ignoreRoute } = meta || {};
// arr.filter 返回 true 表示该元素通过测试
return !ignoreRoute;
};
/**
* @description 根据设置的首页path修正routes中的affix标记固定首页
* */
const patchHomeAffix = async (routes: AppRouteRecordRaw[]) => {
if (!routes || routes.length === 0) return;
let homePath: string = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
function patcher(routes: AppRouteRecordRaw[], parentPath = '') {
if (parentPath) parentPath = parentPath + '/';
routes.forEach((route: AppRouteRecordRaw) => {
const { path, children, redirect } = route;
const currentPath = path.startsWith('/') ? path : parentPath + path;
if (currentPath === homePath) {
if (redirect) {
homePath = route.redirect! as string;
} else {
route.meta = Object.assign({}, route.meta, { affix: true });
throw new Error('end');
}
}
children && children.length > 0 && patcher(children, currentPath);
});
}
try {
const homePage = await getMainPage();
const path = previewPath(homePage);
if (hoemPage) {
window.open(path, '_self');
} else {
patcher(routes);
}
} catch (e) {
// 已处理完毕跳出循环
}
return;
};
switch (permissionMode) {
// 角色权限
case PermissionModeEnum.ROLE:
// 对非一级路由进行过滤
routes = filter(asyncRoutes, routeFilter);
// 对一级路由根据角色权限过滤
routes = routes.filter(routeFilter);
// Convert multi-level routing to level 2 routing
// 将多级路由转换为 2 级路由
routes = flatMultiLevelRoutes(routes);
break;
// 路由映射, 默认进入该case
case PermissionModeEnum.ROUTE_MAPPING:
// 对非一级路由进行过滤
routes = filter(asyncRoutes, routeFilter);
// 对一级路由再次根据角色权限过滤
routes = routes.filter(routeFilter);
// 将路由转换成菜单
const menuList = transformRouteToMenu(routes, true);
// 移除掉 ignoreRoute: true 的路由 非一级路由
routes = filter(routes, routeRemoveIgnoreFilter);
// 移除掉 ignoreRoute: true 的路由 一级路由;
routes = routes.filter(routeRemoveIgnoreFilter);
// 对菜单进行排序
menuList.sort((a, b) => {
return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0);
});
await buildMenusByServer(menuList);
// Convert multi-level routing to level 2 routing
// 将多级路由转换为 2 级路由
routes = flatMultiLevelRoutes(routes);
break;
// If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below
// 如果确定不需要做后台动态权限,请在下方注释整个判断
case PermissionModeEnum.BACK:
const { createMessage } = useMessage();
createMessage.loading({
content: t('sys.app.menuLoading'),
duration: 1,
});
// !Simulate to obtain permission codes from the background,
// 模拟从后台获取权限码,
// this function may only need to be executed once, and the actual project can be put at the right time by itself
// 这个功能可能只需要执行一次,实际项目可以自己放在合适的时间
let routeList: AppRouteRecordRaw[] = [];
try {
await this.changePermissionCode();
routeList = (await getMenuList()) as AppRouteRecordRaw[];
} catch (error) {
console.error(error);
}
// Dynamically introduce components
// 动态引入组件
routeList = transformObjToRoute(routeList);
// Background routing to menu structure
// 后台路由到菜单结构
const backMenuList = transformRouteToMenu(routeList);
this.setBackMenuList(backMenuList);
// remove meta.ignoreRoute item
// 删除 meta.ignoreRoute 项
routeList = filter(routeList, routeRemoveIgnoreFilter);
routeList = routeList.filter(routeRemoveIgnoreFilter);
routeList = flatMultiLevelRoutes(routeList);
routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
break;
}
routes.push(ERROR_LOG_ROUTE);
patchHomeAffix(routes);
return routes;
},
},
});
// Need to be used outside the setup
// 需要在设置之外使用
export function usePermissionStoreWithOut() {
return usePermissionStore(store);
}