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.
- package/package.json +1 -1
- package/src/components/VabUpload/index.vue +242 -242
- package/src/components/VabUpload/mixins.js +1971 -1971
- package/src/components/VabUpload/privateProfileDialog.vue +2 -2
- package/src/components/hiprint/view/design/index.vue +7 -2
- package/src/components/obsUpload/index.vue +234 -234
- package/src/components/obsUpload/mixins.js +1542 -1542
- package/src/components/vb-tabs/x-tabs.vue +7 -3
- 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
- package/src/components/xform/form-designer/form-widget/dialog/formDialog.vue +2 -2
- package/src/components/xform/form-designer/form-widget/dialog/formDrawer.vue +2 -2
- package/src/components/xform/form-designer/form-widget/dialog/searchFormDialog.vue +2 -2
- package/src/components/xform/form-designer/form-widget/field-widget/copy_button-widget.vue +11 -2
- package/src/components/xform/form-designer/form-widget/field-widget/vabUpload-widget.vue +369 -369
- package/src/components/xform/form-designer/setting-panel/property-editor/fileTypesSelect.vue +69 -23
- package/src/components/xform/form-render/container-item/data-table-item.vue +1 -1
- package/src/components/xform/form-render/container-item/data-table-mixin.js +1469 -428
- package/src/components/xform/form-render/indexMixin.js +379 -263
- package/src/components/xform/mixins/defaultHandle.js +234 -28
- package/src/components/xform/mixins/scriptHttp.js +4 -4
- package/src/components/xform/utils/treeTableUtil.js +1265 -0
- package/src/components/xform/utils/util.js +29 -14
- package/src/microRouter/index.js +3 -0
- package/src/mixins/tableTree/index.js +77 -14
- package/src/store/config/index.js +28 -28
- package/src/utils/menuAdapter.js +277 -277
- package/src/utils/resolveViewComponent.js +203 -203
- package/src/utils/vab.js +6 -3
- package/src/views/bd/setting/formVersion/ftHistoryDialog.vue +9 -2
- package/src/views/user/common_attribute/list.vue +1 -1
- package/src/views/user/form/vform/render.vue +17 -2
- package/src/views/user/form/view/list.vue +11 -10
- 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
|
+
};
|