测通完成评查,投票,意见列表,任务列表,任务关联文档列表的内容。剩余创建任务,提出意见的完善
This commit is contained in:
@@ -77,6 +77,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
getCrossCheckingTasks(params, userInfo, frontendJWT),
|
||||
getCrossCheckingStats(userInfo, frontendJWT)
|
||||
]);
|
||||
|
||||
console.log('tasksResponse', tasksResponse.data?.tasks);
|
||||
|
||||
if (!tasksResponse.success) {
|
||||
console.error('获取任务列表失败:', tasksResponse.error);
|
||||
@@ -242,9 +244,9 @@ export default function CrossCheckingIndex() {
|
||||
};
|
||||
|
||||
// 处理查看结果 - 打开文档列表模态框
|
||||
const handleViewResult = async (taskId: number) => {
|
||||
const handleViewResult = async (taskId: number, taskName: string) => {
|
||||
// 存储任务信息用于分页
|
||||
setCurrentTaskInfo({ taskId });
|
||||
setCurrentTaskInfo({ taskId, taskName });
|
||||
|
||||
// 打开模态框
|
||||
setModalState(prev => ({
|
||||
@@ -274,12 +276,13 @@ export default function CrossCheckingIndex() {
|
||||
|
||||
// 处理文档查看 - 导航到评查详情页
|
||||
const handleViewFile = (fileId: string) => {
|
||||
navigate(`/cross-checking/result?id=${fileId}&previousRoute=crossChecking`);
|
||||
navigate(`/cross-checking/result?id=${fileId}&tId=${currentTaskInfo?.taskId}&previousRoute=crossChecking`);
|
||||
};
|
||||
|
||||
// 存储当前任务信息用于分页
|
||||
const [currentTaskInfo, setCurrentTaskInfo] = useState<{
|
||||
taskId: number;
|
||||
taskName: string;
|
||||
} | null>(null);
|
||||
|
||||
// 加载分页数据
|
||||
@@ -348,7 +351,7 @@ export default function CrossCheckingIndex() {
|
||||
type="primary"
|
||||
size="small"
|
||||
className="operation-btn primary"
|
||||
onClick={() => handleViewResult(task.id)}
|
||||
onClick={() => handleViewResult(task.id,task.taskName)}
|
||||
>
|
||||
<i className="ri-play-line"></i>
|
||||
去评查
|
||||
@@ -360,7 +363,7 @@ export default function CrossCheckingIndex() {
|
||||
type="default"
|
||||
size="small"
|
||||
className="operation-btn secondary"
|
||||
onClick={() => handleViewResult(task.id)}
|
||||
onClick={() => handleViewResult(task.id,task.taskName)}
|
||||
>
|
||||
<i className="ri-eye-line"></i>
|
||||
进行中
|
||||
@@ -372,7 +375,7 @@ export default function CrossCheckingIndex() {
|
||||
type="default"
|
||||
size="small"
|
||||
className="operation-btn secondary"
|
||||
onClick={() => handleViewResult(task.id)}
|
||||
onClick={() => handleViewResult(task.id,task.taskName)}
|
||||
>
|
||||
<i className="ri-file-text-line"></i>
|
||||
查看结果
|
||||
@@ -494,7 +497,7 @@ export default function CrossCheckingIndex() {
|
||||
setModalState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
title: `任务 ${currentTaskInfo?.taskId || ''} - 文档列表`,
|
||||
title: `${currentTaskInfo?.taskName || ''} - 文档列表`,
|
||||
files: files || [],
|
||||
total: total || 0,
|
||||
currentPage: currentPage || prev.currentPage,
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
*/
|
||||
|
||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
|
||||
import { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useLoaderData } from "@remix-run/react";
|
||||
import crossCheckingStyles from "~/styles/cross-checking-result.css?url";
|
||||
import { getReviewPoints, updateReviewResult } from "~/api/evaluation_points/reviews";
|
||||
import { getReviewPoints, updateReviewResult} from "~/api/evaluation_points/reviews";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import { confirmReviewResults, checkProposalVotes, findIsProposer } from "~/api/cross-checking/cross-file-result";
|
||||
|
||||
// 导入交叉评查详情页面组件
|
||||
import {
|
||||
@@ -183,19 +184,26 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const id = url.searchParams.get('id') || undefined;
|
||||
const taskId = url.searchParams.get('tId') || undefined;
|
||||
const previousRoute = url.searchParams.get('previousRoute') || '';
|
||||
// console.log("id-------",id);
|
||||
if (!id) {
|
||||
return Response.json({ result: false, message: '文件ID不能为空' });
|
||||
}
|
||||
if (!taskId) {
|
||||
return Response.json({ result: false, message: '任务ID不能为空' });
|
||||
}
|
||||
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 获取评查点数据,传递request对象
|
||||
const reviewData = await getReviewPoints(id, request);
|
||||
|
||||
// 获取当前登录用户是否是发起人
|
||||
const isProposer = await findIsProposer(taskId, userInfo?.user_id);
|
||||
|
||||
// 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));
|
||||
@@ -215,7 +223,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
statistics: reviewData.stats,
|
||||
comparison_document: reviewData.comparison_document,
|
||||
scoring_proposals: reviewData.scoring_proposals || [],
|
||||
jwtToken: frontendJWT // 传递JWT token
|
||||
userInfo: userInfo,
|
||||
jwtToken: frontendJWT, // 传递JWT token
|
||||
isProposer: isProposer
|
||||
});
|
||||
} else {
|
||||
console.error("返回的评查数据格式不正确",JSON.stringify(reviewData,null,2));
|
||||
@@ -298,20 +308,9 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
}
|
||||
|
||||
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 });
|
||||
// 检查文档下提案是否存在未投票用户,首先先打开一个模态框,提示用户是否确认完成评查,如果用户点击确认,则调用confirmReviewResults接口,如果用户点击取消,则关闭模态框
|
||||
// 模态框内的数据需要根据checkProposalVotes返回回来的数据进行显示,如果存在未投票用户,则提示用户存在未投票用户,如果不存在未投票用户,则提示用户完成评查
|
||||
}
|
||||
|
||||
return Response.json({ success: false, error: "未知的操作类型" }, { status: 400 });
|
||||
} catch (error) {
|
||||
console.error('Action处理失败:', error);
|
||||
return Response.json({
|
||||
@@ -324,7 +323,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
export default function CrossCheckingResult() {
|
||||
const navigate = useNavigate();
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const { document, reviewPoints, statistics, reviewInfo, scoring_proposals, jwtToken } = loaderData;
|
||||
const { document, reviewPoints, statistics, reviewInfo, scoring_proposals, jwtToken, userInfo, isProposer } = loaderData;
|
||||
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
|
||||
const [reviewData, setReviewData] = useState<ReviewData | null>(null);
|
||||
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
|
||||
@@ -511,44 +510,90 @@ export default function CrossCheckingResult() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmResults = async () => {
|
||||
/**
|
||||
* 处理确认评查结果
|
||||
* 1. 检查未投票提案
|
||||
* 2. 根据结果弹出确认模态框
|
||||
* 3. 用户确认后更新文档状态并跳转
|
||||
*/
|
||||
const handleConfirmResults = async (event?: React.MouseEvent) => {
|
||||
// 阻止默认行为,防止页面刷新
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!document || !document.id) {
|
||||
toastService.error('文档数据不完整,无法确认评查结果');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 显示加载状态
|
||||
setIsLoading(true);
|
||||
|
||||
// 使用 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();
|
||||
// 1. 先检查未投票
|
||||
const checkRes = await checkProposalVotes(document.id, jwtToken);
|
||||
console.log("checkRes", checkRes);
|
||||
|
||||
if (!result.success) {
|
||||
console.error('确认评查结果失败:', result.error);
|
||||
toastService.error(`确认评查结果失败: ${result.error}`);
|
||||
if (checkRes.error) {
|
||||
toastService.error(checkRes.error);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
toastService.success('评查结果已确认,文档审核状态已更新');
|
||||
// 2. 解析返回数据,定义明确的类型
|
||||
interface CheckProposalResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: {
|
||||
pending_proposals: Array<{
|
||||
evaluation_point_name: string;
|
||||
pending_voters_num: number;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
const responseData = checkRes.data as CheckProposalResponse;
|
||||
const pendingProposals = responseData?.data?.pending_proposals || [];
|
||||
console.log("pendingProposals", pendingProposals);
|
||||
|
||||
// 3. 构建模态框消息
|
||||
let modalMessage: string = '';
|
||||
if (Array.isArray(pendingProposals) && pendingProposals.length > 0) {
|
||||
modalMessage = pendingProposals.map((item) =>
|
||||
`评查名称为:${item.evaluation_point_name} 还剩余 ${item.pending_voters_num}人未投票。`
|
||||
).join('\n');
|
||||
} else {
|
||||
modalMessage = '是否完成评查?';
|
||||
}
|
||||
|
||||
// 4. 弹出模态框
|
||||
messageService.show({
|
||||
title: '提示',
|
||||
message: modalMessage,
|
||||
type: 'warning',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
onConfirm: async () => {
|
||||
setIsLoading(true);
|
||||
const res = await confirmReviewResults(document.id);
|
||||
setIsLoading(false);
|
||||
|
||||
if (res.error) {
|
||||
toastService.error(res.error);
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.success('评查结果已确认,文档审核状态已更新');
|
||||
// 注释掉自动跳转,让用户停留在当前页面
|
||||
navigate('/cross-checking');
|
||||
}
|
||||
});
|
||||
|
||||
// 导航到交叉评查列表页
|
||||
navigate('/cross-checking');
|
||||
} catch (error) {
|
||||
console.error('确认评查结果出错:', error);
|
||||
toastService.error(`确认评查结果失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
toastService.error(`确认评查结果失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -608,13 +653,20 @@ export default function CrossCheckingResult() {
|
||||
</div>
|
||||
|
||||
{/* 完成评查按钮 */}
|
||||
<button
|
||||
onClick={handleConfirmResults}
|
||||
className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-green-800 border border-transparent rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-800"
|
||||
>
|
||||
<i className="ri-check-double-line mr-1.5"></i>
|
||||
完成评查
|
||||
</button>
|
||||
{isProposer && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleConfirmResults(event);
|
||||
}}
|
||||
className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-green-800 border border-transparent rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-800"
|
||||
>
|
||||
<i className="ri-check-double-line mr-1.5"></i>
|
||||
完成评查
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 文件信息和操作按钮 */}
|
||||
@@ -648,6 +700,7 @@ export default function CrossCheckingResult() {
|
||||
onStatusChange={handleReviewPointStatusChange}
|
||||
scoringProposals={scoring_proposals as ScoringProposal[]}
|
||||
jwtToken={jwtToken}
|
||||
userInfo={userInfo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { type MetaFunction, type ActionFunctionArgs } from "@remix-run/node";
|
||||
import { Form, useNavigation, useNavigate } from "@remix-run/react";
|
||||
import { type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/node";
|
||||
import { Form, useNavigate, useLoaderData } from "@remix-run/react";
|
||||
import { UploadArea, type UploadAreaRef } from "~/components/ui/UploadArea";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
@@ -124,6 +124,60 @@ const TreeNodeCheckbox: React.FC<{
|
||||
</div>
|
||||
);
|
||||
};
|
||||
/**
|
||||
* 获取用户会话和前端JWT
|
||||
*/
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
// 获取用户会话信息
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||
|
||||
return json({
|
||||
userInfo,
|
||||
frontendJWT
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建交叉评查任务
|
||||
* @param taskData 任务数据
|
||||
* @param token JWT Token
|
||||
* @returns 创建结果
|
||||
*/
|
||||
async function createCrossReviewTask(taskData: {
|
||||
documentIds: number[];
|
||||
userIds: number[];
|
||||
assignerId: number;
|
||||
taskName: string;
|
||||
}, token: string) {
|
||||
try {
|
||||
const response = await fetch('/admin/crossreview/tasks/assign', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
document_ids: taskData.documentIds,
|
||||
user_ids: taskData.userIds,
|
||||
assigner_id: taskData.assignerId,
|
||||
task_name: taskData.taskName
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('任务创建成功:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('创建任务失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const formData = await request.formData();
|
||||
const caseType = formData.get("caseType") as string;
|
||||
@@ -137,10 +191,15 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
};
|
||||
|
||||
export default function CrossCheckingUpload() {
|
||||
// 获取loader数据
|
||||
const { userInfo, frontendJWT } = useLoaderData<typeof loader>();
|
||||
|
||||
// 基础状态
|
||||
const [caseType, setCaseType] = useState<CaseType>(CaseType.ADMINISTRATIVE_PENALTY);
|
||||
// 步骤状态
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
// 任务创建状态
|
||||
const [isCreatingTask, setIsCreatingTask] = useState(false);
|
||||
// 步骤1:任务信息
|
||||
const [taskInfo, setTaskInfo] = useState({
|
||||
name: '',
|
||||
@@ -171,8 +230,7 @@ export default function CrossCheckingUpload() {
|
||||
const singleUploadRef = useRef<UploadAreaRef>(null);
|
||||
const multipleUploadRef = useRef<UploadAreaRef>(null);
|
||||
|
||||
// 获取当前typeId
|
||||
const currentTypeId = CASE_TYPE_TO_TYPE_ID[caseType];
|
||||
|
||||
|
||||
// 处理案卷类型切换
|
||||
const handleCaseTypeChange = (type: CaseType) => {
|
||||
@@ -330,71 +388,177 @@ export default function CrossCheckingUpload() {
|
||||
setUploadType('none');
|
||||
};
|
||||
|
||||
// 处理完成上传
|
||||
const handleCompleteUpload = async () => {
|
||||
/**
|
||||
* 处理创建任务
|
||||
*/
|
||||
const handleCreateTask = async () => {
|
||||
// 验证步骤1:任务信息
|
||||
if (!taskInfo.name.trim()) {
|
||||
toastService.error("请填写任务名称");
|
||||
return;
|
||||
}
|
||||
if (!taskInfo.date.trim()) {
|
||||
toastService.error("请选择任务日期");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证步骤2:评查小组
|
||||
if (groupChecked.length === 0) {
|
||||
toastService.error("请选择评查小组成员");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证步骤3:文件上传
|
||||
const filesToUpload = uploadType === 'single' ? singleFiles : multipleFiles;
|
||||
|
||||
if (filesToUpload.length === 0) {
|
||||
toastService.error("请先选择要上传的文件");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreatingTask(true);
|
||||
setIsUploading(true);
|
||||
|
||||
try {
|
||||
console.log("开始批量上传文件:", filesToUpload.length, "个,案卷类型:", caseType, "typeId:", currentTypeId);
|
||||
// 第一步:上传文件
|
||||
console.log("开始批量上传文件:", filesToUpload.length, "个,案卷类型:", caseType);
|
||||
|
||||
const result = await batchUploadCrossCheckingFiles(
|
||||
filesToUpload,
|
||||
currentTypeId,
|
||||
const uploadResult = await batchUploadCrossCheckingFiles(
|
||||
filesToUpload.map(f => f.file),
|
||||
caseType,
|
||||
priority,
|
||||
documentNumber,
|
||||
remark,
|
||||
isTestDocument
|
||||
isTestDocument,
|
||||
frontendJWT
|
||||
);
|
||||
|
||||
const { successes, failures } = result;
|
||||
const { successes, failures } = uploadResult;
|
||||
|
||||
if (failures.length === 0) {
|
||||
// 全部成功
|
||||
toastService.success(`成功上传 ${successes.length} 个文件`);
|
||||
// 立即清空文件列表
|
||||
clearAllFiles();
|
||||
messageService.success(`文件上传完成!成功上传 ${successes.length} 个文件,现在可以进行下一步操作。`, {
|
||||
title: '上传成功',
|
||||
confirmText: '确定'
|
||||
});
|
||||
} else if (successes.length === 0) {
|
||||
// 全部失败
|
||||
toastService.error(`文件上传失败,共 ${failures.length} 个文件上传失败`);
|
||||
messageService.error(`所有文件上传失败。失败原因:${failures[0].error}`, {
|
||||
title: '上传失败',
|
||||
confirmText: '确定',
|
||||
});
|
||||
} else {
|
||||
// 部分成功
|
||||
toastService.warning(`部分文件上传成功:成功 ${successes.length} 个,失败 ${failures.length} 个`);
|
||||
messageService.warning(
|
||||
`部分文件上传完成:\n成功:${successes.length} 个文件\n失败:${failures.length} 个文件\n\n失败文件:\n${failures.map(f => `${f.file.name}: ${f.error}`).join('\n')}`,
|
||||
{
|
||||
title: '部分上传成功',
|
||||
confirmText: '确定',
|
||||
}
|
||||
);
|
||||
if (failures.length > 0) {
|
||||
toastService.error(`文件上传失败:${failures[0].error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 第二步:创建交叉评查任务
|
||||
console.log("文件上传成功,开始创建任务");
|
||||
|
||||
// 提取文档ID
|
||||
const documentIds = successes.map(success => success.result.result?.id).filter(id => id !== undefined) as number[];
|
||||
|
||||
// 提取用户ID(从选中的组织架构中获取用户)
|
||||
const userIds = groupChecked.filter(id => {
|
||||
// 检查是否为用户ID(通常用户ID以特定前缀开头或有特定格式)
|
||||
return id.includes('user_');
|
||||
}).map(id => parseInt(id.replace('user_', '')));
|
||||
|
||||
if (userIds.length === 0) {
|
||||
toastService.error("请选择具体的评查人员");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建任务数据
|
||||
const taskData = {
|
||||
documentIds,
|
||||
userIds,
|
||||
assignerId: userInfo?.user_id || 1, // 使用当前用户ID作为分配者
|
||||
taskName: taskInfo.name
|
||||
};
|
||||
|
||||
console.log("创建任务数据:", taskData);
|
||||
|
||||
// 调用创建任务接口
|
||||
await createCrossReviewTask(taskData, frontendJWT);
|
||||
|
||||
// 任务创建成功
|
||||
toastService.success("交叉评查任务创建成功!");
|
||||
messageService.success(
|
||||
`任务创建完成!\n任务名称:${taskInfo.name}\n上传文件:${successes.length} 个\n评查人员:${userIds.length} 人`,
|
||||
{
|
||||
title: '任务创建成功',
|
||||
confirmText: '确定',
|
||||
onConfirm: () => {
|
||||
// 跳转到任务列表页面
|
||||
navigate('/cross-checking');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error("批量上传失败:", error);
|
||||
toastService.error("文件上传过程中发生错误");
|
||||
messageService.error(`文件上传失败:${error instanceof Error ? error.message : '未知错误'}`, {
|
||||
title: '上传失败',
|
||||
console.error("创建任务失败:", error);
|
||||
toastService.error(`创建任务失败:${error instanceof Error ? error.message : '未知错误'}`);
|
||||
messageService.error(`创建任务失败:${error instanceof Error ? error.message : '未知错误'}`, {
|
||||
title: '创建失败',
|
||||
confirmText: '确定',
|
||||
});
|
||||
} finally {
|
||||
setIsCreatingTask(false);
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理完成上传(保留原有功能用于测试)
|
||||
// 处理完成上传(保留原有功能用于测试)
|
||||
// const handleCompleteUpload = async () => {
|
||||
// const filesToUpload = uploadType === 'single' ? singleFiles : multipleFiles;
|
||||
|
||||
// if (filesToUpload.length === 0) {
|
||||
// toastService.error("请先选择要上传的文件");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// setIsUploading(true);
|
||||
|
||||
// try {
|
||||
// console.log("开始批量上传文件:", filesToUpload.length, "个,案卷类型:", caseType);
|
||||
|
||||
// const result = await batchUploadCrossCheckingFiles(
|
||||
// filesToUpload.map(f => f.file),
|
||||
// caseType,
|
||||
// priority,
|
||||
// isTestDocument,
|
||||
// frontendJWT
|
||||
// );
|
||||
|
||||
// const { successes, failures } = result;
|
||||
|
||||
// if (failures.length === 0) {
|
||||
// // 全部成功
|
||||
// toastService.success(`成功上传 ${successes.length} 个文件`);
|
||||
// // 立即清空文件列表
|
||||
// clearAllFiles();
|
||||
// messageService.success(`文件上传完成!成功上传 ${successes.length} 个文件,现在可以进行下一步操作。`, {
|
||||
// title: '上传成功',
|
||||
// confirmText: '确定'
|
||||
// });
|
||||
// } else if (successes.length === 0) {
|
||||
// // 全部失败
|
||||
// toastService.error(`文件上传失败,共 ${failures.length} 个文件上传失败`);
|
||||
// messageService.error(`所有文件上传失败。失败原因:${failures[0].error}`, {
|
||||
// title: '上传失败',
|
||||
// confirmText: '确定',
|
||||
// });
|
||||
// } else {
|
||||
// // 部分成功
|
||||
// toastService.warning(`部分文件上传成功:成功 ${successes.length} 个,失败 ${failures.length} 个`);
|
||||
// messageService.warning(
|
||||
// `部分文件上传完成:\n成功:${successes.length} 个文件\n失败:${failures.length} 个文件\n\n失败文件:\n${failures.map(f => `${f.file.name}: ${f.error}`).join('\n')}`,
|
||||
// {
|
||||
// title: '部分上传成功',
|
||||
// confirmText: '确定',
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
// } catch (error) {
|
||||
// console.error("批量上传失败:", error);
|
||||
// toastService.error("文件上传过程中发生错误");
|
||||
// messageService.error(`文件上传失败:${error instanceof Error ? error.message : '未知错误'}`, {
|
||||
// title: '上传失败',
|
||||
// confirmText: '确定',
|
||||
// });
|
||||
// } finally {
|
||||
// setIsUploading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// 步骤切换
|
||||
const handleNext = () => setCurrentStep((s) => Math.min(s + 1, 3));
|
||||
const handlePrev = () => setCurrentStep((s) => Math.max(s - 1, 1));
|
||||
@@ -405,8 +569,8 @@ export default function CrossCheckingUpload() {
|
||||
|
||||
// 检查是否可以完成
|
||||
const canComplete = (singleFiles.length > 0 || multipleFiles.length > 0) && !isUploading;
|
||||
const navigation = useNavigation();
|
||||
const isSubmitting = navigation.state === "submitting";
|
||||
// const navigation = useNavigation();
|
||||
// 由于 isSubmitting 未被使用,暂时移除该行代码
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -419,7 +583,8 @@ export default function CrossCheckingUpload() {
|
||||
|
||||
try {
|
||||
console.log('开始加载组织架构数据');
|
||||
const response = await getOrganizationTree(true);
|
||||
// 传递JWT token到API调用
|
||||
const response = await getOrganizationTree(true, frontendJWT);
|
||||
|
||||
if (response.success && response.data) {
|
||||
console.log('原始API数据:', response.data);
|
||||
@@ -803,10 +968,10 @@ export default function CrossCheckingUpload() {
|
||||
<Button type="default" onClick={handlePrev}>上一步</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!canComplete || isUploading}
|
||||
onClick={handleCompleteUpload}
|
||||
disabled={!canComplete || isUploading || isCreatingTask}
|
||||
onClick={handleCreateTask}
|
||||
>
|
||||
{isUploading || isSubmitting ? "上传中..." : "开始创建任务"}
|
||||
{isCreatingTask ? "创建任务中..." : isUploading ? "上传中..." : "开始创建任务"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -819,16 +984,21 @@ export default function CrossCheckingUpload() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 上传进度提示 */}
|
||||
{isUploading && (
|
||||
{/* 创建任务进度提示 */}
|
||||
{(isUploading || isCreatingTask) && (
|
||||
<div className="text-center mt-4">
|
||||
<div className="bg-blue-50 p-4 rounded-md border border-blue-100">
|
||||
<div className="flex items-center justify-center text-blue-800 mb-2">
|
||||
<i className="ri-loader-4-line ri-spin animate-spin text-xl mr-2"></i>
|
||||
<span className="font-medium">正在上传文件...</span>
|
||||
<i className="ri-loader-4-line ri-spin animate-spin text-xl mr-2"></i>
|
||||
<span className="font-medium">
|
||||
{isCreatingTask ? "正在创建任务..." : "正在上传文件..."}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-blue-700">
|
||||
正在上传 {uploadType === 'single' ? singleFiles.length : multipleFiles.length} 个文件,请稍候
|
||||
{isCreatingTask
|
||||
? `正在创建交叉评查任务:${taskInfo.name}`
|
||||
: `正在上传 ${uploadType === 'single' ? singleFiles.length : multipleFiles.length} 个文件,请稍候`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+28
-43
@@ -238,7 +238,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, data: response.data });
|
||||
return Response.json({ success: true, data: response.data, intent: "confirmReviewResults" });
|
||||
} catch (updateError) {
|
||||
console.error('调用updateReviewResult时发生异常:', updateError);
|
||||
return Response.json({
|
||||
@@ -258,15 +258,16 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
|
||||
if (response.error) {
|
||||
console.error('confirmReviewResults返回错误:', response.error);
|
||||
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
|
||||
return Response.json({ success: false, error: response.error, intent: "confirmReviewResults" }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, data: response.data });
|
||||
return Response.json({ success: true, data: response.data, intent: "confirmReviewResults" });
|
||||
} catch (confirmError) {
|
||||
console.error('调用confirmReviewResults时发生异常:', confirmError);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: confirmError instanceof Error ? confirmError.message : '确认评查结果时发生未知错误'
|
||||
error: confirmError instanceof Error ? confirmError.message : '确认评查结果时发生未知错误',
|
||||
intent: "confirmReviewResults"
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -522,6 +523,27 @@ export default function ReviewDetails() {
|
||||
}
|
||||
}, [fetcher.state, fetcher.data, pendingUpdate, document, reviewData]);
|
||||
|
||||
// 监听fetcher状态变化 - 处理确认评查结果
|
||||
useEffect(() => {
|
||||
if (fetcher.state === "idle" && fetcher.data && !pendingUpdate) {
|
||||
const result = fetcher.data as { success: boolean; error?: string; intent?: string };
|
||||
|
||||
// 只处理confirmReviewResults的响应
|
||||
if (result.intent === 'confirmReviewResults') {
|
||||
setIsLoading(false);
|
||||
|
||||
if (result.success) {
|
||||
toastService.success('评查结果已确认,文档审核状态已更新');
|
||||
// 导航到文档列表页
|
||||
navigate('/documents');
|
||||
} else {
|
||||
console.error('确认评查结果失败:', result.error);
|
||||
toastService.error(`确认评查结果失败: ${result.error || '未知错误'}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [fetcher.state, fetcher.data, pendingUpdate, navigate]);
|
||||
|
||||
// 处理评审点状态变更
|
||||
const handleReviewPointStatusChange = async (reviewPointResultId: string, editAuditStatusId: string | number, newStatus: string, message: string) => {
|
||||
// 将字符串的布尔值转换为布尔类型
|
||||
@@ -569,53 +591,16 @@ export default function ReviewDetails() {
|
||||
// 显示加载状态
|
||||
setIsLoading(true);
|
||||
|
||||
// 使用 fetch 调用 action
|
||||
// 使用 Remix 的 useFetcher 调用 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();
|
||||
fetcher.submit(formData, { method: "POST" });
|
||||
|
||||
if (!result.success) {
|
||||
console.error('确认评查结果失败:', result.error);
|
||||
toastService.error(`确认评查结果失败: ${result.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
toastService.success('评查结果已确认,文档审核状态已更新');
|
||||
|
||||
// 导航到文档列表页
|
||||
navigate('/documents');
|
||||
} catch (error) {
|
||||
console.error('确认评查结果出错:', error);
|
||||
toastService.error(`确认评查结果失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user