给所有请求都加上jwt,隐藏生成jwt的secret(放到.env中),隐藏app-secret(放在pm2运行配置文件中,后续直接读取环境配置即可)
This commit is contained in:
@@ -462,7 +462,7 @@ const FALLBACK_MENU_DATA: Record<string, MenuItem[]> = {
|
||||
* @param roleKey 角色标识 (如: 'admin', 'common', 'deptLeader', 'groupLeader')
|
||||
* @returns 用户可访问的路由列表
|
||||
*/
|
||||
export async function getUserRoutesByRole(roleKey: string): Promise<{ success: boolean; data?: MenuItem[]; error?: string; shouldRedirectToHome?: boolean }> {
|
||||
export async function getUserRoutesByRole(roleKey: string, jwt?: string): Promise<{ success: boolean; data?: MenuItem[]; error?: string; shouldRedirectToHome?: boolean }> {
|
||||
try {
|
||||
console.log(`获取角色 ${roleKey} 的路由权限`);
|
||||
|
||||
@@ -470,7 +470,8 @@ export async function getUserRoutesByRole(roleKey: string): Promise<{ success: b
|
||||
const roleResult = await postgrestGet<Array<{id: number}>>("roles", {
|
||||
filter: {
|
||||
"role_key": `eq.${roleKey}`
|
||||
}
|
||||
},
|
||||
token: jwt
|
||||
});
|
||||
|
||||
if (roleResult.error || !roleResult.data || roleResult.data.length === 0) {
|
||||
@@ -485,7 +486,8 @@ export async function getUserRoutesByRole(roleKey: string): Promise<{ success: b
|
||||
const roleRoutesResult = await postgrestGet<Array<{route_id: number}>>("role_route", {
|
||||
filter: {
|
||||
"role_id": `eq.${roleId}`
|
||||
}
|
||||
},
|
||||
token: jwt
|
||||
});
|
||||
|
||||
if (roleRoutesResult.error) {
|
||||
@@ -509,7 +511,8 @@ export async function getUserRoutesByRole(roleKey: string): Promise<{ success: b
|
||||
"id": `in.(${routeIds.join(',')})`,
|
||||
"is_menu": "eq.1"
|
||||
},
|
||||
order: "parent_id,meta->>order"
|
||||
order: "parent_id,meta->>order",
|
||||
token: jwt
|
||||
});
|
||||
|
||||
if (routesResult.error) {
|
||||
|
||||
+10
-7
@@ -386,15 +386,18 @@ export async function del<T>(endpoint: string, params?: QueryParams): Promise<Ap
|
||||
|
||||
// 下载文件的方法
|
||||
export async function downloadFile(path: string): Promise<Blob> {
|
||||
const downloadUrl = `${DOCUMENT_URL}${path}`;
|
||||
|
||||
// 使用 PDF 代理路由获取文件,自动添加 JWT 认证
|
||||
const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(path)}`;
|
||||
|
||||
try {
|
||||
// console.log(`📦 axios-client.ts->下载文件: ${downloadUrl}`);
|
||||
const response = await axios.get(downloadUrl, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
return response.data;
|
||||
const response = await fetch(downloadUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.blob();
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
throw error;
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface TemplateSearchParams {
|
||||
pageSize?: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
token?: string; // JWT token
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
@@ -70,12 +71,14 @@ export interface SearchResult {
|
||||
|
||||
/**
|
||||
* 获取所有合同分类
|
||||
* @param jwt JWT token (可选)
|
||||
*/
|
||||
export async function getContractCategories() {
|
||||
export async function getContractCategories(jwt?: string) {
|
||||
try {
|
||||
const params: PostgrestParams = {
|
||||
select: '*',
|
||||
order: 'sort_order.asc,name.asc'
|
||||
order: 'sort_order.asc,name.asc',
|
||||
token: jwt
|
||||
};
|
||||
|
||||
const response = await postgrestGet<ContractCategory[]>('contract_categories', params);
|
||||
@@ -98,13 +101,15 @@ export async function getContractCategories() {
|
||||
|
||||
/**
|
||||
* 获取所有合同分类及其模板数量(使用聚合查询)
|
||||
* @param jwt JWT token (可选)
|
||||
*/
|
||||
export async function getContractCategoriesWithCount() {
|
||||
export async function getContractCategoriesWithCount(jwt?: string) {
|
||||
try {
|
||||
// 获取所有分类
|
||||
const categoriesResponse = await postgrestGet<ContractCategory[]>('contract_categories', {
|
||||
select: '*',
|
||||
order: 'sort_order.asc,name.asc'
|
||||
order: 'sort_order.asc,name.asc',
|
||||
token: jwt
|
||||
});
|
||||
|
||||
if (categoriesResponse.error) {
|
||||
@@ -120,7 +125,8 @@ export async function getContractCategoriesWithCount() {
|
||||
// 简化方案:获取该分类下的所有模板ID,然后计算数量
|
||||
const countResponse = await postgrestGet<{ id: number }[]>('contract_templates', {
|
||||
select: 'id',
|
||||
filter: { 'category_id': `eq.${category.id}` }
|
||||
filter: { 'category_id': `eq.${category.id}` },
|
||||
token: jwt
|
||||
});
|
||||
|
||||
let templateCount = 0;
|
||||
@@ -172,7 +178,8 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
|
||||
page = 1,
|
||||
pageSize = 6,
|
||||
sortBy = 'updated_at',
|
||||
sortOrder = 'desc'
|
||||
sortOrder = 'desc',
|
||||
token
|
||||
} = searchParams;
|
||||
|
||||
// 构建查询参数
|
||||
@@ -180,7 +187,8 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
|
||||
select: 'id,template_code,title,category_id,description,file_path,file_format,is_featured,created_at,updated_at,pdf_file_path,category:contract_categories(id,name,icon,description)',
|
||||
limit: pageSize,
|
||||
offset: (page - 1) * pageSize,
|
||||
order: `${sortBy}.${sortOrder}`
|
||||
order: `${sortBy}.${sortOrder}`,
|
||||
token
|
||||
};
|
||||
|
||||
// 构建过滤条件
|
||||
@@ -207,7 +215,8 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
|
||||
try {
|
||||
const categoryResponse = await postgrestGet<ContractCategory[]>('contract_categories', {
|
||||
select: 'id',
|
||||
filter: { 'name': `ilike.*${cleanKeyword}*` }
|
||||
filter: { 'name': `ilike.*${cleanKeyword}*` },
|
||||
token
|
||||
});
|
||||
|
||||
if (categoryResponse.data) {
|
||||
@@ -237,7 +246,8 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
|
||||
if (category && !category_id) {
|
||||
const categoryResponse = await postgrestGet<ContractCategory[]>('contract_categories', {
|
||||
select: 'id',
|
||||
filter: { 'name': `eq.${category}` }
|
||||
filter: { 'name': `eq.${category}` },
|
||||
token
|
||||
});
|
||||
|
||||
if (categoryResponse.data) {
|
||||
@@ -265,7 +275,8 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
|
||||
const countParams: PostgrestParams = {
|
||||
select: 'id',
|
||||
filter: params.filter,
|
||||
or: params.or
|
||||
or: params.or,
|
||||
token
|
||||
};
|
||||
|
||||
const countResponse = await postgrestGet<{ id: number }[]>('contract_templates', countParams);
|
||||
@@ -295,12 +306,15 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
|
||||
|
||||
/**
|
||||
* 根据ID获取单个合同模板
|
||||
* @param id 模板ID
|
||||
* @param jwt JWT token (可选)
|
||||
*/
|
||||
export async function getContractTemplate(id: string | number) {
|
||||
export async function getContractTemplate(id: string | number, jwt?: string) {
|
||||
try {
|
||||
const params: PostgrestParams = {
|
||||
select: 'id,template_code,title,category_id,description,file_path,file_format,is_featured,created_at,updated_at,pdf_file_path,category:contract_categories(id,name,icon,description)',
|
||||
filter: { 'id': `eq.${id}` }
|
||||
filter: { 'id': `eq.${id}` },
|
||||
token: jwt
|
||||
};
|
||||
|
||||
const response = await postgrestGet<ContractTemplate[]>('contract_templates', params);
|
||||
@@ -327,14 +341,17 @@ export async function getContractTemplate(id: string | number) {
|
||||
|
||||
/**
|
||||
* 获取推荐模板
|
||||
* @param limit 数量限制
|
||||
* @param jwt JWT token (可选)
|
||||
*/
|
||||
export async function getFeaturedTemplates(limit: number = 6) {
|
||||
export async function getFeaturedTemplates(limit: number = 6, jwt?: string) {
|
||||
try {
|
||||
const params: PostgrestParams = {
|
||||
select: 'id,template_code,title,category_id,description,file_path,file_format,is_featured,created_at,updated_at,pdf_file_path,category:contract_categories(id,name,icon,description)',
|
||||
filter: { 'is_featured': 'eq.true' },
|
||||
order: 'updated_at.desc',
|
||||
limit
|
||||
limit,
|
||||
token: jwt
|
||||
};
|
||||
|
||||
const response = await postgrestGet<ContractTemplate[]>('contract_templates', params);
|
||||
@@ -357,6 +374,8 @@ export async function getFeaturedTemplates(limit: number = 6) {
|
||||
|
||||
/**
|
||||
* 搜索合同模板(智能搜索)
|
||||
* @param query 搜索关键词
|
||||
* @param filters 过滤条件
|
||||
*/
|
||||
export async function searchContractTemplates(
|
||||
query: string,
|
||||
|
||||
@@ -87,13 +87,14 @@ async function safeGetJWT(jwtToken?: string): Promise<string> {
|
||||
* @param userId 用户ID
|
||||
* @returns 是否是发起人
|
||||
*/
|
||||
export async function findIsProposer(taskId: string | number, userId: number | undefined): Promise<boolean> {
|
||||
export async function findIsProposer(taskId: string | number, userId: number | undefined, frontendJWT?: string): Promise<boolean> {
|
||||
// 通过postgrest的get请求去cross_examination_tasks表中进行查找assignee_id是否等于userId
|
||||
const response = await postgrestGet(`cross_examination_tasks`, {
|
||||
select: 'assigner_id',
|
||||
filter: {
|
||||
id: `eq.${taskId}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
});
|
||||
if (response.error) {
|
||||
console.error('获取任务数据失败:', response.error);
|
||||
@@ -366,7 +367,8 @@ export async function performOpinionAction(
|
||||
* @returns 完成评查结果
|
||||
*/
|
||||
export async function confirmReviewResults(
|
||||
documentId: string | number
|
||||
documentId: string | number,
|
||||
frontendJWT?: string
|
||||
): Promise<{data?: unknown, error?: string, status?: number}> {
|
||||
try {
|
||||
// 通过postgrest的post请求去documents表中进行查找id等于documentId的数据,更新documents表的audit_status为1
|
||||
@@ -374,7 +376,7 @@ export async function confirmReviewResults(
|
||||
audit_status: 1
|
||||
}, {
|
||||
id: documentId
|
||||
});
|
||||
}, frontendJWT);
|
||||
if(response.error) {
|
||||
return {
|
||||
error: response.error,
|
||||
|
||||
@@ -482,7 +482,7 @@ export async function getTaskDocuments(taskId: number, page: number = 1, pageSiz
|
||||
* @param auditStatus 审核状态
|
||||
* @returns 更新结果
|
||||
*/
|
||||
export async function updateDocumentAuditStatus(id: string, auditStatus: number): Promise<{
|
||||
export async function updateDocumentAuditStatus(id: string, auditStatus: number, frontendJWT?: string): Promise<{
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -491,13 +491,14 @@ export async function updateDocumentAuditStatus(id: string, auditStatus: number)
|
||||
if (!id) {
|
||||
return { error: '文件ID不能为空', status: 400 };
|
||||
}
|
||||
|
||||
|
||||
const response = await postgrestPut<TaskDocument, Partial<TaskDocument>>(
|
||||
'documents',
|
||||
{ audit_status: auditStatus },
|
||||
{
|
||||
{
|
||||
id: parseInt(id)
|
||||
}
|
||||
},
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// app/api/db-client.server.ts
|
||||
import { getUserSession } from '~/api/login/auth.server';
|
||||
import { runWithContext } from './postgrest-client';
|
||||
|
||||
/**
|
||||
* 在认证上下文中运行
|
||||
*
|
||||
* 所有在此上下文中调用的 postgrest 函数(postgrestGet/Post/Put/Delete)
|
||||
* 都会自动获取并添加 JWT Token 到 Authorization 头部
|
||||
*
|
||||
* @param request Remix Request 对象
|
||||
* @param fn 要在认证上下文中执行的函数
|
||||
* @returns 函数执行结果
|
||||
* @throws 如果用户未登录则抛出错误
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export async function loader({ request }: LoaderFunctionArgs) {
|
||||
* return await runInAuthContext(request, async () => {
|
||||
* // 所有 postgrest 调用自动带 JWT
|
||||
* const users = await postgrestGet('users', { limit: 10 });
|
||||
* const docs = await postgrestGet('documents', { filter: { status: 'eq.0' } });
|
||||
*
|
||||
* return json({ users: users.data, docs: docs.data });
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export async function runInAuthContext<T>(
|
||||
request: Request,
|
||||
fn: () => T | Promise<T>
|
||||
): Promise<T> {
|
||||
const { frontendJWT, isAuthenticated } = await getUserSession(request);
|
||||
|
||||
if (!isAuthenticated || !frontendJWT) {
|
||||
throw new Error('用户未登录,无法执行需要认证的操作');
|
||||
}
|
||||
|
||||
// 在上下文中设置 JWT,所有 postgrest 调用都会自动使用
|
||||
return runWithContext({ jwt: frontendJWT }, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在公开上下文中运行(不需要认证)
|
||||
*
|
||||
* 用于公开数据访问,不会添加 JWT Token
|
||||
*
|
||||
* @param fn 要执行的函数
|
||||
* @returns 函数执行结果
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export async function loader() {
|
||||
* return runInPublicContext(async () => {
|
||||
* const articles = await postgrestGet('public_articles', {
|
||||
* filter: { published: 'eq.true' }
|
||||
* });
|
||||
* return json({ articles: articles.data });
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function runInPublicContext<T>(
|
||||
fn: () => T | Promise<T>
|
||||
): T | Promise<T> {
|
||||
// 在空上下文中运行,不设置 JWT
|
||||
return runWithContext({}, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在可选认证上下文中运行
|
||||
*
|
||||
* 如果用户已登录,会自动添加 JWT;
|
||||
* 如果用户未登录,则不添加 JWT(不会抛出错误)
|
||||
*
|
||||
* @param request Remix Request 对象
|
||||
* @param fn 要执行的函数
|
||||
* @returns 函数执行结果
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export async function loader({ request }: LoaderFunctionArgs) {
|
||||
* return await runInOptionalAuthContext(request, async () => {
|
||||
* // 如果用户登录,会带 JWT(可能看到更多内容)
|
||||
* // 如果用户未登录,不带 JWT(看到基础内容)
|
||||
* const content = await postgrestGet('content', { limit: 20 });
|
||||
* return json({ content: content.data });
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export async function runInOptionalAuthContext<T>(
|
||||
request: Request,
|
||||
fn: () => T | Promise<T>
|
||||
): Promise<T> {
|
||||
try {
|
||||
const { frontendJWT, isAuthenticated } = await getUserSession(request);
|
||||
|
||||
if (isAuthenticated && frontendJWT) {
|
||||
// 用户已登录,使用 JWT
|
||||
return runWithContext({ jwt: frontendJWT }, fn);
|
||||
}
|
||||
} catch (error) {
|
||||
// 获取会话失败,继续以公开方式运行
|
||||
console.warn('获取用户会话失败,以公开方式运行:', error);
|
||||
}
|
||||
|
||||
// 用户未登录或获取会话失败,以公开方式运行
|
||||
return runWithContext({}, fn);
|
||||
}
|
||||
|
||||
@@ -90,18 +90,20 @@ function extractApiData<T>(responseData: unknown): T | null {
|
||||
|
||||
/**
|
||||
* 获取所有评查点分组
|
||||
* @param token JWT token (可选)
|
||||
* @returns 评查点分组列表
|
||||
*/
|
||||
export async function getAllEvaluationPointGroups(): Promise<{
|
||||
export async function getAllEvaluationPointGroups(token?: string): Promise<{
|
||||
data?: DocumentTypeGroup[];
|
||||
error?: string;
|
||||
status?: number;
|
||||
}> {
|
||||
try {
|
||||
const params: PostgrestParams = {
|
||||
select: 'id, name'
|
||||
select: 'id, name',
|
||||
token
|
||||
};
|
||||
|
||||
|
||||
const response = await postgrestGet<Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -137,9 +139,10 @@ export async function getAllEvaluationPointGroups(): Promise<{
|
||||
/**
|
||||
* 根据ID获取评查点分组信息
|
||||
* @param ids 评查点分组ID数组
|
||||
* @param token JWT token (可选)
|
||||
* @returns 评查点分组信息
|
||||
*/
|
||||
export async function getEvaluationPointGroupsByIds(ids: number[] | number): Promise<{
|
||||
export async function getEvaluationPointGroupsByIds(ids: number[] | number, token?: string): Promise<{
|
||||
data?: DocumentTypeGroup[];
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -149,24 +152,25 @@ export async function getEvaluationPointGroupsByIds(ids: number[] | number): Pro
|
||||
if (!ids) {
|
||||
return { data: [] };
|
||||
}
|
||||
|
||||
|
||||
// 将单个ID转换为数组
|
||||
const idsArray = Array.isArray(ids) ? ids : [ids];
|
||||
if (idsArray.length === 0) {
|
||||
return { data: [] };
|
||||
}
|
||||
|
||||
|
||||
// console.log('获取评查点分组,ID类型:', typeof ids, '转换后的ID数组:', idsArray);
|
||||
|
||||
|
||||
const params: PostgrestParams = {
|
||||
select: 'id, name',
|
||||
filter: {
|
||||
'id': `in.(${idsArray.join(',')})`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
|
||||
// console.log('获取评查点分组,查询参数:', params);
|
||||
|
||||
|
||||
const response = await postgrestGet<Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -204,7 +208,7 @@ export async function getEvaluationPointGroupsByIds(ids: number[] | number): Pro
|
||||
* @param searchParams 搜索参数
|
||||
* @returns 文档类型列表和总数
|
||||
*/
|
||||
export async function getDocumentTypes(searchParams: DocumentTypeSearchParams = {}): Promise<{
|
||||
export async function getDocumentTypes(searchParams: DocumentTypeSearchParams = {}, frontendJWT?: string): Promise<{
|
||||
data?: { types: DocumentTypeUI[], total: number };
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -231,7 +235,8 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
|
||||
},
|
||||
limit: pageSize,
|
||||
offset: (page - 1) * pageSize,
|
||||
filter: {} as Record<string, string>
|
||||
filter: {} as Record<string, string>,
|
||||
token: frontendJWT
|
||||
};
|
||||
|
||||
// 添加筛选条件
|
||||
@@ -299,10 +304,10 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
|
||||
}
|
||||
|
||||
// console.log(`文档类型 ${type.id} 的分组IDs:`, groupIds);
|
||||
|
||||
|
||||
// 获取这些ID对应的分组信息
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds);
|
||||
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds, frontendJWT);
|
||||
|
||||
// 返回包含分组信息的文档类型
|
||||
return {
|
||||
...type,
|
||||
@@ -347,9 +352,10 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
|
||||
/**
|
||||
* 删除文档类型
|
||||
* @param id 文档类型ID
|
||||
* @param frontendJWT JWT token (可选)
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export async function deleteDocumentType(id: string): Promise<{
|
||||
export async function deleteDocumentType(id: string, frontendJWT?: string): Promise<{
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -365,7 +371,8 @@ export async function deleteDocumentType(id: string): Promise<{
|
||||
{
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
}
|
||||
);
|
||||
|
||||
@@ -435,9 +442,10 @@ function convertToUIDocumentType(type: DocumentType & { groups: DocumentTypeGrou
|
||||
/**
|
||||
* 获取文档类型详情
|
||||
* @param id 文档类型ID
|
||||
* @param frontendJWT JWT token (可选)
|
||||
* @returns 文档类型详情
|
||||
*/
|
||||
export async function getDocumentType(id: string): Promise<{
|
||||
export async function getDocumentType(id: string, frontendJWT?: string): Promise<{
|
||||
data?: DocumentTypeUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -446,7 +454,7 @@ export async function getDocumentType(id: string): Promise<{
|
||||
if (!id) {
|
||||
return { error: '文档类型ID不能为空', status: 400 };
|
||||
}
|
||||
|
||||
|
||||
const params: PostgrestParams = {
|
||||
select: `
|
||||
id,
|
||||
@@ -460,9 +468,10 @@ export async function getDocumentType(id: string): Promise<{
|
||||
`,
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
|
||||
|
||||
const response = await postgrestGet<DocumentType[]>('document_types', params);
|
||||
|
||||
if (response.error) {
|
||||
@@ -499,9 +508,9 @@ export async function getDocumentType(id: string): Promise<{
|
||||
}
|
||||
|
||||
// console.log(`文档类型 ${id} 的分组IDs:`, groupIds);
|
||||
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds);
|
||||
|
||||
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds, frontendJWT);
|
||||
|
||||
if (groupsResponse.error) {
|
||||
return { error: groupsResponse.error, status: 500 };
|
||||
}
|
||||
@@ -527,7 +536,7 @@ export async function getDocumentType(id: string): Promise<{
|
||||
* @param documentType 文档类型数据
|
||||
* @returns 创建结果
|
||||
*/
|
||||
export async function createDocumentType(documentType: DocumentTypeCreateDTO): Promise<{
|
||||
export async function createDocumentType(documentType: DocumentTypeCreateDTO, frontendJWT?: string): Promise<{
|
||||
data?: DocumentTypeUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -610,7 +619,8 @@ export async function createDocumentType(documentType: DocumentTypeCreateDTO): P
|
||||
// 发送创建请求
|
||||
const response = await postgrestPost<DocumentType, typeof apiDocumentType>(
|
||||
'document_types',
|
||||
apiDocumentType
|
||||
apiDocumentType,
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -628,14 +638,14 @@ export async function createDocumentType(documentType: DocumentTypeCreateDTO): P
|
||||
}
|
||||
|
||||
// 获取关联分组信息
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds);
|
||||
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds, frontendJWT);
|
||||
|
||||
// 添加分组信息并转换为UI类型
|
||||
const typeWithGroups = {
|
||||
...newDocumentType,
|
||||
groups: groupsResponse.data || []
|
||||
};
|
||||
|
||||
|
||||
return { data: convertToUIDocumentType(typeWithGroups) };
|
||||
} catch (error) {
|
||||
console.error('创建文档类型失败:', error);
|
||||
@@ -652,7 +662,7 @@ export async function createDocumentType(documentType: DocumentTypeCreateDTO): P
|
||||
* @param documentType 文档类型数据
|
||||
* @returns 更新结果
|
||||
*/
|
||||
export async function updateDocumentType(id: string, documentType: DocumentTypeUpdateDTO): Promise<{
|
||||
export async function updateDocumentType(id: string, documentType: DocumentTypeUpdateDTO, frontendJWT?: string): Promise<{
|
||||
data?: DocumentTypeUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -730,7 +740,8 @@ export async function updateDocumentType(id: string, documentType: DocumentTypeU
|
||||
const response = await postgrestPut<DocumentType, typeof apiDocumentType>(
|
||||
'document_types',
|
||||
apiDocumentType,
|
||||
{id}
|
||||
{id},
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -748,14 +759,14 @@ export async function updateDocumentType(id: string, documentType: DocumentTypeU
|
||||
}
|
||||
|
||||
// 获取关联分组信息
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds);
|
||||
|
||||
const groupsResponse = await getEvaluationPointGroupsByIds(groupIds, frontendJWT);
|
||||
|
||||
// 添加分组信息并转换为UI类型
|
||||
const typeWithGroups = {
|
||||
...updatedDocumentType,
|
||||
groups: groupsResponse.data || []
|
||||
};
|
||||
|
||||
|
||||
return { data: convertToUIDocumentType(typeWithGroups) };
|
||||
} catch (error) {
|
||||
console.error('更新文档类型失败:', error);
|
||||
|
||||
@@ -129,9 +129,9 @@ interface ScoringProposal {
|
||||
* @param request Remix请求对象,用于获取用户会话
|
||||
* @returns 评查点结果列表和统计数据
|
||||
*/
|
||||
export async function getReviewPoints(fileId: string, request: Request) {
|
||||
export async function getReviewPoints(fileId: string, request: Request) {
|
||||
// 获取用户会话信息
|
||||
const { userInfo } = await getUserSession(request);
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (!userInfo?.user_id) {
|
||||
console.error("用户身份验证失败");
|
||||
@@ -141,7 +141,7 @@ export async function getReviewPoints(fileId: string, request: Request) {
|
||||
// const userId = userInfo.user_id.toString();
|
||||
|
||||
// 首先先获取这个文档的数据
|
||||
const documentData = await getDocumentWithNoUserId(fileId);
|
||||
const documentData = await getDocumentWithNoUserId(fileId, frontendJWT);
|
||||
if (documentData.error) {
|
||||
console.error("获取文档数据错误:", documentData.error);
|
||||
return Response.json({ error: documentData.error }, { status: documentData.status || 500 });
|
||||
@@ -154,7 +154,8 @@ export async function getReviewPoints(fileId: string, request: Request) {
|
||||
'document_id': `eq.${fileId}`
|
||||
},
|
||||
order: 'id.desc',
|
||||
limit: 1
|
||||
limit: 1,
|
||||
token: frontendJWT
|
||||
};
|
||||
const contractStructureComparisonResponse = await postgrestGet('contract_structure_comparison', contractStructureComparisonParams);
|
||||
|
||||
@@ -195,7 +196,8 @@ export async function getReviewPoints(fileId: string, request: Request) {
|
||||
select: '*',
|
||||
filter: {
|
||||
'document_id': `eq.${fileId}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
const evaluationResultsResponse = await postgrestGet('evaluation_results', evaluationResultsParams);
|
||||
|
||||
@@ -223,7 +225,8 @@ export async function getReviewPoints(fileId: string, request: Request) {
|
||||
select: '*',
|
||||
filter: {
|
||||
'id': `in.(${evaluationPointIds.join(',')})`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
const evaluationPointsResponse = await postgrestGet('evaluation_points', evaluationPointsParams);
|
||||
|
||||
@@ -249,7 +252,8 @@ export async function getReviewPoints(fileId: string, request: Request) {
|
||||
select: '*',
|
||||
filter: {
|
||||
'id': `in.(${groupIds.join(',')})`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
const groupsResponse = await postgrestGet('evaluation_point_groups', groupsParams);
|
||||
|
||||
@@ -272,7 +276,8 @@ export async function getReviewPoints(fileId: string, request: Request) {
|
||||
filter: {
|
||||
'document_id': `eq.${fileId}`,
|
||||
'evaluation_point_id': `in.(${manualReviewPointsIds.join(',')})`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
const manualReviewPointsResponse = await postgrestGet('audit_status', manualReviewPointsParams);
|
||||
if (manualReviewPointsResponse.error) {
|
||||
@@ -326,7 +331,8 @@ export async function getReviewPoints(fileId: string, request: Request) {
|
||||
filter: {
|
||||
'document_id': `eq.${fileId}`,
|
||||
'deleted_at': `is.null`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
const scoringProposalsResponse = await postgrestGet('cross_scoring_proposals', scoringProposalsParams);
|
||||
|
||||
@@ -754,7 +760,7 @@ export async function updateReviewResult(
|
||||
}> {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { userInfo } = await getUserSession(request);
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (!userInfo?.user_id) {
|
||||
console.error("用户身份验证失败");
|
||||
@@ -770,7 +776,8 @@ export async function updateReviewResult(
|
||||
// 首先获取当前评查结果数据
|
||||
const currentResultResponse = await postgrestGet('evaluation_results', {
|
||||
select: '*',
|
||||
filter: { id: `eq.${resultId}` }
|
||||
filter: { id: `eq.${resultId}` },
|
||||
token: frontendJWT
|
||||
});
|
||||
|
||||
if (currentResultResponse.error) {
|
||||
@@ -805,7 +812,8 @@ export async function updateReviewResult(
|
||||
const resultResponse = await postgrestPut<unknown, typeof updatedData>(
|
||||
'evaluation_results',
|
||||
updatedData,
|
||||
{ id: resultId }
|
||||
{ id: resultId },
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (resultResponse.error) {
|
||||
@@ -830,7 +838,8 @@ export async function updateReviewResult(
|
||||
{
|
||||
id: editAuditStatusId,
|
||||
user_id: userId // 添加用户ID条件,确保只能更新自己的记录
|
||||
}
|
||||
},
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (auditStatusResponse.error) {
|
||||
@@ -853,7 +862,7 @@ export async function updateReviewResult(
|
||||
};
|
||||
|
||||
// 使用postgrestPost创建新记录
|
||||
const postResponse = await postgrestPost('audit_status', newAuditStatus);
|
||||
const postResponse = await postgrestPost('audit_status', newAuditStatus, frontendJWT);
|
||||
|
||||
if (postResponse.error) {
|
||||
return { error: postResponse.error, status: postResponse.status || 500 };
|
||||
@@ -889,7 +898,7 @@ export async function confirmReviewResults(documentId: string, request: Request)
|
||||
}> {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { userInfo } = await getUserSession(request);
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (!userInfo?.user_id) {
|
||||
console.error("用户身份验证失败");
|
||||
@@ -932,7 +941,8 @@ export async function confirmReviewResults(documentId: string, request: Request)
|
||||
{
|
||||
id: documentId,
|
||||
user_id: userId // 添加用户ID条件,确保只能更新自己的文档
|
||||
}
|
||||
},
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -68,9 +68,10 @@ function extractApiData<T>(responseData: unknown): T | null {
|
||||
|
||||
/**
|
||||
* 获取评查点分组列表
|
||||
* @param token JWT token (可选)
|
||||
* @returns 评查点分组列表
|
||||
*/
|
||||
export async function getRuleGroups(): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getRuleGroups(token?: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
const params: PostgrestParams = {
|
||||
select: `
|
||||
@@ -84,7 +85,8 @@ export async function getRuleGroups(): Promise<{data: RuleGroup[]; error?: never
|
||||
`,
|
||||
filter: {
|
||||
'pid': 'eq.0'
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const response = await postgrestGet<{code: number; msg: string; data: Array<{
|
||||
@@ -138,9 +140,10 @@ export async function getRuleGroups(): Promise<{data: RuleGroup[]; error?: never
|
||||
/**
|
||||
* 获取指定分组的子分组
|
||||
* @param parentId 父分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 子分组列表
|
||||
*/
|
||||
export async function getChildGroups(parentId: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getChildGroups(parentId: string, token?: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 1. 获取子分组
|
||||
const childGroupsParams: PostgrestParams = {
|
||||
@@ -154,7 +157,8 @@ export async function getChildGroups(parentId: string): Promise<{data: RuleGroup
|
||||
`,
|
||||
filter: {
|
||||
'pid': `eq.${parentId}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const childGroupsResponse = await postgrestGet<{code: number; msg: string; data: Array<{
|
||||
@@ -179,7 +183,8 @@ export async function getChildGroups(parentId: string): Promise<{data: RuleGroup
|
||||
select: 'id',
|
||||
filter: {
|
||||
'evaluation_point_groups_id': `eq.${group.id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const ruleCountResponse = await postgrestGet<ApiResponse<Array<{id: number}>>>('evaluation_points', ruleCountParams);
|
||||
@@ -203,7 +208,8 @@ export async function getChildGroups(parentId: string): Promise<{data: RuleGroup
|
||||
select: 'id',
|
||||
filter: {
|
||||
'evaluation_point_groups_id': `eq.${group.id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const ruleCountResponse = await postgrestGet<ApiResponse<Array<{id: number}>>>('evaluation_points', ruleCountParams);
|
||||
@@ -234,9 +240,10 @@ export async function getChildGroups(parentId: string): Promise<{data: RuleGroup
|
||||
|
||||
/**
|
||||
* 获取所有评查点分组(包括一级和二级)
|
||||
* @param token JWT token (可选)
|
||||
* @returns 完整的评查点分组列表
|
||||
*/
|
||||
export async function getAllRuleGroups(): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getAllRuleGroups(token?: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 1. 获取所有分组
|
||||
const allGroupsParams: PostgrestParams = {
|
||||
@@ -245,7 +252,8 @@ export async function getAllRuleGroups(): Promise<{data: RuleGroup[]; error?: ne
|
||||
pid,
|
||||
name,
|
||||
is_enabled
|
||||
`
|
||||
`,
|
||||
token
|
||||
};
|
||||
|
||||
const allGroupsResponse = await postgrestGet<{code: number; msg: string; data: Array<{
|
||||
@@ -292,7 +300,8 @@ export async function getAllRuleGroups(): Promise<{data: RuleGroup[]; error?: ne
|
||||
select: 'id',
|
||||
filter: {
|
||||
'evaluation_point_groups_id': `eq.${child.id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const ruleCountResponse = await postgrestGet<ApiResponse<Array<{id: number}>>>('evaluation_points', ruleCountParams);
|
||||
@@ -316,9 +325,10 @@ export async function getAllRuleGroups(): Promise<{data: RuleGroup[]; error?: ne
|
||||
/**
|
||||
* 获取单个评查点分组详情
|
||||
* @param id 分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 分组详情
|
||||
*/
|
||||
export async function getRuleGroup(id: string): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getRuleGroup(id: string, token?: string): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
if (!id) {
|
||||
return { error: '分组ID不能为空', status: 400 };
|
||||
@@ -336,7 +346,8 @@ export async function getRuleGroup(id: string): Promise<{data: RuleGroup; error?
|
||||
`,
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const response = await postgrestGet<{code: number; msg: string; data: Array<{
|
||||
@@ -389,7 +400,8 @@ export async function getRuleGroup(id: string): Promise<{data: RuleGroup; error?
|
||||
select: 'id',
|
||||
filter: {
|
||||
'evaluation_point_groups_id': `eq.${group.id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const ruleCountResponse = await postgrestGet<ApiResponse<Array<{id: number}>>>('evaluation_points', ruleCountParams);
|
||||
@@ -412,9 +424,10 @@ export async function getRuleGroup(id: string): Promise<{data: RuleGroup; error?
|
||||
/**
|
||||
* 创建评查点分组
|
||||
* @param groupData 分组数据
|
||||
* @param token JWT token (可选)
|
||||
* @returns 创建的分组
|
||||
*/
|
||||
export async function createRuleGroup(groupData: RuleGroupCreateUpdateDto): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function createRuleGroup(groupData: RuleGroupCreateUpdateDto, token?: string): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 验证必填字段
|
||||
if (!groupData.name || !groupData.code) {
|
||||
@@ -447,7 +460,8 @@ export async function createRuleGroup(groupData: RuleGroupCreateUpdateDto): Prom
|
||||
// 直接发送到 PostgreSQL 表
|
||||
const response = await postgrestPost<ApiResponse<ApiRuleGroup> | ApiRuleGroup, ApiRuleGroup>(
|
||||
'evaluation_point_groups', // 表名
|
||||
apiGroup
|
||||
apiGroup,
|
||||
token
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -490,15 +504,17 @@ export async function createRuleGroup(groupData: RuleGroupCreateUpdateDto): Prom
|
||||
* 更新评查点分组
|
||||
* @param id 分组ID
|
||||
* @param data 更新的分组数据
|
||||
* @param token JWT token (可选)
|
||||
* @returns 更新后的分组
|
||||
*/
|
||||
export async function updateRuleGroup(id: string, data: RuleGroupCreateUpdateDto): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function updateRuleGroup(id: string, data: RuleGroupCreateUpdateDto, token?: string): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 使用新的filters参数
|
||||
const response = await postgrestPut<ApiResponse<RuleGroup> | RuleGroup, RuleGroupCreateUpdateDto>(
|
||||
'evaluation_point_groups',
|
||||
data,
|
||||
{ id }
|
||||
{ id },
|
||||
token
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -524,12 +540,13 @@ export async function updateRuleGroup(id: string, data: RuleGroupCreateUpdateDto
|
||||
/**
|
||||
* 删除评查点分组
|
||||
* @param id 分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export async function deleteRuleGroup(id: string): Promise<{success: boolean; error?: string}> {
|
||||
export async function deleteRuleGroup(id: string, token?: string): Promise<{success: boolean; error?: string}> {
|
||||
try {
|
||||
// 1. 首先获取分组信息,判断是一级还是二级分组
|
||||
const groupResponse = await getRuleGroup(id);
|
||||
const groupResponse = await getRuleGroup(id, token);
|
||||
if (groupResponse.error) {
|
||||
return { success: false, error: groupResponse.error };
|
||||
}
|
||||
@@ -542,7 +559,7 @@ export async function deleteRuleGroup(id: string): Promise<{success: boolean; er
|
||||
// 2. 如果是一级分组,需要先删除所有子分组
|
||||
if (group.pid === '0') {
|
||||
// 获取所有子分组
|
||||
const childGroupsResponse = await getChildGroups(id);
|
||||
const childGroupsResponse = await getChildGroups(id, token);
|
||||
if (childGroupsResponse.error) {
|
||||
return { success: false, error: childGroupsResponse.error };
|
||||
}
|
||||
@@ -551,7 +568,7 @@ export async function deleteRuleGroup(id: string): Promise<{success: boolean; er
|
||||
|
||||
// 遍历删除每个子分组
|
||||
for (const childGroup of childGroups) {
|
||||
const deleteChildResult = await deleteChildGroup(childGroup.id);
|
||||
const deleteChildResult = await deleteChildGroup(childGroup.id, token);
|
||||
if (!deleteChildResult.success) {
|
||||
return deleteChildResult;
|
||||
}
|
||||
@@ -559,7 +576,7 @@ export async function deleteRuleGroup(id: string): Promise<{success: boolean; er
|
||||
}
|
||||
|
||||
// 3. 删除分组下的所有评查点
|
||||
const deletePointsResult = await deleteEvaluationPointsByGroupId(id);
|
||||
const deletePointsResult = await deleteEvaluationPointsByGroupId(id, token);
|
||||
if (!deletePointsResult.success) {
|
||||
return deletePointsResult;
|
||||
}
|
||||
@@ -568,7 +585,8 @@ export async function deleteRuleGroup(id: string): Promise<{success: boolean; er
|
||||
const response = await postgrestDelete<ApiResponse<{id: number}>>('evaluation_point_groups', {
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
@@ -588,12 +606,13 @@ export async function deleteRuleGroup(id: string): Promise<{success: boolean; er
|
||||
/**
|
||||
* 删除子分组及其相关数据
|
||||
* @param id 子分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
*/
|
||||
async function deleteChildGroup(id: string): Promise<{success: boolean; error?: string}> {
|
||||
async function deleteChildGroup(id: string, token?: string): Promise<{success: boolean; error?: string}> {
|
||||
try {
|
||||
// 1. 删除子分组下的所有评查点
|
||||
const deletePointsResult = await deleteEvaluationPointsByGroupId(id);
|
||||
const deletePointsResult = await deleteEvaluationPointsByGroupId(id, token);
|
||||
if (!deletePointsResult.success) {
|
||||
return deletePointsResult;
|
||||
}
|
||||
@@ -602,7 +621,8 @@ async function deleteChildGroup(id: string): Promise<{success: boolean; error?:
|
||||
const response = await postgrestDelete<ApiResponse<{id: number}>>('evaluation_point_groups', {
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
@@ -622,14 +642,16 @@ async function deleteChildGroup(id: string): Promise<{success: boolean; error?:
|
||||
/**
|
||||
* 删除指定分组下的所有评查点
|
||||
* @param groupId 分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
*/
|
||||
async function deleteEvaluationPointsByGroupId(groupId: string): Promise<{success: boolean; error?: string}> {
|
||||
async function deleteEvaluationPointsByGroupId(groupId: string, token?: string): Promise<{success: boolean; error?: string}> {
|
||||
try {
|
||||
const response = await postgrestDelete<ApiResponse<{id: number}>>('evaluation_points', {
|
||||
filter: {
|
||||
'evaluation_point_groups_id': `eq.${groupId}`
|
||||
}
|
||||
},
|
||||
token
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -100,6 +100,7 @@ export interface DocumentSearchParams {
|
||||
sortOrder?: string; // 排序方式
|
||||
page?: number; // 当前页码
|
||||
pageSize?: number; // 每页条数
|
||||
token?: string; // JWT token
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +169,8 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}, do
|
||||
reviewStatus,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
sortOrder = 'upload_time_desc'
|
||||
sortOrder = 'upload_time_desc',
|
||||
token
|
||||
} = searchParams;
|
||||
|
||||
let p_typeid: number[] | null = null;
|
||||
@@ -204,8 +206,8 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}, do
|
||||
|
||||
// 并行执行获取数据和获取总数的请求
|
||||
const [filesResponse, countResponse] = await Promise.all([
|
||||
postgrestPost<ReviewFileFromSQL[]>('rpc/get_review_files_with_details', listParams),
|
||||
postgrestPost<number>('rpc/count_review_files', rpcParams)
|
||||
postgrestPost<ReviewFileFromSQL[]>('rpc/get_review_files_with_details', listParams, token),
|
||||
postgrestPost<number>('rpc/count_review_files', rpcParams, token)
|
||||
]);
|
||||
|
||||
// 处理获取文档列表的错误
|
||||
@@ -316,9 +318,10 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}, do
|
||||
* @param id 文件ID
|
||||
* @param auditStatus 审核状态
|
||||
* @param userId 用户ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 更新结果
|
||||
*/
|
||||
export async function updateDocumentAuditStatus(id: string, auditStatus: number, userId: string): Promise<{
|
||||
export async function updateDocumentAuditStatus(id: string, auditStatus: number, userId: string, token?: string): Promise<{
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -338,7 +341,8 @@ export async function updateDocumentAuditStatus(id: string, auditStatus: number,
|
||||
{
|
||||
id: parseInt(id),
|
||||
user_id: parseInt(userId) // 确保只能更新自己的文档
|
||||
}
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface RulesQueryParams {
|
||||
orderBy?: string;
|
||||
orderDirection?: 'asc' | 'desc';
|
||||
reviewType?: string; // 添加 reviewType 参数,值为 contract 或 record
|
||||
token?: string; // JWT token
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +165,8 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
|
||||
keyword,
|
||||
orderBy = 'created_at',
|
||||
orderDirection = 'desc',
|
||||
reviewType
|
||||
reviewType,
|
||||
token
|
||||
} = params;
|
||||
|
||||
// 构建PostgrestParams参数
|
||||
@@ -194,7 +196,8 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
|
||||
// 添加额外头部,用于获取总记录数
|
||||
headers: {
|
||||
'Prefer': 'count=exact'
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
// 添加精确匹配过滤:规则组ID
|
||||
@@ -211,7 +214,8 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
|
||||
try {
|
||||
// 先获取所有评查点组数据,用于找到对应的pid
|
||||
const groupsAllResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; pid: number}>}>('evaluation_point_groups', {
|
||||
select: 'id,pid'
|
||||
select: 'id,pid',
|
||||
token
|
||||
});
|
||||
|
||||
let groups: Array<{id: number; pid: number}> = [];
|
||||
@@ -254,7 +258,8 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
|
||||
select: 'id',
|
||||
filter: {
|
||||
'pid': `eq.${ruleType}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
const groupsResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number}>}>('evaluation_point_groups', groupsParams);
|
||||
@@ -364,7 +369,8 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
|
||||
// 使用Promise.all并行查询所有分组信息 - 使用正确的函数名
|
||||
const groupPromises = validGroupIds.map(id =>
|
||||
postgrestGet<{code: number; msg: string; data: {id: number; pid: number; name: string; first_name: string; second_name: string}[]}>(
|
||||
`rpc/get_evaluation_point_group_with_pid?input_id=${id}`
|
||||
`rpc/get_evaluation_point_group_with_pid?input_id=${id}`,
|
||||
{ token }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -447,9 +453,10 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
|
||||
/**
|
||||
* 获取单个评查点详情
|
||||
* @param id 评查点ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 评查点详情
|
||||
*/
|
||||
export async function getRule(id: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getRule(id: string, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 使用postgrestGet获取单个评查点数据
|
||||
const postgrestParams: PostgrestParams = {
|
||||
@@ -473,7 +480,8 @@ export async function getRule(id: string): Promise<{data: Rule; error?: never} |
|
||||
action_config,
|
||||
created_at,
|
||||
updated_at
|
||||
`
|
||||
`,
|
||||
token
|
||||
};
|
||||
|
||||
// 获取评查点详情
|
||||
@@ -498,7 +506,8 @@ export async function getRule(id: string): Promise<{data: Rule; error?: never} |
|
||||
select: 'id,name',
|
||||
filter: {
|
||||
'id': `eq.${apiRule.evaluation_point_groups_id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
// 查询评查点分组
|
||||
@@ -538,9 +547,10 @@ export async function getRule(id: string): Promise<{data: Rule; error?: never} |
|
||||
/**
|
||||
* 创建新评查点
|
||||
* @param ruleData 评查点数据
|
||||
* @param token JWT token (可选)
|
||||
* @returns 创建的评查点
|
||||
*/
|
||||
export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 将前端模型转换为API接受的格式
|
||||
const apiRuleData = {
|
||||
@@ -569,7 +579,7 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
|
||||
};
|
||||
|
||||
// 使用postgrestPost创建评查点
|
||||
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('evaluation_points', apiRuleData);
|
||||
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('evaluation_points', apiRuleData, token);
|
||||
|
||||
// 检查是否有错误响应
|
||||
if (response.error) {
|
||||
@@ -598,9 +608,10 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
|
||||
* 更新评查点
|
||||
* @param id 评查点ID
|
||||
* @param ruleData 评查点数据
|
||||
* @param token JWT token (可选)
|
||||
* @returns 更新后的评查点
|
||||
*/
|
||||
export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 构建API接受的更新数据
|
||||
const apiRuleData: Record<string, unknown> = {};
|
||||
@@ -630,7 +641,7 @@ export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' |
|
||||
}
|
||||
|
||||
// 使用postgrestPut更新评查点
|
||||
const response = await postgrestPut<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>(`evaluation_points/${id}`, apiRuleData);
|
||||
const response = await postgrestPut<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>(`evaluation_points/${id}`, apiRuleData, undefined, token);
|
||||
|
||||
// 检查是否有错误响应
|
||||
if (response.error) {
|
||||
@@ -658,9 +669,10 @@ export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' |
|
||||
/**
|
||||
* 删除评查点
|
||||
* @param id 评查点ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export async function deleteRule(id: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function deleteRule(id: string, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// console.log(`开始删除评查点, ID: ${id}`);
|
||||
|
||||
@@ -671,7 +683,8 @@ export async function deleteRule(id: string): Promise<{data: Rule; error?: never
|
||||
},
|
||||
headers: {
|
||||
'Prefer': 'return=representation' // 请求返回被删除的记录
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
// 使用postgrestDelete删除评查点
|
||||
@@ -771,12 +784,13 @@ export async function deleteRule(id: string): Promise<{data: Rule; error?: never
|
||||
/**
|
||||
* 复制评查点
|
||||
* @param id 评查点ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 新创建的评查点
|
||||
*/
|
||||
export async function duplicateRule(id: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function duplicateRule(id: string, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 1. 获取原评查点详情
|
||||
const ruleResponse = await getRule(id);
|
||||
const ruleResponse = await getRule(id, token);
|
||||
|
||||
if (ruleResponse.error || !ruleResponse.data) {
|
||||
return { error: ruleResponse.error || '获取评查点详情失败', status: 500 };
|
||||
@@ -798,7 +812,7 @@ export async function duplicateRule(id: string): Promise<{data: Rule; error?: ne
|
||||
};
|
||||
|
||||
// 3. 创建新评查点
|
||||
return createRule(newRuleData);
|
||||
return createRule(newRuleData, token);
|
||||
|
||||
} catch (error) {
|
||||
console.error('复制评查点出错:', error);
|
||||
@@ -833,9 +847,10 @@ export interface RuleGroup {
|
||||
/**
|
||||
* 获取评查点类型列表
|
||||
* @param reviewType 评查类型,contract表示合同,record表示卷宗
|
||||
* @param token JWT token (可选)
|
||||
* @returns 评查点类型列表
|
||||
*/
|
||||
export async function getRuleTypes(reviewType?: string): Promise<{data: RuleType[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getRuleTypes(reviewType?: string, token?: string): Promise<{data: RuleType[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 构建PostgrestParams参数
|
||||
const postgrestParams: PostgrestParams = {
|
||||
@@ -850,7 +865,8 @@ export async function getRuleTypes(reviewType?: string): Promise<{data: RuleType
|
||||
// 查询父ID为0的类型(顶级类型)
|
||||
filter: {
|
||||
'pid': 'eq.0'
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
// 根据 reviewType 添加过滤条件
|
||||
@@ -919,9 +935,10 @@ export async function getRuleTypes(reviewType?: string): Promise<{data: RuleType
|
||||
/**
|
||||
* 根据评查点类型ID获取规则组列表
|
||||
* @param typeId 评查点类型ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 规则组列表
|
||||
*/
|
||||
export async function getRuleGroupsByType(typeId: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getRuleGroupsByType(typeId: string, token?: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 如果typeId为空或为"全部",则返回空数组
|
||||
if (!typeId || typeId === 'all') {
|
||||
@@ -941,7 +958,8 @@ export async function getRuleGroupsByType(typeId: string): Promise<{data: RuleGr
|
||||
// 查询指定类型ID的规则组
|
||||
filter: {
|
||||
'pid': `eq.${typeId}`
|
||||
}
|
||||
},
|
||||
token
|
||||
};
|
||||
|
||||
// 发送请求获取规则组列表
|
||||
|
||||
+29
-19
@@ -39,6 +39,7 @@ export interface DocumentSearchParams {
|
||||
pageSize?: number;
|
||||
reviewType?: string;
|
||||
userId?: string; // 添加用户ID筛选
|
||||
token?: string; // JWT token
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +89,7 @@ export interface DocumentUI {
|
||||
fileType: string;
|
||||
path: string;
|
||||
isTest: boolean;
|
||||
remark?: string;
|
||||
updatedAt?: string;
|
||||
pageCount?: number;
|
||||
ocrResult?: unknown;
|
||||
@@ -108,11 +110,12 @@ function getFileExtension(filename: string): string {
|
||||
* @param id 评查结果ID
|
||||
* @returns 评查结果
|
||||
*/
|
||||
async function getEvaluationResults(id: number) {
|
||||
async function getEvaluationResults(id: number, frontendJWT?: string) {
|
||||
const response = await postgrestGet<[]>('evaluation_results', {
|
||||
filter: {
|
||||
'document_id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
});
|
||||
if (response.error) {
|
||||
return { error: response.error, status: response.status };
|
||||
@@ -125,12 +128,12 @@ async function getEvaluationResults(id: number) {
|
||||
/**
|
||||
* 将API文档转换为UI文档
|
||||
*/
|
||||
async function convertToUIDocument(doc: Document): Promise<DocumentUI> {
|
||||
async function convertToUIDocument(doc: Document, frontendJWT?: string): Promise<DocumentUI> {
|
||||
// 获取文档类型信息
|
||||
const typeResponse = await getDocumentTypes();
|
||||
const typeResponse = await getDocumentTypes(undefined, frontendJWT);
|
||||
const documentTypes = typeResponse.data?.types || [];
|
||||
const docType = documentTypes.find(type => type.id.toString() === doc.type_id.toString());
|
||||
const evaluationResult = await getEvaluationResults(doc.id);
|
||||
const evaluationResult = await getEvaluationResults(doc.id, frontendJWT);
|
||||
let issues = 0;
|
||||
|
||||
interface EvaluationResultItem {
|
||||
@@ -164,6 +167,7 @@ async function convertToUIDocument(doc: Document): Promise<DocumentUI> {
|
||||
fileType: getFileExtension(doc.name),
|
||||
path: doc.path,
|
||||
isTest: doc.is_test_document,
|
||||
remark: doc.remark,
|
||||
updatedAt: formatDate(doc.updated_at),
|
||||
pageCount: doc.ocr_result?.__meta?.page_count || 0,
|
||||
ocrResult: doc.ocr_result
|
||||
@@ -216,7 +220,8 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
|
||||
dateFrom,
|
||||
dateTo,
|
||||
reviewType,
|
||||
userId
|
||||
userId,
|
||||
token
|
||||
} = searchParams;
|
||||
|
||||
let documentTypes: number[] | undefined;
|
||||
@@ -248,8 +253,8 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
|
||||
|
||||
// 并行执行获取数据和获取总数的请求
|
||||
const [documentsResponse, countResponse] = await Promise.all([
|
||||
postgrestPost<DocumentFromSQL[], unknown>('rpc/get_documents_with_filters', { ...rpcParams, page, page_size: pageSize }),
|
||||
postgrestPost<number, unknown>('rpc/count_documents_with_filters', rpcParams)
|
||||
postgrestPost<DocumentFromSQL[], unknown>('rpc/get_documents_with_filters', { ...rpcParams, page, page_size: pageSize }, token),
|
||||
postgrestPost<number, unknown>('rpc/count_documents_with_filters', rpcParams, token)
|
||||
]);
|
||||
|
||||
// 处理获取文档列表的错误
|
||||
@@ -305,9 +310,10 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
|
||||
* 删除文档
|
||||
* @param id 文档ID
|
||||
* @param userId 用户ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export async function deleteDocument(id: string, userId: string): Promise<{
|
||||
export async function deleteDocument(id: string, userId: string, token?: string): Promise<{
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -327,7 +333,8 @@ export async function deleteDocument(id: string, userId: string): Promise<{
|
||||
filter: {
|
||||
'id': `eq.${id}`,
|
||||
'user_id': `eq.${userId}` // 确保只能删除自己的文档
|
||||
}
|
||||
},
|
||||
token
|
||||
}
|
||||
);
|
||||
|
||||
@@ -350,7 +357,7 @@ export async function deleteDocument(id: string, userId: string): Promise<{
|
||||
* @param id 文档ID
|
||||
* @returns 文档详情
|
||||
*/
|
||||
export async function getDocument(id: string, userId: string): Promise<{
|
||||
export async function getDocument(id: string, userId: string, frontendJWT?: string): Promise<{
|
||||
data?: DocumentUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -371,7 +378,8 @@ export async function getDocument(id: string, userId: string): Promise<{
|
||||
'id': `eq.${id}`,
|
||||
'user_id': `eq.${userId}`
|
||||
},
|
||||
limit: 1
|
||||
limit: 1,
|
||||
token: frontendJWT
|
||||
}
|
||||
);
|
||||
|
||||
@@ -384,7 +392,7 @@ export async function getDocument(id: string, userId: string): Promise<{
|
||||
return { error: '文档不存在', status: 404 };
|
||||
}
|
||||
|
||||
const documentUI = await convertToUIDocument(extractedData[0]);
|
||||
const documentUI = await convertToUIDocument(extractedData[0], frontendJWT);
|
||||
|
||||
return { data: documentUI };
|
||||
} catch (error) {
|
||||
@@ -402,7 +410,7 @@ export async function getDocument(id: string, userId: string): Promise<{
|
||||
* @param id 文档ID
|
||||
* @returns 文档详情
|
||||
*/
|
||||
export async function getDocumentWithNoUserId(id: string): Promise<{
|
||||
export async function getDocumentWithNoUserId(id: string, frontendJWT?: string): Promise<{
|
||||
data?: DocumentUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -418,7 +426,8 @@ export async function getDocumentWithNoUserId(id: string): Promise<{
|
||||
filter: {
|
||||
'id': `eq.${id}`,
|
||||
},
|
||||
limit: 1
|
||||
limit: 1,
|
||||
token: frontendJWT
|
||||
}
|
||||
);
|
||||
|
||||
@@ -432,7 +441,7 @@ export async function getDocumentWithNoUserId(id: string): Promise<{
|
||||
}
|
||||
|
||||
// console.log('extractedData', extractedData);
|
||||
const documentUI = await convertToUIDocument(extractedData[0]);
|
||||
const documentUI = await convertToUIDocument(extractedData[0], frontendJWT);
|
||||
|
||||
return { data: documentUI };
|
||||
} catch (error) {
|
||||
@@ -488,7 +497,7 @@ export async function getFileDownloadUrl(filePath: string): Promise<{
|
||||
* @param document 部分文档数据
|
||||
* @returns 更新结果
|
||||
*/
|
||||
export async function updateDocument(id: string, document: Partial<DocumentUI> & { remark?: string }, userId: string): Promise<{
|
||||
export async function updateDocument(id: string, document: Partial<DocumentUI> & { remark?: string }, userId: string, frontendJWT?: string): Promise<{
|
||||
data?: DocumentUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -533,7 +542,8 @@ export async function updateDocument(id: string, document: Partial<DocumentUI> &
|
||||
{
|
||||
id: parseInt(id),
|
||||
user_id: parseInt(userId) // 确保只能更新自己的文档
|
||||
}
|
||||
},
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -542,7 +552,7 @@ export async function updateDocument(id: string, document: Partial<DocumentUI> &
|
||||
}
|
||||
|
||||
// 获取更新后的完整文档数据
|
||||
const updatedResponse = await getDocument(id, userId);
|
||||
const updatedResponse = await getDocument(id, userId, frontendJWT);
|
||||
|
||||
return updatedResponse;
|
||||
} catch (error) {
|
||||
|
||||
@@ -357,12 +357,19 @@ export async function uploadDocumentToServer(
|
||||
// const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, {
|
||||
try {
|
||||
// console.log('【调试】开始fetch请求...');
|
||||
|
||||
// 构建请求头,只在有JWT token时添加Authorization
|
||||
const headers: HeadersInit = {
|
||||
'X-File-Name': encodeURIComponent(fileName)
|
||||
};
|
||||
|
||||
if (jwtToken) {
|
||||
headers['Authorization'] = `Bearer ${jwtToken}`;
|
||||
}
|
||||
|
||||
const response = await fetch(uploadUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-File-Name': encodeURIComponent(fileName),
|
||||
'Authorization': `Bearer ${jwtToken || ''}`
|
||||
},
|
||||
headers,
|
||||
body: formData
|
||||
});
|
||||
|
||||
@@ -422,7 +429,7 @@ export async function uploadDocumentToServer(
|
||||
* @param reviewType 审核类型(可选)
|
||||
* @returns 文档列表
|
||||
*/
|
||||
export async function getTodayDocuments(userInfo?: { user_id?: number; [key: string]: unknown }, reviewType?: string): Promise<{data: Document[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getTodayDocuments(userInfo?: { user_id?: number; [key: string]: unknown }, reviewType?: string, token?: string): Promise<{data: Document[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 检查用户信息是否存在
|
||||
if (!userInfo?.user_id) {
|
||||
@@ -492,7 +499,7 @@ export async function getTodayDocuments(userInfo?: { user_id?: number; [key: str
|
||||
// postgrestGet<ContractStructureComparison[]>('contract_structure_comparison', comparisonParams)
|
||||
// ]);
|
||||
|
||||
const documentsResponse = await postgrestGet<Document[]>('documents', documentsParams);
|
||||
const documentsResponse = await postgrestGet<Document[]>('documents', { ...documentsParams, token });
|
||||
|
||||
// console.log('documents表响应:', documentsResponse);
|
||||
// console.log('contract_structure_comparison表响应:', comparisonResponse);
|
||||
@@ -594,7 +601,7 @@ export async function getTodayDocuments(userInfo?: { user_id?: number; [key: str
|
||||
}
|
||||
|
||||
// console.log('发送请求参数:', params);
|
||||
const response = await postgrestGet<Document[]>('documents', params);
|
||||
const response = await postgrestGet<Document[]>('documents', { ...params, token });
|
||||
// console.log('API 响应:', response);
|
||||
|
||||
if (response.error) {
|
||||
@@ -623,9 +630,10 @@ export async function getTodayDocuments(userInfo?: { user_id?: number; [key: str
|
||||
/**
|
||||
* 获取文档类型列表
|
||||
* @param reviewType 审核类型(可选)
|
||||
* @param token JWT token (可选)
|
||||
* @returns 文档类型列表
|
||||
*/
|
||||
export async function getDocumentTypes(reviewType?: string): Promise<{data: DocumentType[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function getDocumentTypes(reviewType?: string, token?: string): Promise<{data: DocumentType[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
const params: PostgrestParams = {
|
||||
select: 'id, name',
|
||||
@@ -649,7 +657,7 @@ export async function getDocumentTypes(reviewType?: string): Promise<{data: Docu
|
||||
}
|
||||
}
|
||||
|
||||
const response = await postgrestGet<DocumentType[]>('document_types', params);
|
||||
const response = await postgrestGet<DocumentType[]>('document_types', { ...params, token });
|
||||
|
||||
if (response.error) {
|
||||
return { error: response.error, status: response.status };
|
||||
@@ -674,11 +682,13 @@ export async function getDocumentTypes(reviewType?: string): Promise<{data: Docu
|
||||
* 获取指定文档的状态
|
||||
* @param documentIds 文档ID列表
|
||||
* @param attachmentIds 合同附件ID列表(可选)
|
||||
* @param token JWT token (可选)
|
||||
* @returns 文档状态列表
|
||||
*/
|
||||
export async function getDocumentsStatus(
|
||||
documentIds: number[],
|
||||
attachmentIds?: number[]
|
||||
attachmentIds?: number[],
|
||||
token?: string
|
||||
): Promise<{data: Document[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
if ((!documentIds || documentIds.length === 0) && (!attachmentIds || attachmentIds.length === 0)) {
|
||||
@@ -695,7 +705,7 @@ export async function getDocumentsStatus(
|
||||
'id': `in.(${documentIds.join(',')})`
|
||||
}
|
||||
};
|
||||
documentsResponse = await postgrestGet<Document[]>('documents', documentsParams);
|
||||
documentsResponse = await postgrestGet<Document[]>('documents', { ...documentsParams, token });
|
||||
}
|
||||
|
||||
// 查询合同附件状态
|
||||
@@ -708,7 +718,7 @@ export async function getDocumentsStatus(
|
||||
'id': `in.(${attachmentIds.join(',')})`
|
||||
}
|
||||
};
|
||||
attachmentResponse = await postgrestGet<ContractStructureComparison[]>('contract_structure_comparison', attachmentParams);
|
||||
attachmentResponse = await postgrestGet<ContractStructureComparison[]>('contract_structure_comparison', { ...attachmentParams, token });
|
||||
}
|
||||
|
||||
if (documentsResponse.error && attachmentResponse.error) {
|
||||
|
||||
+12
-10
@@ -94,9 +94,11 @@ function buildTypeFilter(reviewType: string | null): string {
|
||||
/**
|
||||
* 获取主页数据
|
||||
* @param reviewType 从客户端传入的 reviewType 值
|
||||
* @param userId 用户ID
|
||||
* @param token JWT token
|
||||
* @returns 主页数据
|
||||
*/
|
||||
export async function getHomeData(reviewType?: string | null,userId?: string | number): Promise<HomeStatistics> {
|
||||
export async function getHomeData(reviewType?: string | null,userId?: string | number, token?: string): Promise<HomeStatistics> {
|
||||
try {
|
||||
// 获取当前日期和时间相关值
|
||||
const startOfToday = dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||
@@ -170,7 +172,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
}
|
||||
|
||||
const todayPendingCount = await handleApiResponse<{ count: number }[]>(
|
||||
postgrestGet('documents', todayPendingParams),
|
||||
postgrestGet('documents', { ...todayPendingParams, token }),
|
||||
'获取今日待审核文件数量失败',
|
||||
[]
|
||||
);
|
||||
@@ -201,7 +203,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
}
|
||||
|
||||
const thisMonthReviewedCount = await handleApiResponse<{ count: number }[]>(
|
||||
postgrestGet('documents', thisMonthReviewedParams),
|
||||
postgrestGet('documents', { ...thisMonthReviewedParams, token }),
|
||||
'获取本月已审核文件数量失败',
|
||||
[]
|
||||
);
|
||||
@@ -237,7 +239,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
}
|
||||
|
||||
const lastMonthReviewedCount = await handleApiResponse<{ count: number }[]>(
|
||||
postgrestGet('documents', lastMonthReviewedParams),
|
||||
postgrestGet('documents', { ...lastMonthReviewedParams, token }),
|
||||
'获取上月已审核文件数量失败',
|
||||
[]
|
||||
);
|
||||
@@ -283,7 +285,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
}
|
||||
|
||||
const thisMonthTotalCount = await handleApiResponse<{ count: number }[]>(
|
||||
postgrestGet('documents', thisMonthTotalParams),
|
||||
postgrestGet('documents', { ...thisMonthTotalParams, token }),
|
||||
'获取本月审核通过数量失败',
|
||||
[]
|
||||
);
|
||||
@@ -323,7 +325,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
}
|
||||
|
||||
const lastMonthTotalCount = await handleApiResponse<{ count: number }[]>(
|
||||
postgrestGet('documents', lastMonthTotalParams),
|
||||
postgrestGet('documents', { ...lastMonthTotalParams, token }),
|
||||
'获取上月审核通过数量失败',
|
||||
[]
|
||||
);
|
||||
@@ -373,7 +375,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
end_time: endOfThisMonth,
|
||||
type_val: typeToQuery,
|
||||
userid: parseInt(userId as string)
|
||||
}),
|
||||
}, token),
|
||||
'获取合同本月问题数据失败',
|
||||
[]
|
||||
);
|
||||
@@ -388,7 +390,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
end_time: endOfLastMonth,
|
||||
type_val: typeToQuery,
|
||||
userid: parseInt(userId as string)
|
||||
}),
|
||||
}, token),
|
||||
'获取上月问题数据失败',
|
||||
[]
|
||||
);
|
||||
@@ -406,7 +408,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
end_time: endOfThisMonth,
|
||||
type_val: typeToQuery,
|
||||
userid: parseInt(userId as string)
|
||||
}),
|
||||
}, token),
|
||||
'获取本月许可卷宗类型2问题数据失败',
|
||||
[]
|
||||
);
|
||||
@@ -422,7 +424,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n
|
||||
end_time: endOfLastMonth,
|
||||
type_val: typeToQuery,
|
||||
userid: parseInt(userId as string)
|
||||
}),
|
||||
}, token),
|
||||
'获取上月许可卷宗类型2问题数据失败',
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// app/api/jwt-helper.server.ts
|
||||
import { getUserSession } from './login/auth.server';
|
||||
|
||||
/**
|
||||
* 从 request 中获取 JWT token
|
||||
* @param request Remix Request 对象
|
||||
* @returns JWT token 或 undefined
|
||||
*/
|
||||
export async function getJwtFromRequest(request: Request): Promise<string | undefined> {
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
return frontendJWT || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装 PostgrestParams,自动添加 JWT
|
||||
* @param request Remix Request 对象
|
||||
* @param params 原始参数
|
||||
* @returns 包含 JWT 的参数
|
||||
*/
|
||||
export async function withJwt<T extends { token?: string }>(
|
||||
request: Request,
|
||||
params?: T
|
||||
): Promise<T & { token: string | undefined }> {
|
||||
const jwt = await getJwtFromRequest(request);
|
||||
return {
|
||||
...params as T,
|
||||
token: jwt
|
||||
};
|
||||
}
|
||||
|
||||
@@ -526,10 +526,10 @@ async function callIDaaSLogout(accessToken: string, appId: string): Promise<void
|
||||
* @param userInfo - 从 IDaaS 获取的用户信息
|
||||
* @returns Promise<{success: boolean, data?: SsoUser, error?: string}>
|
||||
*/
|
||||
export async function saveUserInfo(userInfo: UserInfo): Promise<{success: boolean, data?: SsoUser, error?: string}> {
|
||||
export async function saveUserInfo(userInfo: UserInfo, token?: string): Promise<{success: boolean, data?: SsoUser, error?: string}> {
|
||||
try {
|
||||
console.log("开始保存用户信息", userInfo);
|
||||
|
||||
|
||||
// 验证必要字段
|
||||
if (!userInfo.sub) {
|
||||
return { success: false, error: "用户唯一标识 sub 不能为空" };
|
||||
@@ -540,7 +540,8 @@ export async function saveUserInfo(userInfo: UserInfo): Promise<{success: boolea
|
||||
filter: {
|
||||
"sub": `eq.${userInfo.sub}`,
|
||||
"deleted_at": "is.null" // 只查询未删除的记录
|
||||
}
|
||||
},
|
||||
token
|
||||
});
|
||||
|
||||
if (existingUserResult.error) {
|
||||
@@ -572,7 +573,8 @@ export async function saveUserInfo(userInfo: UserInfo): Promise<{success: boolea
|
||||
const updateResult = await postgrestPut<SsoUser[], Partial<SsoUser>>(
|
||||
"sso_users",
|
||||
userData,
|
||||
{ id: existingUser.id! }
|
||||
{ id: existingUser.id! },
|
||||
token
|
||||
);
|
||||
|
||||
if (updateResult.error) {
|
||||
@@ -589,7 +591,7 @@ export async function saveUserInfo(userInfo: UserInfo): Promise<{success: boolea
|
||||
// 3. 用户不存在,执行插入操作 同时需要给这个用户默认添加一个角色,角色为common
|
||||
console.log("用户不存在,执行插入操作");
|
||||
|
||||
const insertResult = await postgrestPost<SsoUser[], SsoUser>("sso_users", userData as SsoUser);
|
||||
const insertResult = await postgrestPost<SsoUser[], SsoUser>("sso_users", userData as SsoUser, token);
|
||||
|
||||
if (insertResult.error) {
|
||||
console.error("插入用户失败:", insertResult.error);
|
||||
@@ -601,7 +603,7 @@ export async function saveUserInfo(userInfo: UserInfo): Promise<{success: boolea
|
||||
// 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);
|
||||
await addDefaultRole(userData_with_id.id, 2, token);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -620,21 +622,23 @@ export async function saveUserInfo(userInfo: UserInfo): Promise<{success: boolea
|
||||
|
||||
/**
|
||||
* 为用户添加默认角色
|
||||
*
|
||||
*
|
||||
* @param userId - 用户ID
|
||||
* @param roleId - 角色ID,默认为2(common角色)
|
||||
* @param token - JWT令牌,用于调用postgrest服务
|
||||
* @returns 添加结果
|
||||
*/
|
||||
export async function addDefaultRole(userId: string, roleId: number = 2) {
|
||||
export async function addDefaultRole(userId: string, roleId: number = 2, token?: string) {
|
||||
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}`
|
||||
}
|
||||
},
|
||||
token
|
||||
});
|
||||
|
||||
if (existingRoleResult.error) {
|
||||
@@ -652,7 +656,7 @@ export async function addDefaultRole(userId: string, roleId: number = 2) {
|
||||
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
|
||||
});
|
||||
}, token);
|
||||
|
||||
if (addRoleResult.error) {
|
||||
console.error("添加用户角色失败:", addRoleResult.error);
|
||||
@@ -749,11 +753,16 @@ export async function simpleRootLogin(
|
||||
});
|
||||
|
||||
const loginResult = await loginResponse.json();
|
||||
console.log('登录接口返回', loginResult);
|
||||
|
||||
// 检查重试次数
|
||||
const retryCount = loginResult.retryCount || loginResult.retry_count || 0;
|
||||
console.log('登录重试次数:', retryCount);
|
||||
|
||||
if (loginResult.code === 0 && loginResult.data) {
|
||||
// 登录成功,构建用户信息
|
||||
const userData = loginResult.data;
|
||||
console.log('管理员登录userData', userData);
|
||||
// console.log('管理员登录userData', userData);
|
||||
const userRole = userData.role; // 默认角色
|
||||
|
||||
// 生成模拟的OAuth token信息
|
||||
@@ -797,13 +806,28 @@ export async function simpleRootLogin(
|
||||
frontendJWT
|
||||
});
|
||||
} else {
|
||||
// 登录失败,返回错误信息
|
||||
const errorMsg = loginResult.msg || "登录失败,请检查用户名和密码";
|
||||
// 登录失败,检查账户是否被锁定
|
||||
let errorMsg = loginResult.msg || "登录失败,请检查用户名和密码";
|
||||
let isLocked = false;
|
||||
|
||||
// 检查是否因重试次数过多被锁定
|
||||
if (retryCount >= 5) {
|
||||
errorMsg = "账户已被锁定,密码错误次数过多,请联系管理员";
|
||||
isLocked = true;
|
||||
} else if (retryCount > 0) {
|
||||
// 显示剩余尝试次数
|
||||
const remainingAttempts = 5 - retryCount;
|
||||
errorMsg = `${loginResult.msg || "用户名或密码错误"},还有 ${remainingAttempts} 次尝试机会`;
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: errorMsg
|
||||
error: errorMsg,
|
||||
retryCount: retryCount,
|
||||
isLocked: isLocked,
|
||||
remainingAttempts: isLocked ? 0 : (5 - retryCount)
|
||||
}), {
|
||||
status: 401,
|
||||
status: isLocked ? 403 : 401, // 403 表示禁止访问(账户被锁)
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* 2. 如果需要新的网络请求,在 `OAuthClient` 中添加
|
||||
*/
|
||||
import { OAuthClient } from "./oauth-client";
|
||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||
import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server";
|
||||
|
||||
interface TokenInfo {
|
||||
accessToken: string;
|
||||
@@ -29,7 +29,9 @@ export class TokenManager {
|
||||
private oauthClient: OAuthClient;
|
||||
|
||||
constructor() {
|
||||
this.oauthClient = new OAuthClient(OAUTH_CONFIG);
|
||||
// 🔒 安全:使用服务器端专用函数获取包含 clientSecret 的完整配置
|
||||
// 从 .server.ts 文件中运行时读取,确保环境变量正确加载
|
||||
this.oauthClient = new OAuthClient(getServerOAuthConfigRuntime());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+98
-30
@@ -1,7 +1,42 @@
|
||||
// app/api/postgrest-client.ts
|
||||
// import { AsyncLocalStorage } from 'async_hooks';
|
||||
import { apiRequest, type QueryParams } from './axios-client';
|
||||
import { handleApiError } from './error-handler';
|
||||
|
||||
/**
|
||||
* 请求上下文接口
|
||||
*/
|
||||
// interface RequestContext {
|
||||
// jwt?: string;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 创建异步本地存储用于存储请求上下文
|
||||
*/
|
||||
// const requestContext = new AsyncLocalStorage<RequestContext>();
|
||||
|
||||
/**
|
||||
* 在指定的上下文中运行函数
|
||||
* @param context 上下文对象
|
||||
* @param fn 要执行的函数
|
||||
* @returns 函数执行结果
|
||||
*/
|
||||
// export function runWithContext<T>(
|
||||
// context: RequestContext,
|
||||
// fn: () => T | Promise<T>
|
||||
// ): T | Promise<T> {
|
||||
// return requestContext.run(context, fn);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 获取当前上下文中的 JWT
|
||||
* @returns JWT token 或 undefined
|
||||
*/
|
||||
// function getContextJWT(): string | undefined {
|
||||
// const context = requestContext.getStore();
|
||||
// return context?.jwt;
|
||||
// }
|
||||
|
||||
/**
|
||||
* PostgresREST 特定的查询参数接口
|
||||
*/
|
||||
@@ -23,6 +58,8 @@ export interface PostgrestParams {
|
||||
[key: string]: unknown;
|
||||
// 自定义头部参数
|
||||
headers?: Record<string, string>;
|
||||
// JWT Token(自动添加到 Authorization 头部)
|
||||
token?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,6 +85,38 @@ function decodeUrlForDisplay(url: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并 JWT Token 到请求头
|
||||
* @param existingHeaders 已有的请求头
|
||||
* @param explicitToken 显式传入的 JWT Token(可选)
|
||||
* @returns 合并后的请求头
|
||||
*/
|
||||
function mergeAuthHeaders(
|
||||
existingHeaders: Record<string, string> = {},
|
||||
explicitToken?: string
|
||||
): Record<string, string> {
|
||||
const headers = { ...existingHeaders };
|
||||
|
||||
// 如果已经有 Authorization 头部(不区分大小写),不覆盖
|
||||
const hasAuth = Object.keys(headers).some(
|
||||
key => key.toLowerCase() === 'authorization'
|
||||
);
|
||||
|
||||
if (hasAuth) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
// 优先使用显式传入的 token,否则从上下文获取
|
||||
const token = explicitToken || 'undefined';
|
||||
|
||||
// 如果有 token(显式传入或从上下文获取),添加到 Authorization 头部
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印 PostgREST 查询日志
|
||||
* @param endpoint 端点
|
||||
@@ -167,8 +236,8 @@ export function transformParams(params: PostgrestParams): QueryParams {
|
||||
|
||||
// 处理其他额外参数
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
// 跳过已处理的特殊参数
|
||||
if (!['select', 'order', 'limit', 'offset', 'filter', 'schema', 'or'].includes(key) && value !== undefined) {
|
||||
// 跳过已处理的特殊参数(包括 headers 和 token)
|
||||
if (!['select', 'order', 'limit', 'offset', 'filter', 'schema', 'or', 'headers', 'token'].includes(key) && value !== undefined) {
|
||||
result[key] = value as string | number | boolean;
|
||||
}
|
||||
});
|
||||
@@ -179,7 +248,7 @@ export function transformParams(params: PostgrestParams): QueryParams {
|
||||
/**
|
||||
* 发送 GET 请求到 PostgresREST 接口
|
||||
* @param endpoint 端点
|
||||
* @param params 查询参数
|
||||
* @param params 查询参数(可包含 token 和 headers)
|
||||
* @returns 响应数据
|
||||
*/
|
||||
export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams): Promise<{data: T; headers?: Record<string, string>; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
@@ -191,13 +260,8 @@ export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams
|
||||
// 打印查询信息
|
||||
logPostgrestQuery(apiEndpoint, queryParams, 'GET');
|
||||
|
||||
// 提取并移除自定义头部参数
|
||||
const headers: Record<string, string> = params?.headers || {};
|
||||
|
||||
// 清除查询参数中的headers属性,避免将其作为URL参数
|
||||
if (queryParams.headers) {
|
||||
delete queryParams.headers;
|
||||
}
|
||||
// 合并 JWT Token 到请求头
|
||||
const headers = mergeAuthHeaders(params?.headers, params?.token);
|
||||
|
||||
const response = await apiRequest<T>(
|
||||
apiEndpoint,
|
||||
@@ -215,7 +279,7 @@ export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams
|
||||
// 返回数据和响应头
|
||||
return {
|
||||
data: response.data as T,
|
||||
headers: response.headers // 假设apiRequest函数已返回响应头
|
||||
headers: response.headers
|
||||
};
|
||||
} catch (error) {
|
||||
const apiError = handleApiError(error);
|
||||
@@ -314,9 +378,10 @@ function handlePostgresError(error: unknown, responseText?: string): { message:
|
||||
* 发送 POST 请求到 PostgresREST 接口
|
||||
* @param endpoint 端点(表名)
|
||||
* @param data 请求体数据
|
||||
* @param token JWT Token(可选)
|
||||
* @returns 响应数据
|
||||
*/
|
||||
export async function postgrestPost<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
export async function postgrestPost<T, D = Record<string, unknown>>(endpoint: string, data: D, token?: string): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 确保端点没有前导斜杠
|
||||
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
|
||||
@@ -332,17 +397,20 @@ export async function postgrestPost<T, D = Record<string, unknown>>(endpoint: st
|
||||
// console.log(`准备发送 PostgreSQL 插入请求到: ${apiEndpoint}`);
|
||||
// console.log(`请求体: ${requestBody}`);
|
||||
|
||||
// 合并 JWT Token 到请求头
|
||||
const headers = mergeAuthHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Prefer': 'return=representation'
|
||||
}, token);
|
||||
|
||||
try {
|
||||
const response = await apiRequest<T>(
|
||||
apiEndpoint,
|
||||
{
|
||||
method: 'POST',
|
||||
body: requestBody,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Prefer': 'return=representation'
|
||||
}
|
||||
headers: headers
|
||||
}
|
||||
);
|
||||
|
||||
@@ -434,12 +502,14 @@ function preprocessData(data: Record<string, unknown>): Record<string, unknown>
|
||||
* @param endpoint 端点
|
||||
* @param data 请求体数据
|
||||
* @param filters 过滤条件
|
||||
* @param token JWT Token(可选)
|
||||
* @returns 响应数据
|
||||
*/
|
||||
export async function postgrestPut<T, D extends object>(
|
||||
endpoint: string,
|
||||
data: D,
|
||||
filters?: Record<string | number, string | number>
|
||||
filters?: Record<string | number, string | number>,
|
||||
token?: string
|
||||
): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 确保端点没有前导斜杠
|
||||
@@ -459,14 +529,17 @@ export async function postgrestPut<T, D extends object>(
|
||||
// 打印查询信息
|
||||
logPostgrestQuery(fullEndpoint, queryParams, 'PATCH', data as unknown as Record<string, unknown>);
|
||||
|
||||
// 合并 JWT Token 到请求头
|
||||
const headers = mergeAuthHeaders({
|
||||
'Prefer': 'return=representation'
|
||||
}, token);
|
||||
|
||||
const response = await apiRequest<T>(
|
||||
fullEndpoint,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Prefer': 'return=representation'
|
||||
}
|
||||
headers: headers
|
||||
},
|
||||
queryParams
|
||||
);
|
||||
@@ -489,7 +562,7 @@ export async function postgrestPut<T, D extends object>(
|
||||
/**
|
||||
* 发送 DELETE 请求到 PostgresREST 接口
|
||||
* @param endpoint 端点
|
||||
* @param params 查询参数,用于指定要删除的记录
|
||||
* @param params 查询参数,用于指定要删除的记录(可包含 token 和 headers)
|
||||
* @returns 响应数据
|
||||
*/
|
||||
export async function postgrestDelete<T>(endpoint: string, params?: PostgrestParams): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
@@ -500,16 +573,11 @@ export async function postgrestDelete<T>(endpoint: string, params?: PostgrestPar
|
||||
// 转换查询参数
|
||||
const queryParams = params ? transformParams(params) : {};
|
||||
|
||||
// 提取并移除自定义头部参数
|
||||
const headers: Record<string, string> = {
|
||||
// 合并 JWT Token 到请求头
|
||||
const headers = mergeAuthHeaders({
|
||||
'Prefer': 'return=representation', // 默认请求返回被删除的记录
|
||||
...(params?.headers || {})
|
||||
};
|
||||
|
||||
// 清除查询参数中的headers属性,避免将其作为URL参数
|
||||
if (queryParams.headers) {
|
||||
delete queryParams.headers;
|
||||
}
|
||||
}, params?.token);
|
||||
|
||||
// 打印查询信息
|
||||
logPostgrestQuery(apiEndpoint, queryParams, 'DELETE');
|
||||
|
||||
+20
-10
@@ -113,9 +113,10 @@ export function convertToUITemplate(template: PromptTemplate): PromptTemplateUI
|
||||
/**
|
||||
* 获取提示词模板列表
|
||||
* @param searchParams 搜索参数
|
||||
* @param frontendJWT JWT token (可选)
|
||||
* @returns 提示词模板列表和总数
|
||||
*/
|
||||
export async function getPromptTemplates(searchParams: PromptSearchParams = {}): Promise<{
|
||||
export async function getPromptTemplates(searchParams: PromptSearchParams = {}, frontendJWT?: string): Promise<{
|
||||
data?: { templates: PromptTemplateUI[], total: number };
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -147,7 +148,8 @@ export async function getPromptTemplates(searchParams: PromptSearchParams = {}):
|
||||
},
|
||||
limit: pageSize,
|
||||
offset: (page - 1) * pageSize,
|
||||
filter: {} as Record<string, string>
|
||||
filter: {} as Record<string, string>,
|
||||
token: frontendJWT
|
||||
};
|
||||
|
||||
// 添加筛选条件
|
||||
@@ -226,9 +228,10 @@ export async function getPromptTemplates(searchParams: PromptSearchParams = {}):
|
||||
/**
|
||||
* 获取提示词模板详情
|
||||
* @param id 模板ID
|
||||
* @param frontendJWT JWT token (可选)
|
||||
* @returns 提示词模板详情
|
||||
*/
|
||||
export async function getPromptTemplate(id: string): Promise<{
|
||||
export async function getPromptTemplate(id: string, frontendJWT?: string): Promise<{
|
||||
data?: PromptTemplateUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -254,7 +257,8 @@ export async function getPromptTemplate(id: string): Promise<{
|
||||
`,
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
|
||||
const response = await postgrestGet<PromptTemplate[]>('prompt_templates', params);
|
||||
@@ -282,9 +286,10 @@ export async function getPromptTemplate(id: string): Promise<{
|
||||
/**
|
||||
* 创建提示词模板
|
||||
* @param template 提示词模板数据
|
||||
* @param frontendJWT JWT token (可选)
|
||||
* @returns 创建的提示词模板
|
||||
*/
|
||||
export async function createPromptTemplate(template: Partial<PromptTemplateUI>): Promise<{
|
||||
export async function createPromptTemplate(template: Partial<PromptTemplateUI>, frontendJWT?: string): Promise<{
|
||||
data?: PromptTemplateUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -326,7 +331,8 @@ export async function createPromptTemplate(template: Partial<PromptTemplateUI>):
|
||||
|
||||
const response = await postgrestPost<PromptTemplate, Partial<PromptTemplate>>(
|
||||
'prompt_templates',
|
||||
apiTemplate
|
||||
apiTemplate,
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -353,9 +359,10 @@ export async function createPromptTemplate(template: Partial<PromptTemplateUI>):
|
||||
* 更新提示词模板
|
||||
* @param id 模板ID
|
||||
* @param template 提示词模板数据
|
||||
* @param frontendJWT JWT token (可选)
|
||||
* @returns 更新后的提示词模板
|
||||
*/
|
||||
export async function updatePromptTemplate(id: string, template: Partial<PromptTemplateUI>): Promise<{
|
||||
export async function updatePromptTemplate(id: string, template: Partial<PromptTemplateUI>, frontendJWT?: string): Promise<{
|
||||
data?: PromptTemplateUI;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -416,7 +423,8 @@ export async function updatePromptTemplate(id: string, template: Partial<PromptT
|
||||
const response = await postgrestPut<PromptTemplate, Partial<PromptTemplate>>(
|
||||
'prompt_templates',
|
||||
apiTemplate,
|
||||
{ id }
|
||||
{ id },
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -442,9 +450,10 @@ export async function updatePromptTemplate(id: string, template: Partial<PromptT
|
||||
/**
|
||||
* 删除提示词模板
|
||||
* @param id 模板ID
|
||||
* @param frontendJWT JWT token (可选)
|
||||
* @returns 成功或失败信息
|
||||
*/
|
||||
export async function deletePromptTemplate(id: string): Promise<{
|
||||
export async function deletePromptTemplate(id: string, frontendJWT?: string): Promise<{
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
status?: number;
|
||||
@@ -460,7 +469,8 @@ export async function deletePromptTemplate(id: string): Promise<{
|
||||
{
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function getConfigLists(params: {
|
||||
is_active?: boolean;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<{data: ConfigItem[]; total: number; error?: never} | {data?: never; error: string}> {
|
||||
}, token?: string): Promise<{data: ConfigItem[]; total: number; error?: never} | {data?: never; error: string}> {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
@@ -90,7 +90,7 @@ export async function getConfigLists(params: {
|
||||
queryParams.filter = filter;
|
||||
|
||||
// 获取数据
|
||||
const response = await postgrestGet<ConfigItem[]>('configurations', queryParams);
|
||||
const response = await postgrestGet<ConfigItem[]>('configurations', { ...queryParams, token });
|
||||
|
||||
if (response.error) {
|
||||
return { error: response.error };
|
||||
@@ -132,11 +132,12 @@ export async function getConfigLists(params: {
|
||||
}
|
||||
|
||||
// 获取配置类型和环境选项
|
||||
export async function getConfigOptions(): Promise<{data: {types: string[]; environments: string[]}; error?: never} | {data?: never; error: string}> {
|
||||
export async function getConfigOptions(token?: string): Promise<{data: {types: string[]; environments: string[]}; error?: never} | {data?: never; error: string}> {
|
||||
try {
|
||||
// 获取类型选项
|
||||
const typeResponse = await postgrestGet<{type: string}[]>('configurations', {
|
||||
select: 'type'
|
||||
select: 'type',
|
||||
token
|
||||
});
|
||||
|
||||
if (typeResponse.error) {
|
||||
@@ -150,7 +151,8 @@ export async function getConfigOptions(): Promise<{data: {types: string[]; envir
|
||||
|
||||
// 获取环境选项
|
||||
const envResponse = await postgrestGet<{environment: string}[]>('configurations', {
|
||||
select: 'environment'
|
||||
select: 'environment',
|
||||
token
|
||||
});
|
||||
|
||||
if (envResponse.error) {
|
||||
@@ -179,12 +181,13 @@ export async function getConfigOptions(): Promise<{data: {types: string[]; envir
|
||||
}
|
||||
|
||||
// 获取配置详情
|
||||
export async function getConfigDetail(id: string): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> {
|
||||
export async function getConfigDetail(id: string, token?: string): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> {
|
||||
try {
|
||||
const response = await postgrestGet<ConfigItem[]>('configurations', {
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
@@ -218,9 +221,9 @@ export async function createConfig(data: {
|
||||
config: Record<string, unknown>;
|
||||
is_active: boolean;
|
||||
remark?: string;
|
||||
}): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> {
|
||||
}, token?: string): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> {
|
||||
try {
|
||||
const response = await postgrestPost<ConfigItem, typeof data>('configurations', data);
|
||||
const response = await postgrestPost<ConfigItem, typeof data>('configurations', data, token);
|
||||
|
||||
if (response.error) {
|
||||
return { error: response.error };
|
||||
@@ -252,11 +255,11 @@ export async function updateConfig(id: string, data: {
|
||||
config: Record<string, unknown>;
|
||||
is_active: boolean;
|
||||
remark?: string;
|
||||
}): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> {
|
||||
}, token?: string): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> {
|
||||
try {
|
||||
const response = await postgrestPut<ConfigItem, typeof data>('configurations', data, {
|
||||
id: id.toString()
|
||||
});
|
||||
}, token);
|
||||
|
||||
if (response.error) {
|
||||
return { error: response.error };
|
||||
@@ -281,12 +284,13 @@ export async function updateConfig(id: string, data: {
|
||||
}
|
||||
|
||||
// 更新配置状态
|
||||
export async function updateConfigStatus(id: number, is_active: boolean): Promise<{success: boolean; error?: string}> {
|
||||
export async function updateConfigStatus(id: number, is_active: boolean, token?: string): Promise<{success: boolean; error?: string}> {
|
||||
try {
|
||||
const response = await postgrestPut<ConfigItem, {is_active: boolean}>(
|
||||
'configurations',
|
||||
{ is_active },
|
||||
{ id: id.toString() }
|
||||
{ id: id.toString() },
|
||||
token
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -27,12 +27,13 @@ interface DocumentListModalProps {
|
||||
total?: number;
|
||||
onPageChange?: (page: number) => void;
|
||||
onPageSizeChange?: (size: number) => void;
|
||||
frontendJWT?: string; // 新增JWT参数
|
||||
}
|
||||
|
||||
export function DocumentListModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
export function DocumentListModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
files,
|
||||
onViewFile,
|
||||
loading = false,
|
||||
@@ -41,7 +42,8 @@ export function DocumentListModal({
|
||||
pageSize = 10,
|
||||
total = 0,
|
||||
onPageChange,
|
||||
onPageSizeChange
|
||||
onPageSizeChange,
|
||||
frontendJWT
|
||||
}: DocumentListModalProps) {
|
||||
// 查看按钮防抖
|
||||
const [isnavigating,setIsnavigating] = useState(false)
|
||||
@@ -58,9 +60,8 @@ export function DocumentListModal({
|
||||
// 检查audit_status是否为0,如果是则更新为2
|
||||
if (auditStatus === 0 || auditStatus === null) {
|
||||
try {
|
||||
// TODO: 不需要传递userId,直接使用fileId找到对应文档,然后更新文档状态
|
||||
// 更新文档状态
|
||||
const updatedFile = await updateDocumentAuditStatus(fileId, 2);
|
||||
// 更新文档状态,传递JWT
|
||||
const updatedFile = await updateDocumentAuditStatus(fileId, 2, frontendJWT);
|
||||
console.log('更新后的文档状态:', updatedFile);
|
||||
} catch (error) {
|
||||
console.error('更新文件审核状态时出错:', error);
|
||||
@@ -68,7 +69,7 @@ export function DocumentListModal({
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果有自定义的查看处理函数,则调用它
|
||||
if (onViewFile) {
|
||||
setIsnavigating(true)
|
||||
|
||||
@@ -410,7 +410,7 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
||||
}}
|
||||
>
|
||||
<Document
|
||||
file={DOCUMENT_URL+real_path}
|
||||
file={`/api/pdf-proxy?path=${encodeURIComponent(real_path)}`}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
onLoadError={(error) => {
|
||||
console.error("PDF加载错误:", error);
|
||||
|
||||
@@ -18,6 +18,7 @@ const REVIEW_TYPE_TO_APP: Record<string, AppModule> = {
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
userRole?: UserRole;
|
||||
frontendJWT?: string;
|
||||
}
|
||||
|
||||
// 添加一个接口表示路由handle可能包含的属性
|
||||
@@ -32,7 +33,7 @@ interface Match {
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export function Layout({ children, userRole = 'developer' }: LayoutProps) {
|
||||
export function Layout({ children, userRole = 'developer' as UserRole, frontendJWT = '' }: LayoutProps) {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [selectedApp, setSelectedApp] = useState<AppModule>('');
|
||||
const matches = useMatches() as Match[];
|
||||
@@ -108,6 +109,7 @@ export function Layout({ children, userRole = 'developer' }: LayoutProps) {
|
||||
onToggle={toggleSidebar}
|
||||
userRole={userRole}
|
||||
selectedApp={selectedApp}
|
||||
frontendJWT={frontendJWT}
|
||||
/>
|
||||
|
||||
<div className={`main-content ${sidebarCollapsed ? 'sidebar-collapsed' : ''}`}>
|
||||
|
||||
@@ -7,6 +7,7 @@ interface SidebarProps {
|
||||
onToggle: () => void;
|
||||
collapsed: boolean;
|
||||
userRole: UserRole;
|
||||
frontendJWT?: string;
|
||||
selectedApp?: string; // 添加所选应用模块参数
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ const APP_ICON_MAP: Record<string, string> = {
|
||||
'model': '/images/icon_assistant.png'
|
||||
};
|
||||
|
||||
export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: SidebarProps) {
|
||||
export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selectedApp = '' }: SidebarProps) {
|
||||
const location = useLocation();
|
||||
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
|
||||
const [currentApp, setCurrentApp] = useState<string>(''); // 初始设置为空字符串而不是selectedApp
|
||||
@@ -47,7 +48,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: Sid
|
||||
try {
|
||||
console.log('userRole', userRole);
|
||||
const roleKey = mapUserRoleToRoleKey(userRole);
|
||||
const result = await getUserRoutesByRole(roleKey);
|
||||
const result = await getUserRoutesByRole(roleKey, frontendJWT);
|
||||
|
||||
if (result.success && result.data) {
|
||||
setMenuItems(result.data);
|
||||
@@ -253,7 +254,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: Sid
|
||||
// })
|
||||
|
||||
return (
|
||||
<div className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
|
||||
<div className={`sidebar ${collapsed ? 'collapsed' : ''} flex flex-col`}>
|
||||
<div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center">
|
||||
<div className="flex items-center"
|
||||
onClick={() => {
|
||||
@@ -300,7 +301,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: Sid
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="py-4 px-[10px]">
|
||||
<div className="py-4 px-[10px] flex-1 overflow-y-auto sidebar-scroll-area">
|
||||
{isLoading || isLoadingRoutes ? (
|
||||
// 加载中状态显示,保留菜单布局结构
|
||||
<div className="py-2">
|
||||
@@ -382,6 +383,19 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: Sid
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作手册下载按钮 */}
|
||||
<div className="mt-auto px-4 py-1 border-t border-gray-100">
|
||||
<a
|
||||
href="/智慧法务平台操作手册.pdf"
|
||||
download="智慧法务平台操作手册.pdf"
|
||||
className={`flex items-center ${collapsed ? 'justify-center' : ''} text-gray-600 hover:text-green-700 transition-colors duration-200`}
|
||||
title="下载操作手册"
|
||||
>
|
||||
<i className={`ri-question-line ${collapsed ? 'text-base' : 'text-base mr-3'}`}></i>
|
||||
{!collapsed && <span className="text-base">操作手册</span>}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -414,7 +414,7 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
||||
}}
|
||||
>
|
||||
<Document
|
||||
file={DOCUMENT_URL+real_path}
|
||||
file={`/api/pdf-proxy?path=${encodeURIComponent(real_path)}`}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
onLoadError={(error) => {
|
||||
console.error("PDF加载错误:", error);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Modal } from '~/components/ui/Modal';
|
||||
import { UploadArea, type UploadAreaRef } from '~/components/ui/UploadArea';
|
||||
import { Button } from '~/components/ui/Button';
|
||||
import { toastService } from '~/components/ui/Toast';
|
||||
import { DOCUMENT_URL } from "~/api/axios-client";
|
||||
// import { DOCUMENT_URL } from "~/api/axios-client";
|
||||
import { uploadFileToBinary, uploadDocumentToServer } from '~/api/files/files-upload';
|
||||
|
||||
interface ReviewTabsProps {
|
||||
@@ -61,7 +61,8 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
|
||||
// 下载原文件
|
||||
const handleDownloadFile = async () => {
|
||||
try {
|
||||
const downloadUrl = `${DOCUMENT_URL}${fileInfo.path}`;
|
||||
// 使用 PDF 代理路由获取文件,自动添加 JWT 认证
|
||||
const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(fileInfo.path || '')}`;
|
||||
|
||||
// 使用fetch获取文件内容
|
||||
const response = await fetch(downloadUrl);
|
||||
|
||||
+38
-27
@@ -3,7 +3,6 @@
|
||||
* 统一管理所有API地址,方便部署时修改
|
||||
* 支持环境变量覆盖配置
|
||||
*/
|
||||
|
||||
// 环境配置类型
|
||||
interface ApiConfig {
|
||||
// 主API基础URL
|
||||
@@ -73,9 +72,12 @@ const portConfigs: Record<string, Partial<ApiConfig>> = {
|
||||
// 主要
|
||||
// 梅州
|
||||
'51703': {
|
||||
baseUrl: 'http://nas.7bm.co:8873',
|
||||
documentUrl: 'http://nas.7bm.co:8873/docauditai/',
|
||||
uploadUrl: 'http://nas.7bm.co:8873/admin/documents'
|
||||
baseUrl: 'http://172.16.0.55:8073',
|
||||
documentUrl: 'http://172.16.0.55:8073/docauditai/',
|
||||
uploadUrl: 'http://172.16.0.55:8073/admin/documents'
|
||||
// baseUrl: 'http://nas.7bm.co:8873',
|
||||
// documentUrl: 'http://nas.7bm.co:8873/docauditai/',
|
||||
// uploadUrl: 'http://nas.7bm.co:8873/admin/documents'
|
||||
},
|
||||
|
||||
|
||||
@@ -121,17 +123,12 @@ const configs: Record<string, ApiConfig> = {
|
||||
// 开发环境
|
||||
development: {
|
||||
baseUrl: 'http://172.16.0.55:8000',
|
||||
// baseUrl: 'http://172.16.0.81:3000',
|
||||
// baseUrl: 'http://nas.7bm.co:3000',
|
||||
// documentUrl: 'http://172.16.0.81:9000/docauditai/',
|
||||
documentUrl: 'http://172.16.0.55:8000/docauditai/',
|
||||
uploadUrl: 'http://172.16.0.55:8000/admin/documents',
|
||||
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
||||
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
||||
oauth: {
|
||||
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
||||
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
|
||||
clientSecret: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb', // 需要替换为实际的Client Secret
|
||||
clientId: 'none',
|
||||
clientSecret: 'none', // 需要替换为实际的Client Secret
|
||||
redirectUri: 'http://10.79.97.17/', // 回调地址
|
||||
appId: 'idaasoauth2' // 应用ID,用于登出
|
||||
}
|
||||
@@ -140,17 +137,12 @@ const configs: Record<string, ApiConfig> = {
|
||||
// 测试环境
|
||||
testing: {
|
||||
baseUrl: 'http://nas.7bm.co:8873',
|
||||
// baseUrl: 'http://172.16.0.58:8873',
|
||||
// baseUrl: 'http://nas.7bm.co:3000',
|
||||
// documentUrl: 'http://172.16.0.81:9000/docauditai/',
|
||||
documentUrl: 'http://nas.7bm.co:8873/docauditai/',
|
||||
uploadUrl: 'http://nas.7bm.co:8873/admin/documents',
|
||||
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
||||
// uploadUrl: 'http://172.16.0.58:8008/admin/documents',
|
||||
oauth: {
|
||||
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
||||
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
|
||||
clientSecret: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb', // 需要替换为实际的Client Secret
|
||||
clientSecret: 'placeholder', // 需要替换为实际的Client Secret
|
||||
redirectUri: 'http://10.79.97.17/', // 回调地址
|
||||
appId: 'idaasoauth2' // 应用ID,用于登出
|
||||
}
|
||||
@@ -159,7 +151,6 @@ const configs: Record<string, ApiConfig> = {
|
||||
// 生产环境
|
||||
production: {
|
||||
// postgrest
|
||||
// baseUrl: 'http://172.16.0.55:8008',
|
||||
baseUrl: 'http://10.79.97.17:8000',
|
||||
// minio
|
||||
documentUrl: 'http://10.76.244.156:9000/docauditai/',
|
||||
@@ -168,7 +159,10 @@ const configs: Record<string, ApiConfig> = {
|
||||
oauth: {
|
||||
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
||||
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
|
||||
clientSecret: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb', // 需要替换为实际的Client Secret
|
||||
// clientSecret: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb', // 需要替换为实际的Client Secret
|
||||
// ⚠️ 安全警告:clientSecret 不应该硬编码在代码中
|
||||
// 请在生产环境使用环境变量 OAUTH_CLIENT_SECRET
|
||||
clientSecret: 'placeholder', // 占位符,实际值从环境变量获取
|
||||
redirectUri: 'http://10.79.97.17/', // 回调地址
|
||||
appId: 'idaasoauth2' // 应用ID,用于登出
|
||||
}
|
||||
@@ -181,7 +175,7 @@ const configs: Record<string, ApiConfig> = {
|
||||
uploadUrl: 'http://172.16.0.119:8000/admin/documents/upload',
|
||||
oauth: {
|
||||
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
||||
clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO', // 需要替换为实际的Client ID
|
||||
clientId: 'none', // 需要替换为实际的Client ID
|
||||
clientSecret: 'your_client_secret', // 需要替换为实际的Client Secret
|
||||
redirectUri: 'http://172.16.0.119:3000/callback', // 回调地址
|
||||
appId: 'idaasoauth2' // 应用ID,用于登出
|
||||
@@ -204,13 +198,11 @@ const getCurrentEnvironment = (): string => {
|
||||
|
||||
// 客户端:优先使用NEXT_PUBLIC_前缀的环境变量
|
||||
const nextPublicNodeEnv = process.env.NEXT_PUBLIC_NODE_ENV;
|
||||
const nextPublicEnv = process.env.NEXT_PUBLIC_API_ENV;
|
||||
const nodeEnv = process.env.NODE_ENV;
|
||||
const result = nextPublicNodeEnv || nextPublicEnv || nodeEnv || 'development';
|
||||
const result = nextPublicNodeEnv || nodeEnv || 'development';
|
||||
|
||||
console.log('🔧 客户端环境检测:', {
|
||||
NEXT_PUBLIC_NODE_ENV: nextPublicNodeEnv,
|
||||
NEXT_PUBLIC_API_ENV: nextPublicEnv,
|
||||
NODE_ENV: nodeEnv,
|
||||
result: result
|
||||
});
|
||||
@@ -227,7 +219,9 @@ const getConfigFromEnv = (defaultConfig: ApiConfig): ApiConfig => {
|
||||
oauth: {
|
||||
serverUrl: process.env.NEXT_PUBLIC_OAUTH_SERVER_URL || defaultConfig.oauth.serverUrl,
|
||||
clientId: process.env.NEXT_PUBLIC_OAUTH_CLIENT_ID || defaultConfig.oauth.clientId,
|
||||
clientSecret: process.env.NEXT_PUBLIC_OAUTH_CLIENT_SECRET || defaultConfig.oauth.clientSecret,
|
||||
// ⚠️ 注意:clientSecret 不应该使用 NEXT_PUBLIC_ 前缀
|
||||
// 应该只在服务器端通过 process.env.OAUTH_CLIENT_SECRET 访问
|
||||
clientSecret: process.env.OAUTH_CLIENT_SECRET || defaultConfig.oauth.clientSecret,
|
||||
redirectUri: process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI || defaultConfig.oauth.redirectUri,
|
||||
appId: process.env.NEXT_PUBLIC_OAUTH_APP_ID || defaultConfig.oauth.appId
|
||||
}
|
||||
@@ -355,6 +349,18 @@ export const {
|
||||
oauth: OAUTH_CONFIG
|
||||
} = apiConfig;
|
||||
|
||||
/**
|
||||
* 🔓 客户端安全的 OAuth 配置(不包含 clientSecret)
|
||||
* 可以安全地在客户端代码中使用
|
||||
*/
|
||||
export const CLIENT_OAUTH_CONFIG = {
|
||||
serverUrl: OAUTH_CONFIG.serverUrl,
|
||||
clientId: OAUTH_CONFIG.clientId,
|
||||
redirectUri: OAUTH_CONFIG.redirectUri,
|
||||
appId: OAUTH_CONFIG.appId,
|
||||
// 客户端不需要 clientSecret
|
||||
};
|
||||
|
||||
// 导出所有配置,供调试使用
|
||||
export { configs };
|
||||
|
||||
@@ -378,12 +384,17 @@ export const getCurrentPortConfig = () => {
|
||||
};
|
||||
|
||||
// 调试信息(仅在开发环境显示)
|
||||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
|
||||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'testing') {
|
||||
console.log('📦 API配置信息:', {
|
||||
environment: getCurrentEnvironment(),
|
||||
currentEnv: process.env.NODE_ENV,
|
||||
nextPublicEnv: process.env.NEXT_PUBLIC_API_ENV,
|
||||
port: getCurrentPort(),
|
||||
config: apiConfig
|
||||
config: {
|
||||
...apiConfig,
|
||||
oauth: {
|
||||
...apiConfig.oauth,
|
||||
clientSecret: '***' // 隐藏敏感信息
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 🔒 服务器端专用:OAuth 敏感配置
|
||||
*
|
||||
* 此文件只在服务器端运行,确保环境变量在运行时读取
|
||||
* Remix 会自动排除 .server.ts 文件不打包到客户端
|
||||
*/
|
||||
|
||||
// 用于控制日志输出(避免重复日志)
|
||||
let hasLoggedSecret = false;
|
||||
|
||||
/**
|
||||
* 从环境变量获取 OAuth Client Secret
|
||||
* 在服务器运行时动态读取,不依赖构建时的环境变量注入
|
||||
*/
|
||||
export function getOAuthClientSecret(): string {
|
||||
const secret = process.env.OAUTH_CLIENT_SECRET;
|
||||
|
||||
// 只在第一次调用时输出详细日志(避免启动时就输出)
|
||||
if (!hasLoggedSecret) {
|
||||
hasLoggedSecret = true;
|
||||
|
||||
console.log('🔍 [oauth-secret.server] 读取 OAUTH_CLIENT_SECRET:');
|
||||
console.log(' - 值存在:', !!secret);
|
||||
console.log(' - 值长度:', secret?.length || 0);
|
||||
console.log(' - 值预览:', secret ? `${secret.substring(0, 10)}...` : 'undefined');
|
||||
console.log(' - 是否为占位符:', secret === 'placeholder' || secret === 'none');
|
||||
|
||||
if (!secret || secret === 'placeholder' || secret === 'none') {
|
||||
console.warn('⚠️ 警告:未设置有效的 OAUTH_CLIENT_SECRET 环境变量');
|
||||
console.warn('⚠️ 当前值:', secret);
|
||||
console.warn('⚠️ 包含 OAUTH 的环境变量:', Object.keys(process.env).filter(k => k.includes('OAUTH')));
|
||||
console.warn('⚠️ 包含 SECRET 的环境变量:', Object.keys(process.env).filter(k => k.includes('SECRET')));
|
||||
} else {
|
||||
console.log('✅ [oauth-secret.server] OAUTH_CLIENT_SECRET 已成功读取');
|
||||
}
|
||||
}
|
||||
|
||||
return secret || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器端 OAuth 配置
|
||||
*/
|
||||
export function getServerOAuthConfigRuntime() {
|
||||
const secret = getOAuthClientSecret();
|
||||
|
||||
// 从基础配置中获取其他 OAuth 参数
|
||||
const baseConfig = {
|
||||
serverUrl: process.env.NEXT_PUBLIC_OAUTH_SERVER_URL || 'http://10.79.112.85',
|
||||
clientId: process.env.NEXT_PUBLIC_OAUTH_CLIENT_ID || '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
|
||||
redirectUri: process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI || 'http://10.79.97.17/',
|
||||
appId: process.env.NEXT_PUBLIC_OAUTH_APP_ID || 'idaasoauth2',
|
||||
};
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
clientSecret: secret
|
||||
};
|
||||
}
|
||||
|
||||
+4
-3
@@ -75,7 +75,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const isPublicPath = publicPaths.some(path => pathname.startsWith(path));
|
||||
|
||||
// 获取用户会话(可能包含刷新后的token)
|
||||
const { isAuthenticated, userRole, refreshedSession } = await getUserSession(request);
|
||||
const { isAuthenticated, userRole, refreshedSession, frontendJWT } = await getUserSession(request);
|
||||
// console.log("是否公开路径:", isPublicPath, "是否已认证:", isAuthenticated);
|
||||
|
||||
// 如果访问需要认证的路径但未登录,重定向到登录页
|
||||
@@ -145,6 +145,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
isAuthenticated,
|
||||
userRole,
|
||||
pathname,
|
||||
frontendJWT,
|
||||
ENV: {
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
|
||||
NEXT_PUBLIC_APP_ID: process.env.NEXT_PUBLIC_APP_ID,
|
||||
@@ -182,7 +183,7 @@ export function links() {
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const { userRole, ENV } = useLoaderData<typeof loader>();
|
||||
const { userRole, ENV, frontendJWT } = useLoaderData<typeof loader>();
|
||||
|
||||
|
||||
return (
|
||||
@@ -215,7 +216,7 @@ export default function App() {
|
||||
<body className="font-sans">
|
||||
<MessageModalProvider>
|
||||
<ToastProvider>
|
||||
<Layout userRole={userRole}>
|
||||
<Layout userRole={userRole} frontendJWT={frontendJWT}>
|
||||
<Outlet />
|
||||
</Layout>
|
||||
<RouteChangeLoader />
|
||||
|
||||
@@ -51,8 +51,8 @@ export default function Index() {
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// setIsPort51708(window.location.port === '51708');
|
||||
setIsPort51708(window.location.port === '5178');
|
||||
setIsPort51708(window.location.port === '51708');
|
||||
// setIsPort51708(window.location.port === '5178');
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type ActionFunctionArgs, json } from "@remix-run/node";
|
||||
import { OAuthClient } from "~/api/login/oauth-client";
|
||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||
import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server";
|
||||
|
||||
/**
|
||||
* 这个Action作为获取OAuth Access Token的服务器端代理。
|
||||
@@ -24,7 +24,8 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
console.log("🔧 [/api/oauth/token] 收到代理请求, code:", code ? `${code.substring(0, 10)}...` : null);
|
||||
|
||||
// 3. 在服务器端使用OAuthClient安全地获取访问令牌
|
||||
const oauthClient = new OAuthClient(OAUTH_CONFIG);
|
||||
// 🔒 安全:从 .server.ts 文件运行时读取配置,确保环境变量正确加载
|
||||
const oauthClient = new OAuthClient(getServerOAuthConfigRuntime());
|
||||
const tokenResponse = await oauthClient.getAccessToken(code);
|
||||
|
||||
// 4. 处理来自IDaaS服务器的响应
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type ActionFunctionArgs, json } from "@remix-run/node";
|
||||
import { OAuthClient } from "~/api/login/oauth-client";
|
||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||
import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server";
|
||||
|
||||
/**
|
||||
* 这个Action作为获取用户信息的服务器端代理。
|
||||
@@ -20,7 +20,8 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
|
||||
console.log("🔧 [/api/oauth/userinfo] 收到代理请求。");
|
||||
|
||||
const oauthClient = new OAuthClient(OAUTH_CONFIG);
|
||||
// 🔒 安全:从 .server.ts 文件运行时读取配置
|
||||
const oauthClient = new OAuthClient(getServerOAuthConfigRuntime());
|
||||
const userInfoResponse = await oauthClient.getUserInfo(accessToken);
|
||||
|
||||
if (!userInfoResponse || !userInfoResponse.success) {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { getUserSession } from "~/api/login/auth.server";
|
||||
import { DOCUMENT_URL } from "~/api/axios-client";
|
||||
|
||||
/**
|
||||
* PDF 代理路由
|
||||
* 用于在服务器端添加 JWT 认证后获取 PDF 文件
|
||||
*/
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
// 获取 JWT token
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 从查询参数获取文件路径
|
||||
const url = new URL(request.url);
|
||||
const filePath = url.searchParams.get("path");
|
||||
|
||||
if (!filePath) {
|
||||
return new Response("缺少文件路径参数", { status: 400 });
|
||||
}
|
||||
|
||||
// 构建完整的文件 URL
|
||||
const fileUrl = `${DOCUMENT_URL}${filePath}`;
|
||||
|
||||
// 使用 JWT 认证获取文件
|
||||
const response = await fetch(fileUrl, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${frontendJWT}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return new Response(`获取文件失败: ${response.statusText}`, {
|
||||
status: response.status
|
||||
});
|
||||
}
|
||||
|
||||
// 获取文件内容
|
||||
const blob = await response.blob();
|
||||
|
||||
// 返回文件,保持原始的 Content-Type
|
||||
return new Response(blob, {
|
||||
headers: {
|
||||
'Content-Type': response.headers.get('Content-Type') || 'application/pdf',
|
||||
'Cache-Control': 'public, max-age=3600', // 缓存1小时
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('PDF 代理错误:', error);
|
||||
return new Response(`服务器错误: ${error instanceof Error ? error.message : '未知错误'}`, {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
}
|
||||
+44
-15
@@ -1,7 +1,21 @@
|
||||
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { createUserSession, saveUserInfo } from "~/api/login/auth.server";
|
||||
import { createUserSession, saveUserInfo, sessionStorage } from "~/api/login/auth.server";
|
||||
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
|
||||
|
||||
/**
|
||||
* 辅助函数:使用 session flash 重定向到登录页面并传递错误信息
|
||||
*/
|
||||
async function redirectToLoginWithError(request: Request, errorMessage: string) {
|
||||
const session = await sessionStorage.getSession(request.headers.get("Cookie"));
|
||||
session.flash("loginError", errorMessage);
|
||||
|
||||
return redirect("/login", {
|
||||
headers: {
|
||||
"Set-Cookie": await sessionStorage.commitSession(session)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const url = new URL(request.url);
|
||||
const origin = url.origin; // 获取请求的源 (e.g., "http://10.79.97.17:51703")
|
||||
@@ -21,19 +35,19 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// 检查是否有错误
|
||||
if (error) {
|
||||
console.error("❌ OAuth2.0授权失败:", error, error_description);
|
||||
return redirect(`/login?error=${encodeURIComponent(error_description || error)}`);
|
||||
return redirectToLoginWithError(request, error_description || error || "授权失败");
|
||||
}
|
||||
|
||||
// 检查是否有授权码
|
||||
if (!code) {
|
||||
console.error("❌ OAuth2.0回调缺少授权码");
|
||||
return redirect("/login?error=missing_code");
|
||||
return redirectToLoginWithError(request, "登录过程中缺少授权码,请重新登录");
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if (!state || !state.endsWith("_idp")) {
|
||||
console.error("❌ OAuth2.0状态值验证失败:", { state, expectedSuffix: "_idp" });
|
||||
return redirect("/login?error=invalid_state");
|
||||
return redirectToLoginWithError(request, "登录状态验证失败,请重新登录");
|
||||
}
|
||||
|
||||
console.log("✅ OAuth2.0回调参数验证通过");
|
||||
@@ -57,7 +71,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
if (!proxyResponse.ok || !tokenResponse.success) {
|
||||
console.error("❌ [Callback] 通过内部代理获取访问令牌失败:", tokenResponse);
|
||||
return redirect("/login?error=token_proxy_error");
|
||||
return redirectToLoginWithError(request, "获取访问令牌失败,请重新登录");
|
||||
}
|
||||
|
||||
// --- 修改结束 ---
|
||||
@@ -78,7 +92,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
if (!userInfoProxyResponse.ok || !userInfoResponse.success) {
|
||||
console.error("❌ [Callback] 通过内部代理获取用户信息失败:", userInfoResponse);
|
||||
return redirect("/login?error=userinfo_proxy_error");
|
||||
return redirectToLoginWithError(request, "获取用户信息失败,请重新登录");
|
||||
}
|
||||
|
||||
// 将代理返回的用户信息包装成与原有一致的结构
|
||||
@@ -95,19 +109,34 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
// 获取重定向URL
|
||||
const redirectTo = url.searchParams.get("redirect") || "/";
|
||||
|
||||
|
||||
// 先生成一个临时 JWT
|
||||
const tempUserInfo = {
|
||||
sub: userInfo.data.sub,
|
||||
user_id: userInfo.data.user_id || "",
|
||||
username: userInfo.data.username,
|
||||
nick_name: userInfo.data.nick_name,
|
||||
email: userInfo.data.email,
|
||||
phone_number: userInfo.data.phone_number,
|
||||
ou_id: userInfo.data.ou_id,
|
||||
ou_name: userInfo.data.ou_name,
|
||||
is_leader: userInfo.data.is_leader,
|
||||
user_role: userRole as 'common' | 'developer'
|
||||
};
|
||||
const tempToken = JWTUtils.generateJWT(tempUserInfo, tokenResponse.expires_in);
|
||||
|
||||
// 成功获取用户信息之后通过auth.server.ts中的saveUserInfo方法去写入自己的数据库中,通过sub作为唯一值去添加数据
|
||||
const saveResult = await saveUserInfo(userInfo.data);
|
||||
const saveResult = await saveUserInfo(userInfo.data, tempToken);
|
||||
if (!saveResult.success) {
|
||||
console.error("保存用户信息到数据库失败:", saveResult.error);
|
||||
// 注意:即使保存到数据库失败,我们仍然继续登录流程,因为用户已经通过了身份验证
|
||||
return redirect("/login?error=save_user_error");
|
||||
return redirectToLoginWithError(request, "保存用户信息失败,请重新登录");
|
||||
}
|
||||
|
||||
|
||||
console.log("用户信息已成功保存到数据库");
|
||||
const savedUserData = saveResult.data!;
|
||||
|
||||
// 生成前端专用JWT
|
||||
|
||||
// 生成前端专用JWT(使用完整的用户信息,包括数据库 ID)
|
||||
const jwtUserInfo: UserInfoForJWT = {
|
||||
sub: userInfo.data.sub,
|
||||
user_id: savedUserData.id!,
|
||||
@@ -120,7 +149,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
is_leader: savedUserData.is_leader,
|
||||
user_role: userRole as 'common' | 'developer'
|
||||
};
|
||||
|
||||
|
||||
const frontendJWT = JWTUtils.generateJWT(jwtUserInfo, tokenResponse.expires_in);
|
||||
console.log("前端JWT已生成");
|
||||
|
||||
@@ -143,7 +172,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// 使用统一的session创建函数
|
||||
return createUserSession({
|
||||
isAuthenticated: true,
|
||||
userRole: userRole as 'common' | 'developer',
|
||||
userRole: userRole,
|
||||
redirectTo,
|
||||
accessToken: tokenResponse.access_token,
|
||||
refreshToken: tokenResponse.refresh_token,
|
||||
@@ -154,7 +183,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
} catch (error) {
|
||||
console.error("OAuth2.0回调处理失败:", error);
|
||||
return redirect("/login?error=callback_error");
|
||||
return redirectToLoginWithError(request, "登录回调处理失败,请重新登录");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getConfigLists, getConfigOptions, updateConfigStatus, type ConfigItem }
|
||||
import configListsStyles from "~/styles/pages/config-lists_index.css?url";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
import { getUserSession } from "~/api/login/auth.server";
|
||||
|
||||
export const links = () => [
|
||||
{ rel: "stylesheet", href: configListsStyles }
|
||||
@@ -72,7 +73,10 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const is_active = url.searchParams.get("is_active") ? url.searchParams.get("is_active") === "true" : undefined;
|
||||
const currentPage = parseInt(url.searchParams.get("page") || "1", 10);
|
||||
const pageSize = parseInt(url.searchParams.get("pageSize") || "10", 10);
|
||||
|
||||
|
||||
// 获取JWT token
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
try {
|
||||
// 获取配置列表
|
||||
const configsResponse = await getConfigLists({
|
||||
@@ -82,14 +86,14 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
is_active,
|
||||
page: currentPage,
|
||||
pageSize
|
||||
});
|
||||
}, frontendJWT);
|
||||
|
||||
if (configsResponse.error || !configsResponse.data) {
|
||||
throw new Error(configsResponse.error || "获取配置列表失败");
|
||||
}
|
||||
|
||||
// 获取配置选项
|
||||
const optionsResponse = await getConfigOptions();
|
||||
const optionsResponse = await getConfigOptions(frontendJWT);
|
||||
|
||||
if (optionsResponse.error || !optionsResponse.data) {
|
||||
throw new Error(optionsResponse.error || "获取配置选项失败");
|
||||
@@ -121,17 +125,20 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const _action = formData.get('_action');
|
||||
const configId = formData.get('configId');
|
||||
|
||||
|
||||
if (!configId) {
|
||||
return Response.json({ result: false, message: "缺少配置ID" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 获取JWT token
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 进行更新启用和禁用的状态
|
||||
try {
|
||||
if (_action === 'toggleStatus') {
|
||||
const is_active = formData.get('is_active') === 'true';
|
||||
|
||||
const response = await updateConfigStatus(parseInt(configId as string), is_active);
|
||||
|
||||
const response = await updateConfigStatus(parseInt(configId as string), is_active, frontendJWT);
|
||||
|
||||
if (response.error) {
|
||||
return Response.json({ result: false, message: response.error }, { status: 500 });
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ENVIRONMENT_LABELS } from "./config-lists._index";
|
||||
import { getConfigOptions, getConfigDetail, createConfig, updateConfig } from "~/api/system_setting/config-lists";
|
||||
import configNewStyles from "~/styles/pages/config-lists_new.css?url";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import { getUserSession } from "~/api/login/auth.server";
|
||||
|
||||
export const links = () => [
|
||||
{ rel: "stylesheet", href: configNewStyles }
|
||||
@@ -113,17 +114,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const url = new URL(request.url);
|
||||
const id = url.searchParams.get("id");
|
||||
let config: ConfigData | undefined = undefined;
|
||||
|
||||
|
||||
// 获取JWT token
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
try {
|
||||
// 获取配置选项
|
||||
const optionsResponse = await getConfigOptions();
|
||||
const optionsResponse = await getConfigOptions(frontendJWT);
|
||||
if (optionsResponse.error) {
|
||||
throw new Error(optionsResponse.error);
|
||||
}
|
||||
|
||||
|
||||
if (id) {
|
||||
// 获取配置详情
|
||||
const detailResponse = await getConfigDetail(id);
|
||||
const detailResponse = await getConfigDetail(id, frontendJWT);
|
||||
if (detailResponse.error) {
|
||||
throw new Error(detailResponse.error);
|
||||
}
|
||||
@@ -159,7 +163,10 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
const config = formData.get("config") as string;
|
||||
const is_active = formData.get("is_active") === "true";
|
||||
const remark = formData.get("remark") as string;
|
||||
|
||||
|
||||
// 获取JWT token
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const errors: ActionData["errors"] = {};
|
||||
|
||||
// 表单验证
|
||||
@@ -206,13 +213,13 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
|
||||
if (id) {
|
||||
// 更新配置
|
||||
const response = await updateConfig(id, configData);
|
||||
const response = await updateConfig(id, configData, frontendJWT);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} else {
|
||||
// 创建配置
|
||||
const response = await createConfig(configData);
|
||||
const response = await createConfig(configData, frontendJWT);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getContractTemplate } from '~/api/contract-template/templates';
|
||||
import type { ContractTemplate } from '~/api/contract-template/templates';
|
||||
import styles from '~/styles/pages/contract-template.css?url';
|
||||
import filePreviewStyles from '~/styles/components/file-preview-isolation.css?url';
|
||||
import { getUserSession } from '~/api/login/auth.server';
|
||||
|
||||
// 导入FilePreview组件
|
||||
import { FilePreview } from '~/components/reviews';
|
||||
@@ -30,20 +31,24 @@ export const handle = {
|
||||
}
|
||||
};
|
||||
|
||||
export async function loader({ params }: LoaderFunctionArgs) {
|
||||
export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
const templateId = params.id!;
|
||||
|
||||
// 获取 JWT
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
const jwt = frontendJWT || undefined;
|
||||
|
||||
try {
|
||||
const response = await getContractTemplate(templateId);
|
||||
|
||||
if (response.error) {
|
||||
throw new Response(response.error, { status: response.status || 404 });
|
||||
}
|
||||
|
||||
if (!response.data) {
|
||||
throw new Response('模板未找到', { status: 404 });
|
||||
}
|
||||
|
||||
const response = await getContractTemplate(templateId, jwt);
|
||||
|
||||
if (response.error) {
|
||||
throw new Response(response.error, { status: response.status || 404 });
|
||||
}
|
||||
|
||||
if (!response.data) {
|
||||
throw new Response('模板未找到', { status: 404 });
|
||||
}
|
||||
|
||||
// 添加调试信息
|
||||
// console.log('模板详情数据:', response.data);
|
||||
// console.log('分类信息:', response.data.category);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Pagination } from '~/components/ui/Pagination';
|
||||
import { getContractTemplates, getContractCategoriesWithCount } from '~/api/contract-template/templates';
|
||||
import type { ContractTemplate, TemplateSearchParams, ContractCategoryWithCount } from '~/api/contract-template/templates';
|
||||
import styles from '~/styles/pages/contract-template.css?url';
|
||||
import { getUserSession } from '~/api/login/auth.server';
|
||||
|
||||
export const links = () => [
|
||||
{ rel: 'stylesheet', href: styles }
|
||||
@@ -47,91 +48,95 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const page = parseInt(url.searchParams.get('page') || '1');
|
||||
const pageSize = 12
|
||||
|
||||
// 获取 JWT
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
const jwt = frontendJWT || undefined;
|
||||
|
||||
try {
|
||||
// 根据sortBy值设置数据库排序参数
|
||||
let dbSortBy = 'id';
|
||||
let dbSortOrder: 'asc' | 'desc' = 'asc';
|
||||
|
||||
switch (sortBy) {
|
||||
case 'relevance':
|
||||
dbSortBy = 'id';
|
||||
dbSortOrder = 'asc';
|
||||
break;
|
||||
case 'newest':
|
||||
dbSortBy = 'updated_at';
|
||||
dbSortOrder = 'desc';
|
||||
break;
|
||||
/* case 'popular':
|
||||
// 暂时按创建时间排序,后续可以加入使用频率字段
|
||||
dbSortBy = 'created_at';
|
||||
dbSortOrder = 'desc';
|
||||
break;
|
||||
case 'rating':
|
||||
// 暂时按特色推荐排序,后续可以加入评分字段
|
||||
dbSortBy = 'is_featured';
|
||||
dbSortOrder = 'desc';
|
||||
break; */
|
||||
default:
|
||||
dbSortBy = 'id';
|
||||
dbSortOrder = 'asc';
|
||||
}
|
||||
|
||||
switch (sortBy) {
|
||||
case 'relevance':
|
||||
dbSortBy = 'id';
|
||||
dbSortOrder = 'asc';
|
||||
break;
|
||||
case 'newest':
|
||||
dbSortBy = 'updated_at';
|
||||
dbSortOrder = 'desc';
|
||||
break;
|
||||
/* case 'popular':
|
||||
// 暂时按创建时间排序,后续可以加入使用频率字段
|
||||
dbSortBy = 'created_at';
|
||||
dbSortOrder = 'desc';
|
||||
break;
|
||||
case 'rating':
|
||||
// 暂时按特色推荐排序,后续可以加入评分字段
|
||||
dbSortBy = 'is_featured';
|
||||
dbSortOrder = 'desc';
|
||||
break; */
|
||||
default:
|
||||
dbSortBy = 'id';
|
||||
dbSortOrder = 'asc';
|
||||
}
|
||||
|
||||
// 构建搜索参数
|
||||
const searchParams: TemplateSearchParams = {
|
||||
page,
|
||||
pageSize,
|
||||
sortBy: dbSortBy,
|
||||
sortOrder: dbSortOrder
|
||||
};
|
||||
// 构建搜索参数
|
||||
const searchParams: TemplateSearchParams = {
|
||||
page,
|
||||
pageSize,
|
||||
sortBy: dbSortBy,
|
||||
sortOrder: dbSortOrder
|
||||
};
|
||||
|
||||
// 优先使用category_id,其次使用category名称
|
||||
if (category_id) {
|
||||
searchParams.category_id = parseInt(category_id);
|
||||
} else if (category) {
|
||||
searchParams.category = category;
|
||||
}
|
||||
// 优先使用category_id,其次使用category名称
|
||||
if (category_id) {
|
||||
searchParams.category_id = parseInt(category_id);
|
||||
} else if (category) {
|
||||
searchParams.category = category;
|
||||
}
|
||||
|
||||
// 并行获取模板数据和分类数据
|
||||
const [templatesResponse, categoriesResponse] = await Promise.all([
|
||||
getContractTemplates(searchParams),
|
||||
getContractCategoriesWithCount()
|
||||
getContractTemplates({ ...searchParams, token: jwt }),
|
||||
getContractCategoriesWithCount(jwt)
|
||||
]);
|
||||
|
||||
// 处理模板数据
|
||||
if (templatesResponse.error) {
|
||||
console.error('获取模板列表失败:', templatesResponse.error);
|
||||
return {
|
||||
templates: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
category,
|
||||
category_id,
|
||||
type,
|
||||
sortBy,
|
||||
categories: []
|
||||
};
|
||||
}
|
||||
// 处理模板数据
|
||||
if (templatesResponse.error) {
|
||||
console.error('获取模板列表失败:', templatesResponse.error);
|
||||
return {
|
||||
templates: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
category,
|
||||
category_id,
|
||||
type,
|
||||
sortBy,
|
||||
categories: []
|
||||
};
|
||||
}
|
||||
|
||||
// 处理分类数据
|
||||
const categories: ContractCategoryWithCount[] = categoriesResponse.error ? [] : categoriesResponse.data || [];
|
||||
// 处理分类数据
|
||||
const categories: ContractCategoryWithCount[] = categoriesResponse.error ? [] : categoriesResponse.data || [];
|
||||
|
||||
// 转换模板数据格式
|
||||
const transformedTemplates = templatesResponse.data?.templates.map(transformTemplate) || [];
|
||||
// 转换模板数据格式
|
||||
const transformedTemplates = templatesResponse.data?.templates.map(transformTemplate) || [];
|
||||
|
||||
// 注释掉类型筛选,因为数据库中没有type字段且已隐藏该功能
|
||||
/* if (type) {
|
||||
transformedTemplates = transformedTemplates.filter(t => t.type === type);
|
||||
} */
|
||||
// 注释掉类型筛选,因为数据库中没有type字段且已隐藏该功能
|
||||
/* if (type) {
|
||||
transformedTemplates = transformedTemplates.filter(t => t.type === type);
|
||||
} */
|
||||
|
||||
// 获取当前分类信息(用于显示)
|
||||
let currentCategory = '全部';
|
||||
if (category_id) {
|
||||
const cat = categories.find(c => c.id === parseInt(category_id));
|
||||
currentCategory = cat?.name || '全部';
|
||||
} else if (category) {
|
||||
currentCategory = category;
|
||||
}
|
||||
// 获取当前分类信息(用于显示)
|
||||
let currentCategory = '全部';
|
||||
if (category_id) {
|
||||
const cat = categories.find(c => c.id === parseInt(category_id));
|
||||
currentCategory = cat?.name || '全部';
|
||||
} else if (category) {
|
||||
currentCategory = category;
|
||||
}
|
||||
|
||||
return {
|
||||
templates: transformedTemplates,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { MetaFunction } from '@remix-run/node';
|
||||
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
|
||||
import { useNavigate, useLoaderData } from '@remix-run/react';
|
||||
import { ContractSearchHero } from '~/components/contract-template/ContractSearchHero';
|
||||
import { getContractCategoriesWithCount } from '~/api/contract-template/templates';
|
||||
import type { ContractCategoryWithCount } from '~/api/contract-template/templates';
|
||||
import styles from '~/styles/pages/contract-template.css?url';
|
||||
import { getUserSession } from '~/api/login/auth.server';
|
||||
|
||||
export const links = () => [
|
||||
{ rel: 'stylesheet', href: styles }
|
||||
@@ -42,18 +43,22 @@ function transformCategory(category: ContractCategoryWithCount) {
|
||||
* 加载分类数据
|
||||
* @returns 分类数据
|
||||
*/
|
||||
export async function loader() {
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// 获取 JWT
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
const jwt = frontendJWT || undefined;
|
||||
|
||||
try {
|
||||
// 使用聚合查询获取分类及其模板数量
|
||||
const categoriesResponse = await getContractCategoriesWithCount();
|
||||
const categoriesResponse = await getContractCategoriesWithCount(jwt);
|
||||
|
||||
// 处理分类数据
|
||||
if (categoriesResponse.error) {
|
||||
console.error('获取分类失败:', categoriesResponse.error);
|
||||
return { categories: [] };
|
||||
}
|
||||
// 处理分类数据
|
||||
if (categoriesResponse.error) {
|
||||
console.error('获取分类失败:', categoriesResponse.error);
|
||||
return { categories: [] };
|
||||
}
|
||||
|
||||
const categories = categoriesResponse.data || [];
|
||||
const categories = categoriesResponse.data || [];
|
||||
|
||||
// 转换分类数据格式
|
||||
const categoriesWithCount = categories.map(transformCategory);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Pagination } from '~/components/ui/Pagination';
|
||||
import { searchContractTemplates, getContractCategories } from '~/api/contract-template/templates';
|
||||
import type { ContractTemplate, ContractCategory } from '~/api/contract-template/templates';
|
||||
import styles from '~/styles/pages/contract-template.css?url';
|
||||
import { getUserSession } from '~/api/login/auth.server';
|
||||
|
||||
export const links = () => [
|
||||
{ rel: 'stylesheet', href: styles }
|
||||
@@ -105,6 +106,10 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
dbSortOrder = 'asc';
|
||||
}
|
||||
|
||||
// 获取 JWT
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
const jwt = frontendJWT || undefined;
|
||||
|
||||
try {
|
||||
// 并行获取搜索结果和分类数据
|
||||
const [searchResponse, categoriesResponse] = await Promise.all([
|
||||
@@ -113,67 +118,69 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
page,
|
||||
pageSize,
|
||||
sortBy: dbSortBy,
|
||||
sortOrder: dbSortOrder
|
||||
sortOrder: dbSortOrder,
|
||||
token: jwt
|
||||
}),
|
||||
getContractCategories()
|
||||
getContractCategories(jwt)
|
||||
]);
|
||||
|
||||
// 处理搜索结果
|
||||
if (searchResponse.error) {
|
||||
console.error('搜索合同模板失败:', searchResponse.error);
|
||||
return {
|
||||
results: [],
|
||||
query,
|
||||
category,
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
sortBy,
|
||||
searchTime: '搜索失败',
|
||||
categories: []
|
||||
};
|
||||
}
|
||||
// 处理搜索结果
|
||||
if (searchResponse.error) {
|
||||
console.error('搜索合同模板失败:', searchResponse.error);
|
||||
return {
|
||||
results: [],
|
||||
query,
|
||||
category,
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
sortBy,
|
||||
searchTime: '搜索失败',
|
||||
categories: []
|
||||
};
|
||||
}
|
||||
|
||||
// 处理分类数据
|
||||
const categories = categoriesResponse.error ? [] : categoriesResponse.data || [];
|
||||
// 处理分类数据
|
||||
const categories = categoriesResponse.error ? [] : categoriesResponse.data || [];
|
||||
|
||||
// 转换模板数据格式
|
||||
const transformedResults = searchResponse.data?.templates.map(transformTemplate) || [];
|
||||
// 转换模板数据格式
|
||||
const transformedResults = searchResponse.data?.templates.map(transformTemplate) || [];
|
||||
|
||||
// 为每个分类获取搜索结果统计
|
||||
let categoriesWithSearchCount: CategoryWithSearchCount[] = [];
|
||||
if (query && query.trim()) {
|
||||
// 并行为每个分类获取搜索结果数量
|
||||
const categorySearchPromises = categories.map(async (cat): Promise<CategoryWithSearchCount> => {
|
||||
try {
|
||||
const categorySearchResponse = await searchContractTemplates(query, {
|
||||
category: cat.name,
|
||||
page: 1,
|
||||
pageSize: 1000 // 设置较大的pageSize来获取总数
|
||||
});
|
||||
|
||||
const count = categorySearchResponse.error ? 0 : (categorySearchResponse.data?.total || 0);
|
||||
return {
|
||||
...cat,
|
||||
searchCount: count
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`获取分类${cat.name}的搜索统计失败:`, error);
|
||||
return {
|
||||
...cat,
|
||||
searchCount: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
// 为每个分类获取搜索结果统计
|
||||
let categoriesWithSearchCount: CategoryWithSearchCount[] = [];
|
||||
if (query && query.trim()) {
|
||||
// 并行为每个分类获取搜索结果数量
|
||||
const categorySearchPromises = categories.map(async (cat): Promise<CategoryWithSearchCount> => {
|
||||
try {
|
||||
const categorySearchResponse = await searchContractTemplates(query, {
|
||||
category: cat.name,
|
||||
page: 1,
|
||||
pageSize: 1000, // 设置较大的pageSize来获取总数
|
||||
token: jwt
|
||||
});
|
||||
|
||||
const count = categorySearchResponse.error ? 0 : (categorySearchResponse.data?.total || 0);
|
||||
return {
|
||||
...cat,
|
||||
searchCount: count
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`获取分类${cat.name}的搜索统计失败:`, error);
|
||||
return {
|
||||
...cat,
|
||||
searchCount: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
categoriesWithSearchCount = await Promise.all(categorySearchPromises);
|
||||
} else {
|
||||
// 如果没有搜索关键词,searchCount设为0
|
||||
categoriesWithSearchCount = categories.map(cat => ({
|
||||
...cat,
|
||||
searchCount: 0
|
||||
}));
|
||||
}
|
||||
categoriesWithSearchCount = await Promise.all(categorySearchPromises);
|
||||
} else {
|
||||
// 如果没有搜索关键词,searchCount设为0
|
||||
categoriesWithSearchCount = categories.map(cat => ({
|
||||
...cat,
|
||||
searchCount: 0
|
||||
}));
|
||||
}
|
||||
|
||||
// 计算搜索耗时
|
||||
const endTime = Date.now();
|
||||
|
||||
@@ -50,6 +50,7 @@ export type LoaderData = {
|
||||
completedTasks: number;
|
||||
};
|
||||
initialLoad?: boolean;
|
||||
frontendJWT?: string; // 新增JWT
|
||||
};
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
@@ -100,7 +101,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
currentPage: tasksResponse.data?.currentPage || params.page,
|
||||
pageSize: tasksResponse.data?.pageSize || params.pageSize,
|
||||
totalPages: tasksResponse.data?.totalPages || 0,
|
||||
stats: statsResponse.data || { totalTasks: 0, pendingTasks: 0, inProgressTasks: 0, completedTasks: 0 }
|
||||
stats: statsResponse.data || { totalTasks: 0, pendingTasks: 0, inProgressTasks: 0, completedTasks: 0 },
|
||||
frontendJWT // 新增:返回JWT给客户端
|
||||
}, {
|
||||
headers: {
|
||||
"Cache-Control": "max-age=60, s-maxage=180"
|
||||
@@ -210,7 +212,7 @@ const docTypeConfig = {
|
||||
|
||||
export default function CrossCheckingIndex() {
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const { tasks, totalCount, currentPage, pageSize, stats } = loaderData;
|
||||
const { tasks, totalCount, currentPage, pageSize, stats, frontendJWT } = loaderData;
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const dateFrom = searchParams.get('dateFrom') || '';
|
||||
const dateTo = searchParams.get('dateTo') || '';
|
||||
@@ -750,6 +752,7 @@ export default function CrossCheckingIndex() {
|
||||
total={modalState.total}
|
||||
onPageChange={handleModalPageChange}
|
||||
onPageSizeChange={handleModalPageSizeChange}
|
||||
frontendJWT={frontendJWT}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -202,7 +202,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const reviewData = await getReviewPoints(id, request);
|
||||
|
||||
// 获取当前登录用户是否是发起人
|
||||
const isProposer = await findIsProposer(taskId, userInfo?.user_id);
|
||||
const isProposer = await findIsProposer(taskId, userInfo?.user_id, frontendJWT);
|
||||
|
||||
// console.log("documentData-------",JSON.stringify(documentData.data,null,2));
|
||||
// console.log("reviewData-------",JSON.stringify('data' in reviewData ? reviewData.data : '',null,2));
|
||||
@@ -560,9 +560,9 @@ export default function CrossCheckingResult() {
|
||||
cancelText: '取消',
|
||||
onConfirm: async () => {
|
||||
setIsLoading(true);
|
||||
const res = await confirmReviewResults(document.id);
|
||||
const res = await confirmReviewResults(document.id, jwtToken);
|
||||
setIsLoading(false);
|
||||
|
||||
|
||||
if (res.error) {
|
||||
toastService.error(res.error);
|
||||
return;
|
||||
|
||||
@@ -42,11 +42,16 @@ interface LoaderData {
|
||||
error?: string;
|
||||
groups: DocumentTypeGroup[];
|
||||
ruleTypes: RuleType[];
|
||||
frontendJWT?: string | null;
|
||||
}
|
||||
|
||||
// 加载函数 - 获取文档类型列表
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const name = url.searchParams.get('name') || undefined;
|
||||
const ruleType = url.searchParams.get('ruleType') || undefined;
|
||||
@@ -64,13 +69,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
};
|
||||
|
||||
// 并行获取文档类型数据和父级评查点分组
|
||||
const ruleTypesResponse = await getRuleTypes();
|
||||
const ruleTypesResponse = await getRuleTypes(undefined, frontendJWT);
|
||||
if(ruleTypesResponse.error){
|
||||
console.error("获取父级评查点分组失败:", ruleTypesResponse.error);
|
||||
}
|
||||
const ruleTypes = ruleTypesResponse.error ? [] : ruleTypesResponse.data;
|
||||
|
||||
const typesResponse = await getDocumentTypes(searchParams);
|
||||
const typesResponse = await getDocumentTypes(searchParams, frontendJWT);
|
||||
if(typesResponse.error){
|
||||
console.error("获取文档类型失败:", typesResponse.error);
|
||||
throw new Error(typesResponse.error);
|
||||
@@ -85,7 +90,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
total: typesResponse.data?.total || typesResult.length,
|
||||
pageSize,
|
||||
currentPage: page,
|
||||
ruleTypes
|
||||
ruleTypes,
|
||||
frontendJWT
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("加载文档类型列表失败:", error);
|
||||
@@ -104,10 +110,12 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const id = formData.get("id") as string;
|
||||
const intent = formData.get("intent") as string;
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (intent === "delete" && id) {
|
||||
try {
|
||||
const result = await deleteDocumentType(id);
|
||||
const result = await deleteDocumentType(id, frontendJWT || undefined);
|
||||
|
||||
if (result.error) {
|
||||
return Response.json({ success: false, error: result.error }, { status: result.status || 500 });
|
||||
@@ -132,7 +140,7 @@ export default function DocumentTypesList() {
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// 获取加载器数据
|
||||
const { types, total, error, ruleTypes } = useLoaderData<LoaderData>();
|
||||
const { types, total, error, ruleTypes, frontendJWT } = useLoaderData<LoaderData>();
|
||||
|
||||
// 状态管理
|
||||
const [ruleGroups, setRuleGroups] = useState<RuleGroup[]>([]);
|
||||
@@ -160,7 +168,7 @@ export default function DocumentTypesList() {
|
||||
const loadRuleGroups = async () => {
|
||||
setLoadingGroups(true);
|
||||
try {
|
||||
const response = await getRuleGroupsByType(ruleTypeParam);
|
||||
const response = await getRuleGroupsByType(ruleTypeParam, frontendJWT || undefined);
|
||||
if (response.data) {
|
||||
setRuleGroups(response.data);
|
||||
} else if (response.error) {
|
||||
|
||||
@@ -62,12 +62,16 @@ interface ActionData {
|
||||
// 获取数据
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const id = url.searchParams.get("id");
|
||||
const isEdit = id ? true : false;
|
||||
|
||||
// 1. 获取评查点分组 - 使用getAllRuleGroups获取所有分组
|
||||
const ruleGroupsResponse = await getAllRuleGroups();
|
||||
const ruleGroupsResponse = await getAllRuleGroups(frontendJWT);
|
||||
if (ruleGroupsResponse.error) {
|
||||
console.error("获取评查点分组失败:", ruleGroupsResponse.error);
|
||||
// throw new Error(ruleGroupsResponse.error);
|
||||
@@ -80,16 +84,16 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// 2. 获取各类型的提示词模板
|
||||
const [llmExtractionTemplatesResponse, vlmExtractionTemplatesResponse, evaluationTemplatesResponse, summaryTemplatesResponse] =
|
||||
await Promise.all([
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.LLM_EXTRACTION }),
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.VLM_EXTRACTION }),
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.EVALUATION }),
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.SUMMARY })
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.LLM_EXTRACTION }, frontendJWT),
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.VLM_EXTRACTION }, frontendJWT),
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.EVALUATION }, frontendJWT),
|
||||
getPromptTemplates({ type: TEMPLATE_TYPES.SUMMARY }, frontendJWT)
|
||||
]);
|
||||
|
||||
// 3. 如果是编辑模式,获取文档类型详情
|
||||
let documentType = undefined;
|
||||
if (id) {
|
||||
const typeResponse = await getDocumentType(id);
|
||||
const typeResponse = await getDocumentType(id, frontendJWT);
|
||||
if (typeResponse.data) {
|
||||
documentType = typeResponse.data;
|
||||
}
|
||||
@@ -121,6 +125,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
// 处理表单提交
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const formData = await request.formData();
|
||||
const id = formData.get("id") as string | null;
|
||||
const name = formData.get("name") as string;
|
||||
@@ -185,11 +192,11 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
// 更新文档类型
|
||||
response = await updateDocumentType(id, {
|
||||
...documentTypeData,
|
||||
id: parseInt(id)
|
||||
});
|
||||
id: parseInt(id),
|
||||
}, frontendJWT);
|
||||
} else {
|
||||
// 创建新文档类型
|
||||
response = await createDocumentType(documentTypeData);
|
||||
response = await createDocumentType(documentTypeData, frontendJWT);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -46,7 +46,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const pageSize = parseInt(url.searchParams.get("pageSize") || "10", 10);
|
||||
|
||||
// 获取文档类型列表,用于筛选条件
|
||||
const typesResponse = await getDocumentTypes({ pageSize: 500 });
|
||||
const typesResponse = await getDocumentTypes({ pageSize: 500 }, frontendJWT);
|
||||
const documentTypes = typesResponse.data?.types || [];
|
||||
const documentTypeOptions = documentTypes.map(type => ({
|
||||
value: type.id,
|
||||
@@ -126,7 +126,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { userInfo } = await getUserSession(request);
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (!userInfo?.user_id) {
|
||||
return Response.json({ result: false, message: "用户身份验证失败" }, { status: 401 });
|
||||
@@ -138,7 +138,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
|
||||
if (action === "delete") {
|
||||
const id = formData.get("id") as string;
|
||||
const response = await deleteDocument(id, userId);
|
||||
const response = await deleteDocument(id, userId, frontendJWT);
|
||||
|
||||
if (response.error) {
|
||||
return Response.json({ result: false, message: response.error }, { status: response.status || 500 });
|
||||
@@ -150,7 +150,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const ids = formData.getAll("ids") as string[];
|
||||
|
||||
// 批量删除处理
|
||||
const results = await Promise.all(ids.map(id => deleteDocument(id, userId)));
|
||||
const results = await Promise.all(ids.map(id => deleteDocument(id, userId, frontendJWT)));
|
||||
const failures = results.filter(r => r.error);
|
||||
|
||||
if (failures.length > 0) {
|
||||
@@ -257,7 +257,8 @@ export default function DocumentsIndex() {
|
||||
reviewType: storedReviewType || undefined,
|
||||
userId: userId, // 添加用户ID筛选
|
||||
page: currentPage,
|
||||
pageSize
|
||||
pageSize,
|
||||
token: loaderData.frontendJWT || undefined // 传递 JWT token
|
||||
};
|
||||
|
||||
// 获取文档列表
|
||||
@@ -270,7 +271,7 @@ export default function DocumentsIndex() {
|
||||
const filteredTypesResponse = await getDocumentTypes({
|
||||
pageSize: 500,
|
||||
reviewType: storedReviewType || undefined
|
||||
});
|
||||
}, loaderData.frontendJWT || undefined);
|
||||
const filteredDocumentTypes = filteredTypesResponse.data?.types || [];
|
||||
const filteredOptions = filteredDocumentTypes.map(type => ({
|
||||
value: type.id,
|
||||
@@ -492,20 +493,21 @@ export default function DocumentsIndex() {
|
||||
// 下载文档
|
||||
const handleDownload = async (path: string) => {
|
||||
try {
|
||||
const downloadUrl = `${DOCUMENT_URL}${path}`;
|
||||
|
||||
// 使用 PDF 代理路由获取文件,自动添加 JWT 认证
|
||||
const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(path)}`;
|
||||
|
||||
// 使用fetch获取文件内容
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
|
||||
// 将响应转换为Blob
|
||||
const blob = await response.blob();
|
||||
|
||||
|
||||
// 创建Blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
|
||||
// 创建一个隐藏的a标签并点击它
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
@@ -515,7 +517,7 @@ export default function DocumentsIndex() {
|
||||
a.download = decodeURIComponent(fileName);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
|
||||
// 清理
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
@@ -631,24 +633,25 @@ export default function DocumentsIndex() {
|
||||
console.warn(`文档 ${doc.name} 没有有效的路径`);
|
||||
return;
|
||||
}
|
||||
|
||||
const downloadUrl = `${DOCUMENT_URL}${doc.path}`;
|
||||
|
||||
|
||||
// 使用 PDF 代理路由获取文件,自动添加 JWT 认证
|
||||
const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(doc.path)}`;
|
||||
|
||||
// 获取文件内容
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
|
||||
// 将响应转换为Blob
|
||||
const blob = await response.blob();
|
||||
|
||||
|
||||
// 从路径中获取文件名
|
||||
const fileName = doc.path.split('/').pop() || doc.name;
|
||||
|
||||
|
||||
// 添加到ZIP文件
|
||||
zip.file(decodeURIComponent(fileName), blob);
|
||||
|
||||
|
||||
return { success: true, name: fileName };
|
||||
} catch (error) {
|
||||
console.error(`下载文件 ${doc.name} 失败:`, error);
|
||||
@@ -714,7 +717,7 @@ export default function DocumentsIndex() {
|
||||
}
|
||||
|
||||
// console.log('开始审核',fileId,auditStatus)
|
||||
const response = await updateDocumentAuditStatus(fileId.toString(), 2, userId);
|
||||
const response = await updateDocumentAuditStatus(fileId.toString(), 2, userId, loaderData.frontendJWT as string | undefined);
|
||||
if (response.error) {
|
||||
console.error('更新文件审核状态失败:', response.error);
|
||||
toastService.error('更新文件审核状态失败:' + (response.error || '未知错误'));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { postgrestGet } from "~/api/postgrest-client";
|
||||
import { getUserSession } from "~/api/login/auth.server";
|
||||
|
||||
/**
|
||||
* 文档下载路由 - 处理文档下载请求
|
||||
@@ -7,6 +8,7 @@ import { postgrestGet } from "~/api/postgrest-client";
|
||||
*/
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
// 获取文件路径参数
|
||||
const url = new URL(request.url);
|
||||
const filePath = url.searchParams.get("path");
|
||||
@@ -23,7 +25,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
filter: {
|
||||
'object_path': `eq.${filePath}`,
|
||||
'expires_in': 'eq.300' // 5分钟有效期
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { userInfo } = await getUserSession(request);
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (!userInfo?.user_id) {
|
||||
throw new Response("用户身份验证失败", { status: 401 });
|
||||
@@ -100,8 +100,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
// 并行获取文档详情和文档类型列表
|
||||
const [documentResponse, documentTypesResponse] = await Promise.all([
|
||||
getDocument(id, userId),
|
||||
getDocumentTypes({ pageSize: 500 })
|
||||
getDocument(id, userId, frontendJWT),
|
||||
getDocumentTypes({ pageSize: 500 }, frontendJWT)
|
||||
]);
|
||||
|
||||
if (documentResponse.error) {
|
||||
@@ -114,7 +114,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
return Response.json({
|
||||
document: documentResponse.data,
|
||||
documentTypes: documentTypesResponse.data?.types || []
|
||||
documentTypes: documentTypesResponse.data?.types || [],
|
||||
frontendJWT: frontendJWT
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("加载文档数据失败:", error);
|
||||
@@ -126,7 +127,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { userInfo } = await getUserSession(request);
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (!userInfo?.user_id) {
|
||||
return Response.json({ error: "用户身份验证失败" }, { status: 401 });
|
||||
@@ -173,7 +174,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
auditStatus,
|
||||
isTest,
|
||||
remark
|
||||
}, userId);
|
||||
}, userId, frontendJWT);
|
||||
|
||||
if (updateResponse.error) {
|
||||
console.error('更新文档失败:', updateResponse.error);
|
||||
@@ -323,7 +324,7 @@ export default function DocumentEdit() {
|
||||
return (
|
||||
<div className="preview-content relative overflow-y-auto max-h-[1000px]">
|
||||
<Document
|
||||
file={DOCUMENT_URL + documentData.path}
|
||||
file={`/api/pdf-proxy?path=${encodeURIComponent(documentData.path)}`}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
onLoadError={(error) => {
|
||||
console.error("PDF加载错误:", error);
|
||||
@@ -416,20 +417,21 @@ export default function DocumentEdit() {
|
||||
// 下载文档
|
||||
const downloadDocument = async () => {
|
||||
try {
|
||||
const downloadUrl = `${DOCUMENT_URL}${documentData.path}`;
|
||||
|
||||
// 使用 PDF 代理路由获取文件,自动添加 JWT 认证
|
||||
const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(documentData.path)}`;
|
||||
|
||||
// 使用fetch获取文件内容
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
|
||||
// 将响应转换为Blob
|
||||
const blob = await response.blob();
|
||||
|
||||
|
||||
// 创建Blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
|
||||
// 创建一个隐藏的a标签并点击它
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
@@ -439,23 +441,24 @@ export default function DocumentEdit() {
|
||||
a.download = decodeURIComponent(fileName);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
|
||||
// 清理
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}, 100);
|
||||
|
||||
|
||||
toastService.success('文件下载成功');
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 在新窗口打开文档预览
|
||||
const openPreview = () => {
|
||||
const previewUrl = `${DOCUMENT_URL}${documentData.path}`;
|
||||
// 使用 PDF 代理路由,自动添加 JWT 认证
|
||||
const previewUrl = `/api/pdf-proxy?path=${encodeURIComponent(documentData.path)}`;
|
||||
window.open(previewUrl, '_blank');
|
||||
};
|
||||
|
||||
|
||||
@@ -245,8 +245,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// 我们不能在服务器端访问 sessionStorage,所以在客户端组件中处理 reviewType 过滤
|
||||
// 并行加载文档和文档类型
|
||||
const [documentsResponse, typesResponse] = await Promise.all([
|
||||
getTodayDocuments(userInfo),
|
||||
getDocumentTypes()
|
||||
getTodayDocuments(userInfo, undefined, frontendJWT),
|
||||
getDocumentTypes(undefined, frontendJWT)
|
||||
]);
|
||||
|
||||
// console.log('loader: 文档加载结果:', documentsResponse);
|
||||
@@ -410,7 +410,7 @@ export default function FilesUpload() {
|
||||
|
||||
try {
|
||||
// 使用 reviewType 获取过滤后的文档列表
|
||||
const response = await getTodayDocuments(loaderData.userInfo || undefined, reviewType);
|
||||
const response = await getTodayDocuments(loaderData.userInfo || undefined, reviewType, loaderData.frontendJWT || undefined);
|
||||
|
||||
if (response.error) {
|
||||
console.error('过滤文档列表失败:', response.error);
|
||||
@@ -575,16 +575,16 @@ export default function FilesUpload() {
|
||||
}
|
||||
});
|
||||
|
||||
console.log('合同主文件ID:', mainDocumentIds);
|
||||
console.log('合同附件ID:', attachmentIds);
|
||||
// console.log('合同主文件ID:', mainDocumentIds);
|
||||
// console.log('合同附件ID:', attachmentIds);
|
||||
|
||||
// 分别查询状态
|
||||
statusResponse = await getDocumentsStatus(mainDocumentIds, attachmentIds);
|
||||
statusResponse = await getDocumentsStatus(mainDocumentIds, attachmentIds, loaderData.frontendJWT || undefined);
|
||||
} else {
|
||||
// 非合同类型,使用原有逻辑
|
||||
const incompleteIds = incompleteFiles.map(file => file.id);
|
||||
// console.log('未完成的文档ID:', incompleteIds);
|
||||
statusResponse = await getDocumentsStatus(incompleteIds);
|
||||
statusResponse = await getDocumentsStatus(incompleteIds, undefined, loaderData.frontendJWT || undefined);
|
||||
}
|
||||
|
||||
// console.log('状态检查响应:', statusResponse);
|
||||
@@ -1666,7 +1666,7 @@ export default function FilesUpload() {
|
||||
|
||||
// 获取文件状态
|
||||
// console.log('【调试-checkProcessingStatus】发送请求获取文件状态');
|
||||
const response = await getDocumentsStatus(fileIds);
|
||||
const response = await getDocumentsStatus(fileIds, undefined, loaderData.frontendJWT || undefined);
|
||||
|
||||
if (response.error) {
|
||||
console.error('【调试-checkProcessingStatus】获取文件状态出错:', response.error);
|
||||
|
||||
+7
-6
@@ -92,7 +92,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
|
||||
export default function Home() {
|
||||
const navigate = useNavigate();
|
||||
const { homeData: initialHomeData, recentFiles: initialRecentFiles, userRole: serverUserRole, userInfo } = useLoaderData<typeof loader>();
|
||||
const { homeData: initialHomeData, recentFiles: initialRecentFiles, userRole: serverUserRole, userInfo, frontendJWT } = useLoaderData<typeof loader>();
|
||||
const [recentFiles, setRecentFiles] = useState<DocumentUI[]>(initialRecentFiles || []);
|
||||
const [homeData, setHomeData] = useState(initialHomeData);
|
||||
const [currentDateTime, setCurrentDateTime] = useState({
|
||||
@@ -160,9 +160,9 @@ export default function Home() {
|
||||
setIsLoading(true);
|
||||
// 从 sessionStorage 获取 reviewType
|
||||
const reviewType = sessionStorage.getItem('reviewType');
|
||||
|
||||
|
||||
// 加载主页数据
|
||||
const newHomeData = await getHomeData(reviewType || undefined,userInfo.user_id);
|
||||
const newHomeData = await getHomeData(reviewType || undefined,userInfo.user_id, frontendJWT);
|
||||
setHomeData(newHomeData);
|
||||
|
||||
// 加载文档数据
|
||||
@@ -185,7 +185,8 @@ export default function Home() {
|
||||
const documentSearchParams: DocumentSearchParams = {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
userId: userInfo.user_id
|
||||
userId: userInfo.user_id,
|
||||
token: frontendJWT || undefined
|
||||
};
|
||||
|
||||
// 根据 reviewType 添加过滤条件
|
||||
@@ -244,9 +245,9 @@ export default function Home() {
|
||||
// 如果 reviewType 发生变化
|
||||
if (currentReviewType !== previousReviewType) {
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
// 更新主页数据
|
||||
const newHomeData = await getHomeData(currentReviewType || undefined,userInfo.user_id);
|
||||
const newHomeData = await getHomeData(currentReviewType || undefined,userInfo.user_id, frontendJWT);
|
||||
setHomeData(newHomeData);
|
||||
|
||||
// 更新文档数据
|
||||
|
||||
+98
-43
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams, Form } from "@remix-run/react";
|
||||
import { useActionData, useLoaderData, Form } from "@remix-run/react";
|
||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { OAuthClient } from "~/api/login/oauth-client";
|
||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||
@@ -32,11 +32,30 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const redirectTo = url.searchParams.get("redirect") || "/";
|
||||
|
||||
const session = await getSession(request);
|
||||
|
||||
// 读取 flash 消息(来自 callback 的错误)
|
||||
const loginError = session.get("loginError");
|
||||
|
||||
session.set("redirectTo", redirectTo);
|
||||
|
||||
// 提交 session 以清除 flash 消息
|
||||
if (loginError) {
|
||||
const { sessionStorage } = await import("~/api/login/auth.server");
|
||||
return Response.json({
|
||||
isAuthenticated: false,
|
||||
redirectTo,
|
||||
flashError: loginError
|
||||
}, {
|
||||
headers: {
|
||||
"Set-Cookie": await sessionStorage.commitSession(session)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
isAuthenticated: false,
|
||||
redirectTo
|
||||
redirectTo,
|
||||
flashError: null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,10 +79,17 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
// 登录成功,直接返回重定向响应
|
||||
return response;
|
||||
} else {
|
||||
// 登录失败,解析错误信息并重定向到登录页面
|
||||
// 登录失败,返回错误信息(不再使用URL参数)
|
||||
const errorData = await response.json();
|
||||
const errorMsg = errorData.error || "登录失败";
|
||||
return redirect(`/login?error=${encodeURIComponent(errorMsg)}`);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: errorData.error || "登录失败",
|
||||
retryCount: errorData.retryCount || 0,
|
||||
isLocked: errorData.isLocked || false,
|
||||
remainingAttempts: errorData.remainingAttempts || 5
|
||||
}, {
|
||||
status: response.status
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,40 +97,19 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const error = searchParams.get("error");
|
||||
const actionData = useActionData<typeof action>();
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
// 获取错误消息的友好描述
|
||||
const getErrorMessage = (error: string | null) => {
|
||||
if (!error) return null;
|
||||
|
||||
switch (error) {
|
||||
case "missing_code":
|
||||
return "登录过程中缺少授权码,请重新登录";
|
||||
case "invalid_state":
|
||||
return "登录状态验证失败,请重新登录";
|
||||
case "token_error":
|
||||
return "获取访问令牌失败,请重新登录";
|
||||
case "userinfo_error":
|
||||
return "获取用户信息失败,请重新登录";
|
||||
case "callback_error":
|
||||
return "登录回调处理失败,请重新登录";
|
||||
case "用户名和密码不能为空":
|
||||
case "用户名和密码不能为空,请重新输入":
|
||||
return "用户名和密码不能为空,请重新输入";
|
||||
case "登录失败,请检查用户名和密码":
|
||||
case "用户名或密码错误,请重新输入":
|
||||
return "用户名或密码错误,请重新输入";
|
||||
case "登录请求失败,请稍后重试":
|
||||
case "网络连接失败,请稍后重试":
|
||||
return "网络连接失败,请稍后重试";
|
||||
default:
|
||||
return decodeURIComponent(error);
|
||||
}
|
||||
};
|
||||
// 从 actionData 或 loaderData 中获取错误信息
|
||||
// actionData 的错误优先(来自密码登录)
|
||||
// loaderData.flashError 次之(来自 OAuth 回调)
|
||||
const error = actionData?.error || loaderData?.flashError;
|
||||
const isLocked = actionData?.isLocked || false;
|
||||
const retryCount = actionData?.retryCount || 0;
|
||||
const remainingAttempts = actionData?.remainingAttempts || 5;
|
||||
|
||||
// 处理OAuth2.0登录
|
||||
const handleOAuthLogin = () => {
|
||||
@@ -143,6 +148,13 @@ export default function Login() {
|
||||
|
||||
// 处理账号密码登录表单提交
|
||||
const handlePasswordLoginSubmit = (e: React.FormEvent) => {
|
||||
// 检查账户是否被锁定
|
||||
if (isLocked) {
|
||||
e.preventDefault();
|
||||
toastService.error("账户已被锁定,请联系管理员");
|
||||
return;
|
||||
}
|
||||
|
||||
// 客户端验证
|
||||
if (!username.trim()) {
|
||||
e.preventDefault();
|
||||
@@ -180,9 +192,22 @@ export default function Login() {
|
||||
<h2 className="login-subtitle">统一身份认证登录</h2>
|
||||
|
||||
{error && (
|
||||
<div className="error-message-container">
|
||||
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
|
||||
<div className="error-text">{getErrorMessage(error)}</div>
|
||||
<div className={`error-message-container ${isLocked ? 'locked' : ''}`}>
|
||||
<div className="error-icon">
|
||||
{isLocked ? (
|
||||
<i className="ri-lock-line"></i>
|
||||
) : (
|
||||
<i className="ri-error-warning-line"></i>
|
||||
)}
|
||||
</div>
|
||||
<div className="error-text">
|
||||
{error}
|
||||
{!isLocked && retryCount > 0 && (
|
||||
<div className="retry-info" style={{ fontSize: '0.9em', marginTop: '4px', opacity: 0.9 }}>
|
||||
剩余尝试次数:{remainingAttempts} 次
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -235,9 +260,22 @@ export default function Login() {
|
||||
<h2 className="login-subtitle">管理员登录</h2>
|
||||
|
||||
{error && (
|
||||
<div className="error-message-container">
|
||||
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
|
||||
<div className="error-text">{getErrorMessage(error)}</div>
|
||||
<div className={`error-message-container ${isLocked ? 'locked' : ''}`}>
|
||||
<div className="error-icon">
|
||||
{isLocked ? (
|
||||
<i className="ri-lock-line"></i>
|
||||
) : (
|
||||
<i className="ri-error-warning-line"></i>
|
||||
)}
|
||||
</div>
|
||||
<div className="error-text">
|
||||
{error}
|
||||
{!isLocked && retryCount > 0 && (
|
||||
<div className="retry-info" style={{ fontSize: '0.9em', marginTop: '4px', opacity: 0.9 }}>
|
||||
剩余尝试次数:{remainingAttempts} 次
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -275,10 +313,27 @@ export default function Login() {
|
||||
<button
|
||||
type="submit"
|
||||
className="admin-login-button"
|
||||
disabled={isLocked}
|
||||
style={isLocked ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
|
||||
>
|
||||
<i className="ri-login-box-line"></i>
|
||||
登录
|
||||
<i className={isLocked ? "ri-lock-line" : "ri-login-box-line"}></i>
|
||||
{isLocked ? "账户已锁定" : "登录"}
|
||||
</button>
|
||||
|
||||
{isLocked && (
|
||||
<div style={{
|
||||
marginTop: '12px',
|
||||
padding: '8px',
|
||||
background: '#fff3cd',
|
||||
border: '1px solid #ffc107',
|
||||
borderRadius: '4px',
|
||||
fontSize: '0.9em',
|
||||
textAlign: 'center',
|
||||
color: '#856404'
|
||||
}}>
|
||||
<i className="ri-information-line"></i> 账户已被锁定,请联系管理员解锁
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
<div className="back-to-oauth">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MetaFunction, json, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { useSearchParams, useNavigate, useLoaderData } from "@remix-run/react";
|
||||
import { useState } from "react";
|
||||
import { MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
|
||||
import { useSearchParams, useNavigate, useLoaderData, useFetcher } from "@remix-run/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import indexStyles from "~/styles/pages/prompts_index.css?url";
|
||||
import { Card } from "~/components/ui/Card";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
@@ -9,19 +9,6 @@ import { Table } from "~/components/ui/Table";
|
||||
import { Pagination } from "~/components/ui/Pagination";
|
||||
import { getPromptTemplates, deletePromptTemplate, type PromptTemplateUI } from "~/api/prompts/prompts";
|
||||
|
||||
// 定义提示词模板类型
|
||||
export interface PromptTemplate {
|
||||
id: string;
|
||||
template_name: string;
|
||||
template_type: 'Common' | 'Extraction' | 'Evaluation' | 'Summary';
|
||||
description: string;
|
||||
version: string;
|
||||
status: 'active' | 'inactive' | 'system';
|
||||
created_by: string;
|
||||
template_content: string;
|
||||
variables: string; // JSON字符串
|
||||
}
|
||||
|
||||
// 样式链接
|
||||
export function links() {
|
||||
return [{ rel: "stylesheet", href: indexStyles }];
|
||||
@@ -44,18 +31,28 @@ interface LoaderData {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// 定义 Action 返回数据类型
|
||||
interface ActionData {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// 数据加载器
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const name = url.searchParams.get('name') || undefined;
|
||||
const type = url.searchParams.get('type') || undefined;
|
||||
const status = url.searchParams.get('status') || undefined;
|
||||
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
||||
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
|
||||
|
||||
|
||||
// console.log('加载提示词模板参数:', { name, type, status, page, pageSize });
|
||||
|
||||
|
||||
// 从 API 获取数据
|
||||
const result = await getPromptTemplates({
|
||||
name,
|
||||
@@ -63,7 +60,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
status,
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
}, frontendJWT);
|
||||
|
||||
if (result.error) {
|
||||
console.error('获取提示词模板失败:', result.error);
|
||||
@@ -102,12 +99,43 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
}
|
||||
}
|
||||
|
||||
// Action函数 - 处理删除请求
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const id = formData.get("id") as string;
|
||||
const intent = formData.get("intent") as string;
|
||||
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (intent === "delete" && id) {
|
||||
try {
|
||||
const result = await deletePromptTemplate(id, frontendJWT);
|
||||
|
||||
if (result.error) {
|
||||
return Response.json({ success: false, error: result.error }, { status: result.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true });
|
||||
} catch (error) {
|
||||
return Response.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : "删除提示词模板失败" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Response.json({ success: false, error: "无效的操作" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 页面组件
|
||||
export default function PromptsIndex() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { templates, total, currentPage, pageSize, error } = useLoaderData<typeof loader>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const fetcher = useFetcher<ActionData>();
|
||||
|
||||
// 处理搜索名称
|
||||
const handleNameSearch = (value: string) => {
|
||||
@@ -176,26 +204,28 @@ export default function PromptsIndex() {
|
||||
};
|
||||
|
||||
// 删除模板
|
||||
const handleDeleteTemplate = async (id: string) => {
|
||||
const handleDeleteTemplate = (id: string) => {
|
||||
if (confirm('确定要删除该模板吗?删除后无法恢复。')) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await deletePromptTemplate(id);
|
||||
if (result.error) {
|
||||
alert(`删除失败: ${result.error}`);
|
||||
} else {
|
||||
alert('删除成功!');
|
||||
// 刷新页面
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`删除失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
formData.append('intent', 'delete');
|
||||
|
||||
fetcher.submit(formData, { method: 'post' });
|
||||
}
|
||||
};
|
||||
|
||||
// 监听 fetcher 状态变化
|
||||
useEffect(() => {
|
||||
if (fetcher.state === 'idle' && fetcher.data) {
|
||||
if (fetcher.data.success) {
|
||||
alert('删除成功!');
|
||||
window.location.reload();
|
||||
} else if (fetcher.data.error) {
|
||||
alert(`删除失败: ${fetcher.data.error}`);
|
||||
}
|
||||
}
|
||||
}, [fetcher.state, fetcher.data]);
|
||||
|
||||
// 处理分页
|
||||
const handlePageChange = (page: number) => {
|
||||
const newParams = new URLSearchParams(searchParams);
|
||||
|
||||
@@ -64,28 +64,32 @@ interface ActionData {
|
||||
// 加载函数
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const id = url.searchParams.get("id");
|
||||
const mode = url.searchParams.get("mode") || "create";
|
||||
|
||||
|
||||
// 模板数据,如果是新建则为空
|
||||
let template = null;
|
||||
|
||||
|
||||
if (id) {
|
||||
// 从API获取数据
|
||||
const result = await getPromptTemplate(id);
|
||||
|
||||
const result = await getPromptTemplate(id, frontendJWT);
|
||||
|
||||
if (result.error) {
|
||||
console.error('获取提示词模板失败:', result.error);
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
|
||||
template = result.data || null;
|
||||
if (!template) {
|
||||
throw new Error(`未找到ID为${id}的模板`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Response.json({
|
||||
template,
|
||||
mode
|
||||
@@ -104,6 +108,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
// Action函数 - 处理表单提交
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const formData = await request.formData();
|
||||
const id = formData.get("id") as string;
|
||||
const template_name = formData.get("template_name") as string;
|
||||
@@ -158,10 +165,10 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
let result;
|
||||
if (id) {
|
||||
// 更新模板
|
||||
result = await updatePromptTemplate(id, apiTemplate);
|
||||
result = await updatePromptTemplate(id, apiTemplate, frontendJWT);
|
||||
} else {
|
||||
// 创建模板
|
||||
result = await createPromptTemplate(apiTemplate);
|
||||
result = await createPromptTemplate(apiTemplate, frontendJWT);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
|
||||
@@ -238,13 +238,13 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
|
||||
try {
|
||||
const response = await updateReviewResult(reviewPointResultId, editAuditStatusId, result, message, request);
|
||||
|
||||
|
||||
if (response.error) {
|
||||
console.error('updateReviewResult返回错误:', response.error);
|
||||
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, data: response.data, intent: "confirmReviewResults" });
|
||||
|
||||
return Response.json({ success: true, data: response.data, intent: "updateReviewResult" });
|
||||
} catch (updateError) {
|
||||
console.error('调用updateReviewResult时发生异常:', updateError);
|
||||
return Response.json({
|
||||
|
||||
@@ -21,13 +21,17 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
export async function loader() {
|
||||
export async function loader({ request }: { request: Request }) {
|
||||
try {
|
||||
const response = await getRuleGroups();
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const response = await getRuleGroups(frontendJWT);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return Response.json({ groups: response.data });
|
||||
return Response.json({ groups: response.data, frontendJWT });
|
||||
} catch (error) {
|
||||
console.error('加载评查点分组失败:', error);
|
||||
return Response.json({ groups: [] });
|
||||
@@ -35,7 +39,7 @@ export async function loader() {
|
||||
}
|
||||
|
||||
export default function RuleGroupsIndex() {
|
||||
const { groups: initialGroups } = useLoaderData<typeof loader>();
|
||||
const { groups: initialGroups, frontendJWT } = useLoaderData<typeof loader>();
|
||||
const rootData = useRouteLoaderData("root") as { userRole: string };
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
@@ -65,7 +69,7 @@ export default function RuleGroupsIndex() {
|
||||
// 并行加载所有父分组的子分组
|
||||
const promises = initialGroups.map(async (group: RuleGroup) => {
|
||||
try {
|
||||
const response = await getChildGroups(group.id);
|
||||
const response = await getChildGroups(group.id, frontendJWT);
|
||||
if (response.error) {
|
||||
console.error(`加载分组 ${group.id} 的子分组失败:`, response.error);
|
||||
return { parentId: group.id, children: [] };
|
||||
@@ -139,7 +143,7 @@ export default function RuleGroupsIndex() {
|
||||
}
|
||||
|
||||
// 否则加载子分组
|
||||
const response = await getChildGroups(groupId);
|
||||
const response = await getChildGroups(groupId, frontendJWT);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
@@ -187,7 +191,7 @@ export default function RuleGroupsIndex() {
|
||||
const handleDeleteGroup = async (groupId: string) => {
|
||||
if (confirm("确定要删除该分组吗?此操作将同时删除该分组下的所有评查点,且不可恢复。")) {
|
||||
try {
|
||||
const result = await deleteRuleGroup(groupId);
|
||||
const result = await deleteRuleGroup(groupId, frontendJWT);
|
||||
if (result.success) {
|
||||
// 从本地状态中移除被删除的分组
|
||||
setGroups(prev => {
|
||||
|
||||
@@ -91,12 +91,16 @@ function mapApiToFrontend(apiGroup: ApiRuleGroup): RuleGroup {
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// console.log("rule-groups.new loader被调用,URL:", request.url);
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const id = url.searchParams.get("id");
|
||||
// console.log("获取到的ID参数:", id);
|
||||
|
||||
// 获取一级分组列表 (用于选择父级分组)
|
||||
const parentGroupsResponse = await getRuleGroups();
|
||||
const parentGroupsResponse = await getRuleGroups(frontendJWT);
|
||||
if (parentGroupsResponse.error) {
|
||||
console.error("获取父分组列表失败:", parentGroupsResponse.error);
|
||||
throw new Error(parentGroupsResponse.error);
|
||||
@@ -112,7 +116,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
// 如果有ID,获取分组详情
|
||||
if (id) {
|
||||
const groupResponse = await getRuleGroup(id);
|
||||
const groupResponse = await getRuleGroup(id, frontendJWT);
|
||||
if (groupResponse.error) {
|
||||
console.error("获取分组详情失败:", groupResponse.error);
|
||||
throw new Error(groupResponse.error);
|
||||
@@ -146,6 +150,10 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 提取表单数据
|
||||
const id = formData.get("id") as string | null;
|
||||
const name = formData.get("name") as string;
|
||||
@@ -193,9 +201,9 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
// 根据是否有ID决定是创建还是更新
|
||||
let response;
|
||||
if (id) {
|
||||
response = await updateRuleGroup(id, saveData);
|
||||
response = await updateRuleGroup(id, saveData, frontendJWT);
|
||||
} else {
|
||||
response = await createRuleGroup(saveData);
|
||||
response = await createRuleGroup(saveData, frontendJWT);
|
||||
}
|
||||
|
||||
// 处理API响应
|
||||
|
||||
@@ -71,7 +71,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
try {
|
||||
// 获取文档类型列表
|
||||
const typesResponse = await getDocumentTypes({pageSize:500});
|
||||
const typesResponse = await getDocumentTypes({pageSize:500}, frontendJWT);
|
||||
const documentTypes = typesResponse.data?.types || [];
|
||||
|
||||
// 返回初始空数据,客户端将根据 sessionStorage 中的 reviewType 加载实际数据
|
||||
@@ -175,7 +175,7 @@ export default function RulesFiles() {
|
||||
const userId = userInfo?.user_id?.toString();
|
||||
|
||||
// 获取文件列表
|
||||
const filesResponse = await getReviewFiles(searchParams, null, userId);
|
||||
const filesResponse = await getReviewFiles({...searchParams, token: frontendJWT}, null, userId);
|
||||
if (filesResponse.error) {
|
||||
throw new Error(filesResponse.error);
|
||||
}
|
||||
@@ -240,14 +240,17 @@ export default function RulesFiles() {
|
||||
|
||||
// 从loader data中获取用户ID
|
||||
const userId = userInfo?.user_id?.toString();
|
||||
|
||||
|
||||
// 添加 token 参数到 apiSearchParams
|
||||
apiSearchParams.token = frontendJWT;
|
||||
|
||||
// 获取文件列表
|
||||
getReviewFiles(apiSearchParams, null, userId)
|
||||
.then(filesResponse => {
|
||||
if (filesResponse.error) {
|
||||
throw new Error(filesResponse.error);
|
||||
}
|
||||
|
||||
|
||||
setFiles(filesResponse.data?.files || []);
|
||||
setTotalCount(filesResponse.data?.total || 0);
|
||||
})
|
||||
@@ -335,7 +338,7 @@ export default function RulesFiles() {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await updateDocumentAuditStatus(fileId, 2, userId);
|
||||
const response = await updateDocumentAuditStatus(fileId, 2, userId, frontendJWT);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
@@ -153,9 +153,10 @@ export default function RuleNew() {
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [instanceKey, setInstanceKey] = useState<string>('new');
|
||||
// 从root路由获取用户角色,而不是从sessionStorage
|
||||
const rootData = useRouteLoaderData("root") as { userRole: UserRole };
|
||||
// 从root路由获取用户角色和JWT token
|
||||
const rootData = useRouteLoaderData("root") as { userRole: UserRole; frontendJWT?: string };
|
||||
const userRole = rootData?.userRole || 'common';
|
||||
const frontendJWT = rootData?.frontendJWT;
|
||||
|
||||
const [formData, setFormData] = useState<EvaluationPoint>({});
|
||||
const [evaluationPointGroups, setEvaluationPointGroups] = useState<EvaluationPointGroup[]>([]);
|
||||
@@ -284,7 +285,8 @@ export default function RuleNew() {
|
||||
const postgrestParams = {
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
},
|
||||
token: frontendJWT
|
||||
};
|
||||
const response = await postgrestGet('evaluation_points', postgrestParams);
|
||||
|
||||
@@ -332,7 +334,7 @@ export default function RuleNew() {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [navigate, extractFieldsFromFormData, resetFormData]);
|
||||
}, [navigate, extractFieldsFromFormData, resetFormData, frontendJWT]);
|
||||
|
||||
/**
|
||||
* 获取评查点组数据
|
||||
@@ -341,7 +343,7 @@ export default function RuleNew() {
|
||||
const fetchEvaluationPointGroups = useCallback(async () => {
|
||||
try {
|
||||
// console.log("获取评查点组数据");
|
||||
const response = await postgrestGet('evaluation_point_groups');
|
||||
const response = await postgrestGet('evaluation_point_groups', { token: frontendJWT });
|
||||
|
||||
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
||||
setEvaluationPointGroups(response.data);
|
||||
@@ -351,7 +353,7 @@ export default function RuleNew() {
|
||||
// 显示错误提示但不影响应用继续使用
|
||||
toastService.error(`获取评查点组数据失败: ${error instanceof Error ? error.message : '未知错误'}\n将使用默认数据`);
|
||||
}
|
||||
}, []);
|
||||
}, [frontendJWT]);
|
||||
|
||||
const handleSave = async () => {
|
||||
// console.log("保存评查点", formData);
|
||||
@@ -582,9 +584,9 @@ export default function RuleNew() {
|
||||
|
||||
let response;
|
||||
if (isEditMode) {
|
||||
response = await postgrestPut('evaluation_points', finalData, {id: formData.id!});
|
||||
response = await postgrestPut('evaluation_points', finalData, {id: formData.id!}, frontendJWT);
|
||||
} else {
|
||||
response = await postgrestPost('evaluation_points', finalData);
|
||||
response = await postgrestPost('evaluation_points', finalData, frontendJWT);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -97,8 +97,12 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
};
|
||||
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 获取评查点类型列表,供前端筛选使用
|
||||
const typeResponse = await getRuleTypes();
|
||||
const typeResponse = await getRuleTypes(undefined, frontendJWT);
|
||||
|
||||
if (typeResponse.error) {
|
||||
console.error('获取评查点类型失败:', typeResponse.error);
|
||||
@@ -113,7 +117,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
currentPage: params.page,
|
||||
pageSize: params.pageSize,
|
||||
ruleTypes,
|
||||
initialLoad: true
|
||||
initialLoad: true,
|
||||
frontendJWT
|
||||
}, {
|
||||
headers: {
|
||||
"Cache-Control": "max-age=60, s-maxage=180"
|
||||
@@ -139,11 +144,15 @@ export async function action({ request }: LoaderFunctionArgs) {
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (_action === 'delete') {
|
||||
// 调用API删除评查点
|
||||
// console.log(`删除评查点 ${ruleId}`);
|
||||
|
||||
const deleteResponse = await deleteRule(ruleId as string);
|
||||
const deleteResponse = await deleteRule(ruleId as string, frontendJWT);
|
||||
|
||||
if (deleteResponse.error) {
|
||||
return Response.json({ result: false, message: deleteResponse.error }, { status: deleteResponse.status || 500 });
|
||||
@@ -257,7 +266,7 @@ export default function RulesIndex() {
|
||||
|
||||
// 获取评查点类型
|
||||
try {
|
||||
const typeResponse = await getRuleTypes(typeToUse);
|
||||
const typeResponse = await getRuleTypes(typeToUse, loaderData.frontendJWT);
|
||||
if (typeResponse.data) {
|
||||
setRuleTypes(typeResponse.data);
|
||||
}
|
||||
@@ -273,7 +282,8 @@ export default function RulesIndex() {
|
||||
keyword: searchParams.get('keyword') || undefined,
|
||||
page: currentPage,
|
||||
pageSize,
|
||||
reviewType: typeToUse
|
||||
reviewType: typeToUse,
|
||||
token: loaderData.frontendJWT
|
||||
};
|
||||
|
||||
// 调用 API 获取数据
|
||||
@@ -307,7 +317,7 @@ export default function RulesIndex() {
|
||||
const loadRuleGroups = async () => {
|
||||
setLoadingGroups(true);
|
||||
try {
|
||||
const response = await getRuleGroupsByType(ruleTypeParam);
|
||||
const response = await getRuleGroupsByType(ruleTypeParam, loaderData.frontendJWT);
|
||||
if (response.data) {
|
||||
setRuleGroups(response.data);
|
||||
} else if (response.error) {
|
||||
|
||||
@@ -138,6 +138,27 @@
|
||||
shadow-[0_0_15px_rgba(0,0,0,0.05)];
|
||||
}
|
||||
|
||||
/* 侧边栏滚动区域样式 */
|
||||
.sidebar-scroll-area {
|
||||
}
|
||||
|
||||
/* 滚动条样式 - 与背景色一致 */
|
||||
.sidebar-scroll-area::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.sidebar-scroll-area::-webkit-scrollbar-track {
|
||||
@apply bg-white;
|
||||
}
|
||||
|
||||
.sidebar-scroll-area::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-400 rounded;
|
||||
}
|
||||
|
||||
.sidebar-scroll-area::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-gray-500;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
@apply w-20;
|
||||
}
|
||||
|
||||
+17
-5
@@ -11,8 +11,14 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
const { sign, verify, decode } = jwt;
|
||||
|
||||
// JWT密钥 - 在生产环境中应该从环境变量读取
|
||||
const JWT_SECRET = 'gdyc-super-secrets-jjwtt-key-change-this-in-production-20250721-from-login-callback';
|
||||
// JWT密钥 - 从环境变量读取,如果未设置则抛出错误
|
||||
const JWT_SECRET: string = (() => {
|
||||
const secret = process.env.JWT_SECRET;
|
||||
if (!secret) {
|
||||
throw new Error('JWT_SECRET environment variable is not set. Please add it to your .env file.');
|
||||
}
|
||||
return secret;
|
||||
})();
|
||||
|
||||
// JWT配置
|
||||
const JWT_CONFIG = {
|
||||
@@ -104,13 +110,19 @@ export class JWTUtils {
|
||||
*/
|
||||
static verifyJWT(token: string): { valid: boolean; payload?: JWTPayload; error?: string } {
|
||||
try {
|
||||
const payload = verify(token, JWT_SECRET, {
|
||||
const decoded = verify(token, JWT_SECRET, {
|
||||
algorithms: [JWT_CONFIG.algorithm],
|
||||
issuer: JWT_CONFIG.issuer,
|
||||
audience: JWT_CONFIG.audience
|
||||
}) as JWTPayload;
|
||||
});
|
||||
|
||||
return { valid: true, payload };
|
||||
// 验证返回的payload是否包含必需字段
|
||||
if (typeof decoded === 'object' && decoded !== null && 'sub' in decoded) {
|
||||
const payload = decoded as JWTPayload;
|
||||
return { valid: true, payload };
|
||||
}
|
||||
|
||||
return { valid: false, error: 'JWT载荷格式不正确' };
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return { valid: false, error: error.message };
|
||||
|
||||
Reference in New Issue
Block a user