@nocobase/plugin-ui-templates 2.0.0-alpha.58 → 2.0.0-alpha.60
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/dist/client/index.js +1 -1
- package/dist/client/models/ReferenceFormGridModel.d.ts +16 -2
- package/dist/client/models/SubModelTemplateImporterModel.d.ts +2 -15
- package/dist/client/models/referenceShared.d.ts +2 -1
- package/dist/client/utils/templateCopy.d.ts +21 -0
- package/dist/externalVersion.js +7 -7
- package/dist/locale/zh-CN.json +1 -1
- package/package.json +3 -3
- package/src/client/index.ts +0 -2
- package/src/client/menuExtensions.tsx +53 -5
- package/src/client/models/ReferenceBlockModel.tsx +4 -0
- package/src/client/models/ReferenceFormGridModel.tsx +175 -29
- package/src/client/models/SubModelTemplateImporterModel.tsx +295 -216
- package/src/client/models/__tests__/ReferenceFormGridModel.test.tsx +787 -3
- package/src/client/models/__tests__/SubModelTemplateImporterModel.test.ts +80 -19
- package/src/client/models/referenceShared.tsx +8 -0
- package/src/client/utils/__tests__/templateCopy.test.ts +67 -0
- package/src/client/utils/templateCopy.ts +59 -0
- package/src/locale/zh-CN.json +1 -1
- package/dist/client/subModelMenuExtensions.d.ts +0 -10
- package/dist/client/utils/refHost.d.ts +0 -20
- package/src/client/subModelMenuExtensions.ts +0 -103
- package/src/client/utils/refHost.ts +0 -44
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { type CreateModelOptions, FlowModel } from '@nocobase/flow-engine';
|
|
11
|
-
import { REF_HOST_CTX_KEY } from '../constants';
|
|
12
|
-
export { REF_HOST_CTX_KEY };
|
|
13
11
|
export type ReferenceFormGridTargetSettings = {
|
|
14
12
|
/** 模板 uid(flowModelTemplates.uid) */
|
|
15
13
|
templateUid: string;
|
|
@@ -22,15 +20,31 @@ export type ReferenceFormGridTargetSettings = {
|
|
|
22
20
|
};
|
|
23
21
|
export declare class ReferenceFormGridModel extends FlowModel {
|
|
24
22
|
private _scopedEngine?;
|
|
23
|
+
private _targetRoot?;
|
|
25
24
|
private _targetGrid?;
|
|
26
25
|
private _resolvedTargetUid?;
|
|
27
26
|
private _invalidTargetUid?;
|
|
27
|
+
/**
|
|
28
|
+
* 字段模板场景下,模板内 FormItemModel/CollectionFieldModel 的 onInit 会调用 ctx.blockModel.addAppends(fieldPath)。
|
|
29
|
+
* 但模板 root 自身也是一个 CollectionBlockModel,会在未桥接宿主上下文时被识别为 blockModel,导致 appends 写到模板 root 的 resource,
|
|
30
|
+
* 从而宿主表单(如 ApplyFormModel)在刷新记录时缺少关联 appends(例如 users.roles)。
|
|
31
|
+
*
|
|
32
|
+
* 这里在目标 grid 解析完成后扫描字段路径,并把需要的 appends 同步到宿主 block(master + forks),确保关系数据可展示。
|
|
33
|
+
*/
|
|
34
|
+
private _syncHostResourceAppends;
|
|
28
35
|
constructor(options: any);
|
|
29
36
|
private _ensureScopedEngine;
|
|
30
37
|
private _getTargetSettings;
|
|
31
38
|
private _syncHostExtraTitle;
|
|
32
39
|
addSubModel<T extends FlowModel>(subKey: string, options: CreateModelOptions | T): T;
|
|
33
40
|
setSubModel(subKey: string, options: CreateModelOptions | FlowModel): FlowModel<import("@nocobase/flow-engine").DefaultStructure>;
|
|
41
|
+
getStepParams(flowKey: string, stepKey: string): any | undefined;
|
|
42
|
+
getStepParams(flowKey: string): Record<string, any> | undefined;
|
|
43
|
+
getStepParams(): Record<string, any>;
|
|
44
|
+
setStepParams(flowKey: string, stepKey: string, params: any): void;
|
|
45
|
+
setStepParams(flowKey: string, stepParams: Record<string, any>): void;
|
|
46
|
+
setStepParams(allParams: Record<string, any>): void;
|
|
47
|
+
saveStepParams(): Promise<any>;
|
|
34
48
|
onDispatchEventStart(eventName: string): Promise<void>;
|
|
35
49
|
clearForks(): void;
|
|
36
50
|
destroy(): Promise<boolean>;
|
|
@@ -6,27 +6,14 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
+
import { CommonItemModel } from '@nocobase/client';
|
|
9
10
|
import { FlowModel, FlowContext } from '@nocobase/flow-engine';
|
|
10
11
|
type ImporterProps = {
|
|
11
|
-
/** 默认从模板根取片段的路径 */
|
|
12
|
-
defaultSourcePath?: string;
|
|
13
|
-
/** 模板根 use 过滤(可选),支持多个候选 */
|
|
14
12
|
expectedRootUse?: string | string[];
|
|
15
|
-
/** 期望的数据源 key(可选,用于禁用不匹配的模板) */
|
|
16
13
|
expectedDataSourceKey?: string;
|
|
17
|
-
/** 期望的 collectionName(可选,用于禁用不匹配的模板) */
|
|
18
14
|
expectedCollectionName?: string;
|
|
19
|
-
/** 默认挂载到当前模型的 subModels 键(可选),否则使用 importer.subKey */
|
|
20
|
-
defaultMountSubKey?: string;
|
|
21
|
-
/**
|
|
22
|
-
* 引入片段时挂载到第几层父级:
|
|
23
|
-
* - 0:挂载到 importer.parent(默认)
|
|
24
|
-
* - 1:挂载到 importer.parent.parent
|
|
25
|
-
* - 2:以此类推
|
|
26
|
-
*/
|
|
27
|
-
mountToParentLevel?: number;
|
|
28
15
|
};
|
|
29
|
-
export declare class SubModelTemplateImporterModel extends
|
|
16
|
+
export declare class SubModelTemplateImporterModel extends CommonItemModel {
|
|
30
17
|
props: ImporterProps;
|
|
31
18
|
resolveExpectedResourceInfo(ctx?: FlowContext, start?: FlowModel): {
|
|
32
19
|
dataSourceKey?: string;
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import React from 'react';
|
|
10
|
-
import { type FlowEngine, type FlowModel } from '@nocobase/flow-engine';
|
|
10
|
+
import { FlowContext, type FlowEngine, type FlowModel } from '@nocobase/flow-engine';
|
|
11
11
|
export declare function ensureBlockScopedEngine(flowEngine: FlowEngine, scopedEngine?: FlowEngine): FlowEngine;
|
|
12
|
+
export declare function ensureScopedEngineView(engine: FlowEngine, hostContext?: FlowContext): void;
|
|
12
13
|
export declare function unlinkScopedEngine(engine?: FlowEngine): void;
|
|
13
14
|
export declare function renderReferenceTargetPlaceholder(model: {
|
|
14
15
|
translate?: (key: string, options?: any) => string;
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
import { FlowModel } from '@nocobase/flow-engine';
|
|
10
|
+
/**
|
|
11
|
+
* 字段模板 copy 模式只会 duplicate `subModels.grid`,而历史模板可能把部分配置(如布局/连动规则)
|
|
12
|
+
* 存在模板 root 上。reference 模式通过 getStepParams 的 fallback 能读到这些值,但 copy 模式需要
|
|
13
|
+
* 主动把这些 stepParams 合并进 grid 的 stepParams,避免丢失。
|
|
14
|
+
*
|
|
15
|
+
* - 仅在 grid 上缺失对应 stepKey 时才回填(不覆盖 grid 上已有值)
|
|
16
|
+
* - 返回 patched 标记,方便调用方决定是否需要 `saveStepParams()`
|
|
17
|
+
*/
|
|
18
|
+
export declare function patchGridOptionsFromTemplateRoot(templateRoot: FlowModel | undefined, gridOptions: any): {
|
|
19
|
+
options: any;
|
|
20
|
+
patched: boolean;
|
|
21
|
+
};
|
package/dist/externalVersion.js
CHANGED
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.0.0-alpha.
|
|
12
|
-
"@nocobase/flow-engine": "2.0.0-alpha.
|
|
11
|
+
"@nocobase/client": "2.0.0-alpha.60",
|
|
12
|
+
"@nocobase/flow-engine": "2.0.0-alpha.60",
|
|
13
13
|
"react": "18.2.0",
|
|
14
14
|
"antd": "5.24.2",
|
|
15
15
|
"@ant-design/icons": "5.6.1",
|
|
16
16
|
"lodash": "4.17.21",
|
|
17
17
|
"@formily/react": "2.3.7",
|
|
18
|
-
"@nocobase/server": "2.0.0-alpha.
|
|
19
|
-
"@nocobase/plugin-flow-engine": "2.0.0-alpha.
|
|
20
|
-
"@nocobase/utils": "2.0.0-alpha.
|
|
18
|
+
"@nocobase/server": "2.0.0-alpha.60",
|
|
19
|
+
"@nocobase/plugin-flow-engine": "2.0.0-alpha.60",
|
|
20
|
+
"@nocobase/utils": "2.0.0-alpha.60",
|
|
21
21
|
"@formily/core": "2.3.7",
|
|
22
|
-
"@nocobase/database": "2.0.0-alpha.
|
|
23
|
-
"@nocobase/actions": "2.0.0-alpha.
|
|
22
|
+
"@nocobase/database": "2.0.0-alpha.60",
|
|
23
|
+
"@nocobase/actions": "2.0.0-alpha.60"
|
|
24
24
|
};
|
package/dist/locale/zh-CN.json
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/plugin-ui-templates",
|
|
3
3
|
"displayName": "UI templates",
|
|
4
|
-
"displayName.zh-CN": "
|
|
4
|
+
"displayName.zh-CN": "UI 模板",
|
|
5
5
|
"description": "Provides block templates and popup templates for UI reuse.",
|
|
6
6
|
"description.zh-CN": "提供区块模板和弹窗模板复用的能力。",
|
|
7
|
-
"version": "2.0.0-alpha.
|
|
7
|
+
"version": "2.0.0-alpha.60",
|
|
8
8
|
"license": "AGPL-3.0",
|
|
9
9
|
"main": "./dist/server/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"block",
|
|
34
34
|
"popup"
|
|
35
35
|
],
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "9113d61ce85b60b7ba3d0e5ca64182d92a15ece4"
|
|
37
37
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import { BlockTemplatesPage, PopupTemplatesPage } from './components/FlowModelTe
|
|
|
15
15
|
// @ts-ignore
|
|
16
16
|
import pkg from '../../package.json';
|
|
17
17
|
import { registerMenuExtensions } from './menuExtensions';
|
|
18
|
-
import { registerSubModelMenuExtensions } from './subModelMenuExtensions';
|
|
19
18
|
import { registerOpenViewPopupTemplateAction } from './openViewActionExtensions';
|
|
20
19
|
|
|
21
20
|
const NAMESPACE = 'ui-templates';
|
|
@@ -27,7 +26,6 @@ export class PluginBlockReferenceClient extends Plugin {
|
|
|
27
26
|
ReferenceFormGridModel,
|
|
28
27
|
SubModelTemplateImporterModel,
|
|
29
28
|
});
|
|
30
|
-
registerSubModelMenuExtensions(this.flowEngine);
|
|
31
29
|
registerOpenViewPopupTemplateAction(this.flowEngine);
|
|
32
30
|
|
|
33
31
|
// 父级菜单(只有标题,无组件)
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
resolveActionScene,
|
|
23
23
|
type PopupTemplateContextFlags,
|
|
24
24
|
} from './utils/templateCompatibility';
|
|
25
|
+
import { patchGridOptionsFromTemplateRoot } from './utils/templateCopy';
|
|
25
26
|
|
|
26
27
|
type MenuItem = {
|
|
27
28
|
key: string;
|
|
@@ -389,18 +390,22 @@ async function handleConvertFieldsToCopy(model: FlowModel, _t: (k: string, opt?:
|
|
|
389
390
|
}
|
|
390
391
|
|
|
391
392
|
const duplicated = await model.flowEngine.duplicateModel(gridModel.uid);
|
|
393
|
+
const merged = patchGridOptionsFromTemplateRoot(root, duplicated);
|
|
392
394
|
|
|
393
395
|
// 将复制出的 grid(默认脱离父级)移动到当前表单 grid 位置,避免再走 save 重建整棵树
|
|
394
396
|
await model.flowEngine.modelRepository.move(duplicated.uid, currentGrid.uid, 'after');
|
|
395
397
|
|
|
396
398
|
const newGrid = model.flowEngine.createModel<FlowModel>({
|
|
397
|
-
...(
|
|
399
|
+
...(merged.options as any),
|
|
398
400
|
parentId: model.uid,
|
|
399
401
|
subKey: 'grid',
|
|
400
402
|
subType: 'object',
|
|
401
403
|
});
|
|
402
404
|
model.setSubModel('grid', newGrid);
|
|
403
405
|
await newGrid.afterAddAsSubModel();
|
|
406
|
+
if (merged.patched) {
|
|
407
|
+
await newGrid.saveStepParams();
|
|
408
|
+
}
|
|
404
409
|
|
|
405
410
|
// 引用已清理,回退临时标题(移除“字段模板”标记)
|
|
406
411
|
const clearTemplateTitle = (m: FlowModel) => {
|
|
@@ -531,6 +536,49 @@ async function handleSavePopupAsTemplate(model: FlowModel, _t: (k: string, opt?:
|
|
|
531
536
|
const s = String(val).trim();
|
|
532
537
|
return s ? s : undefined;
|
|
533
538
|
};
|
|
539
|
+
const resolveDefaultOpenViewResource = (): {
|
|
540
|
+
dataSourceKey?: string;
|
|
541
|
+
collectionName?: string;
|
|
542
|
+
associationName?: string;
|
|
543
|
+
} => {
|
|
544
|
+
const ctx = model.context;
|
|
545
|
+
const field = ctx.collectionField;
|
|
546
|
+
const associationPathName = model.parent?.['associationPathName'];
|
|
547
|
+
const blockModel = ctx.blockModel;
|
|
548
|
+
const fieldCollection = ctx.collection || blockModel?.collection;
|
|
549
|
+
const isAssociationField = (f): boolean => !!f?.isAssociationField?.();
|
|
550
|
+
const associationField =
|
|
551
|
+
!isAssociationField(field) && associationPathName && typeof fieldCollection?.getFieldByPath === 'function'
|
|
552
|
+
? fieldCollection.getFieldByPath(associationPathName)
|
|
553
|
+
: undefined;
|
|
554
|
+
const assocField = isAssociationField(field) ? field : associationField;
|
|
555
|
+
|
|
556
|
+
if (isAssociationField(assocField)) {
|
|
557
|
+
const targetCollection = assocField?.targetCollection;
|
|
558
|
+
return {
|
|
559
|
+
dataSourceKey: toNonEmptyString(targetCollection?.dataSourceKey),
|
|
560
|
+
collectionName: toNonEmptyString(targetCollection?.name),
|
|
561
|
+
associationName: toNonEmptyString(assocField?.resourceName),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const collection = ctx.collection;
|
|
566
|
+
const association = ctx.association;
|
|
567
|
+
return {
|
|
568
|
+
dataSourceKey: toNonEmptyString(collection?.dataSourceKey),
|
|
569
|
+
collectionName: toNonEmptyString(collection?.name),
|
|
570
|
+
associationName: toNonEmptyString(field?.target || association?.resourceName),
|
|
571
|
+
};
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// 兼容历史数据:openViewParams 里固化的 associationName 可能是错的。
|
|
575
|
+
// 当能从上下文推导出 associationName 时(关联字段/关联字段属性),优先使用推导值。
|
|
576
|
+
const defaultOpenViewResource = resolveDefaultOpenViewResource();
|
|
577
|
+
const templateDataSourceKey =
|
|
578
|
+
defaultOpenViewResource.dataSourceKey || toNonEmptyString(openViewParams?.dataSourceKey);
|
|
579
|
+
const templateCollectionName =
|
|
580
|
+
defaultOpenViewResource.collectionName || toNonEmptyString(openViewParams?.collectionName);
|
|
581
|
+
const templateAssociationName = defaultOpenViewResource.associationName;
|
|
534
582
|
const getDefaultFilterByTkExpr = (): string | undefined => {
|
|
535
583
|
// 与 openView 默认行为对齐:尽量落表达式而非具体值
|
|
536
584
|
const recordKeyPath = model.context?.collection?.filterTargetKey || 'id';
|
|
@@ -538,7 +586,7 @@ async function handleSavePopupAsTemplate(model: FlowModel, _t: (k: string, opt?:
|
|
|
538
586
|
};
|
|
539
587
|
const getDefaultSourceIdExpr = (): string | undefined => {
|
|
540
588
|
// 如果有 associationName,说明是关系资源弹窗,默认需要 sourceId
|
|
541
|
-
if (
|
|
589
|
+
if (templateAssociationName) {
|
|
542
590
|
return `{{ ctx.resource.sourceId }}`;
|
|
543
591
|
}
|
|
544
592
|
try {
|
|
@@ -585,9 +633,9 @@ async function handleSavePopupAsTemplate(model: FlowModel, _t: (k: string, opt?:
|
|
|
585
633
|
targetUid: values.targetUid,
|
|
586
634
|
useModel: model.use,
|
|
587
635
|
type: 'popup',
|
|
588
|
-
dataSourceKey:
|
|
589
|
-
collectionName:
|
|
590
|
-
associationName:
|
|
636
|
+
dataSourceKey: templateDataSourceKey,
|
|
637
|
+
collectionName: templateCollectionName,
|
|
638
|
+
associationName: templateAssociationName,
|
|
591
639
|
filterByTk: templateFilterByTk,
|
|
592
640
|
sourceId: templateSourceId,
|
|
593
641
|
};
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
ensureBlockScopedEngine,
|
|
26
26
|
ReferenceScopedRenderer,
|
|
27
27
|
renderReferenceTargetPlaceholder,
|
|
28
|
+
ensureScopedEngineView,
|
|
28
29
|
unlinkScopedEngine,
|
|
29
30
|
} from './referenceShared';
|
|
30
31
|
|
|
@@ -133,6 +134,9 @@ export class ReferenceBlockModel extends BlockModel {
|
|
|
133
134
|
|
|
134
135
|
private _ensureScopedEngine(): FlowEngine {
|
|
135
136
|
this._scopedEngine = ensureBlockScopedEngine(this.flowEngine, this._scopedEngine);
|
|
137
|
+
// 引用区块会在 scoped engine 中 loadModel,目标模型的 onInit 可能会读取 ctx.view。
|
|
138
|
+
// 部分场景(如审批配置)view 仅存在于宿主模型上下文而非 engine.context,需要显式桥接。
|
|
139
|
+
ensureScopedEngineView(this._scopedEngine, this.context as any);
|
|
136
140
|
return this._scopedEngine;
|
|
137
141
|
}
|
|
138
142
|
|
|
@@ -9,21 +9,22 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import _ from 'lodash';
|
|
12
|
-
import { type CreateModelOptions, FlowEngine, FlowModel } from '@nocobase/flow-engine';
|
|
13
|
-
import { FormGridModel } from '@nocobase/client';
|
|
12
|
+
import { type CreateModelOptions, FlowContext, FlowEngine, FlowModel } from '@nocobase/flow-engine';
|
|
14
13
|
import { REF_HOST_CTX_KEY } from '../constants';
|
|
15
14
|
import { NAMESPACE } from '../locale';
|
|
16
15
|
import {
|
|
17
16
|
ensureBlockScopedEngine,
|
|
18
17
|
ReferenceScopedRenderer,
|
|
19
18
|
renderReferenceTargetPlaceholder,
|
|
19
|
+
ensureScopedEngineView,
|
|
20
20
|
unlinkScopedEngine,
|
|
21
21
|
} from './referenceShared';
|
|
22
22
|
|
|
23
23
|
const SETTINGS_FLOW_KEY = 'referenceSettings';
|
|
24
24
|
const SETTINGS_STEP_KEY = 'useTemplate';
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/** 标记已添加 host context bridge,避免重复添加 */
|
|
27
|
+
const BRIDGE_MARKER = Symbol.for('nocobase.refGridHostBridge');
|
|
27
28
|
|
|
28
29
|
export type ReferenceFormGridTargetSettings = {
|
|
29
30
|
/** 模板 uid(flowModelTemplates.uid) */
|
|
@@ -36,25 +37,58 @@ export type ReferenceFormGridTargetSettings = {
|
|
|
36
37
|
targetPath?: string;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
|
-
type ReferenceHostInfo = {
|
|
40
|
-
hostUid?: string;
|
|
41
|
-
hostUse?: string;
|
|
42
|
-
ref: {
|
|
43
|
-
templateUid: string;
|
|
44
|
-
templateName?: string;
|
|
45
|
-
targetUid: string;
|
|
46
|
-
targetPath: string;
|
|
47
|
-
mountSubKey: 'grid';
|
|
48
|
-
mode: 'reference';
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
|
|
52
40
|
export class ReferenceFormGridModel extends FlowModel {
|
|
53
41
|
private _scopedEngine?: FlowEngine;
|
|
42
|
+
private _targetRoot?: FlowModel;
|
|
54
43
|
private _targetGrid?: FlowModel;
|
|
55
44
|
private _resolvedTargetUid?: string;
|
|
56
45
|
private _invalidTargetUid?: string;
|
|
57
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 字段模板场景下,模板内 FormItemModel/CollectionFieldModel 的 onInit 会调用 ctx.blockModel.addAppends(fieldPath)。
|
|
49
|
+
* 但模板 root 自身也是一个 CollectionBlockModel,会在未桥接宿主上下文时被识别为 blockModel,导致 appends 写到模板 root 的 resource,
|
|
50
|
+
* 从而宿主表单(如 ApplyFormModel)在刷新记录时缺少关联 appends(例如 users.roles)。
|
|
51
|
+
*
|
|
52
|
+
* 这里在目标 grid 解析完成后扫描字段路径,并把需要的 appends 同步到宿主 block(master + forks),确保关系数据可展示。
|
|
53
|
+
*/
|
|
54
|
+
private _syncHostResourceAppends(host: FlowModel, targetGrid: FlowModel) {
|
|
55
|
+
if (!host['addAppends']) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const candidates = new Set<string>();
|
|
59
|
+
const addCandidate = (val: unknown) => {
|
|
60
|
+
const s = typeof val === 'string' ? val.trim() : '';
|
|
61
|
+
if (!s) return;
|
|
62
|
+
candidates.add(s);
|
|
63
|
+
const top = s.split('.')[0]?.trim();
|
|
64
|
+
if (top) candidates.add(top);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const visit = (m: FlowModel) => {
|
|
68
|
+
const init = m.getStepParams?.('fieldSettings', 'init') as
|
|
69
|
+
| { fieldPath?: string; associationPathName?: string }
|
|
70
|
+
| undefined;
|
|
71
|
+
if (init) {
|
|
72
|
+
addCandidate(init.fieldPath);
|
|
73
|
+
addCandidate(init.associationPathName);
|
|
74
|
+
}
|
|
75
|
+
const subs = m.subModels || {};
|
|
76
|
+
for (const v of Object.values(subs)) {
|
|
77
|
+
if (Array.isArray(v)) {
|
|
78
|
+
v.forEach((c) => c instanceof FlowModel && visit(c));
|
|
79
|
+
} else if (v instanceof FlowModel) {
|
|
80
|
+
visit(v);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
visit(targetGrid);
|
|
86
|
+
if (candidates.size === 0) return;
|
|
87
|
+
for (const fieldPath of candidates) {
|
|
88
|
+
(host as any).addAppends?.(fieldPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
58
92
|
constructor(options: any) {
|
|
59
93
|
super(options);
|
|
60
94
|
|
|
@@ -167,12 +201,100 @@ export class ReferenceFormGridModel extends FlowModel {
|
|
|
167
201
|
return this._targetGrid.setSubModel(subKey, options);
|
|
168
202
|
}
|
|
169
203
|
|
|
204
|
+
getStepParams(flowKey: string, stepKey: string): any | undefined;
|
|
205
|
+
getStepParams(flowKey: string): Record<string, any> | undefined;
|
|
206
|
+
getStepParams(): Record<string, any>;
|
|
207
|
+
getStepParams(flowKey?: string, stepKey?: string): any {
|
|
208
|
+
if (!flowKey || flowKey === SETTINGS_FLOW_KEY) {
|
|
209
|
+
return super.getStepParams(flowKey, stepKey);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!this._targetGrid) {
|
|
213
|
+
// 未解析完成:允许读取本地 stepParams,避免配置对话框/中间态丢值
|
|
214
|
+
return super.getStepParams(flowKey, stepKey);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (stepKey) {
|
|
218
|
+
const fromGrid = this._targetGrid.getStepParams(flowKey, stepKey);
|
|
219
|
+
if (typeof fromGrid !== 'undefined') return fromGrid;
|
|
220
|
+
return this._targetRoot?.getStepParams?.(flowKey, stepKey);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const gridFlow = this._targetGrid.getStepParams(flowKey) as any;
|
|
224
|
+
const rootFlow = this._targetRoot?.getStepParams?.(flowKey) as any;
|
|
225
|
+
if (rootFlow && typeof rootFlow === 'object') {
|
|
226
|
+
return { ...rootFlow, ...(gridFlow || {}) };
|
|
227
|
+
}
|
|
228
|
+
return gridFlow;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setStepParams(flowKey: string, stepKey: string, params: any): void;
|
|
232
|
+
setStepParams(flowKey: string, stepParams: Record<string, any>): void;
|
|
233
|
+
setStepParams(allParams: Record<string, any>): void;
|
|
234
|
+
setStepParams(flowKeyOrAllParams: any, stepKeyOrStepsParams?: any, params?: any): void {
|
|
235
|
+
if (typeof flowKeyOrAllParams === 'string') {
|
|
236
|
+
const flowKey = flowKeyOrAllParams;
|
|
237
|
+
if (flowKey === SETTINGS_FLOW_KEY || !this._targetGrid) {
|
|
238
|
+
super.setStepParams(flowKeyOrAllParams, stepKeyOrStepsParams, params);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (typeof stepKeyOrStepsParams === 'string' && params !== undefined) {
|
|
242
|
+
this._targetGrid.setStepParams(flowKey, stepKeyOrStepsParams, params);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (typeof stepKeyOrStepsParams === 'object' && stepKeyOrStepsParams !== null) {
|
|
246
|
+
this._targetGrid.setStepParams(flowKey, stepKeyOrStepsParams);
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (typeof flowKeyOrAllParams === 'object' && flowKeyOrAllParams !== null) {
|
|
252
|
+
const allParams = flowKeyOrAllParams as Record<string, any>;
|
|
253
|
+
const localAll: Record<string, any> = {};
|
|
254
|
+
const delegatedAll: Record<string, any> = {};
|
|
255
|
+
for (const [fk, steps] of Object.entries(allParams)) {
|
|
256
|
+
if (fk === SETTINGS_FLOW_KEY || !this._targetGrid) {
|
|
257
|
+
localAll[fk] = steps;
|
|
258
|
+
} else {
|
|
259
|
+
delegatedAll[fk] = steps;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (Object.keys(localAll).length > 0) {
|
|
263
|
+
super.setStepParams(localAll);
|
|
264
|
+
}
|
|
265
|
+
if (Object.keys(delegatedAll).length > 0 && this._targetGrid) {
|
|
266
|
+
this._targetGrid.setStepParams(delegatedAll);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async saveStepParams() {
|
|
272
|
+
// 如果目标尚未解析,先触发解析
|
|
273
|
+
if (!this._targetGrid) {
|
|
274
|
+
await this.dispatchEvent('beforeRender');
|
|
275
|
+
}
|
|
276
|
+
// 将本地非 settings 参数刷新到目标 grid
|
|
277
|
+
if (this._targetGrid && this.stepParams) {
|
|
278
|
+
for (const [flowKey, steps] of Object.entries(this.stepParams)) {
|
|
279
|
+
if (flowKey === SETTINGS_FLOW_KEY || typeof steps !== 'object' || steps === null) continue;
|
|
280
|
+
this._targetGrid.setStepParams(flowKey, steps as Record<string, any>);
|
|
281
|
+
delete (this.stepParams as Record<string, any>)[flowKey];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const res = await super.saveStepParams();
|
|
285
|
+
if (this._targetGrid) {
|
|
286
|
+
await this._targetGrid.saveStepParams();
|
|
287
|
+
}
|
|
288
|
+
return res;
|
|
289
|
+
}
|
|
290
|
+
|
|
170
291
|
public async onDispatchEventStart(eventName: string): Promise<void> {
|
|
171
292
|
if (eventName !== 'beforeRender') return;
|
|
172
293
|
|
|
173
294
|
const settings = this._getTargetSettings();
|
|
174
295
|
if (!settings) {
|
|
175
296
|
this._syncHostExtraTitle(undefined);
|
|
297
|
+
this._targetRoot = undefined;
|
|
176
298
|
this._targetGrid = undefined;
|
|
177
299
|
this._resolvedTargetUid = undefined;
|
|
178
300
|
this._invalidTargetUid = undefined;
|
|
@@ -195,6 +317,8 @@ export class ReferenceFormGridModel extends FlowModel {
|
|
|
195
317
|
}
|
|
196
318
|
|
|
197
319
|
const engine = this._ensureScopedEngine();
|
|
320
|
+
const host = this.parent as FlowModel | undefined;
|
|
321
|
+
ensureScopedEngineView(engine, (host?.context as any) || (this.context as any));
|
|
198
322
|
const targetUid = settings.targetUid;
|
|
199
323
|
const prevTargetGrid = this._targetGrid;
|
|
200
324
|
const prevResolvedTargetUid = this._resolvedTargetUid;
|
|
@@ -205,12 +329,12 @@ export class ReferenceFormGridModel extends FlowModel {
|
|
|
205
329
|
// 在“模板引用”切换的中间态(例如模型树刚替换、上下文尚未稳定)下,
|
|
206
330
|
// 可能出现首次解析不到目标(短暂返回 null/undefined)。这里做一次轻量重试,
|
|
207
331
|
// 避免界面闪现 “Target block is invalid” 占位。
|
|
208
|
-
const tryResolveTargetGrid = async (): Promise<FlowModel | undefined> => {
|
|
332
|
+
const tryResolveTargetGrid = async (): Promise<{ root: FlowModel; grid: FlowModel } | undefined> => {
|
|
209
333
|
const root = await engine.loadModel<FlowModel>({ uid: targetUid });
|
|
210
334
|
if (!root) return undefined;
|
|
211
335
|
|
|
212
|
-
|
|
213
|
-
const hostInfo
|
|
336
|
+
root.setParent(host);
|
|
337
|
+
const hostInfo = {
|
|
214
338
|
hostUid: host?.uid,
|
|
215
339
|
hostUse: host?.use,
|
|
216
340
|
ref: {
|
|
@@ -224,23 +348,43 @@ export class ReferenceFormGridModel extends FlowModel {
|
|
|
224
348
|
};
|
|
225
349
|
root.context.defineProperty(REF_HOST_CTX_KEY, { value: hostInfo });
|
|
226
350
|
|
|
227
|
-
const fragment =
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
351
|
+
const fragment = root.subModels?.grid;
|
|
352
|
+
let gridModel: FlowModel | undefined;
|
|
353
|
+
if (fragment instanceof FlowModel) {
|
|
354
|
+
gridModel = fragment;
|
|
355
|
+
}
|
|
356
|
+
// 将宿主区块上下文注入到被引用的 grid:
|
|
357
|
+
// - Details 区块字段渲染依赖 ctx.record/resource/blockModel 等(定义在宿主 block context 上);
|
|
358
|
+
// - 但同时要保留 scoped engine(ctx.engine)指向,避免丢失实例/缓存隔离。
|
|
359
|
+
// 注意:使用 Symbol 标记避免重复添加 delegate(beforeRender 可能多次触发)
|
|
360
|
+
const contextWithMarker = gridModel?.context as (FlowContext & { [BRIDGE_MARKER]?: boolean }) | undefined;
|
|
361
|
+
if (gridModel && host?.context && !contextWithMarker?.[BRIDGE_MARKER]) {
|
|
362
|
+
const bridge = new FlowContext();
|
|
363
|
+
bridge.defineProperty('engine', { value: engine });
|
|
364
|
+
bridge.addDelegate(host.context);
|
|
365
|
+
gridModel.context.addDelegate(bridge);
|
|
366
|
+
(gridModel.context as FlowContext & { [BRIDGE_MARKER]?: boolean })[BRIDGE_MARKER] = true;
|
|
367
|
+
}
|
|
368
|
+
if (!gridModel) return undefined;
|
|
369
|
+
// 同步模板字段需要的关联 appends 到宿主 block 的 resource(避免 users.roles 等关系字段为空)
|
|
370
|
+
if (host) {
|
|
371
|
+
this._syncHostResourceAppends(host, gridModel);
|
|
372
|
+
}
|
|
373
|
+
return { root, grid: gridModel };
|
|
231
374
|
};
|
|
232
375
|
|
|
233
|
-
let
|
|
234
|
-
if (!
|
|
376
|
+
let resolved = await tryResolveTargetGrid();
|
|
377
|
+
if (!resolved) {
|
|
235
378
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
236
379
|
const latest = this._getTargetSettings();
|
|
237
380
|
if (latest?.targetUid !== targetUid) {
|
|
238
381
|
return;
|
|
239
382
|
}
|
|
240
|
-
|
|
383
|
+
resolved = await tryResolveTargetGrid();
|
|
241
384
|
}
|
|
242
385
|
|
|
243
|
-
if (!
|
|
386
|
+
if (!resolved) {
|
|
387
|
+
this._targetRoot = undefined;
|
|
244
388
|
this._targetGrid = undefined;
|
|
245
389
|
this._resolvedTargetUid = undefined;
|
|
246
390
|
this._invalidTargetUid = targetUid;
|
|
@@ -250,10 +394,12 @@ export class ReferenceFormGridModel extends FlowModel {
|
|
|
250
394
|
return;
|
|
251
395
|
}
|
|
252
396
|
|
|
253
|
-
this.
|
|
397
|
+
this._targetRoot = resolved.root;
|
|
398
|
+
this._targetGrid = resolved.grid;
|
|
254
399
|
this._resolvedTargetUid = targetUid;
|
|
255
400
|
this._invalidTargetUid = undefined;
|
|
256
|
-
|
|
401
|
+
|
|
402
|
+
if (prevTargetGrid !== resolved.grid || prevResolvedTargetUid !== targetUid || prevInvalidTargetUid) {
|
|
257
403
|
this.rerender();
|
|
258
404
|
}
|
|
259
405
|
}
|