616 lines
15 KiB
TypeScript
616 lines
15 KiB
TypeScript
import { toastService } from '~/components/ui';
|
|
import { postgrestGet } from '../postgrest-client';
|
|
|
|
// 路由数据接口
|
|
export interface RouteInfo {
|
|
id: number;
|
|
path: string;
|
|
name: string;
|
|
meta: {
|
|
title: string;
|
|
icon: string;
|
|
order: number;
|
|
requiredRole?: string;
|
|
};
|
|
parent_id: number;
|
|
is_menu: number;
|
|
}
|
|
|
|
// 用户路由权限接口
|
|
export interface UserRoutePermission {
|
|
route_id: number;
|
|
role_id: number;
|
|
permission: string;
|
|
route: RouteInfo;
|
|
}
|
|
|
|
// MenuItem结构接口
|
|
export interface MenuItem {
|
|
id: string;
|
|
title: string;
|
|
path: string;
|
|
icon: string;
|
|
order: number;
|
|
hideBreadcrumb?: boolean;
|
|
requiredRole?: string;
|
|
children?: MenuItem[];
|
|
}
|
|
|
|
// 静态菜单数据作为后备 (保留用于开发和紧急情况,当前不使用)
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const FALLBACK_MENU_DATA: Record<string, MenuItem[]> = {
|
|
'admin': [
|
|
{
|
|
id: 'home',
|
|
title: '系统概览',
|
|
path: '/home',
|
|
icon: 'ri-home-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'chat-with-llm',
|
|
title: 'AI对话',
|
|
path: '/chat-with-llm',
|
|
icon: 'ri-chat-smile-2-line',
|
|
order: 2
|
|
},
|
|
{
|
|
id: 'file-management',
|
|
title: '文件管理',
|
|
path: '/files',
|
|
icon: 'ri-folder-line',
|
|
order: 3,
|
|
children: [
|
|
{
|
|
id: 'file-upload',
|
|
title: '文件上传',
|
|
path: '/files/upload',
|
|
icon: 'ri-upload-cloud-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'documents',
|
|
title: '文档列表',
|
|
path: '/documents',
|
|
icon: 'ri-file-list-3-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'rule-management',
|
|
title: '评查规则库',
|
|
path: '/rules',
|
|
icon: 'ri-book-3-line',
|
|
order: 4,
|
|
children: [
|
|
{
|
|
id: 'rule-groups',
|
|
title: '评查点分组',
|
|
path: '/rule-groups',
|
|
icon: 'ri-folder-open-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'rules-list',
|
|
title: '评查点列表',
|
|
path: '/rules',
|
|
icon: 'ri-list-check-3',
|
|
order: 2
|
|
},
|
|
{
|
|
id: 'rules-file',
|
|
title: '评查文件列表',
|
|
path: '/rules-files',
|
|
icon: 'ri-list-check-2',
|
|
order: 3
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'contract-template',
|
|
title: '合同模板',
|
|
path: '/contract-template',
|
|
icon: 'ri-file-search-line',
|
|
order: 5,
|
|
children: [
|
|
{
|
|
id: 'contract-search-ai',
|
|
title: '智能搜索',
|
|
path: '/contract-template/search',
|
|
icon: 'ri-search-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'contract-list',
|
|
title: '合同列表',
|
|
path: '/contract-template/list',
|
|
icon: 'ri-folder-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'system-settings',
|
|
title: '系统设置',
|
|
path: '/settings',
|
|
icon: 'ri-settings-4-line',
|
|
order: 6,
|
|
requiredRole: 'developer',
|
|
children: [
|
|
{
|
|
id: 'config-lists',
|
|
title: '配置列表',
|
|
path: '/config-lists',
|
|
icon: 'ri-list-check-3',
|
|
order: 1,
|
|
requiredRole: 'developer'
|
|
},
|
|
{
|
|
id: 'document-types',
|
|
title: '文档类型',
|
|
path: '/document-types',
|
|
icon: 'ri-file-list-line',
|
|
order: 2,
|
|
requiredRole: 'developer'
|
|
},
|
|
{
|
|
id: 'prompt-management',
|
|
title: '提示词管理',
|
|
path: '/prompts',
|
|
icon: 'ri-chat-1-line',
|
|
order: 3,
|
|
requiredRole: 'developer'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'cross-checking',
|
|
title: '交叉评查',
|
|
path: '/cross-checking',
|
|
icon: 'ri-color-filter-line',
|
|
order: 7
|
|
}
|
|
],
|
|
'common': [
|
|
{
|
|
id: 'home',
|
|
title: '系统概览',
|
|
path: '/home',
|
|
icon: 'ri-home-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'file-management',
|
|
title: '文件管理',
|
|
path: '/files',
|
|
icon: 'ri-folder-line',
|
|
order: 3,
|
|
children: [
|
|
{
|
|
id: 'file-upload',
|
|
title: '文件上传',
|
|
path: '/files/upload',
|
|
icon: 'ri-upload-cloud-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'documents',
|
|
title: '文档列表',
|
|
path: '/documents',
|
|
icon: 'ri-file-list-3-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'rule-management',
|
|
title: '评查规则库',
|
|
path: '/rules',
|
|
icon: 'ri-book-3-line',
|
|
order: 4,
|
|
children: [
|
|
{
|
|
id: 'rule-groups',
|
|
title: '评查点分组',
|
|
path: '/rule-groups',
|
|
icon: 'ri-folder-open-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'rules-list',
|
|
title: '评查点列表',
|
|
path: '/rules',
|
|
icon: 'ri-list-check-3',
|
|
order: 2
|
|
},
|
|
{
|
|
id: 'rules-file',
|
|
title: '评查文件列表',
|
|
path: '/rules-files',
|
|
icon: 'ri-list-check-2',
|
|
order: 3
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'contract-template',
|
|
title: '合同模板',
|
|
path: '/contract-template',
|
|
icon: 'ri-file-search-line',
|
|
order: 5,
|
|
children: [
|
|
{
|
|
id: 'contract-search-ai',
|
|
title: '智能搜索',
|
|
path: '/contract-template/search',
|
|
icon: 'ri-search-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'contract-list',
|
|
title: '合同列表',
|
|
path: '/contract-template/list',
|
|
icon: 'ri-folder-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'cross-checking',
|
|
title: '交叉评查',
|
|
path: '/cross-checking',
|
|
icon: 'ri-color-filter-line',
|
|
order: 7
|
|
}
|
|
],
|
|
'deptLeader': [
|
|
{
|
|
id: 'home',
|
|
title: '系统概览',
|
|
path: '/home',
|
|
icon: 'ri-home-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'chat-with-llm',
|
|
title: 'AI对话',
|
|
path: '/chat-with-llm',
|
|
icon: 'ri-chat-smile-2-line',
|
|
order: 2
|
|
},
|
|
{
|
|
id: 'file-management',
|
|
title: '文件管理',
|
|
path: '/files',
|
|
icon: 'ri-folder-line',
|
|
order: 3,
|
|
children: [
|
|
{
|
|
id: 'file-upload',
|
|
title: '文件上传',
|
|
path: '/files/upload',
|
|
icon: 'ri-upload-cloud-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'documents',
|
|
title: '文档列表',
|
|
path: '/documents',
|
|
icon: 'ri-file-list-3-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'rule-management',
|
|
title: '评查规则库',
|
|
path: '/rules',
|
|
icon: 'ri-book-3-line',
|
|
order: 4,
|
|
children: [
|
|
{
|
|
id: 'rule-groups',
|
|
title: '评查点分组',
|
|
path: '/rule-groups',
|
|
icon: 'ri-folder-open-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'rules-list',
|
|
title: '评查点列表',
|
|
path: '/rules',
|
|
icon: 'ri-list-check-3',
|
|
order: 2
|
|
},
|
|
{
|
|
id: 'rules-file',
|
|
title: '评查文件列表',
|
|
path: '/rules-files',
|
|
icon: 'ri-list-check-2',
|
|
order: 3
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'contract-template',
|
|
title: '合同模板',
|
|
path: '/contract-template',
|
|
icon: 'ri-file-search-line',
|
|
order: 5,
|
|
children: [
|
|
{
|
|
id: 'contract-search-ai',
|
|
title: '智能搜索',
|
|
path: '/contract-template/search',
|
|
icon: 'ri-search-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'contract-list',
|
|
title: '合同列表',
|
|
path: '/contract-template/list',
|
|
icon: 'ri-folder-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'cross-checking',
|
|
title: '交叉评查',
|
|
path: '/cross-checking',
|
|
icon: 'ri-color-filter-line',
|
|
order: 7
|
|
}
|
|
],
|
|
'groupLeader': [
|
|
{
|
|
id: 'home',
|
|
title: '系统概览',
|
|
path: '/home',
|
|
icon: 'ri-home-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'file-management',
|
|
title: '文件管理',
|
|
path: '/files',
|
|
icon: 'ri-folder-line',
|
|
order: 3,
|
|
children: [
|
|
{
|
|
id: 'file-upload',
|
|
title: '文件上传',
|
|
path: '/files/upload',
|
|
icon: 'ri-upload-cloud-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'documents',
|
|
title: '文档列表',
|
|
path: '/documents',
|
|
icon: 'ri-file-list-3-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'rule-management',
|
|
title: '评查规则库',
|
|
path: '/rules',
|
|
icon: 'ri-book-3-line',
|
|
order: 4,
|
|
children: [
|
|
{
|
|
id: 'rule-groups',
|
|
title: '评查点分组',
|
|
path: '/rule-groups',
|
|
icon: 'ri-folder-open-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'rules-list',
|
|
title: '评查点列表',
|
|
path: '/rules',
|
|
icon: 'ri-list-check-3',
|
|
order: 2
|
|
},
|
|
{
|
|
id: 'rules-file',
|
|
title: '评查文件列表',
|
|
path: '/rules-files',
|
|
icon: 'ri-list-check-2',
|
|
order: 3
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'contract-template',
|
|
title: '合同模板',
|
|
path: '/contract-template',
|
|
icon: 'ri-file-search-line',
|
|
order: 5,
|
|
children: [
|
|
{
|
|
id: 'contract-search-ai',
|
|
title: '智能搜索',
|
|
path: '/contract-template/search',
|
|
icon: 'ri-search-line',
|
|
order: 1
|
|
},
|
|
{
|
|
id: 'contract-list',
|
|
title: '合同列表',
|
|
path: '/contract-template/list',
|
|
icon: 'ri-folder-line',
|
|
order: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'cross-checking',
|
|
title: '交叉评查',
|
|
path: '/cross-checking',
|
|
icon: 'ri-color-filter-line',
|
|
order: 7
|
|
}
|
|
]
|
|
};
|
|
|
|
/**
|
|
* 根据角色获取用户可访问的路由
|
|
* @param roleKey 角色标识 (如: 'admin', 'common', 'deptLeader', 'groupLeader')
|
|
* @returns 用户可访问的路由列表
|
|
*/
|
|
export async function getUserRoutesByRole(roleKey: string, jwt?: string): Promise<{ success: boolean; data?: MenuItem[]; error?: string; shouldRedirectToHome?: boolean }> {
|
|
try {
|
|
console.log(`获取角色 ${roleKey} 的路由权限`);
|
|
|
|
// 首先获取角色ID
|
|
const roleResult = await postgrestGet<Array<{id: number}>>("roles", {
|
|
filter: {
|
|
"role_key": `eq.${roleKey}`
|
|
},
|
|
token: jwt
|
|
});
|
|
|
|
if (roleResult.error || !roleResult.data || roleResult.data.length === 0) {
|
|
console.error("角色不存在:", roleKey);
|
|
toastService.error("角色不存在,请联系管理员配置权限后重新登录");
|
|
return { success: false, error: "角色不存在", shouldRedirectToHome: true };
|
|
}
|
|
|
|
const roleId = roleResult.data[0].id;
|
|
|
|
// 查询角色的路由权限
|
|
const roleRoutesResult = await postgrestGet<Array<{route_id: number}>>("role_route", {
|
|
filter: {
|
|
"role_id": `eq.${roleId}`
|
|
},
|
|
token: jwt
|
|
});
|
|
|
|
if (roleRoutesResult.error) {
|
|
console.error("查询角色路由关联失败:", roleRoutesResult.error);
|
|
toastService.error("查询角色路由关联失败,请稍后再试");
|
|
return { success: false, error: "查询角色路由关联失败", shouldRedirectToHome: true };
|
|
}
|
|
|
|
const roleRoutes = roleRoutesResult.data || [];
|
|
const routeIds = roleRoutes.map(item => item.route_id);
|
|
|
|
if (routeIds.length === 0) {
|
|
console.log(`角色 ${roleKey} 没有分配任何路由权限`);
|
|
toastService.error("您的角色没有分配任何路由权限,请联系管理员配置权限");
|
|
return { success: false, error: "角色没有分配任何路由权限", shouldRedirectToHome: true };
|
|
}
|
|
|
|
// 查询具体的路由信息
|
|
const routesResult = await postgrestGet<RouteInfo[]>("sys_routes", {
|
|
filter: {
|
|
"id": `in.(${routeIds.join(',')})`,
|
|
"is_menu": "eq.1"
|
|
},
|
|
order: "parent_id,meta->>order",
|
|
token: jwt
|
|
});
|
|
|
|
if (routesResult.error) {
|
|
console.error("查询路由信息失败:", routesResult.error);
|
|
toastService.error("查询路由信息失败,请稍后再试");
|
|
return { success: false, error: "查询路由信息失败", shouldRedirectToHome: true };
|
|
}
|
|
|
|
const routes = routesResult.data || [];
|
|
|
|
// 构建菜单树
|
|
const menuItems = buildMenuTreeFromRoutes(routes);
|
|
|
|
console.log(`角色 ${roleKey} 可访问 ${menuItems.length} 个路由`);
|
|
return { success: true, data: menuItems };
|
|
|
|
} catch (error) {
|
|
console.error("获取用户路由时发生错误:", error);
|
|
toastService.error("获取用户路由时发生错误,请稍后再试");
|
|
return {
|
|
success: false,
|
|
error: `获取用户路由失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
shouldRedirectToHome: true
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从路由信息构建菜单树结构
|
|
* @param routes 路由信息数组
|
|
* @returns 菜单树结构
|
|
*/
|
|
function buildMenuTreeFromRoutes(routes: RouteInfo[]): MenuItem[] {
|
|
// 转换为MenuItem格式
|
|
const menuMap = new Map<number, MenuItem>();
|
|
|
|
routes.forEach(route => {
|
|
const menuItem: MenuItem = {
|
|
id: route.name,
|
|
title: route.meta.title,
|
|
path: route.path,
|
|
icon: route.meta.icon,
|
|
order: route.meta.order || 0,
|
|
requiredRole: route.meta.requiredRole
|
|
};
|
|
|
|
menuMap.set(route.id, menuItem);
|
|
});
|
|
|
|
// 构建父子关系
|
|
const rootItems: MenuItem[] = [];
|
|
const itemsWithParent: Array<{ item: MenuItem; parentId: number }> = [];
|
|
|
|
routes.forEach(route => {
|
|
const menuItem = menuMap.get(route.id);
|
|
if (!menuItem) return;
|
|
|
|
if (route.parent_id === 0) {
|
|
rootItems.push(menuItem);
|
|
} else {
|
|
itemsWithParent.push({ item: menuItem, parentId: route.parent_id });
|
|
}
|
|
});
|
|
|
|
// 添加子菜单
|
|
itemsWithParent.forEach(({ item, parentId }) => {
|
|
const parent = menuMap.get(parentId);
|
|
if (parent) {
|
|
if (!parent.children) {
|
|
parent.children = [];
|
|
}
|
|
parent.children.push(item);
|
|
}
|
|
});
|
|
|
|
// 排序
|
|
rootItems.sort((a, b) => a.order - b.order);
|
|
rootItems.forEach(item => {
|
|
if (item.children) {
|
|
item.children.sort((a, b) => a.order - b.order);
|
|
}
|
|
});
|
|
|
|
return rootItems;
|
|
}
|
|
|
|
/**
|
|
* 根据用户角色映射到权限系统的角色标识
|
|
* @param userRole 前端用户角色 ('common' | 'admin' | 'deptLeader' | 'groupLeader')
|
|
* @returns 数据库中的角色标识
|
|
*/
|
|
export function mapUserRoleToRoleKey(userRole: string): string {
|
|
const roleMapping: Record<string, string> = {
|
|
'common': 'common',
|
|
'admin': 'admin',
|
|
'deptLeader': 'deptLeader',
|
|
'groupLeader': 'groupLeader'
|
|
};
|
|
|
|
return roleMapping[userRole];
|
|
}
|