cloud-web-corejs 1.0.54-dev.740 → 1.0.54-dev.742
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/package.json +1 -1
- package/src/components/xform/docs//345/257/271/346/257/224/345/212/237/350/203/275/350/220/275/345/234/260/346/226/271/346/241/210.md +383 -224
- package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +3 -4
- package/src/components/xform/form-render/indexMixin.js +258 -2
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# xform 表单对比(变更前/变更后)功能落地方案
|
|
2
2
|
|
|
3
3
|
> **实现状态:已落地。** 涉及文件:
|
|
4
|
+
>
|
|
4
5
|
> - 新增 `form-render/compareView.vue`(对比提示组件;感叹号固定在字段前、flex 布局不失焦不顶起输入框,文案 `$t1` 国际化)
|
|
5
6
|
> - 新增 `form-render/compareDelList.vue`(查看删除数据弹窗;vxe-grid、列取当前数据表格字段/标题/宽度、固定高度铺满、系统弹框样式、`$t1` 国际化)
|
|
6
|
-
> - `form-render/indexMixin.js`:状态 `isCompare/oldFormData/compareRowKey/compareExcludeTypes/compareExcludeFields`;provide 下发(含 `isCompareExcludeField`);公开方法 `enableCompare/disableCompare/setOldSubData/getDelData/openCompareDelList`;取数 `loadOldData`(主表 getOne + 表头附件各自 getList + data-table 子表 getList,均带 `attachmentType/thirdtabm`);一行接入 `initChangeFromSource`(模式A)/`enableCompareBySource`(模式B)
|
|
7
|
+
> - `form-render/indexMixin.js`:状态 `isCompare/oldFormData/compareRowKey/compareExcludeTypes/compareExcludeFields`;provide 下发(含 `isCompareExcludeField`);公开方法 `enableCompare/disableCompare/setOldSubData/getDelData/openCompareDelList`;取数 `loadOldData`(主表 getOne + 表头附件各自 getList + data-table 子表 getList,均带 `attachmentType/thirdtabm`);一行接入 `initChangeFromSource`(模式 A)/`enableCompareBySource`(模式 B);订单 → 变更单整套编排工厂 `setupChangeCompare(cfg)`(返回按钮联动/加载/清洗一体的控制器,业务只给 cfg);清洗 `sanitizeChangeData`(系统字段清理、树表 id/parent_id 重生成 `regenerateTreeIds`、附件白名单 `attachmentKeepFields`、`defaultFields/subDefaultFields` 不覆盖取默认、`subSourceFields` 原单字段/表头回填);行级样式 `getCompareRowClass`(变更行/新增行)、字段级排除 `isCompareExcludeField`(`create_by/create_date/modify_by/modify_date` 等系统字段默认排除)
|
|
7
8
|
> - `form-render/index.vue`:注册并挂载 compareDelList
|
|
8
|
-
> - `field-widget/fieldMixin.js`:`compareEquals`(原始值判定)、`resolveDisplayValue
|
|
9
|
+
> - `field-widget/fieldMixin.js`:`compareEquals`(原始值判定)、`resolveDisplayValue`(值 →label)、`resolveCompareOldDisplay`(关联类从原单行取伴随 label);各映射组件 override(select/radio/checkbox/status/switch/cascader/vabSearch/autocomplete)
|
|
9
10
|
> - `field-widget/form-item-wrapper.vue`:inject 对比接口、`isCompareOn/compareOldRow/compareIsDiff/compareOldDisplay/compareRowIsNew`(新增行不逐格显示感叹号)、按类型+字段名排除
|
|
10
11
|
> - `container-item/data-table-mixin.js` + `data-table-item.vue`:`rowClassName` 注入变更行/新增行整行高亮;`getSubTableCurrentRows` 读实时表格数据
|
|
11
12
|
> - `mixins/defaultHandle.js`:`getReqFormData` 提交前递归剥除对比快照字段 `hData`
|
|
12
13
|
> - `widget-panel/widgetsConfig.js` + `widget-panel/index.vue`:业务组件标签页下新增「对比组件」分组(对比 / 关闭对比 / 查看删除明细 3 个按钮,默认展开)
|
|
13
14
|
|
|
14
|
-
|
|
15
15
|
> 参考实现:`2023/yx2-vue/sf-cloud-web-vue-tw@std/src/views/yx/certificate/agreementChange/edit.vue`
|
|
16
16
|
> 目标组件:`src/components/xform/`(form-render + field-widget)
|
|
17
17
|
|
|
@@ -20,16 +20,19 @@
|
|
|
20
20
|
> 面向业务配置人员:如何给一张「变更单」接上对比。form-render 已内置能力,业务侧只需配置按钮 onClick 脚本、表单 onMounted 脚本,以及列表页一段 autoAdd。
|
|
21
21
|
|
|
22
22
|
### 前置:后端接口
|
|
23
|
+
|
|
23
24
|
推荐用内置的**通用多包加载 `loadOldData`**,它直接复用原单模板的标准读脚本(主表 `getOne`、data-table 子表 `getList`),**无需为对比新增后端接口** —— 只要原单本身的表单读脚本可按 id 查询即可。
|
|
24
25
|
|
|
25
26
|
仅当需要自定义取数时,才手动取数 + `enableCompare`。**取原单数据统一走 `loadOldData`(内部用 `formHttp` 按模板脚本发包),不要用 `formRef.$http` 直接打 url。** 需要额外定制请求时用 `formRef.formHttp({ scriptCode, data, success })`。
|
|
26
27
|
|
|
27
28
|
### 相关入口(本项目现有机制)
|
|
29
|
+
|
|
28
30
|
- 列表打开详情:`openEditDialog(row, param, option)` —— `row=null` 走新增(无 dataId);`param` 为业务参数,详情用 `getFormParam()` 读取。列表 xform 宿主:`src/views/user/form/view/list.vue`。
|
|
29
31
|
- 详情表单读打开参数:`this.getFormParam()`(`defaultHandle.js`,返回 `formRef.param`)。
|
|
30
32
|
- 按钮/表单脚本上下文:`this` 为组件,`this.getFormRef()` 拿 form-render 实例;注入参数含 `dataId`。
|
|
31
33
|
|
|
32
34
|
### 数据流(三跳)
|
|
35
|
+
|
|
33
36
|
```
|
|
34
37
|
原单详情「变更」按钮
|
|
35
38
|
└─ router.push 到「变更单列表路由」,query 带 sourceId + autoAdd
|
|
@@ -44,125 +47,149 @@
|
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
### 第 1 跳:原单「变更」按钮 onClick
|
|
50
|
+
|
|
47
51
|
从「对比组件」拖入**变更**按钮(`compare-change-button`),预置 onClick 直接调内置方法(`dataId` 为注入参数):
|
|
52
|
+
|
|
48
53
|
```js
|
|
49
|
-
this.getFormRef().goChangeBill(dataId,
|
|
54
|
+
this.getFormRef().goChangeBill(dataId, "变更单formCode"); // 把 '变更单formCode' 改成实际编号
|
|
50
55
|
```
|
|
56
|
+
|
|
51
57
|
> **`goChangeBill(sourceId, formCode, options)`(form-render 内置)**:跳到变更单列表并携带 sourceId 打开新增。
|
|
58
|
+
>
|
|
52
59
|
> - 目标列表路径:默认复用当前路由、把**最后一段替换为 `formCode`**(同基兄弟列表约定);不符合时用 `options.listPath` 指定完整路径。
|
|
53
60
|
> - 打开参数走 `query.param`(JSON):`{ sourceId, autoAdd:1 }`(可用 `options.param` 追加)。
|
|
54
61
|
> - 跳转方式:先 `store.dispatch('tagsView/delCachedView', view)` 清缓存视图,再 `router.replace(view)`,确保列表按新参数重载。
|
|
55
62
|
|
|
56
63
|
### 第 2 跳:变更单列表页自动打开新增详情
|
|
64
|
+
|
|
57
65
|
变更单列表页(xform 宿主 `list.vue` 或其业务包装)`mounted`/`activated` 里加(`param` 由 `query.param` 解析到 `this.param`):
|
|
66
|
+
|
|
58
67
|
```js
|
|
59
68
|
let p = this.param || {};
|
|
60
69
|
if (p.autoAdd) {
|
|
61
70
|
// row=null → 新增;param 带 sourceId,详情用 getFormParam() 取
|
|
62
|
-
this.openEditDialog(
|
|
71
|
+
this.openEditDialog(
|
|
72
|
+
null,
|
|
73
|
+
{ sourceId: p.sourceId },
|
|
74
|
+
{ formCode: this.addFormCode }
|
|
75
|
+
);
|
|
63
76
|
}
|
|
64
77
|
```
|
|
78
|
+
|
|
65
79
|
> 唯一需在列表页新增的一小段(读 param → 调现有 `openEditDialog`)。
|
|
66
80
|
|
|
67
81
|
### 第 3 跳:变更单详情表单 onMounted(模式 A 自动对比)
|
|
82
|
+
|
|
68
83
|
**推荐:一行接入 `initChangeFromSource`** —— 内部自动按原单模板多包拉取(主表 + 所有 data-table 子表)→ 清洗 → 填充新单 → 开启对比。无需逐个子表写发包。
|
|
84
|
+
|
|
69
85
|
```js
|
|
70
|
-
if (!dataId) {
|
|
86
|
+
if (!dataId) {
|
|
87
|
+
// dataId 注入参数,无=新建
|
|
71
88
|
let f = this.getFormRef();
|
|
72
89
|
let sourceId = f.getFormParam() && f.getFormParam().sourceId;
|
|
73
|
-
f.initChangeFromSource(sourceId, {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
f.initChangeFromSource(sourceId, {
|
|
91
|
+
// 单层扁平配置,取数/对比/清洗参数同层
|
|
92
|
+
sourceFormCode: "原单formCode", // 用原单模板拉原单数据(原单≠变更单模板时)
|
|
93
|
+
compareRowKey: "uuid", // 对比参数
|
|
94
|
+
excludeFields: ["f_type"], // 对比参数:主表不对比字段
|
|
95
|
+
defaultFields: ["billType"], // 清洗参数:主表字段不吃原单值、保留变更单默认值
|
|
96
|
+
subDefaultFields: { 明细A: ["status"] }, // 清洗参数:明细列不吃原单值
|
|
97
|
+
subSourceFields: { 明细A: "f_source_id" }, // 清洗参数:原明细 id 回填
|
|
98
|
+
treeSubTables: ["分类明细"], // 清洗参数
|
|
81
99
|
});
|
|
82
100
|
}
|
|
83
101
|
```
|
|
102
|
+
|
|
84
103
|
> `initChangeFromSource` = `loadOldData`(自动多包) + `sanitizeChangeData`(清洗) + `setFormData`(新单) + `enableCompare`(原始旧数据为基准)。
|
|
85
104
|
>
|
|
86
105
|
> **模板解析**:传 `sourceFormCode` 时,内部按 `/formTemplate/getByFormCode` 拉**原单模板**,用其 widgetList 枚举原单子表、entity、formCode 来发包(原单与变更单模板不同时用这个);不传则复用当前变更单模板(结构一致时)。也可用 `reportTemplate/formCode/formVersion/entity` 精确覆盖。
|
|
87
106
|
|
|
88
107
|
**手动版(需自定义拆解逻辑时)**:仍用 `loadOldData` 取数(内部 formHttp),只是手动清洗/填充/对比。`setFormData` 用清洗后数据,`enableCompare` 用原始旧数据;`uuid` 不被清理,新旧行仍匹配。
|
|
108
|
+
|
|
89
109
|
```js
|
|
90
|
-
f.loadOldData(sourceId, { sourceFormCode:
|
|
91
|
-
let newData = f.sanitizeChangeData(oldData, { defaultFields:[
|
|
110
|
+
f.loadOldData(sourceId, { sourceFormCode: "原单formCode" }).then((oldData) => {
|
|
111
|
+
let newData = f.sanitizeChangeData(oldData, { defaultFields: ["billType"] });
|
|
92
112
|
f.setFormData(newData);
|
|
93
|
-
f.enableCompare(oldData, { compareRowKey:
|
|
113
|
+
f.enableCompare(oldData, { compareRowKey: "uuid" });
|
|
94
114
|
// 子表多包补充:f.setOldSubData('子表字段名', 子表旧行);
|
|
95
115
|
});
|
|
96
116
|
```
|
|
117
|
+
|
|
97
118
|
`sanitizeChangeData(oldData, options)` 选项:
|
|
98
|
-
|
|
119
|
+
|
|
120
|
+
- `masterClearFields`:覆盖主表默认清理字段(默认含 id/createBy/createDate/modifyBy/modifyDate/\_createBy/\_modifyBy 及其 snake_case 形式);
|
|
99
121
|
- `subExtraClearFields`:仅子表额外清理的外键字段,默认 `head_table_id/headTableId/alias_id/object_foreign_id`;
|
|
100
122
|
- `defaultFields`(主表)/ `subDefaultFields`(明细 `{keyName:[列prop]}`):这些字段**不吃原单值**,保留变更单自身值/默认值;
|
|
101
123
|
- `subSourceFields`:`{ 明细keyName: 配置 }`,原明细行/原单表头字段回填到明细(字符串/`{from,to}`/`{fromHeader,to}`/数组/`(row,header)=>{}`;早于清 id/树表重生成执行);
|
|
102
124
|
- `attachmentKeepFields`:附件文件对象保留字段白名单(默认 `name/thumbnail/extension/large/fileSize/domain/source/medium/url`);`cleanAttachment:false` 关闭精简;
|
|
103
|
-
- `treeSubTables`:**树结构子表**配置,其 `id/parent_id` 需重新生成并把 parent_id
|
|
125
|
+
- `treeSubTables`:**树结构子表**配置,其 `id/parent_id` 需重新生成并把 parent_id 按旧 → 新映射重指向以保留层级。格式 `[{ name, idField='id', parentField='parent_id' }]`,也可简写字符串数组 `['子表名']`。外部父级的 parent_id 置空作根。**新生成 id 形如 `_row{随机数}`,本表内保证唯一。**
|
|
104
126
|
|
|
105
127
|
示例(含树表):
|
|
128
|
+
|
|
106
129
|
```js
|
|
107
130
|
let newData = f.sanitizeChangeData(oldData, {
|
|
108
|
-
defaultFields: [
|
|
109
|
-
subDefaultFields: { 明细A: [
|
|
110
|
-
subSourceFields: {
|
|
111
|
-
|
|
131
|
+
defaultFields: ["billType"],
|
|
132
|
+
subDefaultFields: { 明细A: ["status"] },
|
|
133
|
+
subSourceFields: {
|
|
134
|
+
明细A: (row, header) => {
|
|
135
|
+
row.f_source_id = row.id;
|
|
136
|
+
row.f_head_no = header.f_no;
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
treeSubTables: [
|
|
140
|
+
{ name: "分类明细", idField: "id", parentField: "parent_id" },
|
|
141
|
+
],
|
|
112
142
|
});
|
|
113
143
|
```
|
|
114
144
|
|
|
115
145
|
### 模式 B:已保存变更单「对比」按钮 onClick
|
|
146
|
+
|
|
116
147
|
**推荐:一行接入 `enableCompareBySource`** —— 自动按模板多包拉取原单并开启对比:
|
|
148
|
+
|
|
117
149
|
```js
|
|
118
150
|
let f = this.getFormRef();
|
|
119
|
-
let sourceId = f.getFormData().sourceId;
|
|
120
|
-
f.enableCompareBySource(sourceId, { compareRowKey:
|
|
151
|
+
let sourceId = f.getFormData().sourceId; // 变更单里存的原单引用
|
|
152
|
+
f.enableCompareBySource(sourceId, { compareRowKey: "uuid" });
|
|
121
153
|
```
|
|
154
|
+
|
|
122
155
|
> 手动版:`f.loadOldData(sourceId).then(old => f.enableCompare(old, { compareRowKey:'uuid' }))`。
|
|
123
156
|
|
|
124
157
|
### 模式 B 变体:搜索框选原单 → 点「对比」
|
|
158
|
+
|
|
125
159
|
支持,**无需额外开发**。本质仍是模式 B,只是 `sourceId` 的来源从「变更单里存的原单引用」换成「搜索框(关联组件)当前选中的值」。
|
|
126
160
|
|
|
127
|
-
关联组件(vabSearch / autocomplete 等)选中后,**原单 id 就存在它绑定的字段**(`formModel[该字段keyName]`,label
|
|
161
|
+
关联组件(vabSearch / autocomplete 等)选中后,**原单 id 就存在它绑定的字段**(`formModel[该字段keyName]`,label 存在伴随字段)。只要「搜索框绑定字段名」= `cfg.sourceIdField`(如都用 `f_source_id`),用 `setupChangeCompare(cfg)` 工厂即可,无需手写逻辑(完整接入见下方「完整接入示例」):
|
|
128
162
|
|
|
129
163
|
```js
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (f.isCompare) f.disableCompare(); // 换原单时先关掉上一轮对比,避免残留 hData
|
|
139
|
-
f.enableCompareBySource(sourceId, {
|
|
140
|
-
compareRowKey: compareRowKey,
|
|
141
|
-
excludeFields: excludeFields,
|
|
142
|
-
// sourceFormCode: '原单formCode', // 原单与变更单模板不同时必填
|
|
143
|
-
});
|
|
144
|
-
this.handleCompareButton(that, true);
|
|
145
|
-
}
|
|
164
|
+
// 搜索框 onChange:选中即加载原单(compare:false,只填充)
|
|
165
|
+
this.getFormRef().loadSourceOnPick(this.formModel.f_source_id);
|
|
166
|
+
// 搜索框 onClear:清空即关闭对比(若还要清掉已填数据,改用 clearCompareSource() + onClearSource 钩子)
|
|
167
|
+
if (!this.formModel.f_source_id) this.getFormRef().disableCompare();
|
|
168
|
+
// 「对比」按钮:点击才开启对比(重拉基准)
|
|
169
|
+
this.getFormRef().enableCompareBySource();
|
|
146
170
|
```
|
|
147
171
|
|
|
148
172
|
注意事项:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
|
|
174
|
+
1. **切换原单**:重选一张再点「对比」,`enableCompareBySource` 会重新拉数并覆盖 `oldFormData`、重做行快照——控制器内部已处理,无需手动先 `disableCompare`。
|
|
175
|
+
2. **模板不同必须传 `cfg.sourceFormCode`**:否则会用变更单模板去枚举子表发包,取不到原单数据。
|
|
176
|
+
3. **搜索框字段本身要排除对比**:框架已自动把 `cfg.sourceIdField` 纳入 `excludeFields`,无需手写;否则它自己会被判为「变更」。
|
|
177
|
+
4. 与模式 A 不冲突:新增走 `loadSourceFromParam()`(路由参数)、手选走 `loadSourceOnPick()`,两条路落到同一个 `cfg.sourceIdField`。
|
|
153
178
|
|
|
154
179
|
### 其它按钮
|
|
180
|
+
|
|
155
181
|
- 关闭对比:`this.getFormRef().disableCompare();`
|
|
156
182
|
- 查看删除数据:`this.getFormRef().openCompareDelList('子表字段名');`(要中文列头传第二参 `[{field,title},...]`)
|
|
157
183
|
|
|
158
184
|
### 全局配置项(可选)
|
|
159
|
-
|
|
185
|
+
|
|
186
|
+
- `formConfig.compareRowKey`:子表行匹配键,默认 `uuid`;也可 `enableCompare(old,{compareRowKey})` 覆盖。值支持 `'字段'`(两边同名)/ `{from,to}`(跨字段名:原单[from]===变更[to])/ 数组(联合键);按明细不同用 `subRowKeys:{明细keyName:键}`。
|
|
160
187
|
- `formConfig.compareExcludeTypes` / `enableCompare(old,{excludeTypes})`:不参与对比的字段类型(附件/上传类已默认排除)。
|
|
161
188
|
- **对比字段名单(主表 / 明细分开配置)**:
|
|
162
189
|
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
| 主表 | `excludeFields: ['f_type', ...]`
|
|
190
|
+
| | 黑名单(不对比) | 白名单(只对比) |
|
|
191
|
+
| ---- | ------------------------------------------------- | --------------------------------------------------- |
|
|
192
|
+
| 主表 | `excludeFields: ['f_type', ...]` | `includeFields: ['f_qty', ...]` |
|
|
166
193
|
| 明细 | `subExcludeFields: { 明细keyName: ['f_x', ...] }` | `subIncludeFields: { 明细keyName: ['f_qty', ...] }` |
|
|
167
194
|
|
|
168
195
|
- 都通过 `initChangeFromSource / enableCompareBySource / enableCompare` 的 options 传;兜底位 `formConfig.compareExcludeFields / compareSubExcludeFields / compareIncludeFields / compareSubIncludeFields`。
|
|
@@ -170,12 +197,13 @@ enableCompareBySource(that) {
|
|
|
170
197
|
- **白名单优先于黑名单**:某张表一旦配了白名单,该表的黑名单整体失效,别同时配。这个优先关系按表独立生效——主表用白名单、明细用黑名单是允许的。
|
|
171
198
|
- 系统字段(`id/create_by/create_date/modify_by/modify_date/head_table_id/alias_id/object_foreign_id` 等)、附件字段(`attachments_` 前缀)、`subSourceFields` 回填字段,**已默认排除**,不用写。
|
|
172
199
|
- 跨表场景(compareMapping)下白名单**自动推导**:主表白名单来自 `mapping.master[].to`,每张明细的白名单来自该表 `fields[].to` —— 主表与各明细互不串味,无需手写。
|
|
173
|
-
> **配置是单层扁平的**:`initChangeFromSource` 的 options 同时供取数、对比、清洗三类参数,**平铺在同一层**,无 `sanitize` 子对象。三组键互不相交(对比 `excludeFields/includeFields/compareRowKey/compareMapping…`、清洗 `defaultFields/subDefaultFields/subSourceFields/treeSubTables…`、取数 `sourceFormCode/subParams…`),各方法各取所需。直接单独调用 `sanitizeChangeData(oldData, options)` 时,它只挑自己认得的清洗键,其余忽略。
|
|
200
|
+
> **配置是单层扁平的**:`initChangeFromSource` 的 options 同时供取数、对比、清洗三类参数,**平铺在同一层**,无 `sanitize` 子对象。三组键互不相交(对比 `excludeFields/includeFields/compareRowKey/compareMapping…`、清洗 `defaultFields/subDefaultFields/subSourceFields/treeSubTables…`、取数 `sourceFormCode/subParams…`),各方法各取所需。直接单独调用 `sanitizeChangeData(oldData, options)` 时,它只挑自己认得的清洗键,其余忽略。
|
|
174
201
|
|
|
175
202
|
- `defaultFields`(主表)/ `subDefaultFields`(明细表 `{keyName:[列prop]}`):这些字段**不吃原单值**,保留变更单自身值/默认值。
|
|
176
203
|
- `subExtraClearFields`:子表额外清空的外键字段,默认 `head_table_id/headTableId/alias_id/object_foreign_id`。
|
|
177
204
|
- `attachmentKeepFields`:附件文件对象保留字段白名单,默认 `name/thumbnail/extension/large/fileSize/domain/source/medium/url`(表头附件、data-table 单元格附件都按此精简;`cleanAttachment:false` 可整体关闭)。
|
|
178
205
|
- `subSourceFields`:`{ 明细keyName: 配置 }`,把原明细行/原单表头字段回填到明细业务字段。配置支持:
|
|
206
|
+
|
|
179
207
|
- `'目标字段'` → 原明细 `id` → 目标字段
|
|
180
208
|
- `{ from:'明细字段', to:'目标字段' }`(from 默认 `id`)
|
|
181
209
|
- `{ fromHeader:'表头字段', to:'目标字段' }` → 原单表头字段回填
|
|
@@ -184,6 +212,7 @@ enableCompareBySource(that) {
|
|
|
184
212
|
|
|
185
213
|
> **回填字段自动排除对比**:回填出的字段(如 `f_source_item_id`)只存在于新单行,原单行没有 → 逐字段比较时是「空 → 有值」,会把整表行判成**变更行(黄)**。`sanitizeChangeData` 已自动把回填目标字段登记进 `compareAutoExcludeFields`(函数式配置也支持:通过比对函数执行前后新增的 key 捕获),无需手工配置。
|
|
186
214
|
> **但模式 A(打开已保存的变更单点「对比」)不会走 sanitize**,此时请把这些字段一并写进 `excludeFields`,例如 `excludeFields: ['f_type', 'f_source_id', 'f_source_item_id']`。
|
|
215
|
+
|
|
187
216
|
- `treeSubTables`:树表配置 `[{ name, idField, parentField }]`,id/parent_id 会重新生成并保留层级。
|
|
188
217
|
- **保存**:提交时 `getReqFormData` 自动剥除对比快照字段 `hData`,无需业务处理。
|
|
189
218
|
- **行级高亮**:变更行橙色、新增行绿色(各带左侧色条),新增行不逐格显示感叹号。
|
|
@@ -191,40 +220,48 @@ enableCompareBySource(that) {
|
|
|
191
220
|
> 附件恒不对比:新单附件已按 `attachmentKeepFields` 白名单精简(9 个字段),原单快照里是完整对象(含 id/createDate/sn…),直接比必然不等。故按 `attachments_` 前缀(设计器 `keyNamePrefixMap` 固定前缀)在行级判定里整体跳过。
|
|
192
221
|
|
|
193
222
|
### 全字段参考配置(options 全集)
|
|
223
|
+
|
|
194
224
|
`initChangeFromSource(sourceId, options)` 的 options 是**单层扁平**对象。`enableCompareBySource` 接收其中的**取数 + 对比**字段(不含清洗,模式 B 不填充新单);`sanitizeChangeData(oldData, options)` 只取其中的**清洗**字段。
|
|
195
225
|
|
|
196
226
|
#### A. 常用字段(按业务需要填)
|
|
227
|
+
|
|
197
228
|
```js
|
|
198
229
|
formRef.initChangeFromSource(sourceId, {
|
|
199
|
-
|
|
200
230
|
// ── 取数 ──
|
|
201
|
-
sourceFormCode:
|
|
202
|
-
sourceKeyField:
|
|
203
|
-
subParams: {
|
|
204
|
-
|
|
231
|
+
sourceFormCode: "订单formCode", // 原单≠变更单模板时必填(枚举原单子表/entity/formCode);结构一致可省略
|
|
232
|
+
sourceKeyField: "f_no", // 原单存的是编码而非 id 时,指定主表用哪个字段查
|
|
233
|
+
subParams: {
|
|
234
|
+
// 明细需要额外查询参数时;值可为对象或 (ctx)=>对象,ctx={masterData,masterId}
|
|
235
|
+
订单明细: { f_status: 1 },
|
|
205
236
|
},
|
|
206
237
|
|
|
207
238
|
// ── 对比 ──
|
|
208
|
-
compareRowKey:
|
|
209
|
-
excludeFields: [
|
|
210
|
-
subExcludeFields: {
|
|
211
|
-
|
|
239
|
+
compareRowKey: "f_uuid", // 子表行匹配主键(务必是明细里稳定唯一的业务键)
|
|
240
|
+
excludeFields: ["f_type"], // 主表不对比字段
|
|
241
|
+
subExcludeFields: {
|
|
242
|
+
// 明细不对比字段:{ 明细keyName: [字段名] }
|
|
243
|
+
test_source_bill_item: ["f_source_item_id"],
|
|
212
244
|
},
|
|
213
245
|
|
|
214
246
|
// ── 清洗 ──
|
|
215
|
-
defaultFields: [
|
|
216
|
-
subDefaultFields: {
|
|
217
|
-
|
|
247
|
+
defaultFields: ["f_no"], // 主表:不吃原单值、保留变更单自身默认值的字段
|
|
248
|
+
subDefaultFields: {
|
|
249
|
+
// 明细:{ 明细keyName: ['列prop'] },取该列设计器默认值
|
|
250
|
+
test_source_bill_item: ["f_state"],
|
|
218
251
|
},
|
|
219
|
-
subSourceFields: {
|
|
220
|
-
|
|
252
|
+
subSourceFields: {
|
|
253
|
+
// 原明细行/原单表头字段回填到明细
|
|
254
|
+
test_source_bill_item: (row, header) => {
|
|
255
|
+
row.f_source_item_id = row.id;
|
|
256
|
+
},
|
|
221
257
|
// 也支持:'目标字段' | { from, to } | { fromHeader, to } | [ ...多个 ]
|
|
222
258
|
},
|
|
223
|
-
treeSubTables: [
|
|
259
|
+
treeSubTables: ["分类明细"], // 有树表时才填:重生成 id/parent_id 并保层级
|
|
224
260
|
});
|
|
225
261
|
```
|
|
226
262
|
|
|
227
263
|
跨表场景(B)另加 `compareMapping`(白名单、行主键由它自动推导,此时 `subRowKeys/includeFields` 不用手写):
|
|
264
|
+
|
|
228
265
|
```js
|
|
229
266
|
compareMapping: {
|
|
230
267
|
master: [{ from: 'o_cust', to: 'f_cust' }],
|
|
@@ -238,128 +275,152 @@ transformOldData: null, // 可选:映射之外的值转换兜底
|
|
|
238
275
|
```
|
|
239
276
|
|
|
240
277
|
#### B. 其余选项(均有默认值,一般省略)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
|
244
|
-
| `
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
247
|
-
| `
|
|
248
|
-
| `
|
|
249
|
-
| `
|
|
250
|
-
| `
|
|
251
|
-
| `
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
278
|
+
|
|
279
|
+
| 字段 | 默认 | 说明 |
|
|
280
|
+
| -------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
281
|
+
| `compare` | `true` | `false`=只填充新单、不开启对比(可稍后手动 `enableCompare`) |
|
|
282
|
+
| `includeFields` | `null` | 主表白名单:设置后主表只比这些字段、主表黑名单失效 |
|
|
283
|
+
| `subIncludeFields` | `{}` | 明细白名单:`{ 明细keyName: [字段] }`,设置后该表只比这些、该表黑名单失效 |
|
|
284
|
+
| `subRowKeys` | `{}` | 按明细覆盖行匹配键 `{ 明细keyName: 键 }`,键支持 `'字段'` / `{from,to}`(跨字段名:原单[from]===变更[to])/ 数组(联合);跨表由 `compareMapping` 自动推导。`setupChangeCompare` 里可直接用 `compareRowKey` 传按明细 map,不必用这个底层名 |
|
|
285
|
+
| `excludeTypes` | 附件/上传类 | 覆盖不参与对比的字段「类型」 |
|
|
286
|
+
| `commonParams` | `{}` | 追加到「所有」明细/附件包的公共查询参数 |
|
|
287
|
+
| `sourceIdField` | `'id'` | 从主表响应取「真实主键」的字段名(用于发明细/附件包) |
|
|
288
|
+
| `sourceIdIsKey` | `false` | `true`=传的就是 id → 主表/明细并行查(快路径) |
|
|
289
|
+
| `isLoading` | `true` | 取数是否显示 loading |
|
|
290
|
+
| `masterScriptCode` | `'getOne'` | 主表读取脚本 |
|
|
291
|
+
| `reportTemplate`/`formCode`/`formVersion`/`entity` | 取自模板 | 覆盖取数用的模板/编号/版本/主表 entity |
|
|
292
|
+
| `masterClearFields` | 系统字段集 | 覆盖主表默认清理字段(id/createBy/createDate/modifyBy/modifyDate/…) |
|
|
293
|
+
| `subExtraClearFields` | 外键集 | 覆盖子表额外清空字段(head_table_id/alias_id/object_foreign_id/…) |
|
|
294
|
+
| `attachmentKeepFields` | 9 字段白名单 | 覆盖附件保留字段(name/thumbnail/extension/large/fileSize/domain/source/medium/url) |
|
|
295
|
+
| `cleanAttachment` | `true` | `false`=不精简附件字段 |
|
|
258
296
|
|
|
259
297
|
### 完整接入示例(GlobalConfig)
|
|
298
|
+
|
|
260
299
|
把对比相关逻辑收敛到 `getGlobalConfig()` 返回对象里,详情页脚本按需调用。
|
|
261
300
|
|
|
262
301
|
#### 步骤 1:从「对比组件」拖入 3 个按钮
|
|
263
302
|
|
|
264
303
|
设计器 → **业务组件**标签页 → **对比组件**分组(默认展开),拖入按钮。分组含 4 个按钮,已预置好 `label`、默认显隐、`onClick` 脚本,拖进去基本即用。
|
|
265
304
|
|
|
266
|
-
| 按钮
|
|
267
|
-
|
|
268
|
-
| 变更
|
|
269
|
-
| 对比
|
|
270
|
-
| 关闭对比
|
|
271
|
-
| 查看删除明细 | `compare-del-button`
|
|
305
|
+
| 按钮 | 面板 type | 放在 | 唯一名称(示例) | 默认显隐 | 预置 onClick |
|
|
306
|
+
| ------------ | ----------------------- | -------- | -------------- | -------- | ---------------------------------------------------------- |
|
|
307
|
+
| 变更 | `compare-change-button` | **原单** | — | 显示 | `this.getFormRef().goChangeBill(dataId, '变更单formCode')` |
|
|
308
|
+
| 对比 | `compare-button` | 变更单 | `button52848` | 显示 | `this.getFormRef().enableCompareBySource()` |
|
|
309
|
+
| 关闭对比 | `compare-close-button` | 变更单 | `button56167` | **隐藏** | `this.getFormRef().disableCompare()` |
|
|
310
|
+
| 查看删除明细 | `compare-del-button` | 变更单 | `button67652` | **隐藏** | `this.getFormRef().openCompareDelList()` |
|
|
272
311
|
|
|
273
|
-
拖入后仅需:① **变更**按钮把 `'变更单formCode'` 改成实际变更单编号(放在原单表单);② 变更单上 3 个按钮设「唯一名称」,与
|
|
312
|
+
拖入后仅需:① **变更**按钮把 `'变更单formCode'` 改成实际变更单编号(放在原单表单);② 变更单上 3 个按钮设「唯一名称」,与 cfg 的 `compareBtnName/closeBtnName/delBtnNames` 对应;③ 明细表名收在 `cfg.detailName`,onClick 全部无参。
|
|
274
313
|
|
|
275
|
-
>
|
|
314
|
+
> 「关闭对比」「查看删除明细」已预置默认隐藏;开启对比后由框架 `setupChangeCompare` 统一放出、隐藏「对比」。
|
|
276
315
|
|
|
277
|
-
#### 步骤 2:`getGlobalConfig`
|
|
316
|
+
#### 步骤 2:`getGlobalConfig` 里配置一次 `setupChangeCompare`
|
|
317
|
+
|
|
318
|
+
编排逻辑已沉到框架 `setupChangeCompare(cfg)`(form-render 内置):按钮显隐联动、原单加载、预置字段、名单派生、提示语全在里面。业务侧 **只给一份 cfg**。`setupChangeCompare` 执行时会把控制器缓存到 form-render,并让 form-render 的同名方法「控制器感知」,所以 onClick / 脚本**直接** `this.getFormRef().enableCompareBySource()` 即可,无需经 `getGlobalConfig`。**换表单只改 cfg**。
|
|
319
|
+
|
|
320
|
+
> 配置在哪都行:放在 `getGlobalConfig`(`return this.getFormRef().setupChangeCompare({...})`,随表单初始化执行一次);或表单 `onMounted`/入口脚本里 `this.getFormRef().setupChangeCompare({...})`(此时 `getGlobalConfig` 可另放别的配置)。两种都会缓存,onClick 调用方式一致。
|
|
321
|
+
>
|
|
322
|
+
> **合二为一**:在 `onMounted`/入口脚本里配置时,可传 `autoLoadFromParam: true`,`setupChangeCompare` 配置后顺带执行 `loadSourceFromParam()`(模式 A 自动加载,编辑态自守卫跳过),省掉单独那一行。⚠️ 放 `getGlobalConfig` 里**不要**开——那时 `formModel`/路由参数可能未就绪,加载时机过早。
|
|
278
323
|
|
|
279
324
|
```js
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
? Number(that.getFormParam()?.sourceId) : null;
|
|
294
|
-
if (sourceId) {
|
|
295
|
-
that.formModel.f_source_id = sourceId;
|
|
296
|
-
that.formModel.f_type = 1;
|
|
297
|
-
that.getFormRef().initChangeFromSource(sourceId, {
|
|
298
|
-
compareRowKey: compareRowKey, // 对比参数
|
|
299
|
-
excludeFields: excludeFields, // 对比参数
|
|
300
|
-
defaultFields: defaultFields, // 清洗参数(同层平铺,无 sanitize 子对象)
|
|
301
|
-
subSourceFields: { // 清洗参数
|
|
302
|
-
// test_source_bill_item 原单明细ID回填到 f_source_item_id
|
|
303
|
-
test_source_bill_item: (row, header) => {
|
|
304
|
-
row.f_source_item_id = row.id;
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
this.handleCompareButton(that, true); // 处理对比按钮的显隐
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
enableCompareBySource(that) {
|
|
313
|
-
// 点击对比,启用对比(模式B:从后端重新拉原单比对)
|
|
314
|
-
let formRef = that.getFormRef();
|
|
315
|
-
let sourceId = that.formModel.f_source_id;
|
|
316
|
-
formRef.enableCompareBySource(sourceId, {
|
|
317
|
-
compareRowKey: compareRowKey,
|
|
318
|
-
excludeFields: excludeFields,
|
|
319
|
-
});
|
|
320
|
-
this.handleCompareButton(that, true); // 处理对比按钮的显隐
|
|
325
|
+
return this.getFormRef().setupChangeCompare({
|
|
326
|
+
sourceIdField: "f_source_id", // 变更单里存「原单ID」的字段(自动纳入不对比+不覆盖)
|
|
327
|
+
presetFields: { f_type: 1 }, // 新建时预置的固定字段(可多个,自动纳入不对比+不覆盖)
|
|
328
|
+
compareRowKey: "f_uuid", // 明细行匹配键(原单行 vs 变更单行按它配对,形态见下)
|
|
329
|
+
detailName: "test_source_bill_item", // 明细表属性名(keyName)
|
|
330
|
+
sourceItemIdField: "f_source_item_id", // 明细「回填原单明细ID」的字段
|
|
331
|
+
|
|
332
|
+
// ── 不对比字段 / 不覆盖字段(都在 sourceIdField+presetFields 之外“额外”追加)──
|
|
333
|
+
excludeFields: ["f_status"], // 表头【不对比】的额外字段
|
|
334
|
+
defaultFields: ["f_no"], // 表头【不覆盖】的额外字段(保住新单自身值/默认值)
|
|
335
|
+
subExcludeFields: {
|
|
336
|
+
// 明细【不对比】:{ 明细keyName: [字段] }
|
|
337
|
+
test_source_bill_item: ["f_line_state"],
|
|
321
338
|
},
|
|
322
|
-
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
this.handleCompareButton(that, false); // 处理对比按钮的显隐
|
|
339
|
+
subDefaultFields: {
|
|
340
|
+
// 明细【不覆盖,取该列设计器默认值】:{ 明细keyName: [列prop] }
|
|
341
|
+
test_source_bill_item: ["f_state"],
|
|
326
342
|
},
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
compareBtnName: "button52848", // 对比按钮唯一名
|
|
344
|
+
closeBtnName: "button56167", // 关闭对比按钮唯一名
|
|
345
|
+
delBtnNames: ["button67652"], // 查看删除明细按钮唯一名(可多个)
|
|
346
|
+
sourceFormCode: "", // 原单≠变更单模板时填原单formCode,否则留空
|
|
347
|
+
sourceLabel: "原单", // 原单业务称呼,用于提示语
|
|
348
|
+
// extraOptions: {}, // 高级:透传给 initChangeFromSource/enableCompareBySource
|
|
349
|
+
|
|
350
|
+
// ── 业务钩子(可选,this = form-render,可直接用 form 能力)──
|
|
351
|
+
onLoaded(oldData, ctx) {}, // 原单加载完成 ctx={mode:'param'|'pick'|'compare', sourceId}
|
|
352
|
+
onCompareToggle(isCompare) {}, // 对比开/关后(同步别的 UI)
|
|
353
|
+
onClearSource() {
|
|
354
|
+
// 搜索框清空时——在此清掉已填数据(框架只关对比、不清数据)
|
|
355
|
+
this.formModel.f_cust = null;
|
|
356
|
+
this.formModel.test_source_bill_item = [];
|
|
334
357
|
},
|
|
335
|
-
};
|
|
358
|
+
});
|
|
336
359
|
```
|
|
337
360
|
|
|
361
|
+
> 框架内部自动:`sourceIdField` + `presetFields` 的键并入「不覆盖(defaultFields) + 不对比(excludeFields)」;`detailName`+`sourceItemIdField` 组成 `subSourceFields` 回填;三个入口共用一份 options。业务无需再写这些派生。
|
|
362
|
+
|
|
363
|
+
##### `compareRowKey` 的形态(原单行 vs 变更行怎么配对)
|
|
364
|
+
|
|
365
|
+
匹配方向固定:`a`=原单行、`b`=变更行。一个字段两用:
|
|
366
|
+
|
|
367
|
+
| 写法 | 含义 |
|
|
368
|
+
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
369
|
+
| `'f_uuid'` | 全局、两边同名:`原单.f_uuid === 变更.f_uuid` |
|
|
370
|
+
| `{ from:'id', to:'f_source_item_id' }` | 全局、**两边字段名不同**:`原单.id === 变更.f_source_item_id`(正好对上 `subSourceFields` 回填的 `f_source_item_id`) |
|
|
371
|
+
| `['f_a','f_b']` 或 `[{from,to},...]` | 全局、多字段联合,全部满足才算同一行 |
|
|
372
|
+
| `{ 明细A: {from,to}, 明细B: 'f_uuid' }` | **按明细** map:每张明细各用各的键(键为明细 keyName、无 `from`/`to` 键时判定为此形态) |
|
|
373
|
+
|
|
374
|
+
- 原单侧字段取值必须非空,否则该行找不到配对 → 判为「新增」。
|
|
375
|
+
- 判定「按明细 map」vs「单个 `{from,to}`」:对象含 `from`/`to` 键 = 单键;否则键是明细名 = 按明细 map。(明细表极少会叫 `from`/`to`;真撞上用 `extraOptions.subRowKeys` 显式指定。)
|
|
376
|
+
|
|
377
|
+
控制器方法(onClick / 入口脚本直接调,均无参):
|
|
378
|
+
|
|
379
|
+
| 方法 | 用途 | 触发 |
|
|
380
|
+
| ---------------------------- | ------------------------------------------------------------------------------ | -------------------- |
|
|
381
|
+
| `loadSourceFromParam()` | 模式 A:路由参数带 sourceId,新建页自动加载 + 开启对比 | 入口脚本 / onMounted |
|
|
382
|
+
| `loadSourceOnPick(sourceId)` | 模式 B 变体:搜索框选原单,只加载不对比 | 搜索框 onChange |
|
|
383
|
+
| `enableCompareBySource()` | 点「对比」:重拉原单作基准并开启对比 | 对比按钮 |
|
|
384
|
+
| `disableCompare()` | 关闭对比 | 关闭对比按钮 |
|
|
385
|
+
| `clearCompareSource()` | = `disableCompare()` **+ 触发 `onClearSource` 钩子**(需连带清已填数据时才用) | 搜索框 onClear |
|
|
386
|
+
| `openCompareDelList()` | 打开「查看删除明细」(表名取 `cfg.detailName`) | 查看删除明细按钮 |
|
|
387
|
+
|
|
388
|
+
> `loadSourceFromParam`(原单 ID 来自路由参数)与 `loadSourceOnPick`(来自搜索框手选)都走框架 `initChangeFromSource`,仅 `compare` 与触发时机不同:前者自动开启对比,后者只填充、把「对比」留给用户手动点。
|
|
389
|
+
|
|
338
390
|
#### 步骤 3:详情页入口脚本 / 按钮 onClick 调用
|
|
339
391
|
|
|
392
|
+
控制器方法内部操作 form-render 自身,**onClick / 入口脚本直接调、不用传 `this`**:
|
|
393
|
+
|
|
340
394
|
```js
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
395
|
+
// ① 新建页入口脚本 / onMounted(模式A:变更跳转,原单ID 来自路由参数)
|
|
396
|
+
this.getFormRef().loadSourceFromParam();
|
|
397
|
+
|
|
398
|
+
// ② 搜索框「选中」原单 onChange(this = 搜索框组件,取其绑定字段的当前值)
|
|
399
|
+
this.getFormRef().loadSourceOnPick(this.formModel.f_source_id);
|
|
400
|
+
|
|
401
|
+
// ③ 搜索框「清空」onClear(或 onChange 里判空)→ 关闭对比
|
|
402
|
+
// 一般用 disableCompare() 即可;若清搜索框还要连带清掉已填数据,改用 clearCompareSource() 并实现 onClearSource 钩子
|
|
403
|
+
if (!this.formModel.f_source_id) this.getFormRef().disableCompare();
|
|
346
404
|
|
|
347
405
|
// 「对比」按钮 onClick
|
|
348
|
-
this.
|
|
406
|
+
this.getFormRef().enableCompareBySource();
|
|
349
407
|
|
|
350
408
|
// 「关闭对比」按钮 onClick
|
|
351
|
-
this.
|
|
409
|
+
this.getFormRef().disableCompare();
|
|
352
410
|
|
|
353
|
-
// 「查看删除明细」按钮 onClick
|
|
354
|
-
this.getFormRef().openCompareDelList(
|
|
411
|
+
// 「查看删除明细」按钮 onClick(表名收在 cfg.detailName)
|
|
412
|
+
this.getFormRef().openCompareDelList();
|
|
355
413
|
```
|
|
356
414
|
|
|
357
415
|
> **易错点**:
|
|
358
|
-
>
|
|
359
|
-
>
|
|
360
|
-
>
|
|
361
|
-
>
|
|
362
|
-
>
|
|
416
|
+
>
|
|
417
|
+
> 1. 三个按钮在设计器里设好唯一名,与 cfg 的 `compareBtnName/closeBtnName/delBtnNames` 对应;「关闭对比」「查看删除明细」默认隐藏,避免非对比态先闪出(对比中由框架自动放出)。
|
|
418
|
+
> 2. 模式 B 靠 `cfg.sourceIdField` 拿原单,确认搜索框绑定字段/保存回显链路带上了该字段。它已被框架自动纳入 `excludeFields`(否则搜索框字段本身会被判为「变更」),无需手写。
|
|
419
|
+
> 3. 搜索框 onClear 一般直接 `disableCompare()`(只关对比、保留已填数据)。`disableCompare` 被「关闭对比」按钮和搜索框清空共用——若想让**搜索框清空时连带清掉已填数据、而「关闭对比」按钮保留数据**,则搜索框走 `clearCompareSource()` 并实现 `onClearSource` 钩子(在钩子里重置字段与明细数组)。这也是 `clearCompareSource` 与 `disableCompare` 的唯一区别。
|
|
420
|
+
> 4. **多张明细**:`cfg.detailName`/`sourceItemIdField` 只是**单明细便捷写法**(回填 + `openCompareDelList()` 默认弹它)。多明细时:
|
|
421
|
+
> - 回填改用 `cfg.subSourceFields`(完整 `{ 明细keyName: 配置 }`,见上方「全字段参考配置」的 `subSourceFields`),与 `detailName` 派生的自动合并;
|
|
422
|
+
> - 查看删除明细给每张明细各放一个按钮,onClick 传各自表名:`this.getFormRef().openCompareDelList('another_detail')`。
|
|
423
|
+
> 5. `loadSourceFromParam` 不对 sourceId 做 `Number()` 转换(兼容编码/ID);若后端主键必须是数字,在 cfg 之外自行转或用 `sourceKeyField`。
|
|
363
424
|
|
|
364
425
|
## 一、背景与目标
|
|
365
426
|
|
|
@@ -384,6 +445,7 @@ this.getFormRef().openCompareDelList('test_source_bill_item');
|
|
|
384
445
|
整个对比发生在「变更单」表单上,分两种模式:
|
|
385
446
|
|
|
386
447
|
### 模式 A:新建变更单(默认开启对比)
|
|
448
|
+
|
|
387
449
|
1. 在原单据详情页点击「变更」按钮。
|
|
388
450
|
2. 跳转到变更单列表页,并自动打开对应的变更单**新增**详情页(此时无变更单 id)。
|
|
389
451
|
3. 页面加载时拿到**原单据数据**作为旧数据,`enableCompare(原单据数据)` 自动执行,默认进入对比态。
|
|
@@ -391,23 +453,28 @@ this.getFormRef().openCompareDelList('test_source_bill_item');
|
|
|
391
453
|
> 对应参考实现:`created()` 中 `if (!this._dataId) this.isCompare = true;`,旧数据来自 `getOld`。
|
|
392
454
|
|
|
393
455
|
### 模式 B:已保存的变更单(手动点「对比」触发)
|
|
456
|
+
|
|
394
457
|
1. 变更单保存成功后,页面已带变更单 id,**默认不开启对比**。
|
|
395
458
|
2. 用户点击「对比」按钮 → 此时才去拉取原单据数据 → `enableCompare(原单据数据)` 进入对比态。
|
|
396
459
|
|
|
397
460
|
> 对应参考实现:保存后 `isCompare=false`,点 `compare()` → `getDataH` 拉原单 → `isCompare=true`。
|
|
398
461
|
|
|
399
462
|
### 职责划分(按钮为 xform 内部 button-widget)
|
|
463
|
+
|
|
400
464
|
「变更 / 对比 / 保存」按钮都是**表单内部的 button-widget**,通过配置在 `field.options.onClick` 的自定义脚本触发。脚本运行时:
|
|
465
|
+
|
|
401
466
|
- `this` = 按钮组件实例,`this.getFormRef()` 返回 form-render 实例。
|
|
402
467
|
- 注入参数含 `dataId`(当前单据 id)、`formCode`、`event`(表格内按钮另有 `row/rowIndex`)。
|
|
403
468
|
- 可用 `this.getFormRef().$http(...)` 发请求、`this.getFormRef().enableCompare(...)` 开启对比。
|
|
404
469
|
|
|
405
470
|
因此:
|
|
471
|
+
|
|
406
472
|
- **form-render 只负责**:提供 `enableCompare(oldData) / disableCompare() / getDelData() / openCompareDelList()` 与对比渲染;**不主动发请求、不做路由**。
|
|
407
473
|
- **onClick 脚本负责**:拉原单据数据、判断新建/已存、调 `enableCompare`。
|
|
408
474
|
- **无需给 button-widget 新增"对比"动作类型** —— 完全靠 onClick 脚本调用 form-render 的公开方法即可。这一点使内部按钮方案对 xform 核心的改动与外层按钮方案一致。
|
|
409
475
|
|
|
410
476
|
### 与 form-render 的衔接点
|
|
477
|
+
|
|
411
478
|
- 模式 A(新建自动开):可写在表单的 `onMounted` 脚本或"变更/新增"入口脚本里,判断无 dataId 时 `getFormRef().enableCompare(oldData)`。
|
|
412
479
|
- 模式 B(点对比开):「对比」按钮 onClick 脚本里拉原单 → `getFormRef().enableCompare(oldData)`。
|
|
413
480
|
|
|
@@ -420,44 +487,66 @@ xform 的 form-render 通过 `loadFormData` 用 `dataId` + `reportTemplate.formC
|
|
|
420
487
|
**核心原则:跨页面传的是「原单 id」,旧数据一律在目标页用 id 向后端拉取,不搬运数据 blob。**(整单+子表数据量大、跨路由易丢失/失真,后端才是快照权威来源。)
|
|
421
488
|
|
|
422
489
|
### 点「变更」时——传 id,不传数据
|
|
490
|
+
|
|
423
491
|
`变更` 打开一张新的变更单(无 dataId),旧数据 = 原单据快照:
|
|
492
|
+
|
|
424
493
|
1. `变更` 按钮 onClick:跳转变更单详情页,URL/打开参数带上**原单 id**(如 `?sourceId=xxx`)。
|
|
425
494
|
2. 变更详情页无 dataId,在 `onMounted` / 入口脚本读到 `sourceId`,用 `initChangeFromSource` 一步完成(内部 `loadOldData`→formHttp 取数,一份两用):
|
|
495
|
+
|
|
426
496
|
```js
|
|
427
|
-
if (!dataId) {
|
|
497
|
+
if (!dataId) {
|
|
498
|
+
// 新建变更单
|
|
428
499
|
let formRef = this.getFormRef();
|
|
429
500
|
let sourceId = formRef.getFormParam() && formRef.getFormParam().sourceId;
|
|
430
|
-
formRef.initChangeFromSource(sourceId, {
|
|
501
|
+
formRef.initChangeFromSource(sourceId, {
|
|
502
|
+
sourceFormCode: "原单formCode",
|
|
503
|
+
compareRowKey: "uuid",
|
|
504
|
+
});
|
|
431
505
|
}
|
|
432
506
|
```
|
|
507
|
+
|
|
433
508
|
> 对应参考:`getOld(原单id)` 既 clone 出新单,又作为 `oldAgreement`。**取数统一走 `loadOldData`(formHttp 按模板脚本发包),不要用 `formRef.$http` 直接打 url。**
|
|
434
509
|
|
|
435
510
|
### 详情页点「对比」时——用当前单关联 id 反查
|
|
511
|
+
|
|
436
512
|
已保存变更单(带 dataId)数据里存有指向原单的引用(如 `sourceId`/`sourceSn`,保存时落库)。`对比` 按钮 onClick:
|
|
513
|
+
|
|
437
514
|
```js
|
|
438
515
|
let formRef = this.getFormRef();
|
|
439
|
-
let sourceId = formRef.getFormData().sourceId;
|
|
440
|
-
formRef.enableCompareBySource(sourceId, {
|
|
516
|
+
let sourceId = formRef.getFormData().sourceId; // 当前变更单里存的原单引用
|
|
517
|
+
formRef.enableCompareBySource(sourceId, {
|
|
518
|
+
sourceFormCode: "原单formCode",
|
|
519
|
+
compareRowKey: "uuid",
|
|
520
|
+
});
|
|
441
521
|
```
|
|
442
522
|
|
|
443
523
|
### 主从表旧数据的获取粒度(多包 vs 单包)
|
|
524
|
+
|
|
444
525
|
xform 现状:加载现单时主表 `getOne` 一个包,**每个 data-table 子表各自 `getList`(`taBm=fieldKeyName`)独立发包**(见 `data-table-mixin.loadDefaultViewList`),子表本就是异步/懒加载。旧数据获取与之对称,有两套落地:
|
|
445
526
|
|
|
446
527
|
**方案一:分表增量(推荐,贴合 xform 异步加载)**
|
|
447
528
|
`loadOldData` 内部即按此思路自动多包(主表 `getOne` + 各 data-table `getList`,均走 `formHttp`)并组装。若要手动控制各包时序,用 `enableCompare(oldMasterData)` 先设主表 + `setOldSubData(name, oldRows)` 增量补子表;子表旧数据同样通过 `formHttp`(按模板脚本)而非 `formRef.$http` 打 url 获取:
|
|
529
|
+
|
|
448
530
|
```js
|
|
449
|
-
formRef.enableCompare(master, { compareRowKey:
|
|
450
|
-
formRef.formHttp({
|
|
451
|
-
|
|
452
|
-
|
|
531
|
+
formRef.enableCompare(master, { compareRowKey: "uuid" }); // 主表旧数据
|
|
532
|
+
formRef.formHttp({
|
|
533
|
+
vue: formRef,
|
|
534
|
+
scriptCode: "getList",
|
|
535
|
+
data: { formCode, formVersion, taBm: "子表A", data: { id } },
|
|
536
|
+
success: (r) =>
|
|
537
|
+
formRef.setOldSubData("子表A", (r.objx && r.objx.records) || r.objx || []),
|
|
538
|
+
});
|
|
453
539
|
```
|
|
540
|
+
|
|
454
541
|
优点:与子表懒加载/分 tab 加载天然契合;某子表未加载不阻塞主表对比。
|
|
455
542
|
|
|
456
543
|
**方案二:一个大包**
|
|
457
544
|
后端提供 `getOldFull(id)` 一次返回主表+所有子表数组,前端 `enableCompare(fullOldData)` 内部遍历所有子表做快照(即"设主表 + 循环 setOldSubData")。
|
|
545
|
+
|
|
458
546
|
- 优点:前端简单、时序可控;缺点:需后端聚合接口,大单据传输量大,且若新子表懒加载未就绪,`getDelData` 需等新子表加载完再算。
|
|
459
547
|
|
|
460
548
|
> **两种方式都要支持,且共用同一套底层 API,无需两套实现。** 由调用方(按钮 onClick 脚本)自行选择:
|
|
549
|
+
>
|
|
461
550
|
> - 走**多包**就多次调用 `setOldSubData`;
|
|
462
551
|
> - 走**单包**就用聚合数据调一次 `enableCompare(fullOldData)`(其内部即"设主表 + 循环 setOldSubData")。
|
|
463
552
|
> - 甚至可混用:主表 + 部分子表随主包一次给(`enableCompare` 内处理已带的子表数组),其余子表后续 `setOldSubData` 增量补入。
|
|
@@ -465,10 +554,12 @@ formRef.formHttp({ vue: formRef, scriptCode: 'getList',
|
|
|
465
554
|
> 因此 form-render 的实现要点是:`enableCompare` 能吃"仅主表"或"主表+任意数量子表数组"的 oldData,`setOldSubData` 可被任意次增量调用,两者对同一子表重复设置时以最后一次为准(幂等)。
|
|
466
555
|
|
|
467
556
|
### 取旧数据的后端约定(二选一)
|
|
557
|
+
|
|
468
558
|
- **A(推荐)**:后端提供 `getOld(变更单 dataId)`,直接返回该变更单对应的"变更前快照"(参考实现即此,`agreementChange/getOld`)。前端不必关心原单 id。
|
|
469
559
|
- **B**:变更单里存 `sourceId`,前端读出后调通用 `get(原单id)`。
|
|
470
560
|
|
|
471
561
|
### 依赖前提
|
|
562
|
+
|
|
472
563
|
两种模式都需**后端提供返回"变更前快照"的接口**。这是外部依赖,不在 xform 前端范围内。
|
|
473
564
|
|
|
474
565
|
## 三、xform 现有架构关键点
|
|
@@ -487,19 +578,23 @@ formRef.formHttp({ vue: formRef, scriptCode: 'getList',
|
|
|
487
578
|
## 五、新增文件
|
|
488
579
|
|
|
489
580
|
### 1. `form-render/compareView.vue`
|
|
581
|
+
|
|
490
582
|
基于参考实现,接口调整以支持字段差异化(见 七·五):
|
|
583
|
+
|
|
491
584
|
- props:`isCompare` / `isDiff` / `oldVal`(展示文本)/ `newVal`(展示文本)。
|
|
492
585
|
- `isCompare && isDiff` → 渲染 `<slot>` + hover popover「变更前:{{ oldVal }}」的 `el-icon-warning` 图标;否则仅渲染 `<slot>`。
|
|
493
586
|
- 差异判定由字段侧(wrapper + fieldMixin.compareEquals)算好通过 `isDiff` 传入,组件本身不再做 `oldVal!=newVal` 裸比。
|
|
494
587
|
- 注册进 form-render 的 components,供 form-item-wrapper 使用。
|
|
495
588
|
|
|
496
589
|
### 2. `form-render/compareDelList.vue`
|
|
590
|
+
|
|
497
591
|
- `el-dialog` + `vxe-grid`,props:`visiable` / `option`(`{ columns, data }`)。
|
|
498
592
|
- 展示某子表被删除的行;列配置由 form-render 依据子表 widget 列自动生成。
|
|
499
593
|
|
|
500
594
|
## 六、form-render 改造(`indexMixin.js`)
|
|
501
595
|
|
|
502
596
|
### data 新增(实际实现)
|
|
597
|
+
|
|
503
598
|
```js
|
|
504
599
|
isCompare: false, // 对比开关
|
|
505
600
|
oldFormData: {}, // 旧整单数据(主表字段 + 各子表数组)
|
|
@@ -512,7 +607,9 @@ compareDelListOption: {},
|
|
|
512
607
|
```
|
|
513
608
|
|
|
514
609
|
### 公开方法(实际实现)
|
|
610
|
+
|
|
515
611
|
**对比开关**
|
|
612
|
+
|
|
516
613
|
- **`enableCompare(oldData, options)`**:`oldFormData=deepClone(oldData)` → 解析 `compareRowKey`(options / formConfig / 默认)→ `isCompare=true` → 对 `oldData` 中已带的数组子表逐个 `applyCompareSubSnapshot`。
|
|
517
614
|
- **`setOldSubData(subFormName, oldRows)`**:多包/增量设置某子表旧数据(幂等),写 `oldFormData[name]` 并 `applyCompareSubSnapshot(name)`。
|
|
518
615
|
- **`applyCompareSubSnapshot(subFormName)`**(内部):按 `compareRowKey` 把旧行匹配到当前行,写每行 `hData`;当前子表未加载时仅保留旧数据,加载后可再次调用补做。
|
|
@@ -520,24 +617,35 @@ compareDelListOption: {},
|
|
|
520
617
|
- **`disableCompare()`**:复位状态、移除各行 `hData`。
|
|
521
618
|
|
|
522
619
|
**删除行 / 弹窗**
|
|
620
|
+
|
|
523
621
|
- **`getDelData(subFormName)`**:**实时计算**(旧数组中当前按主键找不到的行),不缓存。
|
|
524
622
|
- **`openCompareDelList(subFormName, columns)`**:组装 `compareDelListOption` 并弹窗。
|
|
525
623
|
|
|
526
624
|
**变更跳转(原单侧)**
|
|
625
|
+
|
|
527
626
|
- **`goChangeBill(sourceId, formCode, options)`**:跳变更单列表并携带 `sourceId` 打开新增。默认把当前路由最后一段替换为 `formCode`(`options.listPath` 可覆盖),`query.param`=JSON`{sourceId,autoAdd:1}`;先 `tagsView/delCachedView` 清缓存视图再 `router.replace`。
|
|
528
627
|
|
|
529
628
|
**通用取数(自动多包)**
|
|
629
|
+
|
|
530
630
|
- **`loadOldData(sourceId, options)`** → `Promise<oldData>`:先解析模板(`sourceFormCode` 拉原单模板 / 否则复用当前模板)→ 主表 `getOne` 包 + 各 data-table 子表 `getList` 包 → 组装整单返回。
|
|
531
631
|
- **`getCompareSubTables()` / `getCompareSubTablesFrom(widgetList)`**:枚举需独立发包的 data-table 子表。
|
|
532
632
|
- **`fetchTemplateByFormCode(formCode)`** → `Promise<{reportTemplate, formConfig, widgetList}>`:按 formCode 拉模板(`/formTemplate/getByFormCode`)。
|
|
533
|
-
- **`enableCompareBySource(sourceId, options)`**(模式B 一行):`loadOldData` → `enableCompare`。
|
|
534
|
-
- **`initChangeFromSource(sourceId, options)`**(模式A 一行,options 单层扁平):`loadOldData` → `applyCompareSourceTransform` → `sanitizeChangeData(old, options)` → `setFormData(新单)` → `enableCompare(原始旧数据)`。
|
|
633
|
+
- **`enableCompareBySource(sourceId, options)`**(模式 B 一行):`loadOldData` → `enableCompare`。
|
|
634
|
+
- **`initChangeFromSource(sourceId, options)`**(模式 A 一行,options 单层扁平):`loadOldData` → `applyCompareSourceTransform` → `sanitizeChangeData(old, options)` → `setFormData(新单)` → `enableCompare(原始旧数据)`。
|
|
635
|
+
- **`setupChangeCompare(cfg)`**(订单 → 变更单编排工厂):把按钮显隐联动、原单加载、预置字段、名单派生(`sourceIdField`+`presetFields`键 → defaultFields/excludeFields)、`subSourceFields` 回填、提示语全收进框架;业务只给一份 cfg。执行后**缓存控制器到 form-render**,并让 form-render 同名方法「控制器感知」,于是这些方法**直接**在 form-render 上无参调用:
|
|
636
|
+
- `loadSourceFromParam()`(模式 A)/ `loadSourceOnPick(id)`(模式 B 变体)/ `clearCompareSource()`(代理到控制器)
|
|
637
|
+
- `enableCompareBySource()` / `disableCompare()` / `openCompareDelList([明细keyName])`(与底层同名方法合并:无参走控制器、带参走底层)
|
|
638
|
+
- 业务钩子:`onBeforeLoadFromParam / onBeforeLoadOnPick / onLoaded / onCompareToggle / onClearSource`
|
|
639
|
+
- `cfg.autoLoadFromParam:true`:配置后顺带执行 `loadSourceFromParam()`(onMounted 里用;勿在 getGlobalConfig 里开)
|
|
640
|
+
- 也可 `getChangeCompare()` 取控制器对象,或从 `getGlobalConfig()` 返回它。
|
|
535
641
|
|
|
536
642
|
**数据清洗(变更初始化)**
|
|
537
|
-
|
|
538
|
-
- **`
|
|
643
|
+
|
|
644
|
+
- **`sanitizeChangeData(oldData, options)`** → 清洗后克隆:清主/子表系统字段(id/createBy/createDate/modifyBy/modifyDate/\_createBy/\_modifyBy + snake_case)、子表外键(head_table_id/alias_id/object_foreign_id)、附件白名单精简(attachmentKeepFields)、不覆盖取默认(defaultFields/subDefaultFields)、原单字段回填(subSourceFields)、树表 id/parent_id 重生成(treeSubTables)。
|
|
645
|
+
- **`regenerateTreeIds(rows, idField, parentField)`**(内部):树表新 id 形如 `_row{随机}`(本表唯一),parent_id 旧 → 新重映射,外部父级置空作根。
|
|
539
646
|
|
|
540
647
|
### provide 新增下发(实际实现)
|
|
648
|
+
|
|
541
649
|
```js
|
|
542
650
|
getIsCompare: () => e.isCompare,
|
|
543
651
|
getOldFormData: () => e.oldFormData,
|
|
@@ -546,17 +654,25 @@ getCompareExcludeTypes: () => e.compareExcludeTypes,
|
|
|
546
654
|
```
|
|
547
655
|
|
|
548
656
|
### 模板
|
|
657
|
+
|
|
549
658
|
index.vue 末尾挂:
|
|
659
|
+
|
|
550
660
|
```html
|
|
551
|
-
<compareDelList
|
|
661
|
+
<compareDelList
|
|
662
|
+
v-if="showCompareDelList"
|
|
663
|
+
:visiable.sync="showCompareDelList"
|
|
664
|
+
:option="compareDelListOption"
|
|
665
|
+
/>
|
|
552
666
|
```
|
|
553
667
|
|
|
554
668
|
## 七、字段展示改造(`form-item-wrapper.vue`)
|
|
555
669
|
|
|
556
670
|
### inject 新增
|
|
671
|
+
|
|
557
672
|
`getIsCompare` / `getOldFormData` / `getCompareRowKey`(均给默认工厂:非对比场景返回 `false` / `{}`,保证旧调用不受影响)。
|
|
558
673
|
|
|
559
674
|
### computed
|
|
675
|
+
|
|
560
676
|
- `isCompareOn = getIsCompare()`(设计态 `this.designer` 存在、或命中排除类型时强制 false)。
|
|
561
677
|
- `oldRawValue`(原始旧值,用于相等判定):
|
|
562
678
|
- 表格列字段(`this.tableParam && this.tableParam.row`):`this.tableParam.row.hData?.[property]`,`property` 复用 `getPropName` 里已有的取列名逻辑(`getFieldKeyName(field)` / vabSearch 特例)。
|
|
@@ -567,23 +683,38 @@ index.vue 末尾挂:
|
|
|
567
683
|
- `isDiff = !$parent.compareEquals(oldRawValue, newRawValue)`(类型化相等判定,见 七·五)。
|
|
568
684
|
|
|
569
685
|
### 模板包裹
|
|
686
|
+
|
|
570
687
|
`isShowWidget()` 编辑分支的 `<slot>` 与只读分支的 `getShowValue()` 外层各包一层:
|
|
688
|
+
|
|
571
689
|
```html
|
|
572
|
-
<compareView
|
|
690
|
+
<compareView
|
|
691
|
+
:isCompare="isCompareOn"
|
|
692
|
+
:isDiff="isDiff"
|
|
693
|
+
:oldVal="oldDisplay"
|
|
694
|
+
:newVal="newDisplay"
|
|
695
|
+
>
|
|
573
696
|
...原内容...
|
|
574
697
|
</compareView>
|
|
575
698
|
```
|
|
699
|
+
|
|
576
700
|
> compareView 内部按 `isCompare && isDiff` 决定是否显示"变更前"图标+popover(把差异判定从组件内上移到字段侧,以支持差异化比较)。
|
|
577
701
|
|
|
578
702
|
### 不参与对比的字段类型(重要)
|
|
703
|
+
|
|
579
704
|
附件/上传类字段值为文件数组,弹出"变更前"旧值无意义(参考实现的附件 tab 也未包 compareView),需排除。判定:
|
|
705
|
+
|
|
580
706
|
```js
|
|
581
707
|
compareExcludeTypes = [
|
|
582
|
-
|
|
583
|
-
|
|
708
|
+
"file-upload",
|
|
709
|
+
"picture-upload",
|
|
710
|
+
"baseAttachment",
|
|
711
|
+
"vabUpload",
|
|
712
|
+
"vabUpload2",
|
|
713
|
+
"singleUpload",
|
|
584
714
|
];
|
|
585
715
|
// isCompareOn 计算时叠加:!compareExcludeTypes.includes(this.field.type)
|
|
586
716
|
```
|
|
717
|
+
|
|
587
718
|
- 命中排除类型时 `isCompareOn=false`,字段原样渲染,不显示对比图标。
|
|
588
719
|
- 富文本(`rich-editor`)、按钮(`button` 等 static 类)等无值或非数据字段天然不进对比(`newFieldValue` 为空 / 无 fieldModel),可视情况一并纳入排除列表。
|
|
589
720
|
- 排除列表建议做成 form-render 可配置项(`formConfig.compareExcludeTypes`)以便按单据调整。
|
|
@@ -593,47 +724,56 @@ compareExcludeTypes = [
|
|
|
593
724
|
不同字段类型的「存储值」与「展示值」不一致,`compareView` 不能简单地 `oldVal != newVal` 裸比、也不能直接把裸值弹出来。现有 `getOptionLabel()` 只解析当前 `this.fieldModel`,无法解析任意旧值,需扩展。
|
|
594
725
|
|
|
595
726
|
### 各类型的值模型
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
|
599
|
-
|
|
|
600
|
-
|
|
|
601
|
-
|
|
|
602
|
-
|
|
|
603
|
-
|
|
|
604
|
-
|
|
|
605
|
-
|
|
|
727
|
+
|
|
728
|
+
| 字段类型 | 存储值 | 展示值 | 相等判定 | 旧值展示 |
|
|
729
|
+
| ------------------------------------------- | -------------------- | ------------- | ------------------------- | ---------------------------- |
|
|
730
|
+
| input / textarea | 字符串 | 同值 | 归一空值后字符串比 | 原值 |
|
|
731
|
+
| number | 数值 | 格式化 | 数值比(`Number()` 归一) | 格式化旧值 |
|
|
732
|
+
| select / radio | code | options.label | code 比 | 用 options 解析旧 code→label |
|
|
733
|
+
| checkbox / 多选 select | code 数组 | 多 label 拼接 | **数组无序比** | 解析旧数组 → 多 label |
|
|
734
|
+
| switch | 布尔 | 是/否文案 | 布尔比 | 旧布尔 → 文案 |
|
|
735
|
+
| date / time / date-range | 时间串/数组 | 按 format | 归一后比 | 按 format 展示旧值 |
|
|
736
|
+
| cascader / area | 数组路径 | 路径名拼接 | 数组深比 | 解析旧路径 → 名称 |
|
|
737
|
+
| 关联搜索 vabsearch/singerSearch/multiSearch | id(另存 `xxxName`) | 关联 name | id 比 | 展示旧 `xxxName` 字段 |
|
|
606
738
|
|
|
607
739
|
### 判定与展示的分离原则(重要)
|
|
740
|
+
|
|
608
741
|
**差异判定一律用「原始存储值」,弹出的「变更前」一律用「展示文本(label)」。** 二者解耦:
|
|
742
|
+
|
|
609
743
|
- 下拉/单选/多选:`isDiff` 按 code(数组按 code 集合)比;popover 展示旧 code 解析出的 **label**。
|
|
610
744
|
- 关联搜索(vabsearch 等):`isDiff` 按 id 比;popover 展示旧记录的 **name(label)**。
|
|
611
745
|
- 即:`isDiff = !compareEquals(oldRaw, newRaw)`(code/id),`oldVal = resolveDisplayValue(oldRaw)`(label)。
|
|
612
746
|
- 好处:code 相同 label 偶然相同/不同都不误判;用户看到的是可读的中文旧值而非编码。
|
|
613
747
|
|
|
614
748
|
### 设计
|
|
749
|
+
|
|
615
750
|
1. **相等判定 `compareEquals(oldRaw, newRaw)`**(放 fieldMixin,可被具体 widget override):
|
|
616
|
-
- `null / undefined / '' / []` 之间视为"都为空"
|
|
751
|
+
- `null / undefined / '' / []` 之间视为"都为空"→ 相等;
|
|
617
752
|
- 数组:长度+元素无序比(多选/cascader/area);
|
|
618
753
|
- 对象:按 key 深比;
|
|
619
754
|
- 标量:`String(a) === String(b)`(吸收 number/string 差异,或按类型精确比)。
|
|
620
|
-
2. **旧值展示 `resolveDisplayValue(rawValue)`**(放 fieldMixin,可 override):把 `getOptionLabel/getShowValue` 里依赖 `this.fieldModel` 的逻辑改造成"接收任意入参",对 select/radio/checkbox 用 `optionItems`
|
|
755
|
+
2. **旧值展示 `resolveDisplayValue(rawValue)`**(放 fieldMixin,可 override):把 `getOptionLabel/getShowValue` 里依赖 `this.fieldModel` 的逻辑改造成"接收任意入参",对 select/radio/checkbox 用 `optionItems` 解析入参 →label;date/number 按格式;其它返回原值。
|
|
621
756
|
3. **compareView 接口调整**:由 wrapper 计算好后传入
|
|
622
757
|
- `:isCompare`、`:oldVal="oldDisplay"`(已解析文本,用于弹窗展示)、`:newVal="newDisplay"`、`:isDiff="!compareEquals(oldRaw,newRaw)"`(相等判定用原始值,展示用文本)。
|
|
623
758
|
- compareView 内部:`isCompare && isDiff` 时显示图标 + popover「变更前:{{ oldVal }}」。(相比参考版把"是否差异"的判定从组件内 `oldVal!=newVal` 上移到字段侧,以支持类型化比较。)
|
|
624
759
|
|
|
625
760
|
### 关联字段的特殊说明
|
|
761
|
+
|
|
626
762
|
关联搜索类字段值为 id、展示靠同表的 `xxxName` 字段。由于对比是**逐字段**进行的,`xxxName` 字段本身也会被对比与展示,故:
|
|
763
|
+
|
|
627
764
|
- id 字段:`compareEquals` 用 id 比即可;其展示可直接复用 `xxxName`(或在排除列表里让 id 字段不单独弹提示,只让 name 字段弹)。
|
|
628
765
|
- 建议:**对关联字段,只在其 name 展示字段上挂对比提示**,id 字段纳入"不弹提示"集合,避免同一处两个图标。
|
|
629
766
|
|
|
630
767
|
## 八、"查看删除数据"入口(实际实现)
|
|
768
|
+
|
|
631
769
|
未在容器工具栏内置按钮,改由**业务按钮 onClick 脚本**触发(更灵活,不侵入容器):
|
|
770
|
+
|
|
632
771
|
```js
|
|
633
772
|
// 「查看删除数据」按钮 onClick
|
|
634
|
-
this.getFormRef().openCompareDelList(
|
|
773
|
+
this.getFormRef().openCompareDelList("子表字段名");
|
|
635
774
|
// 需中文表头时传列配置:openCompareDelList('子表字段名', [{ field:'xxx', title:'名称' }, ...])
|
|
636
775
|
```
|
|
776
|
+
|
|
637
777
|
- `getDelData(name)` 实时算出旧有、当前按主键找不到的行;`compareDelList.vue`(el-dialog + el-table)展示,列未传时按数据 key 自动生成。
|
|
638
778
|
- 按钮可加显示条件(脚本里先判断 `getFormRef().getDelData(name).length`)。
|
|
639
779
|
|
|
@@ -666,6 +806,7 @@ f.setOldSubData('子表字段名', 子表旧行); // 子表旧数
|
|
|
666
806
|
```
|
|
667
807
|
|
|
668
808
|
## 十、实施进度(已完成)
|
|
809
|
+
|
|
669
810
|
1. ✅ `compareView.vue` + form-item-wrapper 注入(主表字段对比)。
|
|
670
811
|
2. ✅ `enableCompare` / `setOldSubData` 子表 `hData` 快照 + 表格单元格对比。
|
|
671
812
|
3. ✅ `getDelData`(实时)+ `compareDelList.vue` + `openCompareDelList`。
|
|
@@ -676,21 +817,23 @@ f.setOldSubData('子表字段名', 子表旧行); // 子表旧数
|
|
|
676
817
|
|
|
677
818
|
> **状态:已落地。** 在原「同结构对齐」(源单与变更单同表同字段名)基础上,新增支持**源单与变更单是不同数据表、字段名不同**的场景。
|
|
678
819
|
>
|
|
679
|
-
> 落地方法(`form-render/indexMixin.js`):`normalizeBySourceMapping
|
|
820
|
+
> 落地方法(`form-render/indexMixin.js`):`normalizeBySourceMapping`(源 → 目标键空间归一化)、`applyCompareSourceTransform`(归一化 + `transformOldData` 钩子统一入口)、`getCompareRowKeyOf`(按子表行主键)、`isCompareSubTable`(未映射子表不参与对比);状态新增 `compareIncludeFields`(白名单)、`compareSubRowKeys`;`loadOldData` 改为「主表先查 → 用真实 id 查明细」并支持 `subParams/commonParams`;`enableCompare / enableCompareBySource / initChangeFromSource` 全部接入。
|
|
680
821
|
|
|
681
822
|
### 10.5.1 业务前提(已确认)
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
|
685
|
-
|
|
|
686
|
-
|
|
|
687
|
-
|
|
|
688
|
-
|
|
|
689
|
-
|
|
|
690
|
-
|
|
|
823
|
+
|
|
824
|
+
| # | 事项 | 结论 |
|
|
825
|
+
| --- | ------------ | ------------------------------------------------------------------------- |
|
|
826
|
+
| 1 | 单据关系 | **订单 → 变更单**。可能是同一张数据表,也可能**不是同一张表、字段名不同** |
|
|
827
|
+
| 2 | 明细行配对 | **有稳定业务键**可配对(如物料编码/行号) |
|
|
828
|
+
| 3 | 对比范围 | **白名单**:只比映射表里明确列出的关键字段 |
|
|
829
|
+
| 4 | 文案 | 「变更前」暂不改 |
|
|
830
|
+
| 5 | 搜索框绑定值 | **可能是编码,也可能是 ID**;业务侧不做反查 |
|
|
831
|
+
| 6 | 明细读脚本 | `getList`,**可能有额外查询参数** |
|
|
832
|
+
| 7 | 明细外键 | `head_table_id` → **主表的 `id` 字段** |
|
|
691
833
|
|
|
692
834
|
### 10.5.2 核心思路
|
|
693
|
-
|
|
835
|
+
|
|
836
|
+
从**「结构对齐」**(隐式按同名字段比)改为**「语义映射」**(显式声明源 ↔ 目标字段对应关系):
|
|
694
837
|
|
|
695
838
|
1. 用 `sourceFormCode` 拉**源单模板**取数(已支持);
|
|
696
839
|
2. 按 **`compareMapping`** 把源单数据**归一化到变更单的键空间**(字段名、子表 keyName、明细列、行匹配键);
|
|
@@ -698,12 +841,13 @@ f.setOldSubData('子表字段名', 子表旧行); // 子表旧数
|
|
|
698
841
|
|
|
699
842
|
**映射表可选**:同表同字段时不配,自动退化为现在的「结构对齐 + 黑名单」模式,**完全向后兼容**。
|
|
700
843
|
|
|
701
|
-
| 是否配 `compareMapping` | 对齐方式
|
|
702
|
-
|
|
703
|
-
| 否(同表)
|
|
704
|
-
| 是(跨表)
|
|
844
|
+
| 是否配 `compareMapping` | 对齐方式 | 对比范围 |
|
|
845
|
+
| ----------------------- | -------------------- | ----------------------------------------- |
|
|
846
|
+
| 否(同表) | 隐式同名对齐(现状) | 黑名单 `excludeFields` |
|
|
847
|
+
| 是(跨表) | 语义映射 | **白名单**(由 mapping 的 `to` 自动推导) |
|
|
705
848
|
|
|
706
849
|
### 10.5.3 映射表形态
|
|
850
|
+
|
|
707
851
|
```js
|
|
708
852
|
compareMapping: {
|
|
709
853
|
// 主表:源字段 → 变更单字段
|
|
@@ -725,15 +869,20 @@ compareMapping: {
|
|
|
725
869
|
],
|
|
726
870
|
}
|
|
727
871
|
```
|
|
872
|
+
|
|
728
873
|
> **白名单自动推导**:所有 `to` 字段(主表 + 各明细列)自动组成对比白名单,业务无需再写一遍 `includeFields`。未列出的字段一律不参与对比、不显示感叹号、不参与行级高亮判定。
|
|
729
874
|
|
|
730
875
|
### 10.5.4 行匹配(关键技巧:不改 `matchCompareRowKey`)
|
|
876
|
+
|
|
731
877
|
归一化明细行时:
|
|
878
|
+
|
|
732
879
|
```
|
|
733
880
|
归一化旧行[变更单业务键字段] = 源行[源业务键字段] // 如 旧行.f_mat_no = 订单行.f_material_code
|
|
734
881
|
归一化旧行[变更单列名] = 源行[源列名] // 按 fields 映射
|
|
735
882
|
```
|
|
883
|
+
|
|
736
884
|
再把该子表的 `compareRowKey` 设为 `f_mat_no`(**变更单侧**的业务键字段)。于是:
|
|
885
|
+
|
|
737
886
|
- 当前变更单明细行本来就有 `f_mat_no`;
|
|
738
887
|
- 归一化后的旧行也被写入了 `f_mat_no`;
|
|
739
888
|
- `matchCompareRowKey(o, row, 'f_mat_no')` → **直接匹配成功,现有逻辑零改动**。
|
|
@@ -741,6 +890,7 @@ compareMapping: {
|
|
|
741
890
|
> **多字段组合键**(如 物料+批次):归一化时生成合成键字段(如 `_cmpKey = 'M001|B01'`),并在开启对比时对**当前明细行也注入同样的合成键**,`compareRowKey='_cmpKey'`。单字段业务键则用不上。
|
|
742
891
|
|
|
743
892
|
### 10.5.5 取数流程(必须「主表先查」)
|
|
893
|
+
|
|
744
894
|
**为什么**:明细外键 `head_table_id → 主表.id`,而搜索框存的**可能是编码**。若沿用现在的并行发包(主表和明细同时用搜索框的值查),**存编码时明细必然查空**——明细表里没有"编码"这个关联字段。
|
|
745
895
|
|
|
746
896
|
```
|
|
@@ -758,9 +908,11 @@ compareMapping: {
|
|
|
758
908
|
**向后兼容**:搜索框存的就是 id 时,主表返回的 `objx.id` 就是同一个值,明细包参数与现在完全一致 —— 现有模式 A **行为不变**,只多一次串行等待(一个 RTT)。可用 `sourceIdIsKey: true` 保留并行快路径。
|
|
759
909
|
|
|
760
910
|
### 10.5.6 明细额外查询参数
|
|
911
|
+
|
|
761
912
|
**为什么不能直接复用源模板的 `formScriptParam`**:那段脚本在**该 data-table 组件实例**上下文里执行(`this` = 表格组件)。对比取数时源单模板只是一份 JSON,**没有对应组件实例**,跨实例执行有风险。
|
|
762
913
|
|
|
763
914
|
**方案:显式配置(安全可控)**
|
|
915
|
+
|
|
764
916
|
```js
|
|
765
917
|
subParams: {
|
|
766
918
|
'订单明细keyName': { f_status: 1, f_type: 'A' }, // 静态参数
|
|
@@ -768,12 +920,15 @@ subParams: {
|
|
|
768
920
|
},
|
|
769
921
|
commonParams: { ... }, // 所有明细包都带的公共参数
|
|
770
922
|
```
|
|
923
|
+
|
|
771
924
|
merge 进明细包的 `data`(与 `{id, attachmentType, thirdtabm}` 合并)。
|
|
925
|
+
|
|
772
926
|
> 函数形态可拿到 `ctx.masterData`(第 2 步取回的主表数据)—— 这是「主表先查」顺带带来的能力。
|
|
773
927
|
>
|
|
774
928
|
> 兜底开关(可选):`useSourceScriptParam: true` 尝试在 form-render 上下文执行源模板 data-table 的 `formScriptParam`,`try/catch` 失败则忽略;不作为默认。
|
|
775
929
|
|
|
776
930
|
### 10.5.7 配置全貌
|
|
931
|
+
|
|
777
932
|
```js
|
|
778
933
|
formRef.enableCompareBySource(搜索框的值, {
|
|
779
934
|
sourceFormCode: '订单formCode', // ★ 跨表必填,否则按变更单模板枚举子表 → 明细取不到
|
|
@@ -792,34 +947,38 @@ formRef.enableCompareBySource(搜索框的值, {
|
|
|
792
947
|
// transformOldData: (oldData, sourceTpl) => 归一化后的oldData, // 值转换兜底
|
|
793
948
|
});
|
|
794
949
|
```
|
|
950
|
+
|
|
795
951
|
模式 A(`initChangeFromSource`)同样接收上述选项 —— 跨表时**填充变更单也必须走映射**,否则字段名对不上、数据填不进去。
|
|
796
952
|
|
|
797
953
|
### 10.5.8 语义/边界
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
|
801
|
-
|
|
|
802
|
-
|
|
|
803
|
-
|
|
|
804
|
-
|
|
|
805
|
-
|
|
|
806
|
-
|
|
|
954
|
+
|
|
955
|
+
| 情况 | 处理 |
|
|
956
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
957
|
+
| 源单有、变更单没有的字段 | 映射表不写 → 白名单天然忽略 |
|
|
958
|
+
| 变更单有、源单没有的字段 | 不写就不比;写了 `to` 但源无 `from` → 旧值空 → 显示「变更前:(空)」 |
|
|
959
|
+
| 源明细有、变更单未匹配上 | → 进「查看删除明细」(源单有、当前未引用) |
|
|
960
|
+
| 变更单明细有、源单未匹配上 | → 标「新增行」(绿) |
|
|
961
|
+
| **未映射的子表** | **完全不参与对比**(不高亮、删除列表为空)——不做这条会**满屏绿** |
|
|
962
|
+
| 值需要转换(单位/币种/编码体系) | 用 `transformOldData` 钩子 |
|
|
963
|
+
| 清洗 `sanitizeChangeData` | 作用在**归一化后**的数据上,系统字段清理/树表 id 重生成/附件白名单照常工作;`subSourceFields` 回填订单明细 id 依然推荐(给出硬引用,将来行匹配可从业务键切到来源明细 id) |
|
|
807
964
|
|
|
808
965
|
### 10.5.9 改动清单(8 项,已全部实现)
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
|
812
|
-
|
|
|
813
|
-
|
|
|
814
|
-
|
|
|
815
|
-
|
|
|
816
|
-
|
|
|
817
|
-
|
|
|
818
|
-
|
|
|
819
|
-
|
|
820
|
-
|
|
966
|
+
|
|
967
|
+
| # | 改动 | 落地方法 |
|
|
968
|
+
| --- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
969
|
+
| 1 | ✅ `compareMapping` + 源数据归一化到变更单键空间 | `normalizeBySourceMapping(srcData, mapping)` → `{ data, includeFields, rowKeyMap }` |
|
|
970
|
+
| 2 | ✅ 白名单对比(由 mapping 的 `to` 自动推导) | 状态 `compareIncludeFields`;`isCompareExcludeField` 改为「白名单优先:设了白名单则不在名单内一律排除,黑名单失效」。wrapper 的 `compareExcluded` 与行级 `isCompareRowValuesChanged` 都走这一个判定 |
|
|
971
|
+
| 3 | ✅ `compareRowKey` 按子表配置 | 状态 `compareSubRowKeys`(由 `rowKey.to` 自动推导,可被 `options.subRowKeys` 覆盖);新增 `getCompareRowKeyOf(subFormName)`,`applyCompareSubSnapshot / getCompareRowClass / getDelData` 全部改用它 |
|
|
972
|
+
| 4 | ✅ 未映射子表不参与对比 | 新增 `isCompareSubTable(subFormName)`(判据:旧数据里有无该子表数组);`getCompareRowClass` 返回 `''`、`getDelData` 返回 `[]`。单元格侧因 `hData` 缺失 → `compareRowIsNew` → 天然不显示感叹号 |
|
|
973
|
+
| 5 | ✅ `initChangeFromSource` 接入映射 | 先 `applyCompareSourceTransform` 归一化,再 `sanitizeChangeData` → `setFormData` → `enableCompare` |
|
|
974
|
+
| 6 | ✅ `transformOldData` 钩子 | `applyCompareSourceTransform` 里,归一化之后调用 `options.transformOldData(data, srcData)` |
|
|
975
|
+
| 7 | ✅ `loadOldData` 主表先查 → 用真实 id 查明细 | 串行:主表包 `data:{[sourceKeyField]: 入参}` → `masterId = objx[sourceIdField]` → 再并行发表头附件包/明细包。`sourceIdIsKey:true` 可走并行快路径;主表查不到或取不到主键则不发明细包 |
|
|
976
|
+
| 8 | ✅ 明细额外查询参数透传 | `commonParams` + `subParams`(对象或 `(ctx)=>对象`,`ctx.masterData/masterId`)merge 进明细包 `data` |
|
|
977
|
+
|
|
978
|
+
**向后兼容**:不配 `compareMapping` 时 `applyCompareSourceTransform` 原样返回,`compareIncludeFields=null` → 走黑名单,`compareSubRowKeys={}` → 走全局 `compareRowKey`,`isCompareSubTable` 对已有子表恒为 true。**同表场景(现有模式 A/B)行为不变**,仅 `loadOldData` 多一次串行等待(可用 `sourceIdIsKey:true` 消除)。
|
|
821
979
|
|
|
822
980
|
## 十一、说明 / 注意事项
|
|
981
|
+
|
|
823
982
|
- **对比图标注入点**:统一在 `form-item-wrapper.vue`,覆盖主表字段与三类表格容器的单元格(均经 field-widget + tableParam.row 渲染);仅对比态包裹 compareView,非对比态零改动。
|
|
824
983
|
- **「查看删除数据」按钮**:未在容器工具栏内置,由业务按钮 onClick 调 `openCompareDelList('子表名')` 触发(更灵活)。
|
|
825
984
|
- **子表快照时序**:`enableCompare`/`setOldSubData` 对当前已加载行打 `hData` 快照;若子表在开启对比后才异步加载,需在其加载完成后再调一次 `setOldSubData` 补快照(多包流程天然满足)。
|