317 lines
12 KiB
JavaScript
317 lines
12 KiB
JavaScript
import { defineStore } from 'pinia';
|
|
import auth from '@/plugins/auth';
|
|
import router, { constantRoutes, dynamicRoutes } from '@/router';
|
|
import Layout from '@/components/layout/index.vue';
|
|
import { getUserMenu } from '@/api/sys.js';
|
|
import { handleTree } from '~/utils/aiotagro.js';
|
|
|
|
// 匹配views里面所有的.vue文件
|
|
const modules = import.meta.glob('./../views/**/*.vue');
|
|
|
|
const usePermissionStore = defineStore('permission', {
|
|
state: () => ({
|
|
routes: [],
|
|
addRoutes: [],
|
|
sidebarRouters: [],
|
|
routeFlag: false,
|
|
userPermission: [],
|
|
}),
|
|
actions: {
|
|
setRoutes(routes) {
|
|
this.addRoutes = routes;
|
|
this.routes = constantRoutes.concat(routes);
|
|
},
|
|
setSidebarRouters(routes, flag) {
|
|
this.sidebarRouters = routes;
|
|
this.routeFlag = !!(flag == 200);
|
|
},
|
|
setUserPermission(arr) {
|
|
this.userPermission = arr;
|
|
console.log('=== 权限store更新 ===', arr);
|
|
},
|
|
// 强制刷新权限数据
|
|
async refreshPermissions() {
|
|
console.log('=== 强制刷新权限数据 ===');
|
|
this.routeFlag = false; // 重置路由标志,强制重新生成路由
|
|
return this.generateRoutes();
|
|
},
|
|
generateRoutes() {
|
|
return new Promise((resolve) => {
|
|
// 向后端请求路由数据
|
|
getUserMenu().then((res) => {
|
|
const { code, data } = res;
|
|
console.log('=== 权限路由生成 ===', { code, data });
|
|
|
|
const btnList = data.filter((i) => i.type === 2);
|
|
const permissionList = btnList.map((i) => i.authority).filter(auth => auth); // 过滤掉空权限
|
|
console.log('=== 设置用户权限列表 ===', permissionList);
|
|
this.setUserPermission(permissionList);
|
|
|
|
let menuList = data.filter((i) => i.type !== 2);
|
|
menuList = menuList.map((item) => {
|
|
// 确保 routeUrl 存在且不为空
|
|
let routeUrl = item.routeUrl || item.pageUrl || '';
|
|
|
|
// 规范化路径
|
|
routeUrl = normalizeRoutePath(routeUrl);
|
|
|
|
return {
|
|
id: item.id,
|
|
parentId: item.parentId,
|
|
// eslint-disable-next-line no-use-before-define
|
|
name: capitalizeFirstLetter(routeUrl),
|
|
path: routeUrl,
|
|
component: item.pageUrl,
|
|
alwaysShow: true,
|
|
meta: {
|
|
title: item.name,
|
|
icon: item.icon,
|
|
noCache: false,
|
|
link: null,
|
|
},
|
|
};
|
|
});
|
|
menuList = handleTree(menuList, 'id', 'parentId');
|
|
JSON.parse(JSON.stringify(menuList));
|
|
const sdata = JSON.parse(JSON.stringify(menuList));
|
|
|
|
console.log('=== 处理后的菜单列表 ===', menuList);
|
|
console.log('=== 路径检查 ===', menuList.map(item => ({ name: item.name, path: item.path })));
|
|
|
|
// 检查并修复双斜杠路径
|
|
const doubleSlashPaths = menuList.filter(item => item.path && item.path.includes('//'));
|
|
if (doubleSlashPaths.length > 0) {
|
|
console.error('=== 发现双斜杠路径 ===', doubleSlashPaths);
|
|
// 修复双斜杠路径
|
|
menuList.forEach(item => {
|
|
if (item.path && item.path.includes('//')) {
|
|
const originalPath = item.path;
|
|
item.path = item.path.replace(/\/+/g, '/');
|
|
console.warn('修复菜单路径:', originalPath, '->', item.path);
|
|
}
|
|
});
|
|
}
|
|
|
|
// eslint-disable-next-line no-use-before-define
|
|
const rewriteRoutes = filterAsyncRouter(menuList, false, true);
|
|
// eslint-disable-next-line no-use-before-define
|
|
const sidebarRoutes = filterAsyncRouter(sdata);
|
|
// eslint-disable-next-line no-use-before-define
|
|
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
|
|
|
|
console.log('=== 最终路由配置 ===', {
|
|
rewriteRoutes,
|
|
sidebarRoutes,
|
|
asyncRoutes
|
|
});
|
|
|
|
asyncRoutes.forEach((route) => {
|
|
router.addRoute(route);
|
|
});
|
|
this.setSidebarRouters(sidebarRoutes, code);
|
|
this.setRoutes(rewriteRoutes);
|
|
|
|
resolve(rewriteRoutes);
|
|
}).catch((error) => {
|
|
console.error('=== 获取用户菜单失败 ===', error);
|
|
// 如果获取菜单失败,返回空路由数组
|
|
this.setSidebarRouters([], 500);
|
|
this.setRoutes([]);
|
|
resolve([]);
|
|
});
|
|
});
|
|
},
|
|
},
|
|
});
|
|
|
|
function capitalizeFirstLetter(string) {
|
|
// 处理 null 或 undefined 值
|
|
if (!string || typeof string !== 'string') {
|
|
console.warn('capitalizeFirstLetter: Invalid string input:', string);
|
|
return 'Unknown';
|
|
}
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
string = string.replace('/', '');
|
|
return string.charAt(0).toUpperCase() + string.toLowerCase().slice(1);
|
|
}
|
|
|
|
// 规范化路由路径
|
|
function normalizeRoutePath(path) {
|
|
if (!path || typeof path !== 'string') {
|
|
return '/';
|
|
}
|
|
|
|
// 移除首尾空格
|
|
path = path.trim();
|
|
|
|
// 移除重复的斜杠
|
|
path = path.replace(/\/+/g, '/');
|
|
|
|
// 确保路径以 "/" 开头
|
|
if (!path.startsWith('/')) {
|
|
path = '/' + path;
|
|
}
|
|
|
|
// 确保路径不为空
|
|
if (path === '') {
|
|
path = '/';
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
// 安全拼接路径
|
|
function joinPaths(parentPath, childPath) {
|
|
// 规范化父路径
|
|
parentPath = normalizeRoutePath(parentPath);
|
|
childPath = normalizeRoutePath(childPath);
|
|
|
|
// 如果父路径是根路径,直接返回子路径
|
|
if (parentPath === '/') {
|
|
return childPath;
|
|
}
|
|
|
|
// 如果子路径是根路径,直接返回父路径
|
|
if (childPath === '/') {
|
|
return parentPath;
|
|
}
|
|
|
|
// 确保父路径不以斜杠结尾,子路径不以斜杠开头
|
|
if (parentPath.endsWith('/')) {
|
|
parentPath = parentPath.slice(0, -1);
|
|
}
|
|
if (childPath.startsWith('/')) {
|
|
childPath = childPath.slice(1);
|
|
}
|
|
|
|
// 拼接路径
|
|
const joinedPath = parentPath + '/' + childPath;
|
|
|
|
// 再次规范化,确保没有双斜杠
|
|
return normalizeRoutePath(joinedPath);
|
|
}
|
|
|
|
// 遍历后台传来的路由字符串,转换为组件对象
|
|
// eslint-disable-next-line no-unused-vars
|
|
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
|
return asyncRouterMap.filter((route) => {
|
|
if (type && route.children) {
|
|
// eslint-disable-next-line no-use-before-define,no-param-reassign
|
|
route.children = filterChildren(route.children);
|
|
}
|
|
if (route.component) {
|
|
if (route.component === 'Layout') {
|
|
// eslint-disable-next-line no-param-reassign
|
|
route.component = Layout;
|
|
} else if (!route.component) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
route.component = Layout;
|
|
} else {
|
|
// eslint-disable-next-line no-param-reassign,no-use-before-define
|
|
route.component = loadView(route.component);
|
|
}
|
|
}
|
|
if (route.children != null && route.children && route.children.length) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
route.children = filterAsyncRouter(route.children, route, type);
|
|
} else {
|
|
// eslint-disable-next-line no-param-reassign
|
|
delete route.children;
|
|
// eslint-disable-next-line no-param-reassign
|
|
delete route.redirect;
|
|
}
|
|
// eslint-disable-next-line no-param-reassign
|
|
delete route.id;
|
|
// eslint-disable-next-line no-param-reassign
|
|
delete route.parentId;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function filterChildren(childrenMap, lastRouter = false) {
|
|
let children = [];
|
|
childrenMap.forEach((el) => {
|
|
if (el.children && el.children.length) {
|
|
if (el.component === 'ParentView' && !lastRouter) {
|
|
el.children.forEach((c) => {
|
|
// eslint-disable-next-line no-param-reassign
|
|
c.path = joinPaths(el.path, c.path);
|
|
if (c.children && c.children.length) {
|
|
children = children.concat(filterChildren(c.children, c));
|
|
return;
|
|
}
|
|
children.push(c);
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
if (lastRouter) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
el.path = joinPaths(lastRouter.path, el.path);
|
|
if (el.children && el.children.length) {
|
|
children = children.concat(filterChildren(el.children, el));
|
|
return;
|
|
}
|
|
}
|
|
children = children.concat(el);
|
|
});
|
|
return children;
|
|
}
|
|
|
|
// 动态路由遍历,验证是否具备权限
|
|
export function filterDynamicRoutes(routes) {
|
|
const res = [];
|
|
routes.forEach((route) => {
|
|
if (route.permissions) {
|
|
if (auth.hasPermiOr(route.permissions)) {
|
|
res.push(route);
|
|
}
|
|
} else if (route.roles) {
|
|
if (auth.hasRoleOr(route.roles)) {
|
|
res.push(route);
|
|
}
|
|
}
|
|
});
|
|
return res;
|
|
}
|
|
|
|
export const loadView = (view) => {
|
|
// 添加默认的视图路径作为后备方案
|
|
const defaultView = () => import('~/views/entry/details.vue');
|
|
|
|
if (!view) {
|
|
console.warn('loadView: view parameter is empty, using default view');
|
|
return defaultView;
|
|
}
|
|
|
|
console.log('loadView: Loading view:', view);
|
|
|
|
let res;
|
|
// eslint-disable-next-line guard-for-in,no-restricted-syntax
|
|
for (const path in modules) {
|
|
const dir = path.split('views/')[1].split('.vue')[0];
|
|
if (dir === view) {
|
|
console.log('loadView: Found matching module:', path);
|
|
// 使用函数包装导入过程,添加错误处理
|
|
res = () =>
|
|
modules[path]().catch((error) => {
|
|
console.error('Failed to load module:', path, error);
|
|
// 如果模块加载失败,返回默认视图
|
|
return import('~/views/entry/details.vue');
|
|
});
|
|
break; // 找到匹配的模块后立即退出循环
|
|
}
|
|
}
|
|
|
|
// 如果没有找到匹配的视图,返回默认视图
|
|
if (!res) {
|
|
console.warn('loadView: View not found:', view, 'Available modules:', Object.keys(modules));
|
|
return defaultView;
|
|
}
|
|
|
|
return res;
|
|
};
|
|
|
|
export default usePermissionStore;
|