cloud-web-corejs 1.0.54-dev.781 → 1.0.54-dev.783

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 (33) hide show
  1. package/package.json +1 -1
  2. package/src/components/VabUpload/index.vue +242 -242
  3. package/src/components/VabUpload/mixins.js +1971 -1971
  4. package/src/components/VabUpload/privateProfileDialog.vue +2 -2
  5. package/src/components/hiprint/view/design/index.vue +7 -2
  6. package/src/components/obsUpload/index.vue +234 -234
  7. package/src/components/obsUpload/mixins.js +1542 -1542
  8. package/src/components/vb-tabs/x-tabs.vue +7 -3
  9. package/src/components/xform/docs//346/240/221/350/241/250rowField/346/224/266/346/225/233-/350/220/275/345/234/260/346/226/207/346/241/243.md +1427 -0
  10. package/src/components/xform/form-designer/form-widget/dialog/formDialog.vue +2 -2
  11. package/src/components/xform/form-designer/form-widget/dialog/formDrawer.vue +2 -2
  12. package/src/components/xform/form-designer/form-widget/dialog/searchFormDialog.vue +2 -2
  13. package/src/components/xform/form-designer/form-widget/field-widget/copy_button-widget.vue +11 -2
  14. package/src/components/xform/form-designer/form-widget/field-widget/vabUpload-widget.vue +369 -369
  15. package/src/components/xform/form-designer/setting-panel/property-editor/fileTypesSelect.vue +69 -23
  16. package/src/components/xform/form-render/container-item/data-table-item.vue +1 -1
  17. package/src/components/xform/form-render/container-item/data-table-mixin.js +1469 -428
  18. package/src/components/xform/form-render/indexMixin.js +379 -263
  19. package/src/components/xform/mixins/defaultHandle.js +234 -28
  20. package/src/components/xform/mixins/scriptHttp.js +4 -4
  21. package/src/components/xform/utils/treeTableUtil.js +1265 -0
  22. package/src/components/xform/utils/util.js +29 -14
  23. package/src/microRouter/index.js +3 -0
  24. package/src/mixins/tableTree/index.js +77 -14
  25. package/src/store/config/index.js +28 -28
  26. package/src/utils/menuAdapter.js +277 -277
  27. package/src/utils/resolveViewComponent.js +203 -203
  28. package/src/utils/vab.js +6 -3
  29. package/src/views/bd/setting/formVersion/ftHistoryDialog.vue +9 -2
  30. package/src/views/user/common_attribute/list.vue +1 -1
  31. package/src/views/user/form/vform/render.vue +17 -2
  32. package/src/views/user/form/view/list.vue +11 -10
  33. package/src/views/user/wf/wfReport/index.vue +625 -625
@@ -0,0 +1,1265 @@
1
+ export const TREE_TEMP_KEY_FIELD = "_X_TREE_TEMP_KEY";
2
+ export const DEFAULT_MAP_CHILDREN_FIELD = "_X_ROW_CHILD";
3
+
4
+ const INTERNAL_TREE_FIELDS = new Set([
5
+ "_X_ROW_KEY",
6
+ "_X_ROW_CHILD",
7
+ TREE_TEMP_KEY_FIELD,
8
+ ]);
9
+
10
+ let tempKeySeed = 0;
11
+
12
+ const hasOwn = (target, key) =>
13
+ Object.prototype.hasOwnProperty.call(target || {}, key);
14
+
15
+ const isObject = (value) =>
16
+ value !== null && typeof value === "object";
17
+
18
+ const cloneValue = (value, seen = new WeakMap()) => {
19
+ if (!isObject(value)) return value;
20
+ if (value instanceof Date) return new Date(value.getTime());
21
+ if (seen.has(value)) return seen.get(value);
22
+ if (Array.isArray(value)) {
23
+ let result = [];
24
+ seen.set(value, result);
25
+ value.forEach((item) => result.push(cloneValue(item, seen)));
26
+ return result;
27
+ }
28
+ let prototype = Object.getPrototypeOf(value);
29
+ if (prototype !== Object.prototype && prototype !== null) {
30
+ return value;
31
+ }
32
+ let result = {};
33
+ seen.set(value, result);
34
+ Object.keys(value).forEach((key) => {
35
+ result[key] = cloneValue(value[key], seen);
36
+ });
37
+ return result;
38
+ };
39
+
40
+ const normalizeRegistryValue = (value) =>
41
+ isRootParent(value) ? null : normalizeTreeKey(value);
42
+
43
+ const registryHas = (registry, value) => {
44
+ if (!registry || typeof registry.forEach !== "function") return false;
45
+ let normalized = normalizeRegistryValue(value);
46
+ let matched = false;
47
+ registry.forEach((item) => {
48
+ if (normalizeRegistryValue(item) === normalized) matched = true;
49
+ });
50
+ return matched;
51
+ };
52
+
53
+ const assertSyncResult = (value, name) => {
54
+ if (value && typeof value.then === "function") {
55
+ throw new Error(`${name} 必须同步返回,不能返回 Promise`);
56
+ }
57
+ return value;
58
+ };
59
+
60
+ const callExternalParentValidator = (validator, value, row, operation) => {
61
+ if (typeof validator !== "function") return false;
62
+ let result = assertSyncResult(
63
+ validator(value, row, operation),
64
+ "validateExternalParentKey"
65
+ );
66
+ if (typeof result !== "boolean") {
67
+ throw new Error("validateExternalParentKey 必须返回 boolean");
68
+ }
69
+ return result;
70
+ };
71
+
72
+ const stableSerialize = (value, seen = new WeakSet()) => {
73
+ if (value === null) return "null";
74
+ let valueType = typeof value;
75
+ if (valueType === "undefined") return "undefined";
76
+ if (valueType === "string") return `string:${JSON.stringify(value)}`;
77
+ if (valueType === "number") {
78
+ if (Number.isNaN(value)) return "number:NaN";
79
+ if (!Number.isFinite(value)) return `number:${String(value)}`;
80
+ return `number:${value}`;
81
+ }
82
+ if (valueType === "boolean") return `boolean:${value}`;
83
+ if (valueType === "bigint") return `bigint:${value.toString()}`;
84
+ if (valueType === "function" || valueType === "symbol") {
85
+ throw new Error(`树节点包含无法比较的 ${valueType} 字段`);
86
+ }
87
+ if (value instanceof Date) {
88
+ return `date:${value.toISOString()}`;
89
+ }
90
+ if (value instanceof RegExp) {
91
+ return `regexp:${value.toString()}`;
92
+ }
93
+ if (seen.has(value)) {
94
+ throw new Error("树节点业务字段存在循环引用,无法校验多实例一致性");
95
+ }
96
+ seen.add(value);
97
+ let result;
98
+ if (Array.isArray(value)) {
99
+ result = `array:[${value
100
+ .map((item) => stableSerialize(item, seen))
101
+ .join(",")}]`;
102
+ } else {
103
+ result = `object:{${Object.keys(value)
104
+ .sort()
105
+ .map(
106
+ (key) =>
107
+ `${JSON.stringify(key)}:${stableSerialize(value[key], seen)}`
108
+ )
109
+ .join(",")}}`;
110
+ }
111
+ seen.delete(value);
112
+ return result;
113
+ };
114
+
115
+ const getComparableSnapshot = (row, fields) => {
116
+ let result = {};
117
+ Object.keys(row || {})
118
+ .filter(
119
+ (key) =>
120
+ key !== fields.childrenField
121
+ && key !== fields.mapChildrenField
122
+ && !INTERNAL_TREE_FIELDS.has(key)
123
+ )
124
+ .sort()
125
+ .forEach((key) => {
126
+ result[key] = row[key];
127
+ });
128
+ return stableSerialize(result);
129
+ };
130
+
131
+ const validateFieldNames = (fields, identityConfig) => {
132
+ let relationFields = [
133
+ fields.rowField,
134
+ fields.parentField,
135
+ fields.childrenField,
136
+ fields.mapChildrenField,
137
+ fields.hasChildField,
138
+ ];
139
+ let relationSet = new Set(relationFields);
140
+ if (relationSet.size !== relationFields.length) {
141
+ throw new Error(`树表字段不能重复:${relationFields.join(", ")}`);
142
+ }
143
+ [
144
+ fields.rowField,
145
+ fields.parentField,
146
+ fields.childrenField,
147
+ fields.hasChildField,
148
+ ].forEach((field) => {
149
+ if (INTERNAL_TREE_FIELDS.has(field)) {
150
+ throw new Error(`树表业务字段不能使用内部字段名:${field}`);
151
+ }
152
+ });
153
+ if (
154
+ INTERNAL_TREE_FIELDS.has(fields.mapChildrenField)
155
+ && fields.mapChildrenField !== DEFAULT_MAP_CHILDREN_FIELD
156
+ ) {
157
+ throw new Error(
158
+ `mapChildren 不能使用内部字段名:${fields.mapChildrenField}`
159
+ );
160
+ }
161
+ if (!identityConfig) return;
162
+ let physicalIdField = identityConfig.physicalIdField;
163
+ if (
164
+ physicalIdField !== fields.rowField
165
+ && relationSet.has(physicalIdField)
166
+ ) {
167
+ throw new Error(
168
+ `physicalIdField 只能与 rowField 相同,不能使用:${physicalIdField}`
169
+ );
170
+ }
171
+ if (INTERNAL_TREE_FIELDS.has(physicalIdField)) {
172
+ throw new Error(`physicalIdField 不能使用内部字段名:${physicalIdField}`);
173
+ }
174
+ };
175
+
176
+ export const isRootParent = (value) =>
177
+ value === null
178
+ || value === undefined
179
+ || value === ""
180
+ || value === 0
181
+ || value === "0";
182
+
183
+ export const normalizeTreeKey = (value) =>
184
+ isRootParent(value) ? "" : String(value);
185
+
186
+ export const treeKeyEquals = (left, right) => {
187
+ if (isRootParent(left) || isRootParent(right)) {
188
+ return isRootParent(left) && isRootParent(right);
189
+ }
190
+ return normalizeTreeKey(left) === normalizeTreeKey(right);
191
+ };
192
+
193
+ export const resolveTreeFields = (
194
+ treeConfig = {},
195
+ { strict = false } = {}
196
+ ) => {
197
+ let fields = {
198
+ rowField: hasOwn(treeConfig, "rowField")
199
+ ? treeConfig.rowField
200
+ : "id",
201
+ parentField: hasOwn(treeConfig, "parentField")
202
+ ? treeConfig.parentField
203
+ : "f_parent",
204
+ childrenField: hasOwn(treeConfig, "children")
205
+ ? treeConfig.children
206
+ : "f_children",
207
+ mapChildrenField:
208
+ hasOwn(treeConfig, "mapChildren")
209
+ ? treeConfig.mapChildren
210
+ : DEFAULT_MAP_CHILDREN_FIELD,
211
+ hasChildField: hasOwn(treeConfig, "hasChild")
212
+ ? treeConfig.hasChild
213
+ : "f_has_child",
214
+ lazy: !!treeConfig.lazy,
215
+ transform: treeConfig.transform !== false,
216
+ };
217
+ if (strict) {
218
+ [
219
+ "rowField",
220
+ "parentField",
221
+ "childrenField",
222
+ "mapChildrenField",
223
+ "hasChildField",
224
+ ].forEach((key) => {
225
+ if (!fields[key] || typeof fields[key] !== "string") {
226
+ throw new Error(`treeConfig.${key} 不能为空`);
227
+ }
228
+ });
229
+ if (treeConfig.transform === false) {
230
+ throw new Error("xform 树表必须启用 treeConfig.transform");
231
+ }
232
+ validateFieldNames(fields);
233
+ }
234
+ return fields;
235
+ };
236
+
237
+ export const resolveTreeIdentityConfig = (
238
+ treeConfig = {},
239
+ config = {}
240
+ ) => {
241
+ let fields = resolveTreeFields(treeConfig);
242
+ let identityConfig = {
243
+ physicalIdField: hasOwn(config, "physicalIdField")
244
+ ? config.physicalIdField
245
+ : "id",
246
+ rootParentValue:
247
+ config.rootParentValue === undefined ? 0 : config.rootParentValue,
248
+ serverGeneratesRowField:
249
+ config.serverGeneratesRowField === undefined
250
+ ? fields.rowField === (config.physicalIdField || "id")
251
+ : !!config.serverGeneratesRowField,
252
+ persistedRowFieldChangeStrategy:
253
+ config.persistedRowFieldChangeStrategy || "reject",
254
+ changePersistedRowField: config.changePersistedRowField || null,
255
+ validateExternalParentKey:
256
+ config.validateExternalParentKey || null,
257
+ };
258
+ if (!isRootParent(identityConfig.rootParentValue)) {
259
+ throw new Error(
260
+ `rootParentValue 不是有效根值:${identityConfig.rootParentValue}`
261
+ );
262
+ }
263
+ if (
264
+ !identityConfig.physicalIdField
265
+ || typeof identityConfig.physicalIdField !== "string"
266
+ ) {
267
+ throw new Error("physicalIdField 不能为空");
268
+ }
269
+ if (
270
+ fields.rowField === "id"
271
+ && identityConfig.physicalIdField !== "id"
272
+ ) {
273
+ throw new Error(
274
+ 'rowField 为 "id" 时 physicalIdField 必须同时为 "id"'
275
+ );
276
+ }
277
+ if (
278
+ !["reject", "transactional"].includes(
279
+ identityConfig.persistedRowFieldChangeStrategy
280
+ )
281
+ ) {
282
+ throw new Error("persistedRowFieldChangeStrategy 配置无效");
283
+ }
284
+ if (
285
+ identityConfig.persistedRowFieldChangeStrategy === "transactional"
286
+ && typeof identityConfig.changePersistedRowField !== "function"
287
+ ) {
288
+ throw new Error(
289
+ "transactional 模式必须配置 changePersistedRowField"
290
+ );
291
+ }
292
+ validateFieldNames(fields, identityConfig);
293
+ return identityConfig;
294
+ };
295
+
296
+ export const resolveTreeCopyConfig = (
297
+ fields,
298
+ identityConfig,
299
+ config = {}
300
+ ) => {
301
+ let rowFieldStrategy
302
+ = config.rowFieldStrategy
303
+ || (fields.rowField === identityConfig.physicalIdField
304
+ ? "regenerate"
305
+ : "preserve");
306
+ if (!["preserve", "regenerate", "custom"].includes(rowFieldStrategy)) {
307
+ throw new Error(`rowFieldStrategy 配置无效:${rowFieldStrategy}`);
308
+ }
309
+ if (
310
+ fields.rowField === identityConfig.physicalIdField
311
+ && rowFieldStrategy !== "regenerate"
312
+ ) {
313
+ throw new Error(
314
+ "rowField 与 physicalIdField 相同时复制只能使用 regenerate"
315
+ );
316
+ }
317
+ if (
318
+ fields.rowField === identityConfig.physicalIdField
319
+ && !identityConfig.serverGeneratesRowField
320
+ ) {
321
+ throw new Error(
322
+ "rowField 与 physicalIdField 相同时必须由服务端生成正式身份"
323
+ );
324
+ }
325
+ if (
326
+ config.orphanParentStrategy
327
+ && !["detach", "preserve"].includes(config.orphanParentStrategy)
328
+ ) {
329
+ throw new Error("treeCopyConfig.orphanParentStrategy 配置无效");
330
+ }
331
+ if (
332
+ config.lazyStrategy
333
+ && !["reject", "loadAll"].includes(config.lazyStrategy)
334
+ ) {
335
+ throw new Error("treeCopyConfig.lazyStrategy 配置无效");
336
+ }
337
+ if (
338
+ rowFieldStrategy === "custom"
339
+ && typeof config.generateRowField !== "function"
340
+ ) {
341
+ throw new Error("custom 复制策略必须配置 generateRowField");
342
+ }
343
+ if (
344
+ rowFieldStrategy === "regenerate"
345
+ && !config.generateRowField
346
+ && !identityConfig.serverGeneratesRowField
347
+ ) {
348
+ throw new Error(
349
+ "服务端不生成 rowField 时,regenerate 必须配置正式键生成器"
350
+ );
351
+ }
352
+ return {
353
+ rowFieldStrategy,
354
+ orphanParentStrategy: config.orphanParentStrategy || "detach",
355
+ lazyStrategy: config.lazyStrategy || "reject",
356
+ generateRowField: config.generateRowField || null,
357
+ };
358
+ };
359
+
360
+ export const resolveTreeSubmitConfig = (config = {}) => {
361
+ let result = {
362
+ orphanParentStrategy: config.orphanParentStrategy || "preserve",
363
+ unloadedChildrenStrategy:
364
+ config.unloadedChildrenStrategy || "reject",
365
+ successReconcileStrategy:
366
+ config.successReconcileStrategy || "reload",
367
+ };
368
+ if (!["preserve", "reject"].includes(result.orphanParentStrategy)) {
369
+ throw new Error("treeSubmitConfig.orphanParentStrategy 配置无效");
370
+ }
371
+ if (
372
+ !["reject", "loadAll", "omit"].includes(
373
+ result.unloadedChildrenStrategy
374
+ )
375
+ ) {
376
+ throw new Error("treeSubmitConfig.unloadedChildrenStrategy 配置无效");
377
+ }
378
+ if (result.successReconcileStrategy !== "reload") {
379
+ throw new Error("当前仅支持 successReconcileStrategy: reload");
380
+ }
381
+ return result;
382
+ };
383
+
384
+ export const makeTreeTempKey = () => {
385
+ tempKeySeed += 1;
386
+ return `row_${Date.now()}_${tempKeySeed}_${Math.floor(
387
+ Math.random() * 1000000
388
+ )}`;
389
+ };
390
+
391
+ export const markTemporaryTreeKey = (
392
+ row,
393
+ rowField,
394
+ value = makeTreeTempKey()
395
+ ) => {
396
+ row[rowField] = value;
397
+ row[TREE_TEMP_KEY_FIELD] = value;
398
+ return value;
399
+ };
400
+
401
+ export const isTemporaryTreeKey = (
402
+ row,
403
+ rowField,
404
+ identityConfig = {}
405
+ ) => {
406
+ if (!row || !rowField) return false;
407
+ let value = row[rowField];
408
+ if (
409
+ row[TREE_TEMP_KEY_FIELD] !== undefined
410
+ && row[TREE_TEMP_KEY_FIELD] !== null
411
+ ) {
412
+ return value === row[TREE_TEMP_KEY_FIELD];
413
+ }
414
+ return (
415
+ rowField === "id"
416
+ && (identityConfig.physicalIdField || "id") === "id"
417
+ && typeof value === "string"
418
+ && value.startsWith("row_")
419
+ );
420
+ };
421
+
422
+ export const clearTemporaryTreeKey = (row, rowField) => {
423
+ if (!row) return;
424
+ if (
425
+ rowField
426
+ && row[TREE_TEMP_KEY_FIELD] === row[rowField]
427
+ ) {
428
+ row[rowField] = null;
429
+ }
430
+ delete row[TREE_TEMP_KEY_FIELD];
431
+ };
432
+
433
+ export const getTreeLoadState = (
434
+ row,
435
+ { fields, isTreeNodeLoaded } = {}
436
+ ) => {
437
+ let resolvedFields = fields || resolveTreeFields();
438
+ if (!resolvedFields.lazy || !row?.[resolvedFields.hasChildField]) {
439
+ return {
440
+ complete: true,
441
+ loaded: true,
442
+ requiresLoad: false,
443
+ };
444
+ }
445
+ if (typeof isTreeNodeLoaded !== "function") {
446
+ return {
447
+ complete: false,
448
+ loaded: false,
449
+ requiresLoad: true,
450
+ reason: "missing-loader-state",
451
+ };
452
+ }
453
+ let loaded = !!assertSyncResult(
454
+ isTreeNodeLoaded(row),
455
+ "isTreeNodeLoaded"
456
+ );
457
+ return {
458
+ complete: loaded,
459
+ loaded,
460
+ requiresLoad: !loaded,
461
+ };
462
+ };
463
+
464
+ export const collectTreeNodes = (
465
+ source,
466
+ { fields, identityConfig } = {}
467
+ ) => {
468
+ let resolvedFields = fields || resolveTreeFields();
469
+ let nodes = [];
470
+ let edges = [];
471
+ let occurrences = [];
472
+ let nodeMeta = new Map();
473
+ let visitedObjects = new WeakSet();
474
+ let keyMap = new Map();
475
+ let nodeIndexMap = new Map();
476
+ let auxiliaryRowKeyMap = new Map();
477
+ let physicalIdMap = new Map();
478
+ let physicalIdByRowKey = new Map();
479
+
480
+ let registerAuxiliaryIdentity = (
481
+ index,
482
+ value,
483
+ rowKey,
484
+ label
485
+ ) => {
486
+ if (value === null || value === undefined || value === "") return;
487
+ let normalized = normalizeTreeKey(value);
488
+ let previousRowKey = index.get(normalized);
489
+ if (previousRowKey && previousRowKey !== rowKey) {
490
+ throw new Error(`${label} ${value} 同时指向多个树节点`);
491
+ }
492
+ index.set(normalized, rowKey);
493
+ };
494
+
495
+ let visit = (items, parent, sourceField, priority) => {
496
+ if (!Array.isArray(items)) return;
497
+ items.forEach((node, index) => {
498
+ if (!isObject(node) || Array.isArray(node)) return;
499
+ let rowValue = node[resolvedFields.rowField];
500
+ if (isRootParent(rowValue)) {
501
+ throw new Error(
502
+ `树节点 ${resolvedFields.rowField} 不能为空或根哨兵`
503
+ );
504
+ }
505
+ let key = normalizeTreeKey(rowValue);
506
+ let temporaryMarker
507
+ = node[TREE_TEMP_KEY_FIELD] === rowValue
508
+ ? node[TREE_TEMP_KEY_FIELD]
509
+ : null;
510
+ registerAuxiliaryIdentity(
511
+ auxiliaryRowKeyMap,
512
+ node._X_ROW_KEY,
513
+ key,
514
+ "_X_ROW_KEY"
515
+ );
516
+ if (identityConfig?.physicalIdField) {
517
+ let physicalId = node[identityConfig.physicalIdField];
518
+ registerAuxiliaryIdentity(
519
+ physicalIdMap,
520
+ physicalId,
521
+ key,
522
+ identityConfig.physicalIdField
523
+ );
524
+ if (
525
+ physicalId !== null
526
+ && physicalId !== undefined
527
+ && physicalId !== ""
528
+ ) {
529
+ let normalizedPhysicalId = normalizeTreeKey(physicalId);
530
+ let previousPhysicalId = physicalIdByRowKey.get(key);
531
+ if (
532
+ physicalIdByRowKey.has(key)
533
+ && previousPhysicalId !== normalizedPhysicalId
534
+ ) {
535
+ throw new Error(
536
+ `树节点 ${rowValue} 存在冲突的物理 ID`
537
+ );
538
+ }
539
+ physicalIdByRowKey.set(key, normalizedPhysicalId);
540
+ }
541
+ }
542
+ if (parent) {
543
+ edges.push({
544
+ parent,
545
+ child: node,
546
+ parentKey: parent[resolvedFields.rowField],
547
+ childKey: rowValue,
548
+ sourceField,
549
+ });
550
+ }
551
+ occurrences.push({
552
+ node,
553
+ parent,
554
+ sourceField,
555
+ index,
556
+ });
557
+
558
+ let currentMeta = keyMap.get(key);
559
+ if (!currentMeta) {
560
+ let meta = {
561
+ key,
562
+ node,
563
+ priority,
564
+ snapshot: getComparableSnapshot(node, resolvedFields),
565
+ temporaryMarker,
566
+ };
567
+ keyMap.set(key, meta);
568
+ nodeMeta.set(key, meta);
569
+ nodeIndexMap.set(key, nodes.length);
570
+ nodes.push(node);
571
+ } else if (currentMeta.node !== node) {
572
+ let nextSnapshot = getComparableSnapshot(node, resolvedFields);
573
+ if (currentMeta.snapshot !== nextSnapshot) {
574
+ throw new Error(
575
+ `树节点 ${rowValue} 存在内容冲突的多个对象实例`
576
+ );
577
+ }
578
+ if (
579
+ currentMeta.temporaryMarker !== null
580
+ && temporaryMarker !== null
581
+ && currentMeta.temporaryMarker !== temporaryMarker
582
+ ) {
583
+ throw new Error(`树节点 ${rowValue} 存在冲突的临时标记`);
584
+ }
585
+ if (
586
+ currentMeta.temporaryMarker === null
587
+ && temporaryMarker !== null
588
+ ) {
589
+ currentMeta.temporaryMarker = temporaryMarker;
590
+ }
591
+ if (priority > currentMeta.priority) {
592
+ currentMeta.node = node;
593
+ currentMeta.priority = priority;
594
+ currentMeta.snapshot = nextSnapshot;
595
+ nodes[nodeIndexMap.get(key)] = node;
596
+ }
597
+ }
598
+
599
+ if (visitedObjects.has(node)) return;
600
+ visitedObjects.add(node);
601
+ visit(
602
+ node[resolvedFields.childrenField],
603
+ node,
604
+ resolvedFields.childrenField,
605
+ 2
606
+ );
607
+ if (
608
+ resolvedFields.mapChildrenField
609
+ !== resolvedFields.childrenField
610
+ ) {
611
+ visit(
612
+ node[resolvedFields.mapChildrenField],
613
+ node,
614
+ resolvedFields.mapChildrenField,
615
+ 1
616
+ );
617
+ }
618
+ });
619
+ };
620
+
621
+ visit(Array.isArray(source) ? source : [], null, null, 3);
622
+ return {
623
+ nodes,
624
+ edges,
625
+ occurrences,
626
+ nodeMeta,
627
+ };
628
+ };
629
+
630
+ export const validateTreeGraph = (
631
+ nodes,
632
+ edges,
633
+ { fields } = {}
634
+ ) => {
635
+ let resolvedFields = fields || resolveTreeFields();
636
+ let nodeByKey = new Map();
637
+ let valueByKey = new Map();
638
+ nodes.forEach((node) => {
639
+ let value = node[resolvedFields.rowField];
640
+ let key = normalizeTreeKey(value);
641
+ if (nodeByKey.has(key) && nodeByKey.get(key) !== node) {
642
+ throw new Error(`树键重复:${value}`);
643
+ }
644
+ nodeByKey.set(key, node);
645
+ valueByKey.set(key, value);
646
+ });
647
+
648
+ let nestedParents = new Map();
649
+ edges.forEach((edge) => {
650
+ let parentKey = normalizeTreeKey(edge.parentKey);
651
+ let childKey = normalizeTreeKey(edge.childKey);
652
+ if (parentKey === childKey) {
653
+ throw new Error(`树节点不能指向自身:${edge.childKey}`);
654
+ }
655
+ let previous = nestedParents.get(childKey);
656
+ if (previous && previous !== parentKey) {
657
+ throw new Error(`树节点存在多个父节点:${edge.childKey}`);
658
+ }
659
+ nestedParents.set(childKey, parentKey);
660
+ });
661
+
662
+ let parentKeyByChild = new Map();
663
+ nodes.forEach((node) => {
664
+ let childValue = node[resolvedFields.rowField];
665
+ let childKey = normalizeTreeKey(childValue);
666
+ let ownsParent = hasOwn(node, resolvedFields.parentField);
667
+ let parentValue = node[resolvedFields.parentField];
668
+ let nestedParentKey = nestedParents.get(childKey);
669
+
670
+ if (ownsParent) {
671
+ if (isRootParent(parentValue)) {
672
+ if (nestedParentKey) {
673
+ throw new Error(`根节点嵌套在其他父节点下:${childValue}`);
674
+ }
675
+ parentKeyByChild.set(childKey, null);
676
+ return;
677
+ }
678
+ let explicitParentKey = normalizeTreeKey(parentValue);
679
+ if (
680
+ nestedParentKey
681
+ && explicitParentKey !== nestedParentKey
682
+ ) {
683
+ throw new Error(`父字段与嵌套位置冲突:${childValue}`);
684
+ }
685
+ if (explicitParentKey === childKey) {
686
+ throw new Error(`树节点不能指向自身:${childValue}`);
687
+ }
688
+ parentKeyByChild.set(childKey, explicitParentKey);
689
+ return;
690
+ }
691
+
692
+ parentKeyByChild.set(childKey, nestedParentKey || null);
693
+ });
694
+
695
+ let visitState = new Map();
696
+ let visit = (key) => {
697
+ let state = visitState.get(key);
698
+ if (state === 1) {
699
+ throw new Error(`树结构存在环:${valueByKey.get(key) || key}`);
700
+ }
701
+ if (state === 2) return;
702
+ visitState.set(key, 1);
703
+ let parentKey = parentKeyByChild.get(key);
704
+ if (parentKey && nodeByKey.has(parentKey)) visit(parentKey);
705
+ visitState.set(key, 2);
706
+ };
707
+ nodeByKey.forEach((node, key) => visit(key));
708
+
709
+ return {
710
+ nodeByKey,
711
+ valueByKey,
712
+ parentKeyByChild,
713
+ };
714
+ };
715
+
716
+ export const canonicalizeTreeInput = (
717
+ source,
718
+ { fields, identityConfig, strict = true } = {}
719
+ ) => {
720
+ let resolvedFields = fields || resolveTreeFields();
721
+ let collected = collectTreeNodes(source, {
722
+ fields: resolvedFields,
723
+ identityConfig,
724
+ });
725
+ let graph = validateTreeGraph(collected.nodes, collected.edges, {
726
+ fields: resolvedFields,
727
+ });
728
+ let cloneByKey = new Map();
729
+ collected.nodes.forEach((node) => {
730
+ let key = normalizeTreeKey(node[resolvedFields.rowField]);
731
+ let clone = cloneValue(node);
732
+ delete clone[resolvedFields.childrenField];
733
+ delete clone[resolvedFields.mapChildrenField];
734
+ let temporaryMarker
735
+ = collected.nodeMeta.get(key)?.temporaryMarker;
736
+ if (temporaryMarker !== null && temporaryMarker !== undefined) {
737
+ clone[TREE_TEMP_KEY_FIELD] = temporaryMarker;
738
+ }
739
+ cloneByKey.set(key, clone);
740
+ });
741
+
742
+ collected.nodes.forEach((node) => {
743
+ let key = normalizeTreeKey(node[resolvedFields.rowField]);
744
+ let clone = cloneByKey.get(key);
745
+ let parentKey = graph.parentKeyByChild.get(key);
746
+ if (parentKey && graph.nodeByKey.has(parentKey)) {
747
+ clone[resolvedFields.parentField]
748
+ = graph.nodeByKey.get(parentKey)[resolvedFields.rowField];
749
+ } else if (
750
+ !hasOwn(clone, resolvedFields.parentField)
751
+ && identityConfig
752
+ ) {
753
+ clone[resolvedFields.parentField]
754
+ = identityConfig.rootParentValue;
755
+ }
756
+ });
757
+
758
+ if (strict) {
759
+ let normalizedKeys = new Set();
760
+ cloneByKey.forEach((clone) => {
761
+ let key = normalizeTreeKey(clone[resolvedFields.rowField]);
762
+ if (normalizedKeys.has(key)) {
763
+ throw new Error(
764
+ `树键归一后冲突:${clone[resolvedFields.rowField]}`
765
+ );
766
+ }
767
+ normalizedKeys.add(key);
768
+ });
769
+ }
770
+ return collected.nodes.map((node) =>
771
+ cloneByKey.get(normalizeTreeKey(node[resolvedFields.rowField]))
772
+ );
773
+ };
774
+
775
+ export const syncTreeKeyReferences = (
776
+ rows,
777
+ oldValue,
778
+ newValue,
779
+ { fields } = {}
780
+ ) => {
781
+ let resolvedFields = fields || resolveTreeFields();
782
+ let visited = new WeakSet();
783
+ let visit = (items) => {
784
+ if (!Array.isArray(items)) return;
785
+ items.forEach((row) => {
786
+ if (!isObject(row) || visited.has(row)) return;
787
+ visited.add(row);
788
+ if (
789
+ treeKeyEquals(row[resolvedFields.parentField], oldValue)
790
+ ) {
791
+ row[resolvedFields.parentField] = newValue;
792
+ }
793
+ visit(row[resolvedFields.childrenField]);
794
+ visit(row[resolvedFields.mapChildrenField]);
795
+ });
796
+ };
797
+ visit(rows);
798
+ return rows;
799
+ };
800
+
801
+ const normalizeGeneratedValue = (result, fallbackTemporary) => {
802
+ if (isObject(result) && !Array.isArray(result)) {
803
+ return {
804
+ value: result.value,
805
+ temporary:
806
+ result.temporary === undefined
807
+ ? fallbackTemporary
808
+ : !!result.temporary,
809
+ };
810
+ }
811
+ return {
812
+ value: result,
813
+ temporary: fallbackTemporary,
814
+ };
815
+ };
816
+
817
+ const getTopologicalOrder = (nodes, graph, fields) => {
818
+ let nodeOrder = new Map();
819
+ nodes.forEach((node, index) => {
820
+ nodeOrder.set(normalizeTreeKey(node[fields.rowField]), index);
821
+ });
822
+ let childrenByParent = new Map();
823
+ let inDegree = new Map();
824
+ nodes.forEach((node) => {
825
+ let key = normalizeTreeKey(node[fields.rowField]);
826
+ inDegree.set(key, 0);
827
+ });
828
+ graph.parentKeyByChild.forEach((parentKey, childKey) => {
829
+ if (!parentKey || !graph.nodeByKey.has(parentKey)) return;
830
+ if (!childrenByParent.has(parentKey)) {
831
+ childrenByParent.set(parentKey, []);
832
+ }
833
+ childrenByParent.get(parentKey).push(childKey);
834
+ inDegree.set(childKey, (inDegree.get(childKey) || 0) + 1);
835
+ });
836
+ let queue = Array.from(inDegree.keys())
837
+ .filter((key) => inDegree.get(key) === 0)
838
+ .sort((left, right) => nodeOrder.get(left) - nodeOrder.get(right));
839
+ let result = [];
840
+ while (queue.length) {
841
+ let key = queue.shift();
842
+ result.push(graph.nodeByKey.get(key));
843
+ let children = childrenByParent.get(key) || [];
844
+ children
845
+ .sort((left, right) => nodeOrder.get(left) - nodeOrder.get(right))
846
+ .forEach((childKey) => {
847
+ inDegree.set(childKey, inDegree.get(childKey) - 1);
848
+ if (inDegree.get(childKey) === 0) queue.push(childKey);
849
+ });
850
+ }
851
+ if (result.length !== nodes.length) {
852
+ throw new Error("树结构存在环,无法生成复制顺序");
853
+ }
854
+ return result;
855
+ };
856
+
857
+ export const copyTreeData = (
858
+ source,
859
+ {
860
+ fields,
861
+ identityConfig,
862
+ copyConfig,
863
+ targetRows = [],
864
+ temporaryKeyRegistry,
865
+ knownFormalKeyRegistry,
866
+ } = {}
867
+ ) => {
868
+ let resolvedFields = fields || resolveTreeFields();
869
+ let resolvedIdentity
870
+ = identityConfig
871
+ || resolveTreeIdentityConfig(
872
+ {
873
+ rowField: resolvedFields.rowField,
874
+ parentField: resolvedFields.parentField,
875
+ children: resolvedFields.childrenField,
876
+ mapChildren: resolvedFields.mapChildrenField,
877
+ hasChild: resolvedFields.hasChildField,
878
+ },
879
+ {}
880
+ );
881
+ let resolvedCopy
882
+ = copyConfig
883
+ || resolveTreeCopyConfig(resolvedFields, resolvedIdentity, {});
884
+ let canonicalRows = canonicalizeTreeInput(source, {
885
+ fields: resolvedFields,
886
+ identityConfig: resolvedIdentity,
887
+ strict: true,
888
+ });
889
+ let collected = collectTreeNodes(canonicalRows, {
890
+ fields: resolvedFields,
891
+ identityConfig: resolvedIdentity,
892
+ });
893
+ let graph = validateTreeGraph(collected.nodes, collected.edges, {
894
+ fields: resolvedFields,
895
+ });
896
+ let orderedNodes = getTopologicalOrder(
897
+ collected.nodes,
898
+ graph,
899
+ resolvedFields
900
+ );
901
+ let mapping = new Map();
902
+ let copyByOldKey = new Map();
903
+ let resolvedParentByOldKey = new Map();
904
+ let usedNewKeys = new Set();
905
+ let targetKeys = new Set();
906
+ let targetTemporaryKeys = new Set();
907
+ let generatedTemporaryKeys = new Set();
908
+ if (Array.isArray(targetRows) && targetRows.length) {
909
+ let targetCollected = collectTreeNodes(targetRows, {
910
+ fields: resolvedFields,
911
+ identityConfig: resolvedIdentity,
912
+ });
913
+ targetCollected.nodes.forEach((row) => {
914
+ targetKeys.add(
915
+ normalizeTreeKey(row[resolvedFields.rowField])
916
+ );
917
+ if (
918
+ isTemporaryTreeKey(
919
+ row,
920
+ resolvedFields.rowField,
921
+ resolvedIdentity
922
+ )
923
+ ) {
924
+ targetTemporaryKeys.add(row[resolvedFields.rowField]);
925
+ }
926
+ });
927
+ targetCollected.nodeMeta.forEach((meta) => {
928
+ if (meta.temporaryMarker !== null) {
929
+ targetTemporaryKeys.add(meta.temporaryMarker);
930
+ }
931
+ });
932
+ }
933
+
934
+ orderedNodes.forEach((node) => {
935
+ let oldValue = node[resolvedFields.rowField];
936
+ let oldKey = normalizeTreeKey(oldValue);
937
+ let oldParentValue = node[resolvedFields.parentField];
938
+ let oldParentKey = graph.parentKeyByChild.get(oldKey);
939
+ let newParentValue = resolvedIdentity.rootParentValue;
940
+ let parentNode = null;
941
+ let parentCopy = null;
942
+
943
+ if (oldParentKey && graph.nodeByKey.has(oldParentKey)) {
944
+ parentNode = graph.nodeByKey.get(oldParentKey);
945
+ newParentValue = mapping.get(oldParentKey);
946
+ parentCopy = copyByOldKey.get(oldParentKey);
947
+ } else if (oldParentKey) {
948
+ if (resolvedCopy.orphanParentStrategy === "preserve") {
949
+ if (
950
+ registryHas(temporaryKeyRegistry, oldParentValue)
951
+ || registryHas(targetTemporaryKeys, oldParentValue)
952
+ ) {
953
+ throw new Error(`外部父键仍为临时值:${oldParentValue}`);
954
+ }
955
+ let isFormal
956
+ = registryHas(knownFormalKeyRegistry, oldParentValue)
957
+ || callExternalParentValidator(
958
+ resolvedIdentity.validateExternalParentKey,
959
+ oldParentValue,
960
+ node,
961
+ "copy"
962
+ );
963
+ if (!isFormal) {
964
+ throw new Error(`无法确认外部父键:${oldParentValue}`);
965
+ }
966
+ newParentValue = oldParentValue;
967
+ }
968
+ }
969
+
970
+ let generated;
971
+ if (resolvedCopy.rowFieldStrategy === "preserve") {
972
+ if (
973
+ isTemporaryTreeKey(
974
+ node,
975
+ resolvedFields.rowField,
976
+ resolvedIdentity
977
+ )
978
+ || registryHas(temporaryKeyRegistry, oldValue)
979
+ ) {
980
+ throw new Error(`不能保留临时树键:${oldValue}`);
981
+ }
982
+ generated = {
983
+ value: oldValue,
984
+ temporary: false,
985
+ };
986
+ } else if (resolvedCopy.generateRowField) {
987
+ let result = assertSyncResult(
988
+ resolvedCopy.generateRowField({
989
+ node,
990
+ oldValue,
991
+ parentNode,
992
+ oldParentKey: oldParentValue,
993
+ newParentKey: newParentValue,
994
+ parentCopy,
995
+ fields: resolvedFields,
996
+ makeTempKey: makeTreeTempKey,
997
+ }),
998
+ "generateRowField"
999
+ );
1000
+ generated = normalizeGeneratedValue(result, false);
1001
+ } else {
1002
+ generated = {
1003
+ value: makeTreeTempKey(),
1004
+ temporary: true,
1005
+ };
1006
+ }
1007
+ if (isRootParent(generated.value)) {
1008
+ throw new Error(`复制生成的树键无效:${generated.value}`);
1009
+ }
1010
+ if (
1011
+ resolvedFields.rowField === resolvedIdentity.physicalIdField
1012
+ && !generated.temporary
1013
+ ) {
1014
+ throw new Error(
1015
+ "rowField 与 physicalIdField 相同时复制键必须是临时值"
1016
+ );
1017
+ }
1018
+
1019
+ let newKey = normalizeTreeKey(generated.value);
1020
+ if (usedNewKeys.has(newKey) || targetKeys.has(newKey)) {
1021
+ throw new Error(`复制生成的树键冲突:${generated.value}`);
1022
+ }
1023
+ usedNewKeys.add(newKey);
1024
+
1025
+ let copy = cloneValue(node);
1026
+ delete copy[resolvedFields.childrenField];
1027
+ delete copy[resolvedFields.mapChildrenField];
1028
+ INTERNAL_TREE_FIELDS.forEach((field) => delete copy[field]);
1029
+ copy[resolvedIdentity.physicalIdField] = null;
1030
+ copy[resolvedFields.rowField] = generated.value;
1031
+ copy[resolvedFields.parentField] = newParentValue;
1032
+ if (generated.temporary) {
1033
+ copy[TREE_TEMP_KEY_FIELD] = generated.value;
1034
+ generatedTemporaryKeys.add(generated.value);
1035
+ }
1036
+ [
1037
+ "create_by",
1038
+ "_create_by",
1039
+ "create_date",
1040
+ "modify_by",
1041
+ "_modify_by",
1042
+ "modify_date",
1043
+ "head_table_id",
1044
+ "objectForeignId",
1045
+ ].forEach((field) => {
1046
+ if (field === "head_table_id" || field === "objectForeignId") {
1047
+ delete copy[field];
1048
+ } else if (hasOwn(copy, field)) {
1049
+ copy[field] = null;
1050
+ }
1051
+ });
1052
+ mapping.set(oldKey, generated.value);
1053
+ copyByOldKey.set(oldKey, copy);
1054
+ resolvedParentByOldKey.set(oldKey, newParentValue);
1055
+ });
1056
+
1057
+ let copiedRows = canonicalRows.map((row) => {
1058
+ let oldKey = normalizeTreeKey(row[resolvedFields.rowField]);
1059
+ let copy = copyByOldKey.get(oldKey);
1060
+ copy[resolvedFields.parentField]
1061
+ = resolvedParentByOldKey.get(oldKey);
1062
+ return copy;
1063
+ });
1064
+ let copiedGraph = collectTreeNodes(copiedRows, {
1065
+ fields: resolvedFields,
1066
+ identityConfig: resolvedIdentity,
1067
+ });
1068
+ validateTreeGraph(copiedGraph.nodes, copiedGraph.edges, {
1069
+ fields: resolvedFields,
1070
+ });
1071
+ generatedTemporaryKeys.forEach((value) => {
1072
+ temporaryKeyRegistry?.add?.(value);
1073
+ });
1074
+ return copiedRows;
1075
+ };
1076
+
1077
+ export const sanitizeTreeRow = (
1078
+ row,
1079
+ {
1080
+ fields,
1081
+ identityConfig,
1082
+ mode = "tree",
1083
+ temporaryParentKeys,
1084
+ temporaryKeyRegistry,
1085
+ allowUnresolvedTemporary = false,
1086
+ } = {}
1087
+ ) => {
1088
+ let resolvedFields = fields || resolveTreeFields();
1089
+ let resolvedIdentity
1090
+ = identityConfig
1091
+ || resolveTreeIdentityConfig(
1092
+ {
1093
+ rowField: resolvedFields.rowField,
1094
+ parentField: resolvedFields.parentField,
1095
+ children: resolvedFields.childrenField,
1096
+ mapChildren: resolvedFields.mapChildrenField,
1097
+ hasChild: resolvedFields.hasChildField,
1098
+ },
1099
+ {}
1100
+ );
1101
+ let result = cloneValue(row);
1102
+ let children = result[resolvedFields.childrenField];
1103
+ let temporary
1104
+ = isTemporaryTreeKey(
1105
+ result,
1106
+ resolvedFields.rowField,
1107
+ resolvedIdentity
1108
+ )
1109
+ || registryHas(
1110
+ temporaryKeyRegistry,
1111
+ result[resolvedFields.rowField]
1112
+ );
1113
+ if (temporary) {
1114
+ if (resolvedIdentity.serverGeneratesRowField) {
1115
+ result[resolvedFields.rowField] = null;
1116
+ } else if (!allowUnresolvedTemporary) {
1117
+ throw new Error(
1118
+ `${resolvedFields.rowField} 仍是临时值,不能提交`
1119
+ );
1120
+ }
1121
+ }
1122
+ let parentTemporaryKeys
1123
+ = temporaryParentKeys || temporaryKeyRegistry;
1124
+ if (
1125
+ parentTemporaryKeys
1126
+ && registryHas(
1127
+ parentTemporaryKeys,
1128
+ result[resolvedFields.parentField]
1129
+ )
1130
+ ) {
1131
+ result[resolvedFields.parentField] = null;
1132
+ }
1133
+
1134
+ INTERNAL_TREE_FIELDS.forEach((field) => delete result[field]);
1135
+ delete result[resolvedFields.mapChildrenField];
1136
+ delete result[resolvedFields.childrenField];
1137
+ if (mode === "tree" && Array.isArray(children) && children.length) {
1138
+ result[resolvedFields.childrenField] = children.map((child) =>
1139
+ sanitizeTreeRow(child, {
1140
+ fields: resolvedFields,
1141
+ identityConfig: resolvedIdentity,
1142
+ mode,
1143
+ temporaryParentKeys: parentTemporaryKeys,
1144
+ temporaryKeyRegistry,
1145
+ allowUnresolvedTemporary,
1146
+ })
1147
+ );
1148
+ }
1149
+ return result;
1150
+ };
1151
+
1152
+ export const buildTreeSubmitData = (
1153
+ source,
1154
+ {
1155
+ fields,
1156
+ identityConfig,
1157
+ submitConfig,
1158
+ strict = true,
1159
+ temporaryKeyRegistry,
1160
+ knownFormalKeyRegistry,
1161
+ } = {}
1162
+ ) => {
1163
+ let resolvedFields = fields || resolveTreeFields();
1164
+ let resolvedIdentity
1165
+ = identityConfig
1166
+ || resolveTreeIdentityConfig(
1167
+ {
1168
+ rowField: resolvedFields.rowField,
1169
+ parentField: resolvedFields.parentField,
1170
+ children: resolvedFields.childrenField,
1171
+ mapChildren: resolvedFields.mapChildrenField,
1172
+ hasChild: resolvedFields.hasChildField,
1173
+ },
1174
+ {}
1175
+ );
1176
+ let resolvedSubmit
1177
+ = submitConfig || resolveTreeSubmitConfig({});
1178
+ let canonicalRows = canonicalizeTreeInput(source, {
1179
+ fields: resolvedFields,
1180
+ identityConfig: resolvedIdentity,
1181
+ strict,
1182
+ });
1183
+ let collected = collectTreeNodes(canonicalRows, {
1184
+ fields: resolvedFields,
1185
+ identityConfig: resolvedIdentity,
1186
+ });
1187
+ let graph = validateTreeGraph(collected.nodes, collected.edges, {
1188
+ fields: resolvedFields,
1189
+ });
1190
+ let tempKeys = new Set();
1191
+ temporaryKeyRegistry?.forEach?.((value) => tempKeys.add(value));
1192
+ canonicalRows.forEach((row) => {
1193
+ if (
1194
+ isTemporaryTreeKey(
1195
+ row,
1196
+ resolvedFields.rowField,
1197
+ resolvedIdentity
1198
+ )
1199
+ ) {
1200
+ tempKeys.add(row[resolvedFields.rowField]);
1201
+ }
1202
+ });
1203
+
1204
+ let cloneByKey = new Map();
1205
+ canonicalRows.forEach((row) => {
1206
+ let key = normalizeTreeKey(row[resolvedFields.rowField]);
1207
+ let clone = cloneValue(row);
1208
+ delete clone[resolvedFields.childrenField];
1209
+ delete clone[resolvedFields.mapChildrenField];
1210
+ cloneByKey.set(key, clone);
1211
+ });
1212
+
1213
+ let roots = [];
1214
+ canonicalRows.forEach((row) => {
1215
+ let key = normalizeTreeKey(row[resolvedFields.rowField]);
1216
+ let clone = cloneByKey.get(key);
1217
+ let parentKey = graph.parentKeyByChild.get(key);
1218
+ let parentValue = row[resolvedFields.parentField];
1219
+ if (!parentKey) {
1220
+ clone[resolvedFields.parentField]
1221
+ = resolvedIdentity.rootParentValue;
1222
+ roots.push(clone);
1223
+ return;
1224
+ }
1225
+ if (graph.nodeByKey.has(parentKey)) {
1226
+ let parentClone = cloneByKey.get(parentKey);
1227
+ if (!Array.isArray(parentClone[resolvedFields.childrenField])) {
1228
+ parentClone[resolvedFields.childrenField] = [];
1229
+ }
1230
+ parentClone[resolvedFields.childrenField].push(clone);
1231
+ return;
1232
+ }
1233
+
1234
+ if (registryHas(tempKeys, parentValue)) {
1235
+ throw new Error(`父键仍指向未提交的临时节点:${parentValue}`);
1236
+ }
1237
+ if (resolvedSubmit.orphanParentStrategy === "reject") {
1238
+ throw new Error(`父节点不在提交集合中:${parentValue}`);
1239
+ }
1240
+ let formal
1241
+ = !strict
1242
+ || registryHas(knownFormalKeyRegistry, parentValue)
1243
+ || callExternalParentValidator(
1244
+ resolvedIdentity.validateExternalParentKey,
1245
+ parentValue,
1246
+ row,
1247
+ "submit"
1248
+ );
1249
+ if (!formal) {
1250
+ throw new Error(`无法确认外部父键:${parentValue}`);
1251
+ }
1252
+ roots.push(clone);
1253
+ });
1254
+
1255
+ return roots.map((row) =>
1256
+ sanitizeTreeRow(row, {
1257
+ fields: resolvedFields,
1258
+ identityConfig: resolvedIdentity,
1259
+ mode: "tree",
1260
+ temporaryParentKeys: tempKeys,
1261
+ temporaryKeyRegistry: tempKeys,
1262
+ allowUnresolvedTemporary: !strict,
1263
+ })
1264
+ );
1265
+ };