Repo: objectui ・ 类型: 新增 renderer 包 @object-ui/plugin-matrix ・ 一期仅前端组件能力(不动 framework spec;声明式 matrix 视图类型留二期 ADR)
背景 / 平台缺口
objectui 现有两个相关组件都覆盖不了"动态列 + 可编辑 + 多字段单元格"这一类:
| 现有件 |
列 |
单元格 |
可编辑 |
ObjectGrid(plugin-grid) |
schema 固定 |
单字段 |
✅ 内联编辑 |
PivotTable(plugin-dashboard) |
数据驱动动态列 ✅ |
单个聚合值 |
❌ 只读 |
| 缺口 |
数据驱动动态列 |
多字段结构 |
✅ 可编辑 |
这是一类反复出现的需求,不是某个业务专属:排班(人×日,每格 班次/工时/状态)、定价(产品×地区,每格 价格/折扣/生效期)、产能(资源×周期)、计划矩阵(任务×节点)。当前只能每个项目各写一个一次性组件。本 issue 把它沉淀成平台组件。
目标
新增 @object-ui/plugin-matrix,提供一个通用、配置驱动的可编辑交叉表组件:行来自一个对象、列在运行时由列轴对象的数据动态生成、每个交叉格映射一条"单元格对象"(结/明细对象)记录并可编辑多字段,写入全部经平台受治理数据层(校验/RLS/字段权限/hook/summary 重算)。任何业务(含图纸×机位)只需写配置即可使用。
范围
In scope
- 新包
plugin-matrix,注册 matrix(presentational/value 数据)+ object-matrix(数据绑定,async 拉数)两个组件
- 运行时从列轴对象数据生成动态列;冻结左侧固定列 + 横向滚动
- 单元格多字段、按字段类型渲染编辑控件(复用
@object-ui/fields)
- 稀疏矩阵:空格=无单元格记录;填值→建记录、清空→删记录
- 整表脏标记 + 批量保存(
POST /api/v1/batch 原子事务)+ 乐观锁(ifMatch)
- 字段权限(FLS)/校验(400)/并发(409) 的格级回显
- 只读模式;
inputs 配置暴露给 Studio designer
Out of scope(二期)
- framework
spec 里的声明式 matrix 视图类型(需 ADR,另开)
- 实时协同(realtime 订阅)
- 单元格内"加列即建列轴记录"(先只在列轴维护页加)
包结构(沿用 plugin-grid / plugin-dashboard 范式)
packages/plugin-matrix/
├── src/
│ ├── index.tsx # ComponentRegistry.register('matrix'|'object-matrix', …)
│ ├── ObjectMatrix.tsx # 数据绑定 async 包装(仿 ObjectPivotTable)
│ ├── Matrix.tsx # 渲染:动态列组 + 冻结列 + 稀疏格
│ ├── MatrixCell.tsx # 单格:多字段渲染 + 编辑(包 InlineEditing)
│ ├── hooks/
│ │ ├── useDerivedColumns.ts # 列派生(抽自 PivotTable 165-228)
│ │ ├── useDirtyTracking.ts # 脏标记
│ │ └── useMatrixSave.ts # 保存:upsert-by-key + batch + OCC + 错误归位
│ ├── components/{MatrixToolbar,CellError}.tsx
│ ├── utils/{deriveColumns,formatValue}.ts # formatValue 复用 PivotTable
│ └── __tests__/…
├── package.json ・ vite.config.ts ・ README.md
app-shell 以 peerDependency 引入 → side-effect 触发注册(同 plugin-dashboard 接法)。
注册与配置(generic inputs)
ComponentRegistry.register('object-matrix', ObjectMatrix, {
namespace: 'plugin-matrix', label: '可编辑交叉表', category: 'plugin', icon: 'grid-3x3',
inputs: [
{ name: 'cellObject', type: 'string', required: true }, // 单元格/结对象,如 timesheet_entry
{ name: 'rowAxis', type: 'object', required: true }, // { object, filter, keyField, labelFields, ref } 行轴 + 明细中指向行的字段
{ name: 'columnAxis', type: 'object', required: true }, // { object, filter, keyField, labelField, ref } 列轴(动态) + 明细中指向列的字段
{ name: 'cellFields', type: 'array', required: true }, // [{ field, editable }] 每格展示/可编辑的字段
{ name: 'frozenCount', type: 'number', defaultValue: 1 },
{ name: 'allowEmptyCreate', type: 'boolean', defaultValue: true }, // 空格填值→建记录
{ name: 'deleteOnClear', type: 'boolean', defaultValue: true }, // 清空→删记录
{ name: 'readOnly', type: 'boolean', defaultValue: false },
{ name: 'saveMode', type: 'enum', enum: ['batch','onBlur'], defaultValue: 'batch' },
],
});
单元格 / 编辑 / 保存语义(通用)
- 格 ↔ 记录:每格唯一映射
cellObject 中 (rowAxis.ref = rowKey, columnAxis.ref = colKey) 的一条记录;(ref, ref) 应有唯一索引。
- 稀疏:无记录的格显示空;
allowEmptyCreate 时填值 → create;deleteOnClear 时清空全部 cellFields → delete。
- 改已有 →
update(id, patch, { ifMatch })(OCC)。
- 保存:
saveMode:'batch' 收集脏格 → POST /api/v1/batch(原子,支持 $ref 接新建 id);onBlur 即时存。无原生 upsert → useMatrixSave 内做 find-then-create/update(已知 PARTIAL,属预期成本)。
- 受治理:所有写经 ObjectQL → 校验/RLS/FLS/hook/summary 重算照常(
framework/.../objectql/src/engine.ts:2069)。组件不裸写,只调 DataSource。
明确复用(不要重造)
| 复用件 |
位置 |
用法 |
| 列派生逻辑 |
plugin-dashboard/src/PivotTable.tsx:165-228 |
抽成 useDerivedColumns |
| 内联编辑器(校验/异步存/错误/键盘) |
plugin-grid/src/InlineEditing.tsx |
包住每个格字段 |
| 字段类型控件(date/select/currency…) |
@object-ui/fields getCellRenderer(fieldMeta) |
按 cellFields 字段类型渲染 |
| 保存回调范式(onCellChange/onRowSave/onBatchSave) |
plugin-grid/src/ObjectGrid.tsx:144-157 |
useMatrixSave 仿之 |
| 数据契约 |
types/src/data.ts:170(find/create/update[ifMatch]/delete/bulk) |
组件取 useSchemaContext().dataSource |
| 数据加载 hook |
react/src/hooks/useViewData.ts |
ObjectMatrix 拉行/列/明细三路 |
| async 包装范式 |
plugin-dashboard/src/ObjectPivotTable.tsx |
ObjectMatrix 仿之(含 value→label 映射) |
边界情况
- 列数为 0 / 极多(横向滚动 +
frozenCount 冻结左列;列虚拟化可二期)
- 稀疏矩阵正确渲染空格
- 字段无写权限(FLS) → 该格字段只读/脱敏,不报错
- 校验失败(400) → 归位到具体格+字段
- 并发(409) → 提示并给当前值,支持重载该格
- batch 部分失败 → 整体回滚,逐格标错
验收标准
任务拆解(~6-9 人天)
与 PivotTable 的边界(设计决策)
新建 plugin-matrix,不扩展 PivotTable:聚合只读 vs 事务可编辑是两种心智;格结构(单聚合值 vs 多字段)和保存管线差异大。仅复用其列派生 useMemo 与 formatValue 抽成 util。
Open Questions / 二期
- 列虚拟化(机位/周期很多时)——先普通滚动,性能不够再上
- 单元格状态着色(按某字段值)是否纳入通用配置
- 声明式
spec matrix 视图类型(让元数据直接 author,不写 componentId)→ 二期 framework ADR
- 实时协同 / 单元格级订阅
背景溯源:源于一个制造业计划管理系统评估(钢结构/海工,图纸×机位动态列矩阵)。该业务需求是本平台能力的首个验证实例,但组件本身按通用交叉表设计,不与业务耦合。
背景 / 平台缺口
objectui 现有两个相关组件都覆盖不了"动态列 + 可编辑 + 多字段单元格"这一类:
ObjectGrid(plugin-grid)PivotTable(plugin-dashboard)这是一类反复出现的需求,不是某个业务专属:排班(人×日,每格 班次/工时/状态)、定价(产品×地区,每格 价格/折扣/生效期)、产能(资源×周期)、计划矩阵(任务×节点)。当前只能每个项目各写一个一次性组件。本 issue 把它沉淀成平台组件。
目标
新增
@object-ui/plugin-matrix,提供一个通用、配置驱动的可编辑交叉表组件:行来自一个对象、列在运行时由列轴对象的数据动态生成、每个交叉格映射一条"单元格对象"(结/明细对象)记录并可编辑多字段,写入全部经平台受治理数据层(校验/RLS/字段权限/hook/summary 重算)。任何业务(含图纸×机位)只需写配置即可使用。范围
In scope
plugin-matrix,注册matrix(presentational/value 数据)+object-matrix(数据绑定,async 拉数)两个组件@object-ui/fields)POST /api/v1/batch原子事务)+ 乐观锁(ifMatch)inputs配置暴露给 Studio designerOut of scope(二期)
spec里的声明式matrix视图类型(需 ADR,另开)包结构(沿用 plugin-grid / plugin-dashboard 范式)
app-shell以 peerDependency 引入 → side-effect 触发注册(同 plugin-dashboard 接法)。注册与配置(generic
inputs)单元格 / 编辑 / 保存语义(通用)
cellObject中(rowAxis.ref = rowKey, columnAxis.ref = colKey)的一条记录;(ref, ref)应有唯一索引。allowEmptyCreate时填值 →create;deleteOnClear时清空全部cellFields→delete。update(id, patch, { ifMatch })(OCC)。saveMode:'batch'收集脏格 →POST /api/v1/batch(原子,支持$ref接新建 id);onBlur即时存。无原生 upsert →useMatrixSave内做 find-then-create/update(已知 PARTIAL,属预期成本)。framework/.../objectql/src/engine.ts:2069)。组件不裸写,只调DataSource。明确复用(不要重造)
plugin-dashboard/src/PivotTable.tsx:165-228useDerivedColumnsplugin-grid/src/InlineEditing.tsx@object-ui/fieldsgetCellRenderer(fieldMeta)cellFields字段类型渲染plugin-grid/src/ObjectGrid.tsx:144-157useMatrixSave仿之types/src/data.ts:170(find/create/update[ifMatch]/delete/bulk)useSchemaContext().dataSourcereact/src/hooks/useViewData.tsObjectMatrix拉行/列/明细三路plugin-dashboard/src/ObjectPivotTable.tsxObjectMatrix仿之(含 value→label 映射)边界情况
frozenCount冻结左列;列虚拟化可二期)验收标准
readOnly纯只读timesheet_entry(人×日,格含 班次/工时)仅配置跑通cellFields=需求/计划/实际/状态)仅改配置即跑通——证明非业务耦合inputs在 Studio designer 可视化可配任务拆解(~6-9 人天)
inputsschema(0.5d)useDerivedColumns(抽离 PivotTable 列派生)+ 行/列/明细三路加载(1.5d)Matrix渲染:动态列组 + 冻结列 + 横向滚动 + 稀疏多字段格(1.5d)MatrixCell编辑:包InlineEditing+@object-ui/fields类型控件 + 脏标记(1.5d)useMatrixSave:upsert-by-key + batch + OCC + 建/删格 + 错误归位(1.5d)与 PivotTable 的边界(设计决策)
新建
plugin-matrix,不扩展 PivotTable:聚合只读 vs 事务可编辑是两种心智;格结构(单聚合值 vs 多字段)和保存管线差异大。仅复用其列派生useMemo与formatValue抽成 util。Open Questions / 二期
specmatrix视图类型(让元数据直接 author,不写componentId)→ 二期 framework ADR