添加jwt验证,添加交叉评查首页加载对接接口,评查任务文档列表对接接口,意见列表对接接口
This commit is contained in:
+34
-2
@@ -2,6 +2,7 @@ import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { OAuthClient } from "~/api/login/oauth-client";
|
||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||
import { sessionStorage, saveUserInfo } from "~/api/login/auth.server";
|
||||
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
|
||||
import { toastService } from "~/components/ui";
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
@@ -73,9 +74,40 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
if (!saveResult.success) {
|
||||
console.error("保存用户信息到数据库失败:", saveResult.error);
|
||||
// 注意:即使保存到数据库失败,我们仍然继续登录流程,因为用户已经通过了身份验证
|
||||
} else {
|
||||
console.log("用户信息已成功保存到数据库");
|
||||
return redirect("/login?error=save_user_error");
|
||||
}
|
||||
|
||||
console.log("用户信息已成功保存到数据库");
|
||||
const savedUserData = saveResult.data!;
|
||||
|
||||
// 生成前端专用JWT
|
||||
const jwtUserInfo: UserInfoForJWT = {
|
||||
sub: userInfo.data.sub,
|
||||
user_id: savedUserData.id!,
|
||||
username: savedUserData.username,
|
||||
nick_name: savedUserData.nick_name,
|
||||
email: savedUserData.email,
|
||||
phone_number: savedUserData.phone_number,
|
||||
ou_id: savedUserData.ou_id,
|
||||
ou_name: savedUserData.ou_name,
|
||||
is_leader: savedUserData.is_leader,
|
||||
user_role: userRole
|
||||
};
|
||||
|
||||
const frontendJWT = JWTUtils.generateJWT(jwtUserInfo, tokenResponse.expires_in);
|
||||
console.log("前端JWT已生成");
|
||||
|
||||
// 将JWT存储在session中
|
||||
session.set("frontendJWT", frontendJWT);
|
||||
|
||||
// 更新userInfo以包含数据库ID和JWT信息
|
||||
const enhancedUserInfo = {
|
||||
...userInfo.data,
|
||||
user_id: savedUserData.id,
|
||||
user_role: userRole,
|
||||
frontend_jwt: frontendJWT
|
||||
};
|
||||
session.set("userInfo", enhancedUserInfo);
|
||||
|
||||
return redirect(redirectTo, {
|
||||
headers: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
|
||||
import { useLoaderData, useSearchParams, useNavigate, useFetcher } from "@remix-run/react";
|
||||
import { Button } from '~/components/ui/Button';
|
||||
import { Card } from '~/components/ui/Card';
|
||||
@@ -17,12 +17,12 @@ import {
|
||||
deleteCrossCheckingTask,
|
||||
getCrossCheckingTaskDetail,
|
||||
type CrossCheckingTask,
|
||||
type TaskDocument,
|
||||
type TaskListParams,
|
||||
CrossCheckingTaskStatus,
|
||||
CrossCheckingTaskType,
|
||||
CrossCheckingDocType
|
||||
} from '~/api/cross-checking/cross-files';
|
||||
import type { ReviewFileUI } from '~/api/evaluation_points/rules-files';
|
||||
|
||||
export const links = () => [
|
||||
{ rel: "stylesheet", href: crossCheckingStyles }
|
||||
@@ -68,10 +68,14 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
};
|
||||
|
||||
try {
|
||||
// 获取任务列表和统计数据
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 获取任务列表和统计数据,传递用户信息和JWT
|
||||
const [tasksResponse, statsResponse] = await Promise.all([
|
||||
getCrossCheckingTasks(params),
|
||||
getCrossCheckingStats()
|
||||
getCrossCheckingTasks(params, userInfo, frontendJWT),
|
||||
getCrossCheckingStats(userInfo, frontendJWT)
|
||||
]);
|
||||
|
||||
if (!tasksResponse.success) {
|
||||
@@ -108,17 +112,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function action({ request }: LoaderFunctionArgs) {
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const _action = formData.get('_action');
|
||||
const taskId = formData.get('taskId');
|
||||
|
||||
if (!taskId) {
|
||||
return Response.json({ result: false, message: "缺少任务ID" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
if (_action === 'delete') {
|
||||
if (_action === 'delete' && taskId) {
|
||||
try {
|
||||
const deleteResponse = await deleteCrossCheckingTask(Number(taskId));
|
||||
|
||||
if (!deleteResponse.success) {
|
||||
@@ -129,13 +129,57 @@ export async function action({ request }: LoaderFunctionArgs) {
|
||||
}
|
||||
|
||||
return Response.json({ result: true, message: "任务删除成功" }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('操作任务失败:', error);
|
||||
return Response.json({
|
||||
result: false,
|
||||
message: error instanceof Error ? error.message : "操作失败"
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
if (_action === 'getTaskDetail') {
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const page = parseInt(formData.get('page') as string || '1', 10);
|
||||
const pageSize = parseInt(formData.get('pageSize') as string || '10', 10);
|
||||
|
||||
if (!taskId) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: "缺少必要参数"
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const response = await getCrossCheckingTaskDetail(
|
||||
Number(taskId),
|
||||
page,
|
||||
pageSize,
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: response.error
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
// console.log('用户任务详情返回:', response.data);
|
||||
return Response.json({
|
||||
success: true,
|
||||
data: response.data
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取任务详情失败:', error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "获取任务详情失败"
|
||||
}, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('操作任务失败:', error);
|
||||
return Response.json({
|
||||
result: false,
|
||||
message: error instanceof Error ? error.message : "操作失败"
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
return Response.json({ result: false, message: "无效的操作" }, { status: 400 });
|
||||
@@ -174,7 +218,7 @@ export default function CrossCheckingIndex() {
|
||||
const [modalState, setModalState] = useState<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
files: ReviewFileUI[];
|
||||
files: TaskDocument[];
|
||||
loading: boolean;
|
||||
// 分页相关状态
|
||||
currentPage: number;
|
||||
@@ -198,9 +242,9 @@ export default function CrossCheckingIndex() {
|
||||
};
|
||||
|
||||
// 处理查看结果 - 打开文档列表模态框
|
||||
const handleViewResult = async (taskId: number, documentIds: number[]) => {
|
||||
const handleViewResult = async (taskId: number) => {
|
||||
// 存储任务信息用于分页
|
||||
setCurrentTaskInfo({ taskId, documentIds });
|
||||
setCurrentTaskInfo({ taskId });
|
||||
|
||||
// 打开模态框
|
||||
setModalState(prev => ({
|
||||
@@ -211,7 +255,7 @@ export default function CrossCheckingIndex() {
|
||||
}));
|
||||
|
||||
// 加载第一页数据
|
||||
await loadModalData(taskId, documentIds, 1, 10);
|
||||
await loadModalData(taskId, 1, 10);
|
||||
};
|
||||
|
||||
// 关闭模态框
|
||||
@@ -236,35 +280,24 @@ export default function CrossCheckingIndex() {
|
||||
// 存储当前任务信息用于分页
|
||||
const [currentTaskInfo, setCurrentTaskInfo] = useState<{
|
||||
taskId: number;
|
||||
documentIds: number[];
|
||||
} | null>(null);
|
||||
|
||||
// 加载分页数据
|
||||
const loadModalData = async (taskId: number, documentIds: number[], page: number = 1, pageSize: number = 10) => {
|
||||
const loadModalData = async (taskId: number, page: number = 1, pageSize: number = 10) => {
|
||||
try {
|
||||
setModalState(prev => ({
|
||||
...prev,
|
||||
loading: true
|
||||
}));
|
||||
|
||||
// 调用支持分页的API,传递分页参数
|
||||
const response = await getCrossCheckingTaskDetail(taskId, documentIds, page, pageSize);
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
// 使用 fetcher 调用 action 来获取任务详情
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'getTaskDetail');
|
||||
formData.append('taskId', taskId.toString());
|
||||
formData.append('page', page.toString());
|
||||
formData.append('pageSize', pageSize.toString());
|
||||
|
||||
const { task, files, total } = response.data!;
|
||||
|
||||
setModalState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
title: `${task.taskName} - 文档列表`,
|
||||
files: files,
|
||||
total: total,
|
||||
currentPage: page,
|
||||
pageSize: pageSize
|
||||
}));
|
||||
fetcher.submit(formData, { method: "POST" });
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取任务文档列表失败:', error);
|
||||
@@ -280,14 +313,14 @@ export default function CrossCheckingIndex() {
|
||||
// 处理模态框分页变化
|
||||
const handleModalPageChange = (page: number) => {
|
||||
if (currentTaskInfo) {
|
||||
loadModalData(currentTaskInfo.taskId, currentTaskInfo.documentIds, page, modalState.pageSize);
|
||||
loadModalData(currentTaskInfo.taskId, page, modalState.pageSize);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理模态框每页大小变化
|
||||
const handleModalPageSizeChange = (size: number) => {
|
||||
if (currentTaskInfo) {
|
||||
loadModalData(currentTaskInfo.taskId, currentTaskInfo.documentIds, 1, size);
|
||||
loadModalData(currentTaskInfo.taskId, 1, size);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -315,7 +348,7 @@ export default function CrossCheckingIndex() {
|
||||
type="primary"
|
||||
size="small"
|
||||
className="operation-btn primary"
|
||||
onClick={() => handleViewResult(task.id, task.documentIds)}
|
||||
onClick={() => handleViewResult(task.id)}
|
||||
>
|
||||
<i className="ri-play-line"></i>
|
||||
去评查
|
||||
@@ -327,7 +360,7 @@ export default function CrossCheckingIndex() {
|
||||
type="default"
|
||||
size="small"
|
||||
className="operation-btn secondary"
|
||||
onClick={() => handleViewResult(task.id, task.documentIds)}
|
||||
onClick={() => handleViewResult(task.id)}
|
||||
>
|
||||
<i className="ri-eye-line"></i>
|
||||
进行中
|
||||
@@ -339,7 +372,7 @@ export default function CrossCheckingIndex() {
|
||||
type="default"
|
||||
size="small"
|
||||
className="operation-btn secondary"
|
||||
onClick={() => handleViewResult(task.id, task.documentIds)}
|
||||
onClick={() => handleViewResult(task.id)}
|
||||
>
|
||||
<i className="ri-file-text-line"></i>
|
||||
查看结果
|
||||
@@ -425,7 +458,7 @@ export default function CrossCheckingIndex() {
|
||||
|
||||
|
||||
|
||||
// 监听fetcher状态变化
|
||||
// 监听fetcher状态变化 - 删除操作
|
||||
useEffect(() => {
|
||||
if (fetcher.data && fetcher.state === 'idle' && isDeleting) {
|
||||
setIsDeleting(false);
|
||||
@@ -441,6 +474,44 @@ export default function CrossCheckingIndex() {
|
||||
}
|
||||
}, [fetcher.data, fetcher.state, isDeleting]);
|
||||
|
||||
// 监听fetcher状态变化 - 获取任务详情
|
||||
useEffect(() => {
|
||||
if (fetcher.data && fetcher.state === 'idle' && !isDeleting && modalState.loading) {
|
||||
const data = fetcher.data as {
|
||||
success?: boolean;
|
||||
data?: {
|
||||
files: TaskDocument[];
|
||||
total: number;
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
};
|
||||
error?: string;
|
||||
};
|
||||
|
||||
if (data.success && data.data) {
|
||||
const { files, total, currentPage, pageSize: returnedPageSize } = data.data;
|
||||
|
||||
setModalState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
title: `任务 ${currentTaskInfo?.taskId || ''} - 文档列表`,
|
||||
files: files || [],
|
||||
total: total || 0,
|
||||
currentPage: currentPage || prev.currentPage,
|
||||
pageSize: returnedPageSize || prev.pageSize
|
||||
}));
|
||||
} else {
|
||||
console.error('获取任务文档列表失败:', data.error);
|
||||
toastService.error(`获取任务文档列表失败: ${data.error || '未知错误'}`);
|
||||
|
||||
setModalState(prev => ({
|
||||
...prev,
|
||||
loading: false
|
||||
}));
|
||||
}
|
||||
}
|
||||
}, [fetcher.data, fetcher.state, isDeleting, modalState.loading, currentTaskInfo?.taskId]);
|
||||
|
||||
// 定义表格列配置
|
||||
const columns = [
|
||||
{
|
||||
@@ -479,7 +550,7 @@ export default function CrossCheckingIndex() {
|
||||
align: "center" as const,
|
||||
width: "8%",
|
||||
render: (_: unknown, record: CrossCheckingTask) => {
|
||||
const config = docTypeConfig[record.docType];
|
||||
const config = docTypeConfig[record.docType as keyof typeof docTypeConfig] || { label: record.docType, color: 'gray' as const };
|
||||
return (
|
||||
<Tag color={config.color}>
|
||||
{config.label}
|
||||
@@ -521,7 +592,7 @@ export default function CrossCheckingIndex() {
|
||||
align: "center" as const,
|
||||
width: "auto",
|
||||
render: (_: unknown, record: CrossCheckingTask) => {
|
||||
const config = statusConfig[record.status];
|
||||
const config = statusConfig[record.status as keyof typeof statusConfig] || { label: record.status, color: 'gray' as const };
|
||||
return (
|
||||
<Tag color={config.color}>
|
||||
{config.label}
|
||||
@@ -653,8 +724,8 @@ export default function CrossCheckingIndex() {
|
||||
pageSize={pageSize}
|
||||
onChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
showTotal={false}
|
||||
showPageSizeChanger={false}
|
||||
showTotal={true}
|
||||
showPageSizeChanger={true}
|
||||
pageSizeOptions={[10, 20, 30, 50]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
* @author 中国烟草AI合同及卷宗审核系统开发团队
|
||||
*/
|
||||
|
||||
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useLoaderData } from "@remix-run/react";
|
||||
import crossCheckingStyles from "~/styles/cross-checking-result.css?url";
|
||||
import { getReviewPoints, updateReviewResult, confirmReviewResults } from "~/api/evaluation_points/reviews";
|
||||
import { getReviewPoints, updateReviewResult } from "~/api/evaluation_points/reviews";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
|
||||
// 导入交叉评查详情页面组件
|
||||
@@ -189,11 +189,16 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
return Response.json({ result: false, message: '文件ID不能为空' });
|
||||
}
|
||||
|
||||
// 获取评查点数据
|
||||
const reviewData = await getReviewPoints(id);
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 获取评查点数据,传递request对象
|
||||
const reviewData = await getReviewPoints(id, request);
|
||||
|
||||
// console.log("documentData-------",JSON.stringify(documentData.data,null,2));
|
||||
// console.log("reviewData-------",JSON.stringify('data' in reviewData ? reviewData.data : '',null,2));
|
||||
// console.log("reviewData-------",JSON.stringify(reviewData,null,2));
|
||||
if ('error' in reviewData && reviewData.error) {
|
||||
console.error("获取评查点数据错误:", reviewData.error);
|
||||
return Response.json({ result: false, message: reviewData.error });
|
||||
@@ -209,7 +214,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
reviewInfo: reviewData.reviewInfo,
|
||||
statistics: reviewData.stats,
|
||||
comparison_document: reviewData.comparison_document,
|
||||
scoring_proposals: reviewData.scoring_proposals || []
|
||||
scoring_proposals: reviewData.scoring_proposals || [],
|
||||
jwtToken: frontendJWT // 传递JWT token
|
||||
});
|
||||
} else {
|
||||
console.error("返回的评查数据格式不正确",JSON.stringify(reviewData,null,2));
|
||||
@@ -221,10 +227,104 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 action 函数处理需要用户认证的操作
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const intent = formData.get("intent") as string;
|
||||
|
||||
try {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
if (intent === "updateReviewResult") {
|
||||
const reviewPointResultId = formData.get("reviewPointResultId") as string;
|
||||
const editAuditStatusId = formData.get("editAuditStatusId") as string;
|
||||
const result = formData.get("result") as string;
|
||||
const message = formData.get("message") as string;
|
||||
|
||||
const response = await updateReviewResult(reviewPointResultId, editAuditStatusId, result, message, request);
|
||||
|
||||
if (response.error) {
|
||||
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, data: response.data });
|
||||
}
|
||||
|
||||
if (intent === "submitCrossCheckingOpinion") {
|
||||
const { submitCrossCheckingOpinion } = await import("~/api/cross-checking/cross-file-result");
|
||||
|
||||
const reviewPointResultId = formData.get("reviewPointResultId") as string;
|
||||
const documentId = formData.get("documentId") as string;
|
||||
const auditPoint = formData.get("auditPoint") as string;
|
||||
const foundIssue = formData.get("foundIssue") as string;
|
||||
const auditOpinion = formData.get("auditOpinion") as string;
|
||||
const deductionScore = parseFloat(formData.get("deductionScore") as string);
|
||||
|
||||
const opinionData = {
|
||||
reviewPointResultId,
|
||||
documentId,
|
||||
auditPoint,
|
||||
foundIssue,
|
||||
auditOpinion,
|
||||
deductionScore
|
||||
};
|
||||
|
||||
const response = await submitCrossCheckingOpinion(opinionData, frontendJWT);
|
||||
|
||||
if (response.error) {
|
||||
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, data: response.data });
|
||||
}
|
||||
|
||||
if (intent === "getCrossCheckingOpinions") {
|
||||
const { getCrossCheckingOpinions } = await import("~/api/cross-checking/cross-file-result");
|
||||
|
||||
const documentId = formData.get("documentId") as string;
|
||||
const page = parseInt(formData.get("page") as string || "1", 10);
|
||||
const pageSize = parseInt(formData.get("pageSize") as string || "10", 10);
|
||||
const userId = userInfo?.user_id;
|
||||
|
||||
const response = await getCrossCheckingOpinions(documentId, page, pageSize, userId, frontendJWT);
|
||||
|
||||
if (response.error) {
|
||||
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, data: response.data });
|
||||
}
|
||||
|
||||
if (intent === "confirmReviewResults") {
|
||||
toastService.error('确认评查结果功能暂未实现');
|
||||
// TODO 应该在cross-file-result.ts中新增一个确认的方法
|
||||
// const documentId = formData.get("documentId") as string;
|
||||
|
||||
// const response = await confirmReviewResults(documentId, request);
|
||||
|
||||
// if (response.error) {
|
||||
// return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
// }
|
||||
|
||||
// return Response.json({ success: true, data: response.data });
|
||||
}
|
||||
|
||||
return Response.json({ success: false, error: "未知的操作类型" }, { status: 400 });
|
||||
} catch (error) {
|
||||
console.error('Action处理失败:', error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '操作失败'
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export default function CrossCheckingResult() {
|
||||
const navigate = useNavigate();
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const { document, reviewPoints, statistics, reviewInfo, scoring_proposals } = loaderData;
|
||||
const { document, reviewPoints, statistics, reviewInfo, scoring_proposals, jwtToken } = loaderData;
|
||||
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
|
||||
const [reviewData, setReviewData] = useState<ReviewData | null>(null);
|
||||
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
|
||||
@@ -314,12 +414,24 @@ export default function CrossCheckingResult() {
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用 API 更新评查结果
|
||||
const response = await updateReviewResult(reviewPointResultId, editAuditStatusId, boolResult, message);
|
||||
// 使用 fetch 调用 action
|
||||
const formData = new FormData();
|
||||
formData.append("intent", "updateReviewResult");
|
||||
formData.append("reviewPointResultId", reviewPointResultId);
|
||||
formData.append("editAuditStatusId", editAuditStatusId.toString());
|
||||
formData.append("result", boolResult);
|
||||
formData.append("message", message);
|
||||
|
||||
const response = await fetch(window.location.pathname, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.error) {
|
||||
console.error('更新评查结果失败:', response.error);
|
||||
toastService.error(`更新评查结果失败: ${response.error}`);
|
||||
if (!result.success) {
|
||||
console.error('更新评查结果失败:', result.error);
|
||||
toastService.error(`更新评查结果失败: ${result.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -409,12 +521,21 @@ export default function CrossCheckingResult() {
|
||||
// 显示加载状态
|
||||
setIsLoading(true);
|
||||
|
||||
// 调用API确认评查结果
|
||||
const response = await confirmReviewResults(document.id.toString());
|
||||
// 使用 fetch 调用 action
|
||||
const formData = new FormData();
|
||||
formData.append("intent", "confirmReviewResults");
|
||||
formData.append("documentId", document.id.toString());
|
||||
|
||||
const response = await fetch(window.location.pathname, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.error) {
|
||||
console.error('确认评查结果失败:', response.error);
|
||||
toastService.error(`确认评查结果失败: ${response.error}`);
|
||||
if (!result.success) {
|
||||
console.error('确认评查结果失败:', result.error);
|
||||
toastService.error(`确认评查结果失败: ${result.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -526,6 +647,7 @@ export default function CrossCheckingResult() {
|
||||
onReviewPointSelect={handleReviewPointSelect}
|
||||
onStatusChange={handleReviewPointStatusChange}
|
||||
scoringProposals={scoring_proposals as ScoringProposal[]}
|
||||
jwtToken={jwtToken}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+44
-17
@@ -123,7 +123,8 @@ async function handleFileUpload(
|
||||
remark: string | null,
|
||||
isTestDocument: boolean,
|
||||
documentId?: number | null,
|
||||
isReupload: boolean = false
|
||||
isReupload: boolean = false,
|
||||
jwtToken?: string
|
||||
): Promise<FileUploadResponse> {
|
||||
const response = await uploadDocumentToServer(
|
||||
binaryData,
|
||||
@@ -135,7 +136,8 @@ async function handleFileUpload(
|
||||
remark,
|
||||
isTestDocument,
|
||||
documentId,
|
||||
isReupload
|
||||
isReupload,
|
||||
jwtToken
|
||||
);
|
||||
|
||||
if (response.error || !response.data) {
|
||||
@@ -220,6 +222,8 @@ type LoaderData = {
|
||||
nick_name?: string;
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
frontendJWT?: string | null;
|
||||
userError?: string;
|
||||
};
|
||||
|
||||
// 添加 loader 函数
|
||||
@@ -227,7 +231,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);
|
||||
|
||||
// console.log('loader: 开始加载数据...');
|
||||
const url = new URL(request.url);
|
||||
@@ -236,7 +240,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// 我们不能在服务器端访问 sessionStorage,所以在客户端组件中处理 reviewType 过滤
|
||||
// 并行加载文档和文档类型
|
||||
const [documentsResponse, typesResponse] = await Promise.all([
|
||||
getTodayDocuments(),
|
||||
getTodayDocuments(userInfo),
|
||||
getDocumentTypes()
|
||||
]);
|
||||
|
||||
@@ -244,6 +248,16 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// console.log('loader: 文档类型加载结果:', typesResponse);
|
||||
|
||||
if (documentsResponse.error || typesResponse.error) {
|
||||
// 如果是用户信息错误,返回特殊的错误状态
|
||||
if (documentsResponse.error === '没有找到用户信息,请刷新重试') {
|
||||
return Response.json({
|
||||
documents: [],
|
||||
documentTypes: typesResponse.data || [],
|
||||
userInfo: null,
|
||||
frontendJWT: null,
|
||||
userError: documentsResponse.error
|
||||
});
|
||||
}
|
||||
throw new Error(documentsResponse.error || typesResponse.error);
|
||||
}
|
||||
|
||||
@@ -251,14 +265,17 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
mode,
|
||||
documents: documentsResponse.data || [],
|
||||
documentTypes: typesResponse.data || [],
|
||||
userInfo // 传递用户信息到客户端
|
||||
userInfo, // 传递用户信息到客户端
|
||||
frontendJWT // 传递JWT到客户端
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('loader: 加载数据失败:', error);
|
||||
return Response.json({
|
||||
documents: [],
|
||||
documentTypes: [],
|
||||
userInfo: null
|
||||
userInfo: null,
|
||||
frontendJWT: null,
|
||||
userError: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -368,16 +385,17 @@ export default function FilesUpload() {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用 reviewType 获取过滤后的文档列表
|
||||
const response = await getTodayDocuments(reviewType);
|
||||
|
||||
if (response.error) {
|
||||
console.error('过滤文档列表失败:', response.error);
|
||||
// 失败时使用原始数据
|
||||
setQueueFiles(loaderData.documents);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 使用 reviewType 获取过滤后的文档列表
|
||||
const response = await getTodayDocuments(loaderData.userInfo || undefined, reviewType);
|
||||
|
||||
if (response.error) {
|
||||
console.error('过滤文档列表失败:', response.error);
|
||||
toastService.error(response.error);
|
||||
// 失败时使用原始数据
|
||||
setQueueFiles(loaderData.documents);
|
||||
return;
|
||||
}
|
||||
const documents = response.data || [];
|
||||
console.log('过滤文档列表成功:', documents);
|
||||
setQueueFiles(documents);
|
||||
@@ -386,6 +404,7 @@ export default function FilesUpload() {
|
||||
startStatusChecker(documents);
|
||||
} catch (error) {
|
||||
console.error('过滤文档列表失败:', error);
|
||||
toastService.error('获取文档列表失败:'+(error instanceof Error ? error.message : '未知错误'));
|
||||
// 出错时使用原始数据
|
||||
const documents = loaderData.documents;
|
||||
setQueueFiles(documents);
|
||||
@@ -439,6 +458,13 @@ export default function FilesUpload() {
|
||||
setFileTypeError(actionData.errors.fileType);
|
||||
}
|
||||
}, [actionData]);
|
||||
|
||||
// 检查用户错误并显示 toast 提示
|
||||
useEffect(() => {
|
||||
if (loaderData.userError) {
|
||||
toastService.error(loaderData.userError);
|
||||
}
|
||||
}, [loaderData.userError]);
|
||||
|
||||
// 添加组件挂载状态引用
|
||||
const isMountedRef = useRef<boolean>(true);
|
||||
@@ -982,7 +1008,8 @@ export default function FilesUpload() {
|
||||
remark || null,
|
||||
isTestDocument,
|
||||
temp_n > 1 ? firstFileDocumentId : null, // 第二个文件及以后使用第一个文件的document_id
|
||||
false
|
||||
false,
|
||||
loaderData.frontendJWT || undefined
|
||||
);
|
||||
|
||||
const timeoutPromise = new Promise<FileUploadResponse>((_, reject) => {
|
||||
|
||||
+87
-3
@@ -3,7 +3,8 @@ import { useSearchParams, 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";
|
||||
import { getUserSession, getSession, createUserSessionWithInfo, getUserBySub, addDefaultRole } from "~/api/login/auth.server";
|
||||
import { getUserSession, getSession, sessionStorage, getUserBySub, addDefaultRole } from "~/api/login/auth.server";
|
||||
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
|
||||
import styles from "~/styles/pages/login.css?url";
|
||||
|
||||
export const links = () => [
|
||||
@@ -61,8 +62,91 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
await addDefaultRole(user.id, 2); // 添加common角色
|
||||
}
|
||||
|
||||
// 创建用户会话,默认角色为common,并保存用户信息
|
||||
return createUserSessionWithInfo(true, 'common', redirectTo, user);
|
||||
// 设置模拟的OAuth token信息
|
||||
const mockTokenExpiresIn = 60 * 60 * 2; // 2小时,与真实OAuth token保持一致
|
||||
const userRole = 'common';
|
||||
|
||||
// 生成前端专用JWT
|
||||
const jwtUserInfo: UserInfoForJWT = {
|
||||
sub: user.sub,
|
||||
user_id: user.id!,
|
||||
username: user.username,
|
||||
nick_name: user.nick_name,
|
||||
email: user.email,
|
||||
phone_number: user.phone_number,
|
||||
ou_id: user.ou_id,
|
||||
ou_name: user.ou_name,
|
||||
is_leader: user.is_leader,
|
||||
user_role: userRole
|
||||
};
|
||||
|
||||
const frontendJWT = JWTUtils.generateJWT(jwtUserInfo, mockTokenExpiresIn);
|
||||
|
||||
// 打印JWT生成信息
|
||||
console.log("=== 测试用户登录 - JWT生成信息 ===");
|
||||
console.log("用户信息:", jwtUserInfo);
|
||||
console.log("生成的JWT:", frontendJWT);
|
||||
console.log("JWT过期时间:", JWTUtils.getJWTExpiration(frontendJWT));
|
||||
console.log("JWT解析结果:", JWTUtils.decodeJWT(frontendJWT));
|
||||
console.log("JWT验证结果:", JWTUtils.verifyJWT(frontendJWT));
|
||||
|
||||
// 创建session,保持与OAuth登录相同的数据结构
|
||||
session.set("isAuthenticated", true);
|
||||
session.set("accessToken", "mock_access_token_for_test"); // 模拟的访问令牌
|
||||
session.set("refreshToken", "mock_refresh_token_for_test"); // 模拟的刷新令牌
|
||||
session.set("tokenIssuedAt", Date.now());
|
||||
session.set("tokenExpiresIn", mockTokenExpiresIn);
|
||||
session.set("userRole", userRole);
|
||||
session.set("frontendJWT", frontendJWT);
|
||||
|
||||
// 构建与OAuth登录相同结构的userInfo
|
||||
const enhancedUserInfo = {
|
||||
// 保持与callback.tsx中相同的数据结构
|
||||
sub: user.sub,
|
||||
username: user.username,
|
||||
nick_name: user.nick_name,
|
||||
phone_number: user.phone_number,
|
||||
email: user.email,
|
||||
ou_id: user.ou_id,
|
||||
ou_name: user.ou_name,
|
||||
status: user.status,
|
||||
is_leader: user.is_leader,
|
||||
// 增强字段,与OAuth登录保持一致
|
||||
user_id: user.id,
|
||||
user_role: userRole,
|
||||
frontend_jwt: frontendJWT
|
||||
};
|
||||
|
||||
session.set("userInfo", enhancedUserInfo);
|
||||
|
||||
// 打印session信息
|
||||
console.log("=== 测试用户登录 - Session信息 ===");
|
||||
console.log("保存到session的userInfo:", enhancedUserInfo);
|
||||
console.log("session数据结构:", {
|
||||
isAuthenticated: true,
|
||||
userRole: userRole,
|
||||
accessToken: "mock_access_token_for_test",
|
||||
refreshToken: "mock_refresh_token_for_test",
|
||||
tokenIssuedAt: Date.now(),
|
||||
tokenExpiresIn: mockTokenExpiresIn,
|
||||
frontendJWT: frontendJWT,
|
||||
userInfo: enhancedUserInfo
|
||||
});
|
||||
|
||||
const cookie = await sessionStorage.commitSession(session);
|
||||
|
||||
console.log("=== 测试用户登录完成 ===");
|
||||
console.log("用户:", user.username);
|
||||
console.log("角色:", userRole);
|
||||
console.log("重定向到:", redirectTo);
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: redirectTo,
|
||||
"Set-Cookie": cookie,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 如果用户不存在,重定向到登录页面并显示错误
|
||||
return redirect(`/login?error=${encodeURIComponent("测试用户不存在")}`);
|
||||
|
||||
+205
-85
@@ -25,9 +25,9 @@
|
||||
* @author 中国烟草AI合同及卷宗审核系统开发团队
|
||||
*/
|
||||
|
||||
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useLoaderData } from "@remix-run/react";
|
||||
import { useNavigate, useLoaderData, useFetcher } from "@remix-run/react";
|
||||
import reviewsStyles from "~/styles/reviews.css?url";
|
||||
import { getReviewPoints, updateReviewResult, confirmReviewResults } from "~/api/evaluation_points/reviews";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
@@ -183,8 +183,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
return Response.json({ result: false, message: '文件ID不能为空' });
|
||||
}
|
||||
|
||||
// 获取评查点数据
|
||||
const reviewData = await getReviewPoints(id);
|
||||
// 获取评查点数据,传递request对象
|
||||
const reviewData = await getReviewPoints(id, request);
|
||||
|
||||
// console.log("documentData-------",JSON.stringify(documentData.data,null,2));
|
||||
// console.log("reviewData-------",JSON.stringify('data' in reviewData ? reviewData.data : '',null,2));
|
||||
@@ -214,9 +214,78 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 action 函数处理需要用户认证的操作
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const intent = formData.get("intent") as string;
|
||||
|
||||
console.log('Action接收到请求, intent:', intent);
|
||||
|
||||
if (intent === "updateReviewResult") {
|
||||
const reviewPointResultId = formData.get("reviewPointResultId") as string;
|
||||
const editAuditStatusId = formData.get("editAuditStatusId") as string;
|
||||
const result = formData.get("result") as string;
|
||||
const message = formData.get("message") as string;
|
||||
|
||||
console.log('更新评查结果参数:', { reviewPointResultId, editAuditStatusId, result, message });
|
||||
|
||||
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 });
|
||||
} catch (updateError) {
|
||||
console.error('调用updateReviewResult时发生异常:', updateError);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: updateError instanceof Error ? updateError.message : '更新评查结果时发生未知错误'
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
if (intent === "confirmReviewResults") {
|
||||
const documentId = formData.get("documentId") as string;
|
||||
|
||||
console.log('确认评查结果参数:', { documentId });
|
||||
|
||||
try {
|
||||
const response = await confirmReviewResults(documentId, request);
|
||||
|
||||
if (response.error) {
|
||||
console.error('confirmReviewResults返回错误:', response.error);
|
||||
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, data: response.data });
|
||||
} catch (confirmError) {
|
||||
console.error('调用confirmReviewResults时发生异常:', confirmError);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: confirmError instanceof Error ? confirmError.message : '确认评查结果时发生未知错误'
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
console.error('收到未知的操作类型:', intent);
|
||||
return Response.json({ success: false, error: "未知的操作类型" }, { status: 400 });
|
||||
} catch (error) {
|
||||
console.error('Action处理失败:', error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '操作失败'
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export default function ReviewDetails() {
|
||||
const navigate = useNavigate();
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const fetcher = useFetcher();
|
||||
const { document, reviewPoints, statistics, reviewInfo, comparison_document } = loaderData;
|
||||
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
|
||||
const [activeTab, setActiveTab] = useState<string>('preview'); // 'preview', 'analysis', 'fileinfo'
|
||||
@@ -224,6 +293,11 @@ export default function ReviewDetails() {
|
||||
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
|
||||
const [targetPage, setTargetPage] = useState<number | undefined>(undefined);
|
||||
const [templateTargetPage, setTemplateTargetPage] = useState<number | undefined>(undefined);
|
||||
const [pendingUpdate, setPendingUpdate] = useState<{
|
||||
reviewPointResultId: string;
|
||||
newStatus: string;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
|
||||
// loader 数据加载出错
|
||||
useEffect(()=>{
|
||||
@@ -374,6 +448,80 @@ export default function ReviewDetails() {
|
||||
// }
|
||||
// }
|
||||
|
||||
// 监听fetcher状态变化
|
||||
useEffect(() => {
|
||||
if (fetcher.state === "idle" && fetcher.data && pendingUpdate) {
|
||||
const result = fetcher.data as { success: boolean; error?: string; data?: unknown };
|
||||
console.log('Fetcher返回数据:', result);
|
||||
|
||||
if (result.success) {
|
||||
console.log('评查点状态更新成功');
|
||||
|
||||
// 使用pendingUpdate中的参数更新本地状态
|
||||
if (reviewData && pendingUpdate.reviewPointResultId) {
|
||||
const reviewPointToUpdate = reviewData.reviewPoints.find(point => point.id === pendingUpdate.reviewPointResultId);
|
||||
const oldStatus = reviewPointToUpdate?.status || '';
|
||||
const wasSuccess = reviewPointToUpdate?.result === true;
|
||||
const newIsSuccess = pendingUpdate.newStatus === 'true';
|
||||
|
||||
// 更新评查点
|
||||
const updatedReviewPoints = reviewData.reviewPoints.map(point =>
|
||||
point.id === pendingUpdate.reviewPointResultId ? {
|
||||
...point,
|
||||
result: pendingUpdate.newStatus === 'true' ? true : (pendingUpdate.newStatus === 'false' ? false : point.result),
|
||||
editAuditStatus: pendingUpdate.newStatus === 'review' ? 0 : 1,
|
||||
title: pendingUpdate.newStatus === 'review' ? point.title : pendingUpdate.message,
|
||||
editAuditStatusMessage: pendingUpdate.newStatus === 'review' ? point.editAuditStatusMessage : pendingUpdate.message
|
||||
} : point
|
||||
);
|
||||
|
||||
// 更新统计数据
|
||||
const updatedStatistics = { ...reviewData.statistics };
|
||||
|
||||
// 只处理结果实际变化的情况
|
||||
if (pendingUpdate.newStatus !== 'review' && wasSuccess !== newIsSuccess) {
|
||||
if (newIsSuccess) {
|
||||
// 从不通过变为通过
|
||||
updatedStatistics.success += 1;
|
||||
if (oldStatus === 'warning') {
|
||||
updatedStatistics.warning = Math.max(0, updatedStatistics.warning - 1);
|
||||
} else if (oldStatus === 'error') {
|
||||
updatedStatistics.error = Math.max(0, updatedStatistics.error - 1);
|
||||
}
|
||||
} else {
|
||||
// 从通过变为不通过
|
||||
updatedStatistics.success = Math.max(0, updatedStatistics.success - 1);
|
||||
if (oldStatus === 'warning') {
|
||||
updatedStatistics.warning += 1;
|
||||
} else if (oldStatus === 'error') {
|
||||
updatedStatistics.error += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 UI 状态
|
||||
setReviewData({
|
||||
...reviewData,
|
||||
reviewPoints: updatedReviewPoints,
|
||||
statistics: updatedStatistics
|
||||
});
|
||||
}
|
||||
|
||||
if(document && document.id && pendingUpdate.newStatus !== 'review'){
|
||||
toastService.success('评查点状态已更新');
|
||||
}
|
||||
|
||||
// 清除pendingUpdate
|
||||
setPendingUpdate(null);
|
||||
} else {
|
||||
console.error('更新评查结果失败:', result.error);
|
||||
toastService.error(`更新评查结果失败: ${result.error || '未知错误'}`);
|
||||
// 清除pendingUpdate
|
||||
setPendingUpdate(null);
|
||||
}
|
||||
}
|
||||
}, [fetcher.state, fetcher.data, pendingUpdate, document, reviewData]);
|
||||
|
||||
// 处理评审点状态变更
|
||||
const handleReviewPointStatusChange = async (reviewPointResultId: string, editAuditStatusId: string | number, newStatus: string, message: string) => {
|
||||
// 将字符串的布尔值转换为布尔类型
|
||||
@@ -383,85 +531,28 @@ export default function ReviewDetails() {
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用 API 更新评查结果
|
||||
const response = await updateReviewResult(reviewPointResultId, editAuditStatusId, boolResult, message);
|
||||
console.log('开始提交评查结果更新:', { reviewPointResultId, editAuditStatusId, boolResult, message });
|
||||
|
||||
if (response.error) {
|
||||
console.error('更新评查结果失败:', response.error);
|
||||
toastService.error(`更新评查结果失败: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
// 设置待处理的更新信息
|
||||
setPendingUpdate({
|
||||
reviewPointResultId,
|
||||
newStatus: boolResult,
|
||||
message
|
||||
});
|
||||
|
||||
// console.log('评查点状态更新成功:', {
|
||||
// id: reviewPointResultId,
|
||||
// result: boolResult,
|
||||
// message: message
|
||||
// });
|
||||
|
||||
// 更新本地状态
|
||||
if (reviewData) {
|
||||
// 找到要更新的评查点和它的原始状态
|
||||
const reviewPointToUpdate = reviewData.reviewPoints.find(point => point.id === reviewPointResultId);
|
||||
const oldStatus = reviewPointToUpdate?.status || '';
|
||||
const wasSuccess = reviewPointToUpdate?.result === true;
|
||||
const newIsSuccess = newStatus === 'true';
|
||||
|
||||
// 更新评查点
|
||||
const updatedReviewPoints = reviewData.reviewPoints.map(point =>
|
||||
point.id === reviewPointResultId ? {
|
||||
...point,
|
||||
result: newStatus === 'true' ? true : (newStatus === 'false' ? false : point.result),
|
||||
editAuditStatus: boolResult === 'review' ? 0 : 1,
|
||||
title: boolResult === 'review' ? point.title : message,
|
||||
editAuditStatusMessage: boolResult === 'review' ? point.editAuditStatusMessage : message
|
||||
} : point
|
||||
);
|
||||
|
||||
// 更新统计数据
|
||||
const updatedStatistics = { ...reviewData.statistics };
|
||||
|
||||
// 只处理结果实际变化的情况,即从通过变为不通过,或从不通过变为通过
|
||||
if (newStatus !== 'review' && wasSuccess !== newIsSuccess) {
|
||||
if (newIsSuccess) {
|
||||
// 从不通过变为通过
|
||||
updatedStatistics.success += 1;
|
||||
// 减少对应的错误或警告数量
|
||||
if (oldStatus === 'warning') {
|
||||
updatedStatistics.warning = Math.max(0, updatedStatistics.warning - 1);
|
||||
} else if (oldStatus === 'error') {
|
||||
updatedStatistics.error = Math.max(0, updatedStatistics.error - 1);
|
||||
}
|
||||
} else {
|
||||
// 从通过变为不通过
|
||||
updatedStatistics.success = Math.max(0, updatedStatistics.success - 1);
|
||||
// 增加对应的错误或警告数量
|
||||
if (oldStatus === 'warning') {
|
||||
updatedStatistics.warning += 1;
|
||||
} else if (oldStatus === 'error') {
|
||||
updatedStatistics.error += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 UI 状态
|
||||
setReviewData({
|
||||
...reviewData,
|
||||
reviewPoints: updatedReviewPoints,
|
||||
statistics: updatedStatistics
|
||||
});
|
||||
|
||||
// 显示成功消息
|
||||
if(document && document.id && newStatus !== 'review'){
|
||||
toastService.success('评查点状态已更新');
|
||||
}
|
||||
// 使用 Remix 的 useFetcher 调用 action
|
||||
const formData = new FormData();
|
||||
formData.append("intent", "updateReviewResult");
|
||||
formData.append("reviewPointResultId", reviewPointResultId);
|
||||
formData.append("editAuditStatusId", editAuditStatusId.toString());
|
||||
formData.append("result", boolResult);
|
||||
formData.append("message", message);
|
||||
|
||||
// console.log("newReviewPoints",updatedReviewPoints);
|
||||
|
||||
// 如果是review操作才调用API刷新
|
||||
// if (document && document.id && newStatus === 'review') {
|
||||
// await refreshReviewData(document.id.toString());
|
||||
// }
|
||||
}
|
||||
fetcher.submit(formData, { method: "POST" });
|
||||
|
||||
console.log('请求已提交,等待响应...');
|
||||
|
||||
// 注意:本地状态更新现在在useEffect中处理,当fetcher返回成功响应时触发
|
||||
} catch (error) {
|
||||
console.error('更新评查结果出错:', error);
|
||||
toastService.error('更新评查结果失败,请稍后重试');
|
||||
@@ -478,12 +569,41 @@ export default function ReviewDetails() {
|
||||
// 显示加载状态
|
||||
setIsLoading(true);
|
||||
|
||||
// 调用API确认评查结果
|
||||
const response = await confirmReviewResults(document.id.toString());
|
||||
// 使用 fetch 调用 action
|
||||
const formData = new FormData();
|
||||
formData.append("intent", "confirmReviewResults");
|
||||
formData.append("documentId", document.id.toString());
|
||||
|
||||
const response = await fetch(window.location.pathname, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
// 检查响应是否为JSON格式
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType || !contentType.includes("application/json")) {
|
||||
console.error('服务器返回了非JSON响应,状态码:', response.status);
|
||||
const text = await response.text();
|
||||
console.error('响应内容:', text.substring(0, 500));
|
||||
|
||||
if (response.status === 401) {
|
||||
toastService.error('登录已过期,请重新登录');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
} else if (response.status >= 500) {
|
||||
toastService.error('服务器内部错误,请稍后重试');
|
||||
return;
|
||||
} else {
|
||||
toastService.error('请求失败,请检查网络连接');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.error) {
|
||||
console.error('确认评查结果失败:', response.error);
|
||||
toastService.error(`确认评查结果失败: ${response.error}`);
|
||||
if (!result.success) {
|
||||
console.error('确认评查结果失败:', result.error);
|
||||
toastService.error(`确认评查结果失败: ${result.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user