@jc-times/business-ui 0.2.43
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/AGENTS.md +6 -0
- package/CHANGELOG.md +267 -0
- package/README.md +55 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/bin/business-ui-gate.mjs +171 -0
- package/dist/Alert.js +47 -0
- package/dist/AsyncDirectoryPicker.js +134 -0
- package/dist/AsyncSearchPicker.js +155 -0
- package/dist/AutoComplete.js +34 -0
- package/dist/Badge.js +14 -0
- package/dist/Button.js +54 -0
- package/dist/Cascader.js +141 -0
- package/dist/Combobox.js +106 -0
- package/dist/ConfirmDialog.js +48 -0
- package/dist/DataTableHorizontalScrollbar.js +38 -0
- package/dist/DataTableIcons.js +37 -0
- package/dist/DataTableMergedRows.js +45 -0
- package/dist/DataTableTheme.js +135 -0
- package/dist/DateInput.js +223 -0
- package/dist/DropdownMenu.js +130 -0
- package/dist/EmptyState.js +27 -0
- package/dist/ExplainableButton.js +68 -0
- package/dist/FileDropzone.js +70 -0
- package/dist/FilePreparationDialog.js +33 -0
- package/dist/FloatingControls.js +120 -0
- package/dist/FormField.js +143 -0
- package/dist/MetricCard.js +38 -0
- package/dist/ModalShell.js +139 -0
- package/dist/MultiSelect.js +137 -0
- package/dist/NumericInput.js +97 -0
- package/dist/OverlayContainer.js +9 -0
- package/dist/PageHeader.js +36 -0
- package/dist/Pagination.js +115 -0
- package/dist/PreparedFileDropzone.js +30 -0
- package/dist/RecoverableAsyncState.js +35 -0
- package/dist/RovingTabs.js +54 -0
- package/dist/SectionCard.js +35 -0
- package/dist/SegmentedControl.js +46 -0
- package/dist/Select.js +73 -0
- package/dist/Skeleton.js +25 -0
- package/dist/SortableList.js +55 -0
- package/dist/StructuredAddressInput.js +157 -0
- package/dist/Toast.js +100 -0
- package/dist/ToggleSwitch.js +34 -0
- package/dist/TreeSelect.js +444 -0
- package/dist/amount-format.js +20 -0
- package/dist/business-ui.js +44 -0
- package/dist/chinese-address-regions.js +15 -0
- package/dist/classNames.js +6 -0
- package/dist/data-table.js +257 -0
- package/dist/document-render-budget.js +22 -0
- package/dist/document-viewer/ContinuousPdfViewer.js +145 -0
- package/dist/document-viewer/DocumentViewer.js +123 -0
- package/dist/document-viewer/DocumentViewerViewport.js +36 -0
- package/dist/document-viewer/ImagePage.js +39 -0
- package/dist/document-viewer/PdfCanvasPage.js +26 -0
- package/dist/document-viewer/PdfThumbnailRail.js +116 -0
- package/dist/document-viewer/PdfToolbar.js +61 -0
- package/dist/document-viewer/PdfWorkspace.js +42 -0
- package/dist/document-viewer/pdf-render-budget.js +7 -0
- package/dist/document-viewer/usePdfCanvas.js +69 -0
- package/dist/document-viewer/usePdfViewport.js +48 -0
- package/dist/document-viewer-v2/DocumentViewerV2.js +171 -0
- package/dist/document-viewer-v2/HeadlessPdfViewer.js +420 -0
- package/dist/document-viewer-v2/HeadlessSearch.js +66 -0
- package/dist/document-viewer-v2.js +13 -0
- package/dist/document-viewer.js +12 -0
- package/dist/pdfium-viewer.js +18 -0
- package/dist/styles.css +2 -0
- package/dist/types/Alert.d.ts +11 -0
- package/dist/types/AmountFormat.d.ts +15 -0
- package/dist/types/AsyncDirectoryPicker.d.ts +32 -0
- package/dist/types/AsyncSearchPicker.d.ts +26 -0
- package/dist/types/AutoComplete.d.ts +9 -0
- package/dist/types/Badge.d.ts +13 -0
- package/dist/types/Button.d.ts +36 -0
- package/dist/types/Cascader.d.ts +57 -0
- package/dist/types/ChineseAddressRegions.d.ts +4 -0
- package/dist/types/Combobox.d.ts +37 -0
- package/dist/types/ConfirmDialog.d.ts +20 -0
- package/dist/types/DataTable.d.ts +68 -0
- package/dist/types/DataTableHorizontalScrollbar.d.ts +8 -0
- package/dist/types/DataTableIcons.d.ts +19 -0
- package/dist/types/DataTableMergedRows.d.ts +11 -0
- package/dist/types/DataTableTheme.d.ts +4 -0
- package/dist/types/DateInput.d.ts +25 -0
- package/dist/types/DocumentRenderBudget.d.ts +9 -0
- package/dist/types/DocumentViewer.d.ts +11 -0
- package/dist/types/DocumentViewerV2.d.ts +10 -0
- package/dist/types/DropdownMenu.d.ts +73 -0
- package/dist/types/EmptyState.d.ts +9 -0
- package/dist/types/ExplainableButton.d.ts +7 -0
- package/dist/types/FileDropzone.d.ts +20 -0
- package/dist/types/FilePreparationDialog.d.ts +12 -0
- package/dist/types/FloatingControls.d.ts +39 -0
- package/dist/types/FormField.d.ts +35 -0
- package/dist/types/MetricCard.d.ts +17 -0
- package/dist/types/ModalShell.d.ts +33 -0
- package/dist/types/MultiSelect.d.ts +12 -0
- package/dist/types/NumericInput.d.ts +54 -0
- package/dist/types/OverlayContainer.d.ts +2 -0
- package/dist/types/PageHeader.d.ts +23 -0
- package/dist/types/Pagination.d.ts +14 -0
- package/dist/types/PdfiumViewer.d.ts +5 -0
- package/dist/types/PreparedFileDropzone.d.ts +7 -0
- package/dist/types/RecoverableAsyncState.d.ts +11 -0
- package/dist/types/RovingTabs.d.ts +21 -0
- package/dist/types/SectionCard.d.ts +18 -0
- package/dist/types/SegmentedControl.d.ts +26 -0
- package/dist/types/Select.d.ts +31 -0
- package/dist/types/Skeleton.d.ts +11 -0
- package/dist/types/SortableList.d.ts +17 -0
- package/dist/types/StructuredAddressInput.d.ts +57 -0
- package/dist/types/Toast.d.ts +22 -0
- package/dist/types/ToggleSwitch.d.ts +15 -0
- package/dist/types/TreeSelect.d.ts +58 -0
- package/dist/types/classNames.d.ts +1 -0
- package/dist/types/document-viewer/ContinuousPdfViewer.d.ts +16 -0
- package/dist/types/document-viewer/DocumentViewer.d.ts +31 -0
- package/dist/types/document-viewer/DocumentViewerViewport.d.ts +23 -0
- package/dist/types/document-viewer/ImagePage.d.ts +9 -0
- package/dist/types/document-viewer/PdfCanvasPage.d.ts +7 -0
- package/dist/types/document-viewer/PdfThumbnailRail.d.ts +12 -0
- package/dist/types/document-viewer/PdfToolbar.d.ts +13 -0
- package/dist/types/document-viewer/PdfWorkspace.d.ts +5 -0
- package/dist/types/document-viewer/pdf-render-budget.d.ts +2 -0
- package/dist/types/document-viewer/usePdfCanvas.d.ts +22 -0
- package/dist/types/document-viewer/usePdfViewport.d.ts +14 -0
- package/dist/types/document-viewer-v2/DocumentViewerV2.d.ts +50 -0
- package/dist/types/document-viewer-v2/HeadlessPdfViewer.d.ts +39 -0
- package/dist/types/document-viewer-v2/HeadlessSearch.d.ts +9 -0
- package/dist/types/index.d.ts +41 -0
- package/dist/types/useDebouncedValue.d.ts +2 -0
- package/dist/types/useFilePreparation.d.ts +28 -0
- package/dist/types/useModalFocusTrap.d.ts +3 -0
- package/dist/useDebouncedValue.js +16 -0
- package/dist/useFilePreparation.js +51 -0
- package/dist/useModalFocusTrap.js +71 -0
- package/docs/agent-guide.md +145 -0
- package/docs/api.md +220 -0
- package/docs/cascader.md +56 -0
- package/docs/component-strategy.md +94 -0
- package/docs/data-table.md +123 -0
- package/docs/dependencies.md +57 -0
- package/docs/document-viewer-v2.md +259 -0
- package/docs/document-viewer.md +28 -0
- package/docs/file-preparation.md +11 -0
- package/docs/maturity-migration.md +62 -0
- package/docs/modal-shell-migration.md +43 -0
- package/docs/mrt-poc.md +205 -0
- package/docs/release.md +32 -0
- package/docs/viewport-qa.md +99 -0
- package/licenses/rc-cascader-MIT.txt +21 -0
- package/package.json +163 -0
|
@@ -0,0 +1,259 @@
|
|
|
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
|
+
| `className` / `style` | 常规容器属性 | 作用于外层 `<section>` |
|
|
89
|
+
| `ariaLabel` | `"PDF 文档查看器"` | 查看区域的可访问名称 |
|
|
90
|
+
| `onInit` | EmbedPDF 初始化回调 | 仅成品 UI;公共工具栏样式注入后调用 |
|
|
91
|
+
| `onReady` | EmbedPDF 就绪回调 | 仅 URL/Buffer;不等同于业务文件已获授权 |
|
|
92
|
+
|
|
93
|
+
成品 UI 默认配置:
|
|
94
|
+
|
|
95
|
+
- 使用 Web Worker,单文档会话,隐藏多文档标签栏。
|
|
96
|
+
- 默认语言为简体中文,UI 字体使用系统字体,禁用外部字体回退请求。
|
|
97
|
+
- 默认关闭批注、插入、表单编辑和涂黑分类。
|
|
98
|
+
- 打印、导出及 PDF 自身权限仍由消费项目决定,隐藏按钮不能代替服务端授权。
|
|
99
|
+
- 消费方配置与公共默认主题进行合并;显式传入的配置优先。
|
|
100
|
+
|
|
101
|
+
例如同时禁用打印:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<DocumentViewerV2
|
|
105
|
+
source={source}
|
|
106
|
+
config={{
|
|
107
|
+
disabledCategories: [
|
|
108
|
+
...DOCUMENT_VIEWER_V2_DISABLED_CATEGORIES,
|
|
109
|
+
"document-print",
|
|
110
|
+
],
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 工具栏与主题
|
|
116
|
+
|
|
117
|
+
默认 `renderer="viewer"` 保留 EmbedPDF 的完整原生响应式工具栏,包括文件菜单、侧栏、页面设置、缩放、拖动/光标、搜索和可用的业务入口。Headless 模式的工具栏范围见下一节。
|
|
118
|
+
|
|
119
|
+
公共包装只增加视觉层,不改变原生操作逻辑:
|
|
120
|
+
|
|
121
|
+
- 桌面工具栏最小高度 52px,图标按钮 36px;窄屏工具栏最小高度 56px,按钮 40px。
|
|
122
|
+
- 圆角、文字、边框、品牌选中态、悬停、焦点和分隔线使用 `--ui-*` 主题令牌。
|
|
123
|
+
- 缩放百分比、下拉和加减按钮形成紧凑组合控件,避免零散图标挤压。
|
|
124
|
+
- 样式在 EmbedPDF 的开放 Shadow DOM 中幂等注入,并继续转发消费方 `onInit`。
|
|
125
|
+
|
|
126
|
+
消费方仍可通过 `config.ui.schema` 完整替换工具栏,但这会改变内部结构;自定义 schema 必须自行验证公共视觉层、响应式隐藏规则、键盘操作和后续 EmbedPDF 升级兼容性。
|
|
127
|
+
|
|
128
|
+
## Headless 原生页面叠层
|
|
129
|
+
|
|
130
|
+
需要业务印章、定位标记、受控页码或单页显示时,显式选择 `renderer="headless"`。它通过 EmbedPDF 的 `Viewport`、`Scroller`、`RenderLayer`、`Rotate` 和原生缩略图插件渲染 URL/Buffer,仍使用 PDFium;React 叠层直接组合在每页上。
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<DocumentViewerV2
|
|
134
|
+
renderer="headless"
|
|
135
|
+
source={{ id: fileVersion, kind: 'buffer', name: '合同.pdf', buffer: pdfBuffer }}
|
|
136
|
+
currentPage={page}
|
|
137
|
+
onCurrentPageChange={setPage}
|
|
138
|
+
pageMode="single"
|
|
139
|
+
title="合同签署"
|
|
140
|
+
toolbar={<BusinessActions />}
|
|
141
|
+
footer={<BusinessStatus />}
|
|
142
|
+
renderPageOverlay={({ page, width, height }) => (
|
|
143
|
+
<button style={{ position: 'absolute', left: width - 120, top: height - 80,
|
|
144
|
+
width: 100, height: 40, pointerEvents: 'auto' }}
|
|
145
|
+
onClick={() => openStampDialog(page)}>盖章位置</button>
|
|
146
|
+
)}
|
|
147
|
+
/>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| 参数 | 含义 |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `currentPage` / `onCurrentPageChange` | 从 1 开始;可受控或省略后内部管理。手动滚动、缩略图及翻页按钮报告页码;外部跳页不会被布局重算覆盖 |
|
|
153
|
+
| `pageMode` | 默认 `continuous` 使用原生页面虚拟化;`single` 只挂载选中的正文页 |
|
|
154
|
+
| `renderPageOverlay(context)` | 追加 React 内容;context 包含 `documentId`、从 1 开始的 `page`、从 0 开始的 `pageIndex`、原始 `width/height`、`zoom`、角度制 `rotation` |
|
|
155
|
+
| `renderPage({ page, zoom, rotation })` | 与 V1 一样替换整个正文页。只添加印章或标记时应使用 `renderPageOverlay`,避免替换 PDF 位图 |
|
|
156
|
+
| `engineOptions` | `usePdfiumEngine` 的选项,例如 `wasmUrl`;默认 Worker 开启、外部字体回退关闭。改变初始化选项时更新 `source.id` |
|
|
157
|
+
| `onReady(registry)` | 插件初始化回调;不保证文档已加载,不传成品 UI 的 `config/onInit` |
|
|
158
|
+
|
|
159
|
+
叠层坐标以**未缩放、未旋转页面的左上角**为原点;`width/height` 是页面尺寸,不是视口 CSS 尺寸。组件统一应用缩放和 PDF 内置旋转加用户旋转,业务不要再次乘 `zoom` 或旋转整个叠层。业务若使用 PDF 底部原点坐标,应先转换成左上原点。叠层容器不拦截页面事件;可交互元素自行设置 `pointerEvents: 'auto'` 并提供键盘可访问控件。
|
|
160
|
+
|
|
161
|
+
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 可使用外部业务工具栏。叠层是业务 UI,不会自动写入或导出到 PDF 文件。
|
|
162
|
+
|
|
163
|
+
`source.kind/id` 变化销毁旧会话,重置内部缩放、旋转与非受控页码;受控 `currentPage` 由调用方重置。订阅和引擎随卸载清理。原生模块按选择模式动态加载,不进入公共主入口;新增插件是公共包的精确版本依赖,不代表会进入未使用查看器的首屏。
|
|
164
|
+
|
|
165
|
+
## 桌面与移动端行为(成品 UI)
|
|
166
|
+
|
|
167
|
+
EmbedPDF 按查看器容器宽度响应,而不是按浏览器是否为手机判断。因此窄的桌面侧栏或内置浏览器也会进入移动布局;单纯放大浏览器窗口,只有在查看器实际容器跨过对应断点后才会切换桌面布局。
|
|
168
|
+
|
|
169
|
+
移动端双指缩放和工具栏缩放按钮都改变文档缩放状态,但交互不完全相同:
|
|
170
|
+
|
|
171
|
+
- 双指缩放是连续手势,适合快速调整,并以手指中心附近作为交互参照。
|
|
172
|
+
- 加减按钮是离散档位,适合精确、可重复和无手势场景。
|
|
173
|
+
- 两者最终都应触发按目标显示尺寸重新渲染;手势中的短暂 CSS 变换不代表最终页面会一直模糊。
|
|
174
|
+
|
|
175
|
+
当前自动化覆盖移动视口、工具栏无横向溢出、按钮缩放和页面位图像素比例;双指手势、移动浏览器缩放冲突和软键盘不由桌面模拟完整代表,正式放量前仍要做真实手机验收。
|
|
176
|
+
|
|
177
|
+
## 性能、清晰度与网络限制
|
|
178
|
+
|
|
179
|
+
V2 相比旧连续 PDF 实现的主要收益:
|
|
180
|
+
|
|
181
|
+
- 独立子入口和异步引擎,不把 EmbedPDF/PDFium 打进公共主模块。
|
|
182
|
+
- 页面虚拟化,避免滚动时持续测量和更新全部页面。
|
|
183
|
+
- 根据显示尺寸及设备像素比生成页面位图;放大稳定后重新渲染,减少旧画布被永久拉伸造成的模糊。
|
|
184
|
+
- 引擎会取消或替换不再需要的页面工作,降低长文档滚动期间的无效渲染。
|
|
185
|
+
|
|
186
|
+
固定的 EmbedPDF/PDFium 2.15.0 虽接受 `mode: "range-request"`,其当前 URL 编排器仍执行完整 GET。因此该配置不能证明已经实现 HTTP 分段下载,也不能据此承诺大文件首开流量或峰值内存。若这两项是硬指标,必须使用真实大合同记录首屏时间、下载量、滚动帧率和峰值内存后再扩大灰度。
|
|
187
|
+
|
|
188
|
+
清晰度验收不要只看 CSS 尺寸。至少检查可见页面位图的自然像素与显示像素之比接近当前 `devicePixelRatio`,并在 100%、200% 及常用高分屏下观察文字边缘。浏览器自身页面缩放、操作系统缩放和 PDF 内容质量也会影响结果。
|
|
189
|
+
|
|
190
|
+
## 鉴权、安全与生命周期
|
|
191
|
+
|
|
192
|
+
- 优先使用同源 Cookie、短时签名 URL 或受控请求头;不要把长期 Token 写入日志、文档或可公开缓存。
|
|
193
|
+
- CORS、Cookie、Range、缓存与下载权限由文件服务配置,查看器不能绕过服务端策略。
|
|
194
|
+
- 隐藏打印、下载或菜单入口只是 UI 限制,敏感文件仍必须由服务端逐请求鉴权。
|
|
195
|
+
- URL、身份或权限上下文改变时更新 `source.id`,防止旧会话继续显示上一份文档。
|
|
196
|
+
- 使用 ArrayBuffer 时,获取、取消、错误提示和敏感内存生命周期由消费项目管理。
|
|
197
|
+
|
|
198
|
+
## V1 完整兼容与图片示例
|
|
199
|
+
|
|
200
|
+
原有调用可以只更换导入路径:
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
import {
|
|
204
|
+
DocumentViewer, DocumentViewerViewport, PdfThumbnailRail,
|
|
205
|
+
PdfToolbar, usePdfCanvas, usePdfViewport,
|
|
206
|
+
} from '@jc-times/business-ui/document-viewer-v2';
|
|
207
|
+
|
|
208
|
+
<DocumentViewer title="附件" source={{ id: fileVersion, kind: 'image', images: [
|
|
209
|
+
{ src: imageUrl, alt: '设备照片' },
|
|
210
|
+
] }} />
|
|
211
|
+
|
|
212
|
+
<DocumentViewer source={{ id: fileVersion, kind: 'pdf', document: pdf }}
|
|
213
|
+
currentPage={page} onCurrentPageChange={setPage} pageMode="single"
|
|
214
|
+
toolbar={<BusinessActions />} footer={<BusinessStatus />}
|
|
215
|
+
renderPage={({ page, zoom, rotation }) => renderBusinessPage(page, zoom, rotation)} />
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- `DocumentViewer` 是 `DocumentViewerV2` 的同名兼容别名;保留 V1 的 `DocumentViewerProps`、`DocumentViewerSource` 类型导出。也可直接使用 `DocumentViewerV2` 名称。
|
|
219
|
+
- `kind: "pdf" / "image"` 保留全部 V1 参数:`title`、`toolbar`、`footer`、`currentPage`、`onCurrentPageChange`、`pageMode`、`renderPage`、`className`,另支持 `style` 和 `ariaLabel`。受控参数、页面替换/叠层的含义与 V1 一致。
|
|
220
|
+
- V1 全部底层组合导出保留:`DocumentViewerViewport` 及其 props、`ContinuousPdfViewer`、`PdfThumbnailRail`、`PageThumbnail`、`PdfWorkspace`、`PdfCanvasPage`、`PdfToolbar`、`usePdfCanvas`、`usePdfViewport`。这些仍遵循原 PDF.js 对象契约。
|
|
221
|
+
- 图片支持浏览器可解码的格式,传 `images: []` 显示空态;错误图片有失败提示。`src` 可以是业务 URL、data URL 或调用方持有的 blob URL;对象 URL 的创建与释放由调用方负责。
|
|
222
|
+
- `source.id` 改变时重置翻页、缩放和失败状态;切换 source kind 也建立新会话。URL/Buffer 卸载后不继续显示旧 PDFium 页面。
|
|
223
|
+
|
|
224
|
+
**兼容入口与引擎迁移是两件事。** 保留 `PDFDocumentProxy` 就仍需要 PDF.js,不会把已有对象自动转换成 PDFium。URL/Buffer 的默认成品模式支持外层插槽与插件配置;传受控页码、`pageMode`、`renderPage` 或 `renderPageOverlay` 时选择 `renderer="headless"`,类型会区分两种模式。
|
|
225
|
+
|
|
226
|
+
## 消费迁移与旧入口
|
|
227
|
+
|
|
228
|
+
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 原生模式,默认成品模式和旧对象兼容模式同时保留。
|
|
229
|
+
|
|
230
|
+
1. 先升级到正式发布且包含兼容能力的版本,只改导入路径,保留已有 source、业务参数及底层组合。
|
|
231
|
+
2. 已有 PDF.js 消费方同时遵守当前包 PDF.js peer 与 worker 版本要求;参见 [旧查看器依赖迁移](document-viewer.md)。
|
|
232
|
+
3. 在消费仓库验证图片、受控页码、连续/单页、业务印章与叠层、权限、桌面/手机。
|
|
233
|
+
4. 需要切换 PDFium 的页面再独立改成 URL/Buffer,并迁移插件交互;保留鉴权、错误和敏感内存生命周期。
|
|
234
|
+
5. 用真实长文档和真实手机检查首开、内存、手势与高分屏清晰度,最后升级精确 registry 版本并发布消费服务。
|
|
235
|
+
|
|
236
|
+
旧 `document-viewer` 入口继续保留。本次不删除 V1 API、图片能力或 PDF.js peer;即使消费导入都改为 V2,仍使用 `kind: "pdf"` 或旧底层组件时也不能删除 PDF.js。删除旧引擎须另行完成所有对象和叠层迁移,不能仅删除 exports。
|
|
237
|
+
|
|
238
|
+
## 当前验证与未完成项
|
|
239
|
+
|
|
240
|
+
本地候选通过兼容参数、受控分页、单页叠层、插槽、图片错误/空态/重置单测,以及桌面/手机浏览器回归:V1/V2 相同的真实 PDF 翻页、缩略图、缩放旋转与图片比例测试;V2 图片全屏、图片/PDFium 切换;图片阶段无 PDFium/WASM 网络请求。原 PDFium 高清渲染与工具栏回归保持通过。
|
|
241
|
+
|
|
242
|
+
2026-09-12 原生模式验证:138 项单测及类型、库/矩阵构建通过,14 项查看器桌面/手机回归通过。新增覆盖受控初始页、外部跳页、手动滚动、叠层点击及缩放/旋转尺寸、单页互切、全屏、文档更换重置、失败后恢复,并检查 390×844、320×480、768×1024、1440×900、844×390 五档视口。Headless 声明、异步模块及文档进入本地 pack;独立旧手机翻页用例复跑三次及最终整组通过(并行构建期间曾有一次旧用例页码超时)。
|
|
243
|
+
|
|
244
|
+
尚未发布或升级消费服务;真实长合同内存、真实设备多指手势、业务权限和印章等端到端验收仍由各消费服务在升级时完成。
|
|
245
|
+
|
|
246
|
+
0.2.33 验证:类型检查、138 单测、构建与按需门禁通过;V2 桌面/手机 10 项浏览器覆盖搜索、正文选择、模拟双指缩放、叠层、旋转、页模式及资源切换。双指证据为 Chromium CDP 模拟,不代表物理手机验收。
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
### PDFium 专用构建入口(0.2.34)
|
|
250
|
+
|
|
251
|
+
只使用原生 PDF 叠层的页面可从 `@jc-times/business-ui/pdfium-viewer` 导入 `PdfiumViewer`,参数与 Headless 模式一致,但省略 `renderer`。该入口不引入 PDF.js 兼容渲染或完整查看器外壳,适用于独立公开查看页面;引擎仍按需加载。通过 `engineOptions.wasmUrl` 提供本地资源的绝对 URL,保证 Blob worker 可解析;同一 `source.kind/id` 维持会话,文件版本改变时必须改变 `id`。
|
|
252
|
+
|
|
253
|
+
`initialZoom="fit-width"` 可用于无受控 zoom 的只读查看入口;官方缩放插件保持适配页面宽度,现有默认 100% 不变。
|
|
254
|
+
|
|
255
|
+
0.2.38:Headless 支持正文选区 Ctrl/Cmd+C,调用官方 selection.copyToClipboard 并遵守 CopyContents 权限;输入框与可编辑内容仍保留原生复制。
|
|
256
|
+
|
|
257
|
+
0.2.39:原生 PDF 页面图片设为 draggable=false,防止浏览器图片拖放抢占官方文本选择手势。
|
|
258
|
+
|
|
259
|
+
0.2.40 修复公共 PDF 全屏状态:Escape 调用浏览器 exitFullscreen,fullscreenchange 同步工具栏;CSS 全屏回退仍在 Escape 时退出。浏览器拒绝退出时保留真实全屏状态和退出按钮。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# PDF 与图片查看器
|
|
2
|
+
|
|
3
|
+
从独立子入口 `@jc-times/business-ui/document-viewer` 导入,主入口不加载查看器或 PDF.js。
|
|
4
|
+
图片直接传入 URL;PDF 传入已加载的 PDF.js 6.3.289 `PDFDocumentProxy`。
|
|
5
|
+
PDF.js 是可选 peer,只看图片不需要运行 PDF 引擎。消费方负责 worker、字体资源、认证、密码和销毁 PDF;组件不发业务 API、不修改或保存原文件。
|
|
6
|
+
|
|
7
|
+
当前未发布候选已从 PDF.js 4.10.38 升级至 6.3.289。消费方升级本候选时必须同步升级 `pdfjs-dist`,并从同一安装包加载 worker(例如 `pdfjs-dist/build/pdf.worker.min.mjs?url`);不要混用旧 worker 或旧 `PDFDocumentProxy` 类型。已发布 0.2.31 仍使用 4.10.38。`document-viewer-v2` 的 EmbedPDF 引擎不受此变更影响。
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { DocumentViewer } from "@jc-times/business-ui/document-viewer";
|
|
11
|
+
import "@jc-times/business-ui/styles.css";
|
|
12
|
+
|
|
13
|
+
<DocumentViewer title="图纸" source={{ id: fileVersion, kind: "image",
|
|
14
|
+
images: [{ src: imageUrl, alt: "设备正视图" }] }}/>
|
|
15
|
+
<DocumentViewer title="文档" source={{ id: fileVersion, kind: "pdf", document: pdf }}/>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
父容器须有确定高度。默认连续阅读、桌面展开侧栏、900px 以下折叠;缩略图每组最多12页,页码输入支持直接跳转,避免为长文档生成大量下拉选项。图片支持浏览器原生解码格式,如 JPG、PNG、WebP、GIF;HEIC/TIFF 是否可用取决于浏览器,失败会提示。
|
|
19
|
+
|
|
20
|
+
缩放、90°旋转、全屏(不支持原生 API 时使用全窗口模式)、翻页、缩略图及渲染预算统一维护。按 Escape 可退出全窗口模式。改变 `source.id` 重建查看会话,清除页码、缩放、旋转和错误;内容变化时必须更新 id。
|
|
21
|
+
|
|
22
|
+
`currentPage/onCurrentPageChange` 可受控;`toolbar/footer` 提供业务信息和操作插槽。
|
|
23
|
+
`renderPage({page, zoom, rotation})` 用于业务叠加层。精细组合可以使用同入口的 `DocumentViewerViewport`、`PdfThumbnailRail`、`PdfToolbar`、`usePdfViewport`、`usePdfCanvas`;所有路径复用同一实现。
|
|
24
|
+
印章、权限、水印、审核/签署及下载授权由消费方组合,公共库不理解业务角色。
|
|
25
|
+
|
|
26
|
+
`usePdfCanvas` 按画布串行等待取消的渲染完成,卸载或文件切换后不提交旧结果,并返回页级错误。PDF.js 的解析任务生命周期仍属于传入文档的所有者。纯渲染预算从 `document-render-budget` 子入口获取,服务端使用不引入 React。
|
|
27
|
+
|
|
28
|
+
验证:真实混合横竖页 PDF 与图片的桌面/手机浏览器回归,导航、旋转、缩放、侧栏无横向溢出;单元测试覆盖旧渲染取消、卸载、加载错误与图片所有者重置。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 上传前处理
|
|
2
|
+
|
|
3
|
+
`PreparedFileDropzone` 在 `FileDropzone` 的类型、数量和大小校验后,逐个调用 `prepareFile(file, signal)`。
|
|
4
|
+
整批处理成功才调用一次 `onFilesSelected`;任一失败均不提交部分结果。处理后的文件再次校验类型和大小。
|
|
5
|
+
|
|
6
|
+
- `ownerKey` 必填,包含合同/文档/版本等会改变上传目标的身份。切换身份、禁用或卸载时取消请求;适配器必须把 signal 传到 fetch/解析器。
|
|
7
|
+
- `labels` 和 `errorMessage` 由业务适配器提供;公共 `FilePreparationDialog` 统一进度、错误、取消和焦点管理。
|
|
8
|
+
- 非文件选择器入口(例如查看器拖放)复用 `useFilePreparation` 和 `FilePreparationDialog`。
|
|
9
|
+
- 企业解密、格式识别、密码政策、权限、临时文件清理和服务端强制校验由服务端/应用适配器负责。UI 包不包含密钥,不存储密码,不声称支持任意加密软件。
|
|
10
|
+
- 当前合同适配:服务端自动解密已支持的 WWall 版本,Word/Excel/PDF 打开密码提示用户先移除再上传。没有实现前端密码输入解密。
|
|
11
|
+
- 未使用本组件的既有 `FileDropzone` 保持同步回调行为。业务提交失败由业务页面处理,不误报成解密失败。
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 公共组件成熟度更新与迁移
|
|
2
|
+
|
|
3
|
+
本轮实现采用已有主题/API + 成熟交互内核:ModalShell 使用 Radix Dialog,DateInput 使用 Radix Popover + React Aria Calendar,Toast 使用 Radix Toast,MultiSelect 使用 React Aria 原生多选 ComboBox/TagGroup。业务接口、文案、字段映射仍由消费方提供。
|
|
4
|
+
|
|
5
|
+
## 浮层
|
|
6
|
+
|
|
7
|
+
- ModalShell 的 header/content/footer 和 ref 保持原位置,正文不再执行自研 focus trap。背景由内核屏蔽;初始焦点、返回焦点、只关闭最上层继续受支持。
|
|
8
|
+
- 公共下拉、搜索、菜单、日期、树、地址浮层默认进入最近 ModalShell 的 portal 容器,位于滚动正文之外。不要将它们显式 portal 到 modal 外部;第三方浮层需遵循相同边界。
|
|
9
|
+
- Escape 先交给内部面板。日期选择/键盘关闭后回到日期输入,外部点击保留外部目标焦点。
|
|
10
|
+
- `useModalFocusTrap` 保留给历史消费者;同一个 ModalShell 上再次调用会跳过已托管的焦点管理。新代码直接使用 ModalShell。
|
|
11
|
+
|
|
12
|
+
## 日期
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<DateInput value={date} onValueChange={setDate}
|
|
16
|
+
minDate="2026-01-01" maxDate="2026-12-31"
|
|
17
|
+
isDateUnavailable={iso => holidays.has(iso)} />
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `min/max` 的 ISO 字符串别名也生效;`minDate/maxDate` 优先。
|
|
21
|
+
- 范围/不可选日检查覆盖手输、日历和“今天”。readOnly 同时禁用面板入口;required 时不允许清除。
|
|
22
|
+
- 无效草稿保留并显示错误,不提交;修正后再提交。可用 `onValidationError` 获取错误。`value` 外部更新会重置草稿。
|
|
23
|
+
- 年月跳转使用月份输入;Calendar 内核处理方向键、PageUp/Down,Home/End 现在移到月首/月末。
|
|
24
|
+
- 日历 DOM 改为标准表格行/格子结构;消费方不要依赖旧 `.ui-date-grid` 或按钮 DOM,使用公共样式。
|
|
25
|
+
|
|
26
|
+
## 选择与目录
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<MultiSelect label="参与人" items={options} values={ids} onValuesChange={setIds} name="members" />
|
|
30
|
+
<TreeSelect selectionMode="multiple" items={departments} values={ids} onValuesChange={setIds} />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- MultiSelect 的键盘、多选及标签删除由 React Aria 管理。面板可能保持打开以便连续选择,Escape/移出后可操作标签。`maxSelected` 限制数量;`selectedItems` 为当前候选集之外的已选 ID 提供名称。空数组表示清空。
|
|
34
|
+
- TreeSelect 单选 API 不变;多选使用 values/defaultValues/onValuesChange,不与单选 value 混用。父子节点独立选择,不隐式级联,也不自动选择后端未加载子项。
|
|
35
|
+
- TreeSelect 原生表单 `name` 现在提交 ID,多选按同名字段多值提交。若历史系统依赖标签,可暂设 `formValueMode="label"` 后迁移。required 检查已选值,而非搜索草稿;中文输入确认不会提交选项。
|
|
36
|
+
- AsyncDirectoryPicker:外部 selectedItem 清空/改变会重置显示和待处理选择;`refreshKey` 变化清空缓存并取消旧请求,正在编辑的查询会重新执行防抖。`cacheTtlMs` 默认五分钟,0 绕过缓存;`cacheSize` 继续默认 50。查询缓存不再合并大小写不同的关键词。
|
|
37
|
+
|
|
38
|
+
## 表格
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<DataTable rows={pageRows} columns={columns} getRowId={row => row.id}
|
|
42
|
+
sortMode="server" sort={sort} onSortChange={setSort}
|
|
43
|
+
selectedRowIds={selected} onSelectedRowIdsChange={setSelected}
|
|
44
|
+
renderSelectionActions={ids => <Button onClick={() => exportRows(ids)}>导出</Button>} />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- server 模式仅发出排序描述、显示方向,保持 rows 顺序;列通过 sortable=true 开启服务端排序,数据查询由页面负责。
|
|
48
|
+
- 全选明确为本页可选行;保留其他页已有 ID,加载中不能全选。默认显示本页/总选中数,可设 selectionSummary=false 隐藏。
|
|
49
|
+
- `hiddenColumnIds` 控制列显隐,配置菜单与持久化仍由应用负责。
|
|
50
|
+
|
|
51
|
+
## 通知与文件
|
|
52
|
+
|
|
53
|
+
- Toast 的 maxVisible 是可见数量,超出进入 FIFO 队列,不再删除旧错误;等待项出现后才开始计时。相同 ID 更新保留队列位置并重新计时。鼠标悬停、焦点进入、窗口失焦暂停计时;danger 默认手动关闭。应用切换工作上下文时可调用 dismissAll 清队列。
|
|
54
|
+
- FileDropzone 新增 maxFileSize(字节)、maxFiles(每次选择/拖入上限)和 onRejections,返回 `{file, reason: 'type'|'size'|'count'}`。旧 onFilesRejected 仍接受 File[],现在也包含数量超限文件。单文件模式拖入多份时不再静默忽略多余文件。
|
|
55
|
+
- 文件类型/大小筛选仅改善客户端反馈;上传服务仍负责自身校验。进度/重试属于上传接口生命周期,本次未引入网络上传器。
|
|
56
|
+
|
|
57
|
+
## 验证与发布边界
|
|
58
|
+
|
|
59
|
+
- 单元测试覆盖状态、数据、回调和重绘预算;数据测试隔离无布局 jsdom 中的 Popover 定位循环。
|
|
60
|
+
- `pnpm test:browser` 使用真实 Chromium,覆盖桌面/手机视口的 Portal 焦点、Escape、日期限制、输入法、树和搜索多选、通知计时及边界截图。
|
|
61
|
+
- CI 增加 React 18.3.1 / 19.2.8 和浏览器门禁。手机视口模拟不等于真实手机输入法/软键盘验收。
|
|
62
|
+
- 发布包 CSS 标记为副作用,仍按 README 显式导入 styles.css。消费方需要安装新版本并验证自己的主题、表单和业务回调;本库完成不代表消费应用已升级或部署。
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# ModalShell 0.2.6 消费迁移
|
|
2
|
+
|
|
3
|
+
## 稳定 DOM 契约
|
|
4
|
+
|
|
5
|
+
ModalShell 面板稳定保持以下直接子节点顺序:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
.ui-modal
|
|
9
|
+
├─ .ui-modal-header
|
|
10
|
+
├─ .ui-modal-content (children;唯一正文滚动区)
|
|
11
|
+
└─ .ui-modal-footer (仅在传入 footer 时存在;固定操作区)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
消费方可以依赖这些具名插槽 class,但不应使用元素类型或无 class 的层级选择器猜测内部结构。没有 `footer` 时,面板只包含 header 与 content。
|
|
15
|
+
|
|
16
|
+
## 旧式调用迁移
|
|
17
|
+
|
|
18
|
+
旧式写法会把原生 footer 放进 `.ui-modal-content`,因此它会随正文滚动,且 `.panel > footer` 不再匹配:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<ModalShell {...dialogProps}>
|
|
22
|
+
<Form />
|
|
23
|
+
<footer className="actions">...</footer>
|
|
24
|
+
</ModalShell>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
改为显式插槽:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<ModalShell
|
|
31
|
+
{...dialogProps}
|
|
32
|
+
contentClassName="form-scroll"
|
|
33
|
+
contentProps={{ ref: contentRef, "data-section": "dialog-body" }}
|
|
34
|
+
footerClassName="actions"
|
|
35
|
+
footer={<>...</>}
|
|
36
|
+
>
|
|
37
|
+
<Form />
|
|
38
|
+
</ModalShell>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
开发构建会对可识别的旧式直接 footer child 输出迁移警告,`business-ui-gate` 会阻止新增调用。公共包不会自动搬运 footer,因为这可能改变条件渲染、语义、事件边界或消费方业务布局。
|
|
42
|
+
|
|
43
|
+
如果消费仓库使用 `business-ui-gate --only ...`,升级后需把 `legacyModalFooter` 加入 `--only` 列表;默认全量门禁会自动启用该规则。
|
package/docs/mrt-poc.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# 表格能力与 MRT 验证样例
|
|
2
|
+
|
|
3
|
+
更新日期:2026-09-12。已决定采用MRT,当前工作树已替代公共DataTable渲染内核并提供独立入口;对应0.2.31;其他服务需单独升级。当前公共API见 [DataTable迁移](data-table.md)。以下合同包装仍是业务验证样例。本文区分现有公共 `DataTable` 与样例能力,不把工作树功能视为已发布 API。
|
|
4
|
+
|
|
5
|
+
## 选用入口
|
|
6
|
+
|
|
7
|
+
| 场景 | 当前实现 | 调用边界 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 普通列表、排序、选择、加载/空态 | 公共 `DataTable` | 从实际安装版本导入,完整参数以类型声明为准 |
|
|
10
|
+
| 列筛选、列设置、拖动/固定/缩放 | MRT 本地样例 | 仅 `examples/mrt-poc`,无 npm 导出 |
|
|
11
|
+
| 一条主记录包含多条明细,主字段跨行合并 | MRT 样例 `productDisplay="inline"` | 原生 `rowSpan`,按主记录分页 |
|
|
12
|
+
| 点击主记录箭头展开明细 | MRT 样例 `productDisplay="expanded"` | MRT 详情面板,默认收起 |
|
|
13
|
+
|
|
14
|
+
公共 `DataTable` 的旧参数兼容层保留原生行渲染,表格外壳已使用MRT,支持 `hiddenColumnIds`、客户端/服务端排序、受控行选择和行级 memo。列顺序由传入 `columns` 决定;筛选、分页取数及切片由调用方组合。旧参数不自动启用高级能力;完整配置及合并渲染器见DataTable迁移文档。参见 [公共 API](api.md#数据表移动端契约) 和 [成熟度迁移](maturity-migration.md)。
|
|
15
|
+
|
|
16
|
+
现有 `DataTable` 的移动布局可以选择 `scroll` / `cards`;MRT 样例仅使用表格容器内横向滚动,没有卡片模式。
|
|
17
|
+
|
|
18
|
+
## 本地启动
|
|
19
|
+
|
|
20
|
+
```powershell
|
|
21
|
+
pnpm dev:mrt
|
|
22
|
+
# http://127.0.0.1:4186/
|
|
23
|
+
pnpm test:mrt
|
|
24
|
+
pnpm build:mrt
|
|
25
|
+
# 仅用于与简表做构建体积对照
|
|
26
|
+
pnpm exec vite build --config examples/mrt-poc/vite.config.ts --mode baseline
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
样例提供 120 份模拟合同和 5,000 份规模体验,每份合同默认三条模拟产品。没有生产接口或真实客户/员工数据。MRT 3.2.1、MUI material/icons 7.3.11、x-date-pickers 9.13.0、Emotion react 11.14.0 / styled 11.14.1 已迁为正式依赖;公共data-table入口使用这些依赖。
|
|
30
|
+
|
|
31
|
+
## 表格交互
|
|
32
|
+
|
|
33
|
+
| 能力 | 当前行为 |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| 搜索 | 按合同字段做本地全局搜索,不搜索产品明细 |
|
|
36
|
+
| 列筛选 | 文本、金额区间、状态下拉;筛选结果按合同计数 |
|
|
37
|
+
| 排序 | 单击表头;产品字段没有独立排序语义,已关闭排序 |
|
|
38
|
+
| 表头拖动 | 按住表头文字拖动,无独立手柄;有效放下才更新顺序,拖后不触发排序 |
|
|
39
|
+
| 列设置 | 字段勾选、左侧轻量手柄排序、左/右固定;与表头共用顺序状态 |
|
|
40
|
+
| 列显隐 | 合同编号必选;部门默认隐藏;显示字段不是业务权限控制 |
|
|
41
|
+
| 列宽 | 拖动列边缘;金额默认180px、最小140px |
|
|
42
|
+
| 选择/分页 | 以合同为单位,跨页保留选择;一份合同的产品不拆到不同页 |
|
|
43
|
+
| 默认视图 | 表格底部重置搜索、筛选、排序、列顺序、显隐和选择,不重置全部表格状态 |
|
|
44
|
+
| 列设置恢复默认 | 重置列顺序、显隐、固定位置及列宽,不清空业务筛选和选择 |
|
|
45
|
+
|
|
46
|
+
输入框、列菜单及缩放边缘保留各自交互,不触发表头拖列。表头拖动用原生 HTML drag 事件;列设置复用公共 `SortableList`。已验证桌面鼠标拖动,不能据此宣称本样例的真实手机/键盘列排序已验收。
|
|
47
|
+
|
|
48
|
+
## 产品展示配置
|
|
49
|
+
|
|
50
|
+
### 参数
|
|
51
|
+
|
|
52
|
+
独立样例组件为 `examples/mrt-poc/ContractTable.tsx`,参数如下。它依赖示例的 `TableTheme` 和样式环境,不能从已发布 `@jc-times/business-ui` 直接导入。
|
|
53
|
+
|
|
54
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
55
|
+
| --- | --- | --- | --- |
|
|
56
|
+
| `rows` | `Contract[]` | 必填 | 调用方提供合同及产品数据 |
|
|
57
|
+
| `productDisplay` | `'none' \| 'inline' \| 'expanded'` | `'none'` | 调用服务按页面需求固定选择 |
|
|
58
|
+
| `inModal` | `boolean` | `false` | 使用弹窗内表格高度;自身不创建弹窗 |
|
|
59
|
+
| `searchMemoryKey` | `string` | 不启用 | 按服务/租户/用户/列表提供稳定标识,启用本地搜索记忆;变更标识时重建组件 |
|
|
60
|
+
| `virtual` | `boolean` | `false` | 常规/展开模式可启用行虚拟化;合并模式始终分页 |
|
|
61
|
+
|
|
62
|
+
在当前示例的主题和样式上下文内:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// 三个真实产品行,合同字段跨行合并
|
|
66
|
+
<ContractTable rows={contracts} productDisplay="inline" />
|
|
67
|
+
|
|
68
|
+
// 主行默认收起,点击箭头查看产品
|
|
69
|
+
<ContractTable rows={contracts} productDisplay="expanded" />
|
|
70
|
+
|
|
71
|
+
// 普通合同列表
|
|
72
|
+
<ContractTable rows={contracts} productDisplay="none" />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
模式由调用方选择,不要求最终用户必须看到切换器。演示页面的“产品展示”选择器仅便于体验,切换时通过 React key 重建表格,选择等未持久化状态会重置,配置相同searchMemoryKey时恢复搜索和列筛选;弹窗沿用页面选择的模式。其他调用场景动态切换时应同样重建,或显式协调列顺序等受控状态。
|
|
76
|
+
|
|
77
|
+
### 数据契约
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
type ContractProduct = {
|
|
81
|
+
id: string; // 同一合同内稳定唯一
|
|
82
|
+
name: string;
|
|
83
|
+
specification: string;
|
|
84
|
+
quantity: number;
|
|
85
|
+
unit: string;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type Contract = {
|
|
89
|
+
id: string; // 合同稳定唯一,作为选择和行标识
|
|
90
|
+
name: string;
|
|
91
|
+
customer: string;
|
|
92
|
+
amount: number; // 样例以元表示,用 Intl.NumberFormat 格式化
|
|
93
|
+
owner: string;
|
|
94
|
+
status: string;
|
|
95
|
+
signedAt: string;
|
|
96
|
+
department: string;
|
|
97
|
+
products: ContractProduct[];
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
产品数组长度不限于三条;调用方没有产品时传 `[]`。当前状态徽标映射仍是样例业务逻辑。正式通用化需要明细数据/渲染插槽和调用方状态映射,不应把合同字段或产品模型固化为公共库契约。
|
|
102
|
+
|
|
103
|
+
### 同行模式:原生行合并
|
|
104
|
+
|
|
105
|
+
每个产品渲染为主表 `tbody` 中的一个真实 `tr`,产品名称、规格、数量各占独立列。合同信息和选择框使用 `rowSpan=产品数` 跨行显示、纵向居中;没有嵌套表格。
|
|
106
|
+
|
|
107
|
+
实现使用 MRT 的 `semantic` 布局与 `muiTableBodyProps.children`,自定义 `MergedProductRows` 复用 MRT 单元格渲染和列状态。这是样例适配,不是 MRT 开箱提供的自动合并能力。
|
|
108
|
+
|
|
109
|
+
- 根据每份合同的产品数合并,不把不同合同中相同文本自动合并。
|
|
110
|
+
- 无产品时保留一个主行,产品位置显示“暂无产品”和占位符。
|
|
111
|
+
- 隐藏全部产品列时,每份合同恢复为一行。
|
|
112
|
+
- 产品列可以调整顺序、显隐和宽度;合同仍是选择、搜索、排序与分页单位。
|
|
113
|
+
- 尚未提供任意单元格 `rowSpan` / `colSpan` 配置,也未做跨合同合并。
|
|
114
|
+
- 合并模式关闭行虚拟化,以免截断跨行单元格;5,000份数据也按合同分页。
|
|
115
|
+
|
|
116
|
+
### 展开模式
|
|
117
|
+
|
|
118
|
+
通过行首按钮展开/收起产品,使用 `renderDetailPanel`,默认收起且未提供展开全部。详情内使用独立产品小表,合同主列表行高保持紧凑。展开模式保留虚拟化选项,但带产品详情的5,000行场景尚无专项性能结论。
|
|
119
|
+
|
|
120
|
+
两种模式都没有产品独立筛选、排序、编辑或选择功能。
|
|
121
|
+
|
|
122
|
+
## 样式与弹窗约定
|
|
123
|
+
|
|
124
|
+
颜色、边框、圆角和焦点使用 `--ui-*`;复用 Button、Checkbox、Badge、Pagination、SortableList、ModalShell,图标采用 Lucide 适配。公共组件源码不因 POC 而改变。
|
|
125
|
+
|
|
126
|
+
- 文本默认左对齐,金额和数量右对齐,金额采用等宽数字。
|
|
127
|
+
- 表头完全不透明;排序/列菜单图标保持清晰,菜单无边框透明底,仅悬停或键盘聚焦显示浅色背景。
|
|
128
|
+
- 表头筛选桌面32px、粗指针44px,缩短占位符并保留完整无障碍字段名。
|
|
129
|
+
- 金额列保留单行“金额范围”入口,点击弹出最小/最大金额输入;已设置时显示范围摘要。不再上下堆叠输入撑高整行表头,列仍可缩至140px。
|
|
130
|
+
- 窄屏在表格内部横向滚动,列设置内部纵向滚动。
|
|
131
|
+
|
|
132
|
+
MUI Popover/Popper 通过 `useOverlayContainer` 进入现有 ModalShell 边界,避免浮层落到模态外。嵌套 Popover 关闭自己的 enforceFocus,保留外层焦点约束。示例包装层捕获 Escape:有内层 Popover 时先由内层处理,否则关闭外层并恢复触发器焦点。此包装在 `main.tsx`,`ContractTable` 本身不负责创建主题或模态焦点边界。
|
|
133
|
+
|
|
134
|
+
## 性能与验证
|
|
135
|
+
|
|
136
|
+
现有公共 `DataTable` 会渲染所有传入行,单元格规模约为行数乘可见列数。稳定的列定义、行对象和回调有助于行级 memo;大数据应由调用方分页,不能把行缓存视为虚拟化。
|
|
137
|
+
|
|
138
|
+
截至2026-09-12,MRT样例类型检查、14项浏览器回归和样例构建通过。覆盖搜索/筛选、跨页选择、表头及列设置拖动、列显隐、金额缩列、页面/弹窗、Escape、合并行结构、展开收起和常规5,000行虚拟DOM控制。五档视口为1440×900、768×1024、390×844、320×480、844×390;产品模式还检查了390px弹窗显示。
|
|
139
|
+
|
|
140
|
+
初版生产构建测量:常规5,000行虚拟模式首尾仅渲染21/22个DOM行、可达末条且无控制台error。这是初版普通模式的测量,不能套用到新增合并/展开产品模式;不代表首屏耗时、帧率、内存或真实低端机性能。
|
|
141
|
+
|
|
142
|
+
| 构建 | JS gzip | 说明 |
|
|
143
|
+
| --- | ---: | --- |
|
|
144
|
+
| 当前完整样例(2026-09-12) | 约380.8KB | 包含MRT/MUI、SortableList、弹窗及产品展示;CSS gzip约11.0KB |
|
|
145
|
+
| 初版简表基线(2026-09-11) | 约50KB | 现有DataTable最小页面,未在本次文档整理中重测 |
|
|
146
|
+
|
|
147
|
+
两者功能不等价,不能据此计算MRT单包成本或推断运行速度。样例当前单入口打包,正式接入应评估页面懒加载。公共包没有导出这些MRT示例。
|
|
148
|
+
|
|
149
|
+
生产测量入口(需先构建,端口占用时另选并同步测量脚本):
|
|
150
|
+
|
|
151
|
+
```powershell
|
|
152
|
+
pnpm exec vite preview --config examples/mrt-poc/vite.config.ts --host 127.0.0.1 --port 4187 --strictPort
|
|
153
|
+
node examples/mrt-poc/measure.mjs
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
截图/测量输出在 `artifacts/mrt-qa/`,失败trace在 `artifacts/mrt-test-results/`,均为忽略产物。性能结论应注明模式、记录数、视口、构建和测量环境。
|
|
157
|
+
|
|
158
|
+
## 文件与后续接入
|
|
159
|
+
|
|
160
|
+
| 文件 | 职责 |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| `examples/mrt-poc/main.tsx` | 页面、模式选择、主题和现有弹窗适配 |
|
|
163
|
+
| `examples/mrt-poc/ContractTable.tsx` | MRT配置、合同列、筛选/排序/拖动/分页 |
|
|
164
|
+
| `examples/mrt-poc/MergedProductRows.tsx` | 主表真实产品行及合同rowSpan |
|
|
165
|
+
| `examples/mrt-poc/ProductRows.tsx` | 展开详情小表及模式类型 |
|
|
166
|
+
| `examples/mrt-poc/ColumnSettings.tsx` | 显隐、排序、固定及列恢复默认 |
|
|
167
|
+
| `examples/mrt-poc/data.ts` | 模拟数据与样例类型 |
|
|
168
|
+
| `examples/mrt-poc/styles.css`、`icons.tsx`、`tokens.ts` | 局部视觉适配 |
|
|
169
|
+
| `examples/mrt-poc/mrt.browser.ts` | 可重复的浏览器回归 |
|
|
170
|
+
|
|
171
|
+
已决定采用MRT;其他服务接入前,需确定通用列/明细插槽、模式选择、受控选择与查询状态、列偏好持久化、服务端契约和权限边界,再完成公共导出、版本发布及实际消费验证。当前样例未连接接口;仅搜索条件可本地记忆,列顺序/显隐/宽度等偏好尚未持久化,未验证React 19组合或真实手机/键盘拖列。不要跨仓库复制样例冒充已发布能力。
|
|
172
|
+
|
|
173
|
+
## 搜索条件记忆
|
|
174
|
+
|
|
175
|
+
`searchMemoryKey` 启用后,将生效的全局关键词、列筛选(含金额范围)及筛选栏状态保存到当前浏览器localStorage,刷新或重建时恢复。页面日常列表、5,000条体验与选择弹窗使用不同标识,产品显示模式共用所属列表的搜索记忆。
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<ContractTable rows={contracts} searchMemoryKey={`${serviceId}:${tenantId}:${userId}:contracts`} />
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
存储键前缀为 `business-ui:mrt-search:v1:`。不传参数即不持久化;调用方切换用户或列表标识时,应以key重建组件。损坏数据、未知字段或浏览器存储不可用时回退,搜索仍可用。“恢复默认视图”强制清空筛选而非回到挂载时的已恢复条件,同时删除该列表的搜索记忆。列设置中的恢复默认不清除搜索。仅限当前浏览器,不同步到账号或其他设备;分页、选择、排序及列布局没有在此保存。
|
|
182
|
+
|
|
183
|
+
## 2026-09-12 性能专项复核
|
|
184
|
+
|
|
185
|
+
Material/MRT提供基础能力,不免除调用侧性能设计。现有公共DataTable并不基于Material;MRT样例的UI由MUI提供,行模型由TanStack处理。
|
|
186
|
+
|
|
187
|
+
生产构建、Chromium、1440×900、本机无CPU节流,3次“搜索唯一合同并等待匹配数更新”观测:
|
|
188
|
+
|
|
189
|
+
| 模式 | 合同数 | 初始实际tbody行 | 搜索端到端观测(ms) |
|
|
190
|
+
| --- | ---: | ---: | --- |
|
|
191
|
+
| 普通分页 | 120 | 20 | 314 / 340 / 310 |
|
|
192
|
+
| 普通虚拟 | 5,000 | 19 | 321 / 311 / 313 |
|
|
193
|
+
| 合并、每合同3产品、每页20合同 | 5,000 | 60 | 315 / 337 / 343 |
|
|
194
|
+
|
|
195
|
+
这些时间包含MRT输入的250ms防抖、自动化操作及等待,不能当作纯筛选CPU时间或正式基准。记录位于忽略产物 `artifacts/mrt-qa/performance-review.json`。未测真实网络、低端机、内存峰值及滚动帧率;本次没有足够证据宣称已出现计算瓶颈。
|
|
196
|
+
|
|
197
|
+
优先级判断:
|
|
198
|
+
|
|
199
|
+
1. 正式接入优先页面懒加载:当前完整样例JS gzip约381KB,不能让所有业务页面首屏都承担此成本。
|
|
200
|
+
2. 更大数据量优先服务端筛选/排序/分页:虚拟化只减少DOM,5,000条仍在浏览器内参与过滤。没有测量依据时不规定统一行数阈值。
|
|
201
|
+
3. 合并模式的DOM规模为当前页产品数之和。每合同3产品时受控,但单份合同数百产品会放大渲染,届时优先展开详情并对明细分页。
|
|
202
|
+
4. 公共DataTable已有行级memo、稳定选择回调和排序结果缓存,排序键每行预取一次;不能用“全部重写”代替分页。调用方应保持rows/columns引用稳定,重型单元格需单独分析。
|
|
203
|
+
5. MRT样例的自定义合并body会在表格状态更新时遍历当前页,尚未做逐合同渲染隔离;当前60行没有测出明显恶化,不宜直接打开全表memo以牺牲列拖动/缩放等响应。后续复杂产品单元格先用Profiler定位再优化。
|
|
204
|
+
|
|
205
|
+
公共DataTable相关三个测试文件共24项通过,MRT样例14项浏览器回归、类型和构建通过。本轮未改公共DataTable的性能实现;搜索记忆仅在搜索状态改变时写入小对象,不随单纯勾选触发写入。
|