Merge remote-tracking branch 'origin/shiy-login' into Wren
This commit is contained in:
@@ -0,0 +1,613 @@
|
||||
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): 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}`
|
||||
}
|
||||
});
|
||||
|
||||
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}`
|
||||
}
|
||||
});
|
||||
|
||||
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"
|
||||
});
|
||||
|
||||
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' | 'developer')
|
||||
* @returns 数据库中的角色标识
|
||||
*/
|
||||
export function mapUserRoleToRoleKey(userRole: string): string {
|
||||
const roleMapping: Record<string, string> = {
|
||||
'common': 'common',
|
||||
'developer': 'admin',
|
||||
'deptLeader': 'deptLeader',
|
||||
'groupLeader': 'groupLeader'
|
||||
};
|
||||
|
||||
return roleMapping[userRole] || 'common';
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
import { postgrestPut, postgrestPost } from '../postgrest-client';
|
||||
// import dayjs from 'dayjs';
|
||||
// import { getDocumentTypes } from '../document-types/document-types';
|
||||
// import type { DocumentTypeUI } from '../document-types/document-types';
|
||||
// import weekday from 'dayjs/plugin/weekday';
|
||||
// import updateLocale from 'dayjs/plugin/updateLocale';
|
||||
import { formatDate } from '../../utils';
|
||||
|
||||
// 文档数据库表接口
|
||||
@@ -107,58 +102,6 @@ export interface DocumentSearchParams {
|
||||
pageSize?: number; // 每页条数
|
||||
}
|
||||
|
||||
// 添加评查结果和评查点类型定义
|
||||
// 评查结果类型
|
||||
// interface EvaluationResult {
|
||||
// id: string | number;
|
||||
// document_id: string | number;
|
||||
// evaluation_point_id: string | number;
|
||||
// evaluated_results?: {
|
||||
// result?: boolean;
|
||||
// message?: string;
|
||||
// data?: string;
|
||||
// [key: string]: unknown;
|
||||
// };
|
||||
// [key: string]: unknown;
|
||||
// }
|
||||
|
||||
// 评查点类型
|
||||
// interface EvaluationPoint {
|
||||
// id: string | number;
|
||||
// post_action?: string;
|
||||
// score?: number;
|
||||
// [key: string]: unknown;
|
||||
// }
|
||||
|
||||
// 文档评查状态结果
|
||||
// interface DocumentReviewResult {
|
||||
// status: number;
|
||||
// issueCount: number;
|
||||
// passCount: number;
|
||||
// warningCount: number;
|
||||
// failCount: number;
|
||||
// manualCount: number;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 从不同格式的 API 响应中提取数据
|
||||
// * @param responseData API 响应数据
|
||||
// * @returns 提取后的数据或 null
|
||||
// */
|
||||
// function extractApiData<T>(responseData: unknown): T | null {
|
||||
// if (!responseData) return null;
|
||||
|
||||
// // 格式1: { code: number, msg: string, data: T }
|
||||
// if (typeof responseData === 'object' && responseData !== null &&
|
||||
// 'code' in responseData &&
|
||||
// 'data' in responseData &&
|
||||
// (responseData as { data: unknown }).data) {
|
||||
// return (responseData as { data: T }).data;
|
||||
// }
|
||||
|
||||
// // 格式2: 直接是数据对象
|
||||
// return responseData as T;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 将评查状态代码映射到UI状态
|
||||
@@ -202,14 +145,21 @@ export function getFileExtension(fileName: string): string {
|
||||
/**
|
||||
* 获取评查文件列表
|
||||
* @param searchParams 搜索参数
|
||||
* @param documentIds 文档ID数组(可选)
|
||||
* @param userId 用户ID
|
||||
* @returns 评查文件列表和总数
|
||||
*/
|
||||
export async function getReviewFiles(searchParams: DocumentSearchParams = {}, documentIds: number[] | null = null): Promise<{
|
||||
export async function getReviewFiles(searchParams: DocumentSearchParams = {}, documentIds: number[] | null = null, userId?: string): Promise<{
|
||||
data?: { files: ReviewFileUI[], total: number };
|
||||
error?: string;
|
||||
status?: number;
|
||||
}> {
|
||||
try {
|
||||
// 确保userId必须存在,如果不存在则抛出错误
|
||||
if (!userId) {
|
||||
return { error: '用户身份验证失败,无法获取评查文件列表', status: 401 };
|
||||
}
|
||||
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 10,
|
||||
@@ -242,6 +192,7 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}, do
|
||||
p_date_from: dateFrom || null,
|
||||
p_date_to: dateTo || null,
|
||||
p_document_ids: documentIds || null,
|
||||
p_user_id: parseInt(userId, 10), // 强制要求传递用户ID
|
||||
};
|
||||
|
||||
const listParams = {
|
||||
@@ -364,9 +315,10 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}, do
|
||||
* 更新文件的审核状态
|
||||
* @param id 文件ID
|
||||
* @param auditStatus 审核状态
|
||||
* @param userId 用户ID
|
||||
* @returns 更新结果
|
||||
*/
|
||||
export async function updateDocumentAuditStatus(id: string, auditStatus: number): Promise<{
|
||||
export async function updateDocumentAuditStatus(id: string, auditStatus: number, userId: string): Promise<{
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -376,10 +328,17 @@ export async function updateDocumentAuditStatus(id: string, auditStatus: number)
|
||||
return { error: '文件ID不能为空', status: 400 };
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return { error: '用户身份验证失败', status: 401 };
|
||||
}
|
||||
|
||||
const response = await postgrestPut<Document, Partial<Document>>(
|
||||
'documents',
|
||||
{ audit_status: auditStatus },
|
||||
{ id: parseInt(id) }
|
||||
{
|
||||
id: parseInt(id),
|
||||
user_id: parseInt(userId) // 确保只能更新自己的文档
|
||||
}
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface DocumentSearchParams {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
reviewType?: string;
|
||||
userId?: string; // 添加用户ID筛选
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +215,8 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
|
||||
fileStatus,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
reviewType
|
||||
reviewType,
|
||||
userId
|
||||
} = searchParams;
|
||||
|
||||
let documentTypes: number[] | undefined;
|
||||
@@ -228,6 +230,11 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
|
||||
}
|
||||
}
|
||||
|
||||
// 确保userId必须存在,如果不存在则抛出错误
|
||||
if (!userId) {
|
||||
return { error: '用户身份验证失败,无法获取文档列表', status: 401 };
|
||||
}
|
||||
|
||||
const rpcParams = {
|
||||
search_name: name,
|
||||
search_document_number: documentNumber,
|
||||
@@ -236,6 +243,7 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
|
||||
search_file_status: fileStatus,
|
||||
search_date_from: dateFrom,
|
||||
search_date_to: dateTo,
|
||||
search_user_id: parseInt(userId, 10), // 强制要求传递用户ID
|
||||
};
|
||||
|
||||
// 并行执行获取数据和获取总数的请求
|
||||
@@ -296,9 +304,10 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
|
||||
/**
|
||||
* 删除文档
|
||||
* @param id 文档ID
|
||||
* @param userId 用户ID
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export async function deleteDocument(id: string): Promise<{
|
||||
export async function deleteDocument(id: string, userId: string): Promise<{
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -308,11 +317,16 @@ export async function deleteDocument(id: string): Promise<{
|
||||
return { error: '文档ID不能为空', status: 400 };
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return { error: '用户身份验证失败', status: 401 };
|
||||
}
|
||||
|
||||
const response = await postgrestDelete(
|
||||
'documents',
|
||||
{
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
'id': `eq.${id}`,
|
||||
'user_id': `eq.${userId}` // 确保只能删除自己的文档
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -336,7 +350,7 @@ export async function deleteDocument(id: string): Promise<{
|
||||
* @param id 文档ID
|
||||
* @returns 文档详情
|
||||
*/
|
||||
export async function getDocument(id: string): Promise<{
|
||||
export async function getDocument(id: string, userId: string): Promise<{
|
||||
data?: DocumentUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -346,11 +360,16 @@ export async function getDocument(id: string): Promise<{
|
||||
return { error: '文档ID不能为空', status: 400 };
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return { error: '用户身份验证失败', status: 401 };
|
||||
}
|
||||
|
||||
const response = await postgrestGet<Document[]>(
|
||||
'documents',
|
||||
{
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
'id': `eq.${id}`,
|
||||
'user_id': `eq.${userId}`
|
||||
},
|
||||
limit: 1
|
||||
}
|
||||
@@ -419,7 +438,7 @@ export async function getFileDownloadUrl(filePath: string): Promise<{
|
||||
* @param document 部分文档数据
|
||||
* @returns 更新结果
|
||||
*/
|
||||
export async function updateDocument(id: string, document: Partial<DocumentUI> & { remark?: string }): Promise<{
|
||||
export async function updateDocument(id: string, document: Partial<DocumentUI> & { remark?: string }, userId: string): Promise<{
|
||||
data?: DocumentUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -429,6 +448,10 @@ export async function updateDocument(id: string, document: Partial<DocumentUI> &
|
||||
return { error: '文档ID不能为空', status: 400 };
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return { error: '用户身份验证失败', status: 401 };
|
||||
}
|
||||
|
||||
// 准备API数据 - 将UI数据转换为API格式
|
||||
const apiDocument: Partial<Document> = {};
|
||||
|
||||
@@ -457,7 +480,10 @@ export async function updateDocument(id: string, document: Partial<DocumentUI> &
|
||||
const response = await postgrestPut<Document, Partial<Document>>(
|
||||
'documents',
|
||||
apiDocument,
|
||||
{ id: parseInt(id) }
|
||||
{
|
||||
id: parseInt(id),
|
||||
user_id: parseInt(userId) // 确保只能更新自己的文档
|
||||
}
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -466,9 +492,9 @@ export async function updateDocument(id: string, document: Partial<DocumentUI> &
|
||||
}
|
||||
|
||||
// 获取更新后的完整文档数据
|
||||
const updatedResponse = await getDocument(id);
|
||||
const updatedResponse = await getDocument(id, userId);
|
||||
|
||||
return updatedResponse;
|
||||
return updatedResponse;
|
||||
} catch (error) {
|
||||
console.error('更新文档信息失败:', error);
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* - OAuth2.0 Token 自动刷新
|
||||
* - 用户登录状态检查
|
||||
* - 会话创建和销毁
|
||||
* - 用户信息保存到数据库
|
||||
*
|
||||
* 技术栈:
|
||||
* - Remix Session Storage (Cookie-based)
|
||||
@@ -18,6 +19,7 @@
|
||||
|
||||
import { createCookieSessionStorage } from "@remix-run/node";
|
||||
import { tokenManager } from "./token-manager.server";
|
||||
import { postgrestGet, postgrestPost, postgrestPut } from "../postgrest-client";
|
||||
|
||||
/**
|
||||
* 用户角色类型定义
|
||||
@@ -28,6 +30,42 @@ import { tokenManager } from "./token-manager.server";
|
||||
*/
|
||||
export type UserRole = 'common' | 'developer';
|
||||
|
||||
/**
|
||||
* 用户信息接口,对应 sso_users 表结构
|
||||
*/
|
||||
export interface UserInfo {
|
||||
sub: string; // IDaaS用户唯一标识
|
||||
username?: string; // 显示用户名称/工号
|
||||
nick_name?: string; // 用户真实姓名
|
||||
nickname?: string; // OAuth返回的昵称字段
|
||||
name?: string; // 用户姓名(通常映射到 nick_name)
|
||||
phone_number?: string; // 手机号
|
||||
email?: string; // 邮箱地址
|
||||
ou_id?: string; // 所属组织单位ID
|
||||
ou_name?: string; // 所属部门名称
|
||||
status?: number; // 账户状态: 0=正常, 1=禁用
|
||||
is_leader?: boolean; // 是否为部门负责人
|
||||
}
|
||||
|
||||
/**
|
||||
* sso_users 表记录接口
|
||||
*/
|
||||
export interface SsoUser {
|
||||
id?: string;
|
||||
sub: string;
|
||||
username: string;
|
||||
nick_name: string;
|
||||
phone_number?: string;
|
||||
email?: string;
|
||||
ou_id: string;
|
||||
ou_name: string;
|
||||
status: number;
|
||||
is_leader: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
deleted_at?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话存储配置
|
||||
*
|
||||
@@ -230,4 +268,248 @@ export async function logout(request: Request) {
|
||||
"Set-Cookie": await sessionStorage.destroySession(session), // 清除会话 Cookie
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户信息到数据库
|
||||
*
|
||||
* 此函数实现以下逻辑:
|
||||
* 1. 根据 userInfo.sub 查询 sso_users 表中是否已存在该用户
|
||||
* 2. 如果存在,则更新用户信息
|
||||
* 3. 如果不存在,则插入新的用户记录
|
||||
*
|
||||
* @param userInfo - 从 IDaaS 获取的用户信息
|
||||
* @returns Promise<{success: boolean, data?: SsoUser, error?: string}>
|
||||
*/
|
||||
export async function saveUserInfo(userInfo: UserInfo): Promise<{success: boolean, data?: SsoUser, error?: string}> {
|
||||
try {
|
||||
console.log("开始保存用户信息", userInfo);
|
||||
|
||||
// 验证必要字段
|
||||
if (!userInfo.sub) {
|
||||
return { success: false, error: "用户唯一标识 sub 不能为空" };
|
||||
}
|
||||
|
||||
// 1. 根据 sub 查询是否已存在该用户
|
||||
const existingUserResult = await postgrestGet<SsoUser[]>("sso_users", {
|
||||
filter: {
|
||||
"sub": `eq.${userInfo.sub}`,
|
||||
"deleted_at": "is.null" // 只查询未删除的记录
|
||||
}
|
||||
});
|
||||
|
||||
if (existingUserResult.error) {
|
||||
console.error("查询用户失败:", existingUserResult.error);
|
||||
return { success: false, error: `查询用户失败: ${existingUserResult.error}` };
|
||||
}
|
||||
|
||||
const existingUsers = existingUserResult.data || [];
|
||||
const existingUser = existingUsers.length > 0 ? existingUsers[0] : null;
|
||||
|
||||
// 准备要保存的用户数据
|
||||
// 注意:OAuth返回的字段是nickname,而不是nick_name
|
||||
const userData: Partial<SsoUser> = {
|
||||
sub: userInfo.sub,
|
||||
username: userInfo.username || userInfo.name || userInfo.sub,
|
||||
nick_name: userInfo.nick_name || userInfo.nickname || userInfo.name || "未知用户",
|
||||
phone_number: userInfo.phone_number || undefined,
|
||||
email: userInfo.email || undefined,
|
||||
ou_id: userInfo.ou_id || "default",
|
||||
ou_name: userInfo.ou_name || "未知部门",
|
||||
status: userInfo.status !== undefined ? userInfo.status : 0,
|
||||
is_leader: userInfo.is_leader || false,
|
||||
};
|
||||
|
||||
if (existingUser) {
|
||||
// 2. 用户已存在,执行更新操作
|
||||
console.log("用户已存在,执行更新操作", existingUser.id);
|
||||
|
||||
const updateResult = await postgrestPut<SsoUser[], Partial<SsoUser>>(
|
||||
"sso_users",
|
||||
userData,
|
||||
{ id: existingUser.id! }
|
||||
);
|
||||
|
||||
if (updateResult.error) {
|
||||
console.error("更新用户失败:", updateResult.error);
|
||||
return { success: false, error: `更新用户失败: ${updateResult.error}` };
|
||||
}
|
||||
|
||||
console.log("用户信息更新成功");
|
||||
return {
|
||||
success: true,
|
||||
data: Array.isArray(updateResult.data) ? updateResult.data[0] : updateResult.data as unknown as SsoUser
|
||||
};
|
||||
} else {
|
||||
// 3. 用户不存在,执行插入操作 同时需要给这个用户默认添加一个角色,角色为common
|
||||
console.log("用户不存在,执行插入操作");
|
||||
|
||||
const insertResult = await postgrestPost<SsoUser[], SsoUser>("sso_users", userData as SsoUser);
|
||||
|
||||
if (insertResult.error) {
|
||||
console.error("插入用户失败:", insertResult.error);
|
||||
return { success: false, error: `插入用户失败: ${insertResult.error}` };
|
||||
}
|
||||
|
||||
console.log("用户信息插入成功");
|
||||
|
||||
// 4. 给这个用户默认添加一个角色,角色为common
|
||||
const userData_with_id = Array.isArray(insertResult.data) ? insertResult.data[0] : insertResult.data as unknown as SsoUser;
|
||||
if (userData_with_id?.id) {
|
||||
await addDefaultRole(userData_with_id.id, 2);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: userData_with_id
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("保存用户信息时发生错误:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: `保存用户信息失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为用户添加默认角色
|
||||
*
|
||||
* @param userId - 用户ID
|
||||
* @param roleId - 角色ID,默认为2(common角色)
|
||||
* @returns 添加结果
|
||||
*/
|
||||
export async function addDefaultRole(userId: string, roleId: number = 2) {
|
||||
try {
|
||||
console.log(`为用户 ${userId} 添加默认角色 ${roleId}`);
|
||||
|
||||
// 检查用户是否已经有此角色
|
||||
const existingRoleResult = await postgrestGet<Array<{id: number, user_id: string, role_id: number}>>("user_role", {
|
||||
filter: {
|
||||
user_id: `eq.${userId}`,
|
||||
role_id: `eq.${roleId}`
|
||||
}
|
||||
});
|
||||
|
||||
if (existingRoleResult.error) {
|
||||
console.error("查询用户角色失败:", existingRoleResult.error);
|
||||
return { success: false, error: `查询用户角色失败: ${existingRoleResult.error}` };
|
||||
}
|
||||
|
||||
const existingRoles = existingRoleResult.data || [];
|
||||
if (existingRoles.length > 0) {
|
||||
console.log("用户已经拥有此角色,跳过添加");
|
||||
return { success: true, data: existingRoles[0] };
|
||||
}
|
||||
|
||||
// 添加角色
|
||||
const addRoleResult = await postgrestPost<Array<{id: number, user_id: string, role_id: number}>, {user_id: string, role_id: number}>("user_role", {
|
||||
user_id: userId,
|
||||
role_id: roleId
|
||||
});
|
||||
|
||||
if (addRoleResult.error) {
|
||||
console.error("添加用户角色失败:", addRoleResult.error);
|
||||
return { success: false, error: `添加用户角色失败: ${addRoleResult.error}` };
|
||||
}
|
||||
|
||||
console.log("用户角色添加成功");
|
||||
return {
|
||||
success: true,
|
||||
data: Array.isArray(addRoleResult.data) ? addRoleResult.data[0] : addRoleResult.data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("添加用户角色时发生错误:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: `添加用户角色失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户sub获取用户信息
|
||||
*
|
||||
* @param sub - 用户的唯一标识
|
||||
* @returns 用户信息
|
||||
*/
|
||||
export async function getUserBySub(sub: string) {
|
||||
try {
|
||||
console.log(`查询用户: ${sub}`);
|
||||
|
||||
const userResult = await postgrestGet<SsoUser[]>("sso_users", {
|
||||
filter: {
|
||||
sub: `eq.${sub}`
|
||||
}
|
||||
});
|
||||
|
||||
if (userResult.error) {
|
||||
console.error("查询用户失败:", userResult.error);
|
||||
return { success: false, error: `查询用户失败: ${userResult.error}` };
|
||||
}
|
||||
|
||||
const users = userResult.data || [];
|
||||
const user = users.length > 0 ? users[0] : null;
|
||||
|
||||
if (!user) {
|
||||
return { success: false, error: "用户不存在" };
|
||||
}
|
||||
|
||||
return { success: true, data: user };
|
||||
} catch (error) {
|
||||
console.error("查询用户时发生错误:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: `查询用户失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户登录会话(支持用户信息)
|
||||
*
|
||||
* @param isAuthenticated - 是否已认证
|
||||
* @param userRole - 用户角色
|
||||
* @param redirectTo - 重定向URL
|
||||
* @param userInfo - 可选的用户信息
|
||||
* @returns HTTP重定向响应
|
||||
*/
|
||||
export async function createUserSessionWithInfo(
|
||||
isAuthenticated: boolean,
|
||||
userRole: UserRole,
|
||||
redirectTo: string,
|
||||
userInfo?: Partial<SsoUser>
|
||||
) {
|
||||
const session = await sessionStorage.getSession();
|
||||
session.set("isAuthenticated", isAuthenticated);
|
||||
session.set("userRole", userRole);
|
||||
|
||||
// 如果提供了用户信息,也保存到session中
|
||||
if (userInfo) {
|
||||
session.set("userInfo", {
|
||||
sub: userInfo.sub,
|
||||
user_id: userInfo.id,
|
||||
username: userInfo.username,
|
||||
nick_name: userInfo.nick_name,
|
||||
email: userInfo.email,
|
||||
ou_name: userInfo.ou_name,
|
||||
is_leader: userInfo.is_leader,
|
||||
user_role: userRole
|
||||
});
|
||||
}
|
||||
|
||||
const cookie = await sessionStorage.commitSession(session);
|
||||
console.log("创建会话 - 设置Cookie:", !!cookie);
|
||||
console.log("创建会话 - 用户角色:", userRole);
|
||||
console.log("创建会话 - 用户信息:", userInfo?.username || "无");
|
||||
console.log("创建会话 - 重定向到:", redirectTo);
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: redirectTo,
|
||||
"Set-Cookie": cookie,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user