temp:备份代码
This commit is contained in:
@@ -10,22 +10,33 @@
|
|||||||
* @encoding UTF-8
|
* @encoding UTF-8
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useRef } from 'react';
|
import { useRef, forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||||
import type { CollaboraViewerProps } from './types';
|
import type { CollaboraViewerProps, CollaboraViewerHandle } from './types';
|
||||||
import { useCollaboraConfig, useDocumentReady, useCollaboraUnoCommands } from './hooks';
|
import { useCollaboraConfig, useDocumentReady, useCollaboraUnoCommands } from './hooks';
|
||||||
|
import { sendUnoCommand } from './Uno';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collabora 文档查看器组件
|
* Collabora 文档查看器组件
|
||||||
* @param props - 组件属性
|
* @param props - 组件属性
|
||||||
|
* @param ref - 父组件传入的 ref,用于暴露命令接口
|
||||||
*/
|
*/
|
||||||
export function CollaboraViewer({
|
export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewerProps>(
|
||||||
|
function CollaboraViewer(
|
||||||
|
{
|
||||||
fileId,
|
fileId,
|
||||||
mode = 'view',
|
mode = 'view',
|
||||||
userId = 'guest',
|
userId = 'guest',
|
||||||
userName = '访客',
|
userName = '访客',
|
||||||
}: CollaboraViewerProps) {
|
},
|
||||||
|
ref
|
||||||
|
) {
|
||||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
|
|
||||||
|
// 调试面板状态
|
||||||
|
const [unoCmd, setUnoCmd] = useState('.uno:GoToStartOfDoc');
|
||||||
|
const [unoArgs, setUnoArgs] = useState('{}');
|
||||||
|
const [unoResult, setUnoResult] = useState<string | null>(null);
|
||||||
|
|
||||||
// 1. 加载 Collabora 配置
|
// 1. 加载 Collabora 配置
|
||||||
const { config, loading, error } = useCollaboraConfig(fileId, mode, userId, userName);
|
const { config, loading, error } = useCollaboraConfig(fileId, mode, userId, userName);
|
||||||
|
|
||||||
@@ -35,6 +46,28 @@ export function CollaboraViewer({
|
|||||||
// 3. UNO 命令封装
|
// 3. UNO 命令封装
|
||||||
const unoCommands = useCollaboraUnoCommands(iframeRef);
|
const unoCommands = useCollaboraUnoCommands(iframeRef);
|
||||||
|
|
||||||
|
// 4. 暴露接口给父组件
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
unoCommands,
|
||||||
|
isReady: isDocumentLoaded,
|
||||||
|
mode,
|
||||||
|
}), [unoCommands, isDocumentLoaded, mode]);
|
||||||
|
|
||||||
|
// 5. 将 sendUnoCommand 挂载到 window 对象,供调试面板和控制台使用
|
||||||
|
useEffect(() => {
|
||||||
|
if (iframeRef.current?.contentWindow) {
|
||||||
|
(window as any).sendUno = (cmd: string, args: any = {}) => {
|
||||||
|
if (iframeRef.current?.contentWindow) {
|
||||||
|
sendUnoCommand(iframeRef.current.contentWindow, cmd, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
delete (window as any).sendUno;
|
||||||
|
};
|
||||||
|
}, [isDocumentLoaded]);
|
||||||
|
|
||||||
// 加载中状态
|
// 加载中状态
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -60,8 +93,75 @@ export function CollaboraViewer({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送 UNO 命令的处理函数
|
||||||
|
const sendUno = () => {
|
||||||
|
if (!iframeRef.current?.contentWindow) {
|
||||||
|
setUnoResult('iframe 不可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(window as any).sendUno) {
|
||||||
|
setUnoResult('window.sendUno 未初始化');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args: any = {};
|
||||||
|
const raw = (unoArgs || '').trim();
|
||||||
|
if (raw !== '') {
|
||||||
|
try {
|
||||||
|
args = JSON.parse(raw);
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
// fallback: replace single quotes with double quotes and parse
|
||||||
|
args = JSON.parse(raw.replace(/'(.*?)'/g, '"$1"'));
|
||||||
|
} catch (err2) {
|
||||||
|
console.error('解析 UNO Args 失败:', err2);
|
||||||
|
setUnoResult('Args 解析失败,请使用有效 JSON');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先让 iframe 获得焦点
|
||||||
|
iframeRef.current.focus();
|
||||||
|
console.log('[调试面板] 已聚焦 iframe');
|
||||||
|
|
||||||
|
(window as any).sendUno?.(unoCmd, args);
|
||||||
|
setUnoResult(`已发送: ${unoCmd}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('发送 UNO 失败:', e);
|
||||||
|
setUnoResult('发送失败,请查看控制台');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="collabora-viewer relative w-full h-full min-h-[600px]">
|
<div className="collabora-viewer relative w-full h-full min-h-[600px]">
|
||||||
|
{/* UNO 命令测试面板 */}
|
||||||
|
<div className="absolute top-2 left-2 z-50 bg-white bg-opacity-90 px-2 py-1 rounded shadow flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
className="px-2 py-1 border rounded text-sm w-48"
|
||||||
|
value={unoCmd}
|
||||||
|
onChange={(e) => setUnoCmd(e.target.value)}
|
||||||
|
placeholder="UNO 命令"
|
||||||
|
aria-label="UNO 命令"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="px-2 py-1 border rounded text-sm w-64"
|
||||||
|
value={unoArgs}
|
||||||
|
onChange={(e) => setUnoArgs(e.target.value)}
|
||||||
|
placeholder="UNO Args (JSON)"
|
||||||
|
aria-label="UNO Args (JSON)"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="px-3 py-1 bg-indigo-600 text-white rounded hover:bg-indigo-700 text-sm"
|
||||||
|
onClick={sendUno}
|
||||||
|
>
|
||||||
|
发送 UNO
|
||||||
|
</button>
|
||||||
|
{unoResult && <span className="text-xs text-gray-500 ml-2">{unoResult}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 文档加载提示 */}
|
{/* 文档加载提示 */}
|
||||||
{!isDocumentLoaded && (
|
{!isDocumentLoaded && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-white bg-opacity-90 z-10">
|
<div className="absolute inset-0 flex items-center justify-center bg-white bg-opacity-90 z-10">
|
||||||
@@ -85,10 +185,12 @@ export function CollaboraViewer({
|
|||||||
allow="clipboard-read; clipboard-write"
|
allow="clipboard-read; clipboard-write"
|
||||||
title={`Collabora Online - ${config.fileName}`}
|
title={`Collabora Online - ${config.fileName}`}
|
||||||
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals"
|
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals"
|
||||||
|
tabIndex={0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
// 导出 UNO 命令 hook 供父组件使用(如果需要)
|
// 导出类型和 hook
|
||||||
|
export type { CollaboraViewerHandle };
|
||||||
export { useCollaboraUnoCommands };
|
export { useCollaboraUnoCommands };
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export function sendUnoCommand(
|
|||||||
): void {
|
): void {
|
||||||
const message = {
|
const message = {
|
||||||
MessageId: 'Send_UNO_Command',
|
MessageId: 'Send_UNO_Command',
|
||||||
SendTime: Date.now(),
|
|
||||||
Values: {
|
Values: {
|
||||||
Command: command,
|
Command: command,
|
||||||
Args: args,
|
Args: args,
|
||||||
@@ -130,10 +129,25 @@ export function unoEscape(iframeWindow: Window): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 滚动到文档开头
|
* 滚动到文档开头 (带焦点请求)
|
||||||
* @param iframeWindow - iframe 的 contentWindow
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
*/
|
*/
|
||||||
export function unoScrollToTop(iframeWindow: Window): void {
|
export async function unoScrollToTop(iframeWindow: Window): Promise<void> {
|
||||||
|
// 1. 先请求 iframe 获取焦点
|
||||||
|
const focusMessage = {
|
||||||
|
MessageId: 'custompostMessage',
|
||||||
|
Values: {
|
||||||
|
Command: 'REQUEST_FOCUS',
|
||||||
|
Args: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
console.log('[custompostMessage] 请求焦点 (滚动到顶部)');
|
||||||
|
iframeWindow.postMessage(JSON.stringify(focusMessage), '*');
|
||||||
|
|
||||||
|
// 2. 等待焦点激活
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// 3. 发送滚动命令
|
||||||
sendUnoCommand(iframeWindow, '.uno:GoToStartOfDoc', {});
|
sendUnoCommand(iframeWindow, '.uno:GoToStartOfDoc', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,3 +180,64 @@ export function unoGetState(iframeWindow: Window): void {
|
|||||||
console.log('[UNO] 发送 Get_State (.uno:ModifiedStatus) - 等待命令队列执行完成');
|
console.log('[UNO] 发送 Get_State (.uno:ModifiedStatus) - 等待命令队列执行完成');
|
||||||
iframeWindow.postMessage(JSON.stringify(message), '*');
|
iframeWindow.postMessage(JSON.stringify(message), '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放大文档(固定步长)
|
||||||
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
|
*/
|
||||||
|
export function unoZoomPlus(iframeWindow: Window): void {
|
||||||
|
sendUnoCommand(iframeWindow, '.uno:ZoomPlus', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩小文档(固定步长)
|
||||||
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
|
*/
|
||||||
|
export function unoZoomMinus(iframeWindow: Window): void {
|
||||||
|
sendUnoCommand(iframeWindow, '.uno:ZoomMinus', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置文档缩放比例
|
||||||
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
|
* @param percentage - 缩放百分比(例如:100 表示 100%)
|
||||||
|
*/
|
||||||
|
export function unoSetZoom(iframeWindow: Window, percentage: number): void {
|
||||||
|
sendUnoCommand(iframeWindow, '.uno:Zoom', {
|
||||||
|
Zoom: {
|
||||||
|
type: 'short',
|
||||||
|
value: percentage,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到指定页面
|
||||||
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
|
* @param pageNumber - 页码(从1开始)
|
||||||
|
*/
|
||||||
|
export function unoGotoPage(iframeWindow: Window, pageNumber: number): void {
|
||||||
|
sendUnoCommand(iframeWindow, '.uno:GotoPage', {
|
||||||
|
Page: {
|
||||||
|
type: 'long',
|
||||||
|
value: pageNumber,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到第一页
|
||||||
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
|
*/
|
||||||
|
export function unoFirstPage(iframeWindow: Window): void {
|
||||||
|
sendUnoCommand(iframeWindow, '.uno:FirstPage', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到最后一页
|
||||||
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
|
*/
|
||||||
|
export function unoLastPage(iframeWindow: Window): void {
|
||||||
|
sendUnoCommand(iframeWindow, '.uno:LastPage', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ import {
|
|||||||
unoEscape,
|
unoEscape,
|
||||||
unoScrollToTop,
|
unoScrollToTop,
|
||||||
unoSave,
|
unoSave,
|
||||||
|
unoZoomPlus,
|
||||||
|
unoZoomMinus,
|
||||||
|
unoSetZoom,
|
||||||
|
unoGotoPage,
|
||||||
|
unoFirstPage,
|
||||||
|
unoLastPage,
|
||||||
} from './Uno';
|
} from './Uno';
|
||||||
import { COLLABORA_URL } from '~/config/api-config';
|
import { COLLABORA_URL } from '~/config/api-config';
|
||||||
|
|
||||||
@@ -131,9 +137,8 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
console.warn('[UNO] iframe 不可用');
|
console.warn('[UNO] iframe 不可用');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[UNO] 搜索文本: "${text}"`);
|
console.log(`[UNO] 搜索文本: "${text}"`);
|
||||||
unoSearchText(iframeRef.current.contentWindow, text);
|
await unoSearchText(iframeRef.current.contentWindow, text);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
},
|
},
|
||||||
[iframeRef]
|
[iframeRef]
|
||||||
@@ -175,9 +180,8 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
console.warn('[UNO] iframe 不可用');
|
console.warn('[UNO] iframe 不可用');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[UNO] 替换文本: "${searchText}" -> "${replaceText}"`);
|
console.log(`[UNO] 替换文本: "${searchText}" -> "${replaceText}"`);
|
||||||
unoReplaceText(iframeRef.current.contentWindow, searchText, replaceText);
|
await unoReplaceText(iframeRef.current.contentWindow, searchText, replaceText);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
},
|
},
|
||||||
[iframeRef]
|
[iframeRef]
|
||||||
@@ -192,9 +196,8 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
console.warn('[UNO] iframe 不可用');
|
console.warn('[UNO] iframe 不可用');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[UNO] 高亮文本: "${text}"`);
|
console.log(`[UNO] 高亮文本: "${text}"`);
|
||||||
unoHighlightText(iframeRef.current.contentWindow, text, color);
|
await unoHighlightText(iframeRef.current.contentWindow, text, color);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
},
|
},
|
||||||
[iframeRef]
|
[iframeRef]
|
||||||
@@ -209,9 +212,8 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
console.warn('[UNO] iframe 不可用');
|
console.warn('[UNO] iframe 不可用');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[UNO] 移除高亮: "${text}"`);
|
console.log(`[UNO] 移除高亮: "${text}"`);
|
||||||
unoRemoveHighlight(iframeRef.current.contentWindow, text);
|
await unoRemoveHighlight(iframeRef.current.contentWindow, text);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
},
|
},
|
||||||
[iframeRef]
|
[iframeRef]
|
||||||
@@ -227,7 +229,7 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('[UNO] 取消选中');
|
console.log('[UNO] 取消选中');
|
||||||
unoEscape(iframeRef.current.contentWindow);
|
await unoEscape(iframeRef.current.contentWindow);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
}, [iframeRef]);
|
}, [iframeRef]);
|
||||||
|
|
||||||
@@ -241,7 +243,7 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('[UNO] 滚动到顶部');
|
console.log('[UNO] 滚动到顶部');
|
||||||
unoScrollToTop(iframeRef.current.contentWindow);
|
await unoScrollToTop(iframeRef.current.contentWindow);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
}, [iframeRef]);
|
}, [iframeRef]);
|
||||||
|
|
||||||
@@ -255,10 +257,102 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('[UNO] 保存文档');
|
console.log('[UNO] 保存文档');
|
||||||
unoSave(iframeRef.current.contentWindow);
|
await unoSave(iframeRef.current.contentWindow);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
}, [iframeRef]);
|
}, [iframeRef]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放大文档
|
||||||
|
*/
|
||||||
|
const zoomIn = useCallback(async () => {
|
||||||
|
if (!iframeRef.current?.contentWindow) {
|
||||||
|
console.warn('[UNO] iframe 不可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[UNO] 放大文档');
|
||||||
|
await unoZoomPlus(iframeRef.current.contentWindow);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}, [iframeRef]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩小文档
|
||||||
|
*/
|
||||||
|
const zoomOut = useCallback(async () => {
|
||||||
|
if (!iframeRef.current?.contentWindow) {
|
||||||
|
console.warn('[UNO] iframe 不可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[UNO] 缩小文档');
|
||||||
|
await unoZoomMinus(iframeRef.current.contentWindow);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}, [iframeRef]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置缩放比例
|
||||||
|
* @param percentage - 缩放百分比 (例如:100 表示 100%)
|
||||||
|
*/
|
||||||
|
const setZoom = useCallback(
|
||||||
|
async (percentage: number) => {
|
||||||
|
if (!iframeRef.current?.contentWindow) {
|
||||||
|
console.warn('[UNO] iframe 不可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[UNO] 设置缩放比例: ${percentage}%`);
|
||||||
|
await unoSetZoom(iframeRef.current.contentWindow, percentage);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
},
|
||||||
|
[iframeRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到指定页面
|
||||||
|
* @param pageNumber - 页码(从1开始)
|
||||||
|
*/
|
||||||
|
const gotoPage = useCallback(
|
||||||
|
async (pageNumber: number) => {
|
||||||
|
if (!iframeRef.current?.contentWindow) {
|
||||||
|
console.warn('[UNO] iframe 不可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[UNO] 跳转到第 ${pageNumber} 页`);
|
||||||
|
await unoGotoPage(iframeRef.current.contentWindow, pageNumber);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
},
|
||||||
|
[iframeRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到第一页
|
||||||
|
*/
|
||||||
|
const gotoFirstPage = useCallback(async () => {
|
||||||
|
if (!iframeRef.current?.contentWindow) {
|
||||||
|
console.warn('[UNO] iframe 不可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[UNO] 跳转到第一页');
|
||||||
|
await unoFirstPage(iframeRef.current.contentWindow);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}, [iframeRef]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到最后一页
|
||||||
|
*/
|
||||||
|
const gotoLastPage = useCallback(async () => {
|
||||||
|
if (!iframeRef.current?.contentWindow) {
|
||||||
|
console.warn('[UNO] iframe 不可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[UNO] 跳转到最后一页');
|
||||||
|
await unoLastPage(iframeRef.current.contentWindow);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}, [iframeRef]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
searchText,
|
searchText,
|
||||||
@@ -269,6 +363,12 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
escapeSelection,
|
escapeSelection,
|
||||||
scrollToTop,
|
scrollToTop,
|
||||||
saveDocument,
|
saveDocument,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
setZoom,
|
||||||
|
gotoPage,
|
||||||
|
gotoFirstPage,
|
||||||
|
gotoLastPage,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
searchText,
|
searchText,
|
||||||
@@ -279,6 +379,12 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
escapeSelection,
|
escapeSelection,
|
||||||
scrollToTop,
|
scrollToTop,
|
||||||
saveDocument,
|
saveDocument,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
setZoom,
|
||||||
|
gotoPage,
|
||||||
|
gotoFirstPage,
|
||||||
|
gotoLastPage,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,3 +35,30 @@ export interface CollaboraViewerProps {
|
|||||||
/** 用户名称 */
|
/** 用户名称 */
|
||||||
userName?: string;
|
userName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CollaboraViewer 暴露给父组件的方法接口
|
||||||
|
*/
|
||||||
|
export interface CollaboraViewerHandle {
|
||||||
|
/** UNO 命令方法集合 */
|
||||||
|
unoCommands: {
|
||||||
|
searchText: (text: string) => Promise<void>;
|
||||||
|
locateText: (text: string) => Promise<void>;
|
||||||
|
replaceText: (searchText: string, replaceText: string) => Promise<void>;
|
||||||
|
highlightText: (text: string, color?: number) => Promise<void>;
|
||||||
|
removeHighlight: (text: string) => Promise<void>;
|
||||||
|
escapeSelection: () => Promise<void>;
|
||||||
|
scrollToTop: () => Promise<void>;
|
||||||
|
saveDocument: () => Promise<void>;
|
||||||
|
zoomIn: () => Promise<void>;
|
||||||
|
zoomOut: () => Promise<void>;
|
||||||
|
setZoom: (percentage: number) => Promise<void>;
|
||||||
|
gotoPage: (pageNumber: number) => Promise<void>;
|
||||||
|
gotoFirstPage: () => Promise<void>;
|
||||||
|
gotoLastPage: () => Promise<void>;
|
||||||
|
};
|
||||||
|
/** 文档是否已加载完成 */
|
||||||
|
isReady: boolean;
|
||||||
|
/** 当前模式 */
|
||||||
|
mode: 'view' | 'edit';
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { useState, useEffect, useRef, ChangeEvent } from 'react';
|
import { useState, useEffect, useRef, ChangeEvent } from 'react';
|
||||||
import { Document, Page, pdfjs } from 'react-pdf';
|
import { Document, Page, pdfjs } from 'react-pdf';
|
||||||
import { DOCUMENT_URL } from '~/api/axios-client';
|
import { DOCUMENT_URL } from '~/api/axios-client';
|
||||||
import { CollaboraViewer } from '~/components/collabora/CollaboraViewer';
|
import { CollaboraViewer, type CollaboraViewerHandle } from '~/components/collabora/CollaboraViewer';
|
||||||
|
|
||||||
// 设置worker路径为public目录下的worker文件
|
// 设置worker路径为public目录下的worker文件
|
||||||
// 使用已经下载的兼容版本 (pdfjs-dist v2.12.313)
|
// 使用已经下载的兼容版本 (pdfjs-dist v2.12.313)
|
||||||
@@ -85,10 +85,17 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
const [zoomLevel, setZoomLevel] = useState(100);
|
const [zoomLevel, setZoomLevel] = useState(100);
|
||||||
// const [highlightsVisible, setHighlightsVisible] = useState(true);
|
// const [highlightsVisible, setHighlightsVisible] = useState(true);
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
const collaboraViewerRef = useRef<CollaboraViewerHandle>(null);
|
||||||
const [numPages, setNumPages] = useState<number | null>(null);
|
const [numPages, setNumPages] = useState<number | null>(null);
|
||||||
const [loadError, setLoadError] = useState<string | null>(null);
|
const [loadError, setLoadError] = useState<string | null>(null);
|
||||||
const [pageInputValue, setPageInputValue] = useState<string>('');
|
const [pageInputValue, setPageInputValue] = useState<string>('');
|
||||||
|
|
||||||
|
// 获取文件类型
|
||||||
|
const real_path = fileContent.path || fileContent.template_contract_path || '';
|
||||||
|
const fileExtension = real_path.split('.').pop()?.toLowerCase();
|
||||||
|
const isDocx = fileExtension === 'docx';
|
||||||
|
const isPdf = fileExtension === 'pdf';
|
||||||
|
|
||||||
// 拖拽状态管理
|
// 拖拽状态管理
|
||||||
const [dragMode, setDragMode] = useState(false); // 是否处于拖拽模式
|
const [dragMode, setDragMode] = useState(false); // 是否处于拖拽模式
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
@@ -97,16 +104,36 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
|
|
||||||
// 放大文档
|
// 放大文档
|
||||||
const handleZoomIn = () => {
|
const handleZoomIn = () => {
|
||||||
|
if (isDocx) {
|
||||||
|
// DOCX 文件:调用 Collabora UNO 命令
|
||||||
|
if (!collaboraViewerRef.current?.isReady) {
|
||||||
|
toastService.warning('文档尚未加载完成,请稍候...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
collaboraViewerRef.current?.unoCommands.zoomIn();
|
||||||
|
} else if (isPdf) {
|
||||||
|
// PDF 文件:修改 zoomLevel 状态
|
||||||
if (zoomLevel < 200) {
|
if (zoomLevel < 200) {
|
||||||
setZoomLevel(prevZoom => prevZoom + 10);
|
setZoomLevel(prevZoom => prevZoom + 10);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 缩小文档
|
// 缩小文档
|
||||||
const handleZoomOut = () => {
|
const handleZoomOut = () => {
|
||||||
|
if (isDocx) {
|
||||||
|
// DOCX 文件:调用 Collabora UNO 命令
|
||||||
|
if (!collaboraViewerRef.current?.isReady) {
|
||||||
|
toastService.warning('文档尚未加载完成,请稍候...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
collaboraViewerRef.current?.unoCommands.zoomOut();
|
||||||
|
} else if (isPdf) {
|
||||||
|
// PDF 文件:修改 zoomLevel 状态
|
||||||
if (zoomLevel > 50) {
|
if (zoomLevel > 50) {
|
||||||
setZoomLevel(prevZoom => prevZoom - 10);
|
setZoomLevel(prevZoom => prevZoom - 10);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换拖拽模式
|
// 切换拖拽模式
|
||||||
@@ -243,11 +270,26 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
|
|
||||||
// 处理页码跳转
|
// 处理页码跳转
|
||||||
const handlePageJump = () => {
|
const handlePageJump = () => {
|
||||||
if (!pageInputValue || !numPages) return;
|
if (!pageInputValue) return;
|
||||||
|
|
||||||
const targetPageNum = parseInt(pageInputValue, 10);
|
const targetPageNum = parseInt(pageInputValue, 10);
|
||||||
|
|
||||||
// 验证页码是否在有效范围内
|
if (isDocx) {
|
||||||
|
// DOCX 文件:调用 Collabora UNO 命令
|
||||||
|
if (!collaboraViewerRef.current?.isReady) {
|
||||||
|
toastService.warning('文档尚未加载完成,请稍候...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetPageNum > 0) {
|
||||||
|
collaboraViewerRef.current?.unoCommands.gotoPage(targetPageNum);
|
||||||
|
} else {
|
||||||
|
toastService.warning('请输入有效页码');
|
||||||
|
setPageInputValue('');
|
||||||
|
}
|
||||||
|
} else if (isPdf) {
|
||||||
|
// PDF 文件:验证页码并滚动到目标页面
|
||||||
|
if (!numPages) return;
|
||||||
|
|
||||||
if (targetPageNum > 0 && targetPageNum <= numPages) {
|
if (targetPageNum > 0 && targetPageNum <= numPages) {
|
||||||
// 找到目标页面元素并滚动到该位置
|
// 找到目标页面元素并滚动到该位置
|
||||||
const pageElement = document.getElementById(`page-${targetPageNum}`);
|
const pageElement = document.getElementById(`page-${targetPageNum}`);
|
||||||
@@ -259,6 +301,7 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
toastService.warning(`请输入有效页码 (1-${numPages})`);
|
toastService.warning(`请输入有效页码 (1-${numPages})`);
|
||||||
setPageInputValue('');
|
setPageInputValue('');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理回车键跳转
|
// 处理回车键跳转
|
||||||
@@ -285,9 +328,19 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
|
|
||||||
// 滚动到顶部
|
// 滚动到顶部
|
||||||
const handleScrollToTop = () => {
|
const handleScrollToTop = () => {
|
||||||
|
if (isDocx) {
|
||||||
|
// DOCX 文件:调用 Collabora UNO 命令
|
||||||
|
if (!collaboraViewerRef.current?.isReady) {
|
||||||
|
toastService.warning('文档尚未加载完成,请稍候...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
collaboraViewerRef.current?.unoCommands.scrollToTop();
|
||||||
|
} else {
|
||||||
|
// PDF 文件:滚动容器到顶部
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
contentRef.current.scrollTo({ top: 0, behavior: 'smooth' });
|
contentRef.current.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -385,8 +438,6 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
|
|
||||||
// 渲染文档内容
|
// 渲染文档内容
|
||||||
const renderDocumentContent = () => {
|
const renderDocumentContent = () => {
|
||||||
const real_path = fileContent.path || fileContent.template_contract_path || '';
|
|
||||||
|
|
||||||
// 如果路径无效,显示错误信息
|
// 如果路径无效,显示错误信息
|
||||||
if (!real_path) {
|
if (!real_path) {
|
||||||
if(!fileContent.template_contract_path){
|
if(!fileContent.template_contract_path){
|
||||||
@@ -404,8 +455,6 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
}
|
}
|
||||||
|
|
||||||
// console.log('real_path',real_path);
|
// console.log('real_path',real_path);
|
||||||
// 获取文件扩展名
|
|
||||||
const fileExtension = real_path.split('.').pop()?.toLowerCase();
|
|
||||||
|
|
||||||
// PDF内容渲染
|
// PDF内容渲染
|
||||||
const renderPdfContent = () => (
|
const renderPdfContent = () => (
|
||||||
@@ -470,8 +519,9 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
// DOCX文件使用Collabora Online预览
|
// DOCX文件使用Collabora Online预览
|
||||||
return (
|
return (
|
||||||
<CollaboraViewer
|
<CollaboraViewer
|
||||||
|
ref={collaboraViewerRef}
|
||||||
fileId={real_path}
|
fileId={real_path}
|
||||||
mode="view"
|
mode="edit"
|
||||||
userId={userInfo?.sub || 'guest'}
|
userId={userInfo?.sub || 'guest'}
|
||||||
userName={userInfo?.nick_name || '访客'}
|
userName={userInfo?.nick_name || '访客'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user