添加登录内容,尚未完善,先创建分支

This commit is contained in:
2025-07-14 12:31:43 +08:00
parent e3109423d4
commit fff474f46b
25 changed files with 2693 additions and 1102 deletions
+6
View File
@@ -192,6 +192,12 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: Sid
path: '/rules-files',
icon: 'ri-list-check-2'
},
{
id: 'cross-checking',
title: '交叉评查',
path: '/cross-checking',
icon: 'ri-color-filter-line'
},
// {
// id: 'rule-new',
// title: '新增评查点',
+311
View File
@@ -0,0 +1,311 @@
import { useState, useEffect } from "react";
// 定义字段比对结果类型
interface FieldComparison {
field: string;
status: string;
details: string;
source_page: string;
template_page: string;
}
// 定义比对结果类型
interface ComparisonResults {
[sectionName: string]: FieldComparison[];
}
// 定义比对文档类型
interface ComparisonDocument {
comparison_results?: ComparisonResults;
[key: string]: unknown;
}
// 定义组件Props类型
interface ComparisonProps {
comparison_document: ComparisonDocument | null;
onPageJump?: (sourcePage: number, templatePage: number) => void;
}
// 筛选类型
type FilterType = 'all' | 'normal' | 'abnormal';
export function Comparison({ comparison_document, onPageJump }: ComparisonProps) {
const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set());
const [filterType, setFilterType] = useState<FilterType>('all');
// 默认展开所有章节
useEffect(() => {
if (comparison_document?.comparison_results) {
const allSections = Object.keys(comparison_document.comparison_results);
setExpandedSections(new Set(allSections));
}
}, [comparison_document?.comparison_results]);
// 如果没有比对文档,显示暂无数据
if (!comparison_document || !comparison_document.comparison_results) {
return (
<div className="bg-white rounded-lg border border-gray-200 h-[calc(100vh-120px)] overflow-y-auto">
<div className="review-panel-header py-2 px-4 flex items-center">
<i className="ri-file-compare-line text-primary mr-2"></i>
<span className="font-medium text-primary">(0)</span>
</div>
<div className="text-center py-8 text-gray-500">
<i className="ri-file-search-line text-4xl mb-2 block"></i>
<p></p>
</div>
</div>
);
}
const comparisonResults = comparison_document.comparison_results;
// 确保 comparisonResults 的所有值都是数组,过滤掉非数组项
const validComparisonResults: ComparisonResults = {};
if (comparisonResults) {
Object.entries(comparisonResults).forEach(([key, value]) => {
if (Array.isArray(value)) {
validComparisonResults[key] = value;
}
});
}
// 切换章节展开状态
const toggleSection = (sectionName: string) => {
const newExpanded = new Set(expandedSections);
if (newExpanded.has(sectionName)) {
newExpanded.delete(sectionName);
} else {
newExpanded.add(sectionName);
}
setExpandedSections(newExpanded);
};
// 处理整个字段框的点击,同时传递两个页码
const handleFieldClick = (field: FieldComparison) => {
if (onPageJump) {
const sourcePage = field.source_page ? parseInt(field.source_page) : 0;
const templatePage = field.template_page ? parseInt(field.template_page) : 0;
onPageJump(sourcePage, templatePage);
}
};
// 处理键盘事件
const handleFieldKeyDown = (event: React.KeyboardEvent, field: FieldComparison) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleFieldClick(field);
}
};
// 获取状态样式
const getStatusStyle = (status: string) => {
if (status === 'normal') {
return {
icon: 'ri-check-circle-line',
color: 'text-green-500',
bgColor: 'bg-green-50',
borderColor: 'border-green-200'
};
} else {
return {
icon: 'ri-alert-circle-line',
color: 'text-red-500',
bgColor: 'bg-red-50',
borderColor: 'border-red-200'
};
}
};
// 统计总体信息
const totalFields = Object.values(validComparisonResults).flat().length;
const normalFields = Object.values(validComparisonResults).flat().filter(field => field.status === 'normal').length;
const abnormalFields = totalFields - normalFields;
// 根据筛选类型过滤数据
const getFilteredResults = () => {
if (filterType === 'all') {
return validComparisonResults;
}
const filteredResults: ComparisonResults = {};
Object.entries(validComparisonResults).forEach(([sectionName, fields]) => {
let filteredFields: FieldComparison[] = [];
if (filterType === 'normal') {
filteredFields = fields.filter(field => field.status === 'normal');
} else if (filterType === 'abnormal') {
filteredFields = fields.filter(field => field.status !== 'normal');
}
if (filteredFields.length > 0) {
filteredResults[sectionName] = filteredFields;
}
});
return filteredResults;
};
const filteredResults = getFilteredResults();
return (
<div className="bg-white rounded-lg border border-gray-200 h-[calc(100vh-120px)] flex flex-col">
{/* 固定头部 */}
<div className="flex-shrink-0 text-sm font-medium">
<div className="review-panel-header py-2 px-4 flex items-center border-b border-gray-100">
<i className="ri-book-read-line text-primary mr-2"></i>
<span className="font-medium text-primary">({totalFields})</span>
</div>
{/* 固定统计概览和筛选按钮 */}
<div className="p-4 border-b border-gray-200">
{/* 统计概览 */}
<div className="grid grid-cols-2 gap-2 text-sm mb-2">
<button
onClick={() => setFilterType('normal')}
className={`text-center py-2 rounded flex items-center justify-center transition-all duration-200 ${
filterType === 'normal'
? 'bg-green-100 border-2 border-green-300'
: 'bg-green-50 border-2 border-transparent hover:bg-green-100'
}`}
>
<div className="text-green-600 mr-2"></div>
<div className="font-medium text-green-600">{normalFields}</div>
</button>
<button
onClick={() => setFilterType('abnormal')}
className={`text-center py-2 rounded flex items-center justify-center transition-all duration-200 ${
filterType === 'abnormal'
? 'bg-red-100 border-2 border-red-300'
: 'bg-red-50 border-2 border-transparent hover:bg-red-100'
}`}
>
<div className="text-red-600 mr-2"></div>
<div className="font-medium text-red-600">{abnormalFields}</div>
</button>
</div>
{/* 筛选重置按钮 */}
{filterType !== 'all' && (
<button
onClick={() => setFilterType('all')}
className="w-full text-center py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-200 rounded hover:bg-gray-50 transition-colors"
>
({totalFields})
</button>
)}
</div>
</div>
{/* 滚动内容区域 */}
<div className="flex-1 overflow-y-auto">
{Object.keys(filteredResults).length === 0 ? (
<div className="text-center py-8 text-gray-500">
<i className="ri-filter-off-line text-2xl mb-2 block"></i>
<p>{filterType === 'normal' ? '正常' : '异常'}</p>
</div>
) : (
Object.entries(filteredResults).map(([sectionName, fields]) => {
const isExpanded = expandedSections.has(sectionName);
const sectionAbnormalCount = fields.filter(field => field.status !== 'normal').length;
return (
<div key={sectionName} className="border-b border-gray-100 last:border-b-0">
{/* 章节头部 */}
<button
onClick={() => toggleSection(sectionName)}
className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center justify-between transition-colors"
>
<div className="flex items-center">
<i className={`${isExpanded ? 'ri-arrow-down-s-line' : 'ri-arrow-right-s-line'} mr-2 text-gray-400 transition-transform`}></i>
<span className="font-medium text-gray-900">{sectionName}</span>
<span className="ml-2 text-sm text-gray-500">({fields.length})</span>
{sectionAbnormalCount > 0 && filterType === 'all' && (
<span className="ml-2 px-2 py-0.5 bg-red-100 text-red-600 text-xs rounded-full">
{sectionAbnormalCount}
</span>
)}
</div>
</button>
{/* 章节内容 */}
{isExpanded && (
<div className="px-4 pb-3">
{fields.map((field, index) => {
const statusStyle = getStatusStyle(field.status);
return (
<div
key={index}
role="button"
tabIndex={0}
onClick={() => handleFieldClick(field)}
onKeyDown={(e) => handleFieldKeyDown(e, field)}
className={`text-sm mb-3 last:mb-0 p-2 rounded-lg border cursor-pointer transition-all duration-200 ${statusStyle.bgColor} ${statusStyle.borderColor} hover:shadow-md hover:scale-[1.02] hover:border-opacity-80
focus:outline-none focus:ring-1 ${field.status === 'normal' ? 'focus:ring-green-700' : 'focus:ring-red-700'} focus:ring-opacity-50 ${field.status === 'normal' ? 'border-green-200' : 'border-red-200'}`}
>
{/* 字段名和状态 */}
<div className="flex items-center justify-between mb-1">
<div className="flex items-center">
<span className="font-medium text-gray-900">{field.field}</span>
</div>
<span className={`text-xs px-2 py-1 rounded-full ${
field.status === 'normal'
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700'
}`}>
{field.status === 'normal' ? '正常' : '异常'}
</span>
</div>
{/* 详细说明 */}
<p className="text-sm text-gray-600 mb-3">{field.details}</p>
{/* 页码信息 */}
<div className="flex items-center gap-4 text-xs">
{field.source_page ? (
<div className="flex items-center text-blue-600">
<i className="ri-file-text-line mr-1"></i>
主文件: {field.source_page}
</div>
) : (
<div className="flex items-center text-gray-400">
<i className="ri-file-text-line mr-1"></i>
主文件: 未找到内容
</div>
)}
{field.template_page ? (
<div className="flex items-center text-purple-600">
<i className="ri-file-copy-line mr-1"></i>
模板: {field.template_page}
</div>
) : (
<div className="flex items-center text-gray-400">
<i className="ri-file-copy-line mr-1"></i>
模板: 未找到内容
</div>
)}
</div>
</div>
);
})}
</div>
)}
</div>
);
})
)}
</div>
{/* 底部说明 */}
{/* <div className="flex-shrink-0 px-4 py-3 bg-gray-50 text-xs text-gray-500 rounded-b-lg border-t border-gray-100">
<div className="flex items-center">
<i className="ri-information-line mr-1"></i>
点击字段框可同时跳转到主文件和模板对应页面
</div>
</div> */}
</div>
);
}
+41 -19
View File
@@ -63,6 +63,7 @@ interface FileContent {
title: string;
content: string;
}[];
template_contract_path?: string;
}
interface FilePreviewProps {
@@ -183,7 +184,8 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
// toastService.success(`已跳转至目标页码`);
}
// 如果有目标页码,并且与上次不同或activeReviewPointId变化了,则执行跳转
if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointResultId)) {
if (targetPage && numPages && targetPage <= numPages) {
// if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointResultId)) {
prevTargetPageRef.current = targetPage;
let newTargetPage = targetPage;
@@ -375,8 +377,17 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
// 渲染文档内容
const renderDocumentContent = () => {
const real_path = fileContent.path || fileContent.template_contract_path || '';
// 如果路径无效,显示错误信息
if (!fileContent.path) {
if (!real_path) {
if(!fileContent.template_contract_path){
return (
<div className="text-red-500 p-4">
<p></p>
</div>
);
}
return (
<div className="text-red-500 p-4">
<p></p>
@@ -384,8 +395,9 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
);
}
// console.log('real_path',real_path);
// 获取文件扩展名
const fileExtension = fileContent.path.split('.').pop()?.toLowerCase();
const fileExtension = real_path.split('.').pop()?.toLowerCase();
// PDF内容渲染
const renderPdfContent = () => (
@@ -398,7 +410,7 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
}}
>
<Document
file={DOCUMENT_URL+fileContent.path}
file={DOCUMENT_URL+real_path}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={(error) => {
console.error("PDF加载错误:", error);
@@ -457,39 +469,41 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
return (
<div className="file-preview">
<div className="file-preview-header py-2 px-4 text-xs sm:text-xs md:text-sm max-w-full text-overflow-ellipsis">
<div className="flex items-center">
<i className={`${isStructuredView ? 'ri-file-list-line' : 'ri-file-text-line'} text-primary mr-2`}></i>
<span className="font-medium text-primary">{isStructuredView ? '附件预览' : '文件预览'}</span>
<div className="file-preview-header px-2 text-xs sm:text-xs md:text-sm max-w-full flex items-center justify-between min-w-0">
<div className="flex items-center min-w-0 flex-shrink-0">
<i className={`${isStructuredView ? 'ri-file-list-line' : 'ri-file-text-line'} text-primary mr-2 flex-shrink-0`}></i>
<span className="font-medium text-primary truncate max-w-[120px]" title={isStructuredView ? '模板预览' : '文件预览'}>
{isStructuredView ? '模板预览' : '文件预览'}
</span>
</div>
<div className="file-preview-actions flex items-center">
<div className="file-preview-actions flex items-center ml-2 min-w-0 flex-1 justify-end overflow-hidden">
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5"
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 flex-shrink-0"
onClick={handleScrollToTop}
title="返回顶部"
>
<i className="ri-arrow-up-double-line"></i>
<span className="ml-1 sm:inline md:inline lg:hidden xl:inline"></span>
<span className="ml-1 hidden sm:hidden md:hidden lg:hidden xl:inline truncate max-w-[60px]"></span>
</button>
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5"
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 flex-shrink-0"
onClick={handleZoomIn}
title="放大"
>
<i className="ri-zoom-in-line"></i>
</button>
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5"
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 flex-shrink-0"
onClick={handleZoomOut}
title="缩小"
>
<i className="ri-zoom-out-line"></i>
</button>
{/* 页码跳转控件 */}
<div className="inline-flex items-center ml-2">
<div className="inline-flex items-center ml-2 flex-shrink-0">
<input
type="text"
className="ant-input ant-input-sm py-0 px-1 text-xs max-h-6 leading-5 max-w-[2.5rem] text-center
className="ant-input ant-input-sm py-0 px-1 text-xs max-h-6 leading-5 w-[2.5rem] text-center
focus:outline-none focus:ring-1 focus:ring-green-900"
placeholder="页码"
value={pageInputValue}
@@ -504,17 +518,25 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
>
<i className="ri-arrow-right-line"></i>
</button>
{numPages && <span className="ml-1 text-xs text-gray-500 sm:inline md:inline lg:hidden xl:inline">/ {numPages}</span>}
{numPages && (
<span className="ml-1 text-xs text-gray-500 hidden sm:hidden md:hidden lg:hidden xl:inline whitespace-nowrap">
/ {numPages}
</span>
)}
</div>
<span className="ml-2 text-xs text-gray-500 inline-block sm:inline md:inline lg:hidden xl:inline">{"比例:"+zoomLevel+"%"}</span>
<span className="ml-2 text-xs text-gray-500 hidden sm:hidden md:hidden lg:hidden xl:inline whitespace-nowrap flex-shrink-0">
{zoomLevel}%
</span>
<button
className={`ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 ml-2 ${dragMode ? 'active bg-green-300' : ''}`}
className={`ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 ml-2 flex-shrink-0 ${dragMode ? 'active bg-green-300' : ''}`}
title="切换拖拽模式"
aria-pressed={dragMode}
onClick={toggleDragMode}
>
<i className="ri-drag-move-line"></i>
<span className="ml-1 sm:inline md:inline lg:hidden xl:inline">{dragMode ? '(已激活)' : ''}</span>
<span className="ml-1 hidden sm:hidden md:hidden lg:hidden xl:inline truncate max-w-[80px]">
{dragMode ? '(已激活)' : ''}
</span>
</button>
</div>
</div>
+242 -8
View File
@@ -2,16 +2,22 @@
* 评查选项卡组件
* 提供三个选项卡:评查结果、AI智能分析、文件信息
*/
import { ReactNode, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ReactNode, useState, useRef } from 'react';
import { useNavigate, useRevalidator } from 'react-router-dom';
import { loadingBarService } from '~/components/ui/LoadingBar';
import { Modal } from '~/components/ui/Modal';
import { UploadArea, type UploadAreaRef } from '~/components/ui/UploadArea';
import { Button } from '~/components/ui/Button';
import { toastService } from '~/components/ui/Toast';
import { DOCUMENT_URL } from "~/api/axios-client";
import { uploadFileToBinary, uploadDocumentToServer } from '~/api/files/files-upload';
interface ReviewTabsProps {
activeTab: string;
onTabChange: (tabKey: string) => void;
children: ReactNode;
fileInfo: {
id?: number;
previousRoute?: string;
path?: string;
auditStatus?: number;
@@ -22,7 +28,12 @@ interface ReviewTabsProps {
export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfirmResults }: ReviewTabsProps) {
const [isNavigating, setIsNavigating] = useState(false);
const [isReuploadModalOpen, setIsReuploadModalOpen] = useState(false);
const [selectedTemplateFiles, setSelectedTemplateFiles] = useState<File[]>([]);
const [isUploading, setIsUploading] = useState(false);
const uploadAreaRef = useRef<UploadAreaRef>(null);
const navigate = useNavigate();
const revalidator = useRevalidator();
// 返回上一级
const handleBack = () => {
@@ -40,9 +51,10 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
: previousRoute === 'filesUpload'
? "/files/upload"
: "/rules-files";
// 立即导航返回
navigate(returnTo);
// 立即导航返回
navigate(returnTo);
// 触发上级页面数据重新加载
revalidator.revalidate();
};
// 下载原文件
@@ -83,6 +95,127 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
}
};
// 打开重新上传模板模态框
const handleOpenReuploadModal = () => {
setIsReuploadModalOpen(true);
setSelectedTemplateFiles([]);
};
// 关闭重新上传模板模态框
const handleCloseReuploadModal = () => {
setIsReuploadModalOpen(false);
setSelectedTemplateFiles([]);
// 重置文件输入
if (uploadAreaRef.current) {
uploadAreaRef.current.resetFileInput();
}
};
// 处理模板文件选择
const handleTemplateFilesSelected = (files: FileList) => {
try {
if (files.length > 0) {
// 验证文件类型,只允许PDF文件
const validFiles: File[] = [];
let hasInvalidFiles = false;
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
console.error(`无效的文件类型: ${file.name}, 类型: ${file.type}`);
}
});
if (hasInvalidFiles) {
toastService.error('只能上传PDF格式的文件');
}
if (validFiles.length > 0) {
setSelectedTemplateFiles(validFiles);
}
}
} catch (error) {
console.error('处理模板文件选择时发生错误:', error);
toastService.error('文件选择失败,请重试');
}
};
// 确认上传模板文件
const handleConfirmUpload = async () => {
if (selectedTemplateFiles.length === 0) {
toastService.error('请先选择要上传的模板文件');
return;
}
try {
setIsUploading(true);
// 这里可以调用上传API
let binaryData: ArrayBuffer;
try {
binaryData = await uploadFileToBinary(selectedTemplateFiles[0]);
} catch (error) {
console.error('上传文件失败:', error);
throw new Error(`文件 ${selectedTemplateFiles[0].name} 转换失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
// const uploadInfo = {
// binaryData,
// fileName: selectedTemplateFiles[0].name,
// fileType: 'pdf',
// documentType: '1',
// priority: 'normal',
// documentNumber: null,
// remark: null,
// isTestDocument: false,
// documentId: fileInfo
// };
// console.log('uploadInfo',uploadInfo);
const uploadResult = await uploadDocumentToServer(
binaryData,
selectedTemplateFiles[0].name,
'pdf', //file_type 文件类型:pdf
'1', //fileType(type_id) 合同id1
'normal', //priority 优先级:normal
null, //document_number 文档编号
null, //remark 备注
false, //is_test_document 是否为测试文档:false
fileInfo.id, //document_id 主文档id
true //is_reupload 是否为重新上传:true
);
// console.log('重新上传合同模板',uploadResult);
if (uploadResult.error) {
throw new Error(uploadResult.error);
}
toastService.success('模板文件上传成功,结构比对数据将会发生更新,即将返回上一页...');
await new Promise(resolve => setTimeout(resolve, 2000));
handleCloseReuploadModal();
handleBack();
} catch (error) {
console.error('上传模板文件失败:', error);
toastService.error(`上传失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setIsUploading(false);
}
};
// 格式化文件大小
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
return (
<div className="tab-container w-full flex-1">
@@ -105,9 +238,7 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
>
<i className="ri-lightbulb-line"></i> AI智能分析
</button> */}
{/* {fileInfo.type === '1' && ( */}
{/* 隐藏结构比对 */}
{fileInfo.type === '999999' && (
{fileInfo.type === '1' && (
<button
className={`tab-nav-item ${activeTab === 'filecompare' ? 'active' : ''}`}
onClick={() => onTabChange('filecompare')}
@@ -128,6 +259,15 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
</div>
{/* 操作按钮 */}
<div className="flex space-x-3">
{/* 重新上传 */}
{activeTab === 'filecompare' && (
<button
className="ant-btn ant-btn-default flex items-center my-2 mr-4"
onClick={handleOpenReuploadModal}
>
<i className="ri-refresh-line mr-1"></i>
</button>
)}
{/* 返回上一级 */}
<button
className="ant-btn ant-btn-default flex items-center my-2"
@@ -160,6 +300,100 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
<div className="tab-content w-full">
{children}
</div>
{/* 重新上传模板模态框 */}
<Modal
isOpen={isReuploadModalOpen}
onClose={handleCloseReuploadModal}
title="重新上传模板"
size="medium"
footer={
<div className="flex justify-end space-x-3">
<Button
type="default"
onClick={handleCloseReuploadModal}
disabled={isUploading}
>
</Button>
<Button
type="primary"
onClick={handleConfirmUpload}
disabled={selectedTemplateFiles.length === 0 || isUploading}
icon={isUploading ? 'ri-loader-4-line animate-spin' : undefined}
>
{isUploading ? '上传中...' : '确定上传'}
</Button>
</div>
}
>
<div className="space-y-4">
<div className="text-sm text-gray-600 mb-4">
<p></p>
<p className="mt-2 text-orange-600">
<i className="ri-information-line mr-1"></i>
PDF格式的文件
</p>
</div>
<UploadArea
ref={uploadAreaRef}
onFilesSelected={handleTemplateFilesSelected}
accept=".pdf,application/pdf"
multiple={false}
icon="ri-file-pdf-line"
buttonText="选择模板文件"
mainText="点击或拖拽PDF文件到此区域"
tipText={
<span className="text-xs text-gray-500">
PDF
</span>
}
disabled={isUploading}
/>
{/* 已选择的文件列表 */}
{selectedTemplateFiles.length > 0 && (
<div className="mt-4">
<h4 className="text-sm font-medium text-gray-900 mb-2"></h4>
<div className="space-y-2">
{selectedTemplateFiles.map((file, index) => (
<div
key={index}
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border"
>
<div className="flex items-center">
<i className="ri-file-pdf-line text-red-500 mr-2"></i>
<div>
<div className="text-sm font-medium text-gray-900">
{file.name}
</div>
<div className="text-xs text-gray-500">
{formatFileSize(file.size)}
</div>
</div>
</div>
<button
className="text-gray-400 hover:text-red-500 transition-colors"
onClick={() => {
setSelectedTemplateFiles(prev =>
prev.filter((_, i) => i !== index)
);
if (uploadAreaRef.current) {
uploadAreaRef.current.resetFileInput();
}
}}
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
))}
</div>
</div>
)}
</div>
</Modal>
</div>
);
}
+2 -1
View File
@@ -8,4 +8,5 @@ export { FilePreview } from './FilePreview';
export { ReviewPointsList } from './ReviewPointsList';
export type { ReviewPoint } from './ReviewPointsList';
export { AIAnalysis } from './AIAnalysis';
export { FileDetails } from './FileDetails';
export { FileDetails } from './FileDetails';
export { Comparison } from './Comparison';