feat:完成通过自定义Collabora插件实现页面跳转
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
* @encoding UTF-8
|
* @encoding UTF-8
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useRef, forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
import { useRef, forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import type { CollaboraViewerProps, CollaboraViewerHandle } from './types';
|
import type { CollaboraViewerProps, CollaboraViewerHandle } from './types';
|
||||||
import { useCollaboraConfig, useDocumentReady, useCollaboraUnoCommands } from './hooks';
|
import { useCollaboraConfig, useDocumentReady, useCollaboraUnoCommands } from './hooks';
|
||||||
import { sendUnoCommand } from './Uno';
|
import { sendUnoCommand } from './Uno';
|
||||||
@@ -54,20 +54,6 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
|
|||||||
getIframeWindow: () => iframeRef.current?.contentWindow || null,
|
getIframeWindow: () => iframeRef.current?.contentWindow || null,
|
||||||
}), [unoCommands, 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) {
|
||||||
@@ -101,20 +87,15 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(window as any).sendUno) {
|
let args: Record<string, unknown> = {};
|
||||||
setUnoResult('window.sendUno 未初始化');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let args: any = {};
|
|
||||||
const raw = (unoArgs || '').trim();
|
const raw = (unoArgs || '').trim();
|
||||||
if (raw !== '') {
|
if (raw !== '') {
|
||||||
try {
|
try {
|
||||||
args = JSON.parse(raw);
|
args = JSON.parse(raw) as Record<string, unknown>;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
try {
|
||||||
// fallback: replace single quotes with double quotes and parse
|
// fallback: replace single quotes with double quotes and parse
|
||||||
args = JSON.parse(raw.replace(/'(.*?)'/g, '"$1"'));
|
args = JSON.parse(raw.replace(/'(.*?)'/g, '"$1"')) as Record<string, unknown>;
|
||||||
} catch (err2) {
|
} catch (err2) {
|
||||||
console.error('解析 UNO Args 失败:', err2);
|
console.error('解析 UNO Args 失败:', err2);
|
||||||
setUnoResult('Args 解析失败,请使用有效 JSON');
|
setUnoResult('Args 解析失败,请使用有效 JSON');
|
||||||
@@ -122,12 +103,22 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用正确的 sendUnoCommand 方法
|
||||||
|
sendUnoCommand(iframeRef.current.contentWindow, unoCmd, args);
|
||||||
|
setUnoResult(`已发送: ${unoCmd}`);
|
||||||
|
console.log('[UNO Debug] 发送命令:', unoCmd, args);
|
||||||
|
} 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 命令测试面板 */}
|
{/* 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">
|
<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
|
<input
|
||||||
className="px-2 py-1 border rounded text-sm w-48"
|
className="px-2 py-1 border rounded text-sm w-48"
|
||||||
value={unoCmd}
|
value={unoCmd}
|
||||||
@@ -149,7 +140,7 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
|
|||||||
发送 UNO
|
发送 UNO
|
||||||
</button>
|
</button>
|
||||||
{unoResult && <span className="text-xs text-gray-500 ml-2">{unoResult}</span>}
|
{unoResult && <span className="text-xs text-gray-500 ml-2">{unoResult}</span>}
|
||||||
</div> */}
|
</div>
|
||||||
|
|
||||||
{/* 文档加载提示 */}
|
{/* 文档加载提示 */}
|
||||||
{!isDocumentLoaded && (
|
{!isDocumentLoaded && (
|
||||||
@@ -162,7 +153,8 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Collabora iframe */}
|
{/* Collabora iframe - tabIndex is needed for keyboard navigation */}
|
||||||
|
{/* eslint-disable jsx-a11y/no-noninteractive-tabindex */}
|
||||||
<iframe
|
<iframe
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
src={config.iframeUrl}
|
src={config.iframeUrl}
|
||||||
@@ -176,6 +168,7 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
|
|||||||
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}
|
tabIndex={0}
|
||||||
/>
|
/>
|
||||||
|
{/* eslint-enable jsx-a11y/no-noninteractive-tabindex */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,10 +15,11 @@
|
|||||||
export function sendUnoCommand(
|
export function sendUnoCommand(
|
||||||
iframeWindow: Window,
|
iframeWindow: Window,
|
||||||
command: string,
|
command: string,
|
||||||
args: Record<string, any> = {}
|
args: Record<string, unknown> = {}
|
||||||
): 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,
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ import {
|
|||||||
unoZoomPlus,
|
unoZoomPlus,
|
||||||
unoZoomMinus,
|
unoZoomMinus,
|
||||||
unoSetZoom,
|
unoSetZoom,
|
||||||
unoGotoPage,
|
|
||||||
unoFirstPage,
|
|
||||||
unoLastPage,
|
|
||||||
} from './lib';
|
} from './lib';
|
||||||
import { COLLABORA_URL } from '~/config/api-config';
|
import { COLLABORA_URL } from '~/config/api-config';
|
||||||
|
|
||||||
@@ -67,7 +64,8 @@ export function useCollaboraConfig(
|
|||||||
// 检查错误
|
// 检查错误
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fetcher.data && 'error' in fetcher.data) {
|
if (fetcher.data && 'error' in fetcher.data) {
|
||||||
const errorMessage = (fetcher.data as any).error || '加载配置失败';
|
const errorData = fetcher.data as { error: string };
|
||||||
|
const errorMessage = errorData.error || '加载配置失败';
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
toastService.error(`加载文档配置失败: ${errorMessage}`);
|
toastService.error(`加载文档配置失败: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
@@ -307,51 +305,6 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
[iframeRef]
|
[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(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -366,9 +319,6 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
setZoom,
|
setZoom,
|
||||||
gotoPage,
|
|
||||||
gotoFirstPage,
|
|
||||||
gotoLastPage,
|
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
searchText,
|
searchText,
|
||||||
@@ -382,9 +332,6 @@ export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>)
|
|||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
setZoom,
|
setZoom,
|
||||||
gotoPage,
|
|
||||||
gotoFirstPage,
|
|
||||||
gotoLastPage,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ lib/
|
|||||||
├── highlight.ts # 高亮功能
|
├── highlight.ts # 高亮功能
|
||||||
├── navigation.ts # 导航/跳转功能
|
├── navigation.ts # 导航/跳转功能
|
||||||
├── zoom.ts # 缩放功能
|
├── zoom.ts # 缩放功能
|
||||||
└── document.ts # 文档操作
|
├── document.ts # 文档操作
|
||||||
|
├── pageInfo.ts # 页数信息获取
|
||||||
|
└── gotoPage.ts # 自定义页面跳转(不使用 UNO 命令)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 功能模块
|
## 功能模块
|
||||||
@@ -31,9 +33,6 @@ lib/
|
|||||||
|
|
||||||
### 4. navigation.ts - 导航/跳转功能
|
### 4. navigation.ts - 导航/跳转功能
|
||||||
- `unoScrollToTop(iframeWindow)` - 滚动到文档开头(带焦点请求)
|
- `unoScrollToTop(iframeWindow)` - 滚动到文档开头(带焦点请求)
|
||||||
- `unoGotoPage(iframeWindow, pageNumber)` - 跳转到指定页面
|
|
||||||
- `unoFirstPage(iframeWindow)` - 跳转到第一页
|
|
||||||
- `unoLastPage(iframeWindow)` - 跳转到最后一页
|
|
||||||
|
|
||||||
### 5. zoom.ts - 缩放功能
|
### 5. zoom.ts - 缩放功能
|
||||||
- `unoZoomPlus(iframeWindow)` - 放大文档
|
- `unoZoomPlus(iframeWindow)` - 放大文档
|
||||||
@@ -49,6 +48,10 @@ lib/
|
|||||||
- `getPageInfoFromCollabora()` - 从 Collabora 内部直接获取页数(仅 iframe 内部可用)
|
- `getPageInfoFromCollabora()` - 从 Collabora 内部直接获取页数(仅 iframe 内部可用)
|
||||||
- `PageInfo` 接口 - 页数信息类型定义
|
- `PageInfo` 接口 - 页数信息类型定义
|
||||||
|
|
||||||
|
### 8. gotoPage.ts - 自定义页面跳转(不使用 UNO 命令)
|
||||||
|
- `customGotoPage(iframeWindow, pageNumber)` - 跳转到指定页面(使用自定义 PostMessage 协议)
|
||||||
|
- `GotoPageResponse` 接口 - 页面跳转响应信息类型定义
|
||||||
|
|
||||||
## 使用方式
|
## 使用方式
|
||||||
|
|
||||||
### 方式 1: 从统一入口导入(推荐)
|
### 方式 1: 从统一入口导入(推荐)
|
||||||
@@ -60,6 +63,8 @@ import {
|
|||||||
unoHighlightText,
|
unoHighlightText,
|
||||||
unoScrollToTop,
|
unoScrollToTop,
|
||||||
unoSave,
|
unoSave,
|
||||||
|
customGotoPage,
|
||||||
|
requestPageInfo,
|
||||||
} from '~/components/collabora/lib';
|
} from '~/components/collabora/lib';
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -68,6 +73,8 @@ import {
|
|||||||
```typescript
|
```typescript
|
||||||
import { unoSearchText } from '~/components/collabora/lib/search';
|
import { unoSearchText } from '~/components/collabora/lib/search';
|
||||||
import { unoScrollToTop } from '~/components/collabora/lib/navigation';
|
import { unoScrollToTop } from '~/components/collabora/lib/navigation';
|
||||||
|
import { customGotoPage } from '~/components/collabora/lib/gotoPage';
|
||||||
|
import { requestPageInfo } from '~/components/collabora/lib/pageInfo';
|
||||||
```
|
```
|
||||||
|
|
||||||
## 核心工具函数
|
## 核心工具函数
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Collabora 自定义页面跳转模块
|
||||||
|
*
|
||||||
|
* 使用自定义 PostMessage 协议实现页面跳转,不依赖 UNO 命令
|
||||||
|
*
|
||||||
|
* @encoding UTF-8
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面跳转响应接口
|
||||||
|
*/
|
||||||
|
export interface GotoPageResponse {
|
||||||
|
pageNumber: number;
|
||||||
|
pageIndex: number;
|
||||||
|
totalPages: number;
|
||||||
|
currentOffset: number;
|
||||||
|
targetOffset: number;
|
||||||
|
scrollDelta: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collabora PostMessage 数据类型
|
||||||
|
*/
|
||||||
|
interface CollaboraMessageData {
|
||||||
|
MessageId?: string;
|
||||||
|
msgId?: string;
|
||||||
|
Values?: {
|
||||||
|
Command?: string;
|
||||||
|
Status?: string;
|
||||||
|
pageNumber?: number;
|
||||||
|
pageIndex?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
currentOffset?: number;
|
||||||
|
targetOffset?: number;
|
||||||
|
scrollDelta?: number;
|
||||||
|
timestamp?: number;
|
||||||
|
Error?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
args?: {
|
||||||
|
Command?: string;
|
||||||
|
Status?: string;
|
||||||
|
pageNumber?: number;
|
||||||
|
pageIndex?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
currentOffset?: number;
|
||||||
|
targetOffset?: number;
|
||||||
|
scrollDelta?: number;
|
||||||
|
timestamp?: number;
|
||||||
|
Error?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 PostMessage 数据
|
||||||
|
*/
|
||||||
|
function parseMessageData(data: unknown): CollaboraMessageData {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
return JSON.parse(data) as CollaboraMessageData;
|
||||||
|
}
|
||||||
|
return data as CollaboraMessageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到指定页面 (自定义实现,不使用 UNO 命令)
|
||||||
|
*
|
||||||
|
* 使用自定义 custompostMessage 插件实现页面跳转。
|
||||||
|
* 注意: Collabora 的页码是从 0 开始的,但此函数接受从 1 开始的页码。
|
||||||
|
*
|
||||||
|
* @param iframeWindow - iframe 的 contentWindow
|
||||||
|
* @param pageNumber - 页码 (从1开始)
|
||||||
|
* @returns Promise,解析为跳转响应信息
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const response = await customGotoPage(iframeWindow, 5);
|
||||||
|
* console.log(`成功跳转到第 ${response.pageNumber} 页`);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export async function customGotoPage(
|
||||||
|
iframeWindow: Window,
|
||||||
|
pageNumber: number
|
||||||
|
): Promise<GotoPageResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error('页面跳转超时'));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
const handleMessage = (event: MessageEvent) => {
|
||||||
|
try {
|
||||||
|
if (event.source !== iframeWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = parseMessageData(event.data);
|
||||||
|
|
||||||
|
// bundle.js 的 _postMessage 会将消息转换为:
|
||||||
|
// { MessageId: 'custompostMessage_Resp', Values: { Command: 'GOTO_PAGE', ... } }
|
||||||
|
if (
|
||||||
|
data.MessageId === 'custompostMessage_Resp' &&
|
||||||
|
data.Values?.Command === 'GOTO_PAGE'
|
||||||
|
) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
if (data.Values.Status === 'success') {
|
||||||
|
const response: GotoPageResponse = {
|
||||||
|
pageNumber: data.Values.pageNumber || pageNumber,
|
||||||
|
pageIndex: data.Values.pageIndex || pageNumber - 1,
|
||||||
|
totalPages: data.Values.totalPages || 0,
|
||||||
|
currentOffset: data.Values.currentOffset || 0,
|
||||||
|
targetOffset: data.Values.targetOffset || 0,
|
||||||
|
scrollDelta: data.Values.scrollDelta || 0,
|
||||||
|
timestamp: data.Values.timestamp || Date.now(),
|
||||||
|
};
|
||||||
|
resolve(response);
|
||||||
|
} else {
|
||||||
|
reject(new Error(data.Values.Error || '页面跳转失败'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
cleanup();
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
window.removeEventListener('message', handleMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
|
||||||
|
// 发送跳转请求消息
|
||||||
|
const message = {
|
||||||
|
MessageId: 'custompostMessage',
|
||||||
|
Values: {
|
||||||
|
Command: 'GOTO_PAGE',
|
||||||
|
Args: {
|
||||||
|
pageNumber: pageNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
iframeWindow.postMessage(JSON.stringify(message), '*');
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -16,9 +16,6 @@ export { unoHighlightText, unoRemoveHighlight, unoEscape } from './highlight';
|
|||||||
// 导航/跳转功能
|
// 导航/跳转功能
|
||||||
export {
|
export {
|
||||||
unoScrollToTop,
|
unoScrollToTop,
|
||||||
unoGotoPage,
|
|
||||||
unoFirstPage,
|
|
||||||
unoLastPage,
|
|
||||||
} from './navigation';
|
} from './navigation';
|
||||||
|
|
||||||
// 缩放功能
|
// 缩放功能
|
||||||
@@ -32,3 +29,9 @@ export {
|
|||||||
requestPageInfo,
|
requestPageInfo,
|
||||||
type PageInfo,
|
type PageInfo,
|
||||||
} from './pageInfo';
|
} from './pageInfo';
|
||||||
|
|
||||||
|
// 自定义页面跳转功能
|
||||||
|
export {
|
||||||
|
customGotoPage,
|
||||||
|
type GotoPageResponse,
|
||||||
|
} from './gotoPage';
|
||||||
|
|||||||
@@ -31,32 +31,3 @@ export async function unoScrollToTop(iframeWindow: Window): Promise<void> {
|
|||||||
sendUnoCommand(iframeWindow, '.uno:GoToStartOfDoc', {});
|
sendUnoCommand(iframeWindow, '.uno:GoToStartOfDoc', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 跳转到指定页面
|
|
||||||
* @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', {});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ interface CollaboraMessageData {
|
|||||||
currentPage?: number;
|
currentPage?: number;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
pages?: number;
|
pages?: number;
|
||||||
currentPage?: number;
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
};
|
};
|
||||||
// 原始插件返回的格式
|
// 原始插件返回的格式
|
||||||
@@ -42,27 +41,7 @@ interface CollaboraMessageData {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Collabora 全局对象类型
|
|
||||||
*/
|
|
||||||
interface CollaboraApp {
|
|
||||||
map?: {
|
|
||||||
_docLayer?: {
|
|
||||||
_pages?: number;
|
|
||||||
_currentPage?: number;
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 扩展 Window 类型以包含 Collabora app
|
|
||||||
*/
|
|
||||||
interface CollaboraWindow extends Window {
|
|
||||||
app?: CollaboraApp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 PostMessage 数据
|
* 解析 PostMessage 数据
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ export interface CollaboraViewerHandle {
|
|||||||
zoomIn: () => Promise<void>;
|
zoomIn: () => Promise<void>;
|
||||||
zoomOut: () => Promise<void>;
|
zoomOut: () => Promise<void>;
|
||||||
setZoom: (percentage: number) => Promise<void>;
|
setZoom: (percentage: number) => Promise<void>;
|
||||||
gotoPage: (pageNumber: number) => Promise<void>;
|
|
||||||
gotoFirstPage: () => Promise<void>;
|
|
||||||
gotoLastPage: () => Promise<void>;
|
|
||||||
};
|
};
|
||||||
/** 文档是否已加载完成 */
|
/** 文档是否已加载完成 */
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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, type CollaboraViewerHandle } from '~/components/collabora/CollaboraViewer';
|
import { CollaboraViewer, type CollaboraViewerHandle } from '~/components/collabora/CollaboraViewer';
|
||||||
import { requestPageInfo } from '~/components/collabora/lib/pageInfo';
|
import { requestPageInfo, customGotoPage } from '~/components/collabora/lib';
|
||||||
|
|
||||||
// 导入react-pdf的CSS样式(文本层和注释层必需)
|
// 导入react-pdf的CSS样式(文本层和注释层必需)
|
||||||
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
||||||
@@ -317,19 +317,28 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 处理页码跳转
|
// 处理页码跳转
|
||||||
const handlePageJump = () => {
|
const handlePageJump = async () => {
|
||||||
if (!pageInputValue) return;
|
if (!pageInputValue) return;
|
||||||
|
|
||||||
const targetPageNum = parseInt(pageInputValue, 10);
|
const targetPageNum = parseInt(pageInputValue, 10);
|
||||||
|
|
||||||
if (isDocx) {
|
if (isDocx) {
|
||||||
// DOCX 文件:调用 Collabora UNO 命令
|
// DOCX 文件:调用自定义页面跳转
|
||||||
if (!collaboraViewerRef.current?.isReady) {
|
const iframeWindow = collaboraViewerRef.current?.getIframeWindow?.();
|
||||||
|
if (!iframeWindow) {
|
||||||
toastService.warning('文档尚未加载完成,请稍候...');
|
toastService.warning('文档尚未加载完成,请稍候...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetPageNum > 0) {
|
if (targetPageNum > 0) {
|
||||||
collaboraViewerRef.current?.unoCommands.gotoPage(targetPageNum);
|
try {
|
||||||
|
await customGotoPage(iframeWindow, targetPageNum);
|
||||||
|
// 跳转成功,清空输入框
|
||||||
|
setPageInputValue('');
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : '未知错误';
|
||||||
|
toastService.error(`跳转失败: ${errorMessage}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
toastService.warning('请输入有效页码');
|
toastService.warning('请输入有效页码');
|
||||||
setPageInputValue('');
|
setPageInputValue('');
|
||||||
|
|||||||
Reference in New Issue
Block a user