完善评查详情
This commit is contained in:
+46
-8
@@ -8,6 +8,8 @@ import { FileTag, links as fileTagLinks } from "~/components/ui/FileTag";
|
||||
import { Tag } from "~/components/ui/Tag";
|
||||
import homeStyles from "~/styles/pages/home.css?url";
|
||||
import { getDocuments, type DocumentUI } from "~/api/files/documents";
|
||||
import { useState, useEffect } from "react";
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 文件处理状态选项
|
||||
const fileProcessingStatusOptions = [
|
||||
@@ -87,33 +89,69 @@ export async function loader() {
|
||||
|
||||
export default function Index() {
|
||||
const { stats, recentFiles } = useLoaderData<typeof loader>();
|
||||
const [currentDateTime, setCurrentDateTime] = useState<{
|
||||
date: string;
|
||||
time: string;
|
||||
}>({
|
||||
date: dayjs().format('YYYY年MM月DD日'),
|
||||
time: dayjs().format('HH:mm:ss')
|
||||
});
|
||||
|
||||
// 更新当前时间
|
||||
useEffect(() => {
|
||||
// 使用dayjs格式化日期和时间
|
||||
const updateDateTime = () => {
|
||||
const now = dayjs();
|
||||
setCurrentDateTime({
|
||||
date: now.format('YYYY年MM月DD日'),
|
||||
time: now.format('HH:mm:ss')
|
||||
});
|
||||
};
|
||||
|
||||
// 立即更新一次
|
||||
updateDateTime();
|
||||
|
||||
// 设置计时器,每秒更新一次
|
||||
const timerID = setInterval(updateDateTime, 1000);
|
||||
|
||||
// 清理函数,组件卸载时清除计时器
|
||||
return () => clearInterval(timerID);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="dashboard-container">
|
||||
{/* 页面标识 */}
|
||||
{/* 页面头部 */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-medium">系统概览</h2>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span id="current-date">{currentDateTime.date}</span>
|
||||
<span className="mx-2">|</span>
|
||||
<span id="current-time">{currentDateTime.time}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片区域 */}
|
||||
<Card title="统计信息" icon="ri-bar-chart-line" className="mt-6 transition-all duration-200 hover:shadow-[0_4px_15px_rgba(0,0,0,0.1)]">
|
||||
<div className="stat-grid ">
|
||||
<StatCard
|
||||
title="总文件数"
|
||||
title="今日待审文件"
|
||||
value={stats.totalFiles}
|
||||
icon="ri-file-list-3-line"
|
||||
/>
|
||||
<StatCard
|
||||
title="已审核"
|
||||
title="本月已审核文件"
|
||||
value={stats.reviewedFiles}
|
||||
icon="ri-check-double-line"
|
||||
trend={{ value: 5.2, isUp: true }}
|
||||
/>
|
||||
<StatCard
|
||||
title="待审核"
|
||||
title="审核通过率"
|
||||
value={stats.pendingFiles}
|
||||
icon="ri-time-line"
|
||||
trend={{ value: 2.1, isUp: false }}
|
||||
/>
|
||||
<StatCard
|
||||
title="通过率"
|
||||
title="问题检出数"
|
||||
value={`${stats.passRate}%`}
|
||||
icon="ri-pie-chart-line"
|
||||
trend={{ value: 1.5, isUp: true }}
|
||||
@@ -125,10 +163,10 @@ export default function Index() {
|
||||
<Card title="快捷访问" icon="ri-speed-line" className="mt-6 transition-all duration-200 hover:shadow-[0_4px_15px_rgba(0,0,0,0.1)]">
|
||||
<div className="shortcut-grid">
|
||||
<ShortcutItem icon="ri-upload-cloud-line" label="上传文件" to="/files/upload" />
|
||||
<ShortcutItem icon="ri-file-list-3-line" label="文件列表" to="/documents" />
|
||||
<ShortcutItem icon="ri-list-check-2" label="评查点管理" to="/rules" />
|
||||
<ShortcutItem icon="ri-file-list-3-line" label="文档列表" to="/documents" />
|
||||
<ShortcutItem icon="ri-list-check-3" label="评查点列表" to="/rules" />
|
||||
<ShortcutItem icon="ri-folder-open-line" label="评查点分组" to="/rule-groups" />
|
||||
<ShortcutItem icon="ri-file-chart-line" label="评查详情" to="/reviews" />
|
||||
{/* <ShortcutItem icon="ri-file-chart-line" label="评查详情" to="/reviews" /> */}
|
||||
<ShortcutItem icon="ri-file-list-line" label="文档类型" to="/document-types" />
|
||||
{/* <ShortcutItem icon="ri-settings-3-line" label="系统设置" to="/settings" /> */}
|
||||
<ShortcutItem icon="ri-chat-1-line" label="提示词管理" to="/prompts" />
|
||||
|
||||
+159
-30
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useSearchParams, Link, useLoaderData, useFetcher } from "@remix-run/react";
|
||||
import { useSearchParams, useLoaderData, useFetcher, useNavigate,Link } from "@remix-run/react";
|
||||
import { type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { Card } from "~/components/ui/Card";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
@@ -11,6 +11,7 @@ import { FilterPanel, FilterSelect, SearchFilter, DateRangeFilter } from "~/comp
|
||||
import documentsIndexStyles from "~/styles/pages/documents_index.css?url";
|
||||
import { getDocuments, deleteDocument, type DocumentUI } from "~/api/files/documents";
|
||||
import { getDocumentTypes } from "~/api/document-types/document-types";
|
||||
import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files";
|
||||
|
||||
// 导入样式
|
||||
export function links() {
|
||||
@@ -119,11 +120,11 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
// 审核状态筛选选项
|
||||
const auditStatusOptions = [
|
||||
// { value: "", label: "全部" },
|
||||
{ value: "-2", label: "警告" },
|
||||
{ value: "-1", label: "不通过" },
|
||||
{ value: "0", label: "待审核" },
|
||||
{ value: "1", label: "通过" },
|
||||
{ value: "2", label: "警告" },
|
||||
{ value: "3", label: "审核中" },
|
||||
{ value: "2", label: "审核中" },
|
||||
];
|
||||
|
||||
// 文件处理状态选项
|
||||
@@ -148,10 +149,10 @@ const fileStatusOptions = [
|
||||
// 审核状态选项及样式
|
||||
const auditStatusMapping: Record<string, { label: string; color: string; icon: string }> = {
|
||||
"-1": { label: "不通过", color: "red", icon: "ri-close-line" },
|
||||
"-2": { label: "警告", color: "yellow", icon: "ri-alert-line" },
|
||||
"0": { label: "待审核", color: "blue", icon: "ri-time-line" },
|
||||
"1": { label: "通过", color: "green", icon: "ri-check-line" },
|
||||
"2": { label: "警告", color: "yellow", icon: "ri-alert-line" },
|
||||
"3": { label: "审核中", color: "purple", icon: "ri-search-line" },
|
||||
"2": { label: "审核中", color: "purple", icon: "ri-search-line" },
|
||||
};
|
||||
|
||||
// 格式化文件大小
|
||||
@@ -182,6 +183,7 @@ export default function DocumentsIndex() {
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const fetcher = useFetcher();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 从URL获取当前筛选条件
|
||||
const search = searchParams.get("search") || "";
|
||||
@@ -321,29 +323,41 @@ export default function DocumentsIndex() {
|
||||
};
|
||||
|
||||
// 下载文档
|
||||
const handleDownload = async (path: string, fileName: string) => {
|
||||
console.log('handleDownload',path,fileName)
|
||||
const handleDownload = async (path: string) => {
|
||||
try {
|
||||
// 使用API获取授权的下载链接
|
||||
// const { data, error } = await getFileDownloadUrl(path);
|
||||
const urlBefore = 'http://172.18.0.100:9000/docauditai/';
|
||||
const downloadUrl = `${urlBefore}${path}`;
|
||||
|
||||
// if (error || !data?.downloadUrl) {
|
||||
// console.error('获取下载链接失败:', error);
|
||||
// alert('获取下载链接失败: ' + (error || '未知错误'));
|
||||
// return;
|
||||
// }
|
||||
// 使用fetch获取文件内容
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// 将响应转换为Blob
|
||||
const blob = await response.blob();
|
||||
|
||||
// 创建Blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 创建一个隐藏的a标签并点击它
|
||||
const a = document.createElement('a');
|
||||
// a.href = data.downloadUrl;
|
||||
a.href = path;
|
||||
a.download = fileName; // 设置下载的文件名
|
||||
a.style.display = 'none';
|
||||
a.href = blobUrl;
|
||||
// 从路径中获取文件名
|
||||
const fileName = path.split('/').pop() || 'document';
|
||||
a.download = decodeURIComponent(fileName);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} catch (err) {
|
||||
console.error('下载文件失败:', err);
|
||||
alert('下载文件失败: ' + (err instanceof Error ? err.message : '未知错误'));
|
||||
|
||||
// 清理
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
alert(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -412,6 +426,114 @@ export default function DocumentsIndex() {
|
||||
setSearchParams(params);
|
||||
};
|
||||
|
||||
// 导出列表
|
||||
const handleExport = async () => {
|
||||
// 如果没有文档,显示提示信息
|
||||
if (documents.length === 0) {
|
||||
alert('当前页面没有文档可供导出');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建一个ZIP文件
|
||||
const JSZip = await import('jszip').then(module => module.default);
|
||||
const zip = new JSZip();
|
||||
|
||||
// 准备所有下载任务
|
||||
const downloadTasks = documents.map(async (doc: DocumentUI) => {
|
||||
try {
|
||||
if (!doc.path) {
|
||||
console.warn(`文档 ${doc.name} 没有有效的路径`);
|
||||
return;
|
||||
}
|
||||
|
||||
const urlBefore = 'http://172.18.0.100:9000/docauditai/';
|
||||
const downloadUrl = `${urlBefore}${doc.path}`;
|
||||
|
||||
// 获取文件内容
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// 将响应转换为Blob
|
||||
const blob = await response.blob();
|
||||
|
||||
// 从路径中获取文件名
|
||||
const fileName = doc.path.split('/').pop() || doc.name;
|
||||
|
||||
// 添加到ZIP文件
|
||||
zip.file(decodeURIComponent(fileName), blob);
|
||||
|
||||
return { success: true, name: fileName };
|
||||
} catch (error) {
|
||||
console.error(`下载文件 ${doc.name} 失败:`, error);
|
||||
return { success: false, name: doc.name, error };
|
||||
}
|
||||
});
|
||||
|
||||
// 等待所有下载任务完成
|
||||
const results = await Promise.all(downloadTasks);
|
||||
|
||||
// 计算成功和失败的数量
|
||||
const succeeded = results.filter(r => r && r.success).length;
|
||||
const failed = results.filter(r => r && !r.success).length;
|
||||
|
||||
if (succeeded === 0) {
|
||||
alert('所有文件下载失败');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成ZIP文件
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
|
||||
// 创建下载链接
|
||||
const url = URL.createObjectURL(zipBlob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `文档导出_${new Date().toISOString().slice(0, 10)}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
// 清理
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}, 100);
|
||||
|
||||
// 显示结果消息
|
||||
if (failed > 0) {
|
||||
alert(`成功导出 ${succeeded} 个文件,${failed} 个文件失败`);
|
||||
} else {
|
||||
alert(`成功导出 ${succeeded} 个文件`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出文件失败:', error);
|
||||
alert(`导出文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始审核
|
||||
const handleReviewFileClick = async (fileId: number, auditStatus: number | null) => {
|
||||
// 检查audit_status是否为0,如果是则更新为2
|
||||
if (auditStatus === 0) {
|
||||
try {
|
||||
const response = await updateDocumentAuditStatus(fileId.toString(), 2);
|
||||
if (response.error) {
|
||||
console.error('更新文件审核状态失败:', response.error);
|
||||
alert('更新文件审核状态失败:' + (response.error || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新文件审核状态时出错:', error);
|
||||
alert('更新文件审核状态时出错:' + (error instanceof Error ? error.message : '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 导航到评查详情页
|
||||
navigate(`/reviews?id=${fileId}`);
|
||||
};
|
||||
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
@@ -517,7 +639,7 @@ export default function DocumentsIndex() {
|
||||
{
|
||||
title: "问题数量",
|
||||
key: "issues",
|
||||
width:"5%",
|
||||
width:"7%",
|
||||
render: (_: unknown, record: DocumentUI) => (
|
||||
record.issues === null ? "-" : record.issues
|
||||
)
|
||||
@@ -535,14 +657,20 @@ export default function DocumentsIndex() {
|
||||
render: (_: unknown, record: DocumentUI) => (
|
||||
<div className="operations-cell">
|
||||
{(record.auditStatus === 0 || record.auditStatus == null) ? (
|
||||
<Link
|
||||
to={`/reviews?id=${record.id}`}
|
||||
className="mr-1 hover:underline"
|
||||
<button
|
||||
onClick={() => handleReviewFileClick(record.id, record.auditStatus)}
|
||||
disabled={record.fileStatus !== 'Processed'}
|
||||
className={`mr-1 ${
|
||||
record.fileStatus === 'Processed'
|
||||
? 'hover:underline hover:cursor-pointer text-primary'
|
||||
: 'text-gray-400 cursor-not-allowed opacity-60'
|
||||
}`}
|
||||
>
|
||||
<i className="ri-play-circle-line"></i>
|
||||
<i className="ri-play-circle-line"></i>
|
||||
开始审核
|
||||
</Link>
|
||||
) : record.auditStatus === 3 ? (
|
||||
</button>
|
||||
) : record.auditStatus === 3 ? (
|
||||
//record.auditStatus === 3 目前这个状态不存在,所以除了待审核(0)-开始审核,其他都是审核中(2)-查看
|
||||
<Link
|
||||
to={`/documents/${record.id}/progress`}
|
||||
className="mr-1 hover:underline"
|
||||
@@ -552,7 +680,7 @@ export default function DocumentsIndex() {
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
to={`/documents/${record.id}`}
|
||||
to={`/reviews?id=${record.id}`}
|
||||
className="mr-1 hover:underline"
|
||||
>
|
||||
<i className="ri-eye-line"></i>
|
||||
@@ -569,7 +697,7 @@ export default function DocumentsIndex() {
|
||||
<button
|
||||
type="button"
|
||||
className="mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||
onClick={() => handleDownload(record.path, record.name)}
|
||||
onClick={() => handleDownload(record.path)}
|
||||
>
|
||||
<i className="ri-download-line"></i>
|
||||
下载
|
||||
@@ -699,6 +827,7 @@ export default function DocumentsIndex() {
|
||||
<Button
|
||||
type="default"
|
||||
icon="ri-download-line"
|
||||
onClick={handleExport}
|
||||
>
|
||||
导出列表
|
||||
</Button>
|
||||
|
||||
@@ -198,9 +198,9 @@ export default function DocumentEdit() {
|
||||
// 在新窗口打开文档预览
|
||||
const openPreview = () => {
|
||||
// 假设有一个预览URL的格式,比如 /preview?path=xxx
|
||||
console.log('documentstest', document);
|
||||
|
||||
const previewUrl = `/preview?path=${encodeURIComponent(document.path)}&name=${encodeURIComponent(document.name)}`;
|
||||
// console.log('documentstest', document);
|
||||
const urlBefore = 'http://172.18.0.100:9000/docauditai/'
|
||||
const previewUrl = `${urlBefore}${document.path}`;
|
||||
window.open(previewUrl, '_blank');
|
||||
};
|
||||
|
||||
|
||||
+140
-47
@@ -28,9 +28,8 @@
|
||||
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useLoaderData } from "@remix-run/react";
|
||||
import {getDocument} from "~/api/files/documents";
|
||||
import reviewsStyles from "~/styles/reviews.css?url";
|
||||
import { getReviewPoints } from "~/api/evaluation_points/reviews";
|
||||
import { getReviewPoints, updateReviewResult } from "~/api/evaluation_points/reviews";
|
||||
|
||||
// 导入评查详情页面组件
|
||||
import {
|
||||
@@ -176,25 +175,28 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
return Response.json({ error: '评查ID不能为空' }, { status: 400 });
|
||||
}
|
||||
|
||||
const documentData = await getDocument(id);
|
||||
if (documentData.error) {
|
||||
console.error("获取文档数据错误:", documentData.error);
|
||||
return Response.json({ error: documentData.error }, { status: documentData.status || 500 });
|
||||
}
|
||||
|
||||
const reviewData = await getReviewPoints(id);
|
||||
|
||||
console.log("reviewData-------",JSON.stringify(reviewData.data,null,2));
|
||||
if (reviewData.error) {
|
||||
// console.log("documentData-------",JSON.stringify(documentData.data,null,2));
|
||||
console.log("reviewData-------",JSON.stringify('data' in reviewData ? reviewData.data : '',null,2));
|
||||
if ('error' in reviewData && reviewData.error) {
|
||||
console.error("获取评查点数据错误:", reviewData.error);
|
||||
return Response.json({ error: reviewData.error }, { status: reviewData.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
document: documentData.data,
|
||||
reviewPoints: reviewData.data,
|
||||
reviewInfo: reviewData.reviewInfo,
|
||||
statistics: reviewData.stats
|
||||
});
|
||||
// 确保reviewData有效且具有预期的属性
|
||||
if ('document' in reviewData && 'data' in reviewData && 'reviewInfo' in reviewData && 'stats' in reviewData) {
|
||||
return Response.json({
|
||||
document: reviewData.document,
|
||||
reviewPoints: reviewData.data,
|
||||
reviewInfo: reviewData.reviewInfo,
|
||||
statistics: reviewData.stats
|
||||
});
|
||||
} else {
|
||||
console.error("返回的评查数据格式不正确");
|
||||
return Response.json({ error: '返回的评查数据格式不正确' }, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取评查数据失败:', error);
|
||||
return Response.json({ error: '获取评查数据失败' }, { status: 500 });
|
||||
@@ -208,6 +210,7 @@ export default function ReviewDetails() {
|
||||
const [activeTab, setActiveTab] = useState<string>('preview'); // 'preview', 'analysis', 'fileinfo'
|
||||
const [reviewData, setReviewData] = useState<ReviewData | null>(null);
|
||||
const [activeReviewPointId, setActiveReviewPointId] = useState<string | null>(null);
|
||||
const [targetPage, setTargetPage] = useState<number | undefined>(undefined);
|
||||
|
||||
// 模拟获取评查数据
|
||||
useEffect(() => {
|
||||
@@ -238,7 +241,7 @@ export default function ReviewDetails() {
|
||||
reviewPoints: reviewPoints,
|
||||
aiAnalysis: getMockReviewData().aiAnalysis,
|
||||
};
|
||||
console.log("reviewDataObj-------",reviewDataObj);
|
||||
|
||||
|
||||
setReviewData(reviewDataObj);
|
||||
setIsLoading(false);
|
||||
@@ -248,22 +251,130 @@ export default function ReviewDetails() {
|
||||
setActiveTab(tabKey);
|
||||
};
|
||||
|
||||
const handleReviewPointSelect = (reviewPointId: string) => {
|
||||
const handleReviewPointSelect = (reviewPointId: string, page?: number) => {
|
||||
setActiveReviewPointId(reviewPointId);
|
||||
setTargetPage(page);
|
||||
};
|
||||
|
||||
const handleReviewPointStatusChange = (reviewPointId: string, newStatus: string) => {
|
||||
// 更新评查点状态
|
||||
if (reviewData) {
|
||||
const updatedReviewPoints = reviewData.reviewPoints.map(point =>
|
||||
point.id === reviewPointId ? { ...point, status: newStatus } : point
|
||||
);
|
||||
// 刷新评审数据
|
||||
async function refreshReviewData(documentId: string) {
|
||||
// 设置加载状态
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// 获取最新的评审数据
|
||||
const response = await getReviewPoints(documentId);
|
||||
|
||||
setReviewData({
|
||||
...reviewData,
|
||||
reviewPoints: updatedReviewPoints,
|
||||
statistics: calculateStatistics(updatedReviewPoints)
|
||||
if ('error' in response && response.error) {
|
||||
console.error('刷新评审数据失败:', response.error);
|
||||
alert(`刷新评审数据失败: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保response有效且具有预期的属性
|
||||
if ('data' in response && 'stats' in response && 'reviewInfo' in response) {
|
||||
const reviewPointsData = response.data || [];
|
||||
const statisticsData = response.stats || { total: 0, success: 0, warning: 0, error: 0, score: 0 };
|
||||
const reviewInfoData = response.reviewInfo || {
|
||||
reviewTime: '',
|
||||
reviewModel: '',
|
||||
ruleGroup: '',
|
||||
result: '',
|
||||
issueCount: 0
|
||||
};
|
||||
|
||||
// 更新评审数据和统计信息
|
||||
setReviewData(prevData => {
|
||||
if (!prevData) {
|
||||
// 如果prevData为null,创建一个新的ReviewData对象
|
||||
return {
|
||||
fileInfo: {
|
||||
fileName: document?.name || "",
|
||||
contractNumber: document?.documentNumber || "",
|
||||
fileSize: document?.size ? formatFileSize(document.size) : "",
|
||||
fileFormat: document?.fileType ? document.fileType.toUpperCase() : "",
|
||||
pageCount: document?.pageCount || 0,
|
||||
uploadTime: document?.uploadTime || "",
|
||||
uploadUser: document?.uploadUser || "",
|
||||
auditStatus: document?.auditStatus || 0
|
||||
},
|
||||
contractInfo: getMockReviewData().contractInfo,
|
||||
reviewInfo: reviewInfoData as ReviewInfo,
|
||||
statistics: statisticsData as Statistics,
|
||||
fileContent: getMockReviewData().fileContent,
|
||||
reviewPoints: reviewPointsData as unknown as ReviewPoint[],
|
||||
aiAnalysis: getMockReviewData().aiAnalysis
|
||||
};
|
||||
}
|
||||
|
||||
// 处理prevData非null的情况
|
||||
return {
|
||||
...prevData,
|
||||
reviewPoints: reviewPointsData as unknown as ReviewPoint[],
|
||||
statistics: statisticsData as Statistics,
|
||||
reviewInfo: reviewInfoData as ReviewInfo
|
||||
};
|
||||
});
|
||||
|
||||
alert('评审数据已更新');
|
||||
} else {
|
||||
console.error('返回的数据格式不正确');
|
||||
alert('刷新评审数据失败: 返回的数据格式不正确');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新评审数据失败:', error);
|
||||
alert(`刷新评审数据失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理评审点状态变更
|
||||
const handleReviewPointStatusChange = async (reviewPointResultId: string, newStatus: string, message: string) => {
|
||||
// 将字符串的布尔值转换为布尔类型
|
||||
const boolResult = newStatus === 'true';
|
||||
|
||||
try {
|
||||
// 调用 API 更新评查结果
|
||||
const response = await updateReviewResult(reviewPointResultId, boolResult, message);
|
||||
|
||||
if (response.error) {
|
||||
console.error('更新评查结果失败:', response.error);
|
||||
alert(`更新评查结果失败: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('评查点状态更新成功:', {
|
||||
id: reviewPointResultId,
|
||||
result: boolResult,
|
||||
message: message
|
||||
});
|
||||
|
||||
// 更新本地状态
|
||||
if (reviewData) {
|
||||
const updatedReviewPoints = reviewData.reviewPoints.map(point =>
|
||||
point.id === reviewPointResultId ? {
|
||||
...point,
|
||||
result: boolResult,
|
||||
status: boolResult ? 'success' : 'error',
|
||||
message: message
|
||||
} : point
|
||||
);
|
||||
|
||||
// 更新 UI 状态
|
||||
setReviewData({
|
||||
...reviewData,
|
||||
reviewPoints: updatedReviewPoints,
|
||||
// statistics: calculateStatistics(updatedReviewPoints)
|
||||
});
|
||||
|
||||
// 从API获取最新数据刷新列表
|
||||
if (document && document.id) {
|
||||
await refreshReviewData(document.id.toString());
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新评查结果出错:', error);
|
||||
alert('更新评查结果失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,9 +409,10 @@ export default function ReviewDetails() {
|
||||
{/* 左侧:文件预览 */}
|
||||
<div className="w-full lg:w-2/3">
|
||||
<FilePreview
|
||||
fileContent={reviewData.fileContent}
|
||||
fileContent={document}
|
||||
reviewPoints={reviewData.reviewPoints}
|
||||
activeReviewPointId={activeReviewPointId}
|
||||
targetPage={targetPage}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -341,25 +453,6 @@ export default function ReviewDetails() {
|
||||
);
|
||||
}
|
||||
|
||||
// 计算评查统计数据
|
||||
function calculateStatistics(reviewPoints: ReviewPoint[]): Statistics {
|
||||
const total = reviewPoints.length;
|
||||
const success = reviewPoints.filter(point => point.status === 'success').length;
|
||||
const warning = reviewPoints.filter(point => point.status === 'warning').length;
|
||||
const error = reviewPoints.filter(point => point.status === 'error').length;
|
||||
|
||||
// 计算评分:通过占总数的百分比,错误项有额外惩罚
|
||||
const score = Math.round((success / total) * 100 - (error * 5));
|
||||
|
||||
return {
|
||||
total,
|
||||
success,
|
||||
warning,
|
||||
error,
|
||||
score: Math.max(0, Math.min(100, score)) // 确保分数在0-100之间
|
||||
};
|
||||
}
|
||||
|
||||
// 模拟评查数据
|
||||
function getMockReviewData(): ReviewData {
|
||||
return {
|
||||
|
||||
@@ -443,7 +443,7 @@ export default function RuleGroupsIndex() {
|
||||
{
|
||||
title: "分组名称",
|
||||
key: "name",
|
||||
width: "400px",
|
||||
width: "35%",
|
||||
render: (_: unknown, record: RuleGroup & { isParent?: boolean, parentId?: string }) => (
|
||||
<div className={`flex items-center ${!record.isParent ? 'ml-8' : ''}`}>
|
||||
{record.isParent && (
|
||||
@@ -467,7 +467,7 @@ export default function RuleGroupsIndex() {
|
||||
</span>
|
||||
)}
|
||||
<Link
|
||||
to={`/rule-groups/${record.id}/rules`}
|
||||
to={`/rule-groups/new?id=${record.id}`}
|
||||
className="group-name-link flex items-center ml-1 text-green-800"
|
||||
>
|
||||
<i className={`${record.isParent ? 'ri-folder-line' : 'ri-file-list-line'} mr-1 text-green-800`}></i> {record.name}
|
||||
@@ -488,10 +488,11 @@ export default function RuleGroupsIndex() {
|
||||
{
|
||||
title: "评查点数量",
|
||||
key: "ruleCount",
|
||||
width: "12%",
|
||||
render: (_: unknown, record: RuleGroup & { isParent?: boolean, parentId?: string }) => (
|
||||
<Link to={`/rule-groups/${record.id}/rules`} className="badge bg-primary text-white">
|
||||
{calculateTotalRuleCount(record)}
|
||||
</Link>
|
||||
<button onClick={() => navigate(`/rules?${!record.isParent ? `ruleType=${record.parentId}&groupId=${record.id}` : `ruleType=${record.id}`}`)} className="badge bg-primary text-white">
|
||||
<span className="text-xs hover:underline">{calculateTotalRuleCount(record)}</span>
|
||||
</button>
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -508,6 +509,7 @@ export default function RuleGroupsIndex() {
|
||||
{
|
||||
title: "创建时间",
|
||||
key: "createdAt",
|
||||
width: "15%",
|
||||
render: (_: unknown, record: RuleGroup) => (
|
||||
<span>{record.createdAt || '-'}</span>
|
||||
)
|
||||
|
||||
+125
-24
@@ -1,5 +1,5 @@
|
||||
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { useLoaderData, useSearchParams } from "@remix-run/react";
|
||||
import { useLoaderData, useSearchParams, useNavigate } from "@remix-run/react";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { Card } from "~/components/ui/Card";
|
||||
import { FileIcon } from "~/components/ui/FileIcon";
|
||||
@@ -11,8 +11,8 @@ import { StatusBadge } from "~/components/ui/StatusBadge";
|
||||
import rulesFilesStyles from "~/styles/pages/rules-files.css?url";
|
||||
import {
|
||||
getReviewFiles,
|
||||
updateReviewStatus,
|
||||
type ReviewFileUI
|
||||
type ReviewFileUI,
|
||||
updateDocumentAuditStatus
|
||||
} from "~/api/evaluation_points/rules-files";
|
||||
import { getDocumentTypes } from "~/api/document-types/document-types";
|
||||
|
||||
@@ -115,7 +115,8 @@ export function ErrorBoundary() {
|
||||
export default function RulesFiles() {
|
||||
const { files, documentTypes, totalCount, currentPage, pageSize } = useLoaderData<typeof loader>();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 处理筛选条件变更
|
||||
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
@@ -162,21 +163,114 @@ export default function RulesFiles() {
|
||||
newParams.set('page', '1'); // 改变每页条数时重置为第一页
|
||||
setSearchParams(newParams);
|
||||
};
|
||||
|
||||
// 查看评查文件
|
||||
const handleReviewFileClick = async (fileId: string, auditStatus: number | null) => {
|
||||
// 检查audit_status是否为0,如果是则更新为2
|
||||
if (auditStatus === 0) {
|
||||
try {
|
||||
const response = await updateDocumentAuditStatus(fileId, 2);
|
||||
if (response.error) {
|
||||
console.error('更新文件审核状态失败:', response.error);
|
||||
// 尽管更新失败,仍然导航到文件详情页
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新文件审核状态时出错:', error);
|
||||
// 尽管发生错误,仍然导航到文件详情页
|
||||
}
|
||||
}
|
||||
|
||||
// 导航到评查详情页
|
||||
navigate(`/reviews?id=${fileId}`);
|
||||
};
|
||||
|
||||
// 渲染问题摘要
|
||||
const renderIssues = (file: ReviewFileUI) => {
|
||||
// 如果评查状态为通过,显示"所有评查点均通过"
|
||||
if (file.reviewStatus === 'pass') {
|
||||
return (
|
||||
<div className="text-sm text-success">
|
||||
<i className="ri-check-double-line mr-1"></i>所有评查点均通过
|
||||
</div>
|
||||
);
|
||||
// 如果评查状态为通过(说明所有评查结果为true),显示"所有评查点均通过"
|
||||
if (file.status === 'Processed') {
|
||||
if (file.reviewStatus === 'pass') {
|
||||
return (
|
||||
<div className="text-sm text-success">
|
||||
<i className="ri-check-double-line mr-1"></i>所有评查点均通过
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 如果评查状态为通过,显示"所有评查点均通过"
|
||||
if (file.reviewStatus === 'fail') {
|
||||
return (
|
||||
<div className="text-sm text-error">
|
||||
<i className="ri-error-warning-line mr-1"></i>统计分数为:{file.score || 0}。分数低于80分。
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 显示问题列表
|
||||
if (file.reviewStatus !== 'pass' && file.reviewStatus !== 'fail' && 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-warning"></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 text-secondary">-</div>;
|
||||
};
|
||||
|
||||
|
||||
// 下载文件
|
||||
const handleDownload = async (path: string) => {
|
||||
try {
|
||||
const urlBefore = 'http://172.18.0.100:9000/docauditai/';
|
||||
const downloadUrl = `${urlBefore}${path}`;
|
||||
|
||||
// 使用fetch获取文件内容
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// 将响应转换为Blob
|
||||
const blob = await response.blob();
|
||||
|
||||
// 创建Blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 创建一个隐藏的a标签并点击它
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = blobUrl;
|
||||
// 从路径中获取文件名
|
||||
const fileName = path.split('/').pop() || 'document';
|
||||
a.download = decodeURIComponent(fileName);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
// 清理
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
alert(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 文件类型选项
|
||||
const fileTypeOptions = documentTypes.map((type: {id: number, name: string}) => ({
|
||||
@@ -197,7 +291,7 @@ export default function RulesFiles() {
|
||||
{ value: DateRange.TODAY, label: '今天' },
|
||||
{ value: DateRange.WEEK, label: '本周' },
|
||||
{ value: DateRange.MONTH, label: '本月' },
|
||||
{ value: DateRange.CUSTOM, label: '自定义时间段' }
|
||||
// { value: DateRange.CUSTOM, label: '自定义时间段' }
|
||||
];
|
||||
|
||||
// 定义表格列配置
|
||||
@@ -252,13 +346,18 @@ export default function RulesFiles() {
|
||||
title: "评查状态",
|
||||
key: "reviewStatus",
|
||||
width: "12%",
|
||||
render: (_: unknown, file: ReviewFileUI) => (
|
||||
<StatusBadge
|
||||
status={file.reviewStatus}
|
||||
text={REVIEW_STATUS_LABELS[file.reviewStatus]}
|
||||
showIcon={true}
|
||||
/>
|
||||
)
|
||||
render: (_: unknown, file: ReviewFileUI) =>
|
||||
file.status === 'Processed' ? (
|
||||
<StatusBadge
|
||||
status={file.reviewStatus}
|
||||
text={`${REVIEW_STATUS_LABELS[file.reviewStatus]}${file.issueCount>0?'('+file.issueCount+')':''}`}
|
||||
showIcon={true}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-sm">
|
||||
-
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "问题摘要",
|
||||
@@ -276,13 +375,15 @@ export default function RulesFiles() {
|
||||
type="default"
|
||||
size="small"
|
||||
icon="ri-eye-line"
|
||||
to={`/reviews?id=${file.id}`}
|
||||
// to={`/reviews?id=${file.id}`}
|
||||
onClick={() => handleReviewFileClick(file.id, file.auditStatus)}
|
||||
disabled={file.status !== 'Processed'}
|
||||
className="mr-2"
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
|
||||
<Button type="default" size="small" icon="ri-download-2-line">
|
||||
<Button type="default" size="small" icon="ri-download-2-line" className="mt-1" onClick={() => handleDownload(file.path)}>
|
||||
下载
|
||||
</Button>
|
||||
</>
|
||||
@@ -344,8 +445,8 @@ export default function RulesFiles() {
|
||||
options={[
|
||||
{ value: "upload_time_desc", label: "上传时间 ↓" },
|
||||
{ value: "upload_time_asc", label: "上传时间 ↑" },
|
||||
{ value: "issue_count_desc", label: "问题数量 ↓" },
|
||||
{ value: "issue_count_asc", label: "问题数量 ↑" }
|
||||
// { value: "issue_count_desc", label: "问题数量 ↓" },
|
||||
// { value: "issue_count_asc", label: "问题数量 ↑" }
|
||||
]}
|
||||
/>
|
||||
<SearchFilter
|
||||
|
||||
@@ -485,7 +485,7 @@ export default function RulesIndex() {
|
||||
}))
|
||||
]}
|
||||
onChange={handleFilterChange}
|
||||
className="mr-3 w-60 "
|
||||
className="mr-3 w-[20%]"
|
||||
/>
|
||||
|
||||
<FilterSelect
|
||||
@@ -500,7 +500,7 @@ export default function RulesIndex() {
|
||||
}))
|
||||
]}
|
||||
onChange={handleFilterChange}
|
||||
className={`mr-3 w-60 ${isRuleGroupSelectDisabled ? 'opacity-50' : ''}`}
|
||||
className={`mr-3 w-[20%] ${isRuleGroupSelectDisabled ? 'opacity-50' : ''}`}
|
||||
/>
|
||||
|
||||
<FilterSelect
|
||||
@@ -512,7 +512,7 @@ export default function RulesIndex() {
|
||||
{ value: "false", label: "禁用" }
|
||||
]}
|
||||
onChange={handleFilterChange}
|
||||
className="mr-3 w-60"
|
||||
className="mr-3 w-[20%]"
|
||||
/>
|
||||
|
||||
<SearchFilter
|
||||
@@ -520,7 +520,7 @@ export default function RulesIndex() {
|
||||
placeholder="输入评查点名称或编码"
|
||||
value={searchParams.get('keyword') || ''}
|
||||
onSearch={handleSearch}
|
||||
className="flex-1"
|
||||
className="w-[30%]"
|
||||
/>
|
||||
</FilterPanel>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user