@embedpdf-editor/vue3-chapter-viewer 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,299 +1,296 @@
1
1
  # @embedpdf-editor/vue3-chapter-viewer
2
2
 
3
- Vue 3 章节 PDF 阅读器:多章 PDF 拼成一条连续滚动流,支持划词划线、附注、段落书签、工具栏与标注 JSON 导入导出。
3
+ Vue 3 版章节 PDF 阅读器。它把多份章节 PDF 组织成一本连续滚动的书,并内置划线/高亮、附注、段落书签、章节目录和标注导入导出能力。
4
4
 
5
- 底层引擎:`@embedpdf-editor/editor-engine`(Preact 渲染岛 + `@embedpdf/core` 插件体系)。
5
+ 底层使用 `@embedpdf/core` 插件体系和 PDFium 引擎;Vue 3 包只负责提供 SFC 组件、composition hooks 和类型导出。
6
6
 
7
7
  ## 安装
8
8
 
9
9
  ```bash
10
- pnpm add @embedpdf-editor/vue3-chapter-viewer @embedpdf/engines
10
+ pnpm add @embedpdf-editor/vue3-chapter-viewer
11
11
  ```
12
12
 
13
- ## Vite 与 WASM
13
+ ## Vite
14
+
15
+ 包内导出的是一段 Vite 配置,不是 Vite plugin。它用于补齐 `scheduler` alias 和 PDFium 引擎的预构建配置;`@embedpdf/engines` 已经是本包依赖,业务项目不需要单独安装。
14
16
 
15
17
  ```ts
16
18
  // vite.config.ts
17
- import { defineConfig } from 'vite';
19
+ import { defineConfig, mergeConfig } from 'vite';
18
20
  import vue from '@vitejs/plugin-vue';
19
- import { chapterViewerVitePlugin } from '@embedpdf-editor/vue3-chapter-viewer/vite';
20
-
21
- export default defineConfig({
22
- plugins: [vue(), chapterViewerVitePlugin()],
23
- });
21
+ import { chapterViewerViteResolve } from '@embedpdf-editor/vue3-chapter-viewer/vite';
22
+
23
+ export default mergeConfig(
24
+ defineConfig({
25
+ plugins: [vue()],
26
+ }),
27
+ chapterViewerViteResolve(),
28
+ );
24
29
  ```
25
30
 
26
- `pdfium.wasm` 放到 `public/`(或由 `chapterViewerVitePlugin` 自动复制)。`usePdfiumEngine({ wasmUrl: '/pdfium.wasm' })` 的地址须与静态资源路径一致。
31
+ 如果不用 `mergeConfig`,需要手动合并 `resolve.alias` `optimizeDeps.include`,不要直接覆盖现有配置。
27
32
 
28
- ## 快速上手
33
+ ## 快速开始
34
+
35
+ 推荐只传 `options`。`editorOptions` 和 `features` 仍兼容,但已作为低层/旧写法保留。
29
36
 
30
37
  ```vue
31
38
  <script setup lang="ts">
32
- import { ref } from 'vue';
33
39
  import {
34
40
  ChapterPdfViewer,
35
41
  usePdfiumEngine,
36
- createChapterViewerEditorOptions,
37
- DEFAULT_CHAPTER_VIEWER_FEATURES,
38
- CallbackPasswordProvider,
42
+ type ChapterViewerOptions,
39
43
  } from '@embedpdf-editor/vue3-chapter-viewer';
40
- import type { ChapterManifest, NoteAnchor } from '@embedpdf-editor/vue3-chapter-viewer';
41
-
42
- const { engine, isLoading } = usePdfiumEngine({ wasmUrl: '/pdfium.wasm' });
43
-
44
- const manifest: ChapterManifest = {
45
- chapters: [
46
- {
47
- chapterId: 'ch-1',
48
- title: '第一章',
49
- globalPageRange: [1, 10],
50
- localPageRange: [0, 9],
51
- source: { url: '/pdfs/ch1.pdf' },
52
- encrypted: true, // 可选:业务标记该章为加密 PDF
53
- },
54
- ],
55
- };
56
44
 
57
- const viewerProps = createChapterViewerEditorOptions({
58
- manifest,
45
+ const { engine, isLoading, error } = usePdfiumEngine();
46
+
47
+ const options: ChapterViewerOptions = {
48
+ manifest: {
49
+ chapters: [
50
+ {
51
+ chapterId: 'ch-1',
52
+ title: '第一章',
53
+ globalPageRange: [1, 12],
54
+ localPageRange: [0, 11],
55
+ source: { url: '/pdfs/chapter-1.pdf' },
56
+ },
57
+ ],
58
+ },
59
59
  notes: {
60
- loadNotes: async () => [] as NoteAnchor[],
61
- onCreateNote: async (draft) => ({ noteId: crypto.randomUUID() }),
60
+ loadNotes: async () => [],
61
+ onCreateNote: async () => ({ noteId: crypto.randomUUID() }),
62
+ onUpdateNote: async () => {},
63
+ onDeleteNote: async () => {},
62
64
  },
63
65
  bookmarks: {
64
66
  load: async () => [],
65
67
  persist: async () => {},
68
+ onRequestRemove: async () => true,
66
69
  },
67
- features: DEFAULT_CHAPTER_VIEWER_FEATURES,
68
- });
69
-
70
- // 加密章:在 createPdfChapterEditor / 低层 options 中传入 passwordProvider(见下文)
70
+ features: {
71
+ markup: true,
72
+ bookmarks: true,
73
+ notes: true,
74
+ selectionToolbar: true,
75
+ zoom: { pageWidth: 720 },
76
+ },
77
+ };
71
78
  </script>
72
79
 
73
80
  <template>
74
- <ChapterPdfViewer v-if="engine && !isLoading" :engine="engine" :viewer-props="viewerProps" />
81
+ <div class="reader">
82
+ <div v-if="error">PDF engine failed: {{ error.message }}</div>
83
+ <div v-else-if="isLoading || !engine">Loading PDF engine...</div>
84
+ <ChapterPdfViewer
85
+ v-else
86
+ :engine="engine"
87
+ :options="options"
88
+ class-name="reader-fill"
89
+ viewport-class-name="reader-viewport"
90
+ />
91
+ </div>
75
92
  </template>
93
+
94
+ <style scoped>
95
+ .reader {
96
+ height: 100vh;
97
+ min-height: 0;
98
+ }
99
+
100
+ .reader-fill {
101
+ height: 100%;
102
+ min-height: 0;
103
+ }
104
+ </style>
76
105
  ```
77
106
 
78
- 完整交互见 monorepo 示例:`examples/chapter-viewer-demo-vue3`。
107
+ ## `ChapterViewerOptions`
79
108
 
80
- ## Manifest(章节清单)
109
+ `options` 是三端渲染器的统一配置入口。
81
110
 
82
111
  | 字段 | 说明 |
83
- |------|------|
84
- | `chapterId` | 业务唯一 ID,同时作为 `documentManager` 的 `documentId` |
85
- | `title` | 展示标题 |
86
- | `globalPageRange` | 该章在「整本」中的全局页闭区间;相邻章可重叠 |
87
- | `localPageRange` | 该章 PDF 0-based 页闭区间;页数须与 global 区间一致 |
88
- | `source` | `{ url }` / `{ buffer }` / `{ load: () => Promise<...> }`;可省略并在 `chapterPdfLoader` 统一加载 |
89
- | `encrypted` | **可选提示位**:标记该章可能需密码;真正触发密码流程的是 PDFium 打开时报 `Password` 错误 |
90
- | `ownedGlobalPages` | 仅 `overlapStrategy: 'explicit'` 时使用 |
112
+ | --- | --- |
113
+ | `manifest` | 章节清单,描述每章 PDF 和全局页码关系 |
114
+ | `notes` | 附注加载、创建、更新、删除回调 |
115
+ | `bookmarks` | 段落书签加载、持久化、删除确认回调 |
116
+ | `chapterPdfLoader` | 可选:全局 PDF 预处理/拉取(见下文) |
117
+ | `overlapStrategy` | 可选:同一全局页多章节重叠时,用先/后出现的章节 PDF 渲染 |
118
+ | `features` | 可选:功能开关与样式配置,省略时使用默认能力 |
119
+
120
+ ### Manifest
91
121
 
92
- ## 加密 PDF(密码 / 解密)
122
+ ```ts
123
+ type ChapterManifest = {
124
+ chapters: Array<{
125
+ chapterId: string;
126
+ title: string;
127
+ globalPageRange: [number, number];
128
+ localPageRange: [number, number];
129
+ source?: { url: string } | { buffer: ArrayBuffer } | { load: () => Promise<{ url: string } | { buffer: ArrayBuffer }> };
130
+ encrypted?: boolean;
131
+ ownedGlobalPages?: number[];
132
+ }>;
133
+ totalGlobalPages?: number;
134
+ };
135
+ ```
93
136
 
94
- 章节 PDF 若带打开密码,引擎在 `documentManager` 打开失败且错误码为 **Password** 时,会向 `passwordProvider` 索取密码;用户放弃则该章进入 **`password-required`** 状态。
137
+ `globalPageRange` `localPageRange` 都是闭区间,且页数必须一致。`chapterId` 同时作为内部 `documentId`,需要在整本书内唯一。
95
138
 
96
- ### 配置方式
139
+ ### 嵌套目录树与重叠页
97
140
 
98
- `createChapterViewerEditorOptions` 默认使用「始终返回 `null`」的 `CallbackPasswordProvider`(即不弹窗、直接失败)。业务须在 **`createPdfChapterEditor`** 或展开 `viewerProps` 底层 options 时传入自定义 provider:
141
+ 侧栏目录支持**任意层级**(`ChapterTreePanel` + `ChapterTreeNode.children`)。引擎滚动用的 `manifest.chapters` 是**扁平列表**:父节点页范围可包含子节点,相邻/重叠全局页由 **owner 策略** 决定只渲染一个章节的 PDF,避免同页画两遍。
99
142
 
100
143
  ```ts
101
144
  import {
102
- createChapterViewerEditorOptions,
103
- CallbackPasswordProvider,
145
+ buildChapterViewerCatalog,
146
+ overlapStrategyForSamePageOwner,
147
+ type ChapterViewerOptions,
104
148
  } from '@embedpdf-editor/vue3-chapter-viewer';
105
- // StaticPasswordProvider / UiPromptPasswordProvider 从 @embedpdf-editor/chapter-core 引入
106
149
 
107
- const passwordProvider = new CallbackPasswordProvider(async (chapter, attempt) => {
108
- // attempt: 0 为首次,递增表示重试次数(可做「错三次锁定」)
109
- const pwd = await myAskPasswordUi(chapter.title, attempt);
110
- return pwd; // 返回 null 表示用户取消 → 章节 status: password-required
111
- });
150
+ // 从业务树生成 tree + manifest(深度优先 flatten,顺序影响重叠页归属)
151
+ const { tree, manifest } = buildChapterViewerCatalog([
152
+ { chapterId: 'part-1', title: '第一篇', startPage: 1, endPage: 50, source: { url: '/p1.pdf' } },
153
+ {
154
+ chapterId: 'sec-1',
155
+ title: '1.1 节',
156
+ startPage: 5,
157
+ endPage: 20,
158
+ source: { url: '/s1.pdf' },
159
+ children: [/* ... */],
160
+ },
161
+ ]);
112
162
 
113
- const viewerProps = createChapterViewerEditorOptions({ manifest, notes, bookmarks });
114
- // viewerProps 内部会 merge 到 createPdfChapterEditor;若需覆盖 passwordProvider,
115
- // 请使用 createPdfChapterEditor({ ...opts, passwordProvider }) + 自行组装 EmbedPDF。
163
+ const options: ChapterViewerOptions = {
164
+ manifest,
165
+ overlapStrategy: overlapStrategyForSamePageOwner('last'), // 重叠页用 manifest 中「后出现」的章节
166
+ // overlapStrategy: { kind: 'first-wins' }, // 默认:先出现的章节
167
+ notes: { /* ... */ },
168
+ bookmarks: { /* ... */ },
169
+ };
116
170
  ```
117
171
 
118
- ### 内置 Provider(`@embedpdf-editor/chapter-core`)
172
+ | `overlapStrategy` | 同一全局页多个章节条目时 |
173
+ | --- | --- |
174
+ | `{ kind: 'first-wins' }`(默认) | 使用 **manifest 中先出现** 的章节的 PDF |
175
+ | `{ kind: 'last-wins' }` | 使用 **后出现** 的章节(目录 flatten 后,重叠页常以「当前页最后一节」为准) |
176
+ | `{ kind: 'explicit' }` | 各章在 `ownedGlobalPages` 上声明归属页 |
177
+ | `{ kind: 'custom', resolve }` | 完全自定义 |
119
178
 
120
- | | 用途 |
121
- |----|------|
122
- | `CallbackPasswordProvider` | 异步回调 `(chapter, attempt) => string \| null`,最常用 |
123
- | `StaticPasswordProvider` | 构造时传入 `Record<chapterId, password>`,适合 SSO 一次解锁多章 |
124
- | `UiPromptPasswordProvider` | `onPrompt(chapter, attempt)` 通知 UI,UI 调 `submit(chapterId, password \| null)` 完成 Promise |
179
+ 简写:`overlapStrategyForSamePageOwner('first' | 'last')`。
125
180
 
126
- `getCachedPassword?(chapterId)` 可选:同步返回已缓存密码,避免重复弹窗。
181
+ **注意**:仅作分组、没有独立 PDF 的父节点不要放进 flatten 后的 manifest;只保留需要加载 PDF 的节点。Demo:`examples/chapter-viewer-demo-vue3/src/demo/load-catalog.ts`。
127
182
 
128
- ### 章节加载状态
183
+ ### PDF 加载与预处理
129
184
 
130
- `ChapterLoadStatus` 包含:`idle` | `loading` | `loaded` | `error` | **`password-required`**。密码提交成功后 `ChapterManagerPlugin` 会带密码重试打开。
185
+ 打开章节前,引擎按以下优先级解析 PDF(`ChapterManagerPlugin.resolvePdfPayload`):
131
186
 
132
- ## 标注导入 / 导出(JSON 格式)
187
+ 1. `chapter.source.url` / `source.buffer` — 直接使用
188
+ 2. `chapter.source.load()` — **按章**异步预处理,返回 `{ url }` 或 `{ buffer }`
189
+ 3. `chapterPdfLoader.loadPdf(chapter)` — **全局**统一逻辑
133
190
 
134
- 类型定义见 `editor-engine` → `chapter-annotations-io/types.ts`。当前归档版本常量:`CHAPTER_ANNOTATIONS_ARCHIVE_VERSION === 1`。
191
+ **按章 `source.load`(单章解密、鉴权 URL):**
135
192
 
136
- ### 全书归档 `ChapterAnnotationsArchive`
137
-
138
- ```jsonc
193
+ ```ts
139
194
  {
140
- // 固定为 1;未来格式升级时解析器可据此分支
141
- "version": 1,
142
- // ISO 8601 导出时间(UTC 字符串)
143
- "exportedAt": "2026-06-01T12:00:00.000Z",
144
- // key = chapterId;value 不含重复的 chapterId 字段
145
- "chapters": {
146
- "ch-1": {
147
- // ---------- 段落书签(plugin-paragraph-bookmark)----------
148
- "bookmarks": [
149
- {
150
- "id": "bm-uuid",
151
- "label": "用户可见标题",
152
- "metadata": { "任意业务字段": true },
153
- "anchor": {
154
- "chapterId": "ch-1",
155
- "localPageIndex": 0,
156
- "globalPageIndex": 1,
157
- "globalPageNumber": 1,
158
- "rectPdfCoord": { "origin": { "x": 0, "y": 0 }, "size": { "width": 100, "height": 20 } },
159
- "rectsPdfCoord": [],
160
- "markerAnchor": { "x": 100, "y": 20 }
161
- },
162
- "createdAt": 1717243200000
163
- }
164
- ],
165
- // ---------- 附注(plugin-note)----------
166
- "notes": [
167
- {
168
- "noteId": "note-uuid",
169
- "chapterId": "ch-1",
170
- "globalPageIndex": 1,
171
- "globalPageNumber": 1,
172
- "localPageIndex": 0,
173
- "rectsPdfCoord": [{ "origin": { "x": 0, "y": 0 }, "size": { "width": 200, "height": 16 } }],
174
- "endAnchor": { "x": 200, "y": 16 },
175
- "selectedText": "被选中的原文",
176
- "content": "用户笔记正文"
177
- }
178
- ],
179
- // ---------- 划线 / 高亮 / 图章等(@embedpdf/plugin-annotation)----------
180
- "markup": [
181
- {
182
- // 与 AnnotationTransferItem.annotation 同结构(类型、页码、几何、颜色等)
183
- "annotation": {
184
- "id": "ann-uuid",
185
- "type": "highlight",
186
- "pageIndex": 0,
187
- "rect": { "origin": { "x": 0, "y": 0 }, "size": { "width": 100, "height": 12 } }
188
- },
189
- // 图章等二进制附件:导出时 ArrayBuffer → base64
190
- "ctx": {
191
- "dataBase64": "iVBORw0KGgo...",
192
- "mimeType": "image/png"
193
- }
194
- }
195
- ]
196
- }
197
- }
195
+ chapterId: 'ch-1',
196
+ title: '第一章',
197
+ globalPageRange: [1, 10],
198
+ localPageRange: [0, 9],
199
+ source: {
200
+ load: async () => {
201
+ const bytes = await fetchDecryptedPdf('ch-1');
202
+ return { buffer: bytes };
203
+ // 或 return { url: URL.createObjectURL(blob) };
204
+ },
205
+ },
198
206
  }
199
207
  ```
200
208
 
201
- ### 单章快照 `ChapterAnnotationsSnapshot`
209
+ **全局 `chapterPdfLoader`(所有章走同一套 API):**
202
210
 
203
- 与归档中某一章对象相同,但**顶层多一个** `chapterId`:
204
-
205
- ```jsonc
206
- {
207
- "chapterId": "ch-1",
208
- "bookmarks": [],
209
- "notes": [],
210
- "markup": []
211
- }
212
- ```
211
+ ```ts
212
+ import type { ChapterViewerOptions, IChapterPdfLoader } from '@embedpdf-editor/vue3-chapter-viewer';
213
213
 
214
- `bookmarks` / `notes` / `markup` 均可省略;导出时可按 `ExportChapterAnnotationsOptions` 关闭某一类。
214
+ const loader: IChapterPdfLoader = {
215
+ async loadPdf(chapter) {
216
+ const res = await api.getChapterPdf(chapter.chapterId);
217
+ return { url: res.signedUrl };
218
+ // 或 return { buffer: await res.arrayBuffer() };
219
+ },
220
+ };
215
221
 
216
- ### 导入选项 `ImportChapterAnnotationsOptions`
222
+ const options: ChapterViewerOptions = {
223
+ manifest: { chapters: [/* 可省略各条 source */] },
224
+ chapterPdfLoader: loader,
225
+ notes: { /* ... */ },
226
+ bookmarks: { /* ... */ },
227
+ };
228
+ ```
217
229
 
218
- | 选项 | 默认 | 说明 |
219
- |------|------|------|
220
- | `mode` | `replace` | `replace`:先清空该章对应数据再写入;`merge`:与现有合并(书签按 anchor 去重) |
221
- | `ensureChapterLoaded` | `true` | 导入划线前确保该章 PDF 已打开 |
222
- | `bookmarks` / `notes` / `markup` | `true` | 控制是否处理对应字段 |
223
- | `persistNotes` | — | 导入完成后回调,参数为**全书**附注列表,便于写 localStorage / 后端 |
224
- | `persistBookmarks` | — | 同上,段落书签列表 |
230
+ ## Worker WASM
225
231
 
226
- ### API(均需 `PluginRegistry`,Vue 3 `useRegistry()`)
232
+ `usePdfiumEngine()` 默认使用 `@embedpdf/engines` 内置的 PDFium CDN wasm 地址,并默认启用 worker:
227
233
 
228
234
  ```ts
229
- import { useRegistry } from '@embedpdf-editor/vue3-chapter-viewer';
230
- import {
231
- exportChapterAnnotations,
232
- exportAllChapterAnnotations,
233
- importChapterAnnotations,
234
- importChapterAnnotationsArchive,
235
- chapterAnnotationsArchiveToJson,
236
- parseChapterAnnotationsArchiveJson,
237
- chapterAnnotationsSnapshotToJson,
238
- parseChapterAnnotationsSnapshotJson,
239
- downloadChapterAnnotationsArchive,
240
- downloadChapterAnnotationsSnapshot,
241
- } from '@embedpdf-editor/vue3-chapter-viewer';
242
-
243
- const { registry } = useRegistry();
235
+ usePdfiumEngine();
236
+ ```
244
237
 
245
- const archive = await exportAllChapterAnnotations(registry.value!);
246
- const json = chapterAnnotationsArchiveToJson(archive);
238
+ 如果业务要求内网部署、固定版本或自定义 CDN,再传入自托管地址:
247
239
 
248
- await importChapterAnnotationsArchive(registry.value!, parseChapterAnnotationsArchiveJson(json), {
249
- mode: 'replace',
250
- persistNotes: (all) => myStore.saveNotes(all),
251
- persistBookmarks: (all) => myStore.saveBookmarks(all),
252
- });
240
+ ```ts
241
+ usePdfiumEngine({ wasmUrl: '/assets/pdfium.wasm', worker: true });
253
242
  ```
254
243
 
255
- | 函数 | 说明 |
256
- |------|------|
257
- | `exportChapterAnnotations(registry, chapterId, options?)` | 导出单章快照 |
258
- | `exportAllChapterAnnotations(registry, options?)` | 导出全书归档 |
259
- | `importChapterAnnotations(registry, snapshot, options?)` | 导入单章 |
260
- | `importChapterAnnotationsArchive(registry, archive, options?)` | 导入全书 |
261
- | `chapterAnnotationsArchiveToJson` / `parseChapterAnnotationsArchiveJson` | 序列化 / 反序列化 |
262
- | `chapterAnnotationsSnapshotToJson` / `parseChapterAnnotationsSnapshotJson` | 单章版 |
263
- | `downloadChapterAnnotationsArchive` / `downloadChapterAnnotationsSnapshot` | 触发浏览器下载 `.json` |
244
+ 使用 `worker: true` 时,部署环境需要允许 worker 加载 wasm。若只在 worker 模式失败,可先用 `worker: false` 定位资源或响应头问题。
245
+
246
+ ## 进阶组合
264
247
 
265
- Demo 参考:`examples/chapter-viewer-demo-vue3/src/components/AnnotationsDemoBar.vue`。
248
+ 内置 `ChapterPdfViewer` 已完成插件注册和章节视口渲染。需要插入自定义 shell、目录、工具栏或直接访问 registry 时,可以使用低层组合:
266
249
 
267
- ## 功能开关 `features`
250
+ ```vue
251
+ <script setup lang="ts">
252
+ import {
253
+ EmbedPDF,
254
+ PdfChapterViewport,
255
+ createChapterViewerBundle,
256
+ } from '@embedpdf-editor/vue3-chapter-viewer';
268
257
 
269
- 通过 `createChapterViewerEditorOptions({ features })` `DEFAULT_CHAPTER_VIEWER_FEATURES` 控制划线样式、笔记、书签、选区卡片、缩放等。详见 `@embedpdf-editor/chapter-viewer` 的 `ChapterViewerFeaturesConfig`。
258
+ const { plugins, features } = createChapterViewerBundle(options);
259
+ </script>
270
260
 
271
- ## 笔记与书签
261
+ <template>
262
+ <EmbedPDF :engine="engine" :plugins="plugins">
263
+ <template #default="{ pluginsReady }">
264
+ <PdfChapterViewport v-if="pluginsReady" :features="features" />
265
+ </template>
266
+ </EmbedPDF>
267
+ </template>
268
+ ```
272
269
 
273
- - **附注**:`notes.loadNotes`、`onCreateNote` `onRequestCreateNote`(自定义弹窗)、`onUpdateNote`、`onDeleteNote` 等,类型 `NoteCallbacks`。
274
- - **书签**:`bookmarks.load`、`persist`、`onRequestRemove`,类型 `ParagraphBookmarkCallbacks`。
270
+ `createChapterViewerEditorOptions()` 只生成旧式 editor 配置,不含 `features`;新代码优先使用 `options` `createChapterViewerBundle(options)`。
275
271
 
276
- ## 主要导出
272
+ ## 标注导入导出
277
273
 
278
- | 导出 | 说明 |
279
- |------|------|
280
- | `ChapterPdfViewer` | 开箱即用阅读器壳 |
281
- | `PdfChapterViewport` / `ChapterTreePanel` | 自定义布局 |
282
- | `usePdfiumEngine` | 加载 PDFium WASM |
283
- | `useRegistry` / `EmbedPDF` / `useCapability` | 插件注册表与能力 |
284
- | `createChapterViewerEditorOptions` | 推荐配置入口 |
285
- | `CallbackPasswordProvider` | 密码回调(更多 Provider 见 `@embedpdf-editor/chapter-core`) |
286
- | 标注 IO 函数与类型 | 见上表 |
274
+ 包内直接导出章节标注 IO:
287
275
 
288
- ## 与其它包
276
+ ```ts
277
+ import {
278
+ exportAllChapterAnnotations,
279
+ importChapterAnnotationsArchive,
280
+ chapterAnnotationsArchiveToJson,
281
+ parseChapterAnnotationsArchiveJson,
282
+ useRegistry,
283
+ } from '@embedpdf-editor/vue3-chapter-viewer';
284
+ ```
289
285
 
290
- | | 场景 |
291
- |----|------|
292
- | `@embedpdf-editor/react-chapter-viewer` | React 18,API 与本包对称 |
293
- | `@embedpdf-editor/vue2-chapter-viewer` | Vue 2.6,内部为 `chapter-snippet` Web Component |
286
+ 导出格式包含 `bookmarks`、`notes`、`markup`,版本常量为 `CHAPTER_ANNOTATIONS_ARCHIVE_VERSION`。导入时可选择 `mode: 'replace' | 'merge'`,并可传 `persistNotes` / `persistBookmarks` 把导入结果写回业务存储。
294
287
 
295
- ## 故障排查
288
+ ## 常见问题
296
289
 
297
- - **一直 Loading**:确认 `manifest` 已 `setManifest`、章节 `source` / `chapterPdfLoader` 可访问、WASM 与 COOP/COEP 已配置。
298
- - **加密章无法打开**:检查 `passwordProvider` 是否返回非空密码;取消时为 `password-required`。
299
- - **导出 markup 为空**:该章须已 `loaded`,且 `ensureChapterLoaded` 为 true(默认)。
290
+ | 现象 | 处理 |
291
+ | --- | --- |
292
+ | 一直停在引擎加载 | 如果传了自定义 `wasmUrl`,检查地址是否 200、MIME/跨源头是否正确 |
293
+ | 只显示空白 | 确认外层容器有高度,且章节 `source.url` 可访问 |
294
+ | 页码或滚动错位 | 检查 `globalPageRange` 与 `localPageRange` 页数是否一致 |
295
+ | 划词笔记没有保存 | 实现 `notes.onCreateNote` 或自定义 `onRequestCreateNote` 流程 |
296
+ | 书签删除无效 | `bookmarks.onRequestRemove` 需要返回 `true` 才会删除 |