测通完成评查,投票,意见列表,任务列表,任务关联文档列表的内容。剩余创建任务,提出意见的完善

This commit is contained in:
2025-07-23 10:22:51 +08:00
parent 47664fc0e8
commit 8800e982ab
13 changed files with 750 additions and 331 deletions
+226 -56
View File
@@ -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>