diff --git a/app/api/evaluation_points/rules-files.ts b/app/api/evaluation_points/rules-files.ts index ee1f865..ec7f06c 100644 --- a/app/api/evaluation_points/rules-files.ts +++ b/app/api/evaluation_points/rules-files.ts @@ -1,9 +1,4 @@ import { postgrestPut, postgrestPost } from '../postgrest-client'; -// import dayjs from 'dayjs'; -// import { getDocumentTypes } from '../document-types/document-types'; -// import type { DocumentTypeUI } from '../document-types/document-types'; -// import weekday from 'dayjs/plugin/weekday'; -// import updateLocale from 'dayjs/plugin/updateLocale'; import { formatDate } from '../../utils'; // 文档数据库表接口 @@ -107,58 +102,6 @@ export interface DocumentSearchParams { pageSize?: number; // 每页条数 } -// 添加评查结果和评查点类型定义 -// 评查结果类型 -// interface EvaluationResult { -// id: string | number; -// document_id: string | number; -// evaluation_point_id: string | number; -// evaluated_results?: { -// result?: boolean; -// message?: string; -// data?: string; -// [key: string]: unknown; -// }; -// [key: string]: unknown; -// } - -// 评查点类型 -// interface EvaluationPoint { -// id: string | number; -// post_action?: string; -// score?: number; -// [key: string]: unknown; -// } - -// 文档评查状态结果 -// interface DocumentReviewResult { -// status: number; -// issueCount: number; -// passCount: number; -// warningCount: number; -// failCount: number; -// manualCount: number; -// } - -// /** -// * 从不同格式的 API 响应中提取数据 -// * @param responseData API 响应数据 -// * @returns 提取后的数据或 null -// */ -// function extractApiData(responseData: unknown): T | null { -// if (!responseData) return null; - -// // 格式1: { code: number, msg: string, data: T } -// if (typeof responseData === 'object' && responseData !== null && -// 'code' in responseData && -// 'data' in responseData && -// (responseData as { data: unknown }).data) { -// return (responseData as { data: T }).data; -// } - -// // 格式2: 直接是数据对象 -// return responseData as T; -// } /** * 将评查状态代码映射到UI状态 @@ -202,14 +145,21 @@ export function getFileExtension(fileName: string): string { /** * 获取评查文件列表 * @param searchParams 搜索参数 + * @param documentIds 文档ID数组(可选) + * @param userId 用户ID * @returns 评查文件列表和总数 */ -export async function getReviewFiles(searchParams: DocumentSearchParams = {}, documentIds: number[] | null = null): Promise<{ +export async function getReviewFiles(searchParams: DocumentSearchParams = {}, documentIds: number[] | null = null, userId?: string): Promise<{ data?: { files: ReviewFileUI[], total: number }; error?: string; status?: number; }> { try { + // 确保userId必须存在,如果不存在则抛出错误 + if (!userId) { + return { error: '用户身份验证失败,无法获取评查文件列表', status: 401 }; + } + const { page = 1, pageSize = 10, @@ -242,6 +192,7 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}, do p_date_from: dateFrom || null, p_date_to: dateTo || null, p_document_ids: documentIds || null, + p_user_id: parseInt(userId, 10), // 强制要求传递用户ID }; const listParams = { @@ -364,9 +315,10 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}, do * 更新文件的审核状态 * @param id 文件ID * @param auditStatus 审核状态 + * @param userId 用户ID * @returns 更新结果 */ -export async function updateDocumentAuditStatus(id: string, auditStatus: number): Promise<{ +export async function updateDocumentAuditStatus(id: string, auditStatus: number, userId: string): Promise<{ success?: boolean; error?: string; status?: number; @@ -376,10 +328,17 @@ export async function updateDocumentAuditStatus(id: string, auditStatus: number) return { error: '文件ID不能为空', status: 400 }; } + if (!userId) { + return { error: '用户身份验证失败', status: 401 }; + } + const response = await postgrestPut>( 'documents', { audit_status: auditStatus }, - { id: parseInt(id) } + { + id: parseInt(id), + user_id: parseInt(userId) // 确保只能更新自己的文档 + } ); if (response.error) { diff --git a/app/api/files/documents.ts b/app/api/files/documents.ts index 7b7ee4f..6ac1455 100644 --- a/app/api/files/documents.ts +++ b/app/api/files/documents.ts @@ -304,9 +304,10 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro /** * 删除文档 * @param id 文档ID + * @param userId 用户ID * @returns 删除结果 */ -export async function deleteDocument(id: string): Promise<{ +export async function deleteDocument(id: string, userId: string): Promise<{ success?: boolean; error?: string; status?: number; @@ -316,11 +317,16 @@ export async function deleteDocument(id: string): Promise<{ return { error: '文档ID不能为空', status: 400 }; } + if (!userId) { + return { error: '用户身份验证失败', status: 401 }; + } + const response = await postgrestDelete( 'documents', { filter: { - 'id': `eq.${id}` + 'id': `eq.${id}`, + 'user_id': `eq.${userId}` // 确保只能删除自己的文档 } } ); @@ -344,7 +350,7 @@ export async function deleteDocument(id: string): Promise<{ * @param id 文档ID * @returns 文档详情 */ -export async function getDocument(id: string): Promise<{ +export async function getDocument(id: string, userId: string): Promise<{ data?: DocumentUI; error?: string; status?: number; @@ -354,11 +360,16 @@ export async function getDocument(id: string): Promise<{ return { error: '文档ID不能为空', status: 400 }; } + if (!userId) { + return { error: '用户身份验证失败', status: 401 }; + } + const response = await postgrestGet( 'documents', { filter: { - 'id': `eq.${id}` + 'id': `eq.${id}`, + 'user_id': `eq.${userId}` }, limit: 1 } @@ -427,7 +438,7 @@ export async function getFileDownloadUrl(filePath: string): Promise<{ * @param document 部分文档数据 * @returns 更新结果 */ -export async function updateDocument(id: string, document: Partial & { remark?: string }): Promise<{ +export async function updateDocument(id: string, document: Partial & { remark?: string }, userId: string): Promise<{ data?: DocumentUI; error?: string; status?: number; @@ -437,6 +448,10 @@ export async function updateDocument(id: string, document: Partial & return { error: '文档ID不能为空', status: 400 }; } + if (!userId) { + return { error: '用户身份验证失败', status: 401 }; + } + // 准备API数据 - 将UI数据转换为API格式 const apiDocument: Partial = {}; @@ -465,7 +480,10 @@ export async function updateDocument(id: string, document: Partial & const response = await postgrestPut>( 'documents', apiDocument, - { id: parseInt(id) } + { + id: parseInt(id), + user_id: parseInt(userId) // 确保只能更新自己的文档 + } ); if (response.error) { @@ -474,7 +492,7 @@ export async function updateDocument(id: string, document: Partial & } // 获取更新后的完整文档数据 - const updatedResponse = await getDocument(id); + const updatedResponse = await getDocument(id, userId); return updatedResponse; } catch (error) { diff --git a/app/components/cross-checking/DocumentListModal.tsx b/app/components/cross-checking/DocumentListModal.tsx index 3d5a9f3..6a16268 100644 --- a/app/components/cross-checking/DocumentListModal.tsx +++ b/app/components/cross-checking/DocumentListModal.tsx @@ -8,7 +8,7 @@ import { StatusBadge } from '../ui/StatusBadge'; import { Pagination } from '../ui/Pagination'; import { LoadingIndicator } from '../ui/SkeletonScreen'; import type { ReviewFileUI } from '~/api/evaluation_points/rules-files'; -import { updateDocumentAuditStatus } from '~/api/evaluation_points/rules-files'; +// import { updateDocumentAuditStatus } from '~/api/evaluation_points/rules-files'; import { toastService } from '../ui/Toast'; // 导出样式链接 @@ -49,10 +49,13 @@ export function DocumentListModal({ // 检查audit_status是否为0,如果是则更新为2 if (auditStatus === 0 || auditStatus === null) { try { - const response = await updateDocumentAuditStatus(fileId, 2); - if (response.error) { - throw new Error(response.error); - } + // TODO: 这里需要从父组件传递 userId,或者重新设计这个函数的调用方式 + // 暂时跳过状态更新,直接进入查看 + // const response = await updateDocumentAuditStatus(fileId, 2, userId); + // if (response.error) { + // throw new Error(response.error); + // } + console.warn('DocumentListModal: 跳过审核状态更新,需要传递 userId 参数'); } catch (error) { console.error('更新文件审核状态时出错:', error); toastService.error(`更新文件审核状态时出错:${error instanceof Error ? error.message : '未知错误'}`); diff --git a/app/routes/documents._index.tsx b/app/routes/documents._index.tsx index e2e2109..b303f0c 100644 --- a/app/routes/documents._index.tsx +++ b/app/routes/documents._index.tsx @@ -122,12 +122,21 @@ const formatFileSize = (bytes: number) => { // 处理表单提交和删除等操作 export const action = async ({ request }: ActionFunctionArgs) => { try { + // 获取用户会话信息 + const { getUserSession } = await import("~/api/login/auth.server"); + const { userInfo } = await getUserSession(request); + + if (!userInfo?.user_id) { + return Response.json({ result: false, message: "用户身份验证失败" }, { status: 401 }); + } + + const userId = userInfo.user_id.toString(); const formData = await request.formData(); const action = formData.get("_action"); if (action === "delete") { const id = formData.get("id") as string; - const response = await deleteDocument(id); + const response = await deleteDocument(id, userId); if (response.error) { return Response.json({ result: false, message: response.error }, { status: response.status || 500 }); @@ -139,7 +148,7 @@ export const action = async ({ request }: ActionFunctionArgs) => { const ids = formData.getAll("ids") as string[]; // 批量删除处理 - const results = await Promise.all(ids.map(id => deleteDocument(id))); + const results = await Promise.all(ids.map(id => deleteDocument(id, userId))); const failures = results.filter(r => r.error); if (failures.length > 0) { @@ -652,8 +661,15 @@ export default function DocumentsIndex() { // 检查audit_status是否为0,如果是则更新为2 if (auditStatus === 0 || auditStatus === null) { try { + // 从loader data中获取用户ID + const userId = loaderData.userInfo?.user_id?.toString(); + if (!userId) { + toastService.error('用户身份验证失败'); + return; + } + // console.log('开始审核',fileId,auditStatus) - const response = await updateDocumentAuditStatus(fileId.toString(), 2); + const response = await updateDocumentAuditStatus(fileId.toString(), 2, userId); if (response.error) { console.error('更新文件审核状态失败:', response.error); toastService.error('更新文件审核状态失败:' + (response.error || '未知错误')); diff --git a/app/routes/documents.edit.tsx b/app/routes/documents.edit.tsx index 366151d..3353d9d 100644 --- a/app/routes/documents.edit.tsx +++ b/app/routes/documents.edit.tsx @@ -80,6 +80,16 @@ function formatFileSize(bytes: number): string { // Loader函数 export async function loader({ request }: LoaderFunctionArgs) { try { + // 获取用户会话信息 + const { getUserSession } = await import("~/api/login/auth.server"); + const { userInfo } = await getUserSession(request); + + if (!userInfo?.user_id) { + throw new Response("用户身份验证失败", { status: 401 }); + } + + const userId = userInfo.user_id.toString(); + // 从URL查询参数获取文档ID const url = new URL(request.url); const id = url.searchParams.get("id"); @@ -90,7 +100,7 @@ export async function loader({ request }: LoaderFunctionArgs) { // 并行获取文档详情和文档类型列表 const [documentResponse, documentTypesResponse] = await Promise.all([ - getDocument(id), + getDocument(id, userId), getDocumentTypes({ pageSize: 500 }) ]); @@ -114,6 +124,16 @@ export async function loader({ request }: LoaderFunctionArgs) { // Action函数处理表单提交 export async function action({ request }: ActionFunctionArgs) { + // 获取用户会话信息 + const { getUserSession } = await import("~/api/login/auth.server"); + const { userInfo } = await getUserSession(request); + + if (!userInfo?.user_id) { + return Response.json({ error: "用户身份验证失败" }, { status: 401 }); + } + + const userId = userInfo.user_id.toString(); + // 从URL查询参数获取文档ID const url = new URL(request.url); const id = url.searchParams.get("id"); @@ -153,7 +173,7 @@ export async function action({ request }: ActionFunctionArgs) { auditStatus, isTest, remark - }); + }, userId); if (updateResponse.error) { console.error('更新文档失败:', updateResponse.error); diff --git a/app/routes/files.upload.tsx b/app/routes/files.upload.tsx index bec42f9..7ad3e5e 100644 --- a/app/routes/files.upload.tsx +++ b/app/routes/files.upload.tsx @@ -214,11 +214,21 @@ type LoaderData = { documents: Document[]; documentTypes: DocumentType[]; mode: string; + userInfo?: { + user_id?: number; + username?: string; + nick_name?: string; + [key: string]: unknown; + } | null; }; // 添加 loader 函数 export async function loader({ request }: LoaderFunctionArgs) { try { + // 获取用户会话信息 + const { getUserSession } = await import("~/api/login/auth.server"); + const { userInfo } = await getUserSession(request); + // console.log('loader: 开始加载数据...'); const url = new URL(request.url); const mode = url.searchParams.get("mode") || "create"; @@ -240,13 +250,15 @@ export async function loader({ request }: LoaderFunctionArgs) { return Response.json({ mode, documents: documentsResponse.data || [], - documentTypes: typesResponse.data || [] + documentTypes: typesResponse.data || [], + userInfo // 传递用户信息到客户端 }); } catch (error) { console.error('loader: 加载数据失败:', error); return Response.json({ documents: [], - documentTypes: [] + documentTypes: [], + userInfo: null }); } } @@ -1439,8 +1451,15 @@ export default function FilesUpload() { // 检查audit_status是否为0,如果是则更新为2 if (record.audit_status === 0 || record.audit_status === null) { try { + // 从loader data中获取用户ID + const userId = loaderData.userInfo?.user_id?.toString(); + if (!userId) { + toastService.error('用户身份验证失败'); + return; + } + // console.log('【调试-handleViewFile】更新文件审核状态,文件ID:', record.id); - const response = await updateDocumentAuditStatus(record.id.toString(), 2); + const response = await updateDocumentAuditStatus(record.id.toString(), 2, userId); if (response.error) { console.error('【调试-handleViewFile】更新文件审核状态失败:', response.error); toastService.error('更新文件审核状态失败:' + (response.error || '未知错误')); diff --git a/app/routes/rules-files.tsx b/app/routes/rules-files.tsx index ed02863..a8f525f 100644 --- a/app/routes/rules-files.tsx +++ b/app/routes/rules-files.tsx @@ -58,6 +58,10 @@ export const REVIEW_STATUS_LABELS: Record = { // 加载评查文件列表 export async function loader({ request }: LoaderFunctionArgs) { + // 获取用户会话信息 + const { getUserSession } = await import("~/api/login/auth.server"); + const { userInfo } = await getUserSession(request); + // 获取分页参数 const url = new URL(request.url); const currentPage = parseInt(url.searchParams.get("page") || "1", 10); @@ -75,6 +79,7 @@ export async function loader({ request }: LoaderFunctionArgs) { totalCount: 0, currentPage, pageSize, + userInfo, // 传递用户信息到客户端 initialLoad: true }); } catch (error) { @@ -85,7 +90,7 @@ export async function loader({ request }: LoaderFunctionArgs) { export default function RulesFiles() { const navigate = useNavigate(); - const { files: initialFiles, documentTypes: allDocumentTypes, totalCount: initialTotal, currentPage, pageSize, result, message } = useLoaderData(); + const { files: initialFiles, documentTypes: allDocumentTypes, totalCount: initialTotal, currentPage, pageSize, userInfo, result, message } = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const dateFrom = searchParams.get('dateFrom') || ''; const dateTo = searchParams.get('dateTo') || ''; @@ -134,8 +139,11 @@ export default function RulesFiles() { searchParams.fileType = params.fileType; } + // 从loader data中获取用户ID + const userId = userInfo?.user_id?.toString(); + // 获取文件列表 - const filesResponse = await getReviewFiles(searchParams); + const filesResponse = await getReviewFiles(searchParams, null, userId); if (filesResponse.error) { throw new Error(filesResponse.error); } @@ -198,8 +206,11 @@ export default function RulesFiles() { // 设置加载状态 setIsLoading(true); + // 从loader data中获取用户ID + const userId = userInfo?.user_id?.toString(); + // 获取文件列表 - getReviewFiles(apiSearchParams) + getReviewFiles(apiSearchParams, null, userId) .then(filesResponse => { if (filesResponse.error) { throw new Error(filesResponse.error); @@ -281,7 +292,14 @@ export default function RulesFiles() { // 检查audit_status是否为0,如果是则更新为2 if (auditStatus === 0 || auditStatus === null) { try { - const response = await updateDocumentAuditStatus(fileId, 2); + // 从loader data中获取用户ID + const userId = userInfo?.user_id?.toString(); + if (!userId) { + toastService.error('用户身份验证失败'); + return; + } + + const response = await updateDocumentAuditStatus(fileId, 2, userId); if (response.error) { throw new Error(response.error); } diff --git a/sql/update_get_review_files_with_details.sql b/sql/update_get_review_files_with_details.sql index 9760037..bb5e8d5 100644 --- a/sql/update_get_review_files_with_details.sql +++ b/sql/update_get_review_files_with_details.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION "public"."get_review_files_with_details"("p_keyword" text=NULL::text, "p_typeid" _int4=NULL::integer[], "p_evaluations_status" int4=NULL::integer, "p_date_from" date=NULL::date, "p_date_to" date=NULL::date, "p_sort_order" text='created_at_desc'::text, "p_page" int4=1, "p_page_size" int4=10) +CREATE OR REPLACE FUNCTION "public"."get_review_files_with_details"("p_keyword" text=NULL::text, "p_typeid" _int4=NULL::integer[], "p_evaluations_status" int4=NULL::integer, "p_date_from" date=NULL::date, "p_date_to" date=NULL::date, "p_sort_order" text='created_at_desc'::text, "p_page" int4=1, "p_page_size" int4=10, "p_document_ids" _int4=NULL::integer[], "p_user_id" int4=NULL::integer) RETURNS TABLE("id" int4, "status" varchar, "path" varchar, "file_name" varchar, "file_code" varchar, "file_type_name" varchar, "file_type_id" int4, "file_size" int4, "upload_time" timestamptz, "created_at" timestamptz, "evaluations_status" int4, "audit_status" int4, "created_by_user_id" int4, "issue_count" int8, "total_score" numeric, "pass_count" int8, "warning_count" int8, "fail_count" int8, "manual_count" int8, "issues" jsonb) AS $BODY$ DECLARE offset_val integer; @@ -6,6 +6,11 @@ DECLARE sort_direction text; BEGIN offset_val := (p_page - 1) * p_page_size; + + -- 如果p_user_id为NULL,直接返回0 + IF p_user_id IS NULL THEN + RETURN; + END IF; SELECT CASE @@ -70,13 +75,44 @@ BEGIN ($2 IS NULL OR d.type_id = ANY($2)) AND ($3 IS NULL OR d.evaluations_status = $3) AND ($4 IS NULL OR d.created_at >= $4) AND - ($5 IS NULL OR d.created_at < ($5 + INTERVAL ''1 day'')) + ($5 IS NULL OR d.created_at < ($5 + INTERVAL ''1 day'')) AND + ($8 IS NULL OR d.id = ANY($8)) AND + ($9 d.user_id = $9) ORDER BY %I %s LIMIT $6 OFFSET $7 ', sort_column, sort_direction) - USING p_keyword, p_typeid, p_evaluations_status, p_date_from, p_date_to, p_page_size, offset_val; + USING p_keyword, p_typeid, p_evaluations_status, p_date_from, p_date_to, p_page_size, offset_val, p_document_ids, p_user_id; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100 - ROWS 1000; \ No newline at end of file + ROWS 1000; + +-- 同时创建或更新 count_review_files 函数 +CREATE OR REPLACE FUNCTION "public"."count_review_files"("p_keyword" text=NULL::text, "p_typeid" _int4=NULL::integer[], "p_evaluations_status" int4=NULL::integer, "p_date_from" date=NULL::date, "p_date_to" date=NULL::date, "p_document_ids" _int4=NULL::integer[], "p_user_id" int4=NULL::integer) + RETURNS int4 AS $BODY$ +DECLARE + total_count integer; +BEGIN + -- 如果p_user_id为NULL,直接返回0 + IF p_user_id IS NULL THEN + RETURN 0; + END IF; + + SELECT COUNT(*) + INTO total_count + FROM documents d + WHERE + (p_keyword IS NULL OR (d.name ILIKE '%' || p_keyword || '%' OR d.document_number ILIKE '%' || p_keyword || '%')) AND + (p_typeid IS NULL OR d.type_id = ANY(p_typeid)) AND + (p_evaluations_status IS NULL OR d.evaluations_status = p_evaluations_status) AND + (p_date_from IS NULL OR d.created_at >= p_date_from) AND + (p_date_to IS NULL OR d.created_at < (p_date_to + INTERVAL '1 day')) AND + (p_document_ids IS NULL OR d.id = ANY(p_document_ids)) AND + d.user_id = p_user_id; + + RETURN total_count; +END; +$BODY$ + LANGUAGE plpgsql STABLE + COST 100; \ No newline at end of file