cloud-web-corejs 1.1.0-dev.13 → 1.1.0-dev.15
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/excelExport/mixins.js +13 -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 +107 -10
- package/src/components/xform/utils/tableColumnHelper.js +46 -4
package/package.json
CHANGED
|
@@ -82,6 +82,8 @@ function getGrid(that, tableRef) {
|
|
|
82
82
|
CURRENT_PREFIX: "",
|
|
83
83
|
option: {},
|
|
84
84
|
uuid: "",
|
|
85
|
+
// createExcelTemplate 返回的本次导出时间快照,随每页取数请求下发(见 loopToDo)
|
|
86
|
+
nowDate: null,
|
|
85
87
|
fileRows: [],
|
|
86
88
|
title: this.$t2("导出", "components.excelExport.title"),
|
|
87
89
|
tableTarget: null,
|
|
@@ -609,7 +611,17 @@ function getGrid(that, tableRef) {
|
|
|
609
611
|
// 取消后不再发起新页,也不再重试(重试是从 .catch 里递归回来的)
|
|
610
612
|
if (that.isExportStopped()) return;
|
|
611
613
|
let maxWaitNum = limitThreadNum;
|
|
612
|
-
|
|
614
|
+
// 分页取数的时间快照:createExcelTemplate 返回后存在 this.nowDate(见
|
|
615
|
+
// startProcess),此前这里读的是 options.nowDate —— 那是调用方入参,全库
|
|
616
|
+
// 没有任何调用方维护它,于是每页请求的 nowDate 恒为 undefined、被
|
|
617
|
+
// JSON.stringify 丢掉,后端拿不到快照点。导出期间数据发生增删时,分页会
|
|
618
|
+
// 整体前后错位,合出来的文件既漏行又重行,且全程不报错。
|
|
619
|
+
// 仍保留 options.nowDate 兜底:服务端未返回快照时,调用方若显式指定了一个,
|
|
620
|
+
// 用它总好过不带。
|
|
621
|
+
// 两条取数通道都要带:通用表格随请求体平铺下发;xform 走 exportAjax,快照
|
|
622
|
+
// 顺 aParam 进去后由 data-table-mixin.loadDefaultQueryList 提到信封层(与
|
|
623
|
+
// formCode 同级),不能留在内层 data —— 那是业务查询条件的地盘。
|
|
624
|
+
let nowDate = that.nowDate ?? options.nowDate;
|
|
613
625
|
if (flag === 1) {
|
|
614
626
|
/*if (num > that.tTotalPage) {
|
|
615
627
|
let size = page.records.length;
|
|
@@ -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);
|
|
@@ -3366,6 +3457,16 @@ modules = {
|
|
|
3366
3457
|
};
|
|
3367
3458
|
requestData = extendDeeply(requestData, accessParam);
|
|
3368
3459
|
|
|
3460
|
+
// 导出时间快照按 xform 协议放在**信封层**(与 formCode/taBm 同级),不能留在
|
|
3461
|
+
// data 里 —— 那一层是业务查询条件的地盘,后端按字段名当条件解析。它由
|
|
3462
|
+
// excelExport 的分页调度器塞进取数参数(mixins.js loopToDo 的 aParam),一路
|
|
3463
|
+
// 并进 requestData,到这里统一提上来。只有导出通道会带它。
|
|
3464
|
+
let nowDate;
|
|
3465
|
+
if (customParam?.export && requestData.nowDate !== undefined) {
|
|
3466
|
+
nowDate = requestData.nowDate;
|
|
3467
|
+
delete requestData.nowDate;
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3369
3470
|
return this.formHttp({
|
|
3370
3471
|
// url: prefix + "/form_ins/getPage",
|
|
3371
3472
|
scriptCode: scriptCode,
|
|
@@ -3373,6 +3474,7 @@ modules = {
|
|
|
3373
3474
|
formCode: formCode,
|
|
3374
3475
|
formVersion: reportTemplate.formVersion,
|
|
3375
3476
|
taBm: this.fieldKeyName,
|
|
3477
|
+
...(nowDate !== undefined ? { nowDate } : {}),
|
|
3376
3478
|
data: requestData,
|
|
3377
3479
|
},
|
|
3378
3480
|
isLoading: false,
|
|
@@ -4418,14 +4520,9 @@ modules = {
|
|
|
4418
4520
|
let logicalRowKey = obj.row?._X_ROW_KEY || null;
|
|
4419
4521
|
let originalPhysicalId = null;
|
|
4420
4522
|
try {
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
} catch (error) {
|
|
4425
|
-
reject(error);
|
|
4426
|
-
}
|
|
4427
|
-
});
|
|
4428
|
-
if (!valid) return false;
|
|
4523
|
+
// 行内保存只提交当前行,校验范围同步收敛到当前行:其他行与表外字段
|
|
4524
|
+
// 的必填不该拦住单行保存(整体保存仍走 formRef.validate 的全量校验)
|
|
4525
|
+
if (!this.validateEditRow(obj.row)) return false;
|
|
4429
4526
|
|
|
4430
4527
|
let requestRow = obj.row;
|
|
4431
4528
|
if (treeContext) {
|
|
@@ -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
|
|