cloud-web-corejs 1.1.0-dev.14 → 1.1.0-dev.16
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/2026-09 /347/274/226/350/276/221/350/241/250/350/241/214/345/206/205/344/277/235/345/255/230/346/240/241/351/252/214/350/214/203/345/233/264/344/270/216/345/210/227/345/277/205/345/241/253/350/247/243/346/236/220/344/277/256/345/244/215.md" +233 -0
- package/src/components/xform/form-render/container-item/data-table-mixin.js +96 -10
- package/src/components/xform/form-render/container-item/list-h5-item.vue +4 -1
- package/src/components/xform/styles/h5.scss +43 -3
- package/src/components/xform/utils/tableColumnHelper.js +46 -4
package/package.json
CHANGED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# 编辑表行内保存的校验范围 & 列必填解析 —— 修复文档(2026-09-03)
|
|
2
|
+
|
|
3
|
+
> 状态:**两项均已编码 + 单测过,待联调**。
|
|
4
|
+
> 改动文件:`src/components/xform/form-render/container-item/data-table-mixin.js`、
|
|
5
|
+
> `src/components/xform/utils/tableColumnHelper.js`。
|
|
6
|
+
> 新增单测:`tests/unit/components/xform/editRowValidateScope.spec.js`(6 条)、
|
|
7
|
+
> `tests/unit/components/xform/columnRequiredFlag.spec.js`(7 条)。
|
|
8
|
+
> 行号均为 2026-09-03 工作区快照,后续会漂移,以函数名为准。
|
|
9
|
+
> 同步:dev 与 stdxx 两分支均**未搬**。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 一、两个问题
|
|
14
|
+
|
|
15
|
+
| # | 现象 | 根因一句话 |
|
|
16
|
+
|---|------|-----------|
|
|
17
|
+
| A | 编辑(树)表点行内保存 ✓,别的行缺必填、甚至表格外的字段没填,都会把这一行的保存拦住 | `saveEditRow` 走的是整表 + 全表单的 `formRef.validate` |
|
|
18
|
+
| B | 某列 `required` 与 `editWidget.options.required` 都是 `false`,运行起来却必填(表头星号 + 拦保存) | 列在设计器里配过展示组件后又改回空,残留的 `columnOption.required` 仍被 `resolveColumnRequiredFlag` 无条件读取 |
|
|
19
|
+
|
|
20
|
+
两者独立,但都落在"必填校验"这条线上,一起记。
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 二、问题 A:行内保存跑了全表校验
|
|
25
|
+
|
|
26
|
+
### 2.1 原链路
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
data-table-item.vue #editTreeButtonGroup 插槽的「保存」a-link
|
|
30
|
+
→ saveEditRow(obj) [data-table-mixin.js:4469]
|
|
31
|
+
→ formRef.validate(resolve) ← 整表 + 表外字段
|
|
32
|
+
→ el-form.$baseValidate 原生规则(只覆盖已渲染的 form-item)
|
|
33
|
+
→ runExtraFormValidations() [indexMixin.js:3862]
|
|
34
|
+
├─ validateArrayValueFieldsRequired() 全表单数组类值字段
|
|
35
|
+
├─ validateDataTableCellsRequired() **每个数据表格逐行**必填
|
|
36
|
+
└─ validateDataTableDatasetRequired() 数据表整体必填
|
|
37
|
+
→ $baseConfirm → clearActived/setActiveRow → formHttp(只发当前行)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
保存粒度是**行**(`requestRow = obj.row`,报文里只有这一行),校验粒度却是**整表单**。
|
|
41
|
+
|
|
42
|
+
### 2.2 改动
|
|
43
|
+
|
|
44
|
+
`validateCellsRequired` 抽出核心循环,让"校验哪些行"与"prop 里的行下标"分离:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// data-table-mixin.js:1045
|
|
48
|
+
validateCellsRequired() {
|
|
49
|
+
...
|
|
50
|
+
return this.runCellsRequiredCheck(rows, rows); // 整体保存:全表逐行
|
|
51
|
+
},
|
|
52
|
+
// data-table-mixin.js:1063
|
|
53
|
+
runCellsRequiredCheck(targetRows, indexRows) { ... }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `targetRows` —— 本次要校验的行;
|
|
57
|
+
- `indexRows` —— 求行下标用的**完整数据集**。
|
|
58
|
+
单行校验时**必须**传全量数据集,否则 `getValidationRowIndex` 拿到的是 0,
|
|
59
|
+
拼出来的 `items.0.f_code` 会定位到错误的行。
|
|
60
|
+
|
|
61
|
+
新增三个方法:
|
|
62
|
+
|
|
63
|
+
| 方法 | 位置 | 职责 |
|
|
64
|
+
|------|------|------|
|
|
65
|
+
| `validateEditRowRequired(row)` | `data-table-mixin.js:1130` | 只对该行跑必填(内部 `runCellsRequiredCheck([row], 全量)`) |
|
|
66
|
+
| `collectRowValidateProps(row)` | `data-table-mixin.js:1141` | 收该行的单元格 prop,按真实行下标拼,并**按 `renderForm.fields` 过滤掉未注册的** |
|
|
67
|
+
| `validateEditRow(row)` | `data-table-mixin.js:1184` | 入口:原生规则 → 必填兜底,任一失败弹消息返回 false |
|
|
68
|
+
|
|
69
|
+
`saveEditRow` 的第一步随之改为:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
// 行内保存只提交当前行,校验范围同步收敛到当前行:其他行与表外字段
|
|
73
|
+
// 的必填不该拦住单行保存(整体保存仍走 formRef.validate 的全量校验)
|
|
74
|
+
if (!this.validateEditRow(obj.row)) return false;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2.3 行为对照
|
|
78
|
+
|
|
79
|
+
| 通道 | 原生规则(正则 / 自定义 validator) | 必填 |
|
|
80
|
+
|------|--------------------------------|------|
|
|
81
|
+
| 整体保存(`formRef.validate`) | 全表单已渲染的 form-item | 全表逐行 + 数据集必填 + 数组类字段 |
|
|
82
|
+
| 行内保存(`validateEditRow`) | **仅当前行**已注册的 prop | **仅当前行** |
|
|
83
|
+
|
|
84
|
+
### 2.4 两个坑(已在实现里处理)
|
|
85
|
+
|
|
86
|
+
1. **prop 必须先按 `renderForm.fields` 过滤**。element 的 `validateField(props, cb)` 在传入的 prop
|
|
87
|
+
一个都没注册时会打 `[Element Warn] please pass correct props!`。编辑表用的是
|
|
88
|
+
vxe `editConfig: { trigger: "manual", mode: "row" }`,`edit` 插槽只在**正在编辑的那一行**渲染,
|
|
89
|
+
未编辑行的 prop 本就不在 `fields` 里。
|
|
90
|
+
2. **`getValidationRows` 读的是 computed `fieldKeyName`**,不是 `getFieldKeyName(this.widget)`。
|
|
91
|
+
写单测造上下文时必须直给 `fieldKeyName`,否则会掉进 `$grid.getTableData()` 兜底分支。
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 三、问题 B:残留 `columnOption` 让列凭空必填
|
|
96
|
+
|
|
97
|
+
### 3.1 判据
|
|
98
|
+
|
|
99
|
+
问题列的模板 JSON(节选):
|
|
100
|
+
|
|
101
|
+
| 位置 | 值 |
|
|
102
|
+
|------|-----|
|
|
103
|
+
| 列级 `required` | `false` |
|
|
104
|
+
| `formatS` / `widget` | `""` / `null` ← 展示组件已改回空 |
|
|
105
|
+
| `editFormatS` / `editWidget.options.required` | `"editInput"` / `false` |
|
|
106
|
+
| **`columnOption.required`** | **`true`** ← 残留快照 |
|
|
107
|
+
|
|
108
|
+
### 3.2 根因:两条读取路径口径不一致
|
|
109
|
+
|
|
110
|
+
| 路径 | 读什么 | 结果 |
|
|
111
|
+
|------|--------|------|
|
|
112
|
+
| 加载时 `applyColumnMetaToFieldWidget`(`util.js:919`,由 `handleTableColumnWidget`(`indexMixin.js:674`)调用) | **只读对应侧**:编辑侧读 `editWidgetOptions ?? editColumnOption` | `editWidget.options.required` = `false` ✅ |
|
|
113
|
+
| 运行时 `resolveColumnRequiredFlag`(`tableColumnHelper.js:90`) | 改动前**两侧都读、且不看 `formatS`**:`widgetOptions ?? columnOption` | `true` ❌ |
|
|
114
|
+
|
|
115
|
+
该 flag 有**两个出口**,所以现象是"星号 + 拦保存"一起来:
|
|
116
|
+
|
|
117
|
+
- `applyColumnLabelIcon`(`tableColumnHelper.js:126`)→ 表头画 `vxe-cell--required-icon`;
|
|
118
|
+
- `isCellFieldRequired`(`tableCellValidateUtil.js:52`)→ 组件自身 `required` 为 false 时**落到**该 flag → 进必填校验。
|
|
119
|
+
|
|
120
|
+
### 3.3 改动
|
|
121
|
+
|
|
122
|
+
新增两个内部函数,给静态列的列级 option 加门控:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
// tableColumnHelper.js:12
|
|
126
|
+
function hasColumnWidgetSide(columnConfig, isEdit) {
|
|
127
|
+
if (isEdit) {
|
|
128
|
+
return !!(columnConfig.editWidget || columnFormatMap[columnConfig.editFormatS]);
|
|
129
|
+
}
|
|
130
|
+
return !!(columnConfig.widget || columnFormatMap[columnConfig.formatS]);
|
|
131
|
+
}
|
|
132
|
+
// tableColumnHelper.js:32
|
|
133
|
+
function getEffectiveColumnOptions(columnConfig, isEdit) { ... }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`resolveColumnRequiredFlag` 的兜底改为:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const widgetOptions = getEffectiveColumnOptions(columnConfig, false);
|
|
140
|
+
const editWidgetOptions = getEffectiveColumnOptions(columnConfig, true);
|
|
141
|
+
return !!(widgetOptions?.required || editWidgetOptions?.required);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
口径:
|
|
145
|
+
|
|
146
|
+
- **动态列**的 `widgetOptions` / `editWidgetOptions` 直接生效(它们本就没有 `formatS`,不门控);
|
|
147
|
+
- **设计器静态列**的 `columnOption` / `editColumnOption` 只在该侧
|
|
148
|
+
`columnFormatMap[formatS]` 能映射出组件类型(或 widget 已实例化)时才读。
|
|
149
|
+
|
|
150
|
+
**门控判据必须用 `columnFormatMap`,不能用"`formatS` 非空"** —— 纯格式化列(`formatS: "d1"` 这类格式码)
|
|
151
|
+
同样不建 widget,那份残留 option 也不该作数;用 `columnFormatMap` 才与加载时的实例化口径完全一致。
|
|
152
|
+
|
|
153
|
+
### 3.4 排除项:展示态不会被误渲染成组件
|
|
154
|
+
|
|
155
|
+
展示侧组件的实例化在三条路径上都以 `columnFormatMap[formatS]` 为门:
|
|
156
|
+
|
|
157
|
+
| 路径 | 位置 |
|
|
158
|
+
|------|------|
|
|
159
|
+
| 运行态 | `form-render/indexMixin.js:690` |
|
|
160
|
+
| 设计器 | `form-designer/form-widget/container-widget/data-table-mixin.js:220` |
|
|
161
|
+
| H5 列表 | `form-render/container-item/list-h5-item.vue:1692` |
|
|
162
|
+
|
|
163
|
+
`formatS` 为空 → widget 保持 null → `createColumn` 的展示插槽 if 链(`data-table-mixin.js:2158`)
|
|
164
|
+
一条都不命中 → 单元格走 `formatter: formatterValue` 纯文本。
|
|
165
|
+
`addColumProperty`(`data-table-mixin.js:2011`)展开的 `columnOption` 取自 **widget 实例**的 options,
|
|
166
|
+
不是 JSON 里那份残留。
|
|
167
|
+
|
|
168
|
+
所以残留 `columnOption` 在改动前唯一漏出来的地方就是 `resolveColumnRequiredFlag` ——
|
|
169
|
+
全仓读 `columnOption` 的点里只有它不看 `formatS`。
|
|
170
|
+
|
|
171
|
+
### 3.5 老数据不做清理
|
|
172
|
+
|
|
173
|
+
渲染路径对这类残留**只有补齐、没有校验、也没有清理**:`handleTableColumnWidget` 是单向的
|
|
174
|
+
(`formatS` 映射得出组件才建 widget),不会比对"widget 不存在但 columnOption 还在",
|
|
175
|
+
全仓也没有任何删除 `columnOption` 的写法;设计器侧 `cloneTableColumnsForEdit`
|
|
176
|
+
(`table-column-dialog.vue:1107`)还会在 `!row.widget` 时把它深拷贝带进编辑态再写回模板。
|
|
177
|
+
|
|
178
|
+
结论:**DB 里的残留不必清** —— 代码侧已经兜住,清理反而要动存量模板。
|
|
179
|
+
若要早发现,最轻的一档是在 `handleTableColumnWidget` 里对
|
|
180
|
+
"该侧无组件却带着 columnOption"仅开发环境 `console.warn`(未实施)。
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 四、改动清单
|
|
185
|
+
|
|
186
|
+
| 文件 | 改动 |
|
|
187
|
+
|------|------|
|
|
188
|
+
| `form-render/container-item/data-table-mixin.js` | `validateCellsRequired` 抽出 `runCellsRequiredCheck(targetRows, indexRows)`;新增 `validateEditRowRequired` / `collectRowValidateProps` / `validateEditRow`;`saveEditRow` 首步由 `formRef.validate` 换成 `validateEditRow(obj.row)` |
|
|
189
|
+
| `utils/tableColumnHelper.js` | 新增 `hasColumnWidgetSide` / `getEffectiveColumnOptions`(并 `import { columnFormatMap } from "./util"`);`resolveColumnRequiredFlag` 兜底改走门控 |
|
|
190
|
+
| `tests/unit/components/xform/editRowValidateScope.spec.js` | 新增,6 条 |
|
|
191
|
+
| `tests/unit/components/xform/columnRequiredFlag.spec.js` | 新增,7 条 |
|
|
192
|
+
|
|
193
|
+
未改动、行为保持不变:`validateForm` / `validate` / `runExtraFormValidations` 三个整体保存入口,
|
|
194
|
+
`validateCheckRow`(勾选行操作的行级校验),H5 列表的必填口径(只读列级 `t.required`)。
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 五、验证
|
|
199
|
+
|
|
200
|
+
- `npx jest tests/unit/components/xform` —— 28 套全过(含两个新增 spec)。
|
|
201
|
+
- `npx jest tests/unit/components` —— 49 套 712 条,711 通过;唯一失败
|
|
202
|
+
`mobile/h5PopDialog.spec.js`(`selectUserDialog.vue` 缺 `custom-class="h5-pop-dialog"`)
|
|
203
|
+
属 H5 wf 弹层那条线的既有状态,**与本次改动无关**。
|
|
204
|
+
- eslint:`data-table-mixin.js` 无新增报错(51 条 operator-linebreak 均为既有欠账,
|
|
205
|
+
行号不落在新增段);`tableColumnHelper.js` 由 4 条降至 2 条。
|
|
206
|
+
|
|
207
|
+
单测覆盖点:
|
|
208
|
+
|
|
209
|
+
| spec | 覆盖 |
|
|
210
|
+
|------|------|
|
|
211
|
+
| editRowValidateScope | 整体保存仍全表拦 / 别的行缺必填不拦当前行 / 当前行自己缺必填仍拦 / prop 带真实行下标且只发当前行 / 未注册 prop 不下发 / 原生错优先于必填兜底 |
|
|
212
|
+
| columnRequiredFlag | 残留 columnOption 不再必填 / 老数据(formatS 有值、widget 未实例化)仍必填 / 纯格式化列不读 / editColumnOption 按 editFormatS 门控 / 动态列不受门控 / 列级与实例 required 照旧 / 空配置 |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 六、影响面与联调点
|
|
217
|
+
|
|
218
|
+
| 变更 | 影响面 | 联调重点 |
|
|
219
|
+
|------|--------|----------|
|
|
220
|
+
| A | 仅编辑树表的 `editTreeButtonGroup` 行内保存 | 当前行必填仍拦、错误能定位到该行;他行/表外字段缺失不再误拦;原生正则规则在当前行仍生效 |
|
|
221
|
+
| B | 全量表格的必填标记(表头星号)与单元格必填校验 | 抽查若干正常配置的必填列,星号与校验都还在;问题列星号消失、不再拦保存 |
|
|
222
|
+
|
|
223
|
+
B 的潜在风险:若有表单**依赖**这份残留 `columnOption.required` 来实现必填(即列级和组件级都没配、
|
|
224
|
+
全靠残留生效),改后会变成非必填。属于"配置本就不合规"的情况,联调时留意用户反馈的
|
|
225
|
+
"某列突然不必填了",正解是去列配置或组件上把 required 补回。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 七、待办
|
|
230
|
+
|
|
231
|
+
1. 真机联调(两项均未联调)。
|
|
232
|
+
2. **同步 dev 与 stdxx 两分支**(按既有同步规则:JS 可整体覆盖、样式不覆盖)。
|
|
233
|
+
3. 可选:`handleTableColumnWidget` 的 dev-only `console.warn`(残留列级 option 早发现),未实施。
|
|
@@ -1052,6 +1052,16 @@ modules = {
|
|
|
1052
1052
|
return null;
|
|
1053
1053
|
}
|
|
1054
1054
|
|
|
1055
|
+
return this.runCellsRequiredCheck(rows, rows);
|
|
1056
|
+
},
|
|
1057
|
+
/**
|
|
1058
|
+
* 逐单元格必填校验的核心循环。
|
|
1059
|
+
* @param {Array} targetRows 本次要校验的行。
|
|
1060
|
+
* @param {Array} indexRows 求行下标用的完整数据集(决定校验 prop 里的行下标)。
|
|
1061
|
+
* @returns {String|null} 首个错误提示,全部通过返回 null。
|
|
1062
|
+
*/
|
|
1063
|
+
runCellsRequiredCheck(targetRows, indexRows) {
|
|
1064
|
+
const rows = indexRows || [];
|
|
1055
1065
|
const isEditTable = this.widget.options.isEditTable || false;
|
|
1056
1066
|
const tableColumns = this.widget.options.tableColumns || [];
|
|
1057
1067
|
const tableFieldKeyName = this.getFieldKeyName(this.widget);
|
|
@@ -1061,8 +1071,8 @@ modules = {
|
|
|
1061
1071
|
this.collectColumnValidateEntries(column, isEditTable, columnEntries);
|
|
1062
1072
|
});
|
|
1063
1073
|
|
|
1064
|
-
for (let rowIndex = 0; rowIndex <
|
|
1065
|
-
const row =
|
|
1074
|
+
for (let rowIndex = 0; rowIndex < targetRows.length; rowIndex++) {
|
|
1075
|
+
const row = targetRows[rowIndex];
|
|
1066
1076
|
if (!row) {
|
|
1067
1077
|
continue;
|
|
1068
1078
|
}
|
|
@@ -1113,6 +1123,87 @@ modules = {
|
|
|
1113
1123
|
|
|
1114
1124
|
return null;
|
|
1115
1125
|
},
|
|
1126
|
+
/**
|
|
1127
|
+
* 行内保存用:只校验当前行的必填,不牵动其他行。
|
|
1128
|
+
* @param {Object} row 当前行。@returns {String|null} 错误提示,通过为 null。
|
|
1129
|
+
*/
|
|
1130
|
+
validateEditRowRequired(row) {
|
|
1131
|
+
if (!row || this.widget.options.hidden || this.designState) {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
const rows = this.getValidationRows();
|
|
1135
|
+
return this.runCellsRequiredCheck([row], rows.length ? rows : [row]);
|
|
1136
|
+
},
|
|
1137
|
+
/**
|
|
1138
|
+
* 收集当前行所有参与校验的单元格 prop(用于原生规则的行级校验)。
|
|
1139
|
+
* @param {Object} row 当前行。@returns {Array<String>} 已在表单里注册的 prop 列表。
|
|
1140
|
+
*/
|
|
1141
|
+
collectRowValidateProps(row) {
|
|
1142
|
+
const formRef = this.getFormRef?.();
|
|
1143
|
+
const renderForm = formRef?.$refs?.renderForm;
|
|
1144
|
+
if (!row || !renderForm) {
|
|
1145
|
+
return [];
|
|
1146
|
+
}
|
|
1147
|
+
const isEditTable = this.widget.options.isEditTable || false;
|
|
1148
|
+
const tableColumns = this.widget.options.tableColumns || [];
|
|
1149
|
+
const tableFieldKeyName = this.getFieldKeyName(this.widget);
|
|
1150
|
+
const rows = this.getValidationRows();
|
|
1151
|
+
const rowIndex = this.getValidationRowIndex(row, rows, 0);
|
|
1152
|
+
const columnEntries = [];
|
|
1153
|
+
this.loodHandleColumns(tableColumns, (column) => {
|
|
1154
|
+
this.collectColumnValidateEntries(column, isEditTable, columnEntries);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
// 只保留真正注册进 el-form 的 prop:validateField 传入全部未注册的 prop 会打警告
|
|
1158
|
+
const registered = new Set(
|
|
1159
|
+
(renderForm.fields || []).map((item) => item.prop)
|
|
1160
|
+
);
|
|
1161
|
+
const props = [];
|
|
1162
|
+
columnEntries.forEach(({ column, templateWidget }) => {
|
|
1163
|
+
const fieldWidget = this.resolveRowFieldWidget(row, templateWidget);
|
|
1164
|
+
if (shouldSkipCellValidation(fieldWidget)) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const property = getCellFieldKey(fieldWidget, column);
|
|
1168
|
+
const propName = buildTableCellFormProp(
|
|
1169
|
+
tableFieldKeyName,
|
|
1170
|
+
rowIndex,
|
|
1171
|
+
property
|
|
1172
|
+
);
|
|
1173
|
+
if (propName && registered.has(propName) && !props.includes(propName)) {
|
|
1174
|
+
props.push(propName);
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
return props;
|
|
1178
|
+
},
|
|
1179
|
+
/**
|
|
1180
|
+
* 行内保存的校验入口:范围收敛到当前行(原生规则 + 必填兜底),
|
|
1181
|
+
* 不校验其他行、也不校验表格外的表单字段。
|
|
1182
|
+
* @param {Object} row 当前行。@returns {Boolean} 通过为 true。
|
|
1183
|
+
*/
|
|
1184
|
+
validateEditRow(row) {
|
|
1185
|
+
const formRef = this.getFormRef?.();
|
|
1186
|
+
const renderForm = formRef?.$refs?.renderForm;
|
|
1187
|
+
const props = this.collectRowValidateProps(row);
|
|
1188
|
+
let nativeError = null;
|
|
1189
|
+
if (renderForm && props.length) {
|
|
1190
|
+
renderForm.validateField(props, (errorMsg) => {
|
|
1191
|
+
if (!nativeError && errorMsg) {
|
|
1192
|
+
nativeError = errorMsg;
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
if (nativeError) {
|
|
1197
|
+
this.$message.error(nativeError);
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
const requiredError = this.validateEditRowRequired(row);
|
|
1201
|
+
if (requiredError) {
|
|
1202
|
+
this.$message.error(requiredError);
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
return true;
|
|
1206
|
+
},
|
|
1116
1207
|
/** @param {Boolean} required 是否将整个数据表设为必填。 */
|
|
1117
1208
|
setRequired(required) {
|
|
1118
1209
|
this.$set(this.widget.options, "required", !!required);
|
|
@@ -4429,14 +4520,9 @@ modules = {
|
|
|
4429
4520
|
let logicalRowKey = obj.row?._X_ROW_KEY || null;
|
|
4430
4521
|
let originalPhysicalId = null;
|
|
4431
4522
|
try {
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
} catch (error) {
|
|
4436
|
-
reject(error);
|
|
4437
|
-
}
|
|
4438
|
-
});
|
|
4439
|
-
if (!valid) return false;
|
|
4523
|
+
// 行内保存只提交当前行,校验范围同步收敛到当前行:其他行与表外字段
|
|
4524
|
+
// 的必填不该拦住单行保存(整体保存仍走 formRef.validate 的全量校验)
|
|
4525
|
+
if (!this.validateEditRow(obj.row)) return false;
|
|
4440
4526
|
|
|
4441
4527
|
let requestRow = obj.row;
|
|
4442
4528
|
if (treeContext) {
|
|
@@ -604,7 +604,10 @@
|
|
|
604
604
|
<template
|
|
605
605
|
v-if="!widget.options.isQueryTable || !column.isMainFiled"
|
|
606
606
|
>
|
|
607
|
-
|
|
607
|
+
<!-- 与列表卡片同口径:列配了 H5列宽=半行 时打 m2。
|
|
608
|
+
弹框 append-to-body,样式挂在 .el-dialog.h5view-dialog,
|
|
609
|
+
见 styles/h5.scss。 -->
|
|
610
|
+
<dl :key="columnIndex" :class="{ m2: isHalfColumn(column) }">
|
|
608
611
|
<div>
|
|
609
612
|
<dt>{{ column.title }}:</dt>
|
|
610
613
|
<dd>
|
|
@@ -746,8 +746,10 @@ $xformH5Size:14px;
|
|
|
746
746
|
}
|
|
747
747
|
}
|
|
748
748
|
|
|
749
|
-
/* dd
|
|
750
|
-
|
|
749
|
+
/* dd 里是真控件的那份。列表行本身只出纯文本。
|
|
750
|
+
明细详情弹框 append-to-body,不在 .xform-h5 下,真正生效的是
|
|
751
|
+
下方 .el-dialog.h5view-dialog 里同一口径的规则;这段留给弹框
|
|
752
|
+
万一没挂到 body 时的兜底。 */
|
|
751
753
|
.xform-h5 .h5-list-box .item .t2 > dl > div > dd {
|
|
752
754
|
margin:0;
|
|
753
755
|
/* 压 style.scss:14112 的 width:calc(100vw - 159px),本方 (0,5,2) > (0,4,1) */
|
|
@@ -1789,13 +1791,51 @@ $xformH5Size:14px;
|
|
|
1789
1791
|
margin:12px;padding:0 !important;background:none !important;
|
|
1790
1792
|
.item{
|
|
1791
1793
|
border:none;
|
|
1794
|
+
/* 明细详情弹框 append-to-body,节点不在 .xform-h5 里,
|
|
1795
|
+
列表卡片那套 `.xform-h5 .h5-list-box .item .t2` 一行两个打不到。
|
|
1796
|
+
这里按同一口径接上:.t2 变 flex 容器,dl.m2 占 50%。 */
|
|
1792
1797
|
.t2{
|
|
1793
|
-
|
|
1798
|
+
display: flex;
|
|
1799
|
+
flex-wrap: wrap;
|
|
1800
|
+
align-items: flex-start;
|
|
1801
|
+
> dl{
|
|
1794
1802
|
font-size:14px;color:#666;
|
|
1803
|
+
&.m2 {
|
|
1804
|
+
width: 50%;
|
|
1805
|
+
}
|
|
1806
|
+
&.m2 > div {
|
|
1807
|
+
flex-wrap: wrap;
|
|
1808
|
+
}
|
|
1809
|
+
&.m2 > div > dd {
|
|
1810
|
+
flex: 1 1 auto;
|
|
1811
|
+
min-width: 0;
|
|
1812
|
+
}
|
|
1795
1813
|
dd{
|
|
1796
1814
|
margin:0;
|
|
1797
1815
|
color:#323232;
|
|
1798
1816
|
text-align:right;
|
|
1817
|
+
min-width: 0;
|
|
1818
|
+
/* 压 style.scss:14112 的 width:calc(100vw - 159px)。
|
|
1819
|
+
弹框里是真控件,半行净宽撑不住整屏宽。 */
|
|
1820
|
+
.field-wrapper,
|
|
1821
|
+
.field-wrapper.h5-field {
|
|
1822
|
+
display: block;
|
|
1823
|
+
width: auto;
|
|
1824
|
+
}
|
|
1825
|
+
.field-wrapper.h5-field .el-form-item {
|
|
1826
|
+
display: block;
|
|
1827
|
+
min-height: 0;
|
|
1828
|
+
margin-bottom: 0;
|
|
1829
|
+
padding: 0;
|
|
1830
|
+
border-bottom: none;
|
|
1831
|
+
}
|
|
1832
|
+
/* dt 已经出过字段名,藏掉控件自带的第二份 label */
|
|
1833
|
+
.field-wrapper.h5-field .el-form-item > .el-form-item__label {
|
|
1834
|
+
display: none;
|
|
1835
|
+
}
|
|
1836
|
+
.field-wrapper.h5-field .el-form-item > .el-form-item__content {
|
|
1837
|
+
margin-left: 0 !important;
|
|
1838
|
+
}
|
|
1799
1839
|
}
|
|
1800
1840
|
}
|
|
1801
1841
|
}
|
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
import { columnFormatMap } from "./util";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 列的某一侧(展示 / 编辑)是否真的配了组件。
|
|
5
|
+
*
|
|
6
|
+
* 判据与表单加载时的实例化口径一致(见 form-render/indexMixin 的 columnLoopDo):
|
|
7
|
+
* 只有 formatS / editFormatS 能映射出组件类型时该侧才会建 widget。纯格式化列
|
|
8
|
+
* (formatS 是 d1 这类格式码)与被改回空的列都不算。
|
|
9
|
+
* @param {Object} columnConfig 列配置。@param {Boolean} isEdit 是否编辑侧。
|
|
10
|
+
* @returns {Boolean}
|
|
11
|
+
*/
|
|
12
|
+
function hasColumnWidgetSide(columnConfig, isEdit) {
|
|
13
|
+
if (isEdit) {
|
|
14
|
+
return !!(
|
|
15
|
+
columnConfig.editWidget || columnFormatMap[columnConfig.editFormatS]
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return !!(columnConfig.widget || columnFormatMap[columnConfig.formatS]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 取该侧生效的列级组件配置。
|
|
23
|
+
*
|
|
24
|
+
* 动态列走 widgetOptions / editWidgetOptions(本来就没有 formatS),直接生效;
|
|
25
|
+
* 设计器静态列的 columnOption / editColumnOption 必须门控:展示组件被改回空
|
|
26
|
+
* 之后那份 columnOption 仍会留在模板 JSON 里,无条件读会让这列凭空变必填
|
|
27
|
+
* (表头星号 + 拦保存),而加载时的 applyColumnMetaToFieldWidget 只读对应侧,
|
|
28
|
+
* 两边口径不一致正是该现象的根因。
|
|
29
|
+
* @param {Object} columnConfig 列配置。@param {Boolean} isEdit 是否编辑侧。
|
|
30
|
+
* @returns {Object|null}
|
|
31
|
+
*/
|
|
32
|
+
function getEffectiveColumnOptions(columnConfig, isEdit) {
|
|
33
|
+
const dynamicOptions = isEdit
|
|
34
|
+
? columnConfig.editWidgetOptions
|
|
35
|
+
: columnConfig.widgetOptions;
|
|
36
|
+
if (dynamicOptions) {
|
|
37
|
+
return dynamicOptions;
|
|
38
|
+
}
|
|
39
|
+
if (!hasColumnWidgetSide(columnConfig, isEdit)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return isEdit ? columnConfig.editColumnOption : columnConfig.columnOption;
|
|
43
|
+
}
|
|
44
|
+
|
|
1
45
|
function buildColumnConfigMap(tableColumns, map = {}) {
|
|
2
46
|
if (!tableColumns || !tableColumns.length) {
|
|
3
47
|
return map;
|
|
@@ -56,10 +100,8 @@ export function resolveColumnRequiredFlag(columnConfig) {
|
|
|
56
100
|
if (columnConfig.widget?.options?.required) {
|
|
57
101
|
return true;
|
|
58
102
|
}
|
|
59
|
-
const widgetOptions =
|
|
60
|
-
|
|
61
|
-
const editWidgetOptions =
|
|
62
|
-
columnConfig.editWidgetOptions ?? columnConfig.editColumnOption;
|
|
103
|
+
const widgetOptions = getEffectiveColumnOptions(columnConfig, false);
|
|
104
|
+
const editWidgetOptions = getEffectiveColumnOptions(columnConfig, true);
|
|
63
105
|
return !!(widgetOptions?.required || editWidgetOptions?.required);
|
|
64
106
|
}
|
|
65
107
|
|