feat: 初步完成评查详情页面的work文档和pdf文档的加载的页面三栏设计的重构。
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* 右栏 · 详情面板
|
||||
* 包含 3 个选项卡(评查结果、抽取字段、文件信息)+ 底部操作栏
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import type { ReviewPoint, CharPosition } from '../ReviewPointsList';
|
||||
import { ReviewPointDetailCard } from './ReviewPointDetailCard';
|
||||
import { FileInfoPanel } from './FileInfoPanel';
|
||||
|
||||
type TabKey = 'result' | 'fields' | 'info';
|
||||
|
||||
interface FileInfoData {
|
||||
fileName: string;
|
||||
contractNumber: string;
|
||||
fileSize: string;
|
||||
fileFormat: string;
|
||||
pageCount: number;
|
||||
uploadTime: string;
|
||||
uploadUser: string;
|
||||
fileType: string;
|
||||
}
|
||||
|
||||
interface ReviewInfoData {
|
||||
reviewTime: string;
|
||||
reviewModel: string;
|
||||
ruleGroup: string;
|
||||
result: string;
|
||||
issueCount: number;
|
||||
}
|
||||
|
||||
interface DetailPanelProps {
|
||||
activeTab: TabKey;
|
||||
onTabChange: (tab: TabKey) => void;
|
||||
activeReviewPoint: ReviewPoint | null;
|
||||
reviewPoints: ReviewPoint[];
|
||||
fileInfo: FileInfoData;
|
||||
reviewInfo: ReviewInfoData;
|
||||
onReviewPointSelect: (id: string, page?: number, charPositions?: CharPosition[], value?: string) => void;
|
||||
onStatusChange: (id: string, editAuditStatusId: string | number, status: string, message: string) => void;
|
||||
onConfirmResults: () => void;
|
||||
onDownload: () => void;
|
||||
auditStatus?: number;
|
||||
fileFormat?: string;
|
||||
onUploadTemplate?: () => void;
|
||||
onComparison?: () => void;
|
||||
showComparisonButton?: boolean;
|
||||
}
|
||||
|
||||
function ExtractedFieldsPanel({ reviewPoints, onFieldClick }: { reviewPoints: ReviewPoint[]; onFieldClick: (page: number) => void }) {
|
||||
const fields: Array<{ key: string; value: string; page?: number; pointName: string }> = [];
|
||||
|
||||
reviewPoints.forEach((p) => {
|
||||
if (p.content) {
|
||||
Object.entries(p.content).forEach(([key, data]) => {
|
||||
const val = (data as any)?.value;
|
||||
const page = (data as any)?.page;
|
||||
const text = typeof val === 'object' ? (val as any)?.text || JSON.stringify(val) : String(val || '');
|
||||
fields.push({ key, value: text, page: page ? Number(page) : undefined, pointName: p.pointName });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-3">
|
||||
<div className="text-[11px] font-medium text-slate-400 uppercase tracking-wider mb-2">
|
||||
抽取字段 <span className="font-mono normal-case text-[10.5px]">{fields.length}</span>
|
||||
</div>
|
||||
{fields.length === 0 ? (
|
||||
<div className="text-center py-6 text-[12px] text-slate-400">暂无抽取字段</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{fields.map((f, i) => (
|
||||
<button
|
||||
key={`${f.key}-${i}`}
|
||||
type="button"
|
||||
className={`w-full border border-slate-200 rounded-md text-left hover:bg-slate-50 transition p-2.5 ${f.page ? 'cursor-pointer' : 'cursor-default opacity-70'}`}
|
||||
onClick={() => f.page && onFieldClick(f.page)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[11px] text-slate-500 truncate font-medium">{f.key}</span>
|
||||
{f.page && <span className="text-[10.5px] text-slate-400 shrink-0">P{f.page}</span>}
|
||||
</div>
|
||||
{f.value && <div className="text-[12px] text-slate-700 mt-1 leading-relaxed line-clamp-2">{f.value}</div>}
|
||||
<div className="text-[10px] text-slate-400 mt-0.5">{f.pointName}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const TABS: Array<{ key: TabKey; label: string }> = [
|
||||
{ key: 'result', label: '评查结果' },
|
||||
{ key: 'fields', label: '抽取字段' },
|
||||
{ key: 'info', label: '文件信息' },
|
||||
];
|
||||
|
||||
export function DetailPanel({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
activeReviewPoint,
|
||||
reviewPoints,
|
||||
fileInfo,
|
||||
reviewInfo,
|
||||
onReviewPointSelect,
|
||||
onStatusChange,
|
||||
onConfirmResults,
|
||||
onDownload,
|
||||
auditStatus,
|
||||
fileFormat,
|
||||
onUploadTemplate,
|
||||
onComparison,
|
||||
showComparisonButton,
|
||||
}: DetailPanelProps) {
|
||||
return (
|
||||
<aside className="border-l border-slate-200 bg-white flex flex-col min-h-0 min-w-0 overflow-hidden">
|
||||
{/* Tabs */}
|
||||
<nav className="shrink-0 h-11 px-3 flex items-stretch gap-4 border-b border-slate-200 text-[12.5px]">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.key}
|
||||
type="button"
|
||||
className={`h-full flex items-center ${
|
||||
activeTab === tab.key
|
||||
? 'text-slate-900 font-medium border-b-2 border-[#00684a] -mb-[1px]'
|
||||
: 'text-slate-500 hover:text-slate-800'
|
||||
}`}
|
||||
onClick={() => onTabChange(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
{tab.key === 'fields' && (
|
||||
<span className="ml-1 font-mono text-[11px] text-slate-400">{reviewPoints.length}</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1" />
|
||||
{showComparisonButton && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="h-full flex items-center text-slate-500 hover:text-slate-800 hover:bg-slate-50 px-2 transition"
|
||||
title="上传模板"
|
||||
onClick={onUploadTemplate}
|
||||
>
|
||||
<i className="ri-upload-cloud-line text-[15px]" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-full flex items-center text-slate-500 hover:text-slate-800 hover:bg-slate-50 px-2 transition"
|
||||
title="结构比对"
|
||||
onClick={onComparison}
|
||||
>
|
||||
<i className="ri-flip-horizontal-line text-[15px]" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-h-0 overflow-y-auto scroll-slim">
|
||||
{activeTab === 'result' && (
|
||||
activeReviewPoint ? (
|
||||
<div className="p-3">
|
||||
<ReviewPointDetailCard
|
||||
reviewPoint={activeReviewPoint}
|
||||
onReviewPointSelect={onReviewPointSelect}
|
||||
onStatusChange={onStatusChange}
|
||||
fileFormat={fileFormat}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full text-slate-400 text-[13px]">
|
||||
<i className="ri-cursor-line text-3xl mb-2" />
|
||||
<span>点击左侧评查点查看详情</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{activeTab === 'fields' && (
|
||||
<ExtractedFieldsPanel
|
||||
reviewPoints={reviewPoints}
|
||||
onFieldClick={(page) => {
|
||||
// 通过 activeReviewPoint 的 id 跳转页面
|
||||
if (activeReviewPoint) {
|
||||
onReviewPointSelect(activeReviewPoint.id, page);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'info' && (
|
||||
<FileInfoPanel fileInfo={fileInfo} reviewInfo={reviewInfo} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom action bar */}
|
||||
<div className="shrink-0 border-t border-slate-200 p-2.5 bg-slate-50/60 flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-9 w-9 rounded-md text-slate-500 hover:bg-slate-200 grid place-items-center"
|
||||
title="下载文档"
|
||||
onClick={onDownload}
|
||||
>
|
||||
<i className="ri-download-line text-[16px]" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`flex-1 h-9 rounded-md text-white text-[12.5px] font-medium flex items-center justify-center gap-1.5 shadow-sm ${
|
||||
auditStatus === 1
|
||||
? 'bg-slate-300 cursor-not-allowed'
|
||||
: 'bg-[#00684a] hover:bg-[#005a3f]'
|
||||
}`}
|
||||
onClick={auditStatus === 1 ? undefined : onConfirmResults}
|
||||
disabled={auditStatus === 1}
|
||||
>
|
||||
<i className="ri-checkbox-circle-line" /> 确认评查结果
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user