@jc-times/business-ui 0.2.53 → 0.2.54

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,11 +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` 保持同步回调行为。业务提交失败由业务页面处理,不误报成解密失败。
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` 保持同步回调行为。业务提交失败由业务页面处理,不误报成解密失败。
@@ -1,62 +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。消费方需要安装新版本并验证自己的主题、表单和业务回调;本库完成不代表消费应用已升级或部署。
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。消费方需要安装新版本并验证自己的主题、表单和业务回调;本库完成不代表消费应用已升级或部署。
package/docs/mrt-poc.md CHANGED
@@ -1,205 +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
-
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
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的性能实现;搜索记忆仅在搜索状态改变时写入小对象,不随单纯勾选触发写入。
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的性能实现;搜索记忆仅在搜索状态改变时写入小对象,不随单纯勾选触发写入。