添加jwt验证,添加交叉评查首页加载对接接口,评查任务文档列表对接接口,意见列表对接接口
This commit is contained in:
@@ -7,9 +7,9 @@ import { FileTypeTag } from '../ui/FileTypeTag';
|
||||
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, type TaskDocument } from '~/api/cross-checking/cross-files'; // 更新导入
|
||||
import { toastService } from '../ui/Toast';
|
||||
import { formatDate } from '~/utils';
|
||||
|
||||
// 导出样式链接
|
||||
export const links = () => [];
|
||||
@@ -18,7 +18,7 @@ interface DocumentListModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
files: ReviewFileUI[];
|
||||
files: TaskDocument[]; // 更新类型
|
||||
onViewFile?: (fileId: string) => void;
|
||||
loading?: boolean;
|
||||
// 分页相关属性
|
||||
@@ -49,13 +49,10 @@ export function DocumentListModal({
|
||||
// 检查audit_status是否为0,如果是则更新为2
|
||||
if (auditStatus === 0 || auditStatus === null) {
|
||||
try {
|
||||
// TODO: 这里需要从父组件传递 userId,或者重新设计这个函数的调用方式
|
||||
// 暂时跳过状态更新,直接进入查看
|
||||
// const response = await updateDocumentAuditStatus(fileId, 2, userId);
|
||||
// if (response.error) {
|
||||
// throw new Error(response.error);
|
||||
// }
|
||||
console.warn('DocumentListModal: 跳过审核状态更新,需要传递 userId 参数');
|
||||
// TODO: 不需要传递userId,直接使用fileId找到对应文档,然后更新文档状态
|
||||
// 更新文档状态
|
||||
const updatedFile = await updateDocumentAuditStatus(fileId, 2);
|
||||
console.log('更新后的文档状态:', updatedFile);
|
||||
} catch (error) {
|
||||
console.error('更新文件审核状态时出错:', error);
|
||||
toastService.error(`更新文件审核状态时出错:${error instanceof Error ? error.message : '未知错误'}`);
|
||||
@@ -70,60 +67,74 @@ export function DocumentListModal({
|
||||
};
|
||||
|
||||
// 渲染问题摘要
|
||||
const renderIssues = (file: ReviewFileUI) => {
|
||||
// 如果文件状态为完成
|
||||
if (file.status === 'Processed') {
|
||||
// 如果没有问题,显示"所有评查点均通过"
|
||||
if (file.warningCount <= 0 && file.failCount <= 0) {
|
||||
return (
|
||||
<div className="text-sm text-success">
|
||||
<i className="ri-check-double-line mr-1"></i>所有评查点均通过
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const renderIssues = (file: TaskDocument) => {
|
||||
// 如果文件有问题信息
|
||||
if (file.issues && file.issues.length > 0) {
|
||||
// 最多显示2个问题
|
||||
const displayIssues = file.issues.slice(0, 2);
|
||||
|
||||
// 显示问题列表
|
||||
if (file.issues && file.issues.length > 0) {
|
||||
// 最多显示2个问题
|
||||
const displayIssues = file.issues.slice(0, 2);
|
||||
|
||||
return (
|
||||
<div className="text-sm">
|
||||
{displayIssues.map((issue, index) => (
|
||||
<div key={index} className="mb-1">
|
||||
<i className="ri-circle-fill mr-1 text-yellow-400"></i>
|
||||
{issue.message}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{file.issues.length > 2 && (
|
||||
<div className="text-secondary mt-1">
|
||||
还有 {file.issues.length - 2} 个问题...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="text-sm">
|
||||
{displayIssues.map((issue, index) => (
|
||||
<div key={index} className="mb-1">
|
||||
<i className={`ri-circle-fill mr-1 ${
|
||||
issue.severity === 'error' ? 'text-red-400' :
|
||||
issue.severity === 'warning' ? 'text-yellow-400' :
|
||||
'text-blue-400'
|
||||
}`}></i>
|
||||
{issue.message}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{file.issues.length > 2 && (
|
||||
<div className="text-secondary mt-1">
|
||||
还有 {file.issues.length - 2} 个问题...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 如果没有问题信息,根据状态显示
|
||||
if (file.evaluations_status === 1) {
|
||||
return (
|
||||
<div className="text-sm text-success">
|
||||
<i className="ri-check-double-line mr-1"></i>所有评查点均通过
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 其他状态显示占位符
|
||||
return <div className="text-sm text-secondary">-</div>;
|
||||
};
|
||||
|
||||
// 获取文件大小的友好显示
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
// 定义表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: "文件名称",
|
||||
key: "fileName",
|
||||
width: "30%",
|
||||
render: (_: unknown, file: ReviewFileUI) => (
|
||||
render: (_: unknown, file: TaskDocument) => (
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0 flex items-center self-center">
|
||||
<FileIcon fileName={file.fileName} className="text-lg w-10 h-10" />
|
||||
<FileIcon fileName={file.file_name} className="text-lg w-10 h-10" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 flex flex-col py-2 ml-2">
|
||||
<div className="font-normal text-base break-words whitespace-normal leading-normal" title={file.fileName}>{file.fileName}</div>
|
||||
<div className="font-normal text-base break-words whitespace-normal leading-normal" title={file.file_name}>{file.file_name}</div>
|
||||
<div className="text-xs text-secondary mt-2">
|
||||
文件编号:{file.fileCode}
|
||||
文件编号:{file.file_code}
|
||||
</div>
|
||||
<div className="text-xs text-secondary mt-1">
|
||||
大小:{formatFileSize(file.file_size)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,12 +143,12 @@ export function DocumentListModal({
|
||||
{
|
||||
title: "文件类型",
|
||||
key: "fileType",
|
||||
width: "12%",
|
||||
render: (_: unknown, file: ReviewFileUI) => (
|
||||
width: "10%",
|
||||
render: (_: unknown, file: TaskDocument) => (
|
||||
<FileTypeTag
|
||||
type="other"
|
||||
typeName={file.fileType}
|
||||
text={file.fileType}
|
||||
typeName={file.file_type_name}
|
||||
text={file.file_type_name}
|
||||
size="sm"
|
||||
showIcon={false}
|
||||
colorMode="light"
|
||||
@@ -148,13 +159,15 @@ export function DocumentListModal({
|
||||
title: "上传时间",
|
||||
key: "uploadTime",
|
||||
width: "12%",
|
||||
render: (_: unknown, file: ReviewFileUI) => {
|
||||
const [date, time] = file.uploadTime.split(' ');
|
||||
render: (_: unknown, file: TaskDocument) => {
|
||||
const uploadTime = formatDate(file.upload_time).split(' ');
|
||||
const date = uploadTime[0];
|
||||
const time = uploadTime[1];
|
||||
return (
|
||||
<div>
|
||||
<span className="text-base">{date}</span>
|
||||
<span className="text-base">{date}</span> {/* 2025-07-22 */}
|
||||
<br />
|
||||
<span className="text-xs text-secondary">{time}</span>
|
||||
<span className="text-xs text-secondary">{time}</span> {/* 10:00:00 */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -163,34 +176,42 @@ export function DocumentListModal({
|
||||
title: "评查统计",
|
||||
key: "reviewStatus",
|
||||
width: "12%",
|
||||
render: (_: unknown, file: ReviewFileUI) =>
|
||||
render: (_: unknown, file: TaskDocument) =>
|
||||
// 要文件切分处理完之后,再显示评查统计
|
||||
file.status === 'Processed' ? (
|
||||
<div>
|
||||
{file.passCount > 0 && (
|
||||
{file.pass_count > 0 && (
|
||||
<StatusBadge
|
||||
status="pass"
|
||||
text={`通过(${file.passCount})`}
|
||||
text={`通过(${file.pass_count})`}
|
||||
showIcon={true}
|
||||
className="my-2"
|
||||
/>
|
||||
)}
|
||||
{file.warningCount > 0 && (
|
||||
{file.warning_count > 0 && (
|
||||
<StatusBadge
|
||||
status="warning"
|
||||
text={`警告(${file.warningCount})`}
|
||||
text={`警告(${file.warning_count})`}
|
||||
showIcon={true}
|
||||
className="my-2"
|
||||
/>
|
||||
)}
|
||||
{file.failCount > 0 && (
|
||||
{file.fail_count > 0 && (
|
||||
<StatusBadge
|
||||
status="fail"
|
||||
text={`不通过(${file.failCount})`}
|
||||
text={`不通过(${file.fail_count})`}
|
||||
showIcon={true}
|
||||
className="my-2"
|
||||
/>
|
||||
)}
|
||||
{/* {file.manual_count > 0 && (
|
||||
<StatusBadge
|
||||
status="pending"
|
||||
text={`需人工(${file.manual_count})`}
|
||||
showIcon={true}
|
||||
className="my-2"
|
||||
/>
|
||||
)} */}
|
||||
</div>
|
||||
|
||||
) : (
|
||||
@@ -199,23 +220,43 @@ export function DocumentListModal({
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "评查分数",
|
||||
key: "score",
|
||||
width: "8%",
|
||||
render: (_: unknown, file: TaskDocument) => (
|
||||
<div className="text-center">
|
||||
{file.final_score ? (
|
||||
<span className={`font-medium ${
|
||||
file.final_score >= 90 ? 'text-green-600' :
|
||||
file.final_score >= 70 ? 'text-yellow-600' :
|
||||
'text-red-600'
|
||||
}`}>
|
||||
{file.final_score}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "问题摘要",
|
||||
key: "issues",
|
||||
width: "20%",
|
||||
render: (_: unknown, file: ReviewFileUI) => renderIssues(file)
|
||||
render: (_: unknown, file: TaskDocument) => renderIssues(file)
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "operation",
|
||||
width: "14%",
|
||||
render: (_: unknown, file: ReviewFileUI) => (
|
||||
width: "auto",
|
||||
render: (_: unknown, file: TaskDocument) => (
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
icon="ri-eye-line"
|
||||
onClick={() => handleReviewFileClick(file.id, file.auditStatus)}
|
||||
onClick={() => handleReviewFileClick(file.document_id.toString(), file.audit_status)}
|
||||
disabled={file.status !== 'Processed'}
|
||||
className="mr-2"
|
||||
>
|
||||
@@ -258,13 +299,13 @@ export function DocumentListModal({
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={files}
|
||||
rowKey="id"
|
||||
rowKey="document_id"
|
||||
emptyText="暂无文件数据"
|
||||
className="files-table table-auto-height"
|
||||
/>
|
||||
|
||||
{/* 分页组件 - 只有在提供了分页回调函数且总数大于每页大小时才显示 */}
|
||||
{(onPageChange || onPageSizeChange) && total > pageSize ? (
|
||||
{onPageChange && total > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
total={total}
|
||||
@@ -275,11 +316,6 @@ export function DocumentListModal({
|
||||
showPageSizeChanger={!!onPageSizeChange}
|
||||
pageSizeOptions={[10, 20, 30, 50]}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-sm text-gray-500 mt-4 text-center">
|
||||
共 {total} 条记录,每页 {pageSize} 条
|
||||
{total <= pageSize && ""}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -26,13 +26,11 @@ import { Pagination } from '../ui/Pagination';
|
||||
import { Button } from '../ui/Button';
|
||||
import { LoadingIndicator } from '../ui/SkeletonScreen';
|
||||
import {
|
||||
submitCrossCheckingOpinion,
|
||||
getCrossCheckingOpinions,
|
||||
performOpinionAction,
|
||||
type SubmitOpinionRequest,
|
||||
type CrossCheckingOpinion,
|
||||
type OpinionActionType
|
||||
} from '../../api/cross-checking/cross-file-result';
|
||||
import { useFetcher } from '@remix-run/react';
|
||||
// import '../../styles/components/TooltipStyles.css';
|
||||
|
||||
/**
|
||||
@@ -168,6 +166,7 @@ interface ReviewPointsListProps {
|
||||
onReviewPointSelect: (id: string, page?: number) => void;
|
||||
onStatusChange?: (id: string, editAuditStatusId: string | number, status: string, message: string) => void;
|
||||
scoringProposals?: ScoringProposal[];
|
||||
jwtToken?: string; // 添加JWT token参数
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -424,12 +423,14 @@ export function ReviewPointsList({
|
||||
statistics,
|
||||
activeReviewPointResultId,
|
||||
onReviewPointSelect,
|
||||
scoringProposals = []
|
||||
scoringProposals = [],
|
||||
jwtToken
|
||||
}: ReviewPointsListProps) {
|
||||
// 状态管理
|
||||
const [searchText, setSearchText] = useState(''); // 搜索文本
|
||||
const [statusFilter, setStatusFilter] = useState<string | null>(null); // 状态过滤
|
||||
const [evaluationResultIds, setEvaluationResultIds] = useState<number[]>([]); // 评分提案的evaluation_result_id
|
||||
const fetcher = useFetcher();
|
||||
|
||||
// 在组件中使用scoringProposals(这里只是简单使用以避免linter警告)
|
||||
// 将来可以用于显示相关的评分提案信息
|
||||
@@ -467,6 +468,54 @@ export function ReviewPointsList({
|
||||
const [opinionListPageSize, setOpinionListPageSize] = useState(10);
|
||||
const [performingAction, setPerformingAction] = useState<string | null>(null);
|
||||
|
||||
// 监听fetcher状态变化 - 获取意见列表数据
|
||||
useEffect(() => {
|
||||
if (fetcher.data && fetcher.state === 'idle' && opinionListLoading) {
|
||||
const data = fetcher.data as {
|
||||
success?: boolean;
|
||||
data?: {
|
||||
opinions: CrossCheckingOpinion[];
|
||||
total: number;
|
||||
};
|
||||
error?: string;
|
||||
};
|
||||
|
||||
if (data.success && data.data) {
|
||||
console.log('意见列表数据', data.data);
|
||||
setOpinionListData(data.data.opinions || []);
|
||||
setOpinionListTotal(data.data.total || 0);
|
||||
// 使用当前状态值而不是依赖项中的值
|
||||
setOpinionListCurrentPage(prev => prev);
|
||||
setOpinionListPageSize(prev => prev);
|
||||
} else {
|
||||
console.error('加载意见列表失败:', data.error);
|
||||
toastService.error(data.error || '加载意见列表失败');
|
||||
}
|
||||
|
||||
setOpinionListLoading(false);
|
||||
}
|
||||
}, [fetcher.data, fetcher.state, opinionListLoading]);
|
||||
|
||||
// 监听fetcher状态变化 - 提交意见
|
||||
useEffect(() => {
|
||||
if (fetcher.data && fetcher.state === 'idle' && isSubmittingOpinion) {
|
||||
const data = fetcher.data as {
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
if (data.success) {
|
||||
toastService.success('意见提交成功');
|
||||
handleCloseOpinionModal();
|
||||
} else {
|
||||
console.error('提交意见失败:', data.error);
|
||||
toastService.error(data.error || '提交意见失败');
|
||||
}
|
||||
|
||||
setIsSubmittingOpinion(false);
|
||||
}
|
||||
}, [fetcher.data, fetcher.state, isSubmittingOpinion]);
|
||||
|
||||
// 存放评查点ID与有效页码的映射
|
||||
const [effectivePages, setEffectivePages] = useState<Record<string, number>>({});
|
||||
|
||||
@@ -525,22 +574,19 @@ export function ReviewPointsList({
|
||||
setOpinionListLoading(true);
|
||||
try {
|
||||
console.log('加载意见列表数据', targetDocumentId, page, pageSize);
|
||||
const response = await getCrossCheckingOpinions(targetDocumentId, page, pageSize);
|
||||
|
||||
console.log('意见列表数据', response);
|
||||
if (response.error) {
|
||||
toastService.error(response.error);
|
||||
return;
|
||||
}
|
||||
// 使用 fetcher 调用路由的 action
|
||||
const formData = new FormData();
|
||||
formData.append("intent", "getCrossCheckingOpinions");
|
||||
formData.append("documentId", targetDocumentId.toString());
|
||||
formData.append("page", page.toString());
|
||||
formData.append("pageSize", pageSize.toString());
|
||||
|
||||
fetcher.submit(formData, { method: "POST" });
|
||||
|
||||
setOpinionListData(response.data?.opinions || []);
|
||||
setOpinionListTotal(response.data?.total || 0);
|
||||
setOpinionListCurrentPage(page);
|
||||
setOpinionListPageSize(pageSize);
|
||||
} catch (error) {
|
||||
console.error('加载意见列表失败:', error);
|
||||
toastService.error('加载意见列表失败');
|
||||
} finally {
|
||||
setOpinionListLoading(false);
|
||||
}
|
||||
};
|
||||
@@ -580,7 +626,7 @@ export function ReviewPointsList({
|
||||
setPerformingAction(actionKey);
|
||||
|
||||
try {
|
||||
const response = await performOpinionAction({ opinionId, action });
|
||||
const response = await performOpinionAction({ opinionId, action }, jwtToken);
|
||||
|
||||
if (response.error) {
|
||||
toastService.error(response.error);
|
||||
@@ -648,28 +694,21 @@ export function ReviewPointsList({
|
||||
setIsSubmittingOpinion(true);
|
||||
|
||||
try {
|
||||
const opinionData: SubmitOpinionRequest = {
|
||||
reviewPointResultId: selectedReviewPoint.id,
|
||||
documentId: selectedReviewPoint.documentId || '',
|
||||
auditPoint: opinionForm.auditPoint,
|
||||
foundIssue: opinionForm.foundIssue,
|
||||
auditOpinion: opinionForm.auditOpinion,
|
||||
deductionScore: opinionForm.deductionScore
|
||||
};
|
||||
// 使用 fetcher 调用路由的 action
|
||||
const formData = new FormData();
|
||||
formData.append("intent", "submitCrossCheckingOpinion");
|
||||
formData.append("reviewPointResultId", selectedReviewPoint.id);
|
||||
formData.append("documentId", selectedReviewPoint.documentId || '');
|
||||
formData.append("auditPoint", opinionForm.auditPoint);
|
||||
formData.append("foundIssue", opinionForm.foundIssue);
|
||||
formData.append("auditOpinion", opinionForm.auditOpinion);
|
||||
formData.append("deductionScore", opinionForm.deductionScore.toString());
|
||||
|
||||
const response = await submitCrossCheckingOpinion(opinionData);
|
||||
|
||||
if (response.error) {
|
||||
toastService.error(response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.success('意见提交成功');
|
||||
handleCloseOpinionModal();
|
||||
fetcher.submit(formData, { method: "POST" });
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交意见失败:', error);
|
||||
toastService.error('提交意见失败,请稍后重试');
|
||||
} finally {
|
||||
setIsSubmittingOpinion(false);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user