@nocobase/flow-engine 2.1.0-alpha.4 → 2.1.0-alpha.45
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 +201 -661
- package/README.md +79 -10
- package/lib/FlowContextProvider.d.ts +5 -1
- package/lib/FlowContextProvider.js +9 -2
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/FormItem.d.ts +6 -0
- package/lib/components/FormItem.js +11 -3
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +613 -21
- package/lib/components/dnd/index.d.ts +31 -2
- package/lib/components/dnd/index.js +244 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -11
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/LazyDropdown.js +293 -52
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +9 -3
- package/lib/components/variables/VariableHybridInput.d.ts +27 -0
- package/lib/components/variables/VariableHybridInput.js +499 -0
- package/lib/components/variables/index.d.ts +2 -0
- package/lib/components/variables/index.js +3 -0
- package/lib/data-source/index.d.ts +84 -0
- package/lib/data-source/index.js +259 -5
- package/lib/executor/FlowExecutor.js +32 -9
- package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
- package/lib/flow-registry/index.d.ts +1 -0
- package/lib/flow-registry/index.js +3 -1
- package/lib/flowContext.d.ts +3 -0
- package/lib/flowContext.js +46 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +392 -18
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/index.d.ts +2 -0
- package/lib/index.js +7 -0
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +13 -10
- package/lib/models/flowModel.js +81 -21
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +50 -2
- package/lib/types.js +1 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +3 -2
- package/lib/utils/index.js +7 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
- package/lib/utils/parsePathnameToViewParams.js +29 -5
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/FlowView.js +11 -1
- package/lib/views/PageComponent.js +8 -6
- package/lib/views/ViewNavigation.d.ts +12 -2
- package/lib/views/ViewNavigation.js +28 -9
- package/lib/views/createViewMeta.js +114 -50
- package/lib/views/inheritLayoutContext.d.ts +10 -0
- package/lib/views/inheritLayoutContext.js +50 -0
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +22 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +22 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +304 -144
- package/package.json +6 -5
- package/src/FlowContextProvider.tsx +9 -1
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/createViewMeta.popup.test.ts +115 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +82 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/FormItem.tsx +7 -1
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/FormItem.test.tsx +25 -0
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +558 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +758 -19
- package/src/components/dnd/index.tsx +305 -28
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +99 -11
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +194 -5
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/LazyDropdown.tsx +332 -56
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +522 -37
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +7 -1
- package/src/components/variables/VariableHybridInput.tsx +531 -0
- package/src/components/variables/index.ts +2 -0
- package/src/data-source/__tests__/collection.test.ts +41 -2
- package/src/data-source/__tests__/index.test.ts +68 -1
- package/src/data-source/index.ts +322 -6
- package/src/executor/FlowExecutor.ts +35 -10
- package/src/executor/__tests__/flowExecutor.test.ts +85 -0
- package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
- package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
- package/src/flow-registry/index.ts +1 -0
- package/src/flowContext.ts +50 -3
- package/src/flowEngine.ts +449 -14
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/index.ts +2 -0
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
- package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
- package/src/models/__tests__/flowModel.test.ts +80 -37
- package/src/models/flowModel.tsx +122 -36
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +28 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +5 -1
- package/src/utils/parsePathnameToViewParams.ts +47 -7
- package/src/utils/randomId.ts +48 -0
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +22 -2
- package/src/views/PageComponent.tsx +7 -4
- package/src/views/ViewNavigation.ts +46 -9
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/ViewNavigation.test.ts +52 -0
- package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/createViewMeta.ts +106 -34
- package/src/views/inheritLayoutContext.ts +26 -0
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +27 -3
- package/src/views/useDrawer.tsx +27 -3
- package/src/views/usePage.tsx +367 -179
|
@@ -15,6 +15,82 @@ import type { FlowView } from './FlowView';
|
|
|
15
15
|
|
|
16
16
|
type PopupModelLike = { getStepParams?: (a: string, b: string) => any } | undefined;
|
|
17
17
|
|
|
18
|
+
function isDefined(value: any) {
|
|
19
|
+
return value !== undefined && value !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isSameViewParamValue(left: any, right: any) {
|
|
23
|
+
if (left === right) return true;
|
|
24
|
+
if (!isDefined(left) || !isDefined(right)) return false;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
28
|
+
} catch (_) {
|
|
29
|
+
return String(left) === String(right);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getViewStack(view?: FlowView): any[] {
|
|
34
|
+
const stack = view?.navigation?.viewStack;
|
|
35
|
+
return Array.isArray(stack) ? stack : [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getAnchoredViewStackIndex(view?: FlowView, stack = getViewStack(view)): number {
|
|
39
|
+
if (!stack.length) return -1;
|
|
40
|
+
|
|
41
|
+
const args = view?.inputArgs || {};
|
|
42
|
+
const navParams = view?.navigation?.viewParams || {};
|
|
43
|
+
const viewUid = args.viewUid ?? navParams.viewUid;
|
|
44
|
+
|
|
45
|
+
if (!viewUid) {
|
|
46
|
+
return stack.length - 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const candidates = stack.map((item, index) => ({ item, index })).filter(({ item }) => item?.viewUid === viewUid);
|
|
50
|
+
|
|
51
|
+
if (!candidates.length) {
|
|
52
|
+
return stack.length - 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const keys = ['filterByTk', 'sourceId', 'tabUid'];
|
|
56
|
+
let bestIndex = candidates[candidates.length - 1].index;
|
|
57
|
+
let bestScore = -1;
|
|
58
|
+
|
|
59
|
+
for (const { item, index } of candidates) {
|
|
60
|
+
let score = 0;
|
|
61
|
+
let matched = true;
|
|
62
|
+
|
|
63
|
+
for (const key of keys) {
|
|
64
|
+
if (!isDefined(args[key])) continue;
|
|
65
|
+
if (!isSameViewParamValue(item?.[key], args[key])) {
|
|
66
|
+
matched = false;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
score += 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!matched) continue;
|
|
73
|
+
if (score >= bestScore) {
|
|
74
|
+
bestIndex = index;
|
|
75
|
+
bestScore = score;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return bestIndex;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getPopupView(ctx: FlowContext, anchorView?: FlowView) {
|
|
83
|
+
return anchorView ?? ctx.view;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isPopupView(view?: FlowView): boolean {
|
|
87
|
+
if (!view) return false;
|
|
88
|
+
const stack = getViewStack(view);
|
|
89
|
+
const openerUids = view?.inputArgs?.openerUids;
|
|
90
|
+
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
91
|
+
return getAnchoredViewStackIndex(view, stack) >= 1 || hasOpener;
|
|
92
|
+
}
|
|
93
|
+
|
|
18
94
|
// 判断是否为普通对象(Plain Object),避免对类实例/代理等进行深度遍历
|
|
19
95
|
function isPlainObject(val: any) {
|
|
20
96
|
if (val === null || typeof val !== 'object') return false;
|
|
@@ -79,19 +155,11 @@ function makeMetaFromValue(value: any, title?: string, seen?: WeakSet<any>): any
|
|
|
79
155
|
export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): PropertyMetaFactory {
|
|
80
156
|
const t = (k: string) => ctx.t(k);
|
|
81
157
|
|
|
82
|
-
const
|
|
83
|
-
if (!view) return false;
|
|
84
|
-
const stack = Array.isArray(view.navigation?.viewStack) ? view.navigation.viewStack : [];
|
|
85
|
-
const openerUids = view?.inputArgs?.openerUids;
|
|
86
|
-
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
87
|
-
return stack.length >= 2 || hasOpener;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const hasPopupNow = (): boolean => isPopupView(anchorView ?? ctx.view);
|
|
158
|
+
const hasPopupNow = (flowCtx: FlowContext = ctx): boolean => isPopupView(getPopupView(flowCtx, anchorView));
|
|
91
159
|
|
|
92
160
|
// 统一解析锚定视图下的 RecordRef,避免在设置弹窗等二级视图中被误导
|
|
93
161
|
const resolveRecordRef = async (flowCtx: FlowContext): Promise<RecordRef | undefined> => {
|
|
94
|
-
const view = anchorView
|
|
162
|
+
const view = getPopupView(flowCtx, anchorView);
|
|
95
163
|
if (!view || !isPopupView(view)) return undefined;
|
|
96
164
|
|
|
97
165
|
const base = await buildPopupRuntime(flowCtx, view);
|
|
@@ -119,11 +187,12 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
119
187
|
const getParentRecordRef = async (level: number, flowCtx?: FlowContext): Promise<RecordRef | undefined> => {
|
|
120
188
|
try {
|
|
121
189
|
const useCtx = flowCtx || ctx;
|
|
122
|
-
const
|
|
123
|
-
const stack =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
190
|
+
const view = getPopupView(useCtx, anchorView);
|
|
191
|
+
const stack = getViewStack(view);
|
|
192
|
+
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
193
|
+
if (currentIndex < 1 || level < 1) return undefined;
|
|
194
|
+
const idx = currentIndex - level;
|
|
195
|
+
if (idx < 1) return undefined;
|
|
127
196
|
const parent = stack[idx];
|
|
128
197
|
if (!parent?.viewUid) return undefined;
|
|
129
198
|
|
|
@@ -156,9 +225,10 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
156
225
|
|
|
157
226
|
const hasParentNow = (level: number): boolean => {
|
|
158
227
|
try {
|
|
159
|
-
const
|
|
160
|
-
const stack =
|
|
161
|
-
|
|
228
|
+
const view = getPopupView(ctx, anchorView);
|
|
229
|
+
const stack = getViewStack(view);
|
|
230
|
+
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
231
|
+
return currentIndex - level >= 1; // level=1 需要至少一个上级弹窗
|
|
162
232
|
} catch (_) {
|
|
163
233
|
return false;
|
|
164
234
|
}
|
|
@@ -231,9 +301,10 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
231
301
|
disabled: () => !hasPopupNow(),
|
|
232
302
|
hidden: () => !hasPopupNow(),
|
|
233
303
|
buildVariablesParams: async (c) => {
|
|
234
|
-
if (!hasPopupNow()) return undefined;
|
|
304
|
+
if (!hasPopupNow(c)) return undefined;
|
|
235
305
|
const ref = await resolveRecordRef(c);
|
|
236
|
-
const
|
|
306
|
+
const view = getPopupView(c, anchorView);
|
|
307
|
+
const inputArgs = view?.inputArgs;
|
|
237
308
|
type PopupVariableParams = {
|
|
238
309
|
record?: RecordRef;
|
|
239
310
|
sourceRecord?: RecordRef;
|
|
@@ -253,9 +324,9 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
253
324
|
|
|
254
325
|
// 构建 parent 链(用于服务端解析 ctx.popup.parent[.parent...].record.*)
|
|
255
326
|
try {
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
if (
|
|
327
|
+
const stack = getViewStack(view);
|
|
328
|
+
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
329
|
+
if (currentIndex >= 2) {
|
|
259
330
|
let cur: Record<string, any> = params;
|
|
260
331
|
let level = 1;
|
|
261
332
|
let parentRef = await getParentRecordRef(level, c);
|
|
@@ -315,20 +386,21 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
315
386
|
}
|
|
316
387
|
// 当 view.inputArgs 带有 sourceId + associationName 时,提供“上级记录”变量(基于 sourceId 推断)
|
|
317
388
|
try {
|
|
318
|
-
const
|
|
389
|
+
const view = getPopupView(ctx, anchorView);
|
|
390
|
+
const inputArgs = view?.inputArgs;
|
|
319
391
|
const srcId = inputArgs?.sourceId;
|
|
320
392
|
let assoc: string | undefined = inputArgs?.associationName;
|
|
321
393
|
let dsKey: string = inputArgs?.dataSourceKey || 'main';
|
|
322
394
|
|
|
323
395
|
// 兜底:若 associationName 缺失或不含“.”,尝试从当前视图模型的 openView 参数推断
|
|
324
396
|
if (!assoc || typeof assoc !== 'string' || !assoc.includes('.')) {
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
const
|
|
328
|
-
if (
|
|
329
|
-
let model = ctx?.engine?.getModel(
|
|
397
|
+
const stack = getViewStack(view);
|
|
398
|
+
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
399
|
+
const current = currentIndex >= 0 ? stack?.[currentIndex] : undefined;
|
|
400
|
+
if (current?.viewUid) {
|
|
401
|
+
let model = ctx?.engine?.getModel(current.viewUid, true) as PopupModelLike;
|
|
330
402
|
if (!model) {
|
|
331
|
-
model = (await ctx.engine.loadModel({ uid:
|
|
403
|
+
model = (await ctx.engine.loadModel({ uid: current.viewUid })) as PopupModelLike;
|
|
332
404
|
}
|
|
333
405
|
const p = model?.getStepParams?.('popupSettings', 'openView') || {};
|
|
334
406
|
assoc = p?.associationName || assoc;
|
|
@@ -406,12 +478,12 @@ interface PopupNode {
|
|
|
406
478
|
}
|
|
407
479
|
|
|
408
480
|
export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promise<PopupNode | undefined> {
|
|
409
|
-
const
|
|
410
|
-
const
|
|
481
|
+
const stack = getViewStack(view);
|
|
482
|
+
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
411
483
|
|
|
412
484
|
const openerUids = view?.inputArgs?.openerUids;
|
|
413
485
|
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
414
|
-
const hasStackPopup =
|
|
486
|
+
const hasStackPopup = currentIndex >= 1;
|
|
415
487
|
const isPopup = hasStackPopup || hasOpener;
|
|
416
488
|
if (!isPopup) return undefined;
|
|
417
489
|
|
|
@@ -457,7 +529,7 @@ export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promi
|
|
|
457
529
|
if (parentNode) node.parent = parentNode;
|
|
458
530
|
return node;
|
|
459
531
|
};
|
|
460
|
-
const currentNode = await buildNode(
|
|
532
|
+
const currentNode = await buildNode(currentIndex);
|
|
461
533
|
return currentNode;
|
|
462
534
|
}
|
|
463
535
|
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { FlowContext } from '../flowContext';
|
|
11
|
+
|
|
12
|
+
export function inheritLayoutContextForDetachedView(viewContext: FlowContext, sourceContext: FlowContext) {
|
|
13
|
+
const layoutContext = sourceContext?.layoutContext;
|
|
14
|
+
const engineContext = sourceContext?.engine?.context;
|
|
15
|
+
|
|
16
|
+
if (!(layoutContext instanceof FlowContext)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (layoutContext === sourceContext || layoutContext === engineContext) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// inheritContext=false 只隔离业务上下文,Layout 运行时上下文仍需要传给弹窗/嵌入视图。
|
|
25
|
+
viewContext.addDelegate(layoutContext);
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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 type { FlowView, FlowViewBeforeClosePayload } from './FlowView';
|
|
11
|
+
|
|
12
|
+
export async function runViewBeforeClose(view: FlowView, payload: FlowViewBeforeClosePayload): Promise<boolean> {
|
|
13
|
+
if (payload.force) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = await view.beforeClose?.(payload);
|
|
18
|
+
return result !== false;
|
|
19
|
+
}
|
package/src/views/useDialog.tsx
CHANGED
|
@@ -19,6 +19,8 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
23
|
+
import { inheritLayoutContextForDetachedView } from './inheritLayoutContext';
|
|
22
24
|
|
|
23
25
|
let uuid = 0;
|
|
24
26
|
|
|
@@ -87,14 +89,21 @@ export function useDialog() {
|
|
|
87
89
|
ctx.addDelegate(flowContext);
|
|
88
90
|
} else {
|
|
89
91
|
ctx.addDelegate(flowContext.engine.context);
|
|
92
|
+
inheritLayoutContextForDetachedView(ctx, flowContext);
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
// 幂等保护:防止 FlowPage 路由清理时二次调用 destroy
|
|
96
|
+
let destroyed = false;
|
|
97
|
+
|
|
92
98
|
// 构造 currentDialog 实例
|
|
93
99
|
const currentDialog = {
|
|
94
100
|
type: 'dialog' as const,
|
|
95
101
|
inputArgs: config.inputArgs || {},
|
|
96
102
|
preventClose: !!config.preventClose,
|
|
103
|
+
beforeClose: undefined,
|
|
97
104
|
destroy: (result?: any) => {
|
|
105
|
+
if (destroyed) return;
|
|
106
|
+
destroyed = true;
|
|
98
107
|
config.onClose?.();
|
|
99
108
|
dialogRef.current?.destroy();
|
|
100
109
|
closeFunc?.();
|
|
@@ -107,18 +116,24 @@ export function useDialog() {
|
|
|
107
116
|
scopedEngine.unlinkFromStack();
|
|
108
117
|
},
|
|
109
118
|
update: (newConfig) => dialogRef.current?.update(newConfig),
|
|
110
|
-
close: (result?: any, force?: boolean) => {
|
|
119
|
+
close: async (result?: any, force?: boolean) => {
|
|
111
120
|
if (config.preventClose && !force) {
|
|
112
|
-
return;
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const shouldClose = await runViewBeforeClose(currentDialog, { result, force });
|
|
125
|
+
if (!shouldClose) {
|
|
126
|
+
return false;
|
|
113
127
|
}
|
|
114
128
|
|
|
115
129
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
116
130
|
// 交由路由系统来销毁当前视图
|
|
117
131
|
config.inputArgs.navigation.back();
|
|
118
|
-
return;
|
|
132
|
+
return true;
|
|
119
133
|
}
|
|
120
134
|
|
|
121
135
|
currentDialog.destroy(result);
|
|
136
|
+
return true;
|
|
122
137
|
},
|
|
123
138
|
Footer: FooterComponent,
|
|
124
139
|
Header: HeaderComponent,
|
|
@@ -140,6 +155,15 @@ export function useDialog() {
|
|
|
140
155
|
get: () => currentDialog,
|
|
141
156
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
142
157
|
});
|
|
158
|
+
// 注册视图销毁回调,供外部(如 afterSuccess)通过引擎栈遍历来关闭多层弹窗。
|
|
159
|
+
// 对路由触发的弹窗:先 navigation.back() 清理 URL(replace 方式),再 destroy() 立即移除元素;
|
|
160
|
+
// 对非路由弹窗:直接 destroy()。destroy() 已做幂等保护,FlowPage 后续清理不会重复执行。
|
|
161
|
+
scopedEngine.setDestroyView(() => {
|
|
162
|
+
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
163
|
+
config.inputArgs.navigation.back();
|
|
164
|
+
}
|
|
165
|
+
currentDialog.destroy();
|
|
166
|
+
});
|
|
143
167
|
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
|
144
168
|
registerPopupVariable(ctx, currentDialog);
|
|
145
169
|
// 内部组件,在 Provider 内部计算 content
|
package/src/views/useDrawer.tsx
CHANGED
|
@@ -19,6 +19,8 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
23
|
+
import { inheritLayoutContextForDetachedView } from './inheritLayoutContext';
|
|
22
24
|
|
|
23
25
|
export function useDrawer() {
|
|
24
26
|
const holderRef = React.useRef(null);
|
|
@@ -116,14 +118,21 @@ export function useDrawer() {
|
|
|
116
118
|
ctx.addDelegate(flowContext);
|
|
117
119
|
} else {
|
|
118
120
|
ctx.addDelegate(flowContext.engine.context);
|
|
121
|
+
inheritLayoutContextForDetachedView(ctx, flowContext);
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
// 幂等保护:防止 FlowPage 路由清理时二次调用 destroy
|
|
125
|
+
let destroyed = false;
|
|
126
|
+
|
|
121
127
|
// 构造 currentDrawer 实例
|
|
122
128
|
const currentDrawer = {
|
|
123
129
|
type: 'drawer' as const,
|
|
124
130
|
inputArgs: config.inputArgs || {},
|
|
125
131
|
preventClose: !!config.preventClose,
|
|
132
|
+
beforeClose: undefined,
|
|
126
133
|
destroy: (result?: any) => {
|
|
134
|
+
if (destroyed) return;
|
|
135
|
+
destroyed = true;
|
|
127
136
|
config.onClose?.();
|
|
128
137
|
drawerRef.current?.destroy();
|
|
129
138
|
closeFunc?.();
|
|
@@ -136,18 +145,24 @@ export function useDrawer() {
|
|
|
136
145
|
scopedEngine.unlinkFromStack();
|
|
137
146
|
},
|
|
138
147
|
update: (newConfig) => drawerRef.current?.update(newConfig),
|
|
139
|
-
close: (result?: any, force?: boolean) => {
|
|
148
|
+
close: async (result?: any, force?: boolean) => {
|
|
140
149
|
if (config.preventClose && !force) {
|
|
141
|
-
return;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const shouldClose = await runViewBeforeClose(currentDrawer, { result, force });
|
|
154
|
+
if (!shouldClose) {
|
|
155
|
+
return false;
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
145
159
|
// 交由路由系统来销毁当前视图
|
|
146
160
|
config.inputArgs.navigation.back();
|
|
147
|
-
return;
|
|
161
|
+
return true;
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
currentDrawer.destroy(result);
|
|
165
|
+
return true;
|
|
151
166
|
},
|
|
152
167
|
Footer: FooterComponent,
|
|
153
168
|
Header: HeaderComponent,
|
|
@@ -169,6 +184,15 @@ export function useDrawer() {
|
|
|
169
184
|
get: () => currentDrawer,
|
|
170
185
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
171
186
|
});
|
|
187
|
+
// 注册视图销毁回调,供外部(如 afterSuccess)通过引擎栈遍历来关闭多层弹窗。
|
|
188
|
+
// 对路由触发的弹窗:先 navigation.back() 清理 URL(replace 方式),再 destroy() 立即移除元素;
|
|
189
|
+
// 对非路由弹窗:直接 destroy()。destroy() 已做幂等保护,FlowPage 后续清理不会重复执行。
|
|
190
|
+
scopedEngine.setDestroyView(() => {
|
|
191
|
+
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
192
|
+
config.inputArgs.navigation.back();
|
|
193
|
+
}
|
|
194
|
+
currentDrawer.destroy();
|
|
195
|
+
});
|
|
172
196
|
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
|
173
197
|
registerPopupVariable(ctx, currentDrawer);
|
|
174
198
|
|