@jc-times/business-ui 0.2.55 → 0.3.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.
@@ -1,270 +1,270 @@
1
- # 文档与图片查看器 V2
2
-
3
- `document-viewer-v2` 统一提供新版 PDF 查看与 V1 兼容能力。URL/Buffer 使用 EmbedPDF 2.15.0、PDFium/WASM 和页面虚拟化;图片使用浏览器原生图片渲染;已有 PDF.js 对象及其业务组合继续使用兼容渲染路径。PDFium 按需加载,图片和旧对象路径不会初始化它。
4
-
5
- > PDFium V2 于 0.2.30 发布,图片、V1 参数兼容及 Headless 原生叠层于 0.2.32 发布;0.2.33 增加 Headless 搜索、文本选择与双指缩放。消费前核对安装包类型和实际发布版本。
6
-
7
- ## 如何选择入口
8
-
9
- | 场景 | 入口 | 原因 |
10
- | --- | --- | --- |
11
- | 新的只读 PDF 预览、长文档、高分屏缩放 | `document-viewer-v2` | PDFium 渲染、页面虚拟化、按显示尺寸重绘 |
12
- | URL/Buffer 需要 React 叠层、受控页码或单页模式 | V2 的 `renderer="headless"` | EmbedPDF 原生页面层配合公共基础工具栏,不依赖 PDF.js 对象 |
13
- | 已持有 PDF.js `PDFDocumentProxy` 的存量页面 | V2 的 `kind: "pdf"` | 保留原对象及渲染契约,不自动转换为 PDFium |
14
- | 单张或多张图片附件 | V2 的 `kind: "image"` | 缩略图、翻页、缩放、旋转、全屏与加载失败反馈 |
15
- | 已深度组合旧查看器的印章、叠层或受控页码 | V2 兼容导出 | 保留原参数、底层组合组件及业务回调 |
16
-
17
- 旧入口当前继续保留,不执行删除。若未来决定移除,必须先完成 PDF.js 对象契约、图片查看、业务叠层和已上线流程的迁移;应按页面灰度切换,最后在一个明确的破坏性版本中删除旧子入口、导出、样式、测试和 `pdfjs-dist` 浏览器 peer。
18
-
19
- ## 安装与导入
20
-
21
- 使用 URL/Buffer 的应用需要安装匹配的可选 peer(图片渲染不使用该引擎,但构建工具仍可能要求解析此可选动态依赖):
22
-
23
- ```sh
24
- pnpm add @embedpdf/react-pdf-viewer@2.15.0
25
- ```
26
-
27
- ```tsx
28
- import {
29
- DocumentViewerV2,
30
- DOCUMENT_VIEWER_V2_DISABLED_CATEGORIES,
31
- } from "@jc-times/business-ui/document-viewer-v2";
32
- import "@jc-times/business-ui/styles.css";
33
- ```
34
-
35
- 消费项目应固定公共包和 EmbedPDF 的精确版本并提交锁文件。父容器必须有确定高度;组件自身高度为 `100%`,默认最小高度是桌面 360px、窄屏 300px。
36
-
37
- ## URL 与 ArrayBuffer
38
-
39
- URL 适合已有同源鉴权、临时签名地址或能安全附加请求参数的页面:
40
-
41
- ```tsx
42
- import { useMemo } from "react";
43
- import { DocumentViewerV2 } from "@jc-times/business-ui/document-viewer-v2";
44
-
45
- export function ContractPreview({ fileVersion, pdfUrl, token }: Props) {
46
- const source = useMemo(() => ({
47
- id: fileVersion,
48
- kind: "url" as const,
49
- url: pdfUrl,
50
- mode: "range-request" as const,
51
- requestOptions: {
52
- headers: { Authorization: `Bearer ${token}` },
53
- },
54
- }), [fileVersion, pdfUrl, token]);
55
-
56
- return (
57
- <div style={{ height: "80dvh" }}>
58
- <DocumentViewerV2 source={source} />
59
- </div>
60
- );
61
- }
62
- ```
63
-
64
- 本地文件或应用已完成鉴权下载的内容使用 `ArrayBuffer`:
65
-
66
- ```tsx
67
- <div style={{ height: "80dvh" }}>
68
- <DocumentViewerV2
69
- source={{
70
- id: fileVersion,
71
- kind: "buffer",
72
- buffer: pdfBuffer,
73
- name: "合同.pdf",
74
- }}
75
- />
76
- </div>
77
- ```
78
-
79
- `source.id` 是查看会话标识。URL、buffer 或其他影响初始化的内容变化时必须更新它,组件会卸载旧引擎并建立新会话。建议稳定 `source` 和 `config` 对象引用,避免父组件的无关重绘反复生成配置。
80
-
81
- ## 公共 API
82
-
83
- | 参数 | 类型/默认值 | 说明 |
84
- | --- | --- | --- |
85
- | `source` | `url / buffer / pdf / image` 联合类型 | 必填;前两类用 PDFium,后两类完整兼容 V1 |
86
- | `renderer` | `"viewer"`(默认)/ `"headless"` | 仅 URL/Buffer;成品 UI 与原生页面组合模式 |
87
- | `config` | `DocumentViewerV2Config` | 仅成品 UI;透传 EmbedPDF 配置,但初始文档由 `source` 统一管理 |
1
+ # 文档与图片查看器 V2
2
+
3
+ `document-viewer-v2` 统一提供新版 PDF 查看与 V1 兼容能力。URL/Buffer 使用 EmbedPDF 2.15.0、PDFium/WASM 和页面虚拟化;图片使用浏览器原生图片渲染;已有 PDF.js 对象及其业务组合继续使用兼容渲染路径。PDFium 按需加载,图片和旧对象路径不会初始化它。
4
+
5
+ > PDFium V2 于 0.2.30 发布,图片、V1 参数兼容及 Headless 原生叠层于 0.2.32 发布;0.2.33 增加 Headless 搜索、文本选择与双指缩放。消费前核对安装包类型和实际发布版本。
6
+
7
+ ## 如何选择入口
8
+
9
+ | 场景 | 入口 | 原因 |
10
+ | --- | --- | --- |
11
+ | 新的只读 PDF 预览、长文档、高分屏缩放 | `document-viewer-v2` | PDFium 渲染、页面虚拟化、按显示尺寸重绘 |
12
+ | URL/Buffer 需要 React 叠层、受控页码或单页模式 | V2 的 `renderer="headless"` | EmbedPDF 原生页面层配合公共基础工具栏,不依赖 PDF.js 对象 |
13
+ | 已持有 PDF.js `PDFDocumentProxy` 的存量页面 | V2 的 `kind: "pdf"` | 保留原对象及渲染契约,不自动转换为 PDFium |
14
+ | 单张或多张图片附件 | V2 的 `kind: "image"` | 缩略图、翻页、缩放、旋转、全屏与加载失败反馈 |
15
+ | 已深度组合旧查看器的印章、叠层或受控页码 | V2 兼容导出 | 保留原参数、底层组合组件及业务回调 |
16
+
17
+ 旧入口当前继续保留,不执行删除。若未来决定移除,必须先完成 PDF.js 对象契约、图片查看、业务叠层和已上线流程的迁移;应按页面灰度切换,最后在一个明确的破坏性版本中删除旧子入口、导出、样式、测试和 `pdfjs-dist` 浏览器 peer。
18
+
19
+ ## 安装与导入
20
+
21
+ 使用 URL/Buffer 的应用需要安装匹配的可选 peer(图片渲染不使用该引擎,但构建工具仍可能要求解析此可选动态依赖):
22
+
23
+ ```sh
24
+ pnpm add @embedpdf/react-pdf-viewer@2.15.0
25
+ ```
26
+
27
+ ```tsx
28
+ import {
29
+ DocumentViewerV2,
30
+ DOCUMENT_VIEWER_V2_DISABLED_CATEGORIES,
31
+ } from "@jc-times/business-ui/document-viewer-v2";
32
+ import "@jc-times/business-ui/styles.css";
33
+ ```
34
+
35
+ 消费项目应固定公共包和 EmbedPDF 的精确版本并提交锁文件。父容器必须有确定高度;组件自身高度为 `100%`,默认最小高度是桌面 360px、窄屏 300px。
36
+
37
+ ## URL 与 ArrayBuffer
38
+
39
+ URL 适合已有同源鉴权、临时签名地址或能安全附加请求参数的页面:
40
+
41
+ ```tsx
42
+ import { useMemo } from "react";
43
+ import { DocumentViewerV2 } from "@jc-times/business-ui/document-viewer-v2";
44
+
45
+ export function ContractPreview({ fileVersion, pdfUrl, token }: Props) {
46
+ const source = useMemo(() => ({
47
+ id: fileVersion,
48
+ kind: "url" as const,
49
+ url: pdfUrl,
50
+ mode: "range-request" as const,
51
+ requestOptions: {
52
+ headers: { Authorization: `Bearer ${token}` },
53
+ },
54
+ }), [fileVersion, pdfUrl, token]);
55
+
56
+ return (
57
+ <div style={{ height: "80dvh" }}>
58
+ <DocumentViewerV2 source={source} />
59
+ </div>
60
+ );
61
+ }
62
+ ```
63
+
64
+ 本地文件或应用已完成鉴权下载的内容使用 `ArrayBuffer`:
65
+
66
+ ```tsx
67
+ <div style={{ height: "80dvh" }}>
68
+ <DocumentViewerV2
69
+ source={{
70
+ id: fileVersion,
71
+ kind: "buffer",
72
+ buffer: pdfBuffer,
73
+ name: "合同.pdf",
74
+ }}
75
+ />
76
+ </div>
77
+ ```
78
+
79
+ `source.id` 是查看会话标识。URL、buffer 或其他影响初始化的内容变化时必须更新它,组件会卸载旧引擎并建立新会话。建议稳定 `source` 和 `config` 对象引用,避免父组件的无关重绘反复生成配置。
80
+
81
+ ## 公共 API
82
+
83
+ | 参数 | 类型/默认值 | 说明 |
84
+ | --- | --- | --- |
85
+ | `source` | `url / buffer / pdf / image` 联合类型 | 必填;前两类用 PDFium,后两类完整兼容 V1 |
86
+ | `renderer` | `"viewer"`(默认)/ `"headless"` | 仅 URL/Buffer;成品 UI 与原生页面组合模式 |
87
+ | `config` | `DocumentViewerV2Config` | 仅成品 UI;透传 EmbedPDF 配置,但初始文档由 `source` 统一管理 |
88
88
  | `className` / `style` | 常规容器属性 | 作用于外层 `<section>` |
89
89
  | `layoutMode` | `standalone`(默认)/ `fill` | `fill` 用于已有明确高度的弹窗、抽屉或 flex/grid 容器,公共外壳填满父级并取消自身最小高度,避免撑破容器 |
90
- | `ariaLabel` | `"PDF 文档查看器"` | 查看区域的可访问名称 |
91
- | `onInit` | EmbedPDF 初始化回调 | 仅成品 UI;公共工具栏样式注入后调用 |
92
- | `onReady` | EmbedPDF 就绪回调 | 仅 URL/Buffer;不等同于业务文件已获授权 |
93
-
94
- 成品 UI 默认配置:
95
-
96
- - 使用 Web Worker,单文档会话,隐藏多文档标签栏。
97
- - 默认语言为简体中文,UI 字体使用系统字体,禁用外部字体回退请求。
98
- - 默认关闭批注、插入、表单编辑和涂黑分类。
99
- - 打印、导出及 PDF 自身权限仍由消费项目决定,隐藏按钮不能代替服务端授权。
100
- - 消费方配置与公共默认主题进行合并;显式传入的配置优先。
101
-
102
- 例如同时禁用打印:
103
-
104
- ```tsx
105
- <DocumentViewerV2
106
- source={source}
107
- config={{
108
- disabledCategories: [
109
- ...DOCUMENT_VIEWER_V2_DISABLED_CATEGORIES,
110
- "document-print",
111
- ],
112
- }}
113
- />
114
- ```
115
-
116
- ## 工具栏与主题
117
-
118
- 默认 `renderer="viewer"` 保留 EmbedPDF 的完整原生响应式工具栏,包括文件菜单、侧栏、页面设置、缩放、拖动/光标、搜索和可用的业务入口。Headless 模式的工具栏范围见下一节。
119
-
120
- 公共包装只增加视觉层,不改变原生操作逻辑:
121
-
122
- - 桌面工具栏最小高度 52px,图标按钮 36px;窄屏工具栏最小高度 56px,按钮 40px。
123
- - 圆角、文字、边框、品牌选中态、悬停、焦点和分隔线使用 `--ui-*` 主题令牌。
124
- - 缩放百分比、下拉和加减按钮形成紧凑组合控件,避免零散图标挤压。
125
- - 样式在 EmbedPDF 的开放 Shadow DOM 中幂等注入,并继续转发消费方 `onInit`。
126
-
127
- 消费方仍可通过 `config.ui.schema` 完整替换工具栏,但这会改变内部结构;自定义 schema 必须自行验证公共视觉层、响应式隐藏规则、键盘操作和后续 EmbedPDF 升级兼容性。
128
-
129
- ## Headless 原生页面叠层
130
-
131
- 需要业务印章、定位标记、受控页码或单页显示时,显式选择 `renderer="headless"`。它通过 EmbedPDF 的 `Viewport`、`Scroller`、`RenderLayer`、`Rotate` 和原生缩略图插件渲染 URL/Buffer,仍使用 PDFium;React 叠层直接组合在每页上。
132
-
133
- 从 0.2.45 起,Headless 与 PdfiumViewer 的正文、缩略图默认渲染源 PDF 已有批注(例如 Stamp 印章、FreeText 盖章提示)。这只控制显示,不开放编辑或修改文件;源 PDF 自身的隐藏标志仍由引擎遵守。消费方无需重新下载或自行重建签章叠层。
134
-
135
- ```tsx
136
- <DocumentViewerV2
137
- renderer="headless"
138
- source={{ id: fileVersion, kind: 'buffer', name: '合同.pdf', buffer: pdfBuffer }}
139
- currentPage={page}
140
- onCurrentPageChange={setPage}
141
- pageMode="single"
142
- title="合同签署"
143
- toolbar={<BusinessActions />}
144
- footer={<BusinessStatus />}
145
- renderPageOverlay={({ page, width, height }) => (
146
- <button style={{ position: 'absolute', left: width - 120, top: height - 80,
147
- width: 100, height: 40, pointerEvents: 'auto' }}
148
- onClick={() => openStampDialog(page)}>盖章位置</button>
149
- )}
150
- />
151
- ```
152
-
153
- | 参数 | 含义 |
154
- | --- | --- |
155
- | `currentPage` / `onCurrentPageChange` | 从 1 开始;可受控或省略后内部管理。手动滚动、缩略图及翻页按钮报告页码;外部跳页不会被布局重算覆盖 |
156
- | `pageMode` | 默认 `continuous` 使用原生页面虚拟化;`single` 只挂载选中的正文页 |
90
+ | `ariaLabel` | `"PDF 文档查看器"` | 查看区域的可访问名称 |
91
+ | `onInit` | EmbedPDF 初始化回调 | 仅成品 UI;公共工具栏样式注入后调用 |
92
+ | `onReady` | EmbedPDF 就绪回调 | 仅 URL/Buffer;不等同于业务文件已获授权 |
93
+
94
+ 成品 UI 默认配置:
95
+
96
+ - 使用 Web Worker,单文档会话,隐藏多文档标签栏。
97
+ - 默认语言为简体中文,UI 字体使用系统字体,禁用外部字体回退请求。
98
+ - 默认关闭批注、插入、表单编辑和涂黑分类。
99
+ - 打印、导出及 PDF 自身权限仍由消费项目决定,隐藏按钮不能代替服务端授权。
100
+ - 消费方配置与公共默认主题进行合并;显式传入的配置优先。
101
+
102
+ 例如同时禁用打印:
103
+
104
+ ```tsx
105
+ <DocumentViewerV2
106
+ source={source}
107
+ config={{
108
+ disabledCategories: [
109
+ ...DOCUMENT_VIEWER_V2_DISABLED_CATEGORIES,
110
+ "document-print",
111
+ ],
112
+ }}
113
+ />
114
+ ```
115
+
116
+ ## 工具栏与主题
117
+
118
+ 默认 `renderer="viewer"` 保留 EmbedPDF 的完整原生响应式工具栏,包括文件菜单、侧栏、页面设置、缩放、拖动/光标、搜索和可用的业务入口。Headless 模式的工具栏范围见下一节。
119
+
120
+ 公共包装只增加视觉层,不改变原生操作逻辑:
121
+
122
+ - 桌面工具栏最小高度 52px,图标按钮 36px;窄屏工具栏最小高度 56px,按钮 40px。
123
+ - 圆角、文字、边框、品牌选中态、悬停、焦点和分隔线使用 `--ui-*` 主题令牌。
124
+ - 缩放百分比、下拉和加减按钮形成紧凑组合控件,避免零散图标挤压。
125
+ - 样式在 EmbedPDF 的开放 Shadow DOM 中幂等注入,并继续转发消费方 `onInit`。
126
+
127
+ 消费方仍可通过 `config.ui.schema` 完整替换工具栏,但这会改变内部结构;自定义 schema 必须自行验证公共视觉层、响应式隐藏规则、键盘操作和后续 EmbedPDF 升级兼容性。
128
+
129
+ ## Headless 原生页面叠层
130
+
131
+ 需要业务印章、定位标记、受控页码或单页显示时,显式选择 `renderer="headless"`。它通过 EmbedPDF 的 `Viewport`、`Scroller`、`RenderLayer`、`Rotate` 和原生缩略图插件渲染 URL/Buffer,仍使用 PDFium;React 叠层直接组合在每页上。
132
+
133
+ 从 0.2.45 起,Headless 与 PdfiumViewer 的正文、缩略图默认渲染源 PDF 已有批注(例如 Stamp 印章、FreeText 盖章提示)。这只控制显示,不开放编辑或修改文件;源 PDF 自身的隐藏标志仍由引擎遵守。消费方无需重新下载或自行重建签章叠层。
134
+
135
+ ```tsx
136
+ <DocumentViewerV2
137
+ renderer="headless"
138
+ source={{ id: fileVersion, kind: 'buffer', name: '合同.pdf', buffer: pdfBuffer }}
139
+ currentPage={page}
140
+ onCurrentPageChange={setPage}
141
+ pageMode="single"
142
+ title="合同签署"
143
+ toolbar={<BusinessActions />}
144
+ footer={<BusinessStatus />}
145
+ renderPageOverlay={({ page, width, height }) => (
146
+ <button style={{ position: 'absolute', left: width - 120, top: height - 80,
147
+ width: 100, height: 40, pointerEvents: 'auto' }}
148
+ onClick={() => openStampDialog(page)}>盖章位置</button>
149
+ )}
150
+ />
151
+ ```
152
+
153
+ | 参数 | 含义 |
154
+ | --- | --- |
155
+ | `currentPage` / `onCurrentPageChange` | 从 1 开始;可受控或省略后内部管理。手动滚动、缩略图及翻页按钮报告页码;外部跳页不会被布局重算覆盖 |
156
+ | `pageMode` | 默认 `continuous` 使用原生页面虚拟化;`single` 只挂载选中的正文页 |
157
157
  | `mobileMode` | 默认 `standard`;`continuous-reader` 在窄屏/粗指针设备收起缩略图与分页底栏、压缩工具栏并把单指手势交给连续滚动区域,同时将请求的单页模式转为连续模式并关闭文字选择。仅用于连续只读场景 |
158
158
  | `searchMode` | 默认 `expanded`;`collapsible` 将搜索面板改为公共搜索开关,文档切换或收起时清理旧搜索。`showToolbar=false` 时仍显示紧凑的公共搜索入口;配合移动连续阅读模式时仅显示搜索图标 |
159
- | `renderPageOverlay(context)` | 追加 React 内容;context 包含 `documentId`、从 1 开始的 `page`、从 0 开始的 `pageIndex`、原始 `width/height`、`zoom`、角度制 `rotation` |
160
- | `renderPage({ page, zoom, rotation })` | 与 V1 一样替换整个正文页。只添加印章或标记时应使用 `renderPageOverlay`,避免替换 PDF 位图 |
161
- | `engineOptions` | `usePdfiumEngine` 的选项,例如 `wasmUrl`;默认 Worker 开启、外部字体回退关闭。改变初始化选项时更新 `source.id` |
162
- | `onReady(registry)` | 插件初始化回调;不保证文档已加载,不传成品 UI 的 `config/onInit` |
163
-
164
- 叠层坐标以**未缩放、未旋转页面的左上角**为原点;`width/height` 是页面尺寸,不是视口 CSS 尺寸。组件统一应用缩放和 PDF 内置旋转加用户旋转,业务不要再次乘 `zoom` 或旋转整个叠层。业务若使用 PDF 底部原点坐标,应先转换成左上原点。叠层容器不拦截页面事件;可交互元素自行设置 `pointerEvents: 'auto'` 并提供键盘可访问控件。
165
-
159
+ | `renderPageOverlay(context)` | 追加 React 内容;context 包含 `documentId`、从 1 开始的 `page`、从 0 开始的 `pageIndex`、原始 `width/height`、`zoom`、角度制 `rotation` |
160
+ | `renderPage({ page, zoom, rotation })` | 与 V1 一样替换整个正文页。只添加印章或标记时应使用 `renderPageOverlay`,避免替换 PDF 位图 |
161
+ | `engineOptions` | `usePdfiumEngine` 的选项,例如 `wasmUrl`;默认 Worker 开启、外部字体回退关闭。改变初始化选项时更新 `source.id` |
162
+ | `onReady(registry)` | 插件初始化回调;不保证文档已加载,不传成品 UI 的 `config/onInit` |
163
+
164
+ 叠层坐标以**未缩放、未旋转页面的左上角**为原点;`width/height` 是页面尺寸,不是视口 CSS 尺寸。组件统一应用缩放和 PDF 内置旋转加用户旋转,业务不要再次乘 `zoom` 或旋转整个叠层。业务若使用 PDF 底部原点坐标,应先转换成左上原点。叠层容器不拦截页面事件;可交互元素自行设置 `pointerEvents: 'auto'` 并提供键盘可访问控件。
165
+
166
166
  Headless 提供缩略图、翻页、50%–300% 缩放、90° 旋转、全屏以及 `title/toolbar/footer` 插槽。0.2.33 新增 features.search/textSelection/pinchZoom(默认开启),提供文本搜索、选择和双指缩放;打印、批注编辑仍由成品模式及消费方权限决定。搜索没有 OCR 能力。交互式业务控件标记 data-pdf-business-control,避免正文选择截获拖动。zoom/rotation 可受控,onZoomChange/onRotationChange 报告变化,showToolbar=false 可使用外部业务工具栏。`mobileMode="continuous-reader"` 只用于窄屏只读连续阅读,不应用于定位或编辑;公共组件会在匹配设备上统一连续页模式、手势和文字选择策略。`searchMode="collapsible"` 由公共查看器管理搜索入口和资源切换重置。叠层是业务 UI,不会自动写入或导出到 PDF 文件。
167
-
168
- `source.kind/id` 变化销毁旧会话,重置内部缩放、旋转与非受控页码;受控 `currentPage` 由调用方重置。订阅和引擎随卸载清理。原生模块按选择模式动态加载,不进入公共主入口;新增插件是公共包的精确版本依赖,不代表会进入未使用查看器的首屏。
169
-
170
- ## 桌面与移动端行为(成品 UI)
171
-
172
- EmbedPDF 按查看器容器宽度响应,而不是按浏览器是否为手机判断。因此窄的桌面侧栏或内置浏览器也会进入移动布局;单纯放大浏览器窗口,只有在查看器实际容器跨过对应断点后才会切换桌面布局。
173
-
174
- 移动端双指缩放和工具栏缩放按钮都改变文档缩放状态,但交互不完全相同:
175
-
176
- - 双指缩放是连续手势,适合快速调整,并以手指中心附近作为交互参照。
177
- - 加减按钮是离散档位,适合精确、可重复和无手势场景。
178
- - 两者最终都应触发按目标显示尺寸重新渲染;手势中的短暂 CSS 变换不代表最终页面会一直模糊。
179
-
180
- 当前自动化覆盖移动视口、工具栏无横向溢出、按钮缩放和页面位图像素比例;双指手势、移动浏览器缩放冲突和软键盘不由桌面模拟完整代表,正式放量前仍要做真实手机验收。
181
-
182
- ## 性能、清晰度与网络限制
183
-
184
- V2 相比旧连续 PDF 实现的主要收益:
185
-
186
- - 独立子入口和异步引擎,不把 EmbedPDF/PDFium 打进公共主模块。
187
- - 页面虚拟化,避免滚动时持续测量和更新全部页面。
188
- - 根据显示尺寸及设备像素比生成页面位图;放大稳定后重新渲染,减少旧画布被永久拉伸造成的模糊。
189
- - 引擎会取消或替换不再需要的页面工作,降低长文档滚动期间的无效渲染。
190
-
191
- 固定的 EmbedPDF/PDFium 2.15.0 虽接受 `mode: "range-request"`,其当前 URL 编排器仍执行完整 GET。因此该配置不能证明已经实现 HTTP 分段下载,也不能据此承诺大文件首开流量或峰值内存。若这两项是硬指标,必须使用真实大合同记录首屏时间、下载量、滚动帧率和峰值内存后再扩大灰度。
192
-
193
- 清晰度验收不要只看 CSS 尺寸。至少检查可见页面位图的自然像素与显示像素之比接近当前 `devicePixelRatio`,并在 100%、200% 及常用高分屏下观察文字边缘。浏览器自身页面缩放、操作系统缩放和 PDF 内容质量也会影响结果。
194
-
195
- ## 鉴权、安全与生命周期
196
-
197
- - 优先使用同源 Cookie、短时签名 URL 或受控请求头;不要把长期 Token 写入日志、文档或可公开缓存。
198
- - CORS、Cookie、Range、缓存与下载权限由文件服务配置,查看器不能绕过服务端策略。
199
- - 隐藏打印、下载或菜单入口只是 UI 限制,敏感文件仍必须由服务端逐请求鉴权。
200
- - URL、身份或权限上下文改变时更新 `source.id`,防止旧会话继续显示上一份文档。
201
- - 使用 ArrayBuffer 时,获取、取消、错误提示和敏感内存生命周期由消费项目管理。
202
-
203
- ## V1 完整兼容与图片示例
204
-
205
- 原有调用可以只更换导入路径:
206
-
207
- ```tsx
208
- import {
209
- DocumentViewer, DocumentViewerViewport, PdfThumbnailRail,
210
- PdfToolbar, usePdfCanvas, usePdfViewport,
211
- } from '@jc-times/business-ui/document-viewer-v2';
212
-
213
- <DocumentViewer title="附件" source={{ id: fileVersion, kind: 'image', images: [
214
- { src: imageUrl, alt: '设备照片' },
215
- ] }} />
216
-
217
- <DocumentViewer source={{ id: fileVersion, kind: 'pdf', document: pdf }}
218
- currentPage={page} onCurrentPageChange={setPage} pageMode="single"
219
- toolbar={<BusinessActions />} footer={<BusinessStatus />}
220
- renderPage={({ page, zoom, rotation }) => renderBusinessPage(page, zoom, rotation)} />
221
- ```
222
-
223
- - `DocumentViewer` 是 `DocumentViewerV2` 的同名兼容别名;保留 V1 的 `DocumentViewerProps`、`DocumentViewerSource` 类型导出。也可直接使用 `DocumentViewerV2` 名称。
224
- - `kind: "pdf" / "image"` 保留全部 V1 参数:`title`、`toolbar`、`footer`、`currentPage`、`onCurrentPageChange`、`pageMode`、`renderPage`、`className`,另支持 `style` 和 `ariaLabel`。受控参数、页面替换/叠层的含义与 V1 一致。
225
- - V1 全部底层组合导出保留:`DocumentViewerViewport` 及其 props、`ContinuousPdfViewer`、`PdfThumbnailRail`、`PageThumbnail`、`PdfWorkspace`、`PdfCanvasPage`、`PdfToolbar`、`usePdfCanvas`、`usePdfViewport`。这些仍遵循原 PDF.js 对象契约。
226
- - 图片支持浏览器可解码的格式,传 `images: []` 显示空态;错误图片有失败提示。`src` 可以是业务 URL、data URL 或调用方持有的 blob URL;对象 URL 的创建与释放由调用方负责。
227
- - `source.id` 改变时重置翻页、缩放和失败状态;切换 source kind 也建立新会话。URL/Buffer 卸载后不继续显示旧 PDFium 页面。
228
-
229
- **兼容入口与引擎迁移是两件事。** 保留 `PDFDocumentProxy` 就仍需要 PDF.js,不会把已有对象自动转换成 PDFium。URL/Buffer 的默认成品模式支持外层插槽与插件配置;传受控页码、`pageMode`、`renderPage` 或 `renderPageOverlay` 时选择 `renderer="headless"`,类型会区分两种模式。
230
-
231
- ## 消费迁移与旧入口
232
-
233
- EmbedPDF 原生支持页面叠加:[Headless 入门](https://www.embedpdf.com/docs/react/headless/getting-started) 提供 `Scroller.renderPage` 与 `RenderLayer`;[Scroll 插件](https://www.embedpdf.com/docs/react/viewer/plugins/plugin-scroll) 提供跳页与页码事件。本库已在候选版封装 Headless 原生模式,默认成品模式和旧对象兼容模式同时保留。
234
-
235
- 1. 先升级到正式发布且包含兼容能力的版本,只改导入路径,保留已有 source、业务参数及底层组合。
236
- 2. 已有 PDF.js 消费方同时遵守当前包 PDF.js peer 与 worker 版本要求;参见 [旧查看器依赖迁移](document-viewer.md)。
237
- 3. 在消费仓库验证图片、受控页码、连续/单页、业务印章与叠层、权限、桌面/手机。
238
- 4. 需要切换 PDFium 的页面再独立改成 URL/Buffer,并迁移插件交互;保留鉴权、错误和敏感内存生命周期。
239
- 5. 用真实长文档和真实手机检查首开、内存、手势与高分屏清晰度,最后升级精确 registry 版本并发布消费服务。
240
-
241
- 旧 `document-viewer` 入口继续保留。本次不删除 V1 API、图片能力或 PDF.js peer;即使消费导入都改为 V2,仍使用 `kind: "pdf"` 或旧底层组件时也不能删除 PDF.js。删除旧引擎须另行完成所有对象和叠层迁移,不能仅删除 exports。
242
-
243
- ## 当前验证与未完成项
244
-
245
- 本地候选通过兼容参数、受控分页、单页叠层、插槽、图片错误/空态/重置单测,以及桌面/手机浏览器回归:V1/V2 相同的真实 PDF 翻页、缩略图、缩放旋转与图片比例测试;V2 图片全屏、图片/PDFium 切换;图片阶段无 PDFium/WASM 网络请求。原 PDFium 高清渲染与工具栏回归保持通过。
246
-
247
- 2026-09-12 原生模式验证:138 项单测及类型、库/矩阵构建通过,14 项查看器桌面/手机回归通过。新增覆盖受控初始页、外部跳页、手动滚动、叠层点击及缩放/旋转尺寸、单页互切、全屏、文档更换重置、失败后恢复,并检查 390×844、320×480、768×1024、1440×900、844×390 五档视口。Headless 声明、异步模块及文档进入本地 pack;独立旧手机翻页用例复跑三次及最终整组通过(并行构建期间曾有一次旧用例页码超时)。
248
-
249
- 尚未发布或升级消费服务;真实长合同内存、真实设备多指手势、业务权限和印章等端到端验收仍由各消费服务在升级时完成。
250
-
251
- 0.2.33 验证:类型检查、138 单测、构建与按需门禁通过;V2 桌面/手机 10 项浏览器覆盖搜索、正文选择、模拟双指缩放、叠层、旋转、页模式及资源切换。双指证据为 Chromium CDP 模拟,不代表物理手机验收。
252
-
253
-
254
- ### PDFium 专用构建入口(0.2.34)
255
-
256
- 只使用原生 PDF 叠层的页面可从 `@jc-times/business-ui/pdfium-viewer` 导入 `PdfiumViewer`,参数与 Headless 模式一致,但省略 `renderer`。该入口不引入 PDF.js 兼容渲染或完整查看器外壳,适用于独立公开查看页面;引擎仍按需加载。通过 `engineOptions.wasmUrl` 提供本地资源的绝对 URL,保证 Blob worker 可解析;同一 `source.kind/id` 维持会话,文件版本改变时必须改变 `id`。
257
-
258
- `initialZoom="fit-width"` 可用于无受控 zoom 的只读查看入口;官方缩放插件保持适配页面宽度,现有默认 100% 不变。
259
-
260
- 0.2.38:Headless 支持正文选区 Ctrl/Cmd+C,调用官方 selection.copyToClipboard 并遵守 CopyContents 权限;输入框与可编辑内容仍保留原生复制。
261
-
262
- 0.2.39:原生 PDF 页面图片设为 draggable=false,防止浏览器图片拖放抢占官方文本选择手势。
263
-
264
- 0.2.40 修复公共 PDF 全屏状态:Escape 调用浏览器 exitFullscreen,fullscreenchange 同步工具栏;CSS 全屏回退仍在 Escape 时退出。浏览器拒绝退出时保留真实全屏状态和退出按钮。
265
-
266
- ### PDFium 0.2.44
267
- PdfiumViewer 支持 defaultRailCollapsed:传 true 默认收起页面侧栏,用户仍可打开。省略保留按视口初始化的行为。双指缩放逐帧提交到 PDFium 的范围和精度约束,松手保持最后比例;Ctrl/Meta+滚轮继续使用官方手势。
268
-
269
- ### PDFium 0.2.46 — 2026-09-14
270
- 保留官方 ZoomGestureWrapper 的触屏和 Ctrl/Meta+滚轮 CSS 位图预览,结束后只提交一次引擎缩放。移除0.2.44的逐帧触屏实现。修复可见页码回报被当作外部跳页命令导致的缩放后跳页。官方2.15.0预览缺少边界参数,通过精确 pnpm patch 补齐 minZoom/maxZoom 并对齐提交精度;消费方无需自行打补丁,库构建仅内联该 React 手势入口,PDFium/插件状态仍保持外部单例。补丁责任人为公共文档组件维护者,2026-10-14复核;上游具备等价能力并通过同组回归后删除补丁。
167
+
168
+ `source.kind/id` 变化销毁旧会话,重置内部缩放、旋转与非受控页码;受控 `currentPage` 由调用方重置。订阅和引擎随卸载清理。原生模块按选择模式动态加载,不进入公共主入口;新增插件是公共包的精确版本依赖,不代表会进入未使用查看器的首屏。
169
+
170
+ ## 桌面与移动端行为(成品 UI)
171
+
172
+ EmbedPDF 按查看器容器宽度响应,而不是按浏览器是否为手机判断。因此窄的桌面侧栏或内置浏览器也会进入移动布局;单纯放大浏览器窗口,只有在查看器实际容器跨过对应断点后才会切换桌面布局。
173
+
174
+ 移动端双指缩放和工具栏缩放按钮都改变文档缩放状态,但交互不完全相同:
175
+
176
+ - 双指缩放是连续手势,适合快速调整,并以手指中心附近作为交互参照。
177
+ - 加减按钮是离散档位,适合精确、可重复和无手势场景。
178
+ - 两者最终都应触发按目标显示尺寸重新渲染;手势中的短暂 CSS 变换不代表最终页面会一直模糊。
179
+
180
+ 当前自动化覆盖移动视口、工具栏无横向溢出、按钮缩放和页面位图像素比例;双指手势、移动浏览器缩放冲突和软键盘不由桌面模拟完整代表,正式放量前仍要做真实手机验收。
181
+
182
+ ## 性能、清晰度与网络限制
183
+
184
+ V2 相比旧连续 PDF 实现的主要收益:
185
+
186
+ - 独立子入口和异步引擎,不把 EmbedPDF/PDFium 打进公共主模块。
187
+ - 页面虚拟化,避免滚动时持续测量和更新全部页面。
188
+ - 根据显示尺寸及设备像素比生成页面位图;放大稳定后重新渲染,减少旧画布被永久拉伸造成的模糊。
189
+ - 引擎会取消或替换不再需要的页面工作,降低长文档滚动期间的无效渲染。
190
+
191
+ 固定的 EmbedPDF/PDFium 2.15.0 虽接受 `mode: "range-request"`,其当前 URL 编排器仍执行完整 GET。因此该配置不能证明已经实现 HTTP 分段下载,也不能据此承诺大文件首开流量或峰值内存。若这两项是硬指标,必须使用真实大合同记录首屏时间、下载量、滚动帧率和峰值内存后再扩大灰度。
192
+
193
+ 清晰度验收不要只看 CSS 尺寸。至少检查可见页面位图的自然像素与显示像素之比接近当前 `devicePixelRatio`,并在 100%、200% 及常用高分屏下观察文字边缘。浏览器自身页面缩放、操作系统缩放和 PDF 内容质量也会影响结果。
194
+
195
+ ## 鉴权、安全与生命周期
196
+
197
+ - 优先使用同源 Cookie、短时签名 URL 或受控请求头;不要把长期 Token 写入日志、文档或可公开缓存。
198
+ - CORS、Cookie、Range、缓存与下载权限由文件服务配置,查看器不能绕过服务端策略。
199
+ - 隐藏打印、下载或菜单入口只是 UI 限制,敏感文件仍必须由服务端逐请求鉴权。
200
+ - URL、身份或权限上下文改变时更新 `source.id`,防止旧会话继续显示上一份文档。
201
+ - 使用 ArrayBuffer 时,获取、取消、错误提示和敏感内存生命周期由消费项目管理。
202
+
203
+ ## V1 完整兼容与图片示例
204
+
205
+ 原有调用可以只更换导入路径:
206
+
207
+ ```tsx
208
+ import {
209
+ DocumentViewer, DocumentViewerViewport, PdfThumbnailRail,
210
+ PdfToolbar, usePdfCanvas, usePdfViewport,
211
+ } from '@jc-times/business-ui/document-viewer-v2';
212
+
213
+ <DocumentViewer title="附件" source={{ id: fileVersion, kind: 'image', images: [
214
+ { src: imageUrl, alt: '设备照片' },
215
+ ] }} />
216
+
217
+ <DocumentViewer source={{ id: fileVersion, kind: 'pdf', document: pdf }}
218
+ currentPage={page} onCurrentPageChange={setPage} pageMode="single"
219
+ toolbar={<BusinessActions />} footer={<BusinessStatus />}
220
+ renderPage={({ page, zoom, rotation }) => renderBusinessPage(page, zoom, rotation)} />
221
+ ```
222
+
223
+ - `DocumentViewer` 是 `DocumentViewerV2` 的同名兼容别名;保留 V1 的 `DocumentViewerProps`、`DocumentViewerSource` 类型导出。也可直接使用 `DocumentViewerV2` 名称。
224
+ - `kind: "pdf" / "image"` 保留全部 V1 参数:`title`、`toolbar`、`footer`、`currentPage`、`onCurrentPageChange`、`pageMode`、`renderPage`、`className`,另支持 `style` 和 `ariaLabel`。受控参数、页面替换/叠层的含义与 V1 一致。
225
+ - V1 全部底层组合导出保留:`DocumentViewerViewport` 及其 props、`ContinuousPdfViewer`、`PdfThumbnailRail`、`PageThumbnail`、`PdfWorkspace`、`PdfCanvasPage`、`PdfToolbar`、`usePdfCanvas`、`usePdfViewport`。这些仍遵循原 PDF.js 对象契约。
226
+ - 图片支持浏览器可解码的格式,传 `images: []` 显示空态;错误图片有失败提示。`src` 可以是业务 URL、data URL 或调用方持有的 blob URL;对象 URL 的创建与释放由调用方负责。
227
+ - `source.id` 改变时重置翻页、缩放和失败状态;切换 source kind 也建立新会话。URL/Buffer 卸载后不继续显示旧 PDFium 页面。
228
+
229
+ **兼容入口与引擎迁移是两件事。** 保留 `PDFDocumentProxy` 就仍需要 PDF.js,不会把已有对象自动转换成 PDFium。URL/Buffer 的默认成品模式支持外层插槽与插件配置;传受控页码、`pageMode`、`renderPage` 或 `renderPageOverlay` 时选择 `renderer="headless"`,类型会区分两种模式。
230
+
231
+ ## 消费迁移与旧入口
232
+
233
+ EmbedPDF 原生支持页面叠加:[Headless 入门](https://www.embedpdf.com/docs/react/headless/getting-started) 提供 `Scroller.renderPage` 与 `RenderLayer`;[Scroll 插件](https://www.embedpdf.com/docs/react/viewer/plugins/plugin-scroll) 提供跳页与页码事件。本库已在候选版封装 Headless 原生模式,默认成品模式和旧对象兼容模式同时保留。
234
+
235
+ 1. 先升级到正式发布且包含兼容能力的版本,只改导入路径,保留已有 source、业务参数及底层组合。
236
+ 2. 已有 PDF.js 消费方同时遵守当前包 PDF.js peer 与 worker 版本要求;参见 [旧查看器依赖迁移](document-viewer.md)。
237
+ 3. 在消费仓库验证图片、受控页码、连续/单页、业务印章与叠层、权限、桌面/手机。
238
+ 4. 需要切换 PDFium 的页面再独立改成 URL/Buffer,并迁移插件交互;保留鉴权、错误和敏感内存生命周期。
239
+ 5. 用真实长文档和真实手机检查首开、内存、手势与高分屏清晰度,最后升级精确 registry 版本并发布消费服务。
240
+
241
+ 旧 `document-viewer` 入口继续保留。本次不删除 V1 API、图片能力或 PDF.js peer;即使消费导入都改为 V2,仍使用 `kind: "pdf"` 或旧底层组件时也不能删除 PDF.js。删除旧引擎须另行完成所有对象和叠层迁移,不能仅删除 exports。
242
+
243
+ ## 当前验证与未完成项
244
+
245
+ 本地候选通过兼容参数、受控分页、单页叠层、插槽、图片错误/空态/重置单测,以及桌面/手机浏览器回归:V1/V2 相同的真实 PDF 翻页、缩略图、缩放旋转与图片比例测试;V2 图片全屏、图片/PDFium 切换;图片阶段无 PDFium/WASM 网络请求。原 PDFium 高清渲染与工具栏回归保持通过。
246
+
247
+ 2026-09-12 原生模式验证:138 项单测及类型、库/矩阵构建通过,14 项查看器桌面/手机回归通过。新增覆盖受控初始页、外部跳页、手动滚动、叠层点击及缩放/旋转尺寸、单页互切、全屏、文档更换重置、失败后恢复,并检查 390×844、320×480、768×1024、1440×900、844×390 五档视口。Headless 声明、异步模块及文档进入本地 pack;独立旧手机翻页用例复跑三次及最终整组通过(并行构建期间曾有一次旧用例页码超时)。
248
+
249
+ 尚未发布或升级消费服务;真实长合同内存、真实设备多指手势、业务权限和印章等端到端验收仍由各消费服务在升级时完成。
250
+
251
+ 0.2.33 验证:类型检查、138 单测、构建与按需门禁通过;V2 桌面/手机 10 项浏览器覆盖搜索、正文选择、模拟双指缩放、叠层、旋转、页模式及资源切换。双指证据为 Chromium CDP 模拟,不代表物理手机验收。
252
+
253
+
254
+ ### PDFium 专用构建入口(0.2.34)
255
+
256
+ 只使用原生 PDF 叠层的页面可从 `@jc-times/business-ui/pdfium-viewer` 导入 `PdfiumViewer`,参数与 Headless 模式一致,但省略 `renderer`。该入口不引入 PDF.js 兼容渲染或完整查看器外壳,适用于独立公开查看页面;引擎仍按需加载。通过 `engineOptions.wasmUrl` 提供本地资源的绝对 URL,保证 Blob worker 可解析;同一 `source.kind/id` 维持会话,文件版本改变时必须改变 `id`。
257
+
258
+ `initialZoom="fit-width"` 可用于无受控 zoom 的只读查看入口;官方缩放插件保持适配页面宽度,现有默认 100% 不变。
259
+
260
+ 0.2.38:Headless 支持正文选区 Ctrl/Cmd+C,调用官方 selection.copyToClipboard 并遵守 CopyContents 权限;输入框与可编辑内容仍保留原生复制。
261
+
262
+ 0.2.39:原生 PDF 页面图片设为 draggable=false,防止浏览器图片拖放抢占官方文本选择手势。
263
+
264
+ 0.2.40 修复公共 PDF 全屏状态:Escape 调用浏览器 exitFullscreen,fullscreenchange 同步工具栏;CSS 全屏回退仍在 Escape 时退出。浏览器拒绝退出时保留真实全屏状态和退出按钮。
265
+
266
+ ### PDFium 0.2.44
267
+ PdfiumViewer 支持 defaultRailCollapsed:传 true 默认收起页面侧栏,用户仍可打开。省略保留按视口初始化的行为。双指缩放逐帧提交到 PDFium 的范围和精度约束,松手保持最后比例;Ctrl/Meta+滚轮继续使用官方手势。
268
+
269
+ ### PDFium 0.2.46 — 2026-09-14
270
+ 保留官方 ZoomGestureWrapper 的触屏和 Ctrl/Meta+滚轮 CSS 位图预览,结束后只提交一次引擎缩放。移除0.2.44的逐帧触屏实现。修复可见页码回报被当作外部跳页命令导致的缩放后跳页。官方2.15.0预览缺少边界参数,通过精确 pnpm patch 补齐 minZoom/maxZoom 并对齐提交精度;消费方无需自行打补丁,库构建仅内联该 React 手势入口,PDFium/插件状态仍保持外部单例。补丁责任人为公共文档组件维护者,2026-10-14复核;上游具备等价能力并通过同组回归后删除补丁。