cloud-web-corejs 1.1.0-dev.16 → 1.1.0-dev.17
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 /350/241/250/345/215/225/345/257/274/345/205/245/351/200/232/351/201/223/346/242/263/347/220/206/344/270/216/350/220/275/345/234/260/346/226/271/346/241/210.md" +745 -0
- package/src/components/xform/form-designer/form-widget/dialog/importDialog.vue +2 -2
- package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +2385 -1833
- package/src/components/xform/form-designer/form-widget/field-widget/import-button-widget.vue +18 -5
- package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import-button-editor.vue +62 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +90 -2
- package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +21 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
# 表单导入通道梳理与落地方案(2026-09-04)
|
|
2
|
+
|
|
3
|
+
> 状态:**P0 与 P1 全部已编码 + 单测过,待联调**(P0 #7 原本暂缓,已于 10.7 落地;
|
|
4
|
+
> 10.8 是随后一轮代码审查的修复);P2 仍是方案(其中「入表去重」已随 P1 顺带做掉)。
|
|
5
|
+
> 实施记录见第 10 节。本文其余部分是后续实现、测试与验收的基线。
|
|
6
|
+
> 范围:xform 的「导入」(`import-button`)、「明细导入」(`import2-button`) 两个按钮,
|
|
7
|
+
> 及其共用的导入弹框(三份同源副本),不含 `excelExport` / `jsonImport` / `langImport`。
|
|
8
|
+
> 行号为 2026-09-04 工作区快照,后续会漂移,**以函数名为准**。
|
|
9
|
+
> 同步:本文写于 com@publish;按既有惯例编码先在 dev 落地,再外科同步 com@publish 与 stdxx。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1 现状地图
|
|
14
|
+
|
|
15
|
+
### 1.1 三份同源实现
|
|
16
|
+
|
|
17
|
+
| 文件 | 用途 | 行数 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js` | xform 表单导入,**唯一被两个表单按钮使用的一份** | 1831 |
|
|
20
|
+
| `src/components/formImport/importDialogMixin.js` | 上者的整份拷贝,供手写页面(`pmd/product_problem_record`、`pcp/certification_application`)使用 | 1831 |
|
|
21
|
+
| `src/components/excelImport/mixins.js` | 更早的列表页导入(`option.saveUrl` 直连地址,无 `formHttp`、无多 sheet) | 898 |
|
|
22
|
+
|
|
23
|
+
前两份 `diff` 只有四处差异:`var/let`、import 路径、`inject`(xform 那份多 inject 了
|
|
24
|
+
`getReportTemplate`)、以及 `formCode` / `formVersion` 的取值口径(xform 从 `getReportTemplate()`
|
|
25
|
+
取并发 `formVersion`,formImport 从 `importOption.formCode` 取、不发 `formVersion`)。
|
|
26
|
+
**结论:本轮任何修复都要三处同步**(stdxx 另算),这是导入链路最大的维护成本来源。
|
|
27
|
+
|
|
28
|
+
> 这条不是天然成立的。2026-09-04 审查时发现两份实际漂移了 94 行:xform 那份被做过一轮
|
|
29
|
+
> `==` → `===` 与判空加固,formImport 那份一处没跟。已在 10.8 补齐,上面这四处是补齐后的实情。
|
|
30
|
+
|
|
31
|
+
### 1.2 两个按钮,两条通道
|
|
32
|
+
|
|
33
|
+
| 按钮 | 设计器默认标签 | `importFrontOnly` | 主链路 | 进度弹框 | 逐行成败 | 结果表 |
|
|
34
|
+
|---|---|---|---|---|---|---|
|
|
35
|
+
| `import-button` | 导入 | `false` | `dialogPrimary1` → `syncDataForImport` | 有 | 有 | 有 |
|
|
36
|
+
| `import2-button` | **明细导入** | `true` | `dialogPrimary3`(`:1037`) | 无(只有全屏 loading) | 无 | 无 |
|
|
37
|
+
|
|
38
|
+
两个按钮都通过 `getFormRef().openImportDialog(option)`(`form-render/indexMixin.js:4982`)挂起
|
|
39
|
+
`form-widget/dialog/importDialog.vue`,`option` 形状:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
{
|
|
43
|
+
target, // 按钮 widget 实例,脚本事件靠它执行 handleCustomEvent
|
|
44
|
+
importOption: field.options, // 设计器上这颗按钮的全部配置
|
|
45
|
+
importFrontOnly, // true=前端通道,false=后端通道
|
|
46
|
+
multi, multiSize, // 仅后端通道用(import2 不传)
|
|
47
|
+
callback // 关闭时回调
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
弹框自身是三个 `el-dialog` 串起来的(`importDialog.vue`):
|
|
52
|
+
选文件框 → 进度框(含三段图片处理进度条 + 成功/失败计数)→ 结果表(vxe-grid,可导出)。
|
|
53
|
+
|
|
54
|
+
### 1.3 后端通道链路
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
dialogPrimary1 [:191]
|
|
58
|
+
├─ 文件必选 / importFileLimitSize(≤200M) 校验
|
|
59
|
+
├─ importFrontOnly → 转 dialogPrimary3
|
|
60
|
+
└─ readExcel(file, cb) [:379]
|
|
61
|
+
├─ isImportFileOversize / isImportRowsOverLimit (utils/importLimit.js,20MB / 20000 行)
|
|
62
|
+
├─ multipleSheet → readExcel2 [:1498]
|
|
63
|
+
├─ 逐格取值 getImportCellText(优先 cell.w)
|
|
64
|
+
├─ 解析标题后缀 → isNeed(*) / uniqueFields(**) / attachments_ 列
|
|
65
|
+
├─ 必填预检(被 `if (uniqueFields.length)` 包着) [:580]
|
|
66
|
+
└─ cb(resultData={data,titles,fields,uniqueFields,oriCols})
|
|
67
|
+
→ createImportTimer + 打开进度框
|
|
68
|
+
→ handleBeforeImport0 [:275]
|
|
69
|
+
├─ enabledImportPreHandle → uploadFile 分片上传 → mergeChunk → checkData → getData(取回内嵌图片)
|
|
70
|
+
└─ handleBeforeImport → onBeforeImport 脚本 [:252]
|
|
71
|
+
→ syncDataForImport [:691]
|
|
72
|
+
循环:vailData(行必填) → getDefaultValue(option.defaultValue) → saveImportHandle 发包
|
|
73
|
+
→ 回调里累计 successNum/failNum、写 _resultType/_resultContent
|
|
74
|
+
→ prevHandleData 用 setTimeout(...,1) 让步,再跑下一行/下一批 [:743]
|
|
75
|
+
→ 关闭时 onSuccessImport 脚本 + handleTableCallback(isQueryTable 则 searchEvent 刷新)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
发包体(`saveImportHandle` `:646`):
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
formHttp({
|
|
82
|
+
scriptCode: importScriptCode || "import",
|
|
83
|
+
data: { formCode, taBm: importEntity, data: { [importEntity]: row } }, // multi 时 data 直接是行数组
|
|
84
|
+
modal: false, failMsg: false
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
批量模式(`importMultiple`)下后端按 `impSeq` / `impNumber` 回 `objx[].impReturnType` /
|
|
89
|
+
`impReturnMsg`,前端据此逐行判成败。
|
|
90
|
+
|
|
91
|
+
### 1.4 前端通道链路
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
dialogPrimary3 [:1037]
|
|
95
|
+
├─ $baseLoading 全屏遮罩(不是进度框)
|
|
96
|
+
└─ readExcel(file, cb)
|
|
97
|
+
├─ onConfirmImportEnabled → onConfirmImport(resultData, file, done) 脚本自处理
|
|
98
|
+
└─ 否则 tableRef.addTableData(resultData.data) [:1085]
|
|
99
|
+
→ done() → onSuccessImport 脚本
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`addTableData`(`form-render/container-item/data-table-mixin.js:3800`)只做追加,
|
|
103
|
+
第二参 `field`(按字段去重)在这里**没有传**。
|
|
104
|
+
|
|
105
|
+
### 1.5 模板契约(0-based 解析,`readExcel` 内)
|
|
106
|
+
|
|
107
|
+
| Excel 行 | 含义 |
|
|
108
|
+
|---|---|
|
|
109
|
+
| 第 1 行 | 忽略(通常写说明) |
|
|
110
|
+
| 第 2 行 | 中文标题,**后缀即语义** |
|
|
111
|
+
| 第 3 行 | 字段名 |
|
|
112
|
+
| 第 4 行起 | 数据(`impNumber = 数据下标 + 4` 由此而来) |
|
|
113
|
+
|
|
114
|
+
标题后缀:`*` = 必填(`isNeed`);`**` = 分组唯一键(`uniqueFields`,多行合成一张单);
|
|
115
|
+
`***` = 多 sheet 模式下的单据唯一标识(`uniqueKeys`)。
|
|
116
|
+
字段名以 `attachments_` 开头 = 附件列,进 `fileCellFields`。
|
|
117
|
+
|
|
118
|
+
附件列有两种承载方式,解析期由 `normalizeImportAttachmentCells` 统一收口成
|
|
119
|
+
**附件对象数组**(10.10),下游只面对这一种形状:
|
|
120
|
+
|
|
121
|
+
| 承载方式 | 单元格内容 | 靠什么生效 | 两条通道 |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| URL 文本 | `http://x/a.png` 或 `/group1/a.png`,逗号/换行分隔多个 | 解析期归一 | 都可用,无需任何开关 |
|
|
124
|
+
| 内嵌图片 | Excel 里贴的图,单元格本身是空的 | `enabledImportPreHandle` 三段式把图取回来填进该列 | 都可用,明细导入需同时开进度框(10.9) |
|
|
125
|
+
|
|
126
|
+
地址必须带协议域名或以 `/` 开头;填了但不合规的整格判错行,不做部分导入。
|
|
127
|
+
|
|
128
|
+
### 1.6 配置项现状
|
|
129
|
+
|
|
130
|
+
`import-button`(widgetsConfig.js:3408):
|
|
131
|
+
|
|
132
|
+
| 配置 | 默认 | 说明 |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `importEntity` | `""` | 导入数据表名,**未配则整条链路静默失效**(见问题 5) |
|
|
135
|
+
| `importScriptCode` | `""` | 后台脚本编码,空则用 `"import"` |
|
|
136
|
+
| `importAttachCode` / `importAttachName` | `""` | 模板文件(`bd_attach_setting/getByCode`) |
|
|
137
|
+
| `importFileLimitSize` | `200` | 单位 M,上限 200 |
|
|
138
|
+
| `importMultiple` / `importMultiSize` | `false` / `1` | 批量发包 |
|
|
139
|
+
| `multipleSheet` | `false` | 多 sheet 主子表导入 |
|
|
140
|
+
| `enabledImportPreHandle` | `false` | 图片处理三段式(分片上传 → checkData → getData) |
|
|
141
|
+
| `tableRef` | `""` | 导入完成后刷新的表格(仅 `isQueryTable` 生效) |
|
|
142
|
+
| `onBeforeImport` / `onSuccessImport` / `onBeforeClickButton` | `""` | 脚本事件 |
|
|
143
|
+
| `hideCancelButton` | `false` | 只影响进度框的取消按钮 |
|
|
144
|
+
|
|
145
|
+
`import2-button`(widgetsConfig.js:3461):`importFileLimitSize`、`importAttachCode`、
|
|
146
|
+
`tableRef`、`onConfirmImportEnabled` / `onConfirmImport`、`onSuccessImport`、
|
|
147
|
+
`onBeforeClickButton`、`hideCancelButton`、`frontImportFlag`(未使用);
|
|
148
|
+
P1 追加 `showProgressDialog` / `importMode` / `onImportRow` / `onValidateRow` /
|
|
149
|
+
`dedupField` / `requiredCheckMode`,10.9 追加 `enabledImportPreHandle`。
|
|
150
|
+
|
|
151
|
+
### 1.7 脚本事件契约现状
|
|
152
|
+
|
|
153
|
+
`handleCustomEvent`(`fieldMixin.js:644`)恒在参数前追加 `dataId`、`formCode`。
|
|
154
|
+
|
|
155
|
+
| 事件 | 编辑器声明参数 | 运行时实传 | 触发点 | 备注 |
|
|
156
|
+
|---|---|---|---|---|
|
|
157
|
+
| `onBeforeClickButton` | `dataId, formCode, done` | 同左 | 点按钮 | 返回 `false` 可拦截 |
|
|
158
|
+
| `onBeforeImport` | `+ file, resultData, done` | 同左 | 解析后、发包前 | `done(res)` 回填 **有 bug**(问题 4) |
|
|
159
|
+
| `onConfirmImport` | `+ resultData, file, done, close` | **只传 `resultData, file, done`** | 前端通道确认 | `close` 恒 undefined(问题 8) |
|
|
160
|
+
| `onSuccessImport` | `+ resultData, file` | 同左 | 进度框确定/取消/关闭 | 三个出口都会触发一次 |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 2 问题清单
|
|
165
|
+
|
|
166
|
+
### 2.1 缺陷
|
|
167
|
+
|
|
168
|
+
| # | 现象 | 根因 | 位置 | 级别 |
|
|
169
|
+
|---|---|---|---|---|
|
|
170
|
+
| 1 | 多 sheet 导入必崩:弹框直接消失,只有 console 有错 | `let sheetName = resultData[sheetName];` 自引用触发 TDZ `ReferenceError`,被外层 `try/catch` 吞成 `showContent=false` | `readExcel2` `:1727` | P0 |
|
|
171
|
+
| 2 | 多 sheet 模板只要第一列不带 `***` 就报「缺失单据唯一标识」 | `if (!uniqueKeys.length)` 写在**列循环内部**,第一列尚未累计到唯一键就判空 | `readExcel2` `:1641` | P0 |
|
|
172
|
+
| 3 | `*` 必填在前端通道上完全不校验 | 必填只有两处:解析期那道被 `if (uniqueFields.length)` 包着,逐行 `vailData` 是后端通道专属。后端通道因此还有兜底(`**` 分组走解析期、其余走逐行),**前端通道两处都不沾** | `readExcel` `:580` | P0 |
|
|
173
|
+
| 4 | `onBeforeImport` 脚本 `done(res)` 带 `objx` 必崩 | 回填写的是 `resultData[rowIndex]`,而入参 `resultData` 是 `{data,titles,...}` 信封不是行数组 | `handleBeforeImport` `:258` | P0 |
|
|
174
|
+
| 5 | `importEntity` 漏配时进度框永久停在 0%,无任何提示 | `saveImportHandle` `if (!entity) return;` 直接返回,`callback` 永不触发,循环断在原地 | `:652` | P0 |
|
|
175
|
+
| 6 | **明细导入不校验必填**、无逐行成败 | 前端通道既不进 `readExcel` 的必填预检(被问题 3 挡住),也不进 `vailData`(后端通道专属) | `dialogPrimary3` | P1 |
|
|
176
|
+
| 7 | 批量导入(`importMultiple`)时附件列不上浮 | `saveImportHandle` 只在非 multi 分支把 `attachments_*` 从实体里提到信封层 | `:655-672` | P1 |
|
|
177
|
+
| 8 | `onConfirmImport` 脚本按文档写 `close()` 直接报错 | 声明 6 参、实传 3 参。**只在 `components/formImport` 那份成立**,xform 那份早已补了 `close` —— 两副本已经漂移 | `formImport/importDialogMixin.js:1071` | P1 |
|
|
178
|
+
| 9 | 表单里配不了「导入时补默认值」 | `getDefaultValue` 读的是 `option.defaultValue`,只有手写页能传,两个按钮都没往 `openImportDialog` 里放 | `:767` | P1 |
|
|
179
|
+
| 10 | 失败行只能导出 csv,丢模板格式、改完导不回去 | `generateNewExcel`(按成败筛原格式 xlsx)**零调用点**,且对 `File` 直接 `XLSX.read` 也是错的 | `:1423` | P2 |
|
|
180
|
+
| 11 | 同一文件连点两次,明细表数据翻倍 | `addTableData(rows)` 未传去重字段 | `:1085` | P2 ✅ 已随 P1 修(`dedupField`) |
|
|
181
|
+
| 12 | 解析期连点「确定」会起多趟解析 | 确定按钮没有 pending / loading 锁 | `importDialog.vue` footer | P2 |
|
|
182
|
+
| 13 | 关掉弹框结果就没了 | 结果表与后端导入日志(`views/bd/setting/form_import_log`)没打通 | — | P2 |
|
|
183
|
+
| 14 | 导入中取消,在途请求不撤、已发批次不回滚 | 循环只看 `showContent`,无 abort | `prevHandleData` | P2 |
|
|
184
|
+
| 15 | 死代码:`dialogClose2/dialogPrimary2` 的注释旧版、`checkExcelForImport`(`compareExcelFields`)已停用 | 历史遗留 | `:344-364`、`:627` | P2 |
|
|
185
|
+
|
|
186
|
+
### 2.2 能力缺口(按业务场景)
|
|
187
|
+
|
|
188
|
+
| 场景 | 今天能做到 | 缺什么 |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| A 明细导入(excel → 明细表格) | 解析 + 整批 `addTableData` | ✅ 校验/进度框/逐行成败/去重已落地(P1);附件两种承载也已打通(10.9) |
|
|
191
|
+
| B 前端脚本比对表格数据后更新(匹配即更新,标成败) | `onConfirmImport` 拿全量行自己比对、自己 `setValue` | ✅ 已落地:`importMode=script` + `onImportRow`,进度/让步/成败全归壳(P1 #10 #12) |
|
|
192
|
+
| C 提交后台补齐字段后插入(标成败) | 就是现有后端通道(逐行/批量 + 结果表 + 结果导出) | ✅ 已落地:`importDefaultValue`(P1 #13)+问题 4/5 已修(P0) |
|
|
193
|
+
| D 先校验后导入(dry-run) | 无 | 全量预检报告 → 确认再落库 |
|
|
194
|
+
| E 更新导入 / upsert | 全靠后端脚本自判 | 前端无「业务主键」显式配置(只有 `**` 一个隐式约定) |
|
|
195
|
+
| F 失败行重导 | 只能导 csv | 按原模板导出失败行 → 改完直接再导(问题 10) |
|
|
196
|
+
| G 幂等 / 重复导入拦截 | 无(`md5` 只为分片合并) | 文件级或业务级去重口径 |
|
|
197
|
+
| H 移动端导入 | 无 | 两个按钮 `layoutType` 都是 `PC` |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 3 目标形态
|
|
202
|
+
|
|
203
|
+
### 3.1 分层
|
|
204
|
+
|
|
205
|
+
把「逐行执行 + 进度 + 成败 + 结果表 + 失败行重导」抽成**与通道无关的壳**,
|
|
206
|
+
壳下面挂三种 row handler:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
importRunner(壳,从 syncDataForImport 提取)
|
|
210
|
+
├─ 进度:percentageNum / successNum / failNum / doneNum
|
|
211
|
+
├─ 让步:prevHandleData 的 setTimeout 分片(前后端通道共用)
|
|
212
|
+
├─ 结果:successRows / failRows → 结果表 → 结果导出 / 失败行重导
|
|
213
|
+
└─ handler(三选一,由 importMode 决定)
|
|
214
|
+
├─ backend : 现有 saveImportHandle(场景 C,几乎不动)
|
|
215
|
+
├─ frontScript: 新增 onImportRow(row, index, done) 脚本(场景 B)
|
|
216
|
+
└─ frontTable : 现有 addTableData(场景 A,加校验与结果)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 3.2 行结果协议(复用现有字段,不新造)
|
|
220
|
+
|
|
221
|
+
结果表 `initResGird` 已经按下面几个字段渲染,前端通道直接复用即可:
|
|
222
|
+
|
|
223
|
+
| 字段 | 含义 |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `impSeq` | 数据行下标(0-based),排序用 |
|
|
226
|
+
| `impNumber` | Excel 行号 = `impSeq + 4`,展示用 |
|
|
227
|
+
| `_resultType` | `"success"` / `"error"` |
|
|
228
|
+
| `_resultContent` | 成功文案或错误信息(`handleRes` → `getErrorMsg` 会做异常栈裁剪) |
|
|
229
|
+
|
|
230
|
+
handler 统一回调形状:`done({ success: Boolean, message: String })`。
|
|
231
|
+
|
|
232
|
+
### 3.3 校验分三层(都在壳里,与通道无关)
|
|
233
|
+
|
|
234
|
+
| 层 | 时机 | 行为 | 对应改造 |
|
|
235
|
+
|---|---|---|---|
|
|
236
|
+
| L1 解析期硬校验 | `readExcel` 出结果后 | `*` 必填缺失 → 弹框中止,指出行号与列名 | 拆掉问题 3 的 `uniqueFields` 门 |
|
|
237
|
+
| L2 行级软校验 | 壳的逐行循环里 | `vailData` 判不过 → 该行记 `error` 进失败表,其余行继续 | `vailData` 提到壳里,两条通道共用 |
|
|
238
|
+
| L3 业务校验 | L2 之后 | 新增脚本 `onValidateRow(row, index)`,返回 `true` / 错误文案 | 新增事件 |
|
|
239
|
+
|
|
240
|
+
L1 与 L2 的取舍由新配置 `requiredCheckMode` 决定(见 5.1),默认 `row`(软校验),
|
|
241
|
+
保持「错一行不拖垮整批」的现有体感;老模板要硬中断的配 `strict`。
|
|
242
|
+
|
|
243
|
+
### 3.4 三个场景的落点
|
|
244
|
+
|
|
245
|
+
- **A 明细导入**:`importMode = table`,L1/L2 生效,进度框打开,结果表可看可导;
|
|
246
|
+
`addTableData` 补传去重字段(新配置 `dedupField`)。
|
|
247
|
+
- **B 前端比对更新**:`importMode = script`,每行调 `onImportRow(row, index, done)`,
|
|
248
|
+
脚本里 `getWidgetRef('明细表')` 取表格数据做比对、命中即写回,
|
|
249
|
+
`done({success:false, message:"未匹配到编码"})` 落失败表。让步与进度由壳负责。
|
|
250
|
+
- **C 后端落库**:等价于今天的后端通道;额外把 `defaultValue` 以设计器配置
|
|
251
|
+
(`importDefaultValue` 脚本)的形式补上。
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 4 改造点清单
|
|
256
|
+
|
|
257
|
+
### P0(纯修复,可独立发版,不改契约)
|
|
258
|
+
|
|
259
|
+
| # | 文件 / 函数 | 改动 | 状态 |
|
|
260
|
+
|---|---|---|---|
|
|
261
|
+
| 1 | `readExcel2` | 删掉 `let sheetName = resultData[sheetName];` 这行死变量 | ✅ 已改 |
|
|
262
|
+
| 2 | `readExcel2` | 把 `if (!uniqueKeys.length)` 移出列循环,放到该 sheet 全部列解析完之后 | ✅ 已改 |
|
|
263
|
+
| 3 | `readExcel` / `dialogPrimary3` | 抽出 `checkRequiredRows(rows, oriCols)`;解析期那道改调它(语义不变),**前端通道 `dialogPrimary3` 新增同一道硬拦** | ✅ 已改 |
|
|
264
|
+
| 4 | `handleBeforeImport` | 回填目标改为 `resultData.data[rowIndex]`,并容忍老形态(直接传行数组)与 objx 超长 | ✅ 已改 |
|
|
265
|
+
| 5 | `saveImportHandle` | `!entity` 时不再静默 `return`:走新增的 `handleImportAbort`——停循环、停计时器、弹一次明确提示、关掉进度框 | ✅ 已改 |
|
|
266
|
+
| 7 | `saveImportHandle` | 批量分支按新开关 `importMultiAttachEnvelope` 补一层实体信封承载附件 | ✅ 已改,见 10.7 |
|
|
267
|
+
| 8 | `formImport/importDialogMixin.js` | 前端通道实传 `close`(与 xform 那份对齐) | ✅ 已改 |
|
|
268
|
+
|
|
269
|
+
以上各条互相独立,任一条都可单独提交。三副本同步。
|
|
270
|
+
|
|
271
|
+
**为什么 #3 没有把后端通道也改成硬拦**:解析期那道之所以挂在 `uniqueFields` 上,是因为
|
|
272
|
+
`syncDataForImport` 在「multi + 分组」分支里整组发包、不跑逐行 `vailData`,那道正是它的兜底。
|
|
273
|
+
其余分支的必填由逐行 `vailData` 负责(错一行记一行,其余继续)。把它改成无条件硬拦,
|
|
274
|
+
等于把后端通道的「部分导入」体感改掉——那属于 P1 的 `requiredCheckMode`,由配置显式决定,
|
|
275
|
+
不在 P0 里偷偷换语义。
|
|
276
|
+
|
|
277
|
+
### P1(壳与结果协议)
|
|
278
|
+
|
|
279
|
+
| # | 文件 / 函数 | 改动 | 状态 |
|
|
280
|
+
|---|---|---|---|
|
|
281
|
+
| 9 | `importDialogMixin.js` | 抽出 `createImportProgress(size)`(记账)与 `runImportRows({rows, handler, onFinish})`(一行一步的执行壳);`syncDataForImport` 的计数/成败沉淀改走记账器 | ✅ 已改,见 10.5 |
|
|
282
|
+
| 10 | 同上 | 新增 `validateImportRow(row, index, opts)`:必填(L2) + `onValidateRow` 脚本(L3),前后端通道共用;后端两处 `vailData` 调用点改走它 | ✅ 已改,见 10.6 |
|
|
283
|
+
| 11 | `dialogPrimary3` | 新增 `startFrontImport`:进度框 + 逐行校验 + 结果表 + 通过的行一次性入表;由 `showProgressDialog` 分流,默认关=老链路 | ✅ 已改 |
|
|
284
|
+
| 12 | `import2-button-editor.vue` / `widgetsConfig.js` | 新增配置项(见 5.1) | ✅ 已改 |
|
|
285
|
+
| 13 | `import-button-widget.vue` | 把 `importDefaultValue` 脚本结果按 `option.defaultValue` 形状传进 `openImportDialog` | ✅ 已改 |
|
|
286
|
+
|
|
287
|
+
### P2(体验与闭环)
|
|
288
|
+
|
|
289
|
+
| # | 改动 |
|
|
290
|
+
|---|---|
|
|
291
|
+
| 14 | `generateNewExcel` 接线:结果表工具条加「导出失败行(原模板)」,并修 `XLSX.read(file)` → 先 `readAsArrayBuffer` |
|
|
292
|
+
| 15 | `addTableData` 调用点补 `dedupField` |
|
|
293
|
+
| 16 | 选文件框「确定」加 pending 锁(解析期禁用 + loading) |
|
|
294
|
+
| 17 | 导入中取消:置 `isEnd = true` + 取消在途 `axios` 请求 |
|
|
295
|
+
| 18 | 结果表加「查看导入日志」入口,跳 `form_import_log`(需后端给一次导入的批次号,见第 9 节) |
|
|
296
|
+
| 19 | 清理死代码:`:344-364` 注释块、`checkExcelForImport` |
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## 5 新增配置与脚本契约
|
|
301
|
+
|
|
302
|
+
### 5.1 新增配置项(全部带默认值,缺省即今天的行为)——**已全部落地**
|
|
303
|
+
|
|
304
|
+
| 配置 | 归属按钮 | 默认 | 作用 |
|
|
305
|
+
|---|---|---|---|
|
|
306
|
+
| `showProgressDialog` | import2 | `false` | 打开后走进度框 + 结果表;默认关=老行为不变 |
|
|
307
|
+
| `importMode` | import2 | `"table"` | `"table"`(塞表格)/ `"script"`(逐行脚本,见 `onImportRow`) |
|
|
308
|
+
| `dedupField` | import2 | `""` | 入表前按该字段去重(两条链路都透传给 `addTableData`) |
|
|
309
|
+
| `requiredCheckMode` | 两者 | `""` | `""` 默认(有逐行通道就逐行标记,否则整批拦)/ `"strict"` 解析期整批硬拦 / `"off"` 不校验 |
|
|
310
|
+
| `onValidateRow` | 两者 | `""` | 脚本,逐行业务校验 |
|
|
311
|
+
| `onImportRow` | import2 | `""` | 脚本,`importMode="script"` 时逐行执行 |
|
|
312
|
+
| `importDefaultValue` | import-button | `""` | 脚本,逐行返回对象合并进该行(补齐固定字段) |
|
|
313
|
+
| `importMultiAttachEnvelope` | import-button | `false` | 批量导入时给每行补一层实体信封承载附件列;默认关=仍发裸行数组(见 10.7) |
|
|
314
|
+
| `enabledImportPreHandle` | import2(新增) | `false` | 内嵌图片三段式,取回图片填进 `attachments_` 列;仅在 `showProgressDialog` 打开时可用(见 10.9) |
|
|
315
|
+
|
|
316
|
+
`requiredCheckMode` 的默认值定成 `""` 而不是方案初稿里的 `"row"`:后端通道原本就是
|
|
317
|
+
"multi+分组走解析期硬拦、其余走逐行 `vailData`"的混合口径,写死成 `row` 会把分组那支的
|
|
318
|
+
兜底拆掉(那支不跑逐行校验)。`""` 表示"按通道现有口径",两档显式值再覆盖它。
|
|
319
|
+
|
|
320
|
+
### 5.2 脚本签名
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
// 逐行业务校验:返回 true 通过;返回字符串=错误信息,该行记失败
|
|
324
|
+
onValidateRow(dataId, formCode, row, index)
|
|
325
|
+
|
|
326
|
+
// 逐行处理(前端脚本模式):必须调 done,否则该行卡住
|
|
327
|
+
onImportRow(dataId, formCode, row, index, done)
|
|
328
|
+
// done({ success: true })
|
|
329
|
+
// done({ success: false, message: "未匹配到编码" })
|
|
330
|
+
|
|
331
|
+
// 导入时补默认值(后端通道):返回对象,Object.assign 到该行
|
|
332
|
+
importDefaultValue(dataId, formCode, row)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
约定:`onImportRow` 里做表格比对用 `this.getWidgetRef(tableRef)`,
|
|
336
|
+
**禁止在脚本里自己写整趟 for 循环**——让步与进度由壳负责,这正是场景 B 卡死的根因。
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 6 向后兼容口径
|
|
341
|
+
|
|
342
|
+
改造后必须保持不变的东西:
|
|
343
|
+
|
|
344
|
+
1. 模板格式(前 3 行 + `*` / `**` / `***` / `attachments_` 后缀)**不变**,存量模板不重做。
|
|
345
|
+
2. 后端发包体 `{formCode, taBm, data}` 与 `impSeq` / `impNumber` / `impReturnType` / `impReturnMsg`
|
|
346
|
+
协议**不变**,后端脚本不改。批量报文默认仍是裸行数组,一字未动;
|
|
347
|
+
只有显式打开 `importMultiAttachEnvelope` 的表单才会变成信封数组(见 10.7)。
|
|
348
|
+
3. `onBeforeImport` / `onSuccessImport` / `onConfirmImport` 事件名与既有参数顺序**不变**
|
|
349
|
+
(只在末尾补 `close`),存量脚本不动。
|
|
350
|
+
4. `import2` 在 `showProgressDialog=false`(默认)时链路与今天完全一致:
|
|
351
|
+
全屏 loading → `onConfirmImport` 或 `addTableData`。
|
|
352
|
+
5. `hideCancelButton`、`enabledImportPreHandle` 图片三段式、`multipleSheet` 语义不变
|
|
353
|
+
(多 sheet 只修 P0 两条崩溃,不改协议)。
|
|
354
|
+
6. 三副本行为保持一致;`excelImport` 那份没有 `formHttp` 与多 sheet,
|
|
355
|
+
只同步与它相关的修复(问题 3、12)。
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 7 分期
|
|
360
|
+
|
|
361
|
+
| 期 | 内容 | 验收 |
|
|
362
|
+
|---|---|---|
|
|
363
|
+
| 一 | P0 各条 | 多 sheet 模板可导;无 `**` 的模板必填能拦;`onBeforeImport` 补字段不崩;漏配 `importEntity` 有提示;批量 + 附件正常 |
|
|
364
|
+
| 二 | P1(壳 + 结果协议 + 明细导入结果化) | 明细导入开进度框后:有百分比、成功/失败计数、结果表、结果导出;关掉开关行为与今天一致 |
|
|
365
|
+
| 三 | 场景 B(`importMode="script"` + `onImportRow`) | 5000 行比对导入不卡死主线程,逐行成败可见可导出 |
|
|
366
|
+
| 四 | P2(失败行重导、去重、pending 锁、日志打通、死代码清理) | 失败行按原模板导出、改完可直接再导 |
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 8 测试要点
|
|
371
|
+
|
|
372
|
+
单测(`tests/unit/components/xform/`,注意 jest 的 `@base` 映射必须存在):
|
|
373
|
+
|
|
374
|
+
- `readExcel` 标题后缀解析:`*` / `**` / `***` / `attachments_` 四类列的 `isNeed` / `isUnique` / `isFile`;
|
|
375
|
+
- 必填校验三种 `requiredCheckMode` 的输出(中断 / 逐行标记 / 关闭);
|
|
376
|
+
- `runImportRows` 让步与计数:N 行 → 成功 a、失败 b,`a+b=N`,且失败行按 `impSeq` 有序;
|
|
377
|
+
- `handleBeforeImport` 回填:`done({objx:[...]})` 后行数据被正确合并(回归问题 4);
|
|
378
|
+
- 多 sheet:非首列带 `***` 的模板可解析(回归问题 2);合并后 `sheetItems` 结构正确(回归问题 1);
|
|
379
|
+
- `saveImportHandle` 附件上浮:非 multi 恒把 `attachments_*` 放到信封层;
|
|
380
|
+
multi 按 `importMultiAttachEnvelope` 分流,关=裸行数组(引用都不换),开=信封数组(回归问题 7)。
|
|
381
|
+
|
|
382
|
+
联调:
|
|
383
|
+
|
|
384
|
+
- 无 `**` 列的老模板导入,必填缺失能被拦;
|
|
385
|
+
- `importEntity` 故意漏配,出提示而不是永久 0%;
|
|
386
|
+
- 明细导入开进度框:成功/失败点击可看结果表、可导出;
|
|
387
|
+
- 前端脚本模式:比对更新命中 / 未命中分别落成功、失败表;
|
|
388
|
+
- 图片模板(`enabledImportPreHandle`)三段进度正常、附件进结果表 `base-attachment` 列。
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 9 待确认
|
|
393
|
+
|
|
394
|
+
1. **导入批次号**:后端能否在导入接口回一个批次标识,用于结果表跳 `form_import_log`(P2 #18 依赖它)。
|
|
395
|
+
2. **upsert 口径**:场景 E 是否统一约定「`**` 列即业务主键,后端按其做 update」,还是新增显式配置。
|
|
396
|
+
3. **批量大小推荐值**:`importMultiSize` 默认 1(一行一个请求),几千行即几千个包;
|
|
397
|
+
是否给默认值与上限一个统一口径(例如默认 50、上限 200)。
|
|
398
|
+
4. **单元格取值口径**:`getImportCellText` 优先取 `cell.w`(格式化显示串),
|
|
399
|
+
日期会取到 `2026/9/4`、数值可能带千分位;转换责任归前端还是后端脚本,需要写死在文档里。
|
|
400
|
+
5. **移动端**:H5 表单是否需要导入(当前两个按钮 `layoutType: "PC"`)。
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## 10 实施记录
|
|
405
|
+
|
|
406
|
+
### 10.1 第一期(P0)—— 2026-09-04 已编码 + 单测过,待联调
|
|
407
|
+
|
|
408
|
+
改动文件:
|
|
409
|
+
|
|
410
|
+
| 文件 | 改动 |
|
|
411
|
+
|---|---|
|
|
412
|
+
| `src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js` | P0 #1 #2 #3 #4 #5;顺带 `formVersion: reportTemplate?.formVersion` 补空保护 |
|
|
413
|
+
| `src/components/formImport/importDialogMixin.js` | 同上全部 + #8(补传 `close`) |
|
|
414
|
+
| `src/components/excelImport/mixins.js` | **未改**,见 10.3 |
|
|
415
|
+
|
|
416
|
+
新增方法(两副本同名同签名,P1 抽壳时直接复用):
|
|
417
|
+
|
|
418
|
+
- `checkRequiredRows(rows, oriCols)` —— 整批必填预检,返回第一条提示文案或 `null`;
|
|
419
|
+
行号口径 `下标 + 4`;`0` / `false` 是有效值,只有 `null` / `undefined` / `""` 算缺失。
|
|
420
|
+
- `handleImportAbort(message)` —— 导入无法继续时的统一收口:`isEnd = true` + `clearImportTimer`
|
|
421
|
+
+ `$baseAlert` + `handleClose2`。
|
|
422
|
+
|
|
423
|
+
新增单测(20 条,全绿):
|
|
424
|
+
|
|
425
|
+
- `tests/unit/components/xform/importDialogFix.spec.js`(16 条):必填预检口径、前端通道硬拦、
|
|
426
|
+
`handleBeforeImport` 回填、漏配 `importEntity` 的收口与正常发包体;
|
|
427
|
+
- `tests/unit/components/xform/importDialogMultiSheet.spec.js`(4 条):桩掉 `window.XLSX`
|
|
428
|
+
与 `FileReader` 直接驱动 `readExcel2`,覆盖唯一标识不在首列、主子表合并、缺 `***`、必填空值。
|
|
429
|
+
|
|
430
|
+
两个 spec 在修复前跑是 **14 红 / 6 绿**,修复后 20 绿,确认是真回归而不是同义反复。
|
|
431
|
+
`eslint` 对两份 mixin 的报错数与 HEAD 基线一致(各 24 条 `operator-linebreak` 历史欠账),未新增。
|
|
432
|
+
|
|
433
|
+
### 10.2 P0 #7(批量导入附件不上浮)的形状之争
|
|
434
|
+
|
|
435
|
+
非 multi 时信封是 `{ [entity]: {...行}, attachments_x: [...] }`,附件从实体里提到信封层;
|
|
436
|
+
multi 时 `formData = data` 直接是**行数组**,`entity` 只出现在 `taBm` 上,
|
|
437
|
+
没有"实体层"可放——把附件提到哪里、后端按什么形状接收,属于协议问题。
|
|
438
|
+
|
|
439
|
+
一度打算"有附件列就自动信封化、没有就保持裸数组",被否掉:那等于同一个后端脚本
|
|
440
|
+
要按模板内容面对两种形状,而且是前端单方面改协议。**结论是补充而不是替换**——
|
|
441
|
+
加一个显式开关 `importMultiAttachEnvelope`,默认关(报文一字不变),
|
|
442
|
+
谁开谁配套改后端脚本。落地见 10.7。
|
|
443
|
+
|
|
444
|
+
### 10.3 excelImport 那份为什么没动
|
|
445
|
+
|
|
446
|
+
逐条对过:它没有 `handleBeforeImport`(#4 不存在)、没有多 sheet(#1 #2 不存在)、
|
|
447
|
+
`!saveUrl` 时自然转前端 `dialogPrimary3`(#5 的静默卡死不存在);
|
|
448
|
+
必填的解析期 + 逐行 `vailData` 两道与后端通道同构。
|
|
449
|
+
它的前端路径是「把解析结果交给页面自己处理」的通用回调(`ladderPrice/edit.vue`、
|
|
450
|
+
`batchWfObjConfigDialog.vue`、`importItemDialog.vue` 三处在用),
|
|
451
|
+
在那里加硬拦等于替这些页面改契约,不在本轮范围。
|
|
452
|
+
|
|
453
|
+
### 10.4 联调要点
|
|
454
|
+
|
|
455
|
+
- 无 `**` 列的明细导入模板,必填留空 → 应弹「第 N 行[列名]不能为空;」,且明细表格一行不进;
|
|
456
|
+
- 多 sheet 模板(唯一标识不在第一列)→ 应能正常解析并合并主子表;
|
|
457
|
+
- 故意漏配 `importEntity` → 应弹「导入数据表名(importEntity)未配置,无法导入」并关框,
|
|
458
|
+
而不是进度框停在 0%;
|
|
459
|
+
- 配了 `onBeforeImport` 且脚本 `done({objx:[...]})` 回填字段 → 数据应合并进对应行;
|
|
460
|
+
- 老模板(带 `**`、后端通道)导入行为应与改前完全一致:错行进失败表、其余照常导入。
|
|
461
|
+
|
|
462
|
+
### 10.5 P1 #9 + #11 —— 2026-09-04 已编码 + 单测过,待联调
|
|
463
|
+
|
|
464
|
+
**抽出来的两块(两副本同名同签名)**
|
|
465
|
+
|
|
466
|
+
```js
|
|
467
|
+
createImportProgress(size) // 记账:清零 → markSuccess / markError / setPercent / finish
|
|
468
|
+
runImportRows({ rows, handler, onFinish }) // 一行一步的执行壳
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
`handler(row, index, done)`,`done({ success, message })` 回写结果。壳负责:
|
|
472
|
+
打 `impSeq` / `impNumber`、成败沉淀、百分比、`setTimeout(step, 1)` 让步、
|
|
473
|
+
`showContent=false` / `isEnd` 时停推、handler 抛异常算该行失败、重复 `done` 只推一格。
|
|
474
|
+
|
|
475
|
+
**记账改成响应式**:`totalSuccessNum` / `totalErrorNum` / `dataSize` / `percentageNum`
|
|
476
|
+
本来就是 data 上的字段,模板也绑着它们,但计数一直是 `querySelector(".successNum").innerText`
|
|
477
|
+
硬写——两套并存全靠"响应式那份永远不变"侥幸成立。现在统一走 data,
|
|
478
|
+
两个模板里写死的 `<span class="doneNum">0</span>` / `<span class="dataSize">0</span>`
|
|
479
|
+
改成绑定(新增 computed `doneNum = 成功 + 失败`)。副作用:这些数字现在可以被单测直接断言。
|
|
480
|
+
|
|
481
|
+
**后端通道只换记账,循环没动**:批量与唯一键分组两种形态下"一步推进几行"是不固定的,
|
|
482
|
+
把那段循环也搬进壳里收益小、风险大。`syncDataForImport` 里 5 处 `successNum++/failNum++`
|
|
483
|
+
+ 2 处手工 `sort(compareV)` 换成 `progress.markSuccess/markError`(排序进了 `markError`)。
|
|
484
|
+
|
|
485
|
+
**前端通道 `startFrontImport`**(`showProgressDialog` 打开时):
|
|
486
|
+
|
|
487
|
+
- 必填由整批硬拦改成**逐行软校验**(复用 `vailData`),缺失行进失败表,其余照常入表格 ——
|
|
488
|
+
与后端通道体感一致;开关关闭时仍是 P0 的整批硬拦,存量表单无感知;
|
|
489
|
+
- 通过的行在 `onFinish` 里**一次性** `addTableData`。逐行调会逐行 `setValue` + `handleWbs`,
|
|
490
|
+
几百行就卡;
|
|
491
|
+
- `onSuccessImport` 的触发点随之与后端通道一致:由进度框的确定/取消/关闭三个出口触发
|
|
492
|
+
(老链路是 `done()` 里触发)。
|
|
493
|
+
|
|
494
|
+
**新增配置**:`import2-button` 的 `showProgressDialog`(默认 `false`),
|
|
495
|
+
属性面板「显示导入进度」开关 + 说明 tooltip。5.1 表里其余 6 项本轮未加。
|
|
496
|
+
|
|
497
|
+
**新增单测** `tests/unit/components/xform/importRunner.spec.js`(21 条):记账 4 条、
|
|
498
|
+
执行壳 7 条、`startFrontImport` 3 条、**`syncDataForImport` 换记账后的行为 5 条**
|
|
499
|
+
(单行成功/必填不发包/后端失败、批量按 `impSeq` 拆成败、整批失败不污染原始行)、
|
|
500
|
+
`dialogPrimary3` 分流 2 条。全仓 890 条:889 过,1 条失败是工作区里 `selectUserDialog.vue`
|
|
501
|
+
的历史用例,与导入无关。
|
|
502
|
+
|
|
503
|
+
`eslint` 两份 mixin 仍是 24 条历史 `operator-linebreak`,未新增;
|
|
504
|
+
`import2-button-editor.vue` 由 8 条 `vue/no-mutating-props` 变 9 条 —— 该文件(及同目录
|
|
505
|
+
所有属性编辑器,`import-button-editor.vue` 是 13 条)本来就整片用 `v-model="optionModel.x"`,
|
|
506
|
+
新开关沿用同一写法。
|
|
507
|
+
|
|
508
|
+
**这一期的联调要点**(在 10.4 之外)
|
|
509
|
+
|
|
510
|
+
- 「显示导入进度」关(默认):明细导入行为应与上一期完全一致;
|
|
511
|
+
- 打开后:进度框逐行推进、成功/失败可点开看结果表并导出,只有校验通过的行进明细表格;
|
|
512
|
+
- 后端通道(导入按钮)逐行与批量两种模式的成功/失败计数、"已处理/总数"、百分比
|
|
513
|
+
与改前一致 —— 这一块只换了记账实现,是本期最需要盯的回归面;
|
|
514
|
+
- 大数据量(数千行)下明细导入不再卡死主线程。
|
|
515
|
+
|
|
516
|
+
### 10.6 P1 #10 + #12 + #13 —— 2026-09-04 已编码 + 单测过,待联调
|
|
517
|
+
|
|
518
|
+
**#10 校验收进壳**:新增 `validateImportRow(row, index, opts)`,两条通道共用:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
requiredCheckMode !== "off" → vailData(必填, L2)
|
|
522
|
+
↓ 通过
|
|
523
|
+
onValidateRow 脚本(L3):返回 true/无返回=通过;返回字符串=该行错误文案;返回 false=兜底文案
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
必填先判、不过就不跑业务脚本(省掉一次脚本开销,错误信息也更直接)。
|
|
527
|
+
后端 `syncDataForImport` 里两处 `vailData` 调用点改走它 —— 于是**后端通道也能配行校验脚本**了。
|
|
528
|
+
|
|
529
|
+
**#12 配置项**:5.1 表里 7 项全部落地,属性面板对应加了控件;
|
|
530
|
+
`importMode` / `onImportRow` 只在打开进度框时显示(关掉时它们无意义)。
|
|
531
|
+
|
|
532
|
+
**场景 2 至此可配**:`showProgressDialog` 开 + `importMode=script` + `onImportRow` 脚本:
|
|
533
|
+
|
|
534
|
+
```js
|
|
535
|
+
// onImportRow(dataId, formCode, row, index, done)
|
|
536
|
+
let table = this.getWidgetRef("明细表");
|
|
537
|
+
let target = (table.getTableData() || []).find((item) => item.f_code === row.f_code);
|
|
538
|
+
if (!target) {
|
|
539
|
+
done({ success: false, message: "未匹配到编码 " + row.f_code });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
target.f_qty = row.f_qty;
|
|
543
|
+
done({ success: true });
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
循环、让步、进度、成败沉淀都归壳;脚本里**不要**再写整趟 for(那正是卡死主线程的原因)。
|
|
547
|
+
|
|
548
|
+
**#13 补默认值**:`import-button-widget` 在 `importDefaultValue` 配了脚本时,
|
|
549
|
+
把它包成 `option.defaultValue = (row) => 脚本结果 || {}` 交给弹框 —— 弹框侧 `getDefaultValue`
|
|
550
|
+
的取值口径没动,手写页原来的传法照旧可用。
|
|
551
|
+
|
|
552
|
+
**顺带做掉的 P2 #11(入表去重)**:`dedupField` 在进度框形态与老链路两处都透传给
|
|
553
|
+
`addTableData(rows, field)`(该方法本来就支持按字段去重,只是调用点一直没传)。
|
|
554
|
+
|
|
555
|
+
**新增单测**:`importRunner.spec.js` 增至 35 条(新增 `validateImportRow` 6 条、
|
|
556
|
+
脚本模式 4 条、`dedupField` 1 条、`requiredCheckMode` 三档 3 条);
|
|
557
|
+
新增 `importDefaultValue.spec.js` 4 条。四个导入 spec 合计 59 条全绿;
|
|
558
|
+
全仓 908 条:907 过,1 条仍是与导入无关的 `selectUserDialog.vue` 历史用例。
|
|
559
|
+
两份 mixin 的 `eslint` 回到 24 条基线(新代码按仓库的 `operator-linebreak` 风格调过版)。
|
|
560
|
+
|
|
561
|
+
**这一期的联调要点**
|
|
562
|
+
|
|
563
|
+
- 后端通道(导入按钮):不配 `onValidateRow` / `importDefaultValue` 时行为必须与改前一致 ——
|
|
564
|
+
这是本期最大的回归面,因为两处 `vailData` 调用点被换掉了;
|
|
565
|
+
- 配 `onValidateRow` 返回字符串 → 该行进失败表、其余照常导入;
|
|
566
|
+
- 配 `importDefaultValue` → 发包体里能看到补上的字段;
|
|
567
|
+
- `requiredCheckMode=strict` → 前后端通道都在解析期整批拦,选文件框留着可重选;
|
|
568
|
+
- 明细导入 `importMode=script` → 逐行脚本命中/未命中分别落成功、失败表,几千行不卡;
|
|
569
|
+
- `dedupField` 配了之后,同一文件导两次不再翻倍。
|
|
570
|
+
|
|
571
|
+
### 10.7 P0 #7(批量 + 附件)—— 2026-09-04 已编码 + 单测过,待联调
|
|
572
|
+
|
|
573
|
+
**先止血**:接手时工作区里躺着一版半成品——`saveImportHandle` 两个分支都在调
|
|
574
|
+
`this.buildImportRowEnvelope(...)`,而这个方法全仓没有定义,于是后端通道
|
|
575
|
+
(multi 与非 multi)一律抛 `TypeError`,`importDialogFix.spec.js` 实测 15 过 1 挂。
|
|
576
|
+
第一步把 helper 补出来(就是原非 multi 内联逻辑的抽取,`forEach` 对空数组是 no-op,
|
|
577
|
+
原来那层 `if (fileCellFields.length)` 判空可以省掉),multi 分支先回退成 `formData = data`,
|
|
578
|
+
行为与 HEAD 完全一致;`formImport` 那份当时还是 HEAD 原样,一并抽成同名同签名。
|
|
579
|
+
|
|
580
|
+
**再落 #7**:新增配置 `importMultiAttachEnvelope`(`import-button`,默认 `false`),
|
|
581
|
+
属性面板「批量报文带附件信封」开关 + tooltip(写明后端脚本需配套改)。
|
|
582
|
+
弹框侧从 `this.option.importOption` 直接读,没往 `openImportDialog` 的顶层加字段
|
|
583
|
+
——`multi` / `multiSize` 在顶层是历史原因,新开关跟 `importEntity` 一样走 `importOption`。
|
|
584
|
+
|
|
585
|
+
开关打开后每行的形状:
|
|
586
|
+
|
|
587
|
+
```js
|
|
588
|
+
{ [entity]: { ...行 }, attachments_x: [...], impSeq, impNumber }
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
`impSeq` / `impNumber` 在实体里(`{...行}` 带过去的,与单行导入同构)与信封顶层各留一份:
|
|
592
|
+
后端按 `impSeq` 把逐行结果映射回 `objx` 的那条路走的是顶层,缺了会断。
|
|
593
|
+
`taBm` 仍是 `entity`,`impReturnType` / `impReturnMsg` 协议不变。
|
|
594
|
+
|
|
595
|
+
**开关开即恒信封,不看模板里有没有附件列**:同一张表单的导入脚本只面对一种形状,
|
|
596
|
+
比"有附件列才变形"可预测;开关本身就是 opt-in,代价已经由它承担了。
|
|
597
|
+
|
|
598
|
+
**新增单测** `importDialogFix.spec.js` 增至 21 条(新增 5 条):开关关时报文与传入行数组
|
|
599
|
+
**同一个引用**(证明这条链路一字未动)、开关开时附件从实体移到信封层、
|
|
600
|
+
`impSeq` / `impNumber` 在顶层、无附件列时照样信封化、信封化不改动原始行
|
|
601
|
+
(失败表回读的还是原对象)。`createCtx` 里补挂 `buildImportRowEnvelope`。
|
|
602
|
+
`tests/unit/components/xform/` 32 个 spec 共 376 条全绿。
|
|
603
|
+
两份 mixin 的 `eslint` 仍是 24 条历史 `operator-linebreak` 基线;
|
|
604
|
+
`import-button-editor.vue` 15 条 `vue/no-mutating-props`(HEAD 是 17 条),新开关沿用该文件既有写法。
|
|
605
|
+
|
|
606
|
+
**联调要点**
|
|
607
|
+
|
|
608
|
+
- 存量批量导入(开关关):抓包确认 `data` 仍是裸行数组,`attachments_*` 还在行里;
|
|
609
|
+
- 打开开关 + 附件模板:抓包确认信封形状,后端脚本按新形状取 `attachments_*` 与 `impSeq`;
|
|
610
|
+
- 打开开关后逐行成败仍能正确回填(`objx[].impSeq` → 结果表行号),这是本条最需要盯的点。
|
|
611
|
+
|
|
612
|
+
### 10.8 代码审查修复 —— 2026-09-04 已编码 + 单测过,待联调
|
|
613
|
+
|
|
614
|
+
10.7 之后对整个导入模块做了一次审查,五条落地如下(两副本同步)。
|
|
615
|
+
|
|
616
|
+
| # | 问题 | 改法 |
|
|
617
|
+
|---|---|---|
|
|
618
|
+
| 1 | 开进度框时,壳打的 `impSeq` / `impNumber` / `_resultType` / `_resultContent` 会跟着数据进明细表格 | `startFrontImport` 入表前走新增的 `stripImportMeta(row)` 取干净副本 |
|
|
619
|
+
| 2 | `importDefaultValue` 在「批量 + 分组」分支静默失效 | 该分支补一趟 `getDefaultValue`,没配脚本时是 no-op |
|
|
620
|
+
| 3 | `fileCellFields` 只增不清 | `readExcel` / `readExcel2` 开头重置 |
|
|
621
|
+
| 4 | `markError` 每标一行就整体排一次序,整批失败是 O(n²) | 只追加;排序移到 `showErrorResult` 与 `getCallbackData`(新增 `sortRowsByImpSeq`) |
|
|
622
|
+
| 5 | 两副本实际漂移 94 行 | formImport 补齐 `==` → `===` 与判空加固;`isNull` 两边一起收成一行 |
|
|
623
|
+
|
|
624
|
+
**#1 是本轮最实的一条**:`addTableData` 是 `Object.assign(newData, row)` 全量拷贝,不做字段
|
|
625
|
+
白名单;而壳是在原行对象上原地打记账字段的,`passedRows` 装的又是同一批引用。于是这四个
|
|
626
|
+
字段会写进 `formModel`,用户点保存时一起提交给后端。老链路(`showProgressDialog` 关)交的是
|
|
627
|
+
`readExcel` 的原始行,是干净的 —— 所以这是打开开关才出现的差异。原来那条单测
|
|
628
|
+
`toHaveBeenCalledWith([rows[0], rows[2]], undefined)` 比的是被改过的同一批引用,恒等成立,
|
|
629
|
+
抓不到;已改成按形状断言。
|
|
630
|
+
|
|
631
|
+
**#4 的口径变化**:`this.failRows` 现在是追加序,不再随时有序;有序的是结果表与
|
|
632
|
+
`getCallbackData()` 返回的那份(`onSuccessImport` 脚本拿到的就是它)。排的是副本,不动原数组。
|
|
633
|
+
|
|
634
|
+
**明确不改的一条**:导入中途取消,已处理的行仍会入表、进度条仍跳到 100%
|
|
635
|
+
(`runImportRows` 的中止路径照常走 `finish()` → `onFinish`)。按现状保留。
|
|
636
|
+
|
|
637
|
+
**单测**:`importRunner.spec.js` 由 35 条增至 39 条(入表行不带记账字段 1 条、失败表消费点
|
|
638
|
+
排序 1 条、分组补默认值 2 条,另改写 3 条原来抓不到问题的断言)。把 #1 #2 两处修复临时
|
|
639
|
+
撤掉重跑,4 条转红,确认是真回归。四个导入 spec 合计 68 条全绿;全仓 917 条:916 过,
|
|
640
|
+
1 条仍是与导入无关的 `h5PopDialog.spec.js` / `selectUserDialog.vue` 历史用例。
|
|
641
|
+
两份 mixin 的 `eslint` 都是 24 条 `operator-linebreak` 基线,未新增。
|
|
642
|
+
|
|
643
|
+
**这一期的联调要点**
|
|
644
|
+
|
|
645
|
+
- 开进度框导入明细后,检查明细行里没有 `impSeq` / `_resultType` 这类字段,保存报文也干净;
|
|
646
|
+
- 带 `**` 的模板 + 配了 `importDefaultValue`:发包体里能看到补上的字段(这支以前是漏的);
|
|
647
|
+
- 先选带附件列的模板被 strict 拦下、再改选不带附件列的模板,报文里不应出现空的 `attachments_*`;
|
|
648
|
+
- 失败行较多时结果表仍按 Excel 行序,`onSuccessImport` 脚本拿到的 `failRows` 也按行序。
|
|
649
|
+
|
|
650
|
+
### 10.9 明细导入的附件支持 —— 2026-09-04 已编码 + 单测过,待联调
|
|
651
|
+
|
|
652
|
+
起因是一句提问:明细导入到底支不支持附件。查下来是两件事被一句"附件列仍只有后端通道有"
|
|
653
|
+
糊在了一起(2.2 场景 A 原话),实际是:
|
|
654
|
+
|
|
655
|
+
**URL 文本一直就能用,只是没写进文档。** `readExcel` 对 `attachments_` 列的识别是通道无关的,
|
|
656
|
+
值就是单元格文本;`addTableData` 把它写进明细行;明细表里的附件字段
|
|
657
|
+
(`baseAttachment-widget` / `vabUpload-widget`)在 `initAttachmentValue` 与 `setValue` 里
|
|
658
|
+
都会过 `normalizeAttachmentValue`,它支持单个 URL、逗号/换行分隔的多个 URL、URL 数组。
|
|
659
|
+
本轮补了单测把这条口径钉住(见下),并写进 1.5 的表。
|
|
660
|
+
|
|
661
|
+
**内嵌图片此前确实走不到。** 把图片变成附件对象的是 `enabledImportPreHandle` 三段式
|
|
662
|
+
(分片上传 → `mergeChunk` → `checkData` → `getData`),由 `getImportData` 按行下标写回
|
|
663
|
+
`resultData.data[i][attachments_x]`。明细导入三个前提一个都不满足:这个配置项只在
|
|
664
|
+
`import-button` 的 options 里;入口 `handleBeforeImport0` 只在 `dialogPrimary1` 里调;
|
|
665
|
+
三段式每一段开头都是 `if (!this.showImportDialog2) return;`,而老链路根本不开进度框。
|
|
666
|
+
|
|
667
|
+
**改法**:`startFrontImport` 拆成两截 —— 开进度框那段留在原地,逐行执行搬进新增的
|
|
668
|
+
`runFrontImportRows(resultData)`。开了 `enabledImportPreHandle` 就先 `uploadFile`,
|
|
669
|
+
在它的回调里把 `importLoading` 归位再开跑;没开则直接跑。这个位置是唯一可行的:
|
|
670
|
+
必须在 `showImportDialog2 = true` 之后(否则三段式全部早退),又必须在逐行之前
|
|
671
|
+
(否则附件还没填进行里就已经入表了)。
|
|
672
|
+
|
|
673
|
+
**新增配置** `enabledImportPreHandle`(`import2-button`,默认 `false`),属性面板
|
|
674
|
+
「启用图片处理」开关,`v-if="optionModel.showProgressDialog"` —— 默认关的老链路是
|
|
675
|
+
"全屏 loading + 整批入表",塞不进三段进度,所以开关只在开了进度框时出现。
|
|
676
|
+
|
|
677
|
+
**新增单测** `tests/unit/components/xform/importAttachment.spec.js`(6 条):
|
|
678
|
+
URL 原样入表且不被 `stripImportMeta` 顺手带走、逗号分隔的多个 URL 能归一成多个附件对象、
|
|
679
|
+
空值归一成空数组不造假附件;三段式没开时不碰 `uploadFile`、开了则回调之前一行都不入表
|
|
680
|
+
(且此时进度框已打开)、三段式回填的附件对象数组随行入表且归一时原样返回。
|
|
681
|
+
撤掉接线重跑,后两类里的 2 条转红,确认是真回归。
|
|
682
|
+
全仓 923 条:922 过,1 条仍是与导入无关的 `h5PopDialog.spec.js` 历史用例。
|
|
683
|
+
两份 mixin 的 `eslint` 仍是 24 条基线;`import2-button-editor.vue` 由 HEAD 的 10 条
|
|
684
|
+
`vue/no-mutating-props` 变 13 条(本轮新开关贡献 1 条,其余是 10.6 那批控件的)。
|
|
685
|
+
|
|
686
|
+
**没做的一件事**:单测只钉到"入表的值形如 URL 文本"这一层,没有真的挂载一个带附件列的
|
|
687
|
+
明细表去跑组件的 `initAttachmentValue`。那一段是从代码路径推断的,联调时要实际看一眼。
|
|
688
|
+
|
|
689
|
+
### 10.10 附件列解析期归一 + 地址校验 —— 2026-09-04 已编码 + 单测过,待联调
|
|
690
|
+
|
|
691
|
+
10.9 收尾时核对报文形状,发现同一个附件字段最终有**三种形状**在往下走:三段式回填的
|
|
692
|
+
附件对象数组、单元格里的裸 URL 字符串、空值(`buildImportRowEnvelope` 里 `row[field] || []`
|
|
693
|
+
兜出来的 `[]`)。而下游三个消费点都只是照着"附件对象数组"写的:
|
|
694
|
+
|
|
695
|
+
- 后端信封 `buildImportRowEnvelope` 原样把裸字符串提到信封层发走 —— 后端脚本是照它自己
|
|
696
|
+
`getData` 产出的对象数组写的,**10.9 里"URL 文本一直就能用"这句只对明细导入成立**,
|
|
697
|
+
后端通道那条大概率一直是坏的(本轮按"现在就是坏的"当修复改,未与后端联调确认);
|
|
698
|
+
- 结果表的附件列插槽把值直接塞进 `base-attachment`,它对数组只走 `domain + url` 的对象
|
|
699
|
+
分支,裸字符串会渲染成 `undefinedhttp://...`;
|
|
700
|
+
- 明细表的附件字段有 `normalizeAttachmentValue` 兜底,所以只有这一条一直是好的。
|
|
701
|
+
|
|
702
|
+
**改法**:解析期收口。`readExcel` / `readExcel2` 拿到 `oriCols` 之后调
|
|
703
|
+
`normalizeImportAttachmentCells(rows, oriCols)`,按列的 `isFile` 把附件列一律归一成对象数组。
|
|
704
|
+
多 sheet 下按本 sheet 自己的 `oriCols` 判,不能用 `fileCellFields`(它是跨 sheet 累积的)。
|
|
705
|
+
三段式在解析之后才跑、覆盖同一个 key,两者不冲突。
|
|
706
|
+
|
|
707
|
+
**地址判定单独收紧**。`parseFileUrl` 为兼容后端各种相对路径写得极宽松——"这不是地址"
|
|
708
|
+
也会被它当成文件名收下并产出一个附件对象,直接拿它当校验等于没校验。所以导入侧另立一条
|
|
709
|
+
`IMPORT_ATTACH_URL_REG = /^((https?:)?\/\/|\/)/i`:要么带协议(或协议相对)域名,要么以
|
|
710
|
+
`/` 开头。一格里混了一个坏地址就整格判错,不做部分导入——"导进去了但少了一个附件"这种
|
|
711
|
+
无声缺失比整行报错难查得多。
|
|
712
|
+
|
|
713
|
+
**校验落点**:不合规的单元格**原样留着**(就地清成 `[]` 等于把填错的地址静默吞掉,那行
|
|
714
|
+
还照样算导入成功),由 `isBadAttachmentCell` 按"归一后还不是数组"判出来,接进两处既有关口 ——
|
|
715
|
+
逐行的 `vailData`(错行进失败表,其余照常)与整批硬拦的 `checkRequiredRows` / `checkAllSheetData`
|
|
716
|
+
(分组分支和 strict 不走逐行 vailData)。
|
|
717
|
+
|
|
718
|
+
**顺带修掉一个判空缺口**:归一把空附件格从 `null` 变成了 `[]`,而三处判空
|
|
719
|
+
(`vailData`、`checkRequiredRows`、`checkAllSheetData` 的 `isNull`)都只认 `null`/`undefined`/`""`,
|
|
720
|
+
不动的话**必填的附件列会从此填不填都能过**。新增 `isEmptyImportCell` 把空数组也算空,三处统一替换。
|
|
721
|
+
|
|
722
|
+
**单测** `importAttachment.spec.js` 扩到 17 条,新增:URL 文本转对象、逗号/换行拆多个、
|
|
723
|
+
空值一律 `[]`、三段式对象数组原样保留(保住后端带回的 id/fileSize)、不合规地址原样留着、
|
|
724
|
+
`/` 开头的存储路径算合法、一格混坏地址整格判错、没有附件列时不动数据、`vailData` 与
|
|
725
|
+
`checkRequiredRows` 报出错行、归一成 `[]` 后必填仍报"不能为空"、错行进失败表不进明细表。
|
|
726
|
+
分两次撤销验证是真回归:撤掉 `isEmptyImportCell` 的空数组分支转红 2 条,撤掉地址判定转红 5 条。
|
|
727
|
+
全仓 934 条:933 过,1 条仍是与导入无关的 `h5PopDialog.spec.js` 历史用例。两份 mixin 的
|
|
728
|
+
`eslint` 仍是 24 条基线。
|
|
729
|
+
|
|
730
|
+
**联调要点**
|
|
731
|
+
|
|
732
|
+
- 后端通道 + `attachments_` 列填 URL:现在发出去的是附件对象数组(原来是裸字符串),
|
|
733
|
+
**要和后端确认这个形状能收**;
|
|
734
|
+
- 结果表的附件列现在能正常渲染缩略图/文件名,不再是 `undefinedhttp://...`;
|
|
735
|
+
- 模板里附件列填 `a.png`(没有 `/`、没有域名)应该报"不是有效的附件地址"而不是静默导入;
|
|
736
|
+
- 必填的附件列留空,两条通道都应报"不能为空"。
|
|
737
|
+
|
|
738
|
+
**联调要点**
|
|
739
|
+
|
|
740
|
+
- 明细导入模板 `attachments_x` 列填 URL(含逗号分隔多个)→ 明细表附件单元格能看到、能预览;
|
|
741
|
+
- 开「显示导入进度」+「启用图片处理」,用贴了内嵌图的模板 → 先走三段进度,再逐行,
|
|
742
|
+
图片进明细表附件列;
|
|
743
|
+
- 只开「显示导入进度」不开图片处理 → 内嵌图取不回来(单元格本身是空的,该行附件为空),
|
|
744
|
+
这是预期行为,不是 bug;
|
|
745
|
+
- 关掉「显示导入进度」时「启用图片处理」开关应从属性面板消失,且老链路行为不变。
|