From 6fa65ff156453815a21c6d8cf662202f49f34c13 Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Wed, 17 Dec 2025 01:09:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=201.=20=E4=BC=98=E5=8C=96collabora?= =?UTF-8?q?=E7=9A=84=E9=AB=98=E4=BA=AE=E6=95=88=E6=9E=9C=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E4=B8=BB=E8=A6=81=E9=A1=B5=E9=9D=A2=E3=80=82?= =?UTF-8?q?=202.=20=E4=BC=98=E5=8C=96=E8=AF=84=E6=9F=A5=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E4=B8=8B=E8=BD=BD=E6=8C=89=E9=92=AE=EF=BC=8C?= =?UTF-8?q?=E5=A6=82=E6=9E=9C=E5=8A=A0=E8=BD=BDdocx=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E8=AF=9D=E9=9C=80=E8=A6=81=E5=85=88=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=86=8D=E4=B8=8B=E8=BD=BD=E3=80=82=203.=20=E4=BA=A4=E5=8F=89?= =?UTF-8?q?=E8=AF=84=E6=9F=A5=E7=BB=93=E6=9E=9C=E4=B8=AD=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=8C=89=E9=92=AE=EF=BC=8C=E5=B9=B6=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=89=93=E5=BC=80=E5=AF=B9=E5=BA=94=E7=9A=84=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E7=9A=84=E6=96=87=E6=A1=A3=E5=88=97=E8=A1=A8=E3=80=82?= =?UTF-8?q?=204.=20=E6=96=87=E6=A1=A3=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=BB=91=E5=AE=9A=E5=90=88?= =?UTF-8?q?=E5=90=8C=E7=AE=A1=E7=90=86=E4=B8=BA=E5=85=A5=E5=8F=A3=E7=9A=84?= =?UTF-8?q?=E6=97=B6=E5=80=99=E6=96=87=E6=A1=A3=E7=B1=BB=E5=9E=8B=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E5=BF=85=E9=A1=BB=E6=98=AF=E8=A6=81=E9=99=84=E5=B8=A6?= =?UTF-8?q?=E2=80=98=E5=90=88=E5=90=8C=E2=80=99=E5=AD=97=E7=AC=A6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/collabora/CollaboraViewer.tsx | 73 ++++++++++--------- .../collabora/lib/SearchandReplace.ts | 2 +- .../cross-checking/DocumentListModal.tsx | 2 +- app/components/reviews/ReviewPointsList.tsx | 1 + app/components/reviews/ReviewTabs.tsx | 35 ++++++++- app/config/api-config-wafIP.ts | 16 ++-- app/config/api-config.ts | 16 ++-- app/routes/contract-draft.$draftId.tsx | 2 +- app/routes/contract-template.detail.$id.tsx | 3 +- app/routes/cross-checking._index.tsx | 2 +- app/routes/cross-checking.result.tsx | 18 +++++ app/routes/document-types.new.tsx | 52 +++++++++++-- app/routes/reviews.tsx | 66 ++++++++++++++++- 13 files changed, 223 insertions(+), 65 deletions(-) diff --git a/app/components/collabora/CollaboraViewer.tsx b/app/components/collabora/CollaboraViewer.tsx index 4ad0047..eb27f79 100644 --- a/app/components/collabora/CollaboraViewer.tsx +++ b/app/components/collabora/CollaboraViewer.tsx @@ -185,9 +185,10 @@ export const CollaboraViewer = forwardRef { // 如果文档未加载完成,不执行跳转和高亮 if (!isDocumentLoaded || !iframeRef.current?.contentWindow) { @@ -199,10 +200,12 @@ export const CollaboraViewer = forwardRef { const iframeWindow = iframeRef.current!.contentWindow!; const textToHighlight = highlightText.trim(); + // 确定起始页码:有 targetPage 则使用,否则默认从第1页开始 + const startPage = (targetPage !== undefined && targetPage !== null) ? targetPage : 1; try { // 步骤1:清除之前的所有高亮 - console.log('[CollaboraViewer] 步骤1:清除旧高亮...'); + // console.log('[CollaboraViewer] 步骤1:清除旧高亮...'); await clearHighlights(iframeWindow, { color: 16776960, // 黄色 timeout: 5000, @@ -211,36 +214,38 @@ export const CollaboraViewer = forwardRef setTimeout(resolve, 100)); - // 步骤2:根据是否有 targetPage 选择高亮方式 - if (targetPage !== undefined && targetPage !== null) { - // 方案A:有 targetPage - 使用 Python 脚本(跨页搜索 + 高亮 + 跳转) - console.log(`[CollaboraViewer] 步骤2A:使用 Python 脚本跳转到第 ${targetPage} 页并高亮 "${textToHighlight}"`); - - const result = await pythonHighlightText(iframeWindow, textToHighlight, { - color: 16776960, // 黄色 - page: targetPage, - }); - - if (result.success) { - console.log(`[CollaboraViewer] ✓ Python 高亮成功: "${textToHighlight}" (第${targetPage}页, 共${result.highlightedCount}处)`); - } else { - console.error('[CollaboraViewer] ✗ Python 高亮失败:', result.message); - } - } else { - // 方案B:无 targetPage - 使用 UNO 命令(当前页面高亮) - console.log(`[CollaboraViewer] 步骤2B:使用 UNO 命令在当前页高亮 "${textToHighlight}"`); - - unoHighlightText(iframeWindow, textToHighlight, 16776960); // 黄色 - - // 短暂延迟,确保高亮操作完成 - await new Promise(resolve => setTimeout(resolve, 100)); - - // 取消选中状态(避免高亮后文本仍被选中) - console.log('[CollaboraViewer] 步骤3:取消选中状态...'); - sendUnoCommand(iframeWindow, '.uno:Escape', {}); - - console.log(`[CollaboraViewer] ✓ UNO 高亮完成: "${textToHighlight}"`); + // 步骤2:跳转到起始页面 + // console.log(`[CollaboraViewer] 步骤2:跳转到第 ${startPage} 页...`); + try { + await customGotoPage(iframeWindow, startPage); + // 等待页面跳转完成 + await new Promise(resolve => setTimeout(resolve, 300)); + } catch (gotoError) { + console.warn('[CollaboraViewer] 页面跳转失败,继续在当前位置搜索:', gotoError); } + + // 步骤3:使用 UNO 搜索从当前位置向下找第一个匹配项 + // UNO 搜索会自动跳转到匹配位置,如果到文档末尾没找到会循环回开头继续搜索 + console.log(`[CollaboraViewer] 步骤3:从第 ${startPage} 页开始向下搜索...`); + unoSearchNext(iframeWindow, textToHighlight); + + // 等待搜索完成 + await new Promise(resolve => setTimeout(resolve, 200)); + + // 步骤4:对搜索到的文本(当前选中)设置高亮背景色 + // console.log('[CollaboraViewer] 步骤4:设置高亮背景色...'); + sendUnoCommand(iframeWindow, '.uno:BackColor', { + BackColor: { type: 'long', value: 16776960 }, // 黄色 + }); + + // 短暂延迟,确保高亮操作完成 + await new Promise(resolve => setTimeout(resolve, 100)); + + // 步骤5:取消选中状态(避免高亮后文本仍被选中) + // console.log('[CollaboraViewer] 步骤5:取消选中状态...'); + sendUnoCommand(iframeWindow, '.uno:Escape', {}); + + console.log(`[CollaboraViewer] ✓ 搜索高亮完成:(从第${startPage}页开始搜索)`); } catch (error) { console.error('[CollaboraViewer] 高亮失败:', error); } @@ -329,7 +334,7 @@ export const CollaboraViewer = forwardRef setTimeout(resolve, 200)); // 步骤3:自动搜索下一个相同的占位符(如果还有的话) - console.log(`[CollaboraViewer] 步骤3:定位到下一个 "${newSearchText}"`); + // console.log(`[CollaboraViewer] 步骤3:定位到下一个 "${newSearchText}"`); unoSearchNext(iframeRef.current.contentWindow, newSearchText); console.log('[CollaboraViewer] ✓ 静默替换完成,已定位到下一个占位符(如有)'); diff --git a/app/components/collabora/lib/SearchandReplace.ts b/app/components/collabora/lib/SearchandReplace.ts index 17f0843..91d9209 100644 --- a/app/components/collabora/lib/SearchandReplace.ts +++ b/app/components/collabora/lib/SearchandReplace.ts @@ -80,7 +80,7 @@ export function unoSearchNext( 'Quiet': { type: 'boolean', value: quiet }, }); - console.log('[SearchReplace] 搜索下一个:', text, options); + // console.log('[SearchReplace] 搜索下一个:', text, options); } /** diff --git a/app/components/cross-checking/DocumentListModal.tsx b/app/components/cross-checking/DocumentListModal.tsx index 5267eb7..521430e 100644 --- a/app/components/cross-checking/DocumentListModal.tsx +++ b/app/components/cross-checking/DocumentListModal.tsx @@ -650,7 +650,7 @@ export function DocumentListModal({ placeholder="搜索文件名称或文档编号" value={searchKeyword} onChange={(e) => handleSearchChange(e.target.value)} - className="pl-9 pr-4 py-2 border border-gray-300 rounded-md text-sm w-64 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" + className="pl-9 pr-4 py-2 border border-gray-300 rounded-md text-sm w-64 focus:outline-none focus:ring-0" /> {searchKeyword && ( diff --git a/app/components/reviews/ReviewPointsList.tsx b/app/components/reviews/ReviewPointsList.tsx index cd9857c..92ae14e 100644 --- a/app/components/reviews/ReviewPointsList.tsx +++ b/app/components/reviews/ReviewPointsList.tsx @@ -1502,6 +1502,7 @@ export function ReviewPointsList({ if (mainTypeValue.page && typeof onReviewPointSelect === 'function') { console.log("点击了其他评查点", mainTypeValue) onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page), mainTypeValue.char_positions, mainTypeValue.value); + // onReviewPointSelect(reviewPoint.id, undefined, mainTypeValue.char_positions, mainTypeValue.value); }else if(reviewPoint.contentPage && reviewPoint.contentPage[fieldKey]){ onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[fieldKey]), mainTypeValue.char_positions, mainTypeValue.value); }else{ diff --git a/app/components/reviews/ReviewTabs.tsx b/app/components/reviews/ReviewTabs.tsx index 9d43fa5..5029436 100644 --- a/app/components/reviews/ReviewTabs.tsx +++ b/app/components/reviews/ReviewTabs.tsx @@ -28,13 +28,16 @@ interface ReviewTabsProps { }; onConfirmResults: () => void; jwtToken?: string | null; + /** 下载前保存文档的回调,返回 true 表示保存成功可以继续下载 */ + onSaveBeforeDownload?: () => Promise; } -export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfirmResults, jwtToken }: ReviewTabsProps) { +export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfirmResults, jwtToken, onSaveBeforeDownload }: ReviewTabsProps) { const [isNavigating, setIsNavigating] = useState(false); const [isReuploadModalOpen, setIsReuploadModalOpen] = useState(false); const [selectedTemplateFiles, setSelectedTemplateFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); + const [isSaving, setIsSaving] = useState(false); // 下载前保存文档的状态 const uploadAreaRef = useRef(null); const navigate = useNavigate(); const revalidator = useRevalidator(); @@ -64,6 +67,23 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi // 下载原文件 const handleDownloadFile = async () => { try { + // 如果有保存回调,先执行保存(仅对 DOCX 文件有效) + if (onSaveBeforeDownload) { + setIsSaving(true); + toastService.info('正在保存文档...'); + + const saveSuccess = await onSaveBeforeDownload(); + + setIsSaving(false); + + if (!saveSuccess) { + toastService.error('保存失败,下载已取消'); + return; + } + + toastService.success('文档已保存,开始下载...'); + } + // 使用 PDF 代理路由获取文件,自动添加 JWT 认证 const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(fileInfo.path || '')}`; @@ -95,7 +115,7 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi }, 100); } catch (error) { console.error('下载文件失败:', error); - alert(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`); + toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`); } }; @@ -279,8 +299,17 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi {/* + {/* 结构比对/查看评查结果按钮 - 仅当文档类型包含"合同"且有模板时显示 */} {hasTemplateForCompare && (