cloud-web-corejs 1.1.0-dev.19 → 1.1.0-dev.21

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.
Files changed (22) hide show
  1. package/package.json +1 -1
  2. package/src/components/baseInputExport/mixins.js +0 -6
  3. package/src/components/xform/docs/2026-09 /345/212/250/346/200/201/345/255/227/346/256/265/350/247/204/345/210/231/344/270/216/346/265/201/347/250/213/346/216/247/345/210/266/346/211/247/350/241/214/351/241/272/345/272/217/350/220/275/345/234/260/346/226/271/346/241/210.md" +409 -0
  4. package/src/components/xform/form-designer/designer.js +2086 -2084
  5. package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +34 -0
  6. package/src/components/xform/form-designer/form-widget/field-widget/fieldMixin.js +13 -1
  7. package/src/components/xform/form-designer/setting-panel/form-dynamicField-setting.vue +687 -602
  8. package/src/components/xform/form-designer/setting-panel/form-setting.vue +74 -9
  9. package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +8 -0
  10. package/src/components/xform/form-render/container-item/data-table-mixin.js +6304 -6285
  11. package/src/components/xform/form-render/container-item/dynamicFieldMixin.js +55 -1
  12. package/src/components/xform/form-render/container-item/tab-item.vue +138 -129
  13. package/src/components/xform/form-render/fieldControlEngine.js +218 -0
  14. package/src/components/xform/form-render/fieldControlMixin.js +534 -0
  15. package/src/components/xform/form-render/index.vue +164 -153
  16. package/src/components/xform/form-render/indexMixin.js +5415 -5331
  17. package/src/components/xform/mixins/defaultHandle.js +713 -707
  18. package/src/components/xform/utils/util.js +1710 -1708
  19. package/src/views/user/home/default.vue +6 -3
  20. package/src/views/user/home/wjl/content.vue +3 -2
  21. package/src/components/xform/form-render/container-item/dynamicFieldEngine.js +0 -285
  22. package/src/components/xform/form-render/formDynamicFieldMixin.js +0 -131
@@ -1,707 +1,713 @@
1
- import moment from "moment";
2
- import { saveUserLog } from "@base/api/user";
3
- import settingConfig from "@/settings";
4
- import { openStartWfDialog } from "../../../components/wf/wfUtil";
5
- import { getWfButtonNames } from "../../../components/wf/wfButtonName";
6
- import {
7
- isWfAutoConfirm,
8
- loadWfAutoConfirm,
9
- } from "../../../components/wf/wfAutoConfirm";
10
- import {
11
- isWfUserRangeEnabled,
12
- loadWfUserRange,
13
- } from "../../../components/wf/wfUserRange";
14
-
15
- let modules = {};
16
- modules = {
17
- data() {
18
- return {
19
- timerMap: {},
20
- formSavePending: false,
21
- formSaveReconcileTask: null,
22
- };
23
- },
24
- beforeDestroy() {
25
- for (let timer in this.timerMap) {
26
- this.clearInterval(timer);
27
- }
28
- },
29
- methods: {
30
- getMoment(date) {
31
- return moment(date);
32
- },
33
- getMomentUtil() {
34
- return moment;
35
- },
36
- utcToCst(utcStr) {
37
- if (!utcStr) return utcStr;
38
- return moment.utc(utcStr).local().format("YYYY-MM-DD HH:mm:ss");
39
- },
40
- cstToUtc(localTime) {
41
- if (!localTime) return localTime;
42
- return moment(localTime).utc().format("YYYY-MM-DD HH:mm:ss");
43
- },
44
- addFormatDateTime(date, num, type, format) {
45
- if (!date) return date;
46
- if (!type) type = "days";
47
- if (!format) format = "YYYY-MM-DD HH:mm:ss";
48
- return moment(date).add(num, type).format(format);
49
- },
50
- addFormatDate(date, num) {
51
- if (!date) return date;
52
- return moment(date).add(num, "days").format("YYYY-MM-DD");
53
- },
54
- formatDate(date) {
55
- if (!date) return date;
56
- return moment(date).format("YYYY-MM-DD");
57
- },
58
- formatDateTime(date, format = "YYYY-MM-DD HH:mm:ss") {
59
- if (!date) return date;
60
- return moment(date).format(format);
61
- },
62
- getFormEntityData(reqFormData) {
63
- let formRef = this.getFormRef ? this.getFormRef() : this;
64
- let formConfig = formRef.formConfig;
65
- let entity = formConfig.entity;
66
- if (!reqFormData) reqFormData = this.getReqFormData();
67
- return reqFormData.data[entity];
68
- },
69
- getReqFormData(formDataOverride) {
70
- let formRef = this.getFormRef ? this.getFormRef() : this;
71
- let formConfig = formRef.formConfig;
72
- let entity = formConfig.entity;
73
- if (!entity) return;
74
- let dataId = formRef?.dataId;
75
- let reportTemplate = formRef?.reportTemplate;
76
- let formCode = reportTemplate?.formCode;
77
-
78
- let sourceFormData
79
- = arguments.length > 0
80
- ? formDataOverride
81
- : formRef.getRealFormData();
82
- let formData = this.$baseLodash.cloneDeep(sourceFormData);
83
- let mainData = formData[formCode] || {};
84
- delete formData[formCode];
85
- formData[entity] = mainData;
86
-
87
- delete mainData._createNickName;
88
- delete mainData._createMobile;
89
- delete mainData._modifyNickName;
90
- delete mainData._modifyMobile;
91
- delete mainData._createBy;
92
- delete mainData._modifyBy;
93
-
94
- // 移除对比功能给各子表行加的快照字段 hData,避免提交到后端
95
- let stripHData = (obj) => {
96
- if (!obj || typeof obj !== "object") return;
97
- if (Array.isArray(obj)) {
98
- obj.forEach(stripHData);
99
- return;
100
- }
101
- if (Object.prototype.hasOwnProperty.call(obj, "hData")) {
102
- delete obj.hData;
103
- }
104
- Object.keys(obj).forEach((k) => {
105
- let v = obj[k];
106
- if (v && typeof v === "object") stripHData(v);
107
- });
108
- };
109
- stripHData(formData);
110
-
111
- let reqData = {
112
- formCode: formCode,
113
- formVersion: reportTemplate.formVersion,
114
- taBm: entity,
115
- data: formData,
116
- };
117
- return reqData;
118
- },
119
- onAfterSaveHandle(reqData, res, response) {
120
- //保存成功后处理
121
- this.writeSaveLog(reqData, res, response); //写入保存日志
122
- },
123
- writeSaveLog(reqData, res, response) {
124
- //写入保存日志
125
- if (settingConfig.formSaveLogEnabled && res.type === "success") {
126
- let responseConfig = response.config || {};
127
- let formRef = this.getFormRef ? this.getFormRef() : this;
128
- let reportTemplate = formRef ? formRef.reportTemplate : null;
129
- if (!reportTemplate) return;
130
- let isAdd = !formRef.dataId;
131
- let formName = reportTemplate?.formName;
132
- let formCode = reportTemplate?.formCode;
133
- let action = isAdd ? "新增成功" : "更新成功";
134
- let content = `${formName}(${formCode}),${action}`;
135
- let path = responseConfig.url.replace(responseConfig.baseURL, "");
136
- saveUserLog({
137
- data: {
138
- path: path,
139
- businessCode: content,
140
- content: content,
141
- },
142
- });
143
- }
144
- },
145
- async saveAndSubmitWfHandle(option) {
146
- let formRef = this.getFormRef ? this.getFormRef() : this;
147
- let formConfig = formRef.formConfig;
148
- let reportTemplate = formRef.reportTemplate;
149
- let wfParam = formRef.wfParam || {};
150
-
151
- if (!formConfig?.wfEnabled) return;
152
- if (
153
- formRef.hasWf ||
154
- wfParam.hasWf ||
155
- wfParam.wfInfo?.procInstId
156
- ) {
157
- return;
158
- }
159
-
160
- // 提示语跟随后台 param18 下发的按钮名;没配就保持原文案不变。
161
- // 只能用 $t1(找不到 key 原样返回),$t2 在 i18n 命中 key 时会把拼进去的名字吞掉。
162
- let btnName = getWfButtonNames().saveSubmitBtn;
163
- let confirmText =
164
- option?.confirmText ||
165
- option?.config?.confirmText ||
166
- (btnName
167
- ? this.$t1(`您确定要${btnName}吗?`)
168
- : this.$t1("您确定要保存并提交流程吗?"));
169
-
170
- // param19 + param12 都开:这道「保存前」的确认框不弹,把确认交给 wfStartDialog——
171
- // 它在保存之后才探 preStart,探到候选人就由「修改候选人」弹框顶替,探不到才弹确认框。
172
- // 任一为 false 都当场弹:param12 关着后面不可能有候选人弹框,延后没有意义。
173
- await Promise.all([loadWfAutoConfirm(this), loadWfUserRange(this)]);
174
- let autoConfirm = isWfAutoConfirm() && isWfUserRangeEnabled();
175
-
176
- return this.saveDefaultHandle({
177
- ...option,
178
- config: {
179
- ...option?.config,
180
- successMsg: option?.config?.successMsg ?? true,
181
- isConfirm: option?.config?.isConfirm ?? !autoConfirm,
182
- confirmText: option?.config?.confirmText || confirmText,
183
- skipHostReloadAfterSave: true,
184
- //提交前置处理(与「提交流程」按钮共用同一个表单脚本)
185
- beforeConfirm: () =>
186
- formRef.runBeforeSubmitWf
187
- ? formRef.runBeforeSubmitWf("saveSubmit")
188
- : true,
189
- success: (res) => {
190
- // ★ 保存成功后,只升级新增页签的「元数据」为编辑态(设 dataId/isAdd/formCode/标题),
191
- // 不重渲染详情内容。这样即便随后提交失败/取消候选人弹框,页签也已是编辑态,
192
- // 再次提交只会更新、不会重复入库;而详情内容的刷新交给最终的提交后行为(避免刷两次)。
193
- let editFormCode = formConfig.editFormCode;
194
- let listPageRef = formRef.getListPageRef && formRef.getListPageRef();
195
- if (listPageRef && listPageRef.promoteActiveAddTab) {
196
- listPageRef.promoteActiveAddTab(res.objx, editFormCode, formRef);
197
- }
198
-
199
- openStartWfDialog(formRef, {
200
- objId: res.objx,
201
- wfCode: reportTemplate.objTypeCode,
202
- formCode: reportTemplate.formCode,
203
- serviceId: reportTemplate.serviceName,
204
- // 上面确认过就不再弹第二次;上面因 param19 跳过了,就得让弹框自己去确认
205
- skipStartConfirm: !autoConfirm,
206
- // 提交后行为复用 xform 侧统一方法:
207
- // 成功 → onWfStartSuccess(关页签/刷新本页);失败/取消 → onWfStartAbort(刷新到编辑态)
208
- onStartSuccess: () =>
209
- formRef.onWfStartSuccess && formRef.onWfStartSuccess(),
210
- onStartFail: () =>
211
- formRef.onWfStartAbort && formRef.onWfStartAbort(),
212
- onCancel: () => formRef.onWfStartAbort && formRef.onWfStartAbort(),
213
- });
214
- option?.config?.success && option.config.success(res);
215
- },
216
- },
217
- });
218
- },
219
- async saveDefaultHandle(option) {
220
- let formRef = this.getFormRef ? this.getFormRef() : this;
221
- let formConfig = formRef.formConfig;
222
- let entity = formConfig.entity;
223
- if (!entity) return;
224
- let scriptCode = formConfig.saveScriptCode || "saveUpdate";
225
- let config = { ...(option?.config || {}) };
226
- let skipHostReloadAfterSave
227
- = config.skipHostReloadAfterSave === true;
228
- delete config.skipHostReloadAfterSave;
229
- if (typeof formRef.formSaveReconcileTask === "function") {
230
- return formRef.formSaveReconcileTask();
231
- }
232
- if (formRef.formSavePending) {
233
- this.$message.warning(this.$t1("数据正在保存,请勿重复操作"));
234
- return false;
235
- }
236
-
237
- formRef.formSavePending = true;
238
- let keepSaveLocked = false;
239
- try {
240
- let valid = await new Promise((resolve, reject) => {
241
- try {
242
- formRef.validate(resolve);
243
- } catch (error) {
244
- reject(error);
245
- }
246
- });
247
- if (!valid) return false;
248
-
249
- // 提交前置处理:校验通过后、确认框之前拦截。只有「保存并提交流程」入口会传,
250
- // 普通保存不受影响。返回 false 即中止(finally 里会释放 formSavePending)。
251
- let beforeConfirm = config.beforeConfirm;
252
- delete config.beforeConfirm; //不能透传给 formHttp
253
- if (typeof beforeConfirm === "function") {
254
- let pass = await beforeConfirm();
255
- if (pass === false) return false;
256
- }
257
-
258
- let submitFormData
259
- = typeof formRef.buildSubmitPayload === "function"
260
- ? await formRef.buildSubmitPayload()
261
- : formRef.getRealFormData();
262
- let reqData = this.getReqFormData(submitFormData);
263
- let shouldConfirm = config.isConfirm ?? true;
264
- let confirmText
265
- = config.confirmText || this.$t1("您确定要保存吗?");
266
- if (shouldConfirm) {
267
- try {
268
- await this.$baseConfirm(confirmText);
269
- } catch (error) {
270
- return false;
271
- }
272
- }
273
-
274
- let userSuccess = config.success;
275
- let userFail = config.fail;
276
- let userError = config.error;
277
- let userCallback = config.callback;
278
-
279
- return await new Promise((resolve, reject) => {
280
- let settled = false;
281
- let resolveOnce = (value) => {
282
- if (settled) return;
283
- settled = true;
284
- resolve(value);
285
- };
286
- let rejectOnce = (error) => {
287
- if (settled) return;
288
- settled = true;
289
- reject(error);
290
- };
291
- let requestTask = this.formHttp({
292
- // url: "/" + reportTemplate.serviceName + "/form_ins/saveUpdate",
293
- scriptCode: scriptCode,
294
- data: reqData,
295
- successMsg: true,
296
- ...config,
297
- isConfirm: false,
298
- confirmText,
299
- callback: undefined,
300
- success: async (res, response) => {
301
- let editFormCode = formConfig.editFormCode;
302
- let updateParam = {
303
- dataId: res.objx,
304
- };
305
- if (editFormCode) {
306
- updateParam.formCode = editFormCode;
307
- }
308
- // 保存成功后一律让宿主整页重挂载(toggle v-if):新增/编辑都需要出流程信息、
309
- // 切编辑模板、按新状态重算显隐与只读——这些就地取数做不到。
310
- // 唯一例外是「保存并提交」路径(skipHostReloadAfterSave=true),
311
- // 它的刷新交给流程结束后的 onWfStartSuccess / onWfStartAbort,避免刷两次。
312
- //
313
- // 刷新前只同步「元数据」,绝不做整单 setFormData:表单已挂载时那是逐字段同步
314
- // 广播赋值,每个字段的 onChange 会当场执行,先赋值组件的脚本读到的是后赋值
315
- // 组件的旧值,公式与 keyName 联动乱序,子表还会被整体清空(getOne 不返回子表)。
316
- // 首屏之所以正确,靠的是「先填满 formDataModel 再挂载组件」,就地整单赋值破坏
317
- // 了这个前提,故整单刷新只能走重挂载。
318
- let syncSavedMeta = () => {
319
- let nextDataId = updateParam.dataId;
320
- if (
321
- nextDataId !== undefined
322
- && nextDataId !== null
323
- && nextDataId !== ""
324
- ) {
325
- // 提交报文的主表以 currentFormData 打底(见 form-render getSubData2),
326
- // 主键 id 不是表单控件,只能在此回写;否则 skip 路径下再次保存会重复入库。
327
- if (
328
- formRef.currentFormData
329
- && formRef.currentFormData.id == null
330
- ) {
331
- formRef.currentFormData.id = nextDataId;
332
- }
333
- formRef.$emit("update:dataId", nextDataId);
334
- }
335
- if (updateParam.formCode) {
336
- formRef.$emit("update:formCode", updateParam.formCode);
337
- }
338
- };
339
- let runHostReload = async () => {
340
- if (skipHostReloadAfterSave) return;
341
- await formRef.reloadForm({ updateParam });
342
- };
343
- let callbacksStarted = false;
344
- let runPostReloadCallbacks = async () => {
345
- if (callbacksStarted) return res;
346
- callbacksStarted = true;
347
- let callbacks = [
348
- () => this.onAfterSaveHandle(reqData, res, response),
349
- typeof userSuccess === "function"
350
- ? () => userSuccess(res)
351
- : null,
352
- typeof option?.callback === "function"
353
- ? () => option.callback(res)
354
- : null,
355
- typeof userCallback === "function"
356
- ? () => userCallback(res, response)
357
- : null,
358
- ].filter(Boolean);
359
- let hasCallbackError = false;
360
- let firstCallbackError;
361
- for (let index = 0; index < callbacks.length; index++) {
362
- try {
363
- await callbacks[index]();
364
- } catch (error) {
365
- if (!hasCallbackError) {
366
- hasCallbackError = true;
367
- firstCallbackError = error;
368
- }
369
- }
370
- }
371
- if (hasCallbackError) {
372
- let detail
373
- = firstCallbackError?.message || firstCallbackError;
374
- let message = this.$t1("已保存,后续处理失败");
375
- this.$message.error(
376
- detail ? `${message}:${detail}` : message
377
- );
378
- }
379
- return res;
380
- };
381
- let clearReconcileState = () => {
382
- formRef.formSaveReconcileTask = null;
383
- formRef.formSavePending = false;
384
- };
385
- let showHostReloadFailure = (retry, error) => {
386
- let detail = error?.message;
387
- let message = this.$t1(
388
- retry
389
- ? "重新加载仍未成功,请手动刷新后再操作"
390
- : "保存成功,但重新加载失败,请手动刷新后再操作"
391
- );
392
- this.$message.error(
393
- detail ? `${message}:${detail}` : message
394
- );
395
- };
396
- let installHostReloadRetry = (error) => {
397
- keepSaveLocked = true;
398
- let hostRetryPromise = null;
399
- formRef.formSaveReconcileTask = () => {
400
- if (hostRetryPromise) return hostRetryPromise;
401
- hostRetryPromise = (async () => {
402
- try {
403
- await runHostReload();
404
- clearReconcileState();
405
- return res;
406
- } catch (retryError) {
407
- showHostReloadFailure(true, retryError);
408
- return false;
409
- } finally {
410
- hostRetryPromise = null;
411
- }
412
- })();
413
- return hostRetryPromise;
414
- };
415
- showHostReloadFailure(false, error);
416
- };
417
- syncSavedMeta();
418
- await runPostReloadCallbacks();
419
- try {
420
- await runHostReload();
421
- } catch (hostReloadError) {
422
- installHostReloadRetry(hostReloadError);
423
- rejectOnce(hostReloadError);
424
- return;
425
- }
426
- resolveOnce(res);
427
- },
428
- fail: (res, response) => {
429
- this.onAfterSaveHandle(reqData, res, response);
430
- if (typeof option?.callback === "function") {
431
- option.callback(res);
432
- }
433
- if (typeof userCallback === "function") {
434
- userCallback(res, response);
435
- }
436
- Promise.resolve(
437
- typeof userFail === "function" ? userFail(res) : null
438
- ).then(() => resolveOnce(res), rejectOnce);
439
- },
440
- error: (error) => {
441
- Promise.resolve(
442
- typeof userError === "function" ? userError(error) : null
443
- ).then(() => rejectOnce(error), rejectOnce);
444
- },
445
- });
446
- if (requestTask && typeof requestTask.catch === "function") {
447
- requestTask.catch(rejectOnce);
448
- }
449
- });
450
- } catch (error) {
451
- if (!keepSaveLocked) {
452
- let message
453
- = error?.message || error || this.$t1("保存失败");
454
- this.$message.error(message);
455
- }
456
- return false;
457
- } finally {
458
- if (!keepSaveLocked) {
459
- formRef.formSavePending = false;
460
- }
461
- }
462
- },
463
- setInterval(callback, millis) {
464
- let timer = setInterval(callback, millis);
465
- this.timerMap[timer] = timer;
466
- },
467
- clearInterval(timer) {
468
- clearInterval(timer);
469
- delete this.timerMap[timer];
470
- },
471
- getGlobalConfig() {
472
- let that = this.getFormRef ? this.getFormRef() : this;
473
- return that.globalConfig;
474
- },
475
- getFormParam() {
476
- let that = this.getFormRef ? this.getFormRef() : this;
477
- return that.param;
478
- },
479
- setGlobalParam(key, value) {
480
- let that = this.getFormRef ? this.getFormRef() : this;
481
- that.globalParam[key] = value;
482
- },
483
- getGlobalParam(key) {
484
- let that = this.getFormRef ? this.getFormRef() : this;
485
- return that.globalParam[key];
486
- },
487
-
488
- //1个数组的行号处理 begin
489
- /**
490
- * 删除列表中的行并刷新序列
491
- * @param {Array} list - 数据数组
492
- * @param {number} deleteIndex - 要删除的索引
493
- * @param {string} [seqField='seq'] - 序列字段名
494
- * @returns {Array} 更新后的数组
495
- */
496
- deleteNumRows(list, deleteIndex, seqField = "seq") {
497
- if (deleteIndex < 0 || deleteIndex >= list.length) {
498
- return [...list]; // 返回副本
499
- }
500
-
501
- // 创建副本避免修改原数组
502
- const newList = [...list];
503
-
504
- // 删除指定行
505
- newList.splice(deleteIndex, 1);
506
-
507
- // 重新计算序列
508
- newList.forEach((item, index) => {
509
- item[seqField] = index + 1;
510
- });
511
-
512
- return newList;
513
- },
514
-
515
- /**
516
- * 在列表中插入新行并刷新序列
517
- * @param {Array} list - 数据数组
518
- * @param {Array|Object} newItems - 要插入的新项(单个或多个)
519
- * @param {number} [insertIndex] - 插入位置(默认末尾)
520
- * @param {string} [seqField='seq'] - 序列字段名
521
- * @returns {Array} 更新后的数组
522
- */
523
- insertNumRows(list, newItems, insertIndex = list.length, seqField = "seq") {
524
- // 参数标准化
525
- const itemsArray = Array.isArray(newItems) ? newItems : [newItems];
526
-
527
- // 边界检查
528
- insertIndex = Math.max(0, Math.min(insertIndex, list.length));
529
-
530
- // 创建副本
531
- const newList = [...list];
532
-
533
- // 准备要插入的项(移除可能的旧序列)
534
- const itemsToInsert = itemsArray.map((item) => {
535
- const newItem = { ...item };
536
- if (newItem[seqField] !== undefined) {
537
- delete newItem[seqField];
538
- }
539
- return newItem;
540
- });
541
-
542
- // 插入新项
543
- newList.splice(insertIndex, 0, ...itemsToInsert);
544
-
545
- // 重新计算所有序列
546
- newList.forEach((item, index) => {
547
- item[seqField] = index + 1;
548
- });
549
-
550
- return newList;
551
- },
552
- //1个数组的行号处理 end
553
-
554
- //2个数组的行号处理 begin
555
- /**
556
- * 通用新增方法(支持单条/多条)
557
- * @param {Array} list1 - 主数据数组
558
- * @param {Array} list2 - 关联数据数组
559
- * @param {Array|Object} newItems - 要插入的新项(单个对象或多个对象的数组)
560
- * @param {Array|Array[]} subItems - 关联数据(单个数组或多个数组的数组)
561
- * @param {number} [insertIndex] - 插入位置(默认末尾)
562
- * @param {string} [seqField='seq'] - list1中的序列字段名
563
- * @param {string} [superSeqField='super_seq'] - list2中的关联字段名
564
- * @returns {Object} 包含更新后的list1和list2
565
- */
566
- insertRelRows(
567
- list1,
568
- list2,
569
- newItems,
570
- subItems = [],
571
- insertIndex,
572
- seqField = "seq",
573
- superSeqField = "super_seq"
574
- ) {
575
- // 参数标准化
576
- const itemsArray = Array.isArray(newItems) ? newItems : [newItems];
577
- const subItemsArray =
578
- Array.isArray(subItems) &&
579
- subItems.length > 0 &&
580
- Array.isArray(subItems[0])
581
- ? subItems
582
- : [subItems];
583
-
584
- if (insertIndex === null || insertIndex === undefined) {
585
- insertIndex = list1.length;
586
- }
587
- // 边界检查
588
- insertIndex = Math.max(0, Math.min(insertIndex, list1.length));
589
-
590
- // 创建副本
591
- const newList1 = [...list1];
592
- const newList2 = [...list2];
593
-
594
- // 1. 插入新项(不设置序列)
595
- const itemsToInsert = itemsArray.map((item) => {
596
- const newItem = { ...item };
597
- if (newItem[seqField] !== undefined) {
598
- delete newItem[seqField]; // 确保不保留旧序列
599
- }
600
- return newItem;
601
- });
602
-
603
- newList1.splice(insertIndex, 0, ...itemsToInsert);
604
-
605
- // 2. 重新计算序列并创建映射表
606
- const seqMapping = {};
607
- newList1.forEach((item, index) => {
608
- const newSeq = index + 1;
609
- const oldSeq = item[seqField];
610
-
611
- // 记录旧序列到新序列的映射
612
- if (oldSeq !== undefined) {
613
- seqMapping[oldSeq] = newSeq;
614
- }
615
-
616
- // 更新当前项序列
617
- item[seqField] = newSeq;
618
- });
619
-
620
- // 3. 获取新插入项的序列
621
- const newSeqs = [];
622
- for (let i = 0; i < itemsArray.length; i++) {
623
- newSeqs.push(insertIndex + i + 1);
624
- }
625
-
626
- // 4. 更新现有关联字段
627
- newList2.forEach((item) => {
628
- const currentSuperSeq = item[superSeqField];
629
- if (seqMapping[currentSuperSeq] !== undefined) {
630
- item[superSeqField] = seqMapping[currentSuperSeq];
631
- }
632
- });
633
-
634
- // 5. 添加新关联数据
635
- subItemsArray.forEach((subItems, idx) => {
636
- const superSeq = newSeqs[idx];
637
- if (Array.isArray(subItems) && subItems.length > 0) {
638
- subItems.forEach((item) => {
639
- const newItem = { ...item };
640
- newItem[superSeqField] = superSeq;
641
- newList2.push(newItem);
642
- });
643
- }
644
- });
645
-
646
- return { list1: newList1, list2: newList2 };
647
- },
648
-
649
- /**
650
- * 删除关联行(从属数据打平传参)
651
- * @param {Array} masters - 主数据数组
652
- * @param {Array} slaves - 从属数据数组(打平的一维数组)
653
- * @param {number} rowIdx - 要删除的主数据行索引
654
- * @param {string} [seqField='seq'] - 行号字段名
655
- * @param {string} [refField='super_seq'] - 关联字段名
656
- * @returns {Object} 包含更新后的masters和slaves
657
- */
658
- deleteRelRows(
659
- masters,
660
- slaves,
661
- rowIdx,
662
- seqField = "seq",
663
- refField = "super_seq"
664
- ) {
665
- // 边界检查(返回结构与正常分支保持一致:list1/list2)
666
- if (rowIdx < 0 || rowIdx >= masters.length) {
667
- return { list1: [...masters], list2: [...slaves] };
668
- }
669
-
670
- // 创建副本
671
- const newMasters = [...masters];
672
- const newSlaves = [...slaves];
673
-
674
- // 1. 删除主行并获取序列值
675
- const [deleted] = newMasters.splice(rowIdx, 1);
676
- const deletedSeq = deleted[seqField];
677
-
678
- // 2. 删除关联的从属行
679
- for (let i = newSlaves.length - 1; i >= 0; i--) {
680
- if (newSlaves[i][refField] === deletedSeq) {
681
- newSlaves.splice(i, 1);
682
- }
683
- }
684
-
685
- // 3. 重新计算主数据序列
686
- const seqMap = {};
687
- newMasters.forEach((master, idx) => {
688
- const oldSeq = master[seqField];
689
- const newSeq = idx + 1;
690
- seqMap[oldSeq] = newSeq;
691
- master[seqField] = newSeq;
692
- });
693
-
694
- // 4. 更新从属数据引用
695
- newSlaves.forEach((slave) => {
696
- const ref = slave[refField];
697
- if (seqMap[ref] !== undefined) {
698
- slave[refField] = seqMap[ref];
699
- }
700
- });
701
-
702
- return { list1: newMasters, list2: newSlaves };
703
- },
704
- //2个数组的行号处理 end
705
- },
706
- };
707
- export default modules;
1
+ import moment from "moment";
2
+ import { saveUserLog } from "@base/api/user";
3
+ import settingConfig from "@/settings";
4
+ import { openStartWfDialog } from "../../../components/wf/wfUtil";
5
+ import { getWfButtonNames } from "../../../components/wf/wfButtonName";
6
+ import {
7
+ isWfAutoConfirm,
8
+ loadWfAutoConfirm,
9
+ } from "../../../components/wf/wfAutoConfirm";
10
+ import {
11
+ isWfUserRangeEnabled,
12
+ loadWfUserRange,
13
+ } from "../../../components/wf/wfUserRange";
14
+
15
+ let modules = {};
16
+ modules = {
17
+ data() {
18
+ return {
19
+ timerMap: {},
20
+ formSavePending: false,
21
+ formSaveReconcileTask: null,
22
+ };
23
+ },
24
+ beforeDestroy() {
25
+ for (let timer in this.timerMap) {
26
+ this.clearInterval(timer);
27
+ }
28
+ },
29
+ methods: {
30
+ getMoment(date) {
31
+ return moment(date);
32
+ },
33
+ getMomentUtil() {
34
+ return moment;
35
+ },
36
+ utcToCst(utcStr) {
37
+ if (!utcStr) return utcStr;
38
+ return moment.utc(utcStr).local().format("YYYY-MM-DD HH:mm:ss");
39
+ },
40
+ cstToUtc(localTime) {
41
+ if (!localTime) return localTime;
42
+ return moment(localTime).utc().format("YYYY-MM-DD HH:mm:ss");
43
+ },
44
+ addFormatDateTime(date, num, type, format) {
45
+ if (!date) return date;
46
+ if (!type) type = "days";
47
+ if (!format) format = "YYYY-MM-DD HH:mm:ss";
48
+ return moment(date).add(num, type).format(format);
49
+ },
50
+ addFormatDate(date, num) {
51
+ if (!date) return date;
52
+ return moment(date).add(num, "days").format("YYYY-MM-DD");
53
+ },
54
+ formatDate(date) {
55
+ if (!date) return date;
56
+ return moment(date).format("YYYY-MM-DD");
57
+ },
58
+ formatDateTime(date, format = "YYYY-MM-DD HH:mm:ss") {
59
+ if (!date) return date;
60
+ return moment(date).format(format);
61
+ },
62
+ getFormEntityData(reqFormData) {
63
+ let formRef = this.getFormRef ? this.getFormRef() : this;
64
+ let formConfig = formRef.formConfig;
65
+ let entity = formConfig.entity;
66
+ if (!reqFormData) reqFormData = this.getReqFormData();
67
+ return reqFormData.data[entity];
68
+ },
69
+ getReqFormData(formDataOverride) {
70
+ let formRef = this.getFormRef ? this.getFormRef() : this;
71
+ let formConfig = formRef.formConfig;
72
+ let entity = formConfig.entity;
73
+ if (!entity) return;
74
+ let dataId = formRef?.dataId;
75
+ let reportTemplate = formRef?.reportTemplate;
76
+ let formCode = reportTemplate?.formCode;
77
+
78
+ let sourceFormData
79
+ = arguments.length > 0
80
+ ? formDataOverride
81
+ : formRef.getRealFormData();
82
+ let formData = this.$baseLodash.cloneDeep(sourceFormData);
83
+ let mainData = formData[formCode] || {};
84
+ delete formData[formCode];
85
+ formData[entity] = mainData;
86
+
87
+ delete mainData._createNickName;
88
+ delete mainData._createMobile;
89
+ delete mainData._modifyNickName;
90
+ delete mainData._modifyMobile;
91
+ delete mainData._createBy;
92
+ delete mainData._modifyBy;
93
+
94
+ // 移除对比功能给各子表行加的快照字段 hData,避免提交到后端
95
+ let stripHData = (obj) => {
96
+ if (!obj || typeof obj !== "object") return;
97
+ if (Array.isArray(obj)) {
98
+ obj.forEach(stripHData);
99
+ return;
100
+ }
101
+ if (Object.prototype.hasOwnProperty.call(obj, "hData")) {
102
+ delete obj.hData;
103
+ }
104
+ Object.keys(obj).forEach((k) => {
105
+ let v = obj[k];
106
+ if (v && typeof v === "object") stripHData(v);
107
+ });
108
+ };
109
+ stripHData(formData);
110
+
111
+ let reqData = {
112
+ formCode: formCode,
113
+ formVersion: reportTemplate.formVersion,
114
+ taBm: entity,
115
+ data: formData,
116
+ };
117
+ return reqData;
118
+ },
119
+ onAfterSaveHandle(reqData, res, response) {
120
+ //保存成功后处理
121
+ this.writeSaveLog(reqData, res, response); //写入保存日志
122
+ },
123
+ writeSaveLog(reqData, res, response) {
124
+ //写入保存日志
125
+ if (settingConfig.formSaveLogEnabled && res.type === "success") {
126
+ let responseConfig = response.config || {};
127
+ let formRef = this.getFormRef ? this.getFormRef() : this;
128
+ let reportTemplate = formRef ? formRef.reportTemplate : null;
129
+ if (!reportTemplate) return;
130
+ let isAdd = !formRef.dataId;
131
+ let formName = reportTemplate?.formName;
132
+ let formCode = reportTemplate?.formCode;
133
+ let action = isAdd ? "新增成功" : "更新成功";
134
+ let content = `${formName}(${formCode}),${action}`;
135
+ let path = responseConfig.url.replace(responseConfig.baseURL, "");
136
+ saveUserLog({
137
+ data: {
138
+ path: path,
139
+ businessCode: content,
140
+ content: content,
141
+ },
142
+ });
143
+ }
144
+ },
145
+ async saveAndSubmitWfHandle(option) {
146
+ let formRef = this.getFormRef ? this.getFormRef() : this;
147
+ let formConfig = formRef.formConfig;
148
+ let reportTemplate = formRef.reportTemplate;
149
+ let wfParam = formRef.wfParam || {};
150
+
151
+ if (!formConfig?.wfEnabled) return;
152
+ if (
153
+ formRef.hasWf ||
154
+ wfParam.hasWf ||
155
+ wfParam.wfInfo?.procInstId
156
+ ) {
157
+ return;
158
+ }
159
+
160
+ // 提示语跟随后台 param18 下发的按钮名;没配就保持原文案不变。
161
+ // 只能用 $t1(找不到 key 原样返回),$t2 在 i18n 命中 key 时会把拼进去的名字吞掉。
162
+ let btnName = getWfButtonNames().saveSubmitBtn;
163
+ let confirmText =
164
+ option?.confirmText ||
165
+ option?.config?.confirmText ||
166
+ (btnName
167
+ ? this.$t1(`您确定要${btnName}吗?`)
168
+ : this.$t1("您确定要保存并提交流程吗?"));
169
+
170
+ // param19 + param12 都开:这道「保存前」的确认框不弹,把确认交给 wfStartDialog——
171
+ // 它在保存之后才探 preStart,探到候选人就由「修改候选人」弹框顶替,探不到才弹确认框。
172
+ // 任一为 false 都当场弹:param12 关着后面不可能有候选人弹框,延后没有意义。
173
+ await Promise.all([loadWfAutoConfirm(this), loadWfUserRange(this)]);
174
+ let autoConfirm = isWfAutoConfirm() && isWfUserRangeEnabled();
175
+
176
+ return this.saveDefaultHandle({
177
+ ...option,
178
+ config: {
179
+ ...option?.config,
180
+ successMsg: option?.config?.successMsg ?? true,
181
+ isConfirm: option?.config?.isConfirm ?? !autoConfirm,
182
+ confirmText: option?.config?.confirmText || confirmText,
183
+ skipHostReloadAfterSave: true,
184
+ //提交前置处理(与「提交流程」按钮共用同一个表单脚本)
185
+ beforeConfirm: () =>
186
+ formRef.runBeforeSubmitWf
187
+ ? formRef.runBeforeSubmitWf("saveSubmit")
188
+ : true,
189
+ success: (res) => {
190
+ // ★ 保存成功后,只升级新增页签的「元数据」为编辑态(设 dataId/isAdd/formCode/标题),
191
+ // 不重渲染详情内容。这样即便随后提交失败/取消候选人弹框,页签也已是编辑态,
192
+ // 再次提交只会更新、不会重复入库;而详情内容的刷新交给最终的提交后行为(避免刷两次)。
193
+ let editFormCode = formConfig.editFormCode;
194
+ let listPageRef = formRef.getListPageRef && formRef.getListPageRef();
195
+ if (listPageRef && listPageRef.promoteActiveAddTab) {
196
+ listPageRef.promoteActiveAddTab(res.objx, editFormCode, formRef);
197
+ }
198
+
199
+ openStartWfDialog(formRef, {
200
+ objId: res.objx,
201
+ wfCode: reportTemplate.objTypeCode,
202
+ formCode: reportTemplate.formCode,
203
+ serviceId: reportTemplate.serviceName,
204
+ // 上面确认过就不再弹第二次;上面因 param19 跳过了,就得让弹框自己去确认
205
+ skipStartConfirm: !autoConfirm,
206
+ // 提交后行为复用 xform 侧统一方法:
207
+ // 成功 → onWfStartSuccess(关页签/刷新本页);失败/取消 → onWfStartAbort(刷新到编辑态)
208
+ onStartSuccess: () =>
209
+ formRef.onWfStartSuccess && formRef.onWfStartSuccess(),
210
+ onStartFail: () =>
211
+ formRef.onWfStartAbort && formRef.onWfStartAbort(),
212
+ onCancel: () => formRef.onWfStartAbort && formRef.onWfStartAbort(),
213
+ });
214
+ option?.config?.success && option.config.success(res);
215
+ },
216
+ },
217
+ });
218
+ },
219
+ async saveDefaultHandle(option) {
220
+ let formRef = this.getFormRef ? this.getFormRef() : this;
221
+ let formConfig = formRef.formConfig;
222
+ let entity = formConfig.entity;
223
+ if (!entity) return;
224
+ let scriptCode = formConfig.saveScriptCode || "saveUpdate";
225
+ let config = { ...(option?.config || {}) };
226
+ let skipHostReloadAfterSave
227
+ = config.skipHostReloadAfterSave === true;
228
+ delete config.skipHostReloadAfterSave;
229
+ if (typeof formRef.formSaveReconcileTask === "function") {
230
+ return formRef.formSaveReconcileTask();
231
+ }
232
+ if (formRef.formSavePending) {
233
+ this.$message.warning(this.$t1("数据正在保存,请勿重复操作"));
234
+ return false;
235
+ }
236
+
237
+ formRef.formSavePending = true;
238
+ let keepSaveLocked = false;
239
+ try {
240
+ let valid = await new Promise((resolve, reject) => {
241
+ try {
242
+ formRef.validate(resolve);
243
+ } catch (error) {
244
+ reject(error);
245
+ }
246
+ });
247
+ if (!valid) return false;
248
+
249
+ // 提交前置处理:校验通过后、确认框之前拦截。只有「保存并提交流程」入口会传,
250
+ // 普通保存不受影响。返回 false 即中止(finally 里会释放 formSavePending)。
251
+ let beforeConfirm = config.beforeConfirm;
252
+ delete config.beforeConfirm; //不能透传给 formHttp
253
+ if (typeof beforeConfirm === "function") {
254
+ let pass = await beforeConfirm();
255
+ if (pass === false) return false;
256
+ }
257
+
258
+ let submitFormData
259
+ = typeof formRef.buildSubmitPayload === "function"
260
+ ? await formRef.buildSubmitPayload()
261
+ : formRef.getRealFormData();
262
+ let reqData = this.getReqFormData(submitFormData);
263
+ // 快照必须在前置钩子和 payload 之后、确认框之前,避免把钩子合法改数当成确认期间篡改。
264
+ const fieldControlSnapshot = formRef.captureFieldControlSubmission
265
+ ? formRef.captureFieldControlSubmission() : null;
266
+ let shouldConfirm = config.isConfirm ?? true;
267
+ let confirmText
268
+ = config.confirmText || this.$t1("您确定要保存吗?");
269
+ if (shouldConfirm) {
270
+ try {
271
+ await this.$baseConfirm(confirmText);
272
+ } catch (error) {
273
+ return false;
274
+ }
275
+ }
276
+
277
+ if (formRef.assertFieldControlSubmission) {
278
+ await formRef.assertFieldControlSubmission(fieldControlSnapshot);
279
+ }
280
+ let userSuccess = config.success;
281
+ let userFail = config.fail;
282
+ let userError = config.error;
283
+ let userCallback = config.callback;
284
+
285
+ return await new Promise((resolve, reject) => {
286
+ let settled = false;
287
+ let resolveOnce = (value) => {
288
+ if (settled) return;
289
+ settled = true;
290
+ resolve(value);
291
+ };
292
+ let rejectOnce = (error) => {
293
+ if (settled) return;
294
+ settled = true;
295
+ reject(error);
296
+ };
297
+ let requestTask = this.formHttp({
298
+ // url: "/" + reportTemplate.serviceName + "/form_ins/saveUpdate",
299
+ scriptCode: scriptCode,
300
+ data: reqData,
301
+ successMsg: true,
302
+ ...config,
303
+ isConfirm: false,
304
+ confirmText,
305
+ callback: undefined,
306
+ success: async (res, response) => {
307
+ let editFormCode = formConfig.editFormCode;
308
+ let updateParam = {
309
+ dataId: res.objx,
310
+ };
311
+ if (editFormCode) {
312
+ updateParam.formCode = editFormCode;
313
+ }
314
+ // 保存成功后一律让宿主整页重挂载(toggle v-if):新增/编辑都需要出流程信息、
315
+ // 切编辑模板、按新状态重算显隐与只读——这些就地取数做不到。
316
+ // 唯一例外是「保存并提交」路径(skipHostReloadAfterSave=true),
317
+ // 它的刷新交给流程结束后的 onWfStartSuccess / onWfStartAbort,避免刷两次。
318
+ //
319
+ // 刷新前只同步「元数据」,绝不做整单 setFormData:表单已挂载时那是逐字段同步
320
+ // 广播赋值,每个字段的 onChange 会当场执行,先赋值组件的脚本读到的是后赋值
321
+ // 组件的旧值,公式与 keyName 联动乱序,子表还会被整体清空(getOne 不返回子表)。
322
+ // 首屏之所以正确,靠的是「先填满 formDataModel 再挂载组件」,就地整单赋值破坏
323
+ // 了这个前提,故整单刷新只能走重挂载。
324
+ let syncSavedMeta = () => {
325
+ let nextDataId = updateParam.dataId;
326
+ if (
327
+ nextDataId !== undefined
328
+ && nextDataId !== null
329
+ && nextDataId !== ""
330
+ ) {
331
+ // 提交报文的主表以 currentFormData 打底(见 form-render getSubData2),
332
+ // 主键 id 不是表单控件,只能在此回写;否则 skip 路径下再次保存会重复入库。
333
+ if (
334
+ formRef.currentFormData
335
+ && formRef.currentFormData.id == null
336
+ ) {
337
+ formRef.currentFormData.id = nextDataId;
338
+ }
339
+ formRef.$emit("update:dataId", nextDataId);
340
+ }
341
+ if (updateParam.formCode) {
342
+ formRef.$emit("update:formCode", updateParam.formCode);
343
+ }
344
+ };
345
+ let runHostReload = async () => {
346
+ if (skipHostReloadAfterSave) return;
347
+ await formRef.reloadForm({ updateParam });
348
+ };
349
+ let callbacksStarted = false;
350
+ let runPostReloadCallbacks = async () => {
351
+ if (callbacksStarted) return res;
352
+ callbacksStarted = true;
353
+ let callbacks = [
354
+ () => this.onAfterSaveHandle(reqData, res, response),
355
+ typeof userSuccess === "function"
356
+ ? () => userSuccess(res)
357
+ : null,
358
+ typeof option?.callback === "function"
359
+ ? () => option.callback(res)
360
+ : null,
361
+ typeof userCallback === "function"
362
+ ? () => userCallback(res, response)
363
+ : null,
364
+ ].filter(Boolean);
365
+ let hasCallbackError = false;
366
+ let firstCallbackError;
367
+ for (let index = 0; index < callbacks.length; index++) {
368
+ try {
369
+ await callbacks[index]();
370
+ } catch (error) {
371
+ if (!hasCallbackError) {
372
+ hasCallbackError = true;
373
+ firstCallbackError = error;
374
+ }
375
+ }
376
+ }
377
+ if (hasCallbackError) {
378
+ let detail
379
+ = firstCallbackError?.message || firstCallbackError;
380
+ let message = this.$t1("已保存,后续处理失败");
381
+ this.$message.error(
382
+ detail ? `${message}:${detail}` : message
383
+ );
384
+ }
385
+ return res;
386
+ };
387
+ let clearReconcileState = () => {
388
+ formRef.formSaveReconcileTask = null;
389
+ formRef.formSavePending = false;
390
+ };
391
+ let showHostReloadFailure = (retry, error) => {
392
+ let detail = error?.message;
393
+ let message = this.$t1(
394
+ retry
395
+ ? "重新加载仍未成功,请手动刷新后再操作"
396
+ : "保存成功,但重新加载失败,请手动刷新后再操作"
397
+ );
398
+ this.$message.error(
399
+ detail ? `${message}:${detail}` : message
400
+ );
401
+ };
402
+ let installHostReloadRetry = (error) => {
403
+ keepSaveLocked = true;
404
+ let hostRetryPromise = null;
405
+ formRef.formSaveReconcileTask = () => {
406
+ if (hostRetryPromise) return hostRetryPromise;
407
+ hostRetryPromise = (async () => {
408
+ try {
409
+ await runHostReload();
410
+ clearReconcileState();
411
+ return res;
412
+ } catch (retryError) {
413
+ showHostReloadFailure(true, retryError);
414
+ return false;
415
+ } finally {
416
+ hostRetryPromise = null;
417
+ }
418
+ })();
419
+ return hostRetryPromise;
420
+ };
421
+ showHostReloadFailure(false, error);
422
+ };
423
+ syncSavedMeta();
424
+ await runPostReloadCallbacks();
425
+ try {
426
+ await runHostReload();
427
+ } catch (hostReloadError) {
428
+ installHostReloadRetry(hostReloadError);
429
+ rejectOnce(hostReloadError);
430
+ return;
431
+ }
432
+ resolveOnce(res);
433
+ },
434
+ fail: (res, response) => {
435
+ this.onAfterSaveHandle(reqData, res, response);
436
+ if (typeof option?.callback === "function") {
437
+ option.callback(res);
438
+ }
439
+ if (typeof userCallback === "function") {
440
+ userCallback(res, response);
441
+ }
442
+ Promise.resolve(
443
+ typeof userFail === "function" ? userFail(res) : null
444
+ ).then(() => resolveOnce(res), rejectOnce);
445
+ },
446
+ error: (error) => {
447
+ Promise.resolve(
448
+ typeof userError === "function" ? userError(error) : null
449
+ ).then(() => rejectOnce(error), rejectOnce);
450
+ },
451
+ });
452
+ if (requestTask && typeof requestTask.catch === "function") {
453
+ requestTask.catch(rejectOnce);
454
+ }
455
+ });
456
+ } catch (error) {
457
+ if (!keepSaveLocked) {
458
+ let message
459
+ = error?.message || error || this.$t1("保存失败");
460
+ this.$message.error(message);
461
+ }
462
+ return false;
463
+ } finally {
464
+ if (!keepSaveLocked) {
465
+ formRef.formSavePending = false;
466
+ }
467
+ }
468
+ },
469
+ setInterval(callback, millis) {
470
+ let timer = setInterval(callback, millis);
471
+ this.timerMap[timer] = timer;
472
+ },
473
+ clearInterval(timer) {
474
+ clearInterval(timer);
475
+ delete this.timerMap[timer];
476
+ },
477
+ getGlobalConfig() {
478
+ let that = this.getFormRef ? this.getFormRef() : this;
479
+ return that.globalConfig;
480
+ },
481
+ getFormParam() {
482
+ let that = this.getFormRef ? this.getFormRef() : this;
483
+ return that.param;
484
+ },
485
+ setGlobalParam(key, value) {
486
+ let that = this.getFormRef ? this.getFormRef() : this;
487
+ that.globalParam[key] = value;
488
+ },
489
+ getGlobalParam(key) {
490
+ let that = this.getFormRef ? this.getFormRef() : this;
491
+ return that.globalParam[key];
492
+ },
493
+
494
+ //1个数组的行号处理 begin
495
+ /**
496
+ * 删除列表中的行并刷新序列
497
+ * @param {Array} list - 数据数组
498
+ * @param {number} deleteIndex - 要删除的索引
499
+ * @param {string} [seqField='seq'] - 序列字段名
500
+ * @returns {Array} 更新后的数组
501
+ */
502
+ deleteNumRows(list, deleteIndex, seqField = "seq") {
503
+ if (deleteIndex < 0 || deleteIndex >= list.length) {
504
+ return [...list]; // 返回副本
505
+ }
506
+
507
+ // 创建副本避免修改原数组
508
+ const newList = [...list];
509
+
510
+ // 删除指定行
511
+ newList.splice(deleteIndex, 1);
512
+
513
+ // 重新计算序列
514
+ newList.forEach((item, index) => {
515
+ item[seqField] = index + 1;
516
+ });
517
+
518
+ return newList;
519
+ },
520
+
521
+ /**
522
+ * 在列表中插入新行并刷新序列
523
+ * @param {Array} list - 数据数组
524
+ * @param {Array|Object} newItems - 要插入的新项(单个或多个)
525
+ * @param {number} [insertIndex] - 插入位置(默认末尾)
526
+ * @param {string} [seqField='seq'] - 序列字段名
527
+ * @returns {Array} 更新后的数组
528
+ */
529
+ insertNumRows(list, newItems, insertIndex = list.length, seqField = "seq") {
530
+ // 参数标准化
531
+ const itemsArray = Array.isArray(newItems) ? newItems : [newItems];
532
+
533
+ // 边界检查
534
+ insertIndex = Math.max(0, Math.min(insertIndex, list.length));
535
+
536
+ // 创建副本
537
+ const newList = [...list];
538
+
539
+ // 准备要插入的项(移除可能的旧序列)
540
+ const itemsToInsert = itemsArray.map((item) => {
541
+ const newItem = { ...item };
542
+ if (newItem[seqField] !== undefined) {
543
+ delete newItem[seqField];
544
+ }
545
+ return newItem;
546
+ });
547
+
548
+ // 插入新项
549
+ newList.splice(insertIndex, 0, ...itemsToInsert);
550
+
551
+ // 重新计算所有序列
552
+ newList.forEach((item, index) => {
553
+ item[seqField] = index + 1;
554
+ });
555
+
556
+ return newList;
557
+ },
558
+ //1个数组的行号处理 end
559
+
560
+ //2个数组的行号处理 begin
561
+ /**
562
+ * 通用新增方法(支持单条/多条)
563
+ * @param {Array} list1 - 主数据数组
564
+ * @param {Array} list2 - 关联数据数组
565
+ * @param {Array|Object} newItems - 要插入的新项(单个对象或多个对象的数组)
566
+ * @param {Array|Array[]} subItems - 关联数据(单个数组或多个数组的数组)
567
+ * @param {number} [insertIndex] - 插入位置(默认末尾)
568
+ * @param {string} [seqField='seq'] - list1中的序列字段名
569
+ * @param {string} [superSeqField='super_seq'] - list2中的关联字段名
570
+ * @returns {Object} 包含更新后的list1和list2
571
+ */
572
+ insertRelRows(
573
+ list1,
574
+ list2,
575
+ newItems,
576
+ subItems = [],
577
+ insertIndex,
578
+ seqField = "seq",
579
+ superSeqField = "super_seq"
580
+ ) {
581
+ // 参数标准化
582
+ const itemsArray = Array.isArray(newItems) ? newItems : [newItems];
583
+ const subItemsArray =
584
+ Array.isArray(subItems) &&
585
+ subItems.length > 0 &&
586
+ Array.isArray(subItems[0])
587
+ ? subItems
588
+ : [subItems];
589
+
590
+ if (insertIndex === null || insertIndex === undefined) {
591
+ insertIndex = list1.length;
592
+ }
593
+ // 边界检查
594
+ insertIndex = Math.max(0, Math.min(insertIndex, list1.length));
595
+
596
+ // 创建副本
597
+ const newList1 = [...list1];
598
+ const newList2 = [...list2];
599
+
600
+ // 1. 插入新项(不设置序列)
601
+ const itemsToInsert = itemsArray.map((item) => {
602
+ const newItem = { ...item };
603
+ if (newItem[seqField] !== undefined) {
604
+ delete newItem[seqField]; // 确保不保留旧序列
605
+ }
606
+ return newItem;
607
+ });
608
+
609
+ newList1.splice(insertIndex, 0, ...itemsToInsert);
610
+
611
+ // 2. 重新计算序列并创建映射表
612
+ const seqMapping = {};
613
+ newList1.forEach((item, index) => {
614
+ const newSeq = index + 1;
615
+ const oldSeq = item[seqField];
616
+
617
+ // 记录旧序列到新序列的映射
618
+ if (oldSeq !== undefined) {
619
+ seqMapping[oldSeq] = newSeq;
620
+ }
621
+
622
+ // 更新当前项序列
623
+ item[seqField] = newSeq;
624
+ });
625
+
626
+ // 3. 获取新插入项的序列
627
+ const newSeqs = [];
628
+ for (let i = 0; i < itemsArray.length; i++) {
629
+ newSeqs.push(insertIndex + i + 1);
630
+ }
631
+
632
+ // 4. 更新现有关联字段
633
+ newList2.forEach((item) => {
634
+ const currentSuperSeq = item[superSeqField];
635
+ if (seqMapping[currentSuperSeq] !== undefined) {
636
+ item[superSeqField] = seqMapping[currentSuperSeq];
637
+ }
638
+ });
639
+
640
+ // 5. 添加新关联数据
641
+ subItemsArray.forEach((subItems, idx) => {
642
+ const superSeq = newSeqs[idx];
643
+ if (Array.isArray(subItems) && subItems.length > 0) {
644
+ subItems.forEach((item) => {
645
+ const newItem = { ...item };
646
+ newItem[superSeqField] = superSeq;
647
+ newList2.push(newItem);
648
+ });
649
+ }
650
+ });
651
+
652
+ return { list1: newList1, list2: newList2 };
653
+ },
654
+
655
+ /**
656
+ * 删除关联行(从属数据打平传参)
657
+ * @param {Array} masters - 主数据数组
658
+ * @param {Array} slaves - 从属数据数组(打平的一维数组)
659
+ * @param {number} rowIdx - 要删除的主数据行索引
660
+ * @param {string} [seqField='seq'] - 行号字段名
661
+ * @param {string} [refField='super_seq'] - 关联字段名
662
+ * @returns {Object} 包含更新后的masters和slaves
663
+ */
664
+ deleteRelRows(
665
+ masters,
666
+ slaves,
667
+ rowIdx,
668
+ seqField = "seq",
669
+ refField = "super_seq"
670
+ ) {
671
+ // 边界检查(返回结构与正常分支保持一致:list1/list2)
672
+ if (rowIdx < 0 || rowIdx >= masters.length) {
673
+ return { list1: [...masters], list2: [...slaves] };
674
+ }
675
+
676
+ // 创建副本
677
+ const newMasters = [...masters];
678
+ const newSlaves = [...slaves];
679
+
680
+ // 1. 删除主行并获取序列值
681
+ const [deleted] = newMasters.splice(rowIdx, 1);
682
+ const deletedSeq = deleted[seqField];
683
+
684
+ // 2. 删除关联的从属行
685
+ for (let i = newSlaves.length - 1; i >= 0; i--) {
686
+ if (newSlaves[i][refField] === deletedSeq) {
687
+ newSlaves.splice(i, 1);
688
+ }
689
+ }
690
+
691
+ // 3. 重新计算主数据序列
692
+ const seqMap = {};
693
+ newMasters.forEach((master, idx) => {
694
+ const oldSeq = master[seqField];
695
+ const newSeq = idx + 1;
696
+ seqMap[oldSeq] = newSeq;
697
+ master[seqField] = newSeq;
698
+ });
699
+
700
+ // 4. 更新从属数据引用
701
+ newSlaves.forEach((slave) => {
702
+ const ref = slave[refField];
703
+ if (seqMap[ref] !== undefined) {
704
+ slave[refField] = seqMap[ref];
705
+ }
706
+ });
707
+
708
+ return { list1: newMasters, list2: newSlaves };
709
+ },
710
+ //2个数组的行号处理 end
711
+ },
712
+ };
713
+ export default modules;