@nocobase/flow-engine 2.1.0-beta.9 → 2.1.1
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/lib/FlowContextProvider.d.ts +5 -1
- package/lib/FlowContextProvider.js +9 -2
- 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 +607 -19
- 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 +152 -42
- 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/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 +12 -1
- package/lib/components/subModel/LazyDropdown.js +301 -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 +2 -1
- package/lib/components/subModel/utils.js +15 -5
- 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 +269 -7
- package/lib/executor/FlowExecutor.js +6 -3
- 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 +9 -1
- package/lib/flowContext.js +77 -6
- package/lib/flowEngine.d.ts +136 -4
- package/lib/flowEngine.js +429 -51
- 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 +126 -34
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
- package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
- package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/base.js +464 -29
- package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
- package/lib/runjs-context/contexts/elementDoc.js +152 -0
- package/lib/runjs-context/setup.js +1 -0
- 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/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/loadedPageCache.d.ts +24 -0
- package/lib/utils/loadedPageCache.js +139 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
- package/lib/utils/parsePathnameToViewParams.js +28 -4
- 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 +12 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +12 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +304 -144
- package/package.json +5 -4
- package/src/FlowContextProvider.tsx +9 -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 +105 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
- 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 +21 -0
- package/src/__tests__/runjsContextImplementations.test.ts +9 -2
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsLocales.test.ts +6 -5
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +136 -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 +472 -5
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +750 -17
- 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 +178 -48
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +344 -8
- 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 +16 -2
- package/src/components/subModel/LazyDropdown.tsx +341 -56
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +524 -38
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +13 -2
- 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 +69 -2
- package/src/data-source/index.ts +332 -8
- package/src/executor/FlowExecutor.ts +6 -3
- package/src/executor/__tests__/flowExecutor.test.ts +57 -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 +85 -6
- package/src/flowEngine.ts +484 -45
- 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__/flowEngine.resolveUse.test.ts +0 -15
- package/src/models/__tests__/flowModel.test.ts +65 -37
- package/src/models/flowModel.tsx +184 -65
- 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/contexts/FormJSFieldItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
- package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
- package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/base.ts +467 -31
- package/src/runjs-context/contexts/elementDoc.ts +130 -0
- package/src/runjs-context/setup.ts +1 -0
- 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/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +21 -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/loadedPageCache.ts +147 -0
- package/src/utils/parsePathnameToViewParams.ts +45 -5
- 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 +12 -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 +13 -3
- package/src/views/useDrawer.tsx +13 -3
- package/src/views/usePage.tsx +367 -180
|
@@ -0,0 +1,147 @@
|
|
|
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 { FlowModel } from '../models';
|
|
11
|
+
|
|
12
|
+
type LoadedPageOptions = {
|
|
13
|
+
parentId?: string;
|
|
14
|
+
subKey?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type DirtyKeyOptions = {
|
|
18
|
+
force?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type FlowSettingsContextLike = {
|
|
22
|
+
flowSettingsEnabled?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type FlowEngineLike = {
|
|
26
|
+
context?: FlowSettingsContextLike;
|
|
27
|
+
previousEngine?: FlowEngineLike;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getLoadedPageKey = (options?: LoadedPageOptions): string | undefined => {
|
|
31
|
+
const parentId = options?.parentId;
|
|
32
|
+
const subKey = options?.subKey;
|
|
33
|
+
if (!parentId || subKey !== 'page') {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return `${parentId}::${subKey}`;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getLoadedPageKeyFromModel = (model?: FlowModel | null): string | undefined => {
|
|
40
|
+
let current = model;
|
|
41
|
+
while (current) {
|
|
42
|
+
if (current.subKey === 'page' && current.parent?.uid) {
|
|
43
|
+
return getLoadedPageKey({ parentId: current.parent.uid, subKey: current.subKey });
|
|
44
|
+
}
|
|
45
|
+
current = current.parent as FlowModel | undefined;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const isFlowSettingsEnabledForContext = (context?: FlowSettingsContextLike): boolean => {
|
|
51
|
+
try {
|
|
52
|
+
return !!context?.flowSettingsEnabled;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const isFlowSettingsEnabledForModel = (model?: FlowModel | null): boolean => {
|
|
59
|
+
if (isFlowSettingsEnabledForContext(model?.context as FlowSettingsContextLike | undefined)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const visited = new Set<FlowEngineLike>();
|
|
64
|
+
let engine = model?.flowEngine as FlowEngineLike | undefined;
|
|
65
|
+
while (engine && !visited.has(engine)) {
|
|
66
|
+
visited.add(engine);
|
|
67
|
+
if (isFlowSettingsEnabledForContext(engine.context)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
engine = engine.previousEngine;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const removeLoadedModelTree = (model?: FlowModel | null): void => {
|
|
76
|
+
if (!model?.uid || !model.flowEngine) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (model.flowEngine.getModel(model.uid) === model) {
|
|
80
|
+
model.flowEngine.removeModelWithSubModels(model.uid);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const mountLoadedModelToParent = <T extends FlowModel = FlowModel>(model: T | null, forceReplace = false): T | null => {
|
|
85
|
+
if (!model?.parent || !model.subKey) {
|
|
86
|
+
return model;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const mounted = (model.parent.subModels as any)?.[model.subKey];
|
|
90
|
+
const existing =
|
|
91
|
+
forceReplace && model.subType !== 'array' && mounted && !Array.isArray(mounted)
|
|
92
|
+
? (mounted as FlowModel)
|
|
93
|
+
: model.parent.findSubModel(model.subKey, (m) => m.uid === model.uid);
|
|
94
|
+
if (existing) {
|
|
95
|
+
if (!forceReplace || existing === model) {
|
|
96
|
+
return model;
|
|
97
|
+
}
|
|
98
|
+
removeLoadedModelTree(existing);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (model.subType === 'array') {
|
|
102
|
+
model.parent.addSubModel(model.subKey, model);
|
|
103
|
+
} else {
|
|
104
|
+
model.parent.setSubModel(model.subKey, model);
|
|
105
|
+
}
|
|
106
|
+
return model;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const createLoadedPageCache = () => {
|
|
110
|
+
const dirtyKeys = new Set<string>();
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
getDirtyKeyForModel(model?: FlowModel | null, options?: DirtyKeyOptions): string | undefined {
|
|
114
|
+
if (!options?.force && !isFlowSettingsEnabledForModel(model)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return getLoadedPageKeyFromModel(model);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
markDirty(key?: string): void {
|
|
121
|
+
if (key) {
|
|
122
|
+
dirtyKeys.add(key);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
shouldBypass(options?: LoadedPageOptions, isFlowSettingsEnabled?: () => boolean): boolean {
|
|
127
|
+
const key = getLoadedPageKey(options);
|
|
128
|
+
if (!key || !dirtyKeys.has(key)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return !isFlowSettingsEnabled?.();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
clear(options?: LoadedPageOptions): void {
|
|
139
|
+
const key = getLoadedPageKey(options);
|
|
140
|
+
if (key) {
|
|
141
|
+
dirtyKeys.delete(key);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
mountModelToParent: mountLoadedModelToParent,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
@@ -18,6 +18,35 @@ export interface ViewParam {
|
|
|
18
18
|
sourceId?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export interface ParsePathnameToViewParamsOptions {
|
|
22
|
+
rootPrefix?: string;
|
|
23
|
+
basePath?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const normalizePathname = (pathname: string) => {
|
|
27
|
+
if (!pathname || pathname === '/') {
|
|
28
|
+
return '/';
|
|
29
|
+
}
|
|
30
|
+
return `/${pathname.replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const normalizeBasePath = (basePath: string) => `/${basePath.replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
34
|
+
|
|
35
|
+
const stripBasePath = (pathname: string, basePath: string) => {
|
|
36
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
37
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
38
|
+
|
|
39
|
+
if (normalizedPathname === normalizedBasePath) {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (normalizedPathname.startsWith(`${normalizedBasePath}/`)) {
|
|
44
|
+
return normalizedPathname.slice(normalizedBasePath.length + 1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return '';
|
|
48
|
+
};
|
|
49
|
+
|
|
21
50
|
/**
|
|
22
51
|
* 解析路径名为视图参数数组
|
|
23
52
|
*
|
|
@@ -33,15 +62,21 @@ export interface ViewParam {
|
|
|
33
62
|
* parsePathnameToViewParams('/admin/xxx/view/yyy') // [{ viewUid: 'xxx' }, { viewUid: 'yyy' }]
|
|
34
63
|
* ```
|
|
35
64
|
*/
|
|
36
|
-
export const parsePathnameToViewParams = (
|
|
65
|
+
export const parsePathnameToViewParams = (
|
|
66
|
+
pathname: string,
|
|
67
|
+
options: ParsePathnameToViewParamsOptions = {},
|
|
68
|
+
): ViewParam[] => {
|
|
37
69
|
if (!pathname || pathname === '/') {
|
|
38
70
|
return [];
|
|
39
71
|
}
|
|
40
72
|
|
|
73
|
+
const rootPrefix = options.rootPrefix || 'admin';
|
|
74
|
+
const relativePath = options.basePath ? stripBasePath(pathname, options.basePath) : '';
|
|
75
|
+
|
|
41
76
|
// 移除开头的斜杠并分割路径
|
|
42
|
-
const segments = pathname.replace(/^\/+/, '').split('/').filter(Boolean);
|
|
77
|
+
const segments = (options.basePath ? relativePath : pathname).replace(/^\/+/, '').split('/').filter(Boolean);
|
|
43
78
|
|
|
44
|
-
if (segments.length < 2) {
|
|
79
|
+
if (segments.length < (options.basePath ? 1 : 2)) {
|
|
45
80
|
return [];
|
|
46
81
|
}
|
|
47
82
|
|
|
@@ -49,11 +84,16 @@ export const parsePathnameToViewParams = (pathname: string): ViewParam[] => {
|
|
|
49
84
|
let currentView: ViewParam | null = null;
|
|
50
85
|
let i = 0;
|
|
51
86
|
|
|
87
|
+
if (options.basePath) {
|
|
88
|
+
currentView = { viewUid: segments[0] };
|
|
89
|
+
i = 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
52
92
|
while (i < segments.length) {
|
|
53
93
|
const segment = segments[i];
|
|
54
94
|
|
|
55
|
-
//
|
|
56
|
-
if (segment ===
|
|
95
|
+
// 处理布局根前缀或 view 关键字
|
|
96
|
+
if (segment === rootPrefix || segment === 'view') {
|
|
57
97
|
// 如果有当前视图,先保存到结果中
|
|
58
98
|
if (currentView) {
|
|
59
99
|
result.push(currentView);
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
const CHARSET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate a random base36 identifier with an optional semantic prefix.
|
|
14
|
+
*
|
|
15
|
+
* Equivalent in shape to v1's `uid()` from `@formily/shared` (11 chars
|
|
16
|
+
* of `[0-9a-z]`), with an opt-in prefix appended at the front. v2 forbids
|
|
17
|
+
* direct `@formily/*` imports in `src/client-v2/`, so this helper is the
|
|
18
|
+
* single substitute the rest of the codebase should reach for.
|
|
19
|
+
*
|
|
20
|
+
* Common semantic prefixes observed across the codebase — pass the one
|
|
21
|
+
* that matches your domain rather than relying on a default, so the
|
|
22
|
+
* intent is explicit at the call site:
|
|
23
|
+
*
|
|
24
|
+
* - `s_` — service / settings record (authenticators, channels, …)
|
|
25
|
+
* - `v_` — verifier / variable / LLM service
|
|
26
|
+
* - `f_` — field
|
|
27
|
+
* - `t_` — through table
|
|
28
|
+
*
|
|
29
|
+
* Example:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { randomId } from '@nocobase/flow-engine';
|
|
33
|
+
*
|
|
34
|
+
* name: randomId('s_'), // → 's_keeoaui1ubi'
|
|
35
|
+
* name: randomId('v_'), // → 'v_a8f3kp2x9qm'
|
|
36
|
+
* name: randomId(), // → 'a8f3kp2x9qm'
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Not cryptographically secure — uses `Math.random()`. Good enough for
|
|
40
|
+
* unique form names / schema keys, NOT for security tokens.
|
|
41
|
+
*/
|
|
42
|
+
export function randomId(prefix = '', length = 11): string {
|
|
43
|
+
let id = '';
|
|
44
|
+
for (let i = 0; i < length; i++) {
|
|
45
|
+
id += CHARSET[(Math.random() * CHARSET.length) | 0];
|
|
46
|
+
}
|
|
47
|
+
return `${prefix}${id}`;
|
|
48
|
+
}
|
|
@@ -553,8 +553,8 @@ function extractUsedCtxLibKeys(code: string): string[] {
|
|
|
553
553
|
}
|
|
554
554
|
|
|
555
555
|
function injectEnsureLibsPreamble(code: string): string {
|
|
556
|
-
if (!CTX_LIBS_MARKER_RE.test(code)) return code;
|
|
557
556
|
if (ENSURE_LIBS_MARKER_RE.test(code)) return code;
|
|
557
|
+
if (!CTX_LIBS_MARKER_RE.test(code)) return code;
|
|
558
558
|
const keys = extractUsedCtxLibKeys(code);
|
|
559
559
|
if (!keys.length) return code;
|
|
560
560
|
return `/* __runjs_ensure_libs */\nawait ctx.__ensureLibs(${JSON.stringify(keys)});\n${code}`;
|
package/src/utils/runjsValue.ts
CHANGED
|
@@ -236,6 +236,37 @@ function normalizeSubPath(raw: string): { subPath: string; wildcard: boolean } {
|
|
|
236
236
|
return { subPath: s, wildcard: false };
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
function extractCtxRootUsage(expr: string): { varName: string; subPath: string; wildcard: boolean } | null {
|
|
240
|
+
const raw = String(expr || '').trim();
|
|
241
|
+
if (!raw || raw === 'ctx') return null;
|
|
242
|
+
|
|
243
|
+
const dotMatch = raw.match(/^ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*)([\s\S]*)$/);
|
|
244
|
+
if (dotMatch) {
|
|
245
|
+
const varName = dotMatch[1] || '';
|
|
246
|
+
const rest = dotMatch[2] || '';
|
|
247
|
+
const normalized = normalizeSubPath(rest);
|
|
248
|
+
return {
|
|
249
|
+
varName,
|
|
250
|
+
subPath: normalized.subPath,
|
|
251
|
+
wildcard: normalized.wildcard,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const bracketMatch = raw.match(/^ctx\s*\[\s*(['"])([a-zA-Z_$][a-zA-Z0-9_$]*)\1\s*\]([\s\S]*)$/);
|
|
256
|
+
if (bracketMatch) {
|
|
257
|
+
const varName = bracketMatch[2] || '';
|
|
258
|
+
const rest = bracketMatch[3] || '';
|
|
259
|
+
const normalized = normalizeSubPath(rest);
|
|
260
|
+
return {
|
|
261
|
+
varName,
|
|
262
|
+
subPath: normalized.subPath,
|
|
263
|
+
wildcard: normalized.wildcard,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
239
270
|
/**
|
|
240
271
|
* Heuristic extraction of ctx variable usage from RunJS code.
|
|
241
272
|
*
|
|
@@ -256,27 +287,35 @@ export function extractUsedVariablePathsFromRunJS(code: string): Record<string,
|
|
|
256
287
|
usage.set(varName, set);
|
|
257
288
|
};
|
|
258
289
|
|
|
290
|
+
const addCtxUsage = (expr: string) => {
|
|
291
|
+
const hit = extractCtxRootUsage(expr);
|
|
292
|
+
if (!hit?.varName) return;
|
|
293
|
+
add(hit.varName, hit.wildcard ? '' : hit.subPath);
|
|
294
|
+
};
|
|
295
|
+
|
|
259
296
|
// dot form: ctx.foo.bar / ctx.foo[0].bar (excluding ctx.method(...))
|
|
260
297
|
const dotRe = /ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*(?:(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)|(?:\[[^\]]+\]))*)(?!\s*\()/g;
|
|
261
298
|
let match: RegExpExecArray | null;
|
|
262
299
|
while ((match = dotRe.exec(src))) {
|
|
263
|
-
|
|
264
|
-
const firstKeyMatch = pathAfterCtx.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/);
|
|
265
|
-
if (!firstKeyMatch) continue;
|
|
266
|
-
const firstKey = firstKeyMatch[1];
|
|
267
|
-
const rest = pathAfterCtx.slice(firstKey.length);
|
|
268
|
-
const { subPath, wildcard } = normalizeSubPath(rest);
|
|
269
|
-
add(firstKey, wildcard ? '' : subPath);
|
|
300
|
+
addCtxUsage(`ctx.${match[1] || ''}`);
|
|
270
301
|
}
|
|
271
302
|
|
|
272
303
|
// bracket root: ctx['foo'].bar / ctx["foo"][0] (excluding ctx['method'](...))
|
|
273
304
|
const bracketRootRe =
|
|
274
305
|
/ctx\s*\[\s*(['"])([a-zA-Z_$][a-zA-Z0-9_$]*)\1\s*\]((?:(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)|(?:\[[^\]]+\]))*)(?!\s*\()/g;
|
|
275
306
|
while ((match = bracketRootRe.exec(srcWithStrings))) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
307
|
+
addCtxUsage(`ctx['${match[2] || ''}']${match[3] || ''}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// async-safe helper form: await ctx.getVar('ctx.foo.bar')
|
|
311
|
+
const getVarRe = /ctx\.getVar\s*\(\s*(['"])((?:\\.|(?!\1)[\s\S])*)\1\s*\)/g;
|
|
312
|
+
while ((match = getVarRe.exec(srcWithStrings))) {
|
|
313
|
+
const expr = String(match[2] || '')
|
|
314
|
+
.replace(/\\'/g, "'")
|
|
315
|
+
.replace(/\\"/g, '"')
|
|
316
|
+
.trim();
|
|
317
|
+
if (!expr.startsWith('ctx')) continue;
|
|
318
|
+
addCtxUsage(expr);
|
|
280
319
|
}
|
|
281
320
|
|
|
282
321
|
const out: Record<string, string[]> = {};
|
|
@@ -11,7 +11,7 @@ import type { ISchema } from '@formily/json-schema';
|
|
|
11
11
|
import { Schema } from '@formily/json-schema';
|
|
12
12
|
import type { FlowModel } from '../models';
|
|
13
13
|
import { FlowRuntimeContext } from '../flowContext';
|
|
14
|
-
import type { StepDefinition, StepUIMode } from '../types';
|
|
14
|
+
import type { EventDefinition, StepDefinition, StepUIMode } from '../types';
|
|
15
15
|
import { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -242,6 +242,35 @@ export async function resolveStepUiSchema<TModel extends FlowModel = FlowModel>(
|
|
|
242
242
|
return resolvedStepUiSchema;
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* 判断事件在设置菜单中是否应被隐藏。
|
|
247
|
+
* - 支持 EventDefinition.hideInSettings。
|
|
248
|
+
* - hideInSettings 可为布尔值或函数(接收 FlowRuntimeContext)。
|
|
249
|
+
*/
|
|
250
|
+
export async function shouldHideEventInSettings<TModel extends FlowModel = FlowModel>(
|
|
251
|
+
model: TModel,
|
|
252
|
+
flow: any,
|
|
253
|
+
event: EventDefinition<TModel> | undefined,
|
|
254
|
+
): Promise<boolean> {
|
|
255
|
+
if (!event) return true;
|
|
256
|
+
|
|
257
|
+
const { hideInSettings } = event;
|
|
258
|
+
|
|
259
|
+
if (typeof hideInSettings === 'function') {
|
|
260
|
+
try {
|
|
261
|
+
const ctx = new FlowRuntimeContext(model, flow.key, 'settings');
|
|
262
|
+
setupRuntimeContextSteps(ctx, flow.steps || {}, model, flow.key);
|
|
263
|
+
const result = await hideInSettings(ctx as any);
|
|
264
|
+
return !!result;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.warn(`Error evaluating hideInSettings for event '${event.name || ''}' in flow '${flow.key}':`, error);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return !!hideInSettings;
|
|
272
|
+
}
|
|
273
|
+
|
|
245
274
|
/**
|
|
246
275
|
* 判断步骤在设置菜单中是否应被隐藏。
|
|
247
276
|
* - 支持 StepDefinition.hideInSettings 与 ActionDefinition.hideInSettings(step 优先)。
|
package/src/views/FlowView.tsx
CHANGED
|
@@ -11,13 +11,23 @@ import { PopoverProps as AntdPopoverProps } from 'antd';
|
|
|
11
11
|
import { FlowContext } from '../flowContext';
|
|
12
12
|
import { ViewNavigation } from './ViewNavigation';
|
|
13
13
|
|
|
14
|
+
export type FlowViewBeforeClosePayload = {
|
|
15
|
+
result?: any;
|
|
16
|
+
force?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type FlowViewBeforeCloseHandler = (
|
|
20
|
+
payload: FlowViewBeforeClosePayload,
|
|
21
|
+
) => Promise<boolean | void> | boolean | void;
|
|
22
|
+
|
|
14
23
|
export type FlowView = {
|
|
15
24
|
type: 'drawer' | 'popover' | 'dialog' | 'embed';
|
|
16
25
|
inputArgs: any;
|
|
17
26
|
Header: React.FC<{ title?: React.ReactNode; extra?: React.ReactNode }> | null;
|
|
18
27
|
Footer: React.FC<{ children?: React.ReactNode }> | null;
|
|
19
|
-
close: (result?: any, force?: boolean) => void;
|
|
28
|
+
close: (result?: any, force?: boolean) => Promise<boolean | void> | boolean | void;
|
|
20
29
|
update: (newConfig: any) => void;
|
|
30
|
+
beforeClose?: FlowViewBeforeCloseHandler;
|
|
21
31
|
navigation?: ViewNavigation;
|
|
22
32
|
/** 页面的销毁方法 */
|
|
23
33
|
destroy?: () => void;
|
|
@@ -74,11 +84,21 @@ export class FlowViewer {
|
|
|
74
84
|
if (this.types[type]) {
|
|
75
85
|
zIndex += 1;
|
|
76
86
|
const onClose = others.onClose;
|
|
87
|
+
let zIndexReleased = false;
|
|
88
|
+
const releaseZIndex = () => {
|
|
89
|
+
if (!zIndexReleased) {
|
|
90
|
+
zIndexReleased = true;
|
|
91
|
+
zIndex -= 1;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
77
94
|
const _zIndex = others.zIndex;
|
|
78
95
|
others.onClose = (...args) => {
|
|
79
96
|
onClose?.(...args);
|
|
80
|
-
|
|
97
|
+
releaseZIndex();
|
|
81
98
|
};
|
|
99
|
+
if (type === 'embed') {
|
|
100
|
+
others.onOpenCancelled = releaseZIndex;
|
|
101
|
+
}
|
|
82
102
|
// embed 不能设置过高的 zIndex,会遮挡菜单的折叠按钮图表
|
|
83
103
|
if (type !== 'embed') {
|
|
84
104
|
others.zIndex = _zIndex ?? this.getNextZIndex();
|
|
@@ -24,6 +24,7 @@ export const PageComponent = forwardRef((props: any, ref) => {
|
|
|
24
24
|
title: _title,
|
|
25
25
|
styles = {},
|
|
26
26
|
zIndex = 4, // 这个默认值是为了防止表格的阴影显示到子页面上面
|
|
27
|
+
onClose,
|
|
27
28
|
} = mergedProps;
|
|
28
29
|
const closedRef = useRef(false);
|
|
29
30
|
const flowEngine = useFlowEngine();
|
|
@@ -86,10 +87,12 @@ export const PageComponent = forwardRef((props: any, ref) => {
|
|
|
86
87
|
type="text"
|
|
87
88
|
size="small"
|
|
88
89
|
icon={<CloseOutlined />}
|
|
89
|
-
onClick={() => {
|
|
90
|
+
onClick={async () => {
|
|
90
91
|
if (!closedRef.current) {
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
const closed = await onClose?.();
|
|
93
|
+
if (closed !== false) {
|
|
94
|
+
closedRef.current = true;
|
|
95
|
+
}
|
|
93
96
|
}
|
|
94
97
|
}}
|
|
95
98
|
style={{
|
|
@@ -111,7 +114,7 @@ export const PageComponent = forwardRef((props: any, ref) => {
|
|
|
111
114
|
{extra && <div>{extra}</div>}
|
|
112
115
|
</div>
|
|
113
116
|
);
|
|
114
|
-
}, [header, _title, flowEngine.context.themeToken, styles.header,
|
|
117
|
+
}, [header, _title, flowEngine.context.themeToken, styles.header, onClose]);
|
|
115
118
|
|
|
116
119
|
// Footer 组件
|
|
117
120
|
const FooterComponent = useMemo(() => {
|
|
@@ -13,6 +13,16 @@ import { ViewParam as SharedViewParam } from '../utils';
|
|
|
13
13
|
|
|
14
14
|
type ViewParams = Omit<SharedViewParam, 'viewUid'> & { viewUid?: string };
|
|
15
15
|
|
|
16
|
+
export interface GeneratePathnameFromViewParamsOptions {
|
|
17
|
+
prefix?: string;
|
|
18
|
+
basePath?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ViewNavigationOptions {
|
|
22
|
+
basePath?: string;
|
|
23
|
+
layoutBasePath?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
function encodeFilterByTk(val: SharedViewParam['filterByTk']): string {
|
|
17
27
|
if (val === undefined || val === null) return '';
|
|
18
28
|
// 1.x 兼容:对象按 key1=v1&key2=v2 拼接后整体 encodeURIComponent
|
|
@@ -26,6 +36,15 @@ function encodeFilterByTk(val: SharedViewParam['filterByTk']): string {
|
|
|
26
36
|
return encodeURIComponent(String(val));
|
|
27
37
|
}
|
|
28
38
|
|
|
39
|
+
function hasUsableSourceId(sourceId: unknown): sourceId is string | number {
|
|
40
|
+
return sourceId !== undefined && sourceId !== null && String(sourceId) !== '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeBasePath(basePath?: string) {
|
|
44
|
+
const value = basePath || '/admin';
|
|
45
|
+
return `/${value.replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
/**
|
|
30
49
|
* 将 ViewParam 数组转换为 pathname
|
|
31
50
|
*
|
|
@@ -39,12 +58,17 @@ function encodeFilterByTk(val: SharedViewParam['filterByTk']): string {
|
|
|
39
58
|
* generatePathnameFromViewParams([{ viewUid: 'xxx' }, { viewUid: 'yyy' }]) // '/admin/xxx/view/yyy'
|
|
40
59
|
* ```
|
|
41
60
|
*/
|
|
42
|
-
export function generatePathnameFromViewParams(
|
|
61
|
+
export function generatePathnameFromViewParams(
|
|
62
|
+
viewParams: ViewParams[],
|
|
63
|
+
options: GeneratePathnameFromViewParamsOptions = {},
|
|
64
|
+
): string {
|
|
65
|
+
const basePath = normalizeBasePath(options.basePath || options.prefix);
|
|
66
|
+
|
|
43
67
|
if (!viewParams || viewParams.length === 0) {
|
|
44
|
-
return
|
|
68
|
+
return basePath;
|
|
45
69
|
}
|
|
46
70
|
|
|
47
|
-
const segments =
|
|
71
|
+
const segments = basePath.replace(/^\/+/, '').split('/').filter(Boolean);
|
|
48
72
|
|
|
49
73
|
viewParams.forEach((viewParam, index) => {
|
|
50
74
|
// 如果不是第一个视图,添加 'view' 关键字
|
|
@@ -65,8 +89,8 @@ export function generatePathnameFromViewParams(viewParams: ViewParams[]): string
|
|
|
65
89
|
segments.push('filterbytk', encoded);
|
|
66
90
|
}
|
|
67
91
|
}
|
|
68
|
-
if (viewParam.sourceId) {
|
|
69
|
-
segments.push('sourceid', viewParam.sourceId);
|
|
92
|
+
if (hasUsableSourceId(viewParam.sourceId)) {
|
|
93
|
+
segments.push('sourceid', String(viewParam.sourceId));
|
|
70
94
|
}
|
|
71
95
|
});
|
|
72
96
|
|
|
@@ -77,10 +101,12 @@ export class ViewNavigation {
|
|
|
77
101
|
viewStack: ReadonlyArray<ViewParams>; // 只能通过 setViewStack 修改
|
|
78
102
|
ctx: FlowEngineContext;
|
|
79
103
|
viewParams: ViewParams;
|
|
104
|
+
private readonly basePath?: string;
|
|
80
105
|
|
|
81
|
-
constructor(ctx: FlowEngineContext, viewParams: ViewParams[]) {
|
|
106
|
+
constructor(ctx: FlowEngineContext, viewParams: ViewParams[], options: ViewNavigationOptions = {}) {
|
|
82
107
|
this.setViewStack(viewParams);
|
|
83
108
|
this.ctx = ctx;
|
|
109
|
+
this.basePath = options.basePath || options.layoutBasePath;
|
|
84
110
|
|
|
85
111
|
define(this, {
|
|
86
112
|
viewParams: observable,
|
|
@@ -102,7 +128,7 @@ export class ViewNavigation {
|
|
|
102
128
|
});
|
|
103
129
|
|
|
104
130
|
// 2. 根据 viewStack 生成新的 pathname
|
|
105
|
-
const newPathname = generatePathnameFromViewParams(newViewStack);
|
|
131
|
+
const newPathname = generatePathnameFromViewParams(newViewStack, { basePath: this.getLayoutBasePath() });
|
|
106
132
|
|
|
107
133
|
// 3. 触发一次跳转。使用 replace 的方式
|
|
108
134
|
this.ctx.router.navigate(newPathname, { replace: true });
|
|
@@ -111,7 +137,9 @@ export class ViewNavigation {
|
|
|
111
137
|
navigateTo(viewParam: ViewParams, opts?: { replace?: boolean; state?: any }) {
|
|
112
138
|
// 1. 基于当前 viewStack 生成一个 pathname
|
|
113
139
|
// 2. 将当前传入的参数转为 path string
|
|
114
|
-
const newViewPathname = generatePathnameFromViewParams([...this.viewStack, viewParam]
|
|
140
|
+
const newViewPathname = generatePathnameFromViewParams([...this.viewStack, viewParam], {
|
|
141
|
+
basePath: this.getLayoutBasePath(),
|
|
142
|
+
});
|
|
115
143
|
|
|
116
144
|
// 3. 与 pathname 拼接成新的 pathname(这里直接使用新生成的 pathname)
|
|
117
145
|
const newPathname = newViewPathname;
|
|
@@ -122,7 +150,16 @@ export class ViewNavigation {
|
|
|
122
150
|
|
|
123
151
|
back() {
|
|
124
152
|
const prevStack = this.viewStack.slice(0, -1);
|
|
125
|
-
const prevPath = generatePathnameFromViewParams(prevStack);
|
|
153
|
+
const prevPath = generatePathnameFromViewParams(prevStack, { basePath: this.getLayoutBasePath() });
|
|
126
154
|
this.ctx.router.navigate(prevPath, { replace: true });
|
|
127
155
|
}
|
|
156
|
+
|
|
157
|
+
private getLayoutBasePath() {
|
|
158
|
+
const routePath = (this.ctx as any).layout?.routePath;
|
|
159
|
+
return (
|
|
160
|
+
this.basePath ||
|
|
161
|
+
(this.ctx as any).layoutRoute?.basePathname ||
|
|
162
|
+
(routePath?.startsWith('/') ? routePath : '/admin')
|
|
163
|
+
);
|
|
164
|
+
}
|
|
128
165
|
}
|