@nocobase/client-v2 2.1.0-beta.36 → 2.1.0-beta.38
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/es/Application.d.ts +1 -0
- package/es/BaseApplication.d.ts +4 -0
- package/es/RouterManager.d.ts +1 -0
- package/es/components/KeepAlive.d.ts +22 -0
- package/es/components/RouterBridge.d.ts +9 -0
- package/es/components/form/DialogFormLayout.d.ts +5 -29
- package/es/components/form/filter/CollectionFilter.d.ts +41 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +41 -0
- package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
- package/es/components/form/filter/FilterValueInput.d.ts +29 -0
- package/es/components/form/filter/index.d.ts +11 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
- package/es/data-source/index.d.ts +9 -0
- package/es/flow/FlowPage.d.ts +2 -1
- package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
- package/es/flow/admin-shell/admin-layout/index.d.ts +2 -0
- package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
- package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
- package/es/flow/components/FlowRoute.d.ts +10 -1
- package/es/flow/components/filter/index.d.ts +2 -0
- package/es/flow/components/filter/useFilterOptions.d.ts +54 -0
- package/es/flow/index.d.ts +4 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +2 -0
- package/es/index.mjs +491 -439
- package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
- package/es/layout-manager/LayoutManager.d.ts +22 -0
- package/es/layout-manager/LayoutRoute.d.ts +14 -0
- package/es/layout-manager/index.d.ts +13 -0
- package/es/layout-manager/types.d.ts +20 -0
- package/es/layout-manager/utils.d.ts +14 -0
- package/es/nocobase-buildin-plugin/index.d.ts +3 -10
- package/es/settings-center/index.d.ts +1 -1
- package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
- package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
- package/es/settings-center/plugin-manager/types.d.ts +34 -0
- package/lib/index.js +491 -439
- package/package.json +8 -7
- package/src/Application.tsx +27 -12
- package/src/BaseApplication.tsx +19 -0
- package/src/PluginSettingsManager.ts +1 -1
- package/src/RouterManager.tsx +17 -1
- package/src/__tests__/PluginSettingsManager.test.ts +41 -2
- package/src/__tests__/app.test.tsx +17 -1
- package/src/__tests__/globalDeps.test.ts +1 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
- package/src/__tests__/plugin-manager.test.tsx +177 -0
- package/src/__tests__/settings-center.test.tsx +24 -2
- package/src/components/KeepAlive.tsx +131 -0
- package/src/components/README.md +89 -6
- package/src/components/README.zh-CN.md +89 -7
- package/src/components/RouterBridge.tsx +28 -4
- package/src/components/__tests__/KeepAlive.test.tsx +63 -0
- package/src/components/__tests__/RouterBridge.test.tsx +27 -0
- package/src/components/form/DialogFormLayout.tsx +5 -29
- package/src/components/form/filter/CollectionFilter.tsx +101 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +176 -0
- package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
- package/src/components/form/filter/FilterValueInput.tsx +198 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +205 -0
- package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
- package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
- package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
- package/src/components/form/filter/index.ts +13 -0
- package/src/components/form/filter/useFilterActionProps.ts +200 -0
- package/src/components/form/index.tsx +1 -0
- package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
- package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
- package/src/data-source/index.ts +10 -0
- package/src/flow/FlowPage.tsx +35 -7
- package/src/flow/__tests__/FlowPage.test.tsx +79 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
- package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
- package/src/flow/actions/aclCheck.tsx +4 -0
- package/src/flow/actions/aclCheckRefresh.tsx +4 -0
- package/src/flow/actions/dateTimeFormat.tsx +12 -8
- package/src/flow/actions/linkageRules.tsx +122 -0
- package/src/flow/actions/openView.tsx +28 -4
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
- package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +4 -4
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
- package/src/flow/admin-shell/admin-layout/index.ts +2 -0
- package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
- package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
- package/src/flow/components/AdminLayout.tsx +4 -154
- package/src/flow/components/FlowRoute.tsx +105 -15
- package/src/flow/components/filter/index.ts +3 -0
- package/src/flow/components/filter/useFilterOptions.ts +80 -0
- package/src/flow/index.ts +4 -0
- package/src/flow/models/base/ActionModel.tsx +8 -1
- package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
- package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
- package/src/flow/models/base/RouteModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
- package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
- package/src/flow/models/blocks/form/submitValues.ts +4 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
- package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
- package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
- package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
- package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
- package/src/index.ts +2 -0
- package/src/layout-manager/LayoutContentRoute.tsx +90 -0
- package/src/layout-manager/LayoutManager.tsx +185 -0
- package/src/layout-manager/LayoutRoute.tsx +138 -0
- package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
- package/src/layout-manager/index.ts +14 -0
- package/src/layout-manager/types.ts +22 -0
- package/src/layout-manager/utils.ts +37 -0
- package/src/nocobase-buildin-plugin/index.tsx +69 -67
- package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
- package/src/settings-center/index.ts +1 -1
- package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
- package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
- package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
- package/src/settings-center/plugin-manager/index.tsx +254 -0
- package/src/settings-center/plugin-manager/types.ts +35 -0
- package/src/settings-center/utils.tsx +8 -1
- package/src/theme/__tests__/globalStyles.test.ts +24 -0
- package/src/theme/globalStyles.ts +10 -0
- package/src/utils/globalDeps.ts +2 -0
- package/src/settings-center/PluginManagerPage.tsx +0 -162
|
@@ -7,15 +7,18 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { useFlowEngine } from '@nocobase/flow-engine';
|
|
10
|
+
import { type FlowEngine, useFlowContext, useFlowEngine } from '@nocobase/flow-engine';
|
|
11
11
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
12
12
|
import { deviceType } from 'react-device-detect';
|
|
13
|
-
import { useAdminLayoutRoutePage } from '../admin-shell/useAdminLayoutRoutePage';
|
|
14
13
|
import { useParams } from 'react-router-dom';
|
|
15
14
|
import { useApp } from '../../hooks/useApp';
|
|
16
15
|
import { NocoBaseDesktopRouteType } from '../../flow-compat';
|
|
17
16
|
import { resolveAdminRouteRuntimeTarget } from '../admin-shell/admin-layout/resolveAdminRouteRuntimeTarget';
|
|
17
|
+
import { getAdminLayoutModel, type AdminLayoutModel } from '../admin-shell/admin-layout/AdminLayoutModel';
|
|
18
|
+
import { getLayoutModel, type BaseLayoutModel } from '../admin-shell/BaseLayoutModel';
|
|
19
|
+
import { useLayoutRoutePage } from '../admin-shell/useLayoutRoutePage';
|
|
18
20
|
import { AppNotFound } from '../../components';
|
|
21
|
+
import { useKeepAlive } from '../../components/KeepAlive';
|
|
19
22
|
|
|
20
23
|
type FlowRouteGuardState = {
|
|
21
24
|
pending: boolean;
|
|
@@ -23,8 +26,66 @@ type FlowRouteGuardState = {
|
|
|
23
26
|
notFound: boolean;
|
|
24
27
|
};
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
export type LegacyPageBehavior = 'redirect' | 'notFound' | 'bridge';
|
|
30
|
+
|
|
31
|
+
export type FlowRouteProps = {
|
|
32
|
+
pageUid?: string;
|
|
33
|
+
active?: boolean;
|
|
34
|
+
getLayoutModel?: (flowEngine: FlowEngine) => BaseLayoutModel | undefined;
|
|
35
|
+
legacyPageBehavior?: LegacyPageBehavior;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const getDefaultAdminLayoutModel = (flowEngine: FlowEngine) =>
|
|
39
|
+
getAdminLayoutModel<AdminLayoutModel>(flowEngine, { required: true });
|
|
40
|
+
|
|
41
|
+
const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: any) => {
|
|
42
|
+
const layout = contextLayout || flowEngine.context.layout;
|
|
43
|
+
|
|
44
|
+
if (layout?.uid) {
|
|
45
|
+
return getLayoutModel<BaseLayoutModel>(flowEngine, layout.uid, { required: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return getDefaultAdminLayoutModel(flowEngine);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getDefaultLegacyPageBehavior = (flowEngine: FlowEngine, contextLayout?: any): LegacyPageBehavior => {
|
|
52
|
+
const layout = contextLayout || flowEngine.context.layout;
|
|
53
|
+
|
|
54
|
+
if (layout?.routeName && layout.routeName !== 'admin') {
|
|
55
|
+
return 'notFound';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return 'redirect';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const hasFlowModel = async (flowEngine: FlowEngine, pageUid: string) => {
|
|
62
|
+
if (flowEngine.getModel(pageUid)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const modelData = await flowEngine.modelRepository?.findOne({ uid: pageUid }).catch(() => null);
|
|
67
|
+
if (modelData?.uid) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const model = await flowEngine.loadModel({ uid: pageUid }).catch(() => null);
|
|
72
|
+
if (model && flowEngine.getModel(pageUid) === model) {
|
|
73
|
+
flowEngine.removeModelWithSubModels(pageUid);
|
|
74
|
+
}
|
|
75
|
+
return !!model;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const BridgeFlowRoute = ({
|
|
79
|
+
pageUid,
|
|
80
|
+
active,
|
|
81
|
+
getLayoutModel,
|
|
82
|
+
}: {
|
|
83
|
+
pageUid: string;
|
|
84
|
+
active?: boolean;
|
|
85
|
+
getLayoutModel: (flowEngine: FlowEngine) => BaseLayoutModel | undefined;
|
|
86
|
+
}) => {
|
|
27
87
|
const flowEngine = useFlowEngine();
|
|
88
|
+
const { active: keepAliveActive } = useKeepAlive();
|
|
28
89
|
const routeRepository = flowEngine.context.routeRepository;
|
|
29
90
|
const refreshDesktopRoutes = React.useMemo(
|
|
30
91
|
() => routeRepository?.refreshAccessible.bind(routeRepository),
|
|
@@ -60,11 +121,13 @@ const BridgeFlowRoute = ({ pageUid }: { pageUid: string }) => {
|
|
|
60
121
|
});
|
|
61
122
|
}, [flowEngine]);
|
|
62
123
|
|
|
63
|
-
|
|
124
|
+
useLayoutRoutePage({
|
|
64
125
|
flowEngine,
|
|
65
126
|
pageUid,
|
|
127
|
+
active: active ?? keepAliveActive,
|
|
66
128
|
refreshDesktopRoutes,
|
|
67
129
|
layoutContentRef,
|
|
130
|
+
getLayoutModel,
|
|
68
131
|
});
|
|
69
132
|
|
|
70
133
|
return <div ref={layoutContentRef} />;
|
|
@@ -84,12 +147,20 @@ const BridgeFlowRoute = ({ pageUid }: { pageUid: string }) => {
|
|
|
84
147
|
* @returns {JSX.Element} 当前动态页面的布局挂载节点
|
|
85
148
|
* @throws {Error} 当缺少 `route.params.name` 时抛出异常
|
|
86
149
|
*/
|
|
87
|
-
const FlowRoute = () => {
|
|
150
|
+
const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
88
151
|
const flowEngine = useFlowEngine();
|
|
152
|
+
const flowContext = useFlowContext<any>();
|
|
153
|
+
const contextLayout = flowContext?.layout;
|
|
154
|
+
const getLayoutModel = useMemo(
|
|
155
|
+
() => props.getLayoutModel || ((engine: FlowEngine) => getDefaultLayoutModel(engine, contextLayout)),
|
|
156
|
+
[contextLayout, props.getLayoutModel],
|
|
157
|
+
);
|
|
158
|
+
const legacyPageBehavior = props.legacyPageBehavior || getDefaultLegacyPageBehavior(flowEngine, contextLayout);
|
|
89
159
|
const app = useApp();
|
|
90
160
|
const routeRepository = flowEngine.context.routeRepository;
|
|
91
161
|
const params = useParams();
|
|
92
|
-
const pageUid = params?.name;
|
|
162
|
+
const pageUid = props.pageUid || params?.name;
|
|
163
|
+
const skipRouteRepositoryCheck = !routeRepository;
|
|
93
164
|
const [guardState, setGuardState] = useState<FlowRouteGuardState>({
|
|
94
165
|
pending: true,
|
|
95
166
|
allowBridge: false,
|
|
@@ -99,7 +170,7 @@ const FlowRoute = () => {
|
|
|
99
170
|
const requestIdRef = useRef(0);
|
|
100
171
|
|
|
101
172
|
if (!pageUid) {
|
|
102
|
-
throw new Error('[NocoBase] FlowRoute requires route.params.name.');
|
|
173
|
+
throw new Error('[NocoBase] FlowRoute requires pageUid or route.params.name.');
|
|
103
174
|
}
|
|
104
175
|
|
|
105
176
|
useEffect(() => {
|
|
@@ -109,7 +180,7 @@ const FlowRoute = () => {
|
|
|
109
180
|
const run = async () => {
|
|
110
181
|
setGuardState({ pending: true, allowBridge: false, notFound: false });
|
|
111
182
|
|
|
112
|
-
if (!routeRepository?.isAccessibleLoaded?.()) {
|
|
183
|
+
if (!skipRouteRepositoryCheck && !routeRepository?.isAccessibleLoaded?.()) {
|
|
113
184
|
try {
|
|
114
185
|
await routeRepository?.ensureAccessibleLoaded?.();
|
|
115
186
|
} catch (_error) {
|
|
@@ -124,8 +195,26 @@ const FlowRoute = () => {
|
|
|
124
195
|
return;
|
|
125
196
|
}
|
|
126
197
|
|
|
127
|
-
const route = routeRepository?.getRouteBySchemaUid?.(pageUid);
|
|
198
|
+
const route = skipRouteRepositoryCheck ? undefined : routeRepository?.getRouteBySchemaUid?.(pageUid);
|
|
199
|
+
if (!route && legacyPageBehavior === 'notFound') {
|
|
200
|
+
const flowModelExists = await hasFlowModel(flowEngine, pageUid);
|
|
201
|
+
if (active && requestId === requestIdRef.current) {
|
|
202
|
+
setGuardState({ pending: false, allowBridge: flowModelExists, notFound: !flowModelExists });
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
128
207
|
if (route?.type === NocoBaseDesktopRouteType.page) {
|
|
208
|
+
if (legacyPageBehavior === 'notFound') {
|
|
209
|
+
setGuardState({ pending: false, allowBridge: false, notFound: true });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (legacyPageBehavior === 'bridge') {
|
|
214
|
+
setGuardState({ pending: false, allowBridge: true, notFound: false });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
129
218
|
const target = resolveAdminRouteRuntimeTarget({
|
|
130
219
|
app,
|
|
131
220
|
route,
|
|
@@ -161,22 +250,23 @@ const FlowRoute = () => {
|
|
|
161
250
|
return () => {
|
|
162
251
|
active = false;
|
|
163
252
|
};
|
|
164
|
-
}, [app, pageUid, routeRepository]);
|
|
253
|
+
}, [app, flowEngine, legacyPageBehavior, pageUid, routeRepository, skipRouteRepositoryCheck]);
|
|
165
254
|
|
|
166
255
|
const content = useMemo(() => {
|
|
167
256
|
if (guardState.pending) {
|
|
168
257
|
return null;
|
|
169
258
|
}
|
|
170
259
|
|
|
260
|
+
if (guardState.notFound) {
|
|
261
|
+
return <AppNotFound />;
|
|
262
|
+
}
|
|
263
|
+
|
|
171
264
|
if (!guardState.allowBridge) {
|
|
172
|
-
if (guardState.notFound) {
|
|
173
|
-
return <AppNotFound />;
|
|
174
|
-
}
|
|
175
265
|
return null;
|
|
176
266
|
}
|
|
177
267
|
|
|
178
|
-
return <BridgeFlowRoute pageUid={pageUid} />;
|
|
179
|
-
}, [guardState.allowBridge, guardState.notFound, guardState.pending, pageUid]);
|
|
268
|
+
return <BridgeFlowRoute pageUid={pageUid} active={props.active} getLayoutModel={getLayoutModel} />;
|
|
269
|
+
}, [getLayoutModel, guardState.allowBridge, guardState.notFound, guardState.pending, pageUid, props.active]);
|
|
180
270
|
|
|
181
271
|
return content;
|
|
182
272
|
};
|
|
@@ -15,3 +15,6 @@ export { VariableFilterItem } from './VariableFilterItem';
|
|
|
15
15
|
export type { VariableFilterItemProps, VariableFilterItemValue } from './VariableFilterItem';
|
|
16
16
|
export { LinkageFilterItem } from './LinkageFilterItem';
|
|
17
17
|
export type { LinkageFilterItemProps, LinkageFilterItemValue } from './LinkageFilterItem';
|
|
18
|
+
export { useFilterOptions } from './useFilterOptions';
|
|
19
|
+
export type { FilterOption, UseFilterOptionsArgs } from './useFilterOptions';
|
|
20
|
+
// Higher-level filter compositions (`CollectionFilterItem`, `useFilterActionProps`, `createCollectionFilterItem`) live under `src/components/form/filter/`. They compose these flow primitives on top of a `Collection` binding — the dependency direction is form/filter → flow/components/filter, never the reverse.
|
|
@@ -0,0 +1,80 @@
|
|
|
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 { Collection } from '@nocobase/flow-engine';
|
|
11
|
+
import { useMemo } from 'react';
|
|
12
|
+
import { fieldsToOptions } from './fieldsToOptions';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One operator entry on a `FilterOption`. Mirrors v1's interface-defined operator shape so the v2 filter value renderer can pick up the same per-operator value-side schema (e.g. datetime operators wanting the smart date picker, array/enum operators wanting a tag-mode Select).
|
|
16
|
+
*/
|
|
17
|
+
export type FilterOperator = {
|
|
18
|
+
value: string;
|
|
19
|
+
label: string;
|
|
20
|
+
/**
|
|
21
|
+
* Per-operator override for the value-side renderer. Wins over the field's own `uiSchema` when set. The `x-component` string is looked up against the v2 filter component registry.
|
|
22
|
+
*/
|
|
23
|
+
schema?: { 'x-component'?: string; 'x-component-props'?: Record<string, any> } & Record<string, any>;
|
|
24
|
+
/** Operator takes no right-hand value (e.g. `$empty`, `$notEmpty`). */
|
|
25
|
+
noValue?: boolean;
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Single field-tree node returned by `useFilterOptions`. */
|
|
30
|
+
export type FilterOption = {
|
|
31
|
+
name: string;
|
|
32
|
+
type?: string;
|
|
33
|
+
target?: string;
|
|
34
|
+
title: string;
|
|
35
|
+
schema?: Record<string, any>;
|
|
36
|
+
operators?: FilterOperator[];
|
|
37
|
+
children?: FilterOption[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface UseFilterOptionsArgs {
|
|
41
|
+
/**
|
|
42
|
+
* Whitelist of root-level field names to expose. Empty/undefined means "all filterable fields". The whitelist applies only at depth 1 — nested fields under an allowed association field are always included, matching the legacy v1 `Filter.Action` behaviour.
|
|
43
|
+
*/
|
|
44
|
+
filterableFieldNames?: string[];
|
|
45
|
+
/** Bypass the `filterableFieldNames` whitelist (mirrors v1 `noIgnore`). */
|
|
46
|
+
noIgnore?: boolean;
|
|
47
|
+
/** Translator used for field/operator labels. Defaults to identity. */
|
|
48
|
+
t?: (key: string) => string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const identity = (s: string) => s;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* v2 equivalent of v1's `useFilterOptions`/`useFilterFieldOptions`. Walks a `Collection`'s fields and returns the nested option tree consumed by antd `Cascader` in `CollectionFilterItem` (and any other v2 filter surface that wants the same field picker).
|
|
55
|
+
*
|
|
56
|
+
* Mirrors v1 in two ways that matter:
|
|
57
|
+
* - association fields (belongsTo / hasMany / m2m / etc.) are kept and recursed into via `fieldsToOptions`'s `nested` branch — so picking `user.username` is a first-class action, just like the legacy cascader.
|
|
58
|
+
* - the whitelist applies at depth 1 only, so capping the root field list (e.g. to `['lockedTs', 'unlockTs', 'user']`) doesn't accidentally hide the nested `user.username` / `user.nickname` leaves.
|
|
59
|
+
*/
|
|
60
|
+
export function useFilterOptions(collection: Collection | undefined, args: UseFilterOptionsArgs = {}): FilterOption[] {
|
|
61
|
+
const { filterableFieldNames, noIgnore = false, t = identity } = args;
|
|
62
|
+
|
|
63
|
+
const fields = useMemo(() => collection?.getFields() || [], [collection]);
|
|
64
|
+
|
|
65
|
+
const ignoreFieldsNames = useMemo(() => {
|
|
66
|
+
if (noIgnore || !filterableFieldNames?.length) return [];
|
|
67
|
+
return fields.map((f) => f.name).filter((n) => !filterableFieldNames.includes(n));
|
|
68
|
+
}, [fields, filterableFieldNames, noIgnore]);
|
|
69
|
+
|
|
70
|
+
return useMemo(
|
|
71
|
+
() =>
|
|
72
|
+
fieldsToOptions(
|
|
73
|
+
fields.filter((field) => field.target !== 'attachments' && field.interface !== 'formula'),
|
|
74
|
+
1,
|
|
75
|
+
ignoreFieldsNames,
|
|
76
|
+
t,
|
|
77
|
+
).filter(Boolean) as FilterOption[],
|
|
78
|
+
[fields, ignoreFieldsNames, t],
|
|
79
|
+
);
|
|
80
|
+
}
|
package/src/flow/index.ts
CHANGED
|
@@ -100,7 +100,11 @@ export * from './utils';
|
|
|
100
100
|
export * from './actions';
|
|
101
101
|
export * from './system-settings';
|
|
102
102
|
export * from './admin-shell/admin-layout';
|
|
103
|
+
export * from './admin-shell/BaseLayoutModel';
|
|
104
|
+
export * from './admin-shell/BaseLayoutRouteCoordinator';
|
|
103
105
|
export * from './admin-shell/AdminLayoutRouteCoordinator';
|
|
106
|
+
export * from './admin-shell/useLayoutRoutePage';
|
|
107
|
+
export * from './admin-shell/useAdminLayoutRoutePage';
|
|
104
108
|
export * from '../settings-center';
|
|
105
109
|
export { openViewFlow } from './flows/openViewFlow';
|
|
106
110
|
export { editMarkdownFlow } from './flows/editMarkdownFlow';
|
|
@@ -80,7 +80,14 @@ ActionModel.registerFlow({
|
|
|
80
80
|
};
|
|
81
81
|
},
|
|
82
82
|
defaultParams(ctx) {
|
|
83
|
-
|
|
83
|
+
const defaultProps = ctx.model.defaultProps || {};
|
|
84
|
+
if (!ctx.model.enableEditColor) {
|
|
85
|
+
return defaultProps;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...defaultProps,
|
|
89
|
+
color: ctx.model.props?.color ?? defaultProps.color ?? ctx.themeToken?.colorPrimary,
|
|
90
|
+
};
|
|
84
91
|
},
|
|
85
92
|
handler(ctx, params) {
|
|
86
93
|
const { title, tooltip, ...rest } = params;
|
|
@@ -40,6 +40,15 @@ type PageModelStructure = {
|
|
|
40
40
|
};
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
type CurrentRouteWithTabs = {
|
|
44
|
+
id?: string | number | null;
|
|
45
|
+
enableTabs?: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type PageModelContextWithRoute = {
|
|
49
|
+
currentRoute?: CurrentRouteWithTabs | null;
|
|
50
|
+
};
|
|
51
|
+
|
|
43
52
|
export class PageModel extends FlowModel<PageModelStructure> {
|
|
44
53
|
tabBarExtraContent: { left?: ReactNode; right?: ReactNode } = {};
|
|
45
54
|
private viewActivatedListener?: (_payload?: unknown) => void;
|
|
@@ -53,9 +62,15 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
|
|
53
62
|
* 根页面标签页开关以路由表为准,避免 flow model 里的旧配置覆盖路由管理设置。
|
|
54
63
|
*/
|
|
55
64
|
private getEnableTabs(): boolean {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
const currentRoute = (this.context as PageModelContextWithRoute).currentRoute;
|
|
66
|
+
const routeId = this.props.routeId;
|
|
67
|
+
if (
|
|
68
|
+
routeId != null &&
|
|
69
|
+
currentRoute?.id != null &&
|
|
70
|
+
String(currentRoute.id) === String(routeId) &&
|
|
71
|
+
typeof currentRoute.enableTabs === 'boolean'
|
|
72
|
+
) {
|
|
73
|
+
return currentRoute.enableTabs;
|
|
59
74
|
}
|
|
60
75
|
return !!this.props.enableTabs;
|
|
61
76
|
}
|
|
@@ -77,16 +92,27 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
|
|
77
92
|
if (this.unmounted) return;
|
|
78
93
|
// Only skip when explicitly inactive; treat "unknown" (undefined) as active for backward compatibility.
|
|
79
94
|
if (getPageActive(this.context) === false) return;
|
|
80
|
-
|
|
81
|
-
if (activeKey) {
|
|
82
|
-
this.invokeTabModelLifecycleMethod(activeKey, 'onActive', forceRefresh);
|
|
83
|
-
}
|
|
95
|
+
this.activateCurrentTab(forceRefresh);
|
|
84
96
|
})
|
|
85
97
|
.catch(() => {
|
|
86
98
|
// ignore
|
|
87
99
|
});
|
|
88
100
|
}
|
|
89
101
|
|
|
102
|
+
activateCurrentTab(forceRefresh = false) {
|
|
103
|
+
const activeKey = this.getActiveTabKey();
|
|
104
|
+
if (activeKey) {
|
|
105
|
+
this.invokeTabModelLifecycleMethod(activeKey, 'onActive', forceRefresh);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
deactivateCurrentTab() {
|
|
110
|
+
const activeKey = this.props.tabActiveKey || this.getFirstTab()?.uid;
|
|
111
|
+
if (activeKey) {
|
|
112
|
+
this.invokeTabModelLifecycleMethod(activeKey, 'onInactive');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
90
116
|
onMount(): void {
|
|
91
117
|
super.onMount();
|
|
92
118
|
this.unmounted = false;
|
|
@@ -98,10 +124,7 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
|
|
98
124
|
// We align this with the existing tab lifecycle by invoking `onActive` for the current tab blocks.
|
|
99
125
|
if (!this.viewActivatedListener) {
|
|
100
126
|
this.viewActivatedListener = (_payload?: unknown) => {
|
|
101
|
-
|
|
102
|
-
if (activeKey) {
|
|
103
|
-
this.invokeTabModelLifecycleMethod(activeKey, 'onActive');
|
|
104
|
-
}
|
|
127
|
+
this.activateCurrentTab();
|
|
105
128
|
};
|
|
106
129
|
this.flowEngine?.emitter?.on?.(VIEW_ACTIVATED_EVENT, this.viewActivatedListener);
|
|
107
130
|
}
|
|
@@ -113,10 +136,7 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
|
|
113
136
|
emitterActivatedVersion > 0 && emitterActivatedVersion !== this.lastSeenEmitterViewActivatedVersion;
|
|
114
137
|
this.lastSeenEmitterViewActivatedVersion = emitterActivatedVersion;
|
|
115
138
|
if (shouldCatchUp && getPageActive(this.context) !== false) {
|
|
116
|
-
|
|
117
|
-
if (activeKey) {
|
|
118
|
-
this.invokeTabModelLifecycleMethod(activeKey, 'onActive');
|
|
119
|
-
}
|
|
139
|
+
this.activateCurrentTab();
|
|
120
140
|
}
|
|
121
141
|
|
|
122
142
|
// When data is written within the same view, trigger an "active" lifecycle pass so blocks can refresh based on dirty.
|
|
@@ -141,11 +161,21 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
|
|
141
161
|
super.onUnmount();
|
|
142
162
|
}
|
|
143
163
|
|
|
144
|
-
invokeTabModelLifecycleMethod(
|
|
164
|
+
invokeTabModelLifecycleMethod(
|
|
165
|
+
tabActiveKey: string | undefined,
|
|
166
|
+
method: 'onActive' | 'onInactive',
|
|
167
|
+
forceRefresh = false,
|
|
168
|
+
) {
|
|
169
|
+
if (!tabActiveKey) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
145
173
|
if (method === 'onActive' && this.context?.pageInfo) {
|
|
146
174
|
this.context.pageInfo.version = 'v2';
|
|
147
175
|
}
|
|
148
|
-
const tabModel =
|
|
176
|
+
const tabModel =
|
|
177
|
+
this.findSubModel('tabs', (model) => model.uid === tabActiveKey) ||
|
|
178
|
+
(this.flowEngine.getModel(tabActiveKey) as BasePageTabModel | undefined);
|
|
149
179
|
|
|
150
180
|
if (tabModel) {
|
|
151
181
|
if (tabModel.context.tabActive) {
|
|
@@ -183,7 +213,10 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
|
|
183
213
|
const routePathname = this.flowEngine?.context?.route?.pathname;
|
|
184
214
|
// In route-managed multi-view mode, only the top view in URL should mutate document.title.
|
|
185
215
|
if (hasRouteNavigation && currentViewUid && typeof routePathname === 'string') {
|
|
186
|
-
const
|
|
216
|
+
const layoutRoutePath = this.context?.layout?.routePath;
|
|
217
|
+
const topViewUid = parsePathnameToViewParams(routePathname, {
|
|
218
|
+
basePath: this.context?.layoutRoute?.basePathname || (layoutRoutePath?.startsWith('/') ? layoutRoutePath : ''),
|
|
219
|
+
}).at(-1)?.viewUid;
|
|
187
220
|
if (topViewUid && topViewUid !== currentViewUid) {
|
|
188
221
|
return;
|
|
189
222
|
}
|
|
@@ -68,22 +68,15 @@ export class RootPageModel extends PageModel {
|
|
|
68
68
|
reaction(
|
|
69
69
|
() => this.context.pageActive.value,
|
|
70
70
|
() => {
|
|
71
|
+
if (this.context.view?.inputArgs?.activationControlledByLayout) {
|
|
72
|
+
this.mounted = true;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
71
75
|
if (this.context.pageActive.value && this.mounted) {
|
|
72
|
-
|
|
73
|
-
if (firstTab) {
|
|
74
|
-
this.setProps('tabActiveKey', firstTab.uid);
|
|
75
|
-
this.invokeTabModelLifecycleMethod(firstTab.uid, 'onActive', true);
|
|
76
|
-
}
|
|
76
|
+
this.activateCurrentTab(true);
|
|
77
77
|
}
|
|
78
78
|
if (this.context.pageActive.value === false) {
|
|
79
|
-
|
|
80
|
-
this.invokeTabModelLifecycleMethod(this.props.tabActiveKey, 'onInactive');
|
|
81
|
-
} else {
|
|
82
|
-
const firstTab = this.subModels.tabs?.[0];
|
|
83
|
-
if (firstTab) {
|
|
84
|
-
this.invokeTabModelLifecycleMethod(firstTab.uid, 'onInactive');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
79
|
+
this.deactivateCurrentTab();
|
|
87
80
|
}
|
|
88
81
|
this.mounted = true;
|
|
89
82
|
},
|
|
@@ -47,6 +47,13 @@ vi.mock('@nocobase/flow-engine', () => {
|
|
|
47
47
|
return [];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
findSubModel(key: string, callback: any) {
|
|
51
|
+
if (this.subModels[key]) {
|
|
52
|
+
return this.subModels[key].find(callback) || null;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
addSubModel() {}
|
|
51
58
|
setSubModel() {}
|
|
52
59
|
|
|
@@ -260,6 +267,44 @@ describe('PageModel', () => {
|
|
|
260
267
|
});
|
|
261
268
|
});
|
|
262
269
|
|
|
270
|
+
describe('tab lifecycle', () => {
|
|
271
|
+
it('should invoke tab lifecycle on PageModel subModels before engine lookup', () => {
|
|
272
|
+
const blockOnActive = vi.fn();
|
|
273
|
+
const blockOnInactive = vi.fn();
|
|
274
|
+
const tabModel = {
|
|
275
|
+
uid: 'tab1',
|
|
276
|
+
context: {
|
|
277
|
+
tabActive: { value: false },
|
|
278
|
+
},
|
|
279
|
+
subModels: {
|
|
280
|
+
grid: {
|
|
281
|
+
mapSubModels: vi.fn((_key, callback) => {
|
|
282
|
+
callback({ onActive: blockOnActive, onInactive: blockOnInactive });
|
|
283
|
+
}),
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
(pageModel as any).subModels = { tabs: [tabModel] };
|
|
288
|
+
(pageModel as any).flowEngine = {
|
|
289
|
+
getModel: vi.fn(() => undefined),
|
|
290
|
+
};
|
|
291
|
+
(pageModel as any).context = {
|
|
292
|
+
pageInfo: {},
|
|
293
|
+
view: {
|
|
294
|
+
inputArgs: { pageActive: true },
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
pageModel.invokeTabModelLifecycleMethod('tab1', 'onActive', true);
|
|
299
|
+
pageModel.invokeTabModelLifecycleMethod('tab1', 'onInactive');
|
|
300
|
+
|
|
301
|
+
expect((pageModel as any).flowEngine.getModel).not.toHaveBeenCalled();
|
|
302
|
+
expect(tabModel.context.tabActive.value).toBe(false);
|
|
303
|
+
expect(blockOnActive).toHaveBeenCalledWith(true);
|
|
304
|
+
expect(blockOnInactive).toHaveBeenCalledWith(false);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
263
308
|
describe('renderTabs activeKey logic', () => {
|
|
264
309
|
beforeEach(() => {
|
|
265
310
|
// Mock mapTabs to avoid complex rendering logic inside it
|
|
@@ -455,6 +500,7 @@ describe('PageModel', () => {
|
|
|
455
500
|
} as any;
|
|
456
501
|
(pageModel as any).context = {
|
|
457
502
|
currentRoute: {
|
|
503
|
+
id: 'route-1',
|
|
458
504
|
enableTabs: false,
|
|
459
505
|
},
|
|
460
506
|
};
|
|
@@ -479,6 +525,7 @@ describe('PageModel', () => {
|
|
|
479
525
|
} as any;
|
|
480
526
|
(pageModel as any).context = {
|
|
481
527
|
currentRoute: {
|
|
528
|
+
id: 'route-1',
|
|
482
529
|
enableTabs: true,
|
|
483
530
|
},
|
|
484
531
|
};
|
|
@@ -495,6 +542,59 @@ describe('PageModel', () => {
|
|
|
495
542
|
paddingBottom: 0,
|
|
496
543
|
});
|
|
497
544
|
});
|
|
545
|
+
|
|
546
|
+
it('should ignore stale desktop route enableTabs=false from another route', () => {
|
|
547
|
+
pageModel.props = {
|
|
548
|
+
routeId: 'route-1',
|
|
549
|
+
displayTitle: true,
|
|
550
|
+
enableTabs: true,
|
|
551
|
+
title: 'Title',
|
|
552
|
+
headerStyle: { backgroundColor: 'var(--colorBgLayout)' },
|
|
553
|
+
} as any;
|
|
554
|
+
(pageModel as any).context = {
|
|
555
|
+
currentRoute: {
|
|
556
|
+
id: 'route-2',
|
|
557
|
+
enableTabs: false,
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
pageModel.renderTabs = vi.fn(() => null);
|
|
561
|
+
pageModel.renderFirstTab = vi.fn(() => null);
|
|
562
|
+
|
|
563
|
+
const result = pageModel.render() as any;
|
|
564
|
+
const header = result.props.children[0];
|
|
565
|
+
|
|
566
|
+
expect(pageModel.renderTabs).toHaveBeenCalled();
|
|
567
|
+
expect(pageModel.renderFirstTab).not.toHaveBeenCalled();
|
|
568
|
+
expect(header.props.style).toMatchObject({
|
|
569
|
+
backgroundColor: 'var(--colorBgLayout)',
|
|
570
|
+
paddingBottom: 0,
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should ignore stale desktop route enableTabs=true from another route', () => {
|
|
575
|
+
pageModel.props = {
|
|
576
|
+
routeId: 'route-1',
|
|
577
|
+
displayTitle: true,
|
|
578
|
+
enableTabs: false,
|
|
579
|
+
title: 'Title',
|
|
580
|
+
headerStyle: { backgroundColor: 'var(--colorBgLayout)' },
|
|
581
|
+
} as any;
|
|
582
|
+
(pageModel as any).context = {
|
|
583
|
+
currentRoute: {
|
|
584
|
+
id: 'route-2',
|
|
585
|
+
enableTabs: true,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
pageModel.renderTabs = vi.fn(() => null);
|
|
589
|
+
pageModel.renderFirstTab = vi.fn(() => null);
|
|
590
|
+
|
|
591
|
+
const result = pageModel.render() as any;
|
|
592
|
+
const header = result.props.children[0];
|
|
593
|
+
|
|
594
|
+
expect(pageModel.renderTabs).not.toHaveBeenCalled();
|
|
595
|
+
expect(pageModel.renderFirstTab).toHaveBeenCalled();
|
|
596
|
+
expect(header.props.style).toEqual({ backgroundColor: 'var(--colorBgLayout)' });
|
|
597
|
+
});
|
|
498
598
|
});
|
|
499
599
|
|
|
500
600
|
describe('dirty refresh signal', () => {
|
|
@@ -554,7 +654,7 @@ describe('PageModel', () => {
|
|
|
554
654
|
pageModel.onMount();
|
|
555
655
|
|
|
556
656
|
expect(typeof listeners['view:activated']).toBe('function');
|
|
557
|
-
expect(invokeSpy).toHaveBeenCalledWith('tab1', 'onActive');
|
|
657
|
+
expect(invokeSpy).toHaveBeenCalledWith('tab1', 'onActive', false);
|
|
558
658
|
});
|
|
559
659
|
});
|
|
560
660
|
|
|
@@ -630,6 +730,7 @@ describe('PageModel', () => {
|
|
|
630
730
|
it('should use page documentTitle when desktop route disables tabs even if flow model enables tabs', async () => {
|
|
631
731
|
pageModel.props = { routeId: 'route-1', enableTabs: true, title: 'Route page title' } as any;
|
|
632
732
|
(pageModel as any).context.currentRoute = {
|
|
733
|
+
id: 'route-1',
|
|
633
734
|
enableTabs: false,
|
|
634
735
|
};
|
|
635
736
|
(pageModel as any).stepParams = {
|
|
@@ -12,4 +12,18 @@ import { FormActionModel } from './FormActionModel';
|
|
|
12
12
|
|
|
13
13
|
export class FormActionGroupModel extends ActionGroupModel {
|
|
14
14
|
static baseClass = FormActionModel;
|
|
15
|
+
|
|
16
|
+
static async defineChildren(ctx) {
|
|
17
|
+
const allowedModelNames = ctx.allowedFormActionModelNames;
|
|
18
|
+
|
|
19
|
+
if (!Array.isArray(allowedModelNames) || allowedModelNames.length === 0) {
|
|
20
|
+
return super.defineChildren(ctx);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await Promise.all(allowedModelNames.map((name) => ctx.engine?.getModelClassAsync?.(name)));
|
|
24
|
+
|
|
25
|
+
const items = await super.defineChildren(ctx);
|
|
26
|
+
const allowedSet = new Set(allowedModelNames);
|
|
27
|
+
return items.filter((item) => allowedSet.has(item.useModel || item.key));
|
|
28
|
+
}
|
|
15
29
|
}
|
|
@@ -138,6 +138,10 @@ export class FormItemModel<T extends DefaultStructure = DefaultStructure> extend
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
+
fork.context.defineProperty('fieldPathArray', {
|
|
142
|
+
get: () => this.context.fieldPathArray,
|
|
143
|
+
cache: false,
|
|
144
|
+
});
|
|
141
145
|
if (isHiddenReservedValuePreview) {
|
|
142
146
|
fork.setProps({ hidden: false });
|
|
143
147
|
}
|
|
@@ -151,7 +155,10 @@ export class FormItemModel<T extends DefaultStructure = DefaultStructure> extend
|
|
|
151
155
|
: { hidden, ...mergedPropsWithoutInitial };
|
|
152
156
|
const fieldPath = buildDynamicNamePath(this.props.name, idx);
|
|
153
157
|
this.context.defineProperty('fieldPathArray', {
|
|
154
|
-
|
|
158
|
+
get: () => {
|
|
159
|
+
return [...parentFieldPathArray, ..._.castArray(fieldPath)];
|
|
160
|
+
},
|
|
161
|
+
cache: false,
|
|
155
162
|
});
|
|
156
163
|
const record = this.context.item?.value || this.context.record;
|
|
157
164
|
const content = (
|