npm package: @koi-br/ocr-web-sdk
Description: 一个支持多种Office文件格式预览的Vue3组件SDK，包括PDF、Word、Excel、图片、OFD、TIF等格式
Author: chunyu.ma
Latest version: 1.0.68

# @koi-br/ocr-web-sdk

一个功能强大的 Vue3 文件预览组件 SDK，支持多种 Office 文件格式和图片格式的在线预览，包括 PDF、Word、Excel、图片、OFD、TIF 等格式。

## ✨ 功能特性

### 📄 PDF 预览
- ✅ 完整的 PDF 文档渲染和浏览
- ✅ 目录导航和印章列表（支持侧边栏切换）
- ✅ 多种缩放模式（适应宽度、适应高度、实际大小、适应页面）
- ✅ 缩放控制（放大、缩小、重置）
- ✅ 全屏预览
- ✅ 文本选择和复制
- ✅ 页面跳转和定位
- ✅ 支持外部调用跳转方法
- ✅ 支持跳转事件监听
- ✅ PDF 分块数据支持（blocksData）

### 📊 Excel 预览
- ✅ XLSX 格式完整支持
- ✅ 缩放控制（0.5x - 2x）
- ✅ 下载功能（可选）

### 📝 Word 预览
- ✅ DOCX 格式完整支持
- ✅ 文档样式渲染
- ✅ 下载功能（可选）

### 🖼️ 图片预览
- ✅ 支持 JPG、PNG、GIF、BMP、WEBP、SVG、APNG、AVIF 等格式
- ✅ 缩放控制（可配置范围）
- ✅ 图片旋转（左右旋转）
- ✅ 拖拽平移
- ✅ 滚轮缩放
- ✅ 查看原图功能
- ✅ 下载功能（可选）

### 📑 OFD 预览
- ✅ OFD 格式文件预览
- ✅ 下载功能（可选）

### 🖨️ TIF 预览
- ✅ TIF/TIFF 多页图片支持
- ✅ 缩放控制
- ✅ 图片旋转
- ✅ 拖拽平移
- ✅ 查看原图功能
- ✅ 下载功能（可选）

### 🔄 同步滚动
- ✅ 多个预览组件同步滚动
- ✅ 支持比例同步和绝对同步两种模式
- ✅ 支持垂直、水平、双向同步
- ✅ 适用于不同大小、不同页数的图片

## 📦 安装

```bash
npm install @koi-br/ocr-web-sdk
# 或
yarn add @koi-br/ocr-web-sdk
# 或
pnpm add @koi-br/ocr-web-sdk
```

## 🔧 依赖要求

### 必需依赖
- **Vue 3.x** - Vue 框架（peer dependency）
- **@arco-design/web-vue** - Arco Design UI 组件库

### 内部依赖（已包含）
- `pdfjs-dist` - PDF 渲染引擎
- `@vue-office/excel` - Excel 预览组件
- `docx-preview` - Word 文档预览
- `bestofdview` - OFD 文件预览
- `lucide-vue-next` - 图标库

## 🚀 快速开始

> **✨ 样式自动加载**：SDK 会自动加载样式，无需手动导入样式文件！

### 方式一：使用 FilePreview 组件（推荐）

`FilePreview` 是一个统一的文件预览入口组件，会根据文件类型自动选择合适的预览组件。

```vue
<template>
  <div>
    <FilePreview 
      ref="previewRef" 
      :is-download="true"
      :pdf-props="{
        showTocSidebar: true,
        blocksData: blocksData
      }"
      :image-props="{
        minScale: 0.1,
        maxScale: 5,
        clickStep: 0.25
      }"
    />
    <button @click="previewFile">预览文件</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { FilePreview } from '@koi-br/ocr-web-sdk';

const previewRef = ref(null);
const blocksData = ref([]); // PDF 分块数据

// 预览文件 URL
const previewFile = () => {
  previewRef.value.preview('https://example.com/file.pdf', 'file.pdf');
};

// 预览 Blob 对象
const previewBlob = async () => {
  const response = await fetch('https://example.com/file.xlsx');
  const blob = await response.blob();
  previewRef.value.preview(blob, 'file.xlsx');
};

// 预览 File 对象（上传的文件）
const handleFileUpload = (event) => {
  const file = event.target.files[0];
  if (file) {
    previewRef.value.preview(file, file.name);
  }
};

// 带选项的预览
const previewWithOptions = () => {
  previewRef.value.preview('https://example.com/image.jpg', 'image.jpg', {
    onProgress: (progress) => {
      console.log('下载进度:', progress);
    },
    originalId: 'original-image-id'
  });
};

// 清除预览
const clearPreview = () => {
  previewRef.value.clearPreview();
};
</script>
```

### 方式二：使用单个预览组件

如果需要更精细的控制，可以直接使用对应的预览组件。

```vue
<template>
  <!-- PDF 预览 -->
  <PdfPreview
    ref="pdfRef"
    :pdf-url="pdfUrl"
    :blocks-data="blocksData"
    :show-toc-sidebar="true"
    @pdf-loaded="handlePdfLoaded"
    @page-jump="handlePageJump"
    @position-jump="handlePositionJump"
  />

  <!-- 图片预览 -->
  <ImagePreview
    :url="imageUrl"
    :min-scale="0.1"
    :max-scale="5"
    :original-id="originalId"
    :is-download="true"
    @original="handleOriginal"
    @download="handleDownload"
  />

  <!-- Excel 预览 -->
  <XlsxPreview
    :url="excelUrl"
    :is-download="true"
    @download="handleDownload"
  />

  <!-- Word 预览 -->
  <DocxPreview
    :url="wordUrl"
    :is-download="true"
    @download="handleDownload"
  />

  <!-- OFD 预览 -->
  <OfdPreview
    :url="ofdUrl"
    :is-download="true"
    @download="handleDownload"
  />

  <!-- TIF 预览 -->
  <TifPreview
    :url="tifUrl"
    :min-scale="0.1"
    :max-scale="5"
    :original-id="originalId"
    :is-download="true"
    @original="handleOriginal"
    @download="handleDownload"
  />
</template>

<script setup>
import {
  PdfPreview,
  ImagePreview,
  XlsxPreview,
  DocxPreview,
  OfdPreview,
  TifPreview
} from '@koi-br/ocr-web-sdk';

const pdfUrl = ref('https://example.com/file.pdf');
const imageUrl = ref('https://example.com/image.jpg');
// ... 其他 URL

const handlePdfLoaded = () => {
  console.log('PDF 加载完成');
};

const handlePageJump = (pageNum) => {
  console.log('跳转到第', pageNum, '页');
};

const handlePositionJump = (pageNum, bbox, type) => {
  const typeName = type === 'seal' ? '印章' : '文本块';
  console.log(`跳转到${typeName}:`, pageNum, bbox);
};

const handleOriginal = () => {
  console.log('查看原图');
};

const handleDownload = () => {
  console.log('下载文件');
};
</script>
```

## 📚 API 文档

### FilePreview 组件

统一的文件预览入口组件，自动根据文件类型选择合适的预览组件。

#### Props

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `isDownload` | `boolean` | `false` | 是否显示下载按钮（会传递给所有子组件） |
| `pdfProps` | `object` | `{}` | 传递给 PDF 预览组件的 props（见 [PdfPreview Props](#pdfpreview-组件)） |
| `imageProps` | `object` | `{}` | 传递给图片预览组件的 props（见 [ImagePreview Props](#imagepreview-组件)） |
| `tifProps` | `object` | `{}` | 传递给 TIF 预览组件的 props（见 [TifPreview Props](#tifpreview-组件)） |
| `docxProps` | `object` | `{}` | 传递给 Word 预览组件的 props（见 [DocxPreview Props](#docxpreview-组件)） |
| `ofdProps` | `object` | `{}` | 传递给 OFD 预览组件的 props（见 [OfdPreview Props](#ofdpreview-组件)） |
| `xlsxProps` | `object` | `{}` | 传递给 Excel 预览组件的 props（见 [XlsxPreview Props](#xlsxpreview-组件)） |

**注意：** `isDownload` 会单独传递给子组件，不会包含在对应的 `*Props` 对象中。如果需要为不同文件类型设置不同的下载按钮显示状态，可以在对应的 `*Props` 中设置 `isDownload`（优先级更高）。

#### Methods

##### `preview(file, fileName?, options?)`

预览文件方法。

**参数：**
- `file` (`string | Blob | File`) - 文件 URL、Blob 对象或 File 对象
- `fileName` (`string`, 可选) - 文件名，用于判断文件类型。如果不提供，会尝试从 URL 或 File 对象中获取
- `options` (`object`, 可选) - 配置选项
  - `onProgress` (`Function`) - 下载进度回调函数，参数为进度百分比 `(progress: number) => void`
  - `originalId` (`string`) - 原图 ID（用于图片预览）

**示例：**
```javascript
// 预览 URL
previewRef.value.preview('https://example.com/file.pdf', 'file.pdf');

// 预览 Blob
previewRef.value.preview(blob, 'file.xlsx');

// 预览 File 对象
previewRef.value.preview(file, file.name);

// 带选项
previewRef.value.preview(url, 'file.pdf', {
  onProgress: (progress) => {
    console.log('下载进度:', progress);
  },
  originalId: 'original-image-id'
});
```

##### `clearPreview()`

清除当前预览，释放资源。

**示例：**
```javascript
previewRef.value.clearPreview();
```

#### 向子组件传递 Props

`FilePreview` 支持通过 `*Props` 属性向对应的子组件传递配置。例如，向 PDF 预览组件传递 `blocksData` 和 `showTocSidebar`：

**示例：**
```vue
<template>
  <FilePreview 
    ref="previewRef"
    :is-download="true"
    :pdf-props="{
      showTocSidebar: true,
      blocksData: pdfBlocksData
    }"
    :image-props="{
      minScale: 0.1,
      maxScale: 5,
      clickStep: 0.25,
      wheelStep: 0.1
    }"
    :docx-props="{
      docName: '我的文档',
      minScale: 0.1,
      maxScale: 3
    }"
  />
</template>

<script setup>
import { ref } from 'vue';
import { FilePreview } from '@koi-br/ocr-web-sdk';

const previewRef = ref(null);
const pdfBlocksData = ref([
  {
    blockLabel: '标题',
    blockContent: '第一章',
    blockBbox: [100, 100, 200, 150],
    blockPage: 1
  }
]);

// 预览文件
previewRef.value.preview('https://example.com/file.pdf', 'file.pdf');
</script>
```

**各子组件支持的 Props：**

- **PDF (`pdfProps`)**: `showTocSidebar`, `blocksData`（详见 [PdfPreview Props](#pdfpreview-组件)）
- **图片 (`imageProps`)**: `minScale`, `maxScale`, `clickStep`, `wheelStep`, `originalId`（详见 [ImagePreview Props](#imagepreview-组件)）
- **TIF (`tifProps`)**: `minScale`, `maxScale`, `clickStep`, `wheelStep`, `originalId`（详见 [TifPreview Props](#tifpreview-组件)）
- **Word (`docxProps`)**: `docName`, `docData`, `minScale`, `maxScale`, `clickStep`, `wheelStep`（详见 [DocxPreview Props](#docxpreview-组件)）
- **OFD (`ofdProps`)**: 无额外 props（详见 [OfdPreview Props](#ofdpreview-组件)）
- **Excel (`xlsxProps`)**: 无额外 props（详见 [XlsxPreview Props](#xlsxpreview-组件)）

**注意：** `isDownload` 会单独传递给所有子组件。如果需要在 `*Props` 中覆盖 `isDownload`，子组件会优先使用 `*Props` 中的值。

---

### PdfPreview 组件

PDF 文档预览组件，功能最丰富。

#### Props

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `pdfUrl` | `string` | - | PDF 文件 URL（必需） |
| `blocksData` | `Array<BlockInfo>` | - | PDF 分块数据（从后端 layout-parsing 接口获取），用于目录和印章列表 |
| `showTocSidebar` | `boolean` | `true` | 是否显示目录侧边栏（包括工具栏中的切换按钮） |
| `isDownload` | `boolean` | `false` | 是否显示下载按钮 |

**BlockInfo 类型定义：**
```typescript
interface BlockInfo {
  blockLabel: string; // 块标签
  blockContent: string; // 块内容
  blockBbox: [number, number, number, number]; // 块的边界框 [x1, y1, x2, y2]
  blockPage: number; // 块所属的页码（从1开始）
}
```

#### Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| `pdf-loaded` | - | PDF 加载并渲染完成时触发 |
| `page-jump` | `pageNum: number` | 页面跳转时触发，传递目标页码 |
| `position-jump` | `pageNum: number, bbox: [number, number, number, number], type: "block" \| "seal"` | 位置跳转时触发，传递页码、边界框和类型（自动检测，block 文本块 或 seal 印章） |
| `download` | - | 点击下载按钮时触发 |

#### Methods（通过 ref 调用）

| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| `goToPage` | `pageNum: number` | - | 跳转到指定页码（从1开始） |
| `jumpToPosition` | `pageNum: number, bbox: [number, number, number, number], emitEvent?: boolean` | - | 跳转到指定位置并高亮（统一接口，**自动识别类型**，无需传入类型参数） |
| `getCurrentPage` | - | `number` | 获取当前页码 |
| `getTotalPages` | - | `number` | 获取总页数 |
| `reset` | - | - | 重置预览状态 |
| `cleanup` | - | - | 清理资源 |

**示例：**
```vue
<template>
  <PdfPreview
    ref="pdfRef"
    :pdf-url="pdfUrl"
    :blocks-data="blocksData"
    :show-toc-sidebar="true"
    @pdf-loaded="handlePdfLoaded"
    @page-jump="handlePageJump"
    @position-jump="handlePositionJump"
  />
  
  <div>
    <button @click="jumpToPage(5)">跳转到第5页</button>
    <button @click="jumpToBlock">跳转到文本块</button>
    <button @click="jumpToSeal">跳转到印章</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { PdfPreview } from '@koi-br/ocr-web-sdk';

const pdfRef = ref(null);

const handlePdfLoaded = () => {
  console.log('PDF 加载完成');
};

const handlePageJump = (pageNum) => {
  console.log('跳转到第', pageNum, '页');
};

// 统一的位置跳转事件处理（类型会自动检测）
const handlePositionJump = (pageNum, bbox, type) => {
  const typeName = type === 'seal' ? '印章' : '文本块';
  console.log(`跳转到${typeName}:`, pageNum, bbox);
};

// 跳转到指定页面
const jumpToPage = (pageNum) => {
  pdfRef.value?.goToPage(pageNum);
};

// 跳转到指定位置（自动识别是文本块还是印章）
const jumpToBlock = () => {
  // bbox 格式: [x1, y1, x2, y2] - PDF 坐标系的边界框
  // 无需指定类型，系统会根据坐标自动匹配
  const pageNum = 3;
  const bbox = [100, 200, 300, 400];
  pdfRef.value?.jumpToPosition(pageNum, bbox);
};

// 跳转到印章位置（同样无需指定类型）
const jumpToSeal = () => {
  const pageNum = 2;
  const bbox = [150, 250, 250, 350];
  // 系统会自动识别这是印章还是文本块
  pdfRef.value?.jumpToPosition(pageNum, bbox);
};

// 获取当前页码
const getCurrentPage = () => {
  const currentPage = pdfRef.value?.getCurrentPage();
  console.log('当前页码:', currentPage);
};

// 获取总页数
const getTotalPages = () => {
  const totalPages = pdfRef.value?.getTotalPages();
  console.log('总页数:', totalPages);
};
</script>
```

---

### ImagePreview 组件

图片预览组件，支持缩放、旋转、拖拽等功能。

#### Props

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `url` | `string` | - | 图片 URL（必需） |
| `minScale` | `number` | `0.1` | 最小缩放比例 |
| `maxScale` | `number` | `5` | 最大缩放比例 |
| `clickStep` | `number` | `0.25` | 点击缩放按钮的步长 |
| `wheelStep` | `number` | `0.1` | 滚轮缩放的步长 |
| `originalId` | `string` | `''` | 原图 ID，用于查看原图功能 |
| `isDownload` | `boolean` | `false` | 是否显示下载按钮 |

#### Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| `original` | - | 点击"查看原图"按钮时触发 |
| `download` | - | 点击"下载"按钮时触发 |

#### Methods（通过 ref 调用）

| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| `reset` | - | - | 重置图片状态（缩放、旋转、位置） |

**示例：**
```vue
<ImagePreview
  ref="imageRef"
  :url="imageUrl"
  :min-scale="0.1"
  :max-scale="5"
  :original-id="originalId"
  :is-download="true"
  @original="handleOriginal"
  @download="handleDownload"
/>
```

---

### XlsxPreview 组件

Excel 文件预览组件。

#### Props

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `url` | `string` | - | Excel 文件 URL（必需） |
| `isDownload` | `boolean` | `false` | 是否显示下载按钮 |

#### Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| `download` | - | 点击"下载"按钮时触发 |

#### Methods（通过 ref 调用）

| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| `cleanup` | - | - | 清理资源 |

**示例：**
```vue
<XlsxPreview
  ref="excelRef"
  :url="excelUrl"
  :is-download="true"
  @download="handleDownload"
/>
```

---

### DocxPreview 组件

Word 文档预览组件。

#### Props

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `url` | `string` | - | Word 文档 URL（必需） |
| `docName` | `string` | `'文档预览'` | 文档名称 |
| `docData` | `Blob \| ArrayBuffer` | `null` | 直接传入文档数据（优先级高于 url） |
| `minScale` | `number` | `0.1` | 最小缩放比例 |
| `maxScale` | `number` | `3` | 最大缩放比例 |
| `clickStep` | `number` | `0.25` | 点击缩放按钮的步长 |
| `wheelStep` | `number` | `0.1` | 滚轮缩放的步长 |
| `isDownload` | `boolean` | `false` | 是否显示下载按钮 |

#### Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| `download` | - | 点击"下载"按钮时触发 |

#### Methods（通过 ref 调用）

| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| `reset` | - | - | 重置文档状态 |
| `zoom` | `delta: number, isWheel?: boolean` | - | 缩放文档 |
| `rotate` | `delta: number` | - | 旋转文档 |

**示例：**
```vue
<DocxPreview
  ref="wordRef"
  :url="wordUrl"
  :is-download="true"
  @download="handleDownload"
/>
```

---

### OfdPreview 组件

OFD 文件预览组件。

#### Props

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `url` | `string` | - | OFD 文件 URL（必需） |
| `isDownload` | `boolean` | `false` | 是否显示下载按钮 |

#### Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| `download` | - | 点击"下载"按钮时触发 |

#### Methods（通过 ref 调用）

| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| `cleanup` | - | - | 清理资源 |

**示例：**
```vue
<OfdPreview
  ref="ofdRef"
  :url="ofdUrl"
  :is-download="true"
  @download="handleDownload"
/>
```

---

### TifPreview 组件

TIF/TIFF 图片预览组件，支持多页 TIF 文件。

**✨ 自动加载：** SDK 会自动通过 npm 依赖加载 TIFF 库，无需手动引入。

#### Props

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `url` | `string` | - | TIF 文件 URL（必需） |
| `minScale` | `number` | `0.1` | 最小缩放比例 |
| `maxScale` | `number` | `5` | 最大缩放比例 |
| `clickStep` | `number` | `0.25` | 点击缩放按钮的步长 |
| `wheelStep` | `number` | `0.1` | 滚轮缩放的步长 |
| `originalId` | `string` | `''` | 原图 ID，用于查看原图功能 |
| `isDownload` | `boolean` | `false` | 是否显示下载按钮 |

#### Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| `original` | - | 点击"查看原图"按钮时触发 |
| `download` | - | 点击"下载"按钮时触发 |
| `load` | - | TIF 文件加载完成时触发 |
| `error` | `error: Error` | TIF 文件加载失败时触发 |

#### Methods（通过 ref 调用）

| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| `cleanup` | - | - | 清理资源 |

**示例：**
```vue
<TifPreview
  ref="tifRef"
  :url="tifUrl"
  :min-scale="0.1"
  :max-scale="5"
  :original-id="originalId"
  :is-download="true"
  @original="handleOriginal"
  @download="handleDownload"
  @load="handleLoad"
  @error="handleError"
/>
```

## 📋 支持的文件格式

| 格式 | 扩展名 | 预览组件 |
|------|--------|----------|
| **PDF** | `.pdf` | `PdfPreview` |
| **Excel** | `.xlsx` | `XlsxPreview` |
| **Word** | `.docx` | `DocxPreview` |
| **图片** | `.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp`, `.webp`, `.svg`, `.apng`, `.avif` | `ImagePreview` |
| **OFD** | `.ofd` | `OfdPreview` |
| **TIF** | `.tif`, `.tiff` | `TifPreview` |

## 🛠️ 开发

### 本地开发

```bash
# 克隆项目
git clone <repository-url>

# 安装依赖
npm install

# 启动开发服务器
npm run dev

# 构建生产版本
npm run build

# 预览构建结果
npm run preview
```

### 项目结构

```
@koi-br/ocr-web-sdk/
├── Preview/              # 预览组件目录
│   ├── index.vue        # FilePreview 主组件
│   ├── PdfPreview.vue   # PDF 预览组件
│   ├── ImagePreview.vue # 图片预览组件
│   ├── xlsxPreview.vue  # Excel 预览组件
│   ├── docxPreview.vue  # Word 预览组件
│   ├── ofdPreview.vue   # OFD 预览组件
│   └── tifPreview.vue   # TIF 预览组件
├── src/                 # 源码目录
│   ├── index.ts         # 入口文件
│   └── styles/          # 样式文件
├── demo/                # 示例项目
└── package.json         # 项目配置
```

## 💡 使用技巧

### 1. 文件类型判断

`FilePreview` 组件会根据文件扩展名自动判断文件类型。如果文件名没有扩展名，可以手动指定：

```javascript
// 手动指定文件类型
previewRef.value.preview(blob, 'document.pdf');
```

### 2. 预览 Blob 对象

当从 API 获取文件数据时，可以转换为 Blob 对象进行预览：

```javascript
const response = await fetch('https://api.example.com/file');
const blob = await response.blob();
previewRef.value.preview(blob, 'file.pdf');
```

### 3. 处理文件上传

结合文件上传组件使用：

```vue
<template>
  <input type="file" @change="handleFileChange" />
  <FilePreview ref="previewRef" />
</template>

<script setup>
const handleFileChange = (event) => {
  const file = event.target.files[0];
  if (file) {
    previewRef.value.preview(file, file.name);
  }
};
</script>
```

### 4. PDF 分块数据使用

如果后端提供了 PDF 分块数据（blocksData），可以传递给 `PdfPreview` 组件以启用目录和印章列表功能：

```vue
<PdfPreview
  :pdf-url="pdfUrl"
  :blocks-data="blocksData"
/>
```

### 5. PDF 位置跳转

通过坐标自动匹配文本块或印章，无需指定类型：

```javascript
// 自动识别类型（推荐）
pdfRef.value?.jumpToPosition(pageNum, bbox);
```

### 6. 资源清理

组件会在卸载时自动清理资源，但也可以手动调用清理方法：

```javascript
// 切换文件时，先清除旧预览
previewRef.value.clearPreview();
// 然后加载新文件
previewRef.value.preview(newFileUrl, 'new-file.pdf');
```

### 7. 同步滚动功能

实现多个预览组件之间的同步滚动，支持不同大小、不同页数的图片：

```vue
<template>
  <div class="sync-preview-container">
    <button @click="toggleSync">
      {{ syncEnabled ? "禁用" : "启用" }}同步滚动
    </button>
    
    <div class="preview-wrapper">
      <FilePreview ref="preview1Ref" :image-props="imageProps1" @load="handleLoad1" />
      <FilePreview ref="preview2Ref" :image-props="imageProps2" @load="handleLoad2" />
    </div>
  </div>
</template>

<script setup>
import { ref, nextTick } from 'vue';
import FilePreview, { useSyncScroll } from '@koi-br/ocr-web-sdk';

const preview1Ref = ref(null);
const preview2Ref = ref(null);
const syncEnabled = ref(false);

// 初始化同步滚动管理器
const { enable, disable, toggle: toggleSync, register, unregister } = useSyncScroll({
  enabled: false,
  mode: 'ratio',        // 比例同步（推荐，适用于不同大小的图片）
  direction: 'vertical', // 垂直同步
  debounce: 16,         // 16ms 节流，约 60fps
});

const imageProps1 = {
  url: ['image1-page1.jpg', 'image1-page2.jpg'],
  autoFitWidth: true,
};

const imageProps2 = {
  url: ['image2-page1.jpg', 'image2-page2.jpg'],
  autoFitWidth: true,
};

// 注册容器到同步滚动管理器
const registerContainers = async () => {
  await nextTick();
  
  const preview1 = preview1Ref.value;
  const preview2 = preview2Ref.value;
  
  const container1 = preview1?.imagePreviewRef?.getContainer?.();
  const container2 = preview2?.imagePreviewRef?.getContainer?.();

  if (container1 && container2) {
    register('preview1', container1);
    register('preview2', container2);
  }
};

// 切换同步滚动
const toggleSync = () => {
  syncEnabled.value = !syncEnabled.value;
  if (syncEnabled.value) {
    enable();
    registerContainers();
  } else {
    disable();
    unregister('preview1');
    unregister('preview2');
  }
};

// 图片加载完成后注册
const handleLoad1 = () => {
  if (syncEnabled.value) {
    nextTick(() => registerContainers());
  }
};

const handleLoad2 = () => {
  if (syncEnabled.value) {
    nextTick(() => registerContainers());
  }
};
</script>
```

**同步滚动 API：**

```typescript
import { useSyncScroll, SyncScrollManager, type SyncScrollConfig } from '@koi-br/ocr-web-sdk';

// Vue Composable 方式（推荐）
const { enable, disable, register, unregister } = useSyncScroll({
  enabled: true,
  mode: 'ratio',      // 'ratio' | 'absolute'
  direction: 'vertical', // 'vertical' | 'horizontal' | 'both'
  debounce: 16,      // 防抖延迟（毫秒）
});

// 或直接使用 SyncScrollManager（非 Vue 环境）
const manager = new SyncScrollManager({
  enabled: true,
  mode: 'ratio',
  direction: 'vertical',
});
```

详细使用文档请参考：[同步滚动使用指南](./docs/sync-scroll-usage.md)

## ⚠️ 注意事项

1. **样式自动加载**：SDK 会自动加载样式，无需手动导入样式文件。样式已内联到 JS 文件中，导入组件后样式会自动生效。

2. **Vue 版本要求**：本项目需要 Vue 3.x，不支持 Vue 2.x

3. **UI 组件库**：需要安装 `@arco-design/web-vue` 作为依赖

4. **文件大小**：大文件预览可能会影响性能，建议对文件大小进行限制

5. **跨域问题**：预览跨域文件时，需要确保服务器配置了正确的 CORS 头

6. **PDF 渲染**：PDF 预览使用 pdfjs-dist，首次加载可能需要下载 worker 文件

7. **TIF 格式**：TIF 预览支持多页，但需要浏览器支持相应的解码器

## 📄 许可证

MIT

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！