重新构建路由和配置样式文件

This commit is contained in:
2025-03-26 10:04:27 +08:00
parent a42a9990bf
commit 97ccf5a077
141 changed files with 88034 additions and 179 deletions
+414
View File
@@ -0,0 +1,414 @@
import React, { useState } from 'react';
import { json, type MetaFunction } from '@remix-run/node';
import { useLoaderData, useParams } from '@remix-run/react';
import { Button } from '~/components/ui/Button';
import { Card } from '~/components/ui/Card';
import { Breadcrumb } from '~/components/layout/Breadcrumb';
import type { ReviewResult, RuleCheckResult } from '~/models/review';
import type { File } from '~/models/file';
import { RULE_CHECK_STATUS_LABELS, RULE_CHECK_STATUS_COLORS } from '~/models/review';
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 评查详情" },
{ name: "description", content: "文件评查详情页面" }
];
};
export const handle = {
breadcrumb: '评查详情'
};
interface LoaderData {
file: File;
reviewResult: ReviewResult;
reviewPoints: RuleCheckResult[];
fileContent?: string; // 模拟文件内容
}
export async function loader({ params }) {
const { reviewId } = params;
// 模拟数据,实际项目中应从API获取
const file: File = {
id: "1",
fileName: "2023年度烟草专卖零售许可证.pdf",
fileType: "application/pdf",
documentTypeId: "2",
documentTypeName: "专卖许可证",
fileSize: 1024 * 1024 * 2.5, // 2.5MB
uploaderId: "1",
uploaderName: "张三",
status: "completed",
reviewStatus: "pass",
createdAt: "2023-12-24 14:30",
updatedAt: "2023-12-24 16:45"
};
const reviewResult: ReviewResult = {
id: reviewId,
fileId: "1",
fileName: "2023年度烟草专卖零售许可证.pdf",
totalPoints: 15,
passPoints: 6,
warningPoints: 7,
errorPoints: 2,
score: 80,
reviewStatus: "warning",
reviewedAt: "2023-12-24 16:45",
reviewerId: "system",
reviewerName: "AI系统",
createdAt: "2023-12-24 14:35",
updatedAt: "2023-12-24 16:45"
};
const reviewPoints: RuleCheckResult[] = [
{
id: "1",
reviewResultId: reviewId,
ruleId: "1",
ruleName: "合同主体信息完整性检查",
status: "pass",
location: "第1页 第3段",
content: "甲方:XX烟草公司,地址:XX市XX区XX路XX号,法定代表人:张XX",
suggestion: "主体信息完整,符合规范",
manualReviewed: false,
createdAt: "2023-12-24 14:40",
updatedAt: "2023-12-24 14:40"
},
{
id: "2",
reviewResultId: reviewId,
ruleId: "2",
ruleName: "许可证编号格式检查",
status: "warning",
location: "第1页 第5段",
content: "许可证编号:(2023)12345",
suggestion: "许可证编号格式不完全符合规范,建议修改为'烟零许(2023)12345号'",
manualReviewed: true,
createdAt: "2023-12-24 14:40",
updatedAt: "2023-12-24 15:20"
},
{
id: "3",
reviewResultId: reviewId,
ruleId: "3",
ruleName: "许可证有效期检查",
status: "fail",
location: "第1页 第8段",
content: "有效期:自2023年1月1日",
suggestion: "许可证缺少有效期截止日期,必须明确注明有效期限",
manualReviewed: false,
createdAt: "2023-12-24 14:40",
updatedAt: "2023-12-24 14:40"
},
{
id: "4",
reviewResultId: reviewId,
ruleId: "4",
ruleName: "经营场所信息检查",
status: "pass",
location: "第1页 第12段",
content: "经营场所:XX市XX区XX街XX号,面积:120平方米",
suggestion: "经营场所信息完整",
manualReviewed: false,
createdAt: "2023-12-24 14:40",
updatedAt: "2023-12-24 14:40"
}
];
// 模拟文件内容,实际项目中应从API获取或使用专用组件展示
const fileContent = `烟草专卖零售许可证
发证机关:XX市烟草专卖局
发证日期:2023年1月1日
甲方:XX烟草公司,地址:XX市XX区XX路XX号,法定代表人:张XX
零售单位名称:XX便利店
许可证编号:(2023)12345
法定代表人/负责人:李XX
经营者类型:个体工商户
有效期:自2023年1月1日
联系电话:123-4567890
经营场所:XX市XX区XX街XX号,面积:120平方米
零售烟草制品品种:卷烟、雪茄烟
特别说明:本许可证不得伪造、变造、转让、涂改。
`;
return json<LoaderData>({
file,
reviewResult,
reviewPoints,
fileContent
});
}
export default function ReviewDetail() {
const { file, reviewResult, reviewPoints, fileContent } = useLoaderData<typeof loader>();
const [activeTab, setActiveTab] = useState('tab-preview');
const [selectedPoint, setSelectedPoint] = useState<string | null>(null);
const handleTabChange = (tabId: string) => {
setActiveTab(tabId);
};
const handlePointSelect = (pointId: string) => {
setSelectedPoint(pointId === selectedPoint ? null : pointId);
};
return (
<div>
<Breadcrumb
items={[
{ title: '评查结果', to: '/reviews' },
{ title: '评查详情', to: `/reviews/${reviewResult.id}` }
]}
/>
<div className="flex justify-between items-center mb-4">
<div className="flex items-center">
<h2 className="text-xl font-medium">{file.fileName}</h2>
<span className={`ml-3 status-badge status-${reviewResult.reviewStatus === 'pass' ? 'success' : reviewResult.reviewStatus === 'warning' ? 'warning' : 'error'}`}>
<i className={`ri-${reviewResult.reviewStatus === 'pass' ? 'checkbox-circle' : reviewResult.reviewStatus === 'warning' ? 'error-warning' : 'close-circle'}-line mr-1`}></i>
{reviewResult.reviewStatus === 'pass' ? '通过' : reviewResult.reviewStatus === 'warning' ? '警告' : '不通过'}
</span>
</div>
<div className="space-x-2">
<Button type="default" icon="ri-download-line">
</Button>
<Button type="primary" icon="ri-check-double-line">
</Button>
</div>
</div>
<div className="tab-container">
<div className="tab-nav">
<div
className={`tab-nav-item ${activeTab === 'tab-preview' ? 'active' : ''}`}
onClick={() => handleTabChange('tab-preview')}
>
<i className="ri-file-text-line"></i>
</div>
<div
className={`tab-nav-item ${activeTab === 'tab-suggestion' ? 'active' : ''}`}
onClick={() => handleTabChange('tab-suggestion')}
>
<i className="ri-lightbulb-line"></i> AI智能分析
</div>
<div
className={`tab-nav-item ${activeTab === 'tab-fileinfo' ? 'active' : ''}`}
onClick={() => handleTabChange('tab-fileinfo')}
>
<i className="ri-information-line"></i>
</div>
</div>
<div className="tab-content">
<div className={`tab-pane ${activeTab === 'tab-preview' ? 'active' : ''}`}>
<div className="flex flex-col lg:flex-row lg:h-[calc(100vh-250px)]">
{/* 文件内容预览 */}
<div className="w-full lg:w-2/3 h-full mb-4 lg:mb-0 lg:pr-4">
<div className="bg-white p-4 rounded-md shadow-sm h-full overflow-y-auto">
<pre className="whitespace-pre-wrap font-sans text-gray-800">
{fileContent}
</pre>
</div>
</div>
{/* 评查点列表 */}
<div className="w-full lg:w-1/3 h-full lg:pl-4">
<div className="review-points-panel h-full flex flex-col">
<div className="review-panel-header py-2 px-4 flex items-center bg-primary-light">
<i className="ri-file-list-check-line text-primary mr-2"></i>
<span className="font-medium text-primary"></span>
</div>
{/* 评查统计 */}
<div className="review-statistics bg-white border-b border-gray-100 py-3 px-4">
<div className="flex justify-between items-center">
<div className="flex items-center">
<div className="w-7 h-7 bg-gray-100 rounded-md flex items-center justify-center">
<span className="text-sm font-semibold text-gray-600">{reviewResult.totalPoints}</span>
</div>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
<div className="h-8 border-r border-gray-200"></div>
<div className="flex items-center">
<div className="w-7 h-7 bg-green-50 rounded-md flex items-center justify-center">
<span className="text-sm font-semibold text-success">{reviewResult.passPoints}</span>
</div>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
<div className="h-8 border-r border-gray-200"></div>
<div className="flex items-center">
<div className="w-7 h-7 bg-yellow-50 rounded-md flex items-center justify-center">
<span className="text-sm font-semibold text-warning">{reviewResult.warningPoints}</span>
</div>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
<div className="h-8 border-r border-gray-200"></div>
<div className="flex items-center">
<div className="w-7 h-7 bg-red-50 rounded-md flex items-center justify-center">
<span className="text-sm font-semibold text-error">{reviewResult.errorPoints}</span>
</div>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
</div>
</div>
{/* 评查点列表 */}
<div className="flex-1 overflow-y-auto">
{reviewPoints.map(point => (
<div
key={point.id}
className={`review-point-item ${selectedPoint === point.id ? 'bg-gray-50' : ''}`}
onClick={() => handlePointSelect(point.id)}
>
<div className="review-point-header">
<div className="review-point-title">{point.ruleName}</div>
<span className={`status-badge status-${RULE_CHECK_STATUS_COLORS[point.status]}`}>
<i className={`ri-${point.status === 'pass' ? 'checkbox-circle' : point.status === 'warning' ? 'error-warning' : 'close-circle'}-line mr-1`}></i>
{RULE_CHECK_STATUS_LABELS[point.status]}
</span>
</div>
<div className="review-point-location">
<i className="ri-file-list-line mr-1"></i>
<span>{point.location}</span>
</div>
{selectedPoint === point.id && (
<div className="mt-2 pt-2 border-t border-gray-100">
<div className="text-xs text-gray-600 mb-1">
<span className="font-medium"></span>
<span>{point.content}</span>
</div>
<div className="text-xs text-gray-600">
<span className="font-medium"></span>
<span>{point.suggestion}</span>
</div>
<div className="mt-2 flex justify-between">
<div className="text-xs text-gray-500">
{point.manualReviewed &&
<span><i className="ri-user-line mr-1"></i></span>
}
</div>
<div>
<Button type="default" size="small">
<i className="ri-edit-line mr-1"></i>
</Button>
</div>
</div>
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
</div>
<div className={`tab-pane ${activeTab === 'tab-suggestion' ? 'active' : ''}`}>
<Card>
<div className="text-lg font-medium mb-4 text-gray-800">AI智能分析意见</div>
<div className="mb-6">
<div className="font-medium text-gray-700 mb-2"></div>
<div className="p-3 bg-gray-50 rounded-md text-gray-600">
</div>
</div>
<div className="mb-6">
<div className="font-medium text-gray-700 mb-2"></div>
<ul className="list-disc pl-5 space-y-2 text-gray-600">
<li><span className="text-warning font-medium"></span> - "(2023)12345""烟零许(2023)12345号"</li>
<li><span className="text-error font-medium"></span> - "自2023年1月1日"</li>
</ul>
</div>
<div>
<div className="font-medium text-gray-700 mb-2"></div>
<ul className="list-decimal pl-5 space-y-2 text-gray-600">
<li>"烟零许""号"</li>
<li>"自2023年1月1日至2023年12月31日"</li>
<li></li>
</ul>
</div>
</Card>
</div>
<div className={`tab-pane ${activeTab === 'tab-fileinfo' ? 'active' : ''}`}>
<Card>
<div className="text-lg font-medium mb-4 text-gray-800"></div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<table className="w-full">
<tbody>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500 w-1/3"></td>
<td className="py-2 text-gray-800">{file.fileName}</td>
</tr>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{file.documentTypeName}</td>
</tr>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{(file.fileSize / (1024 * 1024)).toFixed(2)} MB</td>
</tr>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{file.uploaderName}</td>
</tr>
<tr>
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{file.createdAt}</td>
</tr>
</tbody>
</table>
</div>
<div>
<table className="w-full">
<tbody>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500 w-1/3"></td>
<td className="py-2 text-gray-800">
<span className={`status-badge status-${reviewResult.reviewStatus === 'pass' ? 'success' : reviewResult.reviewStatus === 'warning' ? 'warning' : 'error'}`}>
<i className={`ri-${reviewResult.reviewStatus === 'pass' ? 'checkbox-circle' : reviewResult.reviewStatus === 'warning' ? 'error-warning' : 'close-circle'}-line mr-1`}></i>
{reviewResult.reviewStatus === 'pass' ? '通过' : reviewResult.reviewStatus === 'warning' ? '警告' : '不通过'}
</span>
</td>
</tr>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{reviewResult.score} </td>
</tr>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{reviewResult.reviewedAt}</td>
</tr>
<tr className="border-b border-gray-100">
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{reviewResult.reviewerName}</td>
</tr>
<tr>
<td className="py-2 text-gray-500"></td>
<td className="py-2 text-gray-800">{reviewResult.totalPoints} </td>
</tr>
</tbody>
</table>
</div>
</div>
</Card>
</div>
</div>
</div>
</div>
);
}