@agilehub/vue-pdf-flipbook 1.3.4 → 2.0.0
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 +15 -640
- package/dist/VuePdfFlipbook.vue.d.ts +13 -1
- package/dist/annotationGeometry.d.ts +35 -0
- package/dist/annotationTypes.d.ts +163 -0
- package/dist/components/PdfAnnotationControls.vue.d.ts +26 -0
- package/dist/components/PdfAnnotationLayer.vue.d.ts +29 -0
- package/dist/components/PdfCanvasPage.vue.d.ts +14 -1
- package/dist/components/PdfThumbnails.vue.d.ts +2 -2
- package/dist/composables/types.d.ts +7 -0
- package/dist/composables/usePageFlip.d.ts +2 -0
- package/dist/composables/usePdfFlipbook.d.ts +6 -0
- package/dist/composables/usePdfPageCanvas.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/loadPdfRuntime.d.ts +2 -0
- package/dist/{pdfLegacyRuntime-CExhLOw9.js → pdfLegacyRuntime-DV1gnfno.js} +1 -1
- package/dist/pdfLegacyRuntime.d.ts +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/usePdfAnnotations.d.ts +28 -0
- package/dist/vue-pdf-flipbook.css +1 -1
- package/dist/vue-pdf-flipbook.js +1 -1
- package/docs/guide/annotation-click.md +109 -0
- package/docs/guide/annotations.md +273 -0
- package/docs/guide/api.md +163 -0
- package/docs/guide/demo-annotation-flow.md +355 -0
- package/docs/guide/development.md +49 -0
- package/docs/guide/faq.md +39 -0
- package/docs/guide/index.md +25 -0
- package/docs/guide/layout-and-zoom.md +156 -0
- package/docs/guide/note-content.md +96 -0
- package/docs/guide/pdf-loading.md +49 -0
- package/docs/guide/slots-and-thumbnails.md +214 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -2,19 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
基于 Vue 3、PDF.js 和 PageFlip 的 PDF 翻页组件。核心负责 PDF 加载、页面渲染、单页 / 双页布局和点击、拖拽翻页,内置阅读区两侧翻页按钮、首次加载提示和可选的缩略图列表。缩略图可放在书页底部,也可挂载到外部容器;工具栏、缩放、全屏和快捷键由外部实现。组件不依赖 UI 框架。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* [The Online Flipbook Maker](https://www.paperturn.com/)
|
|
7
|
-
* [turnjs](https://www.turnjs.cn/)
|
|
8
|
-
|
|
9
|
-
## 目录
|
|
10
|
-
|
|
11
|
-
- [安装](#安装)
|
|
12
|
-
- [在业务页面使用](#在业务页面使用)
|
|
13
|
-
- [外部容器:放大、缩小与全屏](#外部容器放大缩小与全屏)
|
|
14
|
-
- [Props](#props)、[事件](#事件)、[实例方法](#实例方法)
|
|
15
|
-
- [自定义插槽](#外部功能接入)
|
|
16
|
-
- [常见接入问题](#常见接入问题)
|
|
17
|
-
- [开发](#开发)
|
|
5
|
+
灵感来源:[Paperturn](https://www.paperturn.com/)、[turnjs](https://www.turnjs.cn/)。采用 [MIT License](LICENSE)。
|
|
18
6
|
|
|
19
7
|
## 安装
|
|
20
8
|
|
|
@@ -42,7 +30,7 @@ import '@agilehub/vue-pdf-flipbook/style.css'
|
|
|
42
30
|
|
|
43
31
|
## 在业务页面使用
|
|
44
32
|
|
|
45
|
-
下面可保存为业务项目中的 `PdfReader.vue`。它接收 PDF 地址,提供翻页、缩略图开关、错误提示与重试。PDF 地址必须支持 HTTP Range / 206;跨域时还需要 CORS,具体要求见[大文件按需预览](#大文件按需预览)。
|
|
33
|
+
下面可保存为业务项目中的 `PdfReader.vue`。它接收 PDF 地址,提供翻页、缩略图开关、错误提示与重试。PDF 地址必须支持 HTTP Range / 206;跨域时还需要 CORS,具体要求见[大文件按需预览](docs/guide/pdf-loading.md#大文件按需预览)。
|
|
46
34
|
|
|
47
35
|
```vue
|
|
48
36
|
<script setup lang="ts">
|
|
@@ -120,632 +108,19 @@ createApp(App).use(PdfFlipbook).mount('#app')
|
|
|
120
108
|
|
|
121
109
|
注册后可直接使用 `<VuePdfFlipbook />`。需要调用实例方法时,仍应导入 `PdfFlipbookExpose` 为组件 ref 标注类型。
|
|
122
110
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
组件根据父容器的实际尺寸适配书页。它没有 `zoom` 属性或 `zoomIn()` / `zoomOut()` 方法;业务页面通过调整包裹组件的内容层尺寸实现缩放。
|
|
126
|
-
|
|
127
|
-
容器分为三层:
|
|
128
|
-
|
|
129
|
-
| 层级 | 职责 | 关键样式 |
|
|
130
|
-
| --- | --- | --- |
|
|
131
|
-
| 页面容器 | 确定阅读区域总高度,容纳工具栏和阅读区 | 明确的 `height`、纵向 flex、`overflow: hidden` |
|
|
132
|
-
| 滚动视口 | 高度不随倍率增长,放大后允许滚动查看 | `flex: 1; min-width: 0; min-height: 0; overflow: auto` |
|
|
133
|
-
| 缩放内容层 | 宽和高同时乘以倍率,组件占满该层 | `flex: none`,动态 `width` 和 `height`,组件 `height="100%"` |
|
|
134
|
-
|
|
135
|
-
只修改宽度时,书页可能仍受原高度约束,看起来没有变大;使用 `transform: scale()` 也不会改变正常布局尺寸。下面采用宽高同步变化,让组件的 ResizeObserver 和翻页引擎按实际新尺寸重新适配。
|
|
136
|
-
|
|
137
|
-
### 完整示例:缩放、鼠标拖动平移、恢复与全屏
|
|
138
|
-
|
|
139
|
-
此示例可以作为另一个业务组件 `ZoomPdfReader.vue`,通过 `<ZoomPdfReader :url="pdfUrl" />` 使用。工具栏放在滚动视口之外,保持原尺寸。默认倍率下支持书页拖拽翻页;放大后鼠标拖动用于平移,翻页使用外部工具栏。
|
|
111
|
+
### 更多使用说明
|
|
140
112
|
|
|
141
|
-
|
|
142
|
-
<script setup lang="ts">
|
|
143
|
-
import { nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
|
144
|
-
import { VuePdfFlipbook } from '@agilehub/vue-pdf-flipbook'
|
|
145
|
-
import type { PdfFlipbookExpose, PdfFlipbookState } from '@agilehub/vue-pdf-flipbook'
|
|
146
|
-
import '@agilehub/vue-pdf-flipbook/style.css'
|
|
113
|
+
完整说明按功能拆分,统一入口见[功能文档目录](docs/guide/index.md)。
|
|
147
114
|
|
|
148
|
-
|
|
149
|
-
const reader = ref<PdfFlipbookExpose>()
|
|
150
|
-
const shell = ref<HTMLElement>()
|
|
151
|
-
const scroll = ref<HTMLElement>()
|
|
152
|
-
const state = ref<PdfFlipbookState>()
|
|
153
|
-
const zoom = ref(1)
|
|
154
|
-
const dragging = ref(false)
|
|
155
|
-
const errorMessage = ref('')
|
|
156
|
-
let pan: { id: number; x: number; y: number; left: number; top: number } | undefined
|
|
157
|
-
|
|
158
|
-
function changeZoom(value: number) {
|
|
159
|
-
zoom.value = Math.max(0.5, Math.min(2.5, Math.round(value * 100) / 100))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function stopPan() {
|
|
163
|
-
const id = pan?.id
|
|
164
|
-
pan = undefined
|
|
165
|
-
dragging.value = false
|
|
166
|
-
if (id !== undefined && scroll.value?.hasPointerCapture(id)) scroll.value.releasePointerCapture(id)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function startPan(event: PointerEvent) {
|
|
170
|
-
const element = scroll.value
|
|
171
|
-
if (!element || zoom.value <= 1 || event.pointerType !== 'mouse' || event.button !== 0 || !event.isPrimary) return
|
|
172
|
-
// 自定义控件可加 data-no-pan,避免被平移逻辑接管。
|
|
173
|
-
if ((event.target as Element).closest('button, input, a, [data-no-pan]')) return
|
|
174
|
-
event.preventDefault()
|
|
175
|
-
event.stopPropagation()
|
|
176
|
-
pan = { id: event.pointerId, x: event.clientX, y: event.clientY, left: element.scrollLeft, top: element.scrollTop }
|
|
177
|
-
dragging.value = true
|
|
178
|
-
element.setPointerCapture(event.pointerId)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function movePan(event: PointerEvent) {
|
|
182
|
-
if (!pan || !scroll.value || event.pointerId !== pan.id) return
|
|
183
|
-
if (!(event.buttons & 1)) {
|
|
184
|
-
stopPan()
|
|
185
|
-
return
|
|
186
|
-
}
|
|
187
|
-
event.preventDefault()
|
|
188
|
-
scroll.value.scrollLeft = pan.left - (event.clientX - pan.x)
|
|
189
|
-
scroll.value.scrollTop = pan.top - (event.clientY - pan.y)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function endPan(event: PointerEvent) {
|
|
193
|
-
if (event.pointerId === pan?.id) stopPan()
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function onError(error: unknown) {
|
|
197
|
-
errorMessage.value = error instanceof Error ? error.message : String(error)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function onStateChange(value: PdfFlipbookState) {
|
|
201
|
-
state.value = value
|
|
202
|
-
if (value.loading || value.pageLoading) stopPan()
|
|
203
|
-
if (value.loading) {
|
|
204
|
-
errorMessage.value = ''
|
|
205
|
-
zoom.value = 1
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function toggleFullscreen() {
|
|
210
|
-
try {
|
|
211
|
-
if (document.fullscreenElement === shell.value) await document.exitFullscreen()
|
|
212
|
-
else await shell.value?.requestFullscreen()
|
|
213
|
-
} catch (error) { onError(error) }
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
watch(zoom, async (value) => {
|
|
217
|
-
stopPan()
|
|
218
|
-
await nextTick()
|
|
219
|
-
if (value <= 1) scroll.value?.scrollTo(0, 0)
|
|
220
|
-
})
|
|
221
|
-
onBeforeUnmount(stopPan)
|
|
222
|
-
</script>
|
|
223
|
-
|
|
224
|
-
<template>
|
|
225
|
-
<section ref="shell" class="vpf-zoom-page">
|
|
226
|
-
<nav class="vpf-zoom-toolbar" aria-label="阅读工具栏">
|
|
227
|
-
<button :disabled="!state?.canPrevious || state?.pageLoading" @click="reader?.previous()">上一页</button>
|
|
228
|
-
<span>{{ state?.page ?? 1 }} / {{ state?.pages ?? 0 }}</span>
|
|
229
|
-
<button :disabled="!state?.canNext || state?.pageLoading" @click="reader?.next()">下一页</button>
|
|
230
|
-
<button :disabled="zoom <= 0.5" @click="changeZoom(zoom - 0.25)">缩小</button>
|
|
231
|
-
<button @click="changeZoom(1)">{{ Math.round(zoom * 100) }}% · 恢复</button>
|
|
232
|
-
<button :disabled="zoom >= 2.5" @click="changeZoom(zoom + 0.25)">放大</button>
|
|
233
|
-
<button @click="toggleFullscreen">切换全屏</button>
|
|
234
|
-
</nav>
|
|
235
|
-
<p v-if="errorMessage" role="alert">{{ errorMessage }} <button @click="reader?.reload()">重试</button></p>
|
|
236
|
-
<div
|
|
237
|
-
ref="scroll" class="vpf-zoom-scroll"
|
|
238
|
-
:class="{ 'is-zoomed': zoom > 1, 'is-dragging': dragging }"
|
|
239
|
-
@pointerdown.capture="startPan" @pointermove="movePan"
|
|
240
|
-
@pointerup="endPan" @pointercancel="endPan" @lostpointercapture="endPan"
|
|
241
|
-
>
|
|
242
|
-
<div class="vpf-zoom-content" :style="{ width: `${zoom * 100}%`, height: `${zoom * 100}%` }">
|
|
243
|
-
<VuePdfFlipbook
|
|
244
|
-
ref="reader" :url="props.url" :file-size="props.fileSize" height="100%"
|
|
245
|
-
@state-change="onStateChange" @error="onError"
|
|
246
|
-
/>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</section>
|
|
250
|
-
</template>
|
|
251
|
-
|
|
252
|
-
<style scoped>
|
|
253
|
-
.vpf-zoom-page { display: flex; flex-direction: column; width: 100%; height: 80dvh; min-width: 0; min-height: 0; overflow: hidden; background: #edf0eb; }
|
|
254
|
-
.vpf-zoom-toolbar { display: flex; flex: none; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px; }
|
|
255
|
-
.vpf-zoom-scroll { display: flex; flex: 1; min-width: 0; min-height: 0; overflow: hidden; }
|
|
256
|
-
.vpf-zoom-content { flex: none; margin: auto; }
|
|
257
|
-
.vpf-zoom-scroll.is-zoomed { overflow: auto; cursor: grab; user-select: none; }
|
|
258
|
-
.vpf-zoom-scroll.is-dragging { cursor: grabbing; }
|
|
259
|
-
/* 放大时把鼠标操作交给外层平移,避免同时触发书页折角或拖拽翻页。 */
|
|
260
|
-
.vpf-zoom-scroll.is-zoomed :deep(.vpf-book-stage),
|
|
261
|
-
.vpf-zoom-scroll.is-zoomed :deep(.vpf-book-stage *) { pointer-events: none; }
|
|
262
|
-
.vpf-zoom-page:fullscreen { width: 100%; height: 100%; }
|
|
263
|
-
</style>
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
上述 `pointer-events` 规则也会禁用书页上的内置按钮和底部缩略图,因此放大时使用外部工具栏,以及放在缩放内容层之外的自定义侧栏。若只需要滚轮/滚动条浏览、希望继续使用书页拖拽翻页,可去掉指针事件处理、`is-dragging` 和该 `pointer-events` 规则。
|
|
267
|
-
|
|
268
|
-
### 容器布局注意事项
|
|
269
|
-
|
|
270
|
-
- 示例采用 `80dvh`。在后台管理页面可改成 `calc(100dvh - 64px)`;只有父级高度已经确定时才使用 `height: 100%`。全页应用也可在全局设置 `html, body, #app { height: 100%; margin: 0; }`。
|
|
271
|
-
- flex 或 grid 布局中,阅读区及中间容器都需要 `min-width: 0; min-height: 0`,否则内容可能撑开父级,使整个页面产生滚动条。
|
|
272
|
-
- 缩放层需要 `flex: none`,否则 flex 自动收缩可能抵消放大。组件继续使用 `height="100%"`,不要同时写死为 `720px`。
|
|
273
|
-
- 固定侧栏、工具栏放在缩放层之外;横向覆盖式缩略图可放在外层定位容器中,使其保持业务指定的尺寸。
|
|
274
|
-
- 放大改变布局尺寸,正文 Canvas 仍遵守组件的分辨率上限;很高倍率下可能变模糊。事件返回的缩略图用于预览,不应作为大图阅读源。
|
|
275
|
-
- 全屏目标应包含阅读区、工具栏和 Teleport 的缩略图目标。Teleport 到全屏元素之外的内容不会跟随显示。
|
|
276
|
-
|
|
277
|
-
## Props
|
|
278
|
-
|
|
279
|
-
| 属性 | 类型 | 默认值 | 说明 |
|
|
280
|
-
| --- | --- | --- | --- |
|
|
281
|
-
| `url` | `string` | 必填 | PDF URL,变化时重新加载 |
|
|
282
|
-
| `fileSize` | `number` | 自动读取 `Content-Range` | PDF 原始字节数;跨域响应未暴露 `Content-Range` 时必须由文件元数据接口提供,变化时重新加载 |
|
|
283
|
-
| `initialPage` | `number` | `1` | 每次加载文档的初始页码,从 1 开始 |
|
|
284
|
-
| `initialMode` | `'single'` / `'double'` | 根据 PDF 首页比例选择 | 横向页面默认单栏,纵向页面默认双栏;显式传入时优先使用指定模式,后续可用 `setMode()` 切换 |
|
|
285
|
-
| `flipAnimation` | `{ single?: boolean; double?: boolean }` | 两种模式均开启 | 按实际单栏、双栏布局分别控制翻页动画;关闭时翻页直接定位,属性变化无需重新加载 PDF |
|
|
286
|
-
| `height` | `string` / `number` | `100%` | 默认填满父容器高度;数字单位为 px |
|
|
287
|
-
| `background` | `string` | `'transparent'` | 背景色 |
|
|
288
|
-
| `workerSrc` | `string` | 内置 Worker | 自定义 PDF.js Worker URL |
|
|
289
|
-
| `loadingText` | `string` | `'PDF 加载中…'` | 首次加载和重新加载时遮罩中的标题文字,支持动态更新 |
|
|
290
|
-
| `showPreviousButton` | `boolean` | `true` | 显示上一页按钮或其自定义插槽,仍遵守首尾页和动画显示规则 |
|
|
291
|
-
| `showNextButton` | `boolean` | `true` | 显示下一页按钮或其自定义插槽,仍遵守首尾页和动画显示规则 |
|
|
292
|
-
|
|
293
|
-
双页模式在空间不足时自动显示单页。`visiblePages` 反映实际显示页,而 `mode` 表示选择的模式。
|
|
294
|
-
|
|
295
|
-
例如单栏直接切页、双栏保留翻书动画:
|
|
296
|
-
|
|
297
|
-
```vue
|
|
298
|
-
<VuePdfFlipbook :url="pdfUrl" :flip-animation="{ single: false, double: true }" />
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
两项都不传时均播放动画;可只传其中一项。按钮、缩略图、`goToPage()` 以及书页鼠标操作使用实际布局对应的设置,关闭动画后仍会等待目标页渲染完成并发送原有页码事件。窄屏下双栏会退化为单栏,此时采用 `single` 的设置。
|
|
302
|
-
|
|
303
|
-
关闭动画时,引擎只在页面或容器尺寸变化时重绘,停止空闲时的持续帧循环和动画 Canvas 克隆监听,以减少 CPU 与 DOM 更新开销。阅读模式、缩放适配、缩略图预加载范围及事件保持一致;重新开启动画无需重载 PDF。若在翻页中关闭动画,会先完成当前折页,再进入按需绘制。
|
|
304
|
-
|
|
305
|
-
可通过 `<VuePdfFlipbook :url="pdfUrl" loading-text="文档加载中,请稍候…" />` 自定义加载提示。加载错误仍显示错误标题和具体错误信息,不使用此文案。
|
|
306
|
-
|
|
307
|
-
双页模式两侧显示纸张层叠边缘,厚度按当前页组之前和之后的页数分配:前半本右厚左薄,后半本左厚右薄,翻页或跳页后平滑变化。每侧最多 16px,短文档按页数减薄,单页模式隐藏。布局在双页状态下每侧预留 18px,防止页叠被裁切;封面及封底的页叠随闭合位置移动。
|
|
308
|
-
|
|
309
|
-
双页模式采用书本封面布局:第 1 页单独居中显示,向后翻动展开为第 2–3 页,之后依次为第 4–5 页、第 6–7 页。返回第 1 页(上一页、缩略图或 `goToPage(1)`)会合上封面。开合使用翻页引擎动画,纸张尺寸保持一致;封面不显示中缝阴影。偶数总页数的最后一页作为单独封底显示。单页模式与窄屏下仍逐页阅读。`visiblePages` 分组相应变为 `[1]`、`[2, 3]`、`[4, 5]`,`page` 在双页内页表示该组左页。
|
|
310
|
-
|
|
311
|
-
默认 Worker 以 data URL 内嵌在库中,与主线程 PDF.js 一起构建,不依赖业务项目的 `/assets` 路径或资源复制配置,支持 Vite 依赖预打包和子路径部署。组件会检查浏览器对 PDF.js 所需 API 的支持情况,缺失时自动加载 PDF.js 的 `legacy` 构建及其配套 Worker;两套延迟模块都会进入发布包,但浏览器只加载选中的一套。若业务 CSP 限制 data/blob 模块,可通过 `workerSrc` 指定外部 Worker 地址,并按业务 CSP 规则提供资源。当前依赖 `pdfjs-dist@6.2.108`,外部 Worker 必须使用相同版本,并与当前选中的现代或 `legacy` 构建对应;业务项目中已有的 5.x Worker 不能混用。
|
|
312
|
-
|
|
313
|
-
### 自适应父容器
|
|
314
|
-
|
|
315
|
-
父容器设置明确宽高后,组件默认占满父容器。书页按 PDF 比例等比缩放:宽度受限时铺满宽度,高度受限时铺满高度,另一方向居中留白,不裁剪或拉伸。双页按两页合计宽高比计算;可用宽度小于 520px 时自动退回单页。容器尺寸变化、全屏及缩略图显隐会自动重新计算。
|
|
316
|
-
|
|
317
|
-
```vue
|
|
318
|
-
<div style="width: 100%; height: 80vh; min-width: 0; min-height: 0">
|
|
319
|
-
<VuePdfFlipbook :url="pdfUrl" />
|
|
320
|
-
</div>
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
`height` 默认值已从 `720` 改为 `'100%'`,因此父容器必须有明确高度(使用百分比时,其祖先也需要确定高度)。需要原来的固定高度时传入 `:height="720"`。已取消单页 500px、双页 1120px 及 PDF 原始尺寸的显示上限;此处调整显示尺寸,Canvas 渲染分辨率策略保持不变。
|
|
324
|
-
|
|
325
|
-
鼠标移入阅读区时,左侧显示“上一页”、右侧显示“下一页”按钮。双页模式首页只显示“下一页”,显示最后一页的页面组只显示“上一页”;封面打开或合上的动画过程中同时隐藏两个按钮,动画结束后再按当前页面恢复,避免按钮先于书本开合换位。单页模式在首尾保留对应禁用按钮。按钮复用现有翻页与预加载流程,目标页加载期间禁用相应操作。键盘聚焦时也会显示按钮;无悬停能力的触屏设备上保持显示。缩略图列表不占用按钮的定位区域。
|
|
326
|
-
|
|
327
|
-
调用方可设置 `:show-previous-button="false"` 或 `:show-next-button="false"` 分别隐藏内置按钮,两者默认均为 `true`,属性变化会立即更新显示。隐藏按钮只影响界面;`previous()`、`next()`、缩略图和翻页手势仍可使用。上述首页、末页及动画期间的隐藏规则继续生效。
|
|
328
|
-
|
|
329
|
-
使用 `#previous-button` 和 `#next-button` 可分别替换整个按钮。插槽提供 `disabled` 和 `navigate()`;外部按钮应绑定禁用状态,并在点击时调用 `navigate()`。插槽仍受对应的 `showPreviousButton` / `showNextButton` 属性及首尾页、封面动画期间的显示规则控制。外部负责按钮的布局与样式;可以沿用默认的 `vpf-page-nav`、`vpf-page-nav--previous` / `vpf-page-nav--next` 类,也可以使用自己的类。
|
|
330
|
-
|
|
331
|
-
下面的模板片段放入前面的业务页面中。按钮内容可换成业务图标或 UI 组件。建议使用原生 `button`;普通 `div` 的 `disabled` 属性不会提供原生禁用和键盘操作,需要自行实现。若使用自己的 CSS 类,请设置定位,否则组件不会自动为插槽内容补上按钮样式。
|
|
332
|
-
|
|
333
|
-
```vue
|
|
334
|
-
<VuePdfFlipbook :url="pdfUrl">
|
|
335
|
-
<template #previous-button="{ disabled, navigate }">
|
|
336
|
-
<button type="button" class="vpf-page-nav vpf-page-nav--previous" :disabled="disabled" @click.stop="navigate">
|
|
337
|
-
返回
|
|
338
|
-
</button>
|
|
339
|
-
</template>
|
|
340
|
-
<template #next-button="{ disabled, navigate }">
|
|
341
|
-
<button type="button" class="vpf-page-nav vpf-page-nav--next" :disabled="disabled" @click.stop="navigate">
|
|
342
|
-
继续
|
|
343
|
-
</button>
|
|
344
|
-
</template>
|
|
345
|
-
</VuePdfFlipbook>
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
## 事件
|
|
349
|
-
|
|
350
|
-
| 事件 | 参数 | 说明 |
|
|
351
|
-
| --- | --- | --- |
|
|
352
|
-
| `loaded` | `{ pages: number }` | 文档加载与翻页引擎初始化完成,页面 Canvas 可能仍在渲染 |
|
|
353
|
-
| `error` | `unknown` | 加载或页面渲染错误,外部负责提示 |
|
|
354
|
-
| `progress` | `number` | 下载百分比,服务端未提供总长度时可能为 0 |
|
|
355
|
-
| `page-change` | `(page: number, thumbnailUrl: string \| null)` | 当前页码及该页的 PNG Data URL 缩略图;生成失败时图片为 `null` |
|
|
356
|
-
| `mode-change` | `ReaderMode` | 调用 `setMode` 改变模式 |
|
|
357
|
-
| `state-change` | `PdfFlipbookState` | 加载、页码、布局等状态变化 |
|
|
358
|
-
|
|
359
|
-
`PdfFlipbookState` 包含 `page`、`pages`、`mode`、`visiblePages`、`loading`、`progress`、`error`(文档加载错误文本)、`canPrevious`、`canNext`。状态为快照,修改它不会修改组件。
|
|
360
|
-
|
|
361
|
-
`page-change` 保持首个参数为页码,已有只接收页码的监听器无需修改。第二个参数可直接绑定到 `<img :src="thumbnailUrl">`;组件从已渲染的正文 Canvas 等比缩小为 PNG Data URL,竖向页的宽高上限为 160 × 220 像素,横向页为 220 × 160 像素,不会拉伸原始比例。生成过程不依赖缩略图列表是否显示,也适用于自定义缩略图插槽。首次加载时会等当前页 Canvas 绘制完成后再发送此事件;`loaded` 仍在文档和翻页引擎初始化后立即发送。双栏模式的页码和图片均对应当前页组的左页。
|
|
362
|
-
|
|
363
|
-
```vue
|
|
364
|
-
<VuePdfFlipbook :url="pdfUrl" @page-change="onPageChange" />
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
```ts
|
|
368
|
-
import { ref } from 'vue'
|
|
369
|
-
|
|
370
|
-
const preview = ref<string | null>(null)
|
|
371
|
-
function onPageChange(page: number, thumbnailUrl: string | null) {
|
|
372
|
-
console.log('当前页码', page)
|
|
373
|
-
preview.value = thumbnailUrl
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
另外,`pageLoading` 表示跳页前正在下载或渲染目标及动画必需页面,`targetPage` 表示目标页码(准备及动画期间有效,空闲时为 `null`)。外部可监听 `state-change` 展示 `正在加载第 {{ state.targetPage }} 页`。它与首次打开文档的 `loading` 独立;页面准备完成后、动画开始前自动结束,失败、切换模式、重新加载或取消跳页时也会清除。已准备好的页面不会显示加载状态。demo 在阅读区显示遮罩和转圈提示,等待期间仍可通过进度条选择其他目标页。
|
|
378
|
-
|
|
379
|
-
## 实例方法
|
|
380
|
-
|
|
381
|
-
`next()`、`previous()`、`goToPage()` 共用 150ms 防抖:最后一次调用后等待 150ms,再准备页面并翻页。连续点击按钮、缩略图或拖动进度条时只执行最后一次有效请求,避免为中间目标反复加载。被替换或取消的 `goToPage()` Promise 会正常结束。动画进行时继续忽略新的程序跳页;切换模式、重新加载及卸载会取消待执行请求。已就绪书页的原生拖拽仍即时响应,并保留动画期间的重复操作保护。
|
|
382
|
-
|
|
383
|
-
| 方法 | 说明 |
|
|
115
|
+
| 功能模块 | 内容 |
|
|
384
116
|
| --- | --- |
|
|
385
|
-
|
|
|
386
|
-
|
|
|
387
|
-
|
|
|
388
|
-
|
|
|
389
|
-
|
|
|
390
|
-
|
|
|
391
|
-
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
外部项目通过组件 ref 调用跳页,建议在 `loaded` 事件之后调用:
|
|
396
|
-
|
|
397
|
-
```vue
|
|
398
|
-
<script setup lang="ts">
|
|
399
|
-
import { ref } from 'vue'
|
|
400
|
-
import { VuePdfFlipbook } from '@agilehub/vue-pdf-flipbook'
|
|
401
|
-
import type { PdfFlipbookExpose } from '@agilehub/vue-pdf-flipbook'
|
|
402
|
-
import '@agilehub/vue-pdf-flipbook/style.css'
|
|
403
|
-
|
|
404
|
-
const reader = ref<PdfFlipbookExpose>()
|
|
405
|
-
const targetPage = ref(1)
|
|
406
|
-
const jumpError = ref('')
|
|
407
|
-
|
|
408
|
-
async function jump() {
|
|
409
|
-
jumpError.value = ''
|
|
410
|
-
await reader.value?.goToPage(targetPage.value)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function onError(error: unknown) {
|
|
414
|
-
jumpError.value = error instanceof Error ? error.message : String(error)
|
|
415
|
-
}
|
|
416
|
-
</script>
|
|
417
|
-
|
|
418
|
-
<template>
|
|
419
|
-
<VuePdfFlipbook ref="reader" url="https://example.com/catalog.pdf" :height="720" @error="onError" />
|
|
420
|
-
<input v-model.number="targetPage" type="number" min="1" step="1" />
|
|
421
|
-
<button @click="jump">跳转</button>
|
|
422
|
-
<p v-if="jumpError">{{ jumpError }}</p>
|
|
423
|
-
</template>
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
组件会在运行时严格校验:`0`、负数、小数、`NaN`、`Infinity`、字符串和超过总页数的值均不跳转,也不会取消已有的有效跳页请求,并通过 `error` 事件报告 `RangeError`(Promise 正常结束)。不再将非法值自动取整或调整到首尾页。双页模式显示包含目标页的那一组页面;目标页已经可见时无需翻动。
|
|
427
|
-
|
|
428
|
-
## 大文件按需预览
|
|
429
|
-
|
|
430
|
-
默认只渲染当前可见页面,以及前后各 5 页。单页模式稳定状态最多 11 个页面 Canvas,双页模式最多 12 个;首页、末页会按文档边界裁剪。翻页后移出窗口的页面取消渲染并释放 Canvas 位图,翻回时重新渲染。所有页码保留轻量 DOM 占位以维持翻页引擎索引。
|
|
431
|
-
|
|
432
|
-
远距离跳页时临时保留起点和目标两个窗口,不加载中间所有页面;目标及动画必需页面准备完成后立即播放动画,不等待全部前后预加载页,结束后释放旧窗口。准备期间新的跳页请求会替代旧请求;动画进行时忽略新的程序跳页请求。渲染失败通过 `error` 报告,可调用 `reload()` 重试。初次布局仍会读取第 1 页尺寸。首次展示和跳页优先渲染可见页及动画背面,再逐步补齐前后各 5 页的窗口,同时最多预加载两张尚未完成的正文页。缩略图等对应正文页就绪后再渲染,避免竞争首屏资源;正文和缩略图共享的 PDF 页面资源在最后一个使用者卸载后释放。
|
|
433
|
-
|
|
434
|
-
连续请求相同或重叠的目标窗口会保留已完成的渲染记录。书页点击、拖拽也会检查动画必需页面:未准备好时保留当前页,通过 `pageLoading` 提示外部,准备完成后自动翻页;已准备好时保留原有拖拽交互。尚未绘制的 Canvas 隐藏,避免显示黑色底图。
|
|
435
|
-
|
|
436
|
-
组件使用自定义 `PDFDataRangeTransport`:第一次请求就带 `Range: bytes=0-262143`,之后只请求 PDF.js 所需的字节段,关闭流式下载和自动预取。请求按 256 KiB 对齐,PDF.js 可能合并相邻段,减少大图、字体跨多个小段时的网络往返。每段校验 HTTP `206`、长度以及浏览器可见的 `Content-Range`;收到 `200` 时立即取消响应并报告错误,不再回退到整文件下载。
|
|
437
|
-
|
|
438
|
-
首段下载与 PDF.js/Worker 模块加载并行进行。分段请求遵守服务器的 HTTP 缓存策略,组件不再强制 `no-store`;实际缓存命中仍取决于浏览器、服务器响应头及开发者工具是否启用“停用缓存”。Network 中的多条 PDF 请求对应字节段,不是一条请求对应一页。排查慢请求时分别检查排队、等待首字节(TTFB)和下载耗时:较长的 TTFB 常需检查 CDN 命中及源站响应;图像较大的 PDF 还受下载带宽影响。
|
|
439
|
-
|
|
440
|
-
跨域服务器必须允许 CORS 和 Range 请求。建议 OSS/CDN 的 `Access-Control-Expose-Headers` 包含 `Content-Range, Accept-Ranges, Content-Length, Content-Encoding, ETag`,PDF 的 `Content-Type` 设置为 `application/pdf`。组件优先从 `Content-Range` 获取总长度,不再依赖浏览器是否能读到 `Accept-Ranges`。
|
|
441
|
-
|
|
442
|
-
若暂时不能调整服务器,可通过 `fileSize` 传入文件元数据中的**原始字节数**:
|
|
443
|
-
|
|
444
|
-
```vue
|
|
445
|
-
<VuePdfFlipbook :url="file.url" :file-size="file.size" />
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
`file.size` 应来自上传文件的 `File.size` 或后端文件元数据。不能使用可能被 gzip/br 压缩的 HEAD `Content-Length`。未暴露 `Content-Range` 且未提供 `fileSize` 时会明确报错;不会猜测长度或下载全文。换用其他 URL 时需暴露响应头或提供该文件自己的大小。`blob:` / `data:` URL 和不支持 Range 的服务器不适用于此分段传输模式。
|
|
449
|
-
|
|
450
|
-
PDF 的页与字节段不是一一对应:解析索引、字体及共享资源可能读取窗口之外的字节。因此这里限制的是页面渲染窗口,并尽量按需请求文件字节,不保证只下载这几页的数据,也不保证 PDF.js 已下载的数据缓存固定大小。`loaded` 表示文档可用,并不要求下载进度达到 100%。
|
|
451
|
-
|
|
452
|
-
内置缩略图只渲染当前可见页前后各 5 页的窗口,其余页保留页码按钮,点击后按需跳转,避免缩略图触发全书渲染。
|
|
453
|
-
|
|
454
|
-
## 外部功能接入
|
|
455
|
-
|
|
456
|
-
### 插槽一览
|
|
457
|
-
|
|
458
|
-
| 插槽 | 参数 | 由调用方负责的内容 |
|
|
459
|
-
| --- | --- | --- |
|
|
460
|
-
| `previous-button` | `{ disabled, navigate }` | 上一页按钮的标签、样式与点击绑定 |
|
|
461
|
-
| `next-button` | `{ disabled, navigate }` | 下一页按钮的标签、样式与点击绑定 |
|
|
462
|
-
| `thumbnail` | `{ page, pdf, isActive, shouldRender }` | 单项缩略图内容;外层点击和布局仍由组件处理 |
|
|
463
|
-
| `thumbnails` | `{ pdf, items, visible, pageAspectRatio, select, hide, reportError }` | 整个缩略图列表的布局、位置、尺寸和交互 |
|
|
464
|
-
|
|
465
|
-
只提供需要替换的插槽即可。翻页按钮示例见 [Props](#props) 下方;缩略图示例见本节。加载标题使用 `loadingText` 属性,目前没有 `loading` 或 `toolbar` 插槽。
|
|
466
|
-
|
|
467
|
-
### 缩略图扩展:按需选择一个插槽
|
|
468
|
-
|
|
469
|
-
默认不传插槽时使用内置底部缩略图,包含拖动滚动、点击跳页和封面动画同步。`showThumbnails()` / `hideThumbnails()` 控制显示开关,`state-change` 的 `thumbnailsVisible` 反映该状态。
|
|
470
|
-
|
|
471
|
-
- `#thumbnail`:仅替换单项内容,组件保留列表容器、外层 `div`、选中状态及点击/键盘跳页。适合简单内容替换。
|
|
472
|
-
- `#thumbnails`:接管整个列表,不附加列表或单项外层,不注入缩略图尺寸、排列或点击处理。适合侧栏、横向覆盖、不同尺寸、菜单、筛选、虚拟列表等功能。
|
|
473
|
-
|
|
474
|
-
两个插槽同时存在时以 `#thumbnails` 为准,不重复渲染内置列表。使用完整列表插槽不需要任何缩略图布局属性。样式由自己的 CSS 控制,位置由普通布局、绝对定位或 Vue `Teleport` 控制。
|
|
475
|
-
|
|
476
|
-
选中状态请绑定插槽实时提供的 `item.isActive`,不要仅在点击时记录本地选中页。它会随按钮翻页、拖拽翻页和程序跳页更新;双栏内页的两张缩略图都会选中。完整自定义列表需要自行定义高亮样式:例如绑定 `:aria-current="item.isActive ? 'page' : undefined"`,为普通项设置 `border: 2px solid transparent`,再为 `[aria-current='page']` 设置边框颜色。仅设置 `border-color` 而未设置边框宽度和类型,不会显示选中边框。
|
|
477
|
-
|
|
478
|
-
完整插槽参数类型 `PdfThumbnailsSlotProps` 和单项数据类型 `PdfThumbnailItem` 从包入口导出:
|
|
479
|
-
|
|
480
|
-
| 参数 | 用途 |
|
|
481
|
-
| --- | --- |
|
|
482
|
-
| `pdf` | 借用的 PDF 文档;加载期间为 `undefined`,不要调用 `destroy()` |
|
|
483
|
-
| `items` | 所有页的 `{ page, isActive, shouldRender }`,可自行筛选或分页 |
|
|
484
|
-
| `visible` | 缩略图显示开关,由 `showThumbnails()` / `hideThumbnails()` 更新 |
|
|
485
|
-
| `pageAspectRatio` | PDF 首页宽高比,可用于设置预览的 `aspect-ratio` |
|
|
486
|
-
| `select(page)` | 经过校验、防抖和页面准备的跳页操作,与 `goToPage()` 相同 |
|
|
487
|
-
| `hide()` | 隐藏缩略图 |
|
|
488
|
-
| `reportError(error)` | 将自定义预览错误转发为阅读器的 `error` 事件 |
|
|
489
|
-
|
|
490
|
-
插槽在加载期间也可用,便于自定义空状态。请通过 `pdf && visible` 控制预览挂载,并仅在 `item.shouldRender` 时创建 `PdfCanvasPage`,防止缩略图抢占正文资源或触发全书渲染。更换 PDF 时插槽子树会重新挂载,释放旧预览。自定义列表负责自己的滚动、拖动、键盘可访问性和封面动画行为;组件不会强制干预这些交互。
|
|
491
|
-
|
|
492
|
-
### 横向覆盖阅读区的缩略图
|
|
493
|
-
|
|
494
|
-
下面是可独立使用的业务组件示例。单项宽高、边框、额外按钮都由业务模板决定:
|
|
495
|
-
|
|
496
|
-
```vue
|
|
497
|
-
<script setup lang="ts">
|
|
498
|
-
import { ref } from 'vue'
|
|
499
|
-
import { VuePdfFlipbook, PdfCanvasPage } from '@agilehub/vue-pdf-flipbook'
|
|
500
|
-
import type { PdfFlipbookExpose } from '@agilehub/vue-pdf-flipbook'
|
|
501
|
-
import '@agilehub/vue-pdf-flipbook/style.css'
|
|
502
|
-
|
|
503
|
-
const props = defineProps<{ url: string }>()
|
|
504
|
-
const reader = ref<PdfFlipbookExpose>()
|
|
505
|
-
</script>
|
|
506
|
-
|
|
507
|
-
<template>
|
|
508
|
-
<button @click="reader?.showThumbnails()">显示缩略图</button>
|
|
509
|
-
<div class="reader-wrap">
|
|
510
|
-
<VuePdfFlipbook ref="reader" :url="props.url" height="100%">
|
|
511
|
-
<template #thumbnails="{ pdf, items, visible, select, hide, reportError, pageAspectRatio }">
|
|
512
|
-
<section v-if="pdf && visible" class="my-thumbnails">
|
|
513
|
-
<button @click="hide()">关闭</button>
|
|
514
|
-
<nav class="my-thumbnail-list" aria-label="PDF 缩略图">
|
|
515
|
-
<div v-for="item in items" :key="item.page" class="my-thumbnail">
|
|
516
|
-
<button
|
|
517
|
-
class="my-thumbnail-select"
|
|
518
|
-
:aria-label="`跳转到第 ${item.page} 页`"
|
|
519
|
-
:aria-current="item.isActive ? 'page' : undefined"
|
|
520
|
-
@click="select(item.page)"
|
|
521
|
-
>
|
|
522
|
-
<div :style="{ aspectRatio: pageAspectRatio }">
|
|
523
|
-
<PdfCanvasPage
|
|
524
|
-
v-if="item.shouldRender" :pdf="pdf" :page-number="item.page"
|
|
525
|
-
:render-scale="0.22" @error="reportError"
|
|
526
|
-
/>
|
|
527
|
-
</div>
|
|
528
|
-
第 {{ item.page }} 页
|
|
529
|
-
</button>
|
|
530
|
-
<!-- 收藏、菜单等独立操作可放在这里,不会被组件强制触发跳页。 -->
|
|
531
|
-
</div>
|
|
532
|
-
</nav>
|
|
533
|
-
</section>
|
|
534
|
-
</template>
|
|
535
|
-
</VuePdfFlipbook>
|
|
536
|
-
</div>
|
|
537
|
-
</template>
|
|
538
|
-
|
|
539
|
-
<style scoped>
|
|
540
|
-
.reader-wrap { position: relative; height: 600px; min-width: 0; min-height: 0; }
|
|
541
|
-
.my-thumbnails { position: absolute; z-index: 20; left: 10%; right: 10%; bottom: 12px; background: #ffffffdd; }
|
|
542
|
-
.my-thumbnail-list { display: flex; gap: 8px; overflow: auto; max-height: 180px; }
|
|
543
|
-
.my-thumbnail { flex: 0 0 auto; width: 96px; }
|
|
544
|
-
.my-thumbnail-select { width: 100%; padding: 4px; border: 2px solid transparent; background: white; cursor: pointer; }
|
|
545
|
-
.my-thumbnail-select[aria-current] { border-color: #987044; }
|
|
546
|
-
</style>
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
缩略图相对于 `.reader-wrap` 定位;`position: relative` 用于建立定位参照。若与缩放示例组合,固定尺寸的覆盖列表应 Teleport 到缩放层之外的定位容器;直接放在缩放内容层里会跟随该层的位置变化。
|
|
550
|
-
|
|
551
|
-
### 外部两列侧栏与点击事件
|
|
552
|
-
|
|
553
|
-
下面示例可保存为 `SidebarPdfReader.vue`。它使用元素 ref 作为 Teleport 目标,多份阅读器不会共享同一个固定 ID。先挂载侧栏容器,再挂载阅读器;侧栏用 `v-show` 显隐,避免 Teleport 目标被移除。
|
|
554
|
-
|
|
555
|
-
```vue
|
|
556
|
-
<script setup lang="ts">
|
|
557
|
-
import { ref } from 'vue'
|
|
558
|
-
import { VuePdfFlipbook, PdfCanvasPage } from '@agilehub/vue-pdf-flipbook'
|
|
559
|
-
import type { PdfFlipbookExpose, PdfFlipbookState, PdfThumbnailsSlotProps } from '@agilehub/vue-pdf-flipbook'
|
|
560
|
-
import '@agilehub/vue-pdf-flipbook/style.css'
|
|
561
|
-
|
|
562
|
-
const props = defineProps<{ url: string; fileSize?: number }>()
|
|
563
|
-
const emit = defineEmits<{ 'thumbnail-click': [page: number] }>()
|
|
564
|
-
const reader = ref<PdfFlipbookExpose>()
|
|
565
|
-
const state = ref<PdfFlipbookState>()
|
|
566
|
-
const thumbnailTarget = ref<HTMLElement | null>(null)
|
|
567
|
-
const errorMessage = ref('')
|
|
568
|
-
|
|
569
|
-
function onThumbnailClick(page: number, select: PdfThumbnailsSlotProps['select']) {
|
|
570
|
-
emit('thumbnail-click', page) // 这是业务封装发出的事件,page 从 1 开始。
|
|
571
|
-
void select(page)
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function onError(error: unknown) {
|
|
575
|
-
errorMessage.value = error instanceof Error ? error.message : String(error)
|
|
576
|
-
}
|
|
577
|
-
</script>
|
|
578
|
-
|
|
579
|
-
<template>
|
|
580
|
-
<section class="vpf-sidebar-page">
|
|
581
|
-
<nav class="vpf-sidebar-toolbar">
|
|
582
|
-
<button :disabled="!state?.pages || state?.loading" @click="state?.thumbnailsVisible ? reader?.hideThumbnails() : reader?.showThumbnails()">切换缩略图</button>
|
|
583
|
-
<span>{{ state?.page ?? 1 }} / {{ state?.pages ?? 0 }}</span>
|
|
584
|
-
</nav>
|
|
585
|
-
<p v-if="errorMessage" role="alert">{{ errorMessage }}</p>
|
|
586
|
-
<div class="vpf-sidebar-layout" :class="{ 'is-sidebar-open': state?.thumbnailsVisible }">
|
|
587
|
-
<aside ref="thumbnailTarget" v-show="state?.thumbnailsVisible" class="vpf-sidebar-target" aria-label="缩略图侧栏" />
|
|
588
|
-
<main class="vpf-sidebar-main">
|
|
589
|
-
<!-- 如需缩放,把前述滚动视口和缩放内容层放在 main 内,aside 保留在外侧。 -->
|
|
590
|
-
<VuePdfFlipbook
|
|
591
|
-
v-if="thumbnailTarget" ref="reader" :url="props.url" :file-size="props.fileSize"
|
|
592
|
-
height="100%" @state-change="state = $event" @error="onError"
|
|
593
|
-
>
|
|
594
|
-
<template #thumbnails="{ pdf, items, visible, select, reportError, pageAspectRatio }">
|
|
595
|
-
<Teleport v-if="pdf && visible && thumbnailTarget" :to="thumbnailTarget">
|
|
596
|
-
<nav class="vpf-sidebar-grid" aria-label="PDF 缩略图">
|
|
597
|
-
<button
|
|
598
|
-
v-for="item in items" :key="item.page" type="button"
|
|
599
|
-
class="vpf-sidebar-item" :aria-label="`跳转到第 ${item.page} 页`"
|
|
600
|
-
:aria-current="item.isActive ? 'page' : undefined"
|
|
601
|
-
@click.stop="onThumbnailClick(item.page, select)"
|
|
602
|
-
>
|
|
603
|
-
<PdfCanvasPage
|
|
604
|
-
v-if="item.shouldRender" :pdf="pdf" :page-number="item.page"
|
|
605
|
-
:render-scale="0.22" @error="reportError"
|
|
606
|
-
/>
|
|
607
|
-
<div v-else :style="{ aspectRatio: pageAspectRatio }" />
|
|
608
|
-
<span>第 {{ item.page }} 页</span>
|
|
609
|
-
</button>
|
|
610
|
-
</nav>
|
|
611
|
-
</Teleport>
|
|
612
|
-
</template>
|
|
613
|
-
</VuePdfFlipbook>
|
|
614
|
-
</main>
|
|
615
|
-
</div>
|
|
616
|
-
</section>
|
|
617
|
-
</template>
|
|
618
|
-
|
|
619
|
-
<style scoped>
|
|
620
|
-
.vpf-sidebar-page { display: flex; flex-direction: column; height: 80dvh; min-width: 0; min-height: 0; overflow: hidden; }
|
|
621
|
-
.vpf-sidebar-toolbar { display: flex; flex: none; gap: 8px; padding: 8px; }
|
|
622
|
-
.vpf-sidebar-layout { display: grid; grid-template-columns: minmax(0, 1fr) 0; grid-template-rows: minmax(0, 1fr); flex: 1; min-width: 0; min-height: 0; }
|
|
623
|
-
.vpf-sidebar-layout.is-sidebar-open { grid-template-columns: minmax(0, 1fr) min(240px, 40%); }
|
|
624
|
-
.vpf-sidebar-main { display: flex; grid-column: 1; grid-row: 1; min-width: 0; min-height: 0; overflow: hidden; }
|
|
625
|
-
.vpf-sidebar-target { grid-column: 2; grid-row: 1; min-width: 0; min-height: 0; overflow: auto; padding: 8px; background: #edf0eb; box-sizing: border-box; }
|
|
626
|
-
.vpf-sidebar-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
627
|
-
.vpf-sidebar-item { min-width: 0; width: 100%; padding: 4px; border: 2px solid transparent; background: white; cursor: pointer; }
|
|
628
|
-
.vpf-sidebar-item[aria-current='page'] { border-color: #36835c; }
|
|
629
|
-
</style>
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
父页面可以监听业务封装的 `@thumbnail-click="onThumbnailClick"`,获取点击的确切页码。原始 `VuePdfFlipbook` 没有 `thumbnail-click` 事件;在完整列表插槽中,点击由自己的元素处理。`page-change` 表示实际阅读页变化,双栏时它可能是所点击页所在页组的左页;重复点击当前可见页仍可触发业务点击事件,但不一定发生翻页。
|
|
633
|
-
|
|
634
|
-
`item.isActive` 用于阅读页同步高亮,`item.shouldRender` 用于控制 Canvas 是否创建。单项宽高直接通过自己的 CSS 决定,图片容器应保持比例;`pageAspectRatio` 是 PDF 首页比例,混合尺寸文档中仅适合用作未渲染项的占位参考。`select(page)` 完成表示请求已启动、取消或被忽略,不代表翻页动画已经结束。
|
|
635
|
-
|
|
636
|
-
### 只替换单项内容
|
|
637
|
-
|
|
638
|
-
若保留组件内置列表和点击跳页,只需使用 `#thumbnail`。以下是替换内容的模板片段,`PdfCanvasPage` 需要从包入口导入:
|
|
639
|
-
|
|
640
|
-
```vue
|
|
641
|
-
<VuePdfFlipbook ref="reader" :url="pdfUrl">
|
|
642
|
-
<template #thumbnail="{ page, pdf, isActive, shouldRender }">
|
|
643
|
-
<div :class="{ selected: isActive }" @click="console.log('点击缩略图', page)">
|
|
644
|
-
<PdfCanvasPage v-if="shouldRender" :pdf="pdf" :page-number="page" :render-scale="0.22" @error="onError" />
|
|
645
|
-
<span>第 {{ page }} 页</span>
|
|
646
|
-
</div>
|
|
647
|
-
</template>
|
|
648
|
-
</VuePdfFlipbook>
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
单项插槽外层仍有组件提供的可点击 `div`。不要在上例的点击监听中使用 `.stop`,否则事件无法传到该外层并自动跳页。需要完全控制外层宽高、键盘交互和跳页行为时,使用前述 `#thumbnails`。
|
|
652
|
-
|
|
653
|
-
`#thumbnail` 的参数为 `PdfThumbnailSlotProps`:`page`、`pdf`、`isActive`、`shouldRender`,继续支持单项内容自定义。
|
|
654
|
-
|
|
655
|
-
新版已移除曾标记为 `@deprecated` 的 `thumbnailTarget`、`thumbnailLayout`、`thumbnailColumns`、`thumbnailItemStyle`,不再提供这些参数的兼容逻辑。需要自定义挂载位置时,在 `#thumbnails` 内使用 Vue `Teleport`;横排、网格、列数以及单项宽高等样式由调用方自己的容器与 CSS 控制。上方完整列表示例已采用此方式。
|
|
656
|
-
|
|
657
|
-
- 工具栏、进度滑块、键盘快捷键:调用翻页方法并监听状态事件。
|
|
658
|
-
- 加载提示、错误提示、重试:监听状态或 `error`,调用 `reload()`。
|
|
659
|
-
- 缩略图:调用 `showThumbnails()` / `hideThumbnails()`,结合本节的单项或完整列表插槽。
|
|
660
|
-
- 全屏:在外部容器上调用浏览器 Fullscreen API,组件自动响应容器尺寸变化。
|
|
661
|
-
- 缩放和平移:由外部包装容器改变尺寸并处理滚动,完整代码见[外部容器示例](#外部容器放大缩小与全屏)。
|
|
662
|
-
|
|
663
|
-
`src/demo/App.vue` 展示外部控件接入:放大后按住鼠标左键拖动可平移阅读区域,松开即停止;恢复 100% 时重置滚动位置并恢复书页拖拽翻页。放大时仍可通过外部按钮翻页。平移逻辑位于 demo,核心组件不会自动处理键盘事件或创建弹层。
|
|
664
|
-
|
|
665
|
-
## 常见接入问题
|
|
666
|
-
|
|
667
|
-
### 安装后提示找不到组件入口或类型
|
|
668
|
-
|
|
669
|
-
确认安装和导入的都是 `@agilehub/vue-pdf-flipbook`,并且保留完整安装包中的 `dist/`。`VuePdfFlipbook` 使用具名导入,类型使用 `import type`。不要从包内的 `src/` 或 demo 路径导入。`useZoomPan`、`useFullscreen` 是仓库示例代码,不是 npm 包的公共导出;可以采用本 README 的独立实现。
|
|
670
|
-
|
|
671
|
-
### 页面空白或出现整页滚动条
|
|
672
|
-
|
|
673
|
-
检查阅读区父级是否有确定高度、flex/grid 子项是否设置 `min-width: 0; min-height: 0`,以及样式入口是否引入。缩放示例在 100% 时外层隐藏溢出,放大时仅阅读视口开启滚动。PDF 请求失败则根据 `error` 事件检查 URL、CORS 和 HTTP 206 支持。
|
|
674
|
-
|
|
675
|
-
### 点击放大,书页尺寸没有变化
|
|
676
|
-
|
|
677
|
-
同时改变缩放内容层的 `width` 和 `height`,保持 `flex: none`,并让组件使用 `height="100%"`。仅改变宽度、倍率变量或调用已移除的 `zoomIn()` 不会按上述容器方案放大。需要“适应页面”时把倍率恢复为 1。
|
|
678
|
-
|
|
679
|
-
### 自定义缩略图不显示或选中状态不更新
|
|
680
|
-
|
|
681
|
-
先调用 `showThumbnails()`,完整列表插槽中用 `pdf && visible` 控制挂载;Teleport 的目标必须已存在。选中样式绑定 `item.isActive`,预览 Canvas 绑定 `item.shouldRender`。业务 CSS 必须实际定义边框或背景高亮。仅替换单项时使用 `#thumbnail`,完全控制位置和尺寸时使用 `#thumbnails`。
|
|
682
|
-
|
|
683
|
-
### 如何指定外部 Worker
|
|
684
|
-
|
|
685
|
-
默认无需配置。必须使用外部文件时,将与当前包依赖版本完全一致的 `pdf.worker.min.mjs` 部署为可访问的静态资源,然后指定其地址:
|
|
686
|
-
|
|
687
|
-
```vue
|
|
688
|
-
<VuePdfFlipbook :url="pdfUrl" worker-src="/pdf.worker.min.mjs" />
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
当前源码依赖 PDF.js 6.2.108,不能配用其他版本的 Worker。部署在子路径时请传入对应子路径下的实际 URL;跨域 Worker 还需要符合业务站点的跨域和 CSP 配置。
|
|
692
|
-
|
|
693
|
-
### 翻页返回的图片如何显示
|
|
694
|
-
|
|
695
|
-
监听 `@page-change="onPageChange"`,处理函数的两个参数分别是页码和 `string | null` 图片 URL。将第二个参数保存到 ref 后使用 `<img v-if="preview" :src="preview" style="max-width: 220px; height: auto" />`。这是 PNG Data URL,无需调用 `URL.revokeObjectURL()`;若业务累计保存每页图片,应自行限制缓存数量。
|
|
696
|
-
|
|
697
|
-
## 从内置阅读器迁移
|
|
698
|
-
|
|
699
|
-
已移除属性 `showToolbar`、`showThumbnails`、`minZoom`、`maxZoom`、`zoomStep`,以及方法 `zoomIn()`、`zoomOut()`、`resetZoom()`、`toggleFullscreen()` 和事件 `zoom-change`。缩略图仍通过实例方法 `showThumbnails()` / `hideThumbnails()` 控制。请将工具栏、缩放与全屏逻辑迁移到业务组件。默认背景为透明,页码工具栏由业务实现;阅读区提供左右翻页按钮和首次加载遮罩。
|
|
700
|
-
|
|
701
|
-
## 开发
|
|
702
|
-
|
|
703
|
-
### 代码组织
|
|
704
|
-
|
|
705
|
-
组件采用 Composition API 按职责组合,内部 composable 不作为包的公共导出。
|
|
706
|
-
|
|
707
|
-
| 模块 | 职责 |
|
|
708
|
-
| --- | --- |
|
|
709
|
-
| `VuePdfFlipbook.vue` | 声明组件接口、组合逻辑和模板渲染 |
|
|
710
|
-
| `usePdfFlipbook` | 外观层:协调文档、导航、布局和引擎,统一生成对外状态快照 |
|
|
711
|
-
| `usePdfDocument` | 管理 PDF 加载、进度、请求取消与文档生命周期 |
|
|
712
|
-
| `usePageNavigation` | 管理页码、预览窗口和跳页准备流程,通过引擎接口执行翻页 |
|
|
713
|
-
| `usePageFlip` | 适配 PageFlip,封装初始化、事件、Canvas 克隆和销毁 |
|
|
714
|
-
| `useBookLayout` | 计算容器尺寸、观察尺寸变化和调度布局更新 |
|
|
715
|
-
| `usePdfPageCanvas` | 管理单页 Canvas 渲染、取消及资源释放 |
|
|
716
|
-
|
|
717
|
-
上述 composable 位于 `src/composables/`,采用单一职责、外观和适配器模式。响应式状态由各模块持有,通过只读 ref 和显式方法交互;副作用在 Vue 生命周期内注册并清理。导航依赖最小引擎接口,因此可以独立验证异步跳页和取消行为。
|
|
718
|
-
|
|
719
|
-
demo 的缩放拖动与全屏分别封装在 `src/demo/composables/useZoomPan.ts`、`useFullscreen.ts`,业务 UI 仍由 demo 组合,不进入库的公共 API。
|
|
720
|
-
|
|
721
|
-
### 命令
|
|
722
|
-
|
|
723
|
-
需要 Node.js 22.15 或更新版本(当前 PDF.js 依赖和测试脚本的共同要求)。远程 PDF 必须允许浏览器 CORS 访问。
|
|
724
|
-
|
|
725
|
-
```bash
|
|
726
|
-
npm install
|
|
727
|
-
npm run dev
|
|
728
|
-
npm run typecheck
|
|
729
|
-
npm test
|
|
730
|
-
npm run build
|
|
731
|
-
npm run pack:check
|
|
732
|
-
npm run test:package
|
|
733
|
-
```
|
|
734
|
-
|
|
735
|
-
`npm test` 使用 Node.js 原生测试运行器,需要 Node.js 22.15+,覆盖页面窗口、网络分段以及异步跳页的等待、替换、失败和取消场景。
|
|
736
|
-
|
|
737
|
-
`npm pack` 和 `npm publish` 会通过 `prepack` 自动重新构建,避免发布旧产物。`npm run test:package` 解包实际 tarball,验证发布入口文件、组件和类型导入以及样式入口。
|
|
738
|
-
|
|
739
|
-
## 运行时资源与兼容性
|
|
740
|
-
|
|
741
|
-
- 单页 Canvas 最多 200 万像素、单边最多 4096 像素。超大页面会自动降低渲染倍率;`PdfCanvasPage` 的 `renderScale` 必须是有限正数。
|
|
742
|
-
- 当前翻页引擎依赖全量页节点,单份 PDF 最多支持 2000 页。超过上限时,在生成正文/缩略图节点前停止并发送 `error`,请拆分文档后阅读。这是容量保护,尚未实现正文虚拟化。
|
|
743
|
-
- 单次分段请求(含响应体读取)、文档解析、页面绘制及跳页准备采用 30 秒超时。失败通过 `error` 报告;网络恢复后可调用 `reload()`,跳页准备超时也可重新跳转。
|
|
744
|
-
- 浏览器缺少 PDF.js 现代构建使用的 `Promise.withResolvers`、`Promise.try`、`URL.parse` 或 Base64 字节数组 API 时,自动使用 `legacy` 构建。PDF.js 官方目前将 `legacy` 浏览器支持范围列为 Chrome 125 及以上;更旧的内核仍建议升级浏览器。
|
|
745
|
-
- 支持服务端导入和 SSR 初始占位渲染,PDF 内容在客户端挂载后加载。发布包新增 PDF.js 延迟模块,部署时应保留整个 `dist/`,不要只复制主 JS 文件。
|
|
746
|
-
- 使用本地维护的 StPageFlip 2.0.7 生命周期修补版本,卸载/重载时取消动画帧、初始化及触摸延迟任务。第三方 MIT 声明随包发布在 `dist/LICENSE.page-flip`。
|
|
747
|
-
- 这些预算限制单页和单文档资源,不是整个宿主应用的总内存保证;外部自定义插槽应自行控制同时挂载的阅读器和 Canvas 数量。
|
|
748
|
-
|
|
749
|
-
## License
|
|
750
|
-
|
|
751
|
-
MIT
|
|
117
|
+
| [组件 API](docs/guide/api.md) | Props、事件、实例方法及自适应容器参数 |
|
|
118
|
+
| [外部容器、缩放与全屏](docs/guide/layout-and-zoom.md) | 容器布局、放大缩小、鼠标拖动平移和全屏 |
|
|
119
|
+
| [PDF 加载、资源与兼容性](docs/guide/pdf-loading.md) | HTTP Range、CORS、Worker 和运行时资源管理 |
|
|
120
|
+
| [插槽与缩略图](docs/guide/slots-and-thumbnails.md) | 插槽 API、覆盖式缩略图、侧栏布局和选中联动 |
|
|
121
|
+
| [重点标记与锚点批注](docs/guide/annotations.md) | 标注接入、控制器、按页加载、单条保存和工具栏 |
|
|
122
|
+
| [笔记内容与外部编辑器](docs/guide/note-content.md) | 图形与正文分离、富文本和媒体内容的编辑与存储 |
|
|
123
|
+
| [标注笔记点击事件](docs/guide/annotation-click.md) | 仅返回 ID 的点击事件、触发条件和详情查询代码 |
|
|
124
|
+
| [dev/demo 标注笔记操作流程](docs/guide/demo-annotation-flow.md) | 流程图、方法参数、调用链、保存与失败处理 |
|
|
125
|
+
| [常见问题与迁移](docs/guide/faq.md) | 入口解析、布局、缩略图、外部 Worker 和旧接口迁移 |
|
|
126
|
+
| [开发说明](docs/guide/development.md) | 项目组织、开发与验证命令、许可证 |
|