@nocobase/plugin-ui-templates 2.0.0-alpha.57
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/LICENSE.txt +172 -0
- package/build.config.ts +12 -0
- package/client.js +1 -0
- package/dist/client/collections/flowModelTemplates.d.ts +67 -0
- package/dist/client/components/FlowModelTemplatesPage.d.ts +12 -0
- package/dist/client/components/TemplateSelectOption.d.ts +20 -0
- package/dist/client/constants.d.ts +9 -0
- package/dist/client/hooks/useFlowModelTemplateActions.d.ts +24 -0
- package/dist/client/index.d.ts +13 -0
- package/dist/client/index.js +10 -0
- package/dist/client/locale.d.ts +18 -0
- package/dist/client/menuExtensions.d.ts +9 -0
- package/dist/client/models/ReferenceBlockModel.d.ts +47 -0
- package/dist/client/models/ReferenceFormGridModel.d.ts +38 -0
- package/dist/client/models/SubModelTemplateImporterModel.d.ts +55 -0
- package/dist/client/models/referenceShared.d.ts +23 -0
- package/dist/client/openViewActionExtensions.d.ts +10 -0
- package/dist/client/schemas/flowModelTemplates.d.ts +11 -0
- package/dist/client/subModelMenuExtensions.d.ts +10 -0
- package/dist/client/utils/infiniteSelect.d.ts +28 -0
- package/dist/client/utils/refHost.d.ts +20 -0
- package/dist/client/utils/templateCompatibility.d.ts +91 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +42 -0
- package/dist/externalVersion.js +24 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +48 -0
- package/dist/locale/de-DE.json +14 -0
- package/dist/locale/en-US.json +72 -0
- package/dist/locale/es-ES.json +14 -0
- package/dist/locale/fr-FR.json +14 -0
- package/dist/locale/hu-HU.json +14 -0
- package/dist/locale/id-ID.json +14 -0
- package/dist/locale/it-IT.json +14 -0
- package/dist/locale/ja-JP.json +14 -0
- package/dist/locale/ko-KR.json +14 -0
- package/dist/locale/nl-NL.json +14 -0
- package/dist/locale/pt-BR.json +14 -0
- package/dist/locale/ru-RU.json +14 -0
- package/dist/locale/tr-TR.json +14 -0
- package/dist/locale/uk-UA.json +14 -0
- package/dist/locale/vi-VN.json +14 -0
- package/dist/locale/zh-CN.json +71 -0
- package/dist/locale/zh-TW.json +14 -0
- package/dist/server/collections/flowModelTemplateUsages.d.ts +11 -0
- package/dist/server/collections/flowModelTemplateUsages.js +71 -0
- package/dist/server/collections/flowModelTemplates.d.ts +11 -0
- package/dist/server/collections/flowModelTemplates.js +96 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.js +42 -0
- package/dist/server/plugin.d.ts +17 -0
- package/dist/server/plugin.js +242 -0
- package/dist/server/resources/flowModelTemplateUsages.d.ts +19 -0
- package/dist/server/resources/flowModelTemplateUsages.js +91 -0
- package/dist/server/resources/flowModelTemplates.d.ts +20 -0
- package/dist/server/resources/flowModelTemplates.js +267 -0
- package/package.json +37 -0
- package/server.js +1 -0
- package/src/client/__tests__/openViewActionExtensions.test.ts +1208 -0
- package/src/client/collections/flowModelTemplates.ts +131 -0
- package/src/client/components/FlowModelTemplatesPage.tsx +78 -0
- package/src/client/components/TemplateSelectOption.tsx +106 -0
- package/src/client/constants.ts +10 -0
- package/src/client/hooks/useFlowModelTemplateActions.tsx +137 -0
- package/src/client/index.ts +54 -0
- package/src/client/locale.ts +40 -0
- package/src/client/menuExtensions.tsx +1033 -0
- package/src/client/models/ReferenceBlockModel.tsx +793 -0
- package/src/client/models/ReferenceFormGridModel.tsx +302 -0
- package/src/client/models/SubModelTemplateImporterModel.tsx +634 -0
- package/src/client/models/__tests__/ReferenceBlockModel.test.tsx +482 -0
- package/src/client/models/__tests__/ReferenceFormGridModel.test.tsx +175 -0
- package/src/client/models/__tests__/SubModelTemplateImporterModel.test.ts +447 -0
- package/src/client/models/referenceShared.tsx +99 -0
- package/src/client/openViewActionExtensions.tsx +981 -0
- package/src/client/schemas/flowModelTemplates.ts +264 -0
- package/src/client/subModelMenuExtensions.ts +103 -0
- package/src/client/utils/infiniteSelect.ts +150 -0
- package/src/client/utils/refHost.ts +44 -0
- package/src/client/utils/templateCompatibility.ts +374 -0
- package/src/client.ts +10 -0
- package/src/index.ts +11 -0
- package/src/locale/de-DE.json +14 -0
- package/src/locale/en-US.json +72 -0
- package/src/locale/es-ES.json +14 -0
- package/src/locale/fr-FR.json +14 -0
- package/src/locale/hu-HU.json +14 -0
- package/src/locale/id-ID.json +14 -0
- package/src/locale/it-IT.json +14 -0
- package/src/locale/ja-JP.json +14 -0
- package/src/locale/ko-KR.json +14 -0
- package/src/locale/nl-NL.json +14 -0
- package/src/locale/pt-BR.json +14 -0
- package/src/locale/ru-RU.json +14 -0
- package/src/locale/tr-TR.json +14 -0
- package/src/locale/uk-UA.json +14 -0
- package/src/locale/vi-VN.json +14 -0
- package/src/locale/zh-CN.json +71 -0
- package/src/locale/zh-TW.json +14 -0
- package/src/server/__tests__/template-usage.test.ts +351 -0
- package/src/server/collections/flowModelTemplateUsages.ts +51 -0
- package/src/server/collections/flowModelTemplates.ts +76 -0
- package/src/server/index.ts +10 -0
- package/src/server/plugin.ts +236 -0
- package/src/server/resources/flowModelTemplateUsages.ts +61 -0
- package/src/server/resources/flowModelTemplates.ts +251 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import _ from 'lodash';
|
|
12
|
+
import { escapeT, type FlowEngine, type FlowModel } from '@nocobase/flow-engine';
|
|
13
|
+
import { tStr, NAMESPACE } from '../locale';
|
|
14
|
+
import { BlockModel } from '@nocobase/client';
|
|
15
|
+
import { renderTemplateSelectLabel, renderTemplateSelectOption } from '../components/TemplateSelectOption';
|
|
16
|
+
import {
|
|
17
|
+
TEMPLATE_LIST_PAGE_SIZE,
|
|
18
|
+
calcHasMore,
|
|
19
|
+
getTemplateAvailabilityDisabledReason,
|
|
20
|
+
normalizeStr,
|
|
21
|
+
parseResourceListResponse,
|
|
22
|
+
} from '../utils/templateCompatibility';
|
|
23
|
+
import { bindInfiniteScrollToFormilySelect, defaultSelectOptionComparator } from '../utils/infiniteSelect';
|
|
24
|
+
import {
|
|
25
|
+
ensureBlockScopedEngine,
|
|
26
|
+
ReferenceScopedRenderer,
|
|
27
|
+
renderReferenceTargetPlaceholder,
|
|
28
|
+
unlinkScopedEngine,
|
|
29
|
+
} from './referenceShared';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ReferenceBlockModel(插件版)
|
|
33
|
+
* - 通过配置 targetUid(实例 model.uid)引用并渲染另一个区块;
|
|
34
|
+
* - 在 BlockScoped 引擎中实例化目标区块,隔离模型实例与事件缓存;
|
|
35
|
+
* - 与目标区块建立父子关系(目标仅作为 Reference 的子模型用于设置菜单聚合,不做持久化);
|
|
36
|
+
* - 当目标缺失/非法/循环时,渲染占位提示;
|
|
37
|
+
* - title 仅展示目标标题;模板信息通过 extraTitle 展示(配置态双标签)。
|
|
38
|
+
*/
|
|
39
|
+
export class ReferenceBlockModel extends BlockModel {
|
|
40
|
+
constructor(options: any) {
|
|
41
|
+
super(options);
|
|
42
|
+
// 通过 Proxy 将未知属性/方法转发到目标模型:
|
|
43
|
+
// - 若属性/方法在本实例(含原型链)已存在,则不代理;
|
|
44
|
+
// - 若不存在且已解析出 _targetModel,则从目标模型读取;函数按目标模型上下文绑定;
|
|
45
|
+
// - 支持写入:未知属性写入目标模型(若存在对应键);否则写回自身。
|
|
46
|
+
const proxy = new Proxy(this as any, {
|
|
47
|
+
get(target, prop: string | symbol, receiver) {
|
|
48
|
+
if (prop in target) {
|
|
49
|
+
// 自身已有(含原型链)则直接返回;若为函数需绑定到 target,避免 private field 访问报错
|
|
50
|
+
const val = Reflect.get(target, prop, target);
|
|
51
|
+
if (typeof val === 'function' && prop !== 'constructor') {
|
|
52
|
+
return val.bind(target);
|
|
53
|
+
}
|
|
54
|
+
return val;
|
|
55
|
+
}
|
|
56
|
+
const t = target._targetModel as any;
|
|
57
|
+
if (t) {
|
|
58
|
+
const val = Reflect.get(t, prop, t);
|
|
59
|
+
if (typeof val === 'function' && prop !== 'constructor') {
|
|
60
|
+
return val.bind(t);
|
|
61
|
+
}
|
|
62
|
+
if (val !== undefined) return val;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
},
|
|
66
|
+
set(target, prop: string | symbol, value, receiver) {
|
|
67
|
+
if (prop in target) {
|
|
68
|
+
return Reflect.set(target, prop, value, target);
|
|
69
|
+
}
|
|
70
|
+
const t = target._targetModel as any;
|
|
71
|
+
if (t && prop in t) {
|
|
72
|
+
return Reflect.set(t, prop, value, t);
|
|
73
|
+
}
|
|
74
|
+
return Reflect.set(target, prop, value, target);
|
|
75
|
+
},
|
|
76
|
+
has(target, prop: string | symbol) {
|
|
77
|
+
if (prop in target) return true;
|
|
78
|
+
const t = target._targetModel as any;
|
|
79
|
+
return !!t && prop in t;
|
|
80
|
+
},
|
|
81
|
+
ownKeys(target) {
|
|
82
|
+
const keys = new Set(Reflect.ownKeys(target));
|
|
83
|
+
const t = target._targetModel as any;
|
|
84
|
+
if (t) {
|
|
85
|
+
for (const k of Reflect.ownKeys(t)) keys.add(k);
|
|
86
|
+
}
|
|
87
|
+
return Array.from(keys);
|
|
88
|
+
},
|
|
89
|
+
getOwnPropertyDescriptor(target, prop: string | symbol) {
|
|
90
|
+
const desc = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
91
|
+
if (desc) return desc;
|
|
92
|
+
const t = target._targetModel as any;
|
|
93
|
+
if (!t) return undefined;
|
|
94
|
+
return Object.getOwnPropertyDescriptor(t, prop) || undefined;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
return proxy as any;
|
|
98
|
+
}
|
|
99
|
+
public settingsMenuLevel = 2;
|
|
100
|
+
private _scopedEngine?: FlowEngine;
|
|
101
|
+
private _targetModel?: FlowModel;
|
|
102
|
+
private _resolvedTargetUid?: string;
|
|
103
|
+
private _invalidTargetUid?: string;
|
|
104
|
+
|
|
105
|
+
get title() {
|
|
106
|
+
return this._targetModel?.title || super.title;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
onInit(option) {
|
|
110
|
+
super.onInit(option);
|
|
111
|
+
this.context.defineProperty('refModel', {
|
|
112
|
+
get: () => this._targetModel,
|
|
113
|
+
cache: false,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private _getTargetUidFromParams(): string | undefined {
|
|
118
|
+
const p = this.getStepParams('referenceSettings', 'target') || {};
|
|
119
|
+
return (p?.targetUid || '').trim() || undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private _syncExtraTitle(configured: boolean) {
|
|
123
|
+
if (!configured) {
|
|
124
|
+
this.setExtraTitle('');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const label = this.context.t('Reference template', { ns: [NAMESPACE, 'client'], nsMode: 'fallback' });
|
|
129
|
+
const step = this.getStepParams('referenceSettings', 'useTemplate') || {};
|
|
130
|
+
const tplName = step?.templateName || step?.templateUid;
|
|
131
|
+
this.setExtraTitle(tplName ? `${label}: ${tplName}` : label);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _ensureScopedEngine(): FlowEngine {
|
|
135
|
+
this._scopedEngine = ensureBlockScopedEngine(this.flowEngine, this._scopedEngine);
|
|
136
|
+
return this._scopedEngine;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 解析最终目标模型:
|
|
141
|
+
* - 支持 reference-of-reference 扁平化(直到非 ReferenceBlockModel);
|
|
142
|
+
* - 简单循环检测(A→B→A 等);
|
|
143
|
+
* - 目标缺失或非法时返回 null。
|
|
144
|
+
*/
|
|
145
|
+
private async _resolveFinalTarget(uid: string): Promise<FlowModel | null> {
|
|
146
|
+
const engine = this._ensureScopedEngine();
|
|
147
|
+
const visited = new Set<string>();
|
|
148
|
+
let currentUid = uid;
|
|
149
|
+
for (let i = 0; i < 20; i++) {
|
|
150
|
+
if (!currentUid) return null;
|
|
151
|
+
if (visited.has(currentUid) || currentUid === this.uid) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
visited.add(currentUid);
|
|
155
|
+
|
|
156
|
+
const model = await engine.loadModel<FlowModel>({ uid: currentUid });
|
|
157
|
+
if (!model) return null;
|
|
158
|
+
|
|
159
|
+
const isReference = model.constructor.name === 'ReferenceBlockModel';
|
|
160
|
+
if (!isReference) {
|
|
161
|
+
return model;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const next = model.getStepParams('referenceSettings', 'target')?.targetUid;
|
|
165
|
+
if (!next || typeof next !== 'string' || !next.trim()) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
currentUid = String(next).trim();
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public async onDispatchEventStart(eventName: string): Promise<void> {
|
|
174
|
+
if (eventName !== 'beforeRender') return;
|
|
175
|
+
const stepParams = (this.getStepParams as any)?.('referenceSettings', 'target') || {};
|
|
176
|
+
const targetUid = (stepParams?.targetUid || '').trim() || undefined;
|
|
177
|
+
if (!targetUid) {
|
|
178
|
+
this._syncExtraTitle(false);
|
|
179
|
+
const oldTarget: FlowModel | undefined = (this.subModels as any)['target'];
|
|
180
|
+
if (oldTarget) {
|
|
181
|
+
(this as any).emitter?.emit?.('onSubModelRemoved', oldTarget);
|
|
182
|
+
this._scopedEngine?.removeModel(oldTarget.uid);
|
|
183
|
+
}
|
|
184
|
+
this._targetModel = undefined;
|
|
185
|
+
this._resolvedTargetUid = undefined;
|
|
186
|
+
this._invalidTargetUid = undefined;
|
|
187
|
+
(this.subModels as any)['target'] = undefined;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this._syncExtraTitle(true);
|
|
192
|
+
if (this._resolvedTargetUid === targetUid && this._targetModel) {
|
|
193
|
+
this._invalidTargetUid = undefined;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// 进入解析流程:先清理 invalid 标记,避免渲染层误判为 invalid
|
|
197
|
+
this._invalidTargetUid = undefined;
|
|
198
|
+
let target = await this._resolveFinalTarget(targetUid);
|
|
199
|
+
if (!target) {
|
|
200
|
+
// 与 ReferenceFormGridModel 保持一致:中间态下可能首次解析失败,做一次轻量重试避免闪错
|
|
201
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
202
|
+
const latestStepParams = (this.getStepParams as any)?.('referenceSettings', 'target') || {};
|
|
203
|
+
const latestTargetUid = (latestStepParams?.targetUid || '').trim() || undefined;
|
|
204
|
+
if (latestTargetUid !== targetUid) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
target = await this._resolveFinalTarget(targetUid);
|
|
208
|
+
}
|
|
209
|
+
if (!target) {
|
|
210
|
+
const oldTarget: FlowModel | undefined = (this.subModels as any)['target'];
|
|
211
|
+
if (oldTarget) {
|
|
212
|
+
(this as any).emitter?.emit?.('onSubModelRemoved', oldTarget);
|
|
213
|
+
this._scopedEngine?.removeModel(oldTarget.uid);
|
|
214
|
+
}
|
|
215
|
+
this._targetModel = undefined;
|
|
216
|
+
this._resolvedTargetUid = undefined;
|
|
217
|
+
this._invalidTargetUid = targetUid;
|
|
218
|
+
(this.subModels as any)['target'] = undefined;
|
|
219
|
+
this.rerender();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
target.setParent(this);
|
|
224
|
+
const oldTarget: FlowModel | undefined = (this.subModels as any)['target'];
|
|
225
|
+
if (oldTarget?.uid !== target.uid) {
|
|
226
|
+
this.setSubModel('target', target);
|
|
227
|
+
if (oldTarget) {
|
|
228
|
+
this._scopedEngine?.removeModel(oldTarget.uid);
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
(this.subModels as any)['target'] = target;
|
|
232
|
+
(this as any).emitter?.emit?.('onSubModelReplaced', { oldModel: oldTarget, newModel: target });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this._targetModel = target;
|
|
236
|
+
this._resolvedTargetUid = targetUid;
|
|
237
|
+
this._invalidTargetUid = undefined;
|
|
238
|
+
this.rerender();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async destroy(): Promise<boolean> {
|
|
242
|
+
try {
|
|
243
|
+
unlinkScopedEngine(this._scopedEngine);
|
|
244
|
+
} finally {
|
|
245
|
+
this._scopedEngine = undefined;
|
|
246
|
+
}
|
|
247
|
+
return await super.destroy();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 重写 serialize 方法,排除 target 子模型的序列化
|
|
252
|
+
* 这样在保存引用区块时不会连带保存目标区块,避免破坏目标区块的父子关系
|
|
253
|
+
*/
|
|
254
|
+
serialize(): Record<string, any> {
|
|
255
|
+
const data = super.serialize();
|
|
256
|
+
// 从序列化结果中移除 target 子模型
|
|
257
|
+
if (data.subModels && 'target' in data.subModels) {
|
|
258
|
+
delete data.subModels.target;
|
|
259
|
+
// 如果 subModels 为空对象,也删除它
|
|
260
|
+
if (Object.keys(data.subModels).length === 0) {
|
|
261
|
+
delete data.subModels;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return data;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
renderComponent() {
|
|
268
|
+
const target: FlowModel | undefined = (this.subModels as any)?.['target'];
|
|
269
|
+
const configuredUid = this._getTargetUidFromParams();
|
|
270
|
+
if (!target) {
|
|
271
|
+
if (!configuredUid) {
|
|
272
|
+
return renderReferenceTargetPlaceholder(this as any, 'unconfigured');
|
|
273
|
+
}
|
|
274
|
+
if (this._invalidTargetUid === configuredUid) {
|
|
275
|
+
return renderReferenceTargetPlaceholder(this as any, 'invalid');
|
|
276
|
+
}
|
|
277
|
+
// 目标尚未解析完成:展示 resolving,占位避免闪现 invalid
|
|
278
|
+
return renderReferenceTargetPlaceholder(this as any, 'resolving');
|
|
279
|
+
}
|
|
280
|
+
// 使用 BlockScoped 引擎包裹渲染,确保拖拽/移动等操作拿到正确的 engine
|
|
281
|
+
const engine = this._ensureScopedEngine();
|
|
282
|
+
return <ReferenceScopedRenderer engine={engine} model={target} />;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public render(): any {
|
|
286
|
+
return <div ref={this.context.ref}>{this.renderComponent()}</div>;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
ReferenceBlockModel.registerFlow({
|
|
291
|
+
key: 'referenceSettings',
|
|
292
|
+
sort: -999,
|
|
293
|
+
title: tStr('Select block template'),
|
|
294
|
+
steps: {
|
|
295
|
+
useTemplate: {
|
|
296
|
+
preset: true,
|
|
297
|
+
hideInSettings: true,
|
|
298
|
+
sort: -10,
|
|
299
|
+
title: tStr('Select block template'),
|
|
300
|
+
uiSchema: (ctx) => {
|
|
301
|
+
const m = (ctx.model as any) || {};
|
|
302
|
+
const step = m.getStepParams?.('referenceSettings', 'useTemplate') || {};
|
|
303
|
+
const templateUid = (step?.templateUid || '').trim();
|
|
304
|
+
const isNew = !!m.isNew;
|
|
305
|
+
const disableSelect = !isNew && !!templateUid;
|
|
306
|
+
const api = (ctx as any)?.api;
|
|
307
|
+
const resolveExpectedAssociationName = (): string => {
|
|
308
|
+
try {
|
|
309
|
+
const init = m.getStepParams?.('resourceSettings', 'init') || {};
|
|
310
|
+
const fromInit = normalizeStr(init?.associationName);
|
|
311
|
+
if (fromInit) return fromInit;
|
|
312
|
+
|
|
313
|
+
const assocName = normalizeStr((m as any)?.context?.association?.resourceName);
|
|
314
|
+
if (assocName) return assocName;
|
|
315
|
+
|
|
316
|
+
const resourceCtx = (m as any)?.context?.resource;
|
|
317
|
+
if (resourceCtx) {
|
|
318
|
+
const fromResourceAssoc =
|
|
319
|
+
typeof resourceCtx.getAssociationName === 'function'
|
|
320
|
+
? normalizeStr(resourceCtx.getAssociationName())
|
|
321
|
+
: '';
|
|
322
|
+
if (fromResourceAssoc) return fromResourceAssoc;
|
|
323
|
+
// resourceName 在关联资源场景通常形如 `users.profile`
|
|
324
|
+
const fromResourceName =
|
|
325
|
+
typeof resourceCtx.getResourceName === 'function' ? normalizeStr(resourceCtx.getResourceName()) : '';
|
|
326
|
+
if (fromResourceName) return fromResourceName;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const viewArgs = (m as any)?.context?.view?.inputArgs || {};
|
|
330
|
+
const fromView = normalizeStr(viewArgs?.associationName);
|
|
331
|
+
if (fromView) return fromView;
|
|
332
|
+
} catch (_) {
|
|
333
|
+
// ignore
|
|
334
|
+
}
|
|
335
|
+
return '';
|
|
336
|
+
};
|
|
337
|
+
const expectedAssociationName = resolveExpectedAssociationName();
|
|
338
|
+
const getTemplateDisabledReason = (tpl: Record<string, any>): string | undefined => {
|
|
339
|
+
return getTemplateAvailabilityDisabledReason(
|
|
340
|
+
ctx,
|
|
341
|
+
tpl,
|
|
342
|
+
{ associationName: expectedAssociationName },
|
|
343
|
+
{ checkResource: false, associationMatch: 'associationResourceOnly' },
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const fetchOptions = async (
|
|
348
|
+
keyword?: string,
|
|
349
|
+
pagination?: { page?: number; pageSize?: number },
|
|
350
|
+
): Promise<{ options: any[]; hasMore: boolean }> => {
|
|
351
|
+
const page = Math.max(1, Number(pagination?.page || 1));
|
|
352
|
+
const pageSize = Math.max(1, Number(pagination?.pageSize || TEMPLATE_LIST_PAGE_SIZE));
|
|
353
|
+
try {
|
|
354
|
+
const res = await api?.resource?.('flowModelTemplates')?.list({
|
|
355
|
+
page,
|
|
356
|
+
pageSize,
|
|
357
|
+
search: keyword || undefined,
|
|
358
|
+
filter: {
|
|
359
|
+
$and: [
|
|
360
|
+
{
|
|
361
|
+
$or: [{ type: { $notIn: ['popup'] } }, { type: null }, { type: '' }],
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
const { rows, count } = parseResourceListResponse<any>(res);
|
|
367
|
+
const rawLength = rows.length;
|
|
368
|
+
const optsWithIndex = rows.map((r, idx) => {
|
|
369
|
+
const name = r.name || r.uid || '';
|
|
370
|
+
const desc = r.description;
|
|
371
|
+
const disabledReason = getTemplateDisabledReason(r || {});
|
|
372
|
+
return {
|
|
373
|
+
__idx: (page - 1) * pageSize + idx,
|
|
374
|
+
label: renderTemplateSelectLabel(name),
|
|
375
|
+
value: r.uid,
|
|
376
|
+
description: desc,
|
|
377
|
+
disabled: !!disabledReason,
|
|
378
|
+
disabledReason,
|
|
379
|
+
rawName: name,
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
options: optsWithIndex.slice().sort(defaultSelectOptionComparator),
|
|
384
|
+
hasMore: calcHasMore({ page, pageSize, rowsLength: rawLength, count }),
|
|
385
|
+
};
|
|
386
|
+
} catch (e) {
|
|
387
|
+
console.error('fetch template options failed', e);
|
|
388
|
+
return { options: [], hasMore: false };
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
return {
|
|
392
|
+
templateUid: {
|
|
393
|
+
title: tStr('Template'),
|
|
394
|
+
'x-decorator': 'FormItem',
|
|
395
|
+
'x-component': 'Select',
|
|
396
|
+
'x-component-props': {
|
|
397
|
+
showSearch: true,
|
|
398
|
+
filterOption: false,
|
|
399
|
+
allowClear: true,
|
|
400
|
+
placeholder: tStr('Search templates'),
|
|
401
|
+
disabled: disableSelect,
|
|
402
|
+
optionLabelProp: 'label',
|
|
403
|
+
dropdownMatchSelectWidth: true,
|
|
404
|
+
dropdownStyle: { maxWidth: 560 },
|
|
405
|
+
getPopupContainer: () => document.body,
|
|
406
|
+
optionRender: renderTemplateSelectOption,
|
|
407
|
+
},
|
|
408
|
+
default: templateUid || undefined,
|
|
409
|
+
'x-validator': [
|
|
410
|
+
{
|
|
411
|
+
required: true,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
'x-reactions': [
|
|
415
|
+
(field) => {
|
|
416
|
+
bindInfiniteScrollToFormilySelect(
|
|
417
|
+
field,
|
|
418
|
+
async (keyword: string, page: number, pageSize: number) => {
|
|
419
|
+
return fetchOptions(keyword, { page, pageSize });
|
|
420
|
+
},
|
|
421
|
+
{ pageSize: TEMPLATE_LIST_PAGE_SIZE, composingKey: '__templateComposing' },
|
|
422
|
+
);
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
templateName: {
|
|
427
|
+
'x-hidden': true,
|
|
428
|
+
'x-component': 'Input',
|
|
429
|
+
default: step?.templateName,
|
|
430
|
+
},
|
|
431
|
+
templateDescription: {
|
|
432
|
+
'x-hidden': true,
|
|
433
|
+
'x-component': 'Input',
|
|
434
|
+
default: step?.templateDescription,
|
|
435
|
+
},
|
|
436
|
+
mode: {
|
|
437
|
+
title: tStr('Mode'),
|
|
438
|
+
'x-decorator': 'FormItem',
|
|
439
|
+
'x-component': 'Radio.Group',
|
|
440
|
+
enum: [
|
|
441
|
+
{ label: tStr('Reference'), value: 'reference' },
|
|
442
|
+
{ label: tStr('Duplicate'), value: 'copy' },
|
|
443
|
+
],
|
|
444
|
+
default: step?.mode || 'reference',
|
|
445
|
+
},
|
|
446
|
+
modeDescriptionReference: {
|
|
447
|
+
type: 'void',
|
|
448
|
+
'x-decorator': 'FormItem',
|
|
449
|
+
'x-decorator-props': { colon: false },
|
|
450
|
+
'x-component': 'Alert',
|
|
451
|
+
'x-component-props': {
|
|
452
|
+
type: 'info',
|
|
453
|
+
showIcon: false,
|
|
454
|
+
message: tStr('Reference mode description'),
|
|
455
|
+
style: { marginTop: -8 },
|
|
456
|
+
},
|
|
457
|
+
'x-reactions': {
|
|
458
|
+
dependencies: ['mode'],
|
|
459
|
+
fulfill: { state: { hidden: '{{$deps[0] === "copy"}}' } },
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
modeDescriptionDuplicate: {
|
|
463
|
+
type: 'void',
|
|
464
|
+
'x-decorator': 'FormItem',
|
|
465
|
+
'x-decorator-props': { colon: false },
|
|
466
|
+
'x-component': 'Alert',
|
|
467
|
+
'x-component-props': {
|
|
468
|
+
type: 'info',
|
|
469
|
+
showIcon: false,
|
|
470
|
+
message: tStr('Duplicate mode description'),
|
|
471
|
+
style: { marginTop: -8 },
|
|
472
|
+
},
|
|
473
|
+
'x-reactions': {
|
|
474
|
+
dependencies: ['mode'],
|
|
475
|
+
fulfill: { state: { hidden: '{{$deps[0] !== "copy"}}' } },
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
},
|
|
480
|
+
async beforeParamsSave(ctx, params) {
|
|
481
|
+
const templateUid = (params?.templateUid || '').trim();
|
|
482
|
+
if (!templateUid) return;
|
|
483
|
+
|
|
484
|
+
const api = (ctx as any)?.api;
|
|
485
|
+
let tpl: any = null;
|
|
486
|
+
if (api?.resource) {
|
|
487
|
+
try {
|
|
488
|
+
const res = await api.resource('flowModelTemplates').get({
|
|
489
|
+
filterByTk: templateUid,
|
|
490
|
+
});
|
|
491
|
+
tpl = res?.data?.data || res;
|
|
492
|
+
} catch (e) {
|
|
493
|
+
console.warn('fetch template failed', e);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const targetUid = tpl?.targetUid;
|
|
498
|
+
const mode = params?.mode || 'reference';
|
|
499
|
+
|
|
500
|
+
// 复制模式:调用 target step 的 beforeParamsSave
|
|
501
|
+
if (mode === 'copy' && targetUid) {
|
|
502
|
+
const flow = (ctx.model.constructor as typeof FlowModel).globalFlowRegistry.getFlow('referenceSettings');
|
|
503
|
+
const targetStepDef = flow?.steps?.target as any;
|
|
504
|
+
if (targetStepDef?.beforeParamsSave) {
|
|
505
|
+
await targetStepDef.beforeParamsSave(ctx, { targetUid, mode: 'copy' });
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// 引用模式:不需要特殊处理,handler 会处理
|
|
511
|
+
},
|
|
512
|
+
async handler(ctx, params) {
|
|
513
|
+
const templateUid = (params?.templateUid || '').trim();
|
|
514
|
+
if (!templateUid) return;
|
|
515
|
+
|
|
516
|
+
const mode = params?.mode || 'reference';
|
|
517
|
+
// 复制模式已在 beforeParamsSave 中处理完毕
|
|
518
|
+
if (mode === 'copy') return;
|
|
519
|
+
|
|
520
|
+
const api = (ctx as any)?.api;
|
|
521
|
+
let tpl: any = null;
|
|
522
|
+
if (api?.resource) {
|
|
523
|
+
try {
|
|
524
|
+
const res = await api.resource('flowModelTemplates').get({
|
|
525
|
+
filterByTk: templateUid,
|
|
526
|
+
});
|
|
527
|
+
tpl = res?.data?.data || res;
|
|
528
|
+
} catch (e) {
|
|
529
|
+
console.warn('fetch template failed', e);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const templateName = tpl?.name || params?.templateName;
|
|
534
|
+
const templateDescription = tpl?.description || params?.templateDescription;
|
|
535
|
+
const targetUid = tpl?.targetUid;
|
|
536
|
+
|
|
537
|
+
// 引用模式:保存参数,ReferenceBlockModel 会在 beforeRender 中加载目标
|
|
538
|
+
const useTemplateParams = (ctx.model as FlowModel).getStepParams('referenceSettings', 'useTemplate') || {};
|
|
539
|
+
(ctx.model as FlowModel).setStepParams('referenceSettings', 'useTemplate', {
|
|
540
|
+
...useTemplateParams,
|
|
541
|
+
templateUid,
|
|
542
|
+
templateName,
|
|
543
|
+
templateDescription,
|
|
544
|
+
targetUid: targetUid || useTemplateParams?.targetUid,
|
|
545
|
+
mode,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
if (targetUid) {
|
|
549
|
+
const targetParams = (ctx.model as FlowModel).getStepParams('referenceSettings', 'target') || {};
|
|
550
|
+
(ctx.model as FlowModel).setStepParams('referenceSettings', 'target', {
|
|
551
|
+
...targetParams,
|
|
552
|
+
targetUid,
|
|
553
|
+
mode,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const resourceInit = (ctx.model as FlowModel).getStepParams('resourceSettings', 'init') || {};
|
|
558
|
+
const dataSourceKey = tpl?.dataSourceKey ?? resourceInit?.dataSourceKey;
|
|
559
|
+
const collectionName = tpl?.collectionName ?? resourceInit?.collectionName;
|
|
560
|
+
const associationName = tpl?.associationName ?? resourceInit?.associationName;
|
|
561
|
+
const filterByTk = tpl?.filterByTk ?? resourceInit?.filterByTk;
|
|
562
|
+
const sourceId = tpl?.sourceId ?? resourceInit?.sourceId;
|
|
563
|
+
if (dataSourceKey || collectionName || associationName || filterByTk || sourceId) {
|
|
564
|
+
(ctx.model as FlowModel).setStepParams('resourceSettings', 'init', {
|
|
565
|
+
...resourceInit,
|
|
566
|
+
dataSourceKey,
|
|
567
|
+
collectionName,
|
|
568
|
+
associationName,
|
|
569
|
+
filterByTk,
|
|
570
|
+
sourceId,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
target: {
|
|
576
|
+
preset: true,
|
|
577
|
+
hideInSettings: true,
|
|
578
|
+
title: tStr('Template settings'),
|
|
579
|
+
uiSchema: (ctx) => {
|
|
580
|
+
const m = (ctx.model as any) || {};
|
|
581
|
+
const step = m.getStepParams?.('referenceSettings', 'target') || {};
|
|
582
|
+
const uid = (step?.targetUid || '').trim();
|
|
583
|
+
const isNew = !!m.isNew;
|
|
584
|
+
const hasConfigured = !!uid;
|
|
585
|
+
const templateStep = m.getStepParams?.('referenceSettings', 'useTemplate') || {};
|
|
586
|
+
const hasTemplate = !!templateStep?.templateUid;
|
|
587
|
+
// 更精准的有效性判断:要求已解析的 _targetModel 存在且与当前 uid 匹配
|
|
588
|
+
const resolvedUid = m._resolvedTargetUid;
|
|
589
|
+
const resolvedModel = m._targetModel;
|
|
590
|
+
const hasValidTarget = !!resolvedModel && resolvedUid === uid;
|
|
591
|
+
const disableUid = (!isNew && hasConfigured && hasValidTarget) || hasTemplate;
|
|
592
|
+
return {
|
|
593
|
+
targetUid: {
|
|
594
|
+
title: tStr('Block UID'),
|
|
595
|
+
'x-component': 'Input',
|
|
596
|
+
'x-decorator': 'FormItem',
|
|
597
|
+
'x-decorator-props': {
|
|
598
|
+
tooltip: disableUid ? tStr('Block UID is already set and cannot be modified') : undefined,
|
|
599
|
+
},
|
|
600
|
+
'x-component-props': {
|
|
601
|
+
disabled: disableUid,
|
|
602
|
+
},
|
|
603
|
+
'x-validator': [
|
|
604
|
+
{
|
|
605
|
+
format: 'string',
|
|
606
|
+
required: true,
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
},
|
|
610
|
+
mode: {
|
|
611
|
+
title: tStr('Mode'),
|
|
612
|
+
'x-component': 'Radio.Group',
|
|
613
|
+
'x-decorator': 'FormItem',
|
|
614
|
+
'x-component-props': {
|
|
615
|
+
disabled: hasTemplate,
|
|
616
|
+
},
|
|
617
|
+
enum: [
|
|
618
|
+
{ label: tStr('Reference'), value: 'reference' },
|
|
619
|
+
{ label: tStr('Duplicate'), value: 'copy' },
|
|
620
|
+
],
|
|
621
|
+
},
|
|
622
|
+
copyNotice: {
|
|
623
|
+
type: 'void',
|
|
624
|
+
'x-decorator': 'FormItem',
|
|
625
|
+
'x-component': 'Alert',
|
|
626
|
+
'x-component-props': {
|
|
627
|
+
type: 'warning',
|
|
628
|
+
showIcon: true,
|
|
629
|
+
message: tStr('Some configurations using uid may need to be reconfigured'),
|
|
630
|
+
},
|
|
631
|
+
'x-reactions': {
|
|
632
|
+
dependencies: ['mode'],
|
|
633
|
+
fulfill: {
|
|
634
|
+
state: {
|
|
635
|
+
hidden: '{{$deps[0] !== "copy"}}',
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
},
|
|
642
|
+
defaultParams() {
|
|
643
|
+
return { mode: 'reference' };
|
|
644
|
+
},
|
|
645
|
+
async beforeParamsSave(ctx, params) {
|
|
646
|
+
const v = (params?.targetUid || '').trim();
|
|
647
|
+
const mode = params?.mode || 'reference';
|
|
648
|
+
if (mode !== 'copy' || !v) return;
|
|
649
|
+
const engine = ctx.engine;
|
|
650
|
+
// 1) 先在服务端复制目标模型,得到新的根节点 JSON(含新 uid)
|
|
651
|
+
const duplicated = await engine.duplicateModel(v);
|
|
652
|
+
if (!duplicated) return;
|
|
653
|
+
|
|
654
|
+
// 2) 计算父模型与原位置
|
|
655
|
+
const oldModel = ctx.model as FlowModel;
|
|
656
|
+
const parent = oldModel.parent as FlowModel | undefined;
|
|
657
|
+
const subKey = oldModel.subKey as string;
|
|
658
|
+
const subType = (oldModel as any).subType as 'array' | 'object';
|
|
659
|
+
|
|
660
|
+
// 若没有父模型,直接退出(无处安放新实例)
|
|
661
|
+
if (!parent || !subKey) {
|
|
662
|
+
ctx.exit();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
let insertIndex = -1;
|
|
667
|
+
if (subType === 'array') {
|
|
668
|
+
const arr = ((parent.subModels as any)[subKey] || []) as FlowModel[];
|
|
669
|
+
insertIndex = Array.isArray(arr) ? arr.findIndex((m) => m?.uid === oldModel.uid) : -1;
|
|
670
|
+
if (insertIndex < 0) insertIndex = arr.length;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// 3) 在本地创建新实例(挂到同一父与 subKey)
|
|
674
|
+
const newOptions = {
|
|
675
|
+
...duplicated,
|
|
676
|
+
parentId: parent.uid,
|
|
677
|
+
subKey,
|
|
678
|
+
subType,
|
|
679
|
+
sortIndex: insertIndex >= 0 ? insertIndex : 0,
|
|
680
|
+
} as any;
|
|
681
|
+
const newModel = engine.createModel<FlowModel>(newOptions);
|
|
682
|
+
newModel.setParent(parent);
|
|
683
|
+
newModel.sortIndex = insertIndex >= 0 ? insertIndex : 0;
|
|
684
|
+
|
|
685
|
+
const isPresetOrNew = !!(oldModel as any).isNew;
|
|
686
|
+
// 4) 预设/新建 与 已持久化 分支分别处理
|
|
687
|
+
if (isPresetOrNew) {
|
|
688
|
+
// 新建场景:直接替换临时的 ReferenceBlockModel
|
|
689
|
+
if (subType === 'array') {
|
|
690
|
+
let arr = (parent.subModels as any)[subKey] as FlowModel[] | undefined;
|
|
691
|
+
if (!Array.isArray(arr)) {
|
|
692
|
+
(parent.subModels as any)[subKey] = [];
|
|
693
|
+
arr = (parent.subModels as any)[subKey] as FlowModel[];
|
|
694
|
+
}
|
|
695
|
+
// 找到旧模型的位置并替换
|
|
696
|
+
const oldIndex = arr.findIndex((m) => m?.uid === oldModel.uid);
|
|
697
|
+
if (oldIndex >= 0) {
|
|
698
|
+
arr.splice(oldIndex, 1, newModel);
|
|
699
|
+
} else {
|
|
700
|
+
arr.push(newModel);
|
|
701
|
+
}
|
|
702
|
+
arr.forEach((m, idx) => (m.sortIndex = idx));
|
|
703
|
+
} else {
|
|
704
|
+
parent.setSubModel(subKey, newModel);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
engine.removeModel(oldModel.uid);
|
|
708
|
+
|
|
709
|
+
(newModel as any).isNew = true;
|
|
710
|
+
(parent as any).emitter?.emit?.('onSubModelAdded', newModel);
|
|
711
|
+
await (newModel as any).afterAddAsSubModel?.();
|
|
712
|
+
|
|
713
|
+
await engine.modelRepository.destroy(newModel.uid);
|
|
714
|
+
await newModel.save();
|
|
715
|
+
|
|
716
|
+
(newModel as any).isNew = false;
|
|
717
|
+
await parent.saveStepParams();
|
|
718
|
+
parent.rerender();
|
|
719
|
+
} else {
|
|
720
|
+
// replace:保持当前位置不变——手动插入到与旧实例相同的索引,并更新 rows 将旧 uid 替换为新 uid
|
|
721
|
+
if (subType === 'array') {
|
|
722
|
+
let arr = (parent.subModels as any)[subKey] as FlowModel[] | undefined;
|
|
723
|
+
if (!Array.isArray(arr)) {
|
|
724
|
+
(parent.subModels as any)[subKey] = [];
|
|
725
|
+
arr = (parent.subModels as any)[subKey] as FlowModel[];
|
|
726
|
+
}
|
|
727
|
+
const finalIndex = Math.min(Math.max(insertIndex, 0), arr.length);
|
|
728
|
+
arr.splice(finalIndex, 0, newModel);
|
|
729
|
+
arr.forEach((m, idx) => (m.sortIndex = idx));
|
|
730
|
+
|
|
731
|
+
// 替换 Grid rows 中的 uid,保持原位置
|
|
732
|
+
const gridParams = parent.getStepParams('gridSettings', 'grid') || {};
|
|
733
|
+
if (gridParams?.rows && typeof gridParams.rows === 'object') {
|
|
734
|
+
const newRows = _.cloneDeep(gridParams.rows);
|
|
735
|
+
for (const rowId of Object.keys(newRows)) {
|
|
736
|
+
const columns = newRows[rowId];
|
|
737
|
+
if (Array.isArray(columns)) {
|
|
738
|
+
for (let ci = 0; ci < columns.length; ci++) {
|
|
739
|
+
const col = columns[ci];
|
|
740
|
+
if (Array.isArray(col)) {
|
|
741
|
+
for (let ii = 0; ii < col.length; ii++) {
|
|
742
|
+
if (col[ii] === oldModel.uid) {
|
|
743
|
+
col[ii] = newModel.uid;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
parent.setStepParams('gridSettings', 'grid', { rows: newRows, sizes: gridParams.sizes || {} });
|
|
751
|
+
parent.setProps('rows', newRows);
|
|
752
|
+
}
|
|
753
|
+
await (newModel as any).afterAddAsSubModel?.();
|
|
754
|
+
} else {
|
|
755
|
+
parent.setSubModel(subKey, newModel);
|
|
756
|
+
await (newModel as any).afterAddAsSubModel?.();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// 5) 已持久化场景:先保存新实例、再相对移动并删除旧实例,最后只保存布局参数
|
|
760
|
+
await newModel.save();
|
|
761
|
+
(newModel as any).isNew = false;
|
|
762
|
+
// 5b) 已持久化场景:若为数组子模型,则在服务端相对移动保持原位置;随后销毁旧实例
|
|
763
|
+
if (subType === 'array' && engine.modelRepository) {
|
|
764
|
+
const targetExists = await (engine.modelRepository as any).findOne({ uid: oldModel.uid });
|
|
765
|
+
if (targetExists && typeof (engine.modelRepository as any).move === 'function') {
|
|
766
|
+
await (engine.modelRepository as any).move(newModel.uid, oldModel.uid, 'before');
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
await engine.destroyModel(oldModel.uid);
|
|
770
|
+
// 持久化父模型的布局参数
|
|
771
|
+
await parent.saveStepParams();
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// 关闭设置视图
|
|
775
|
+
ctx.exit();
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
ReferenceBlockModel.registerFlow({
|
|
782
|
+
key: 'cardSettings',
|
|
783
|
+
steps: {}, // 隐藏自身的block配置
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
ReferenceBlockModel.define({
|
|
787
|
+
label: tStr('Block template'),
|
|
788
|
+
group: escapeT('Other blocks'),
|
|
789
|
+
createModelOptions: {
|
|
790
|
+
use: 'ReferenceBlockModel',
|
|
791
|
+
},
|
|
792
|
+
sort: 900,
|
|
793
|
+
});
|