@@ -55,6 +55,8 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
const [ showSearchReplacePanel , setShowSearchReplacePanel ] = useState ( false ) ;
// 标记是否应该自动执行搜索
const shouldAutoSearchRef = useRef ( false ) ;
// 标记是否应该自动执行替换(静默模式)
const shouldAutoReplaceRef = useRef ( false ) ;
// 高亮测试面板状态
const [ highlightTextInput , setHighlightTextInput ] = useState ( '' ) ;
@@ -131,7 +133,7 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
// 3. UNO 命令封装
const unoCommands = useCollaboraUnoCommands ( iframeRef ) ;
// 4. 暴露接口给父组件(包括清除高亮方法)
// 4. 暴露接口给父组件(包括清除高亮方法和保存方法 )
useImperativeHandle ( ref , ( ) = > ( {
unoCommands ,
isReady : isDocumentLoaded ,
@@ -150,6 +152,35 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
console . warn ( '[CollaboraViewer] ⚠️ 无法清除高亮:iframe window 不可用' ) ;
}
} ,
saveDocument : async ( ) = > {
const savedWindow = iframeWindowRef . current || iframeRef . current ? . contentWindow ;
if ( savedWindow ) {
console . log ( '[CollaboraViewer] 💾 父组件调用保存文档' ) ;
try {
// 步骤1:发送保存命令
sendUnoCommand ( savedWindow , '.uno:Save' , { } ) ;
// console.log('[CollaboraViewer] ✓ 保存命令已发送');
// 步骤2:等待 WOPI PutFile 请求完成(增加到 2000ms)
// await new Promise(resolve => setTimeout(resolve, 2000));
// 步骤3:再次发送保存命令确保完全保存
sendUnoCommand ( savedWindow , '.uno:Save' , { } ) ;
// console.log('[CollaboraViewer] ✓ 二次保存命令已发送');
// 步骤4:再等待一段时间确保保存完成
// await new Promise(resolve => setTimeout(resolve, 1000));
// console.log('[CollaboraViewer] ✓ 文档保存完成(总等待 3000ms) ');
} catch ( error ) {
console . error ( '[CollaboraViewer] ✗ 保存文档失败:' , error ) ;
throw error ;
}
} else {
console . warn ( '[CollaboraViewer] ⚠️ 无法保存文档:iframe window 不可用' ) ;
throw new Error ( 'iframe window 不可用' ) ;
}
} ,
} ) , [ unoCommands , isDocumentLoaded , mode ] ) ;
// 5. 监听 targetPage 和 highlightText 变化,自动跳转并高亮
@@ -182,7 +213,7 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
textToHighlight ,
{ page : targetPage }
) ;
console . log ( ` [CollaboraViewer] 已高亮文本: " ${ textToHighlight } " ${ targetPage ? ` (第 ${ targetPage } 页) ` : '' } ` ) ;
// console.log(` [CollaboraViewer] 已高亮文本: "${textToHighlight}"${targetPage ? ` (第${targetPage}页)` : ''}`);
} catch ( error ) {
console . error ( '[CollaboraViewer] 高亮失败:' , error ) ;
}
@@ -192,16 +223,30 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
}
} , [ targetPage , highlightText , isDocumentLoaded ] ) ;
// 6. 组件销毁时清除所有高亮(使用保存的 window 引用)
// 6. 组件销毁时保存文档并 清除所有高亮(使用保存的 window 引用)
useEffect ( ( ) = > {
// 返回清理函数,在组件卸载时执行
return ( ) = > {
const savedWindow = iframeWindowRef . current ;
if ( savedWindow ) {
console . log ( '[CollaboraViewer] 🔥 组件即将销毁,立即清除所有高亮' ) ;
// console.log( '[CollaboraViewer] 🔥 组件即将销毁,触发文档保存和清除高亮');
// 立即触发清除操作,不等待异步完成
// 使用 void 关键字表示我们不关心 Promise 的结果
// 步骤1:发送保存命令(如果是编辑模式)
if ( mode === 'edit' ) {
try {
console . log ( '[CollaboraViewer] 💾 组件销毁时发送保存命令' ) ;
sendUnoCommand ( savedWindow , '.uno:Save' , { } ) ;
// 再次发送确保保存
setTimeout ( ( ) = > {
sendUnoCommand ( savedWindow , '.uno:Save' , { } ) ;
console . log ( '[CollaboraViewer] ✓ 二次保存命令已发送' ) ;
} , 100 ) ;
} catch ( error ) {
console . error ( '[CollaboraViewer] ✗ 组件销毁时保存失败:' , error ) ;
}
}
// 步骤2:清除高亮
void clearHighlights ( savedWindow , {
color : 16776960 , // 黄色
timeout : 3000 ,
@@ -217,7 +262,7 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
console . warn ( '[CollaboraViewer] ⚠️ 组件销毁时未找到保存的 window 引用' ) ;
}
} ;
} , [ ] ) ;
} , [ mode ] ) ;
// 7. 监听 AI 建议替换参数变化,设置搜索参数
useEffect ( ( ) = > {
@@ -227,20 +272,32 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
console . log ( '[CollaboraViewer] 收到 AI 建议替换参数:' , aiSuggestionReplace ) ;
const { searchText : newSearchText , replaceText : newReplaceText , pageNumber } = aiSuggestionReplace ;
const { searchText : newSearchText , replaceText : newReplaceText , pageNumber , silentReplace } = aiSuggestionReplace ;
// 显示搜索替换 面板
setShowSearchReplacePanel ( true ) ;
// 根据 silentReplace 标志决定是否 显示面板
if ( silentReplace ) {
// 静默替换模式:不显示面板
console . log ( '[CollaboraViewer] 静默替换模式,不显示面板' ) ;
} else {
// 显示搜索替换面板
setShowSearchReplacePanel ( true ) ;
}
// 设置搜索、替换和页码输入框的值
setSearchText ( newSearchText ) ;
setReplaceText ( newReplaceText ) ;
setSearchReplacePageNumber ( String ( pageNumber ) ) ;
// 设置自动搜索 标志
shouldAutoSearchRef . current = true ;
console . log ( '[CollaboraViewer] 已设置搜索参数,等待状态更新后自动执行查找' ) ;
// 根据模式设置对应的自动执行 标志
if ( silentReplace ) {
// 静默替换:自动执行替换
shouldAutoReplaceRef . current = true ;
console . log ( '[CollaboraViewer] 已设置自动替换标志' ) ;
} else {
// 普通模式:仅自动执行查找
shouldAutoSearchRef . current = true ;
console . log ( '[CollaboraViewer] 已设置搜索参数,等待状态更新后自动执行查找' ) ;
}
} , [ aiSuggestionReplace , isDocumentLoaded ] ) ;
// 8. 当搜索参数更新完成后,自动执行查找
@@ -261,6 +318,55 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
}
} , [ searchText , searchReplacePageNumber , isDocumentLoaded ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
// 9. 当搜索参数更新完成后,自动执行替换(静默模式)
useEffect ( ( ) = > {
if ( shouldAutoReplaceRef . current && searchText && replaceText && searchReplacePageNumber && isDocumentLoaded ) {
console . log ( '[CollaboraViewer] 静默替换模式:自动执行替换:' , { searchText , replaceText , searchReplacePageNumber } ) ;
// 重置标志
shouldAutoReplaceRef . current = false ;
// 延迟执行,确保 DOM 更新完成
const timer = setTimeout ( async ( ) = > {
if ( ! iframeRef . current ? . contentWindow ) {
console . error ( '[CollaboraViewer] iframe 未就绪,无法执行替换' ) ;
return ;
}
try {
const pageNumber = parseInt ( searchReplacePageNumber , 10 ) ;
// 步骤1:跳转到指定页
console . log ( ` [CollaboraViewer] 步骤1:跳转到第 ${ pageNumber } 页 ` ) ;
await customGotoPage ( iframeRef . current . contentWindow , pageNumber ) ;
// 等待页面渲染
await new Promise ( resolve = > setTimeout ( resolve , 300 ) ) ;
// 步骤2:搜索文本(确保文本被选中)
console . log ( ` [CollaboraViewer] 步骤2:搜索文本 " ${ searchText } " ` ) ;
unoSearchNext ( iframeRef . current . contentWindow , searchText ) ;
// 等待搜索完成
await new Promise ( resolve = > setTimeout ( resolve , 300 ) ) ;
// 步骤3:执行替换
console . log ( ` [CollaboraViewer] 步骤3:替换为 " ${ replaceText } " ` ) ;
unoReplaceCurrent ( iframeRef . current . contentWindow , searchText , replaceText ) ;
console . log ( '[CollaboraViewer] ✓ 静默替换完成' ) ;
// 显示成功提示(可选)
// toastService.success(`已替换: "${searchText}" → "${replaceText}"`);
} catch ( error ) {
console . error ( '[CollaboraViewer] 静默替换失败:' , error ) ;
}
} , 300 ) ;
return ( ) = > clearTimeout ( timer ) ;
}
} , [ searchText , replaceText , searchReplacePageNumber , isDocumentLoaded ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
// 加载中状态
if ( loading ) {
return (