/** * Collabora Online 文档查看器组件 * * 功能: * - 加载 Collabora Online iframe * - 管理文档加载状态 * - 提供 UNO 命令接口 * - 支持只读和编辑模式 * * @encoding UTF-8 */ import { useRef, forwardRef, useImperativeHandle, useState } from 'react'; import type { CollaboraViewerProps, CollaboraViewerHandle } from './types'; import { useCollaboraConfig, useDocumentReady, useCollaboraUnoCommands } from './hooks'; import { sendUnoCommand } from './Uno'; import { highlightText } from './lib/Highlightselecttext'; import { clearHighlights } from './lib/ClearHighlight'; import { unoScrollToTop, requestPageInfo, customGotoPage, type PageInfo, type GotoPageResponse } from './lib'; /** * Collabora 文档查看器组件 * @param props - 组件属性 * @param ref - 父组件传入的 ref,用于暴露命令接口 */ export const CollaboraViewer = forwardRef( function CollaboraViewer( { fileId, mode = 'view', userId = 'guest', userName = '', }, ref ) { const iframeRef = useRef(null); // 高亮测试面板状态 const [highlightTextInput, setHighlightTextInput] = useState(''); const [highlightPage, setHighlightPage] = useState(''); const [previousHighlightText, setPreviousHighlightText] = useState(null); const [highlightResult, setHighlightResult] = useState(null); // 清除高亮测试面板状态 const [clearHighlightResult, setClearHighlightResult] = useState(null); const [isClearing, setIsClearing] = useState(false); // 页数信息测试状态 const [pageInfoResult, setPageInfoResult] = useState(null); const [isLoadingPageInfo, setIsLoadingPageInfo] = useState(false); // 页面跳转测试状态 const [gotoPageInput, setGotoPageInput] = useState(''); const [gotoPageResult, setGotoPageResult] = useState(null); const [isJumping, setIsJumping] = useState(false); // 1. 加载 Collabora 配置 const { config, loading, error } = useCollaboraConfig(fileId, mode, userId, userName); // 2. 监听文档加载状态 const { isDocumentLoaded } = useDocumentReady(iframeRef); // 3. UNO 命令封装 const unoCommands = useCollaboraUnoCommands(iframeRef); // 4. 暴露接口给父组件 useImperativeHandle(ref, () => ({ unoCommands, isReady: isDocumentLoaded, mode, getIframeWindow: () => iframeRef.current?.contentWindow || null, }), [unoCommands, isDocumentLoaded, mode]); // 加载中状态 if (loading) { return (

加载文档配置中...

); } // 错误状态 if (error || !config) { return (

{error || '加载配置失败'}

请刷新页面重试或联系管理员

); } // 发送 UNO 命令的处理函数(测试用) // const sendUno = () => { // if (!iframeRef.current?.contentWindow) { // setUnoResult('iframe 不可用'); // return; // } // let args: Record = {}; // const raw = (unoArgs || '').trim(); // if (raw !== '') { // try { // args = JSON.parse(raw) as Record; // } catch (err) { // try { // // fallback: replace single quotes with double quotes and parse // args = JSON.parse(raw.replace(/'(.*?)'/g, '"$1"')) as Record; // } catch (err2) { // console.error('解析 UNO Args 失败:', err2); // setUnoResult('Args 解析失败,请使用有效 JSON'); // return; // } // } // } // try { // // 使用正确的 sendUnoCommand 方法 // sendUnoCommand(iframeRef.current.contentWindow, unoCmd, args); // setUnoResult(`已发送: ${unoCmd}`); // console.log('[UNO Debug] 发送命令:', unoCmd, args); // } catch (e) { // console.error('发送 UNO 失败:', e); // setUnoResult('发送失败,请查看控制台'); // } // }; // 高亮测试处理函数 const handleSwitchHighlight = async () => { if (!iframeRef.current?.contentWindow) { setHighlightResult('iframe 未就绪'); return; } if (!highlightTextInput || highlightTextInput.trim() === '') { setHighlightResult('请输入要高亮的文本'); return; } try { // 解析页码 (可选) const page = highlightPage && highlightPage.trim() !== '' ? parseInt(highlightPage.trim(), 10) : undefined; // 验证页码 if (page !== undefined && (isNaN(page) || page < 1)) { setHighlightResult('页码必须是大于0的整数'); return; } await highlightText( iframeRef.current.contentWindow, highlightTextInput.trim(), { page } ); // 更新上一次高亮的文本 setPreviousHighlightText(highlightTextInput.trim()); const pageInfo = page ? ` (第${page}页)` : ''; setHighlightResult(`✓ 已切换高亮: ${highlightTextInput.trim()}${pageInfo}`); } catch (e) { console.error('切换高亮失败:', e); setHighlightResult(`切换失败: ${e instanceof Error ? e.message : '未知错误'}`); } }; // 滚动到顶部处理函数 const handleScrollToTop = async () => { if (!iframeRef.current?.contentWindow) { console.error('iframe 未就绪'); return; } try { await unoScrollToTop(iframeRef.current.contentWindow); console.log('[CollaboraViewer] 已滚动到文档顶部'); } catch (e) { console.error('滚动到顶部失败:', e); } }; // 获取页数信息处理函数 const handleGetPageInfo = async () => { if (!iframeRef.current?.contentWindow) { setPageInfoResult('iframe 未就绪'); return; } setIsLoadingPageInfo(true); setPageInfoResult('正在获取页数信息...'); try { const info: PageInfo = await requestPageInfo(iframeRef.current.contentWindow); setPageInfoResult( `✓ 总页数: ${info.totalPages} | 当前页: ${info.currentPage + 1} (索引: ${info.currentPage})` ); console.log('[CollaboraViewer] 页数信息:', info); // 3秒后清除提示 setTimeout(() => { setPageInfoResult(null); }, 3000); } catch (e) { console.error('获取页数信息失败:', e); setPageInfoResult(`✗ 获取失败: ${e instanceof Error ? e.message : '未知错误'}`); // 5秒后清除错误提示 setTimeout(() => { setPageInfoResult(null); }, 5000); } finally { setIsLoadingPageInfo(false); } }; // 页面跳转处理函数 const handleGotoPage = async () => { if (!iframeRef.current?.contentWindow) { setGotoPageResult('iframe 未就绪'); return; } const pageNumber = parseInt(gotoPageInput.trim(), 10); if (isNaN(pageNumber) || pageNumber < 1) { setGotoPageResult('请输入有效的页码 (大于0的整数)'); return; } setIsJumping(true); setGotoPageResult(`正在跳转到第 ${pageNumber} 页...`); try { const response: GotoPageResponse = await customGotoPage( iframeRef.current.contentWindow, pageNumber ); setGotoPageResult( `✓ 已跳转到第 ${response.pageNumber} 页 (总页数: ${response.totalPages})` ); console.log('[CollaboraViewer] 页面跳转成功:', response); // 3秒后清除提示 setTimeout(() => { setGotoPageResult(null); }, 3000); } catch (e) { console.error('页面跳转失败:', e); setGotoPageResult(`✗ 跳转失败: ${e instanceof Error ? e.message : '未知错误'}`); // 5秒后清除错误提示 setTimeout(() => { setGotoPageResult(null); }, 5000); } finally { setIsJumping(false); } }; // 清除高亮处理函数 const handleClearHighlights = async () => { if (!iframeRef.current?.contentWindow) { setClearHighlightResult('iframe 未就绪'); return; } setIsClearing(true); setClearHighlightResult('正在清除高亮...'); try { const result = await clearHighlights(iframeRef.current.contentWindow, { color: 16776960, // 黄色 timeout: 10000, }); if (result.success) { setClearHighlightResult(`✓ ${result.message}`); console.log('[CollaboraViewer] 清除高亮成功:', result); // 清空之前记录的高亮文本 setPreviousHighlightText(null); } else { setClearHighlightResult(`✗ 清除失败: ${result.message}`); console.error('[CollaboraViewer] 清除高亮失败:', result); } // 3秒后清除提示 setTimeout(() => { setClearHighlightResult(null); }, 3000); } catch (e) { console.error('清除高亮失败:', e); setClearHighlightResult(`✗ 清除失败: ${e instanceof Error ? e.message : '未知错误'}`); // 5秒后清除错误提示 setTimeout(() => { setClearHighlightResult(null); }, 5000); } finally { setIsClearing(false); } }; return (
{/* UNO 命令测试面板 */} {/*
setUnoCmd(e.target.value)} placeholder="UNO 命令" aria-label="UNO 命令" /> setUnoArgs(e.target.value)} placeholder="UNO Args (JSON)" aria-label="UNO Args (JSON)" /> {unoResult && {unoResult}}
*/} {/* 高亮测试面板 */}
setHighlightTextInput(e.target.value)} placeholder="输入要高亮的文本 (如: 合同编号)" aria-label="高亮文本" onKeyPress={(e) => { if (e.key === 'Enter') { handleSwitchHighlight(); } }} /> setHighlightPage(e.target.value)} placeholder="页码" aria-label="页码" type="number" min="1" onKeyPress={(e) => { if (e.key === 'Enter') { handleSwitchHighlight(); } }} /> {previousHighlightText && ( 上次: {previousHighlightText} )} {highlightResult && ( {highlightResult} )}
{/* 清除高亮测试面板 */}
{clearHighlightResult && ( {clearHighlightResult} )}
{/* 文档加载提示 */} {!isDocumentLoaded && (

正在加载文档...

{config.fileName}

)} {/* Collabora iframe - tabIndex is needed for keyboard navigation */} {/* eslint-disable jsx-a11y/no-noninteractive-tabindex */}