完成智慧法务前端调整20250522,还有登录和主页需要完善

This commit is contained in:
2025-05-27 23:48:28 +08:00
parent 742a789244
commit 690d369f57
30 changed files with 1557 additions and 292 deletions
+87 -27
View File
@@ -69,10 +69,11 @@ interface FilePreviewProps {
reviewPoints?: ReviewPoint[]; // 设为可选
activeReviewPointResultId: string | null;
targetPage?: number; // 新增目标页码参数
isStructuredView?: boolean; // 是否显示结构化视图
}
// export function FilePreview({ fileContent, reviewPoints, activeReviewPointResultId, targetPage }: FilePreviewProps) {
export function FilePreview({ fileContent, activeReviewPointResultId, targetPage }: FilePreviewProps) {
export function FilePreview({ fileContent, activeReviewPointResultId, targetPage, isStructuredView = false }: FilePreviewProps) {
const [zoomLevel, setZoomLevel] = useState(100);
// const [highlightsVisible, setHighlightsVisible] = useState(true);
const contentRef = useRef<HTMLDivElement>(null);
@@ -173,6 +174,9 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
// 处理页面跳转
const prevTargetPageRef = useRef<number | undefined>(undefined);
useEffect(() => {
// 调试信息:记录组件状态
// console.log(`FilePreview更新 - isStructuredView:${isStructuredView}, targetPage:${targetPage}, activeReviewPointResultId:${activeReviewPointResultId}, numPages:${numPages}`);
// 如果有目标页码,并且与上次相同,提示用户
if(targetPage && numPages && targetPage <= numPages && targetPage === prevTargetPageRef.current){
toastService.success(`已跳转至目标页码`);
@@ -181,9 +185,6 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointResultId)) {
prevTargetPageRef.current = targetPage;
let newTargetPage = targetPage;
// let newTargetPage = targetPage;
// console.log("targetPage:", targetPage);
// console.log("fileContent:", fileContent);
// 页码偏移量
try {
@@ -197,13 +198,18 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
toastService.error("访问ocrResult时出错:" + (error instanceof Error ? error.message : '未知错误'));
}
const pageElement = document.getElementById(`page-${newTargetPage}`);
const pageElementId = `page-${newTargetPage}${isStructuredView ? '-structured' : ''}`;
// console.log(`尝试跳转到元素ID: ${pageElementId}`);
const pageElement = document.getElementById(pageElementId);
if (pageElement) {
console.log(`跳转到第${newTargetPage}页,对应评查点结果ID: ${activeReviewPointResultId}`);
pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else {
console.warn(`未找到页面元素: ${pageElementId}`);
}
}
}, [targetPage, numPages, fileContent, activeReviewPointResultId]);
}, [targetPage, numPages, fileContent, activeReviewPointResultId, isStructuredView]);
// 获取评查点对应的样式类
// const getHighlightClass = (status: string) => {
@@ -302,9 +308,12 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
marginBottom: `${calculatePageMargin(zoomFactor)}px`, // 动态计算页面间距
};
// 为结构化视图和普通视图创建不同的ID
const pageId = isStructuredView ? `page-${i}-structured` : `page-${i}`;
// 为每一页创建组件
pages.push(
<div key={i} id={`page-${i}`} style={pageContainerStyle}>
<div key={i} id={pageId} style={pageContainerStyle}>
{/* 页码标识,显示在页面上方 */}
<div className="text-center text-gray-500 text-sm mb-2"> {i} </div>
@@ -365,7 +374,20 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
// 渲染文档内容
const renderDocumentContent = () => {
return (
// 如果路径无效,显示错误信息
if (!fileContent.path) {
return (
<div className="text-red-500 p-4">
<p></p>
</div>
);
}
// 获取文件扩展名
const fileExtension = fileContent.path.split('.').pop()?.toLowerCase();
// PDF内容渲染
const renderPdfContent = () => (
<div
style={{
...styles.pdfContainer,
@@ -390,32 +412,75 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
</Document>
</div>
);
// 结构化数据渲染
const renderStructuredData = () => (
<div className="structured-view p-4 text-left mt-4 border-t border-gray-200">
<div className="text-xs font-medium mb-2"></div>
{fileContent.ocrResult ? (
<div className="overflow-auto max-h-[300px]">
<pre className="text-xs whitespace-pre-wrap bg-gray-50 p-3 rounded">
{JSON.stringify(fileContent.ocrResult, null, 2)}
</pre>
</div>
) : (
<div className="text-gray-500 p-4 text-center">
<p></p>
</div>
)}
</div>
);
// 根据文件类型选择不同的渲染方式
if (fileExtension === 'pdf') {
// 结构化视图模式:显示PDF和结构化数据
if (isStructuredView) {
return (
<div>
{renderPdfContent()}
{renderStructuredData()}
</div>
);
}
// 普通模式:仅显示PDF
return renderPdfContent();
} else {
// 非PDF文件显示不支持消息
return (
<div className="text-gray-500 p-4">
<p>{fileExtension}</p>
</div>
);
}
};
return (
<div className="file-preview">
<div className="file-preview-header py-2 px-4">
<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="ri-file-text-line text-primary mr-2"></i>
<span className="font-medium text-primary"></span>
<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>
<div className="file-preview-actions flex items-center">
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5 "
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5"
onClick={handleScrollToTop}
title="返回顶部"
>
<i className="ri-arrow-up-double-line"></i>
<span className="ml-1"></span>
<span className="ml-1 sm:inline md:inline lg:hidden xl:inline"></span>
</button>
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5"
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5"
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 h-5 leading-5"
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5"
onClick={handleZoomOut}
title="缩小"
>
<i className="ri-zoom-out-line"></i>
</button>
@@ -423,7 +488,7 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
<div className="inline-flex items-center ml-2">
<input
type="text"
className="ant-input ant-input-sm py-0 px-1 text-xs h-5 leading-5 w-10 text-center
className="ant-input ant-input-sm py-0 px-1 text-xs max-h-6 leading-5 max-w-[2.5rem] text-center
focus:outline-none focus:ring-1 focus:ring-green-900"
placeholder="页码"
value={pageInputValue}
@@ -431,29 +496,24 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
onKeyDown={handlePageInputKeyDown}
/>
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5 ml-1"
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs max-h-6 leading-5 ml-1"
onClick={handlePageJump}
disabled={!numPages}
title="跳转到页面"
>
<i className="ri-arrow-right-line"></i>
</button>
{numPages && <span className="ml-1 text-xs text-gray-500">/ {numPages}</span>}
{numPages && <span className="ml-1 text-xs text-gray-500 sm:inline md:inline lg:hidden xl:inline">/ {numPages}</span>}
</div>
{/* <button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5"
onClick={toggleHighlights}
>
<i className="ri-mark-pen-line"></i> {highlightsVisible ? '隐藏问题' : '显示问题'}
</button> */}
<span className="ml-2 text-xs text-gray-500 inline-block">{"比例:"+zoomLevel+"%"}</span>
<span className="ml-2 text-xs text-gray-500 inline-block sm:inline md:inline lg:hidden xl:inline">{"比例:"+zoomLevel+"%"}</span>
<button
className={`ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 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 ${dragMode ? 'active bg-green-300' : ''}`}
title="切换拖拽模式"
aria-pressed={dragMode}
onClick={toggleDragMode}
>
<i className="ri-drag-move-line"></i>
<span className="ml-1">{dragMode ? '(已激活)' : ''}</span>
<span className="ml-1 sm:inline md:inline lg:hidden xl:inline">{dragMode ? '(已激活)' : ''}</span>
</button>
</div>
</div>
+166 -79
View File
@@ -55,6 +55,16 @@ export interface ReviewPoint {
};
postAction?: string;
actionContent?: string;
evaluationConfig?: {
rules?: Array<{
type: string;
config?: {
fields?: string[];
pairs?: Array<{ sourceField?: string; targetField?: string }>;
logic?: string;
};
}>;
};
}
// 统计数据类型
@@ -444,84 +454,160 @@ export function ReviewPointsList({
* @returns 评查点主要内容组件
*/
const renderContent = (reviewPoint: ReviewPoint, result?: boolean) => {
// 获取evaluationConfig中type为consistency的规则 评查点一致性规则组的规则
const consistencyRules = reviewPoint.evaluationConfig?.rules?.filter(rule => rule.type === 'consistency') || [];
// 获取所有consistency规则中的fields
const allConsistencyFields: string[][] = [];
consistencyRules.forEach(rule => {
if (rule.config?.fields) {
allConsistencyFields.push(rule.config.fields);
}else if (rule.config?.pairs) {
// 处理pairs情况,提取sourceField和targetField
const fields: string[] = [];
rule.config.pairs.forEach(pair => {
if (pair.sourceField) fields.push(pair.sourceField);
if (pair.targetField) fields.push(pair.targetField);
});
if (fields.length > 0) {
allConsistencyFields.push(fields);
}
}
});
// 对content进行排序
const contentEntries = Object.entries(reviewPoint.content);
// 按照consistency规则分组
const groupedContent: Record<string, Array<[string, { page?: number | string, value?: object }]>> = {
'default': [] // 默认组,存放不属于任何consistency规则的项
};
// 为每个consistency规则创建分组
allConsistencyFields.forEach((fields, index) => {
groupedContent[`consistency_${index}`] = [];
});
// 将content按照规则分组
contentEntries.forEach(entry => {
const [key, value] = entry;
// 检查是否属于某个consistency规则
let assigned = false;
allConsistencyFields.forEach((fields, index) => {
if (fields.includes(key)) {
groupedContent[`consistency_${index}`].push(entry);
assigned = true;
}
});
// 如果不属于任何规则,放入默认组
if (!assigned) {
groupedContent['default'].push(entry);
}
});
return (
<>
{/* 修改评查结果的结构之后,显示新的结构 */}
{Object.entries(reviewPoint.content).map(([key, value], index) => !(result && value.value?.toString().trim() == '') && (
<div
key={index}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1 group"
onClick={(e) => {
e.stopPropagation();
console.log(`单独点击${key}----`, reviewPoint);
const valuePage = parseInt(value.page as string);
const contentPage = parseInt(reviewPoint.contentPage?.[key] as string);
// 检查value中的page属性是否存在,优先取value中的page
if (valuePage > 0) {
console.log(`存在page且不为空:单独点击${key}---------->evaluated_results内的页码:`, valuePage);
onReviewPointSelect(reviewPoint.id, valuePage);
{/* 渲染各个分组 */}
{Object.entries(groupedContent).map(([groupKey, entries], groupIndex) => {
if (entries.length === 0) return null;
// 非默认组添加边框
const isDefaultGroup = groupKey === 'default';
return (
<div
key={groupKey}
className={`mb-1 ${!isDefaultGroup ? `border border-${result ? 'green' : 'red'}-200 rounded-md` : ''}`}
>
{/* 分组标题,只有非默认组显示 */}
{/* {!isDefaultGroup && (
<div className="text-xs font-medium text-red-500 mb-2">
规则组 {groupIndex}
</div>
)} */}
{/* 渲染组内内容 */}
{entries.map(([key, value], index) =>
!(result && value.value?.toString().trim() == '') && (
<div
key={`${groupKey}_${index}`}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1 group"
onClick={(e) => {
e.stopPropagation();
console.log(`单独点击${key}----`, reviewPoint);
const valuePage = parseInt(value.page as string);
const contentPage = parseInt(reviewPoint.contentPage?.[key] as string);
// 检查value中的page属性是否存在,优先取value中的page
if (valuePage > 0) {
console.log(`存在page且不为空:单独点击${key}---------->evaluated_results内的页码:`, valuePage);
onReviewPointSelect(reviewPoint.id, valuePage);
} else if(contentPage && contentPage > 0) {
console.log(`存在page且为空:单独点击${key}---------->ocr_result内的页码:`, contentPage);
onReviewPointSelect(reviewPoint.id, contentPage);
}else {
toastService.error(`无法找到"${key}"对应的索引内容`);
console.log(`单独点击${key}--------没有对应页码`);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
const valuePage = parseInt(value.page as string);
const contentPage = parseInt(reviewPoint.contentPage?.[key] as string);
// 检查value中的page属性是否存在,优先取value中的page
if (valuePage > 0) {
onReviewPointSelect(reviewPoint.id, valuePage);
} else if(contentPage && contentPage > 0) {
onReviewPointSelect(reviewPoint.id, contentPage);
} else {
toastService.error(`无法找到"${key}"对应的索引内容`);
console.log(`单独点击${key}--------没有对应页码`);
}
}
}}
role="button"
tabIndex={0}
aria-label={`查看${key}内容详情`}
onMouseLeave={(e) => {
// 获取容器内的滚动区域元素
const scrollContainer = e.currentTarget.querySelector('.text-container');
if (scrollContainer) {
// 在文本缩回之前重置滚动位置
scrollContainer.scrollTop = 0;
}
}}
>
{/* <div className="flex justify-between items-center mb-1"> */}
<div className="flex items-center mb-1">
<span className="text-xs pr-5">
{key}
</span>
<span className={`flex-shrink-0 text-xs w-15 ${value.value?.toString().trim() ? 'text-error' : 'text-warning'}`}>
{parseInt(value.page as string)>0 || parseInt(reviewPoint.contentPage?.[key] as string)>0 ? '' : <i title="无法找到索引内容" className="ri-alert-line text-red-500 mr-2"></i>}
{value.value?.toString().trim() ? '' : '缺失'}
</span>
</div>
} else if(contentPage && contentPage > 0) {
console.log(`存在page且为空:单独点击${key}---------->ocr_result内的页码:`, contentPage);
onReviewPointSelect(reviewPoint.id, contentPage);
}else {
toastService.error(`无法找到"${key}"对应的索引内容`);
console.log(`单独点击${key}--------没有对应页码`);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
const valuePage = parseInt(value.page as string);
const contentPage = parseInt(reviewPoint.contentPage?.[key] as string);
// 检查value中的page属性是否存在,优先取value中的page
if (valuePage > 0) {
onReviewPointSelect(reviewPoint.id, valuePage);
} else if(contentPage && contentPage > 0) {
onReviewPointSelect(reviewPoint.id, contentPage);
} else {
toastService.error(`无法找到"${key}"对应的索引内容`);
console.log(`单独点击${key}--------没有对应页码`);
}
}
}}
role="button"
tabIndex={0}
aria-label={`查看${key}内容详情`}
onMouseLeave={(e) => {
// 获取容器内的滚动区域元素
const scrollContainer = e.currentTarget.querySelector('.text-container');
if (scrollContainer) {
// 在文本缩回之前重置滚动位置
scrollContainer.scrollTop = 0;
}
}}
>
{/* <div className="flex justify-between items-center mb-1"> */}
<div className="flex items-center mb-1">
<span className="text-xs pr-5">
{key}
</span>
<span className={`flex-shrink-0 text-xs w-15 ${value.value?.toString().trim() ? 'text-error' : 'text-warning'}`}>
{parseInt(value.page as string)>0 || parseInt(reviewPoint.contentPage?.[key] as string)>0 ? '' : <i title="无法找到索引内容" className="ri-alert-line text-red-500 mr-2"></i>}
{value.value?.toString().trim() ? '' : '缺失'}
</span>
</div>
<div className="relative text-container max-h-96 group-hover:overflow-auto overflow-hidden">
<p
className="text-xs text-left select-text block overflow-hidden !line-clamp-2
group-hover:!line-clamp-none group-hover:bg-white group-hover:shadow-md group-hover:z-10 group-hover:relative px-1 rounded transition-all duration-300 ease-in-out cursor-text"
// title={value.value?.toString() || ''}
// style={{ userSelect: 'all' }}
>
{(value.value?.toString().trim() === '')
? ""
: value.value?.toString() || ''}
</p>
<div className="relative text-container max-h-96 group-hover:overflow-auto overflow-hidden">
<p
className="text-xs text-left select-text block overflow-hidden !line-clamp-2
group-hover:!line-clamp-none group-hover:bg-white group-hover:shadow-md group-hover:z-10 group-hover:relative px-1 rounded transition-all duration-300 ease-in-out cursor-text"
// title={value.value?.toString() || ''}
// style={{ userSelect: 'all' }}
>
{(value.value?.toString().trim() === '')
? ""
: value.value?.toString() || ''}
</p>
</div>
</div>
))}
</div>
</div>
))}
);
})}
</>
);
};
@@ -533,7 +619,7 @@ export function ReviewPointsList({
* @returns 评查点内容与建议组件
*/
const renderReviewPointContent = (reviewPoint: ReviewPoint) => {
const handleManualReviewNotesChange = (reviewPointId: string, text: string) => {
setManualReviewNotes(prev => ({
...prev,
@@ -541,8 +627,9 @@ export function ReviewPointsList({
}));
};
// 如果当前评查点不处于编辑状态,只显示简单信息
// 如果当前评查点不处于编辑状态 TODO delete
if (editingReviewPoint !== reviewPoint.id) {
// 根据result和status决定渲染哪种样式
if (reviewPoint.result === true) {
// 已通过的评查点只显示基本信息和人工审核注释
@@ -893,11 +980,11 @@ export function ReviewPointsList({
>
{/* 评查点标题和状态 */}
{/* 评查点名称 pointName*/}
<div className="review-point-title flex-1 text-left text-blue-500">{'评查点名称:' + reviewPoint.pointName}</div>
<div className="review-point-title flex-1 text-left text-blue-500">{reviewPoint.pointName}</div>
<div className="review-point-header flex justify-between items-start">
<div className="review-point-title flex-1 text-left min-w-[25%]">{reviewPoint.title}</div>
<div className="flex-1 text-left min-w-[25%] font-medium text-[13px]">{reviewPoint.title}</div>
{/* 评查点所属分组 */}
<div className="review-point-location max-w-[40%] flex items-center">
{/* <div className="review-point-location max-w-[40%] flex items-center">
<i className="ri-file-list-line mr-1 flex-shrink-0"></i>
<span
className="truncate block whitespace-nowrap overflow-hidden
@@ -907,7 +994,7 @@ export function ReviewPointsList({
>
{reviewPoint.groupName}
</span>
</div>
</div> */}
<div className="flex flex-col items-center ml-2 flex-shrink-0 max-w-[15%]">
{renderStatusBadge(reviewPoint.status, reviewPoint.result)}
{renderHumanReviewBadge(reviewPoint)}
+10 -7
View File
@@ -15,6 +15,7 @@ interface ReviewTabsProps {
previousRoute?: string;
path?: string;
auditStatus?: number;
type?: string;
};
onConfirmResults: () => void;
}
@@ -104,14 +105,16 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
>
<i className="ri-lightbulb-line"></i> AI智能分析
</button> */}
<button
className={`tab-nav-item ${activeTab === 'fileinfo' ? 'active' : ''}`}
onClick={() => onTabChange('fileinfo')}
type="button"
aria-pressed={activeTab === 'fileinfo'}
{fileInfo.type === '1' && (
<button
className={`tab-nav-item ${activeTab === 'filecompare' ? 'active' : ''}`}
onClick={() => onTabChange('filecompare')}
type="button"
aria-pressed={activeTab === 'filecompare'}
>
<i className="ri-flip-horizontal-line"></i>
</button>
<i className="ri-flip-horizontal-line"></i>
</button>
)}
<button
className={`tab-nav-item ${activeTab === 'fileinfo' ? 'active' : ''}`}
onClick={() => onTabChange('fileinfo')}
+51 -13
View File
@@ -118,8 +118,27 @@ export function ExtractionSettings({
// 自动保存字段变更状态
// 这个效果确保添加字段后自动保存到组件状态,但不自动提交更新
useEffect(() => {
setHasPendingChanges(true);
}, [fields, regexFields, promptContent])
// 初始加载时不设置hasPendingChanges为true
// 仅当用户进行了实际修改后才标记为有变更
const initialLlmFields = initialData?.extraction_config?.llm?.fields || [];
const initialVlmFields = initialData?.extraction_config?.vlm?.fields || [];
const initialRegexFields = initialData?.extraction_config?.regex?.fields || [];
const initialLlmPrompt = initialData?.extraction_config?.llm?.prompt_setting?.template || '';
const initialVlmPrompt = initialData?.extraction_config?.vlm?.prompt_setting?.template || '';
// 检查是否有实际变化
const hasLlmFieldsChanged = JSON.stringify(fields.llm) !== JSON.stringify(initialLlmFields);
const hasVlmFieldsChanged = JSON.stringify(fields.vlm) !== JSON.stringify(initialVlmFields);
const hasRegexFieldsChanged = JSON.stringify(regexFields) !== JSON.stringify(initialRegexFields);
const hasPromptContentChanged =
promptContent.llm !== initialLlmPrompt ||
promptContent.vlm !== initialVlmPrompt;
// 只有实际发生变化时才设置为true
if (hasLlmFieldsChanged || hasVlmFieldsChanged || hasRegexFieldsChanged || hasPromptContentChanged) {
setHasPendingChanges(true);
}
}, [fields, regexFields, promptContent, initialData])
// 处理字段输入变化
const handleFieldInputChange = (
@@ -541,7 +560,7 @@ export function ExtractionSettings({
<label className="form-label mb-1" htmlFor="field-input">
</label>
<div className="flex mb-2">
<div className="flex">
<input
type="text"
className="form-input mr-2"
@@ -561,6 +580,9 @@ export function ExtractionSettings({
</button>
</div>
<div className="form-tip mb-2 text-xs">
</div>
<div className="chips-container" id="fields-container">
{fields.llm.map((field, index) => (
<div className="chip" key={`llm-field-${index}`}>
@@ -581,9 +603,7 @@ export function ExtractionSettings({
</div>
))}
</div>
<div className="form-tip mt-1 text-xs">
</div>
</div>
</div>
@@ -666,11 +686,18 @@ export function ExtractionSettings({
].map((variable) => (
<button
key={variable}
type="button"
type="button"
className="var-tag"
onClick={() => applyVariableToPrompt(variable, "llm")}
>
{variable}
{variable=='docType' ? '文档类型:{docType}':
variable=='fieldsList' ? '抽取字段列表:{fieldsList}':
variable=='companyName' ? '公司名称:{companyName}':
variable=='documentId' ? '文档编号:{documentId}':
variable=='date' ? '日期:{date}':
variable=='industry' ? '行业:{industry}':
variable=='ocrText' ? 'OCR文本:{ocrText}':
variable}
</button>
))}
</div>
@@ -692,7 +719,7 @@ export function ExtractionSettings({
<label className="form-label mb-1" htmlFor="field-input-vlm">
</label>
<div className="flex mb-2">
<div className="flex">
<input
type="text"
className="form-input mr-2"
@@ -713,6 +740,9 @@ export function ExtractionSettings({
</button>
</div>
<div className="form-tip mb-2 text-xs">
</div>
<div className="chips-container" id="fields-container-vlm">
{fields.vlm.map((field, index) => {
const { fieldName, fieldType, typeName, badgeClass } =
@@ -747,9 +777,7 @@ export function ExtractionSettings({
);
})}
</div>
<div className="form-tip mt-1 text-xs">
</div>
</div>
</div>
@@ -843,7 +871,17 @@ export function ExtractionSettings({
className="var-tag"
onClick={() => applyVariableToPrompt(variable, "vlm")}
>
{variable}
{variable=='docType' ? '文档类型:{docType}':
variable=='fieldsList' ? '抽取字段列表:{fieldsList}':
variable=='companyName' ? '公司名称:{companyName}':
variable=='documentId' ? '文档编号:{documentId}':
variable=='date' ? '日期:{date}':
variable=='industry' ? '行业:{industry}':
variable=='contentType' ? '内容类型:{contentType}':
variable=='pageRange' ? '页面范围:{pageRange}':
variable=='colorMode' ? '色彩模式:{colorMode}':
variable=='ocrText' ? 'OCR文本:{ocrText}':
variable}
</button>
))}
</div>
+21 -4
View File
@@ -13,6 +13,7 @@ export interface DateRangePickerProps {
endId?: string;
format?: string;
placeholder?: string;
colorMode?: 'light' | 'dark' | 'auto';
}
export function links() {
@@ -57,7 +58,8 @@ export function DateRangePicker({
startId = "date-start",
endId = "date-end",
format = "yyyy-MM-dd",
placeholder = "请选择时间"
placeholder = "请选择时间",
colorMode = 'auto'
}: DateRangePickerProps) {
// 使用ref获取input元素
const startInputRef = useRef<HTMLInputElement>(null);
@@ -121,8 +123,15 @@ export function DateRangePicker({
setFocusedInput(null);
};
// 获取颜色模式类名
const getColorModeClass = () => {
if (colorMode === 'light') return 'color-mode-light';
if (colorMode === 'dark') return 'color-mode-dark';
return ''; // auto模式不添加额外类名
};
return (
<div className={`date-range-picker ${className}`}>
<div className={`date-range-picker ${getColorModeClass()} ${className}`}>
<div className="date-range-fields">
<div className="date-field">
<label htmlFor={startId} className="date-label">{startLabel}</label>
@@ -200,7 +209,8 @@ export function SimpleDateRangePicker({
startId = "date-start-simple",
endId = "date-end-simple",
format = "yyyy-MM-dd",
placeholder = "请选择时间"
placeholder = "请选择时间",
colorMode = 'auto'
}: Omit<DateRangePickerProps, 'startLabel' | 'endLabel'>) {
// 使用ref获取input元素
const startInputRef = useRef<HTMLInputElement>(null);
@@ -264,8 +274,15 @@ export function SimpleDateRangePicker({
setFocusedInput(null);
};
// 获取颜色模式类名
const getColorModeClass = () => {
if (colorMode === 'light') return 'color-mode-light';
if (colorMode === 'dark') return 'color-mode-dark';
return ''; // auto模式不添加额外类名
};
return (
<div className={`date-range-picker simple-date-range-picker ${className}`}>
<div className={`date-range-picker simple-date-range-picker ${getColorModeClass()} ${className}`}>
<div className="date-range-fields">
<div
className={`date-input-wrapper ${focusedInput === startId ? 'focused' : ''}`}
+68 -2
View File
@@ -6,6 +6,10 @@ export type FileType =
| 'license'
| 'punishment'
| 'agreement'
| 'contract' // 合同文档
| 'license-doc' // 行政许可卷宗
| 'punishment-doc' // 行政处罚卷宗
| 'other' // 其他
| string;
interface FileTypeTagProps {
@@ -15,6 +19,8 @@ interface FileTypeTagProps {
size?: 'default' | 'sm' | 'lg';
showIcon?: boolean;
fileType?: string;
colorMode?: 'light' | 'dark' | 'auto';
typeName?: string; // 新增typeName属性,用于根据文档类型名称判断样式
}
export function links() {
@@ -30,6 +36,8 @@ export function links() {
* @param size 尺寸:default, sm, lg
* @param showIcon 是否显示图标,默认为true
* @param fileType 文件类型,不提供则使用文档类型决定样式
* @param colorMode 颜色模式:light, dark, auto(默认),设置为auto时跟随系统
* @param typeName 文档类型名称,用于判断样式
*/
export function FileTypeTag({
type,
@@ -37,7 +45,9 @@ export function FileTypeTag({
className = '',
size = 'default',
showIcon = true,
fileType
fileType,
colorMode = 'auto',
typeName
}: FileTypeTagProps) {
// 文档类型对应的图标
const getTypeIcon = () => {
@@ -47,26 +57,75 @@ export function FileTypeTag({
'license': 'ri-vip-crown-line',
'punishment': 'ri-scales-line',
'agreement': 'ri-file-paper-line',
'pdf': 'ri-file-pdf-line',
'doc': 'ri-file-word-line',
'docx': 'ri-file-word-line',
'xls': 'ri-file-excel-line',
'xlsx': 'ri-file-excel-line',
'ppt': 'ri-file-ppt-line',
'pptx': 'ri-file-ppt-line',
'contract': 'ri-file-list-3-line', // 合同文档
'license-doc': 'ri-vip-crown-line', // 行政许可卷宗
'punishment-doc': 'ri-scales-line', // 行政处罚卷宗
'other': 'ri-file-paper-line', // 其他
};
return typeIconMap[type] || 'ri-file-text-line';
};
// 文档类型对应的文本
const getTypeText = () => {
// 如果提供了自定义文本,优先使用
if (text) return text;
// 如果提供了typeName,优先使用
if (typeName) return typeName;
const typeTextMap: Record<string, string> = {
'sales-contract': '销售合同',
'purchase-contract': '采购合同',
'license': '专卖许可证',
'punishment': '行政处罚决定书',
'agreement': '承包协议',
'pdf': 'PDF',
'doc': 'Word',
'docx': 'Word',
'xls': 'Excel',
'xlsx': 'Excel',
'ppt': 'PPT',
'contract': '合同文档', // 合同文档
'license-doc': '行政许可卷宗', // 行政许可卷宗
'punishment-doc': '行政处罚卷宗', // 行政处罚卷宗
'other': '其他文档', // 其他
};
return typeTextMap[type] || type;
};
// 获取根据typeName判断的样式类名
const getTypeNameClass = () => {
if (!typeName) return '';
// 根据typeName判断文档类型
if (typeName.includes('合同')) {
return 'file-type-tag-contract';
} else if (typeName.includes('许可') || typeName.includes('行政许可')) {
return 'file-type-tag-license-doc';
} else if (typeName.includes('处罚') || typeName.includes('行政处罚')) {
return 'file-type-tag-punishment-doc';
} else {
return 'file-type-tag-other';
}
};
// 获取文档类型对应的类名
const getTypeClass = () => {
// 如果有typeName,根据typeName判断样式
if (typeName) {
const typeNameClass = getTypeNameClass();
if (typeNameClass) {
return `file-type-tag ${typeNameClass}`;
}
}
// 如果有文件类型,优先使用文件类型决定样式
if (fileType) {
const fileTypeClass = getFileTypeClass(fileType);
@@ -108,8 +167,15 @@ export function FileTypeTag({
return !showIcon ? 'file-type-tag-no-icon' : '';
};
// 获取颜色模式类名
const getColorModeClass = () => {
if (colorMode === 'light') return 'color-mode-light';
if (colorMode === 'dark') return 'color-mode-dark';
return ''; // auto模式不添加额外类名
};
return (
<span className={`${getTypeClass()} ${getSizeClass()} ${getIconClass()} ${className}`}>
<span className={`${getTypeClass()} ${getSizeClass()} ${getIconClass()} ${getColorModeClass()} ${className}`}>
{showIcon && <i className={`${getTypeIcon()} file-type-icon`}></i>}
<span className="file-type-text">{getTypeText()}</span>
</span>
+5 -1
View File
@@ -143,6 +143,7 @@ interface DateRangeFilterProps {
startLabel?: string;
endLabel?: string;
simple?: boolean;
colorMode?: 'light' | 'dark' | 'auto';
}
/**
@@ -168,7 +169,8 @@ export function DateRangeFilter({
className = '',
startLabel = "从",
endLabel = "至",
simple = false
simple = false,
colorMode = 'auto'
}: DateRangeFilterProps) {
return (
<div className={`filter-item ${className}`}>
@@ -180,6 +182,7 @@ export function DateRangeFilter({
onStartDateChange={onStartDateChange}
onEndDateChange={onEndDateChange}
className="filter-control"
colorMode={colorMode}
/>
) : (
<DateRangePicker
@@ -190,6 +193,7 @@ export function DateRangeFilter({
startLabel={startLabel}
endLabel={endLabel}
className="filter-control"
colorMode={colorMode}
/>
)}
</div>