@nocobase/client-v2 2.1.0-alpha.40 → 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/es/Application.d.ts +7 -0
- package/es/BaseApplication.d.ts +13 -0
- package/es/RouterManager.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
- package/es/collection-manager/field-configure.d.ts +80 -0
- package/es/collection-manager/field-validation.d.ts +43 -0
- package/es/collection-manager/filter-operators/index.d.ts +46 -0
- package/es/collection-manager/filter-operators/operators.d.ts +30 -0
- package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
- package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
- package/es/collection-manager/interfaces/collection.d.ts +12 -51
- package/es/collection-manager/interfaces/color.d.ts +1 -16
- package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
- package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
- package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
- package/es/collection-manager/interfaces/datetime.d.ts +1 -44
- package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
- package/es/collection-manager/interfaces/email.d.ts +1 -29
- package/es/collection-manager/interfaces/id.d.ts +1 -16
- package/es/collection-manager/interfaces/index.d.ts +2 -3
- package/es/collection-manager/interfaces/input.d.ts +1 -102
- package/es/collection-manager/interfaces/integer.d.ts +1 -95
- package/es/collection-manager/interfaces/json.d.ts +16 -7
- package/es/collection-manager/interfaces/m2m.d.ts +11 -19
- package/es/collection-manager/interfaces/m2o.d.ts +11 -19
- package/es/collection-manager/interfaces/markdown.d.ts +1 -63
- package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
- package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
- package/es/collection-manager/interfaces/number.d.ts +1 -87
- package/es/collection-manager/interfaces/o2m.d.ts +12 -24
- package/es/collection-manager/interfaces/obo.d.ts +207 -0
- package/es/collection-manager/interfaces/oho.d.ts +207 -0
- package/es/collection-manager/interfaces/password.d.ts +1 -56
- package/es/collection-manager/interfaces/percent.d.ts +1 -84
- package/es/collection-manager/interfaces/phone.d.ts +1 -25
- package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
- package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
- package/es/collection-manager/interfaces/richText.d.ts +1 -63
- package/es/collection-manager/interfaces/select.d.ts +12 -44
- package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
- package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
- package/es/collection-manager/interfaces/textarea.d.ts +1 -51
- package/es/collection-manager/interfaces/time.d.ts +1 -16
- package/es/collection-manager/interfaces/types.d.ts +3 -12
- package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
- package/es/collection-manager/interfaces/url.d.ts +1 -20
- package/es/collection-manager/interfaces/uuid.d.ts +1 -34
- package/es/collection-manager/template-fields.d.ts +53 -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/VariableInput.d.ts +53 -2
- package/es/components/form/filter/CollectionFilter.d.ts +49 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +49 -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/AppListRender.d.ts +11 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
- 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 +66 -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/assign-form/assignFieldValuesFlow.d.ts +84 -0
- package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
- package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
- package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
- 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 +6 -0
- package/es/index.mjs +552 -459
- 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 +552 -459
- package/package.json +8 -7
- package/src/Application.tsx +51 -12
- package/src/BaseApplication.tsx +32 -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/collection-field-interface/CollectionFieldInterface.ts +71 -77
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
- package/src/collection-manager/field-configure.ts +548 -0
- package/src/collection-manager/field-validation.ts +195 -0
- package/src/collection-manager/filter-operators/index.ts +176 -0
- package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
- package/src/collection-manager/interfaces/checkbox.ts +2 -9
- package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
- package/src/collection-manager/interfaces/collection.ts +2 -15
- package/src/collection-manager/interfaces/color.ts +2 -2
- package/src/collection-manager/interfaces/createdAt.ts +2 -2
- package/src/collection-manager/interfaces/createdBy.ts +1 -12
- package/src/collection-manager/interfaces/dateOnly.ts +8 -2
- package/src/collection-manager/interfaces/datetime.ts +2 -2
- package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
- package/src/collection-manager/interfaces/email.ts +2 -9
- package/src/collection-manager/interfaces/id.ts +1 -2
- package/src/collection-manager/interfaces/index.ts +2 -3
- package/src/collection-manager/interfaces/input.ts +2 -133
- package/src/collection-manager/interfaces/integer.ts +2 -71
- package/src/collection-manager/interfaces/json.tsx +17 -11
- package/src/collection-manager/interfaces/m2m.tsx +0 -21
- package/src/collection-manager/interfaces/m2o.tsx +0 -22
- package/src/collection-manager/interfaces/markdown.ts +2 -51
- package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
- package/src/collection-manager/interfaces/nanoid.ts +2 -2
- package/src/collection-manager/interfaces/number.ts +2 -85
- package/src/collection-manager/interfaces/o2m.tsx +1 -22
- package/src/collection-manager/interfaces/obo.tsx +145 -0
- package/src/collection-manager/interfaces/oho.tsx +145 -0
- package/src/collection-manager/interfaces/password.ts +2 -44
- package/src/collection-manager/interfaces/percent.ts +2 -74
- package/src/collection-manager/interfaces/phone.ts +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +0 -133
- package/src/collection-manager/interfaces/radioGroup.ts +2 -2
- package/src/collection-manager/interfaces/richText.ts +2 -51
- package/src/collection-manager/interfaces/select.ts +2 -14
- package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
- package/src/collection-manager/interfaces/tableoid.ts +1 -2
- package/src/collection-manager/interfaces/textarea.ts +2 -51
- package/src/collection-manager/interfaces/time.ts +2 -2
- package/src/collection-manager/interfaces/types.ts +4 -12
- package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
- package/src/collection-manager/interfaces/updatedAt.ts +2 -2
- package/src/collection-manager/interfaces/updatedBy.ts +1 -12
- package/src/collection-manager/interfaces/url.ts +2 -4
- package/src/collection-manager/interfaces/uuid.ts +2 -2
- package/src/collection-manager/template-fields.ts +109 -0
- package/src/components/KeepAlive.tsx +131 -0
- package/src/components/README.md +90 -6
- package/src/components/README.zh-CN.md +90 -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/VariableInput.tsx +101 -28
- package/src/components/form/__tests__/VariableInput.test.ts +85 -0
- package/src/components/form/filter/CollectionFilter.tsx +111 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +184 -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 +247 -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 +203 -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 +35 -7
- 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/AppListRender.tsx +139 -0
- 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 +3 -0
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
- 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 +102 -0
- package/src/flow/index.ts +4 -0
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
- package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +26 -1
- 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/assign-form/AssignFormItemModel.tsx +63 -2
- package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +206 -0
- package/src/flow/models/blocks/assign-form/index.ts +1 -0
- package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
- 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/__tests__/submitHandler.test.ts +71 -0
- package/src/flow/models/blocks/form/submitHandler.ts +8 -1
- 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/CollectionSelectorFieldModel.tsx +8 -2
- package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
- 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 +6 -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/es/collection-manager/interfaces/linkTo.d.ts +0 -90
- package/es/collection-manager/interfaces/o2o.d.ts +0 -621
- package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
- package/es/collection-manager/interfaces/subTable.d.ts +0 -172
- package/src/collection-manager/interfaces/linkTo.ts +0 -120
- package/src/collection-manager/interfaces/o2o.tsx +0 -561
- package/src/collection-manager/interfaces/subTable.ts +0 -218
- 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,102 @@
|
|
|
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
|
+
* **Preferred way to restrict fields**, alongside `nonfilterableFieldNames`.
|
|
43
|
+
*
|
|
44
|
+
* 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.
|
|
45
|
+
*/
|
|
46
|
+
filterableFieldNames?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* **Preferred way to restrict fields**, alongside `filterableFieldNames`.
|
|
49
|
+
*
|
|
50
|
+
* Blacklist of root-level field names to drop. Mirrors v1's `nonfilterable: [...]` schema prop on `Filter.Action`. Applies at depth 1 only, same as the whitelist. When both `filterableFieldNames` and `nonfilterableFieldNames` are provided, both apply: the final field set is `(whitelist or all) minus blacklist`.
|
|
51
|
+
*/
|
|
52
|
+
nonfilterableFieldNames?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Bypass the `filterableFieldNames` whitelist (mirrors v1 `noIgnore`).
|
|
55
|
+
*
|
|
56
|
+
* Legacy escape hatch from v1 schemas — prefer adjusting `filterableFieldNames` / `nonfilterableFieldNames` instead. Kept only for parity with existing v1 schemas that already set `noIgnore`.
|
|
57
|
+
*/
|
|
58
|
+
noIgnore?: boolean;
|
|
59
|
+
/** Translator used for field/operator labels. Defaults to identity. */
|
|
60
|
+
t?: (key: string) => string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const identity = (s: string) => s;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 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).
|
|
67
|
+
*
|
|
68
|
+
* Mirrors v1 in two ways that matter:
|
|
69
|
+
* - 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.
|
|
70
|
+
* - 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.
|
|
71
|
+
*/
|
|
72
|
+
export function useFilterOptions(collection: Collection | undefined, args: UseFilterOptionsArgs = {}): FilterOption[] {
|
|
73
|
+
const { filterableFieldNames, nonfilterableFieldNames, noIgnore = false, t = identity } = args;
|
|
74
|
+
|
|
75
|
+
const fields = useMemo(() => collection?.getFields() || [], [collection]);
|
|
76
|
+
|
|
77
|
+
const ignoreFieldsNames = useMemo(() => {
|
|
78
|
+
// Whitelist contribution: every field not in `filterableFieldNames`.
|
|
79
|
+
// Skipped when `noIgnore` is set, or when no whitelist was provided.
|
|
80
|
+
const whitelistIgnored =
|
|
81
|
+
noIgnore || !filterableFieldNames?.length
|
|
82
|
+
? []
|
|
83
|
+
: fields.map((f) => f.name).filter((n) => !filterableFieldNames.includes(n));
|
|
84
|
+
// Blacklist contribution: explicit names. Always applied, even with
|
|
85
|
+
// `noIgnore` (the blacklist's whole job is to subtract specific fields).
|
|
86
|
+
const blacklistIgnored = nonfilterableFieldNames ?? [];
|
|
87
|
+
if (!blacklistIgnored.length) return whitelistIgnored;
|
|
88
|
+
// Union the two so the final ignore set is `whitelist-derived ∪ blacklist`.
|
|
89
|
+
return Array.from(new Set([...whitelistIgnored, ...blacklistIgnored]));
|
|
90
|
+
}, [fields, filterableFieldNames, nonfilterableFieldNames, noIgnore]);
|
|
91
|
+
|
|
92
|
+
return useMemo(
|
|
93
|
+
() =>
|
|
94
|
+
fieldsToOptions(
|
|
95
|
+
fields.filter((field) => field.target !== 'attachments' && field.interface !== 'formula'),
|
|
96
|
+
1,
|
|
97
|
+
ignoreFieldsNames,
|
|
98
|
+
t,
|
|
99
|
+
).filter(Boolean) as FilterOption[],
|
|
100
|
+
[fields, ignoreFieldsNames, t],
|
|
101
|
+
);
|
|
102
|
+
}
|
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';
|
|
@@ -7,75 +7,22 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { tExpr,
|
|
10
|
+
import { tExpr, useFlowSettingsContext } from '@nocobase/flow-engine';
|
|
11
11
|
import { Alert, ButtonProps } from 'antd';
|
|
12
|
-
import React
|
|
12
|
+
import React from 'react';
|
|
13
13
|
import { AxiosRequestConfig } from 'axios';
|
|
14
14
|
import { ActionModel, ActionSceneEnum } from '../base/ActionModel';
|
|
15
|
-
import { CollectionActionModel } from '../base/CollectionActionModel';
|
|
16
|
-
import { RecordActionModel } from '../base/RecordActionModel';
|
|
17
15
|
import { AssignFormModel } from '../blocks/assign-form/AssignFormModel';
|
|
16
|
+
import {
|
|
17
|
+
createAssignFieldValuesStep,
|
|
18
|
+
createAssignFormSubModelOptions,
|
|
19
|
+
getAssignFieldValuesDefaultParams,
|
|
20
|
+
} from '../blocks/assign-form/assignFieldValuesFlow';
|
|
18
21
|
import { applyUpdateRecordAction } from './UpdateRecordActionUtils';
|
|
19
22
|
// import { RemoteFlowModelRenderer } from '../../FlowPage';
|
|
20
23
|
|
|
21
24
|
const SETTINGS_FLOW_KEY = 'assignSettings';
|
|
22
25
|
|
|
23
|
-
// 配置态编辑器:渲染 assignForm 子模型
|
|
24
|
-
function AssignFieldsEditor() {
|
|
25
|
-
const { model, blockModel } = useFlowSettingsContext();
|
|
26
|
-
const action: any = model;
|
|
27
|
-
const engine = useFlowEngine();
|
|
28
|
-
const initializedRef = useRef(false);
|
|
29
|
-
const [formModel, setFormModel] = React.useState<AssignFormModel | null>(null);
|
|
30
|
-
|
|
31
|
-
// 初始化回填
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
let cancelled = false;
|
|
34
|
-
(async () => {
|
|
35
|
-
const loaded = (await engine.loadOrCreateModel(
|
|
36
|
-
{
|
|
37
|
-
parentId: action.uid,
|
|
38
|
-
subKey: 'assignForm',
|
|
39
|
-
use: 'AssignFormModel',
|
|
40
|
-
},
|
|
41
|
-
{ skipSave: !model.context.flowSettingsEnabled },
|
|
42
|
-
)) as AssignFormModel;
|
|
43
|
-
if (cancelled) return;
|
|
44
|
-
setFormModel(loaded);
|
|
45
|
-
action.assignFormUid = (loaded as any)?.uid || action.assignFormUid;
|
|
46
|
-
})();
|
|
47
|
-
return () => {
|
|
48
|
-
cancelled = true;
|
|
49
|
-
};
|
|
50
|
-
}, [action, engine]);
|
|
51
|
-
|
|
52
|
-
// 初始化回填(在子模型加载完成后)
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (initializedRef.current) return;
|
|
55
|
-
if (!formModel) return;
|
|
56
|
-
const prev = action.getStepParams?.(SETTINGS_FLOW_KEY, 'assignFieldValues') || {};
|
|
57
|
-
// 注入资源上下文:与所在表格区块一致
|
|
58
|
-
const coll = blockModel?.collection || action?.context?.collection;
|
|
59
|
-
const dsKey = coll?.dataSourceKey;
|
|
60
|
-
const collName = coll?.name;
|
|
61
|
-
if (dsKey && collName) {
|
|
62
|
-
formModel.setStepParams('resourceSettings', 'init', {
|
|
63
|
-
dataSourceKey: dsKey,
|
|
64
|
-
collectionName: collName,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
formModel.setInitialAssignedValues(prev?.assignedValues || {});
|
|
68
|
-
// 批量配置态:移除 ctx.record(Action 为区块级,不具备单条记录上下文)
|
|
69
|
-
const isBulk = action instanceof CollectionActionModel && !(action instanceof RecordActionModel);
|
|
70
|
-
if (isBulk && (formModel as any)?.context?.defineProperty) {
|
|
71
|
-
formModel.context.defineProperty('record', { get: () => undefined, cache: false });
|
|
72
|
-
}
|
|
73
|
-
initializedRef.current = true;
|
|
74
|
-
}, [action, blockModel?.collection, formModel]);
|
|
75
|
-
|
|
76
|
-
return formModel ? <FlowModelRenderer model={formModel} showFlowSettings={false} /> : null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
26
|
function Info() {
|
|
80
27
|
const ctx = useFlowSettingsContext();
|
|
81
28
|
return (
|
|
@@ -124,16 +71,9 @@ UpdateRecordActionModel.define({
|
|
|
124
71
|
sort: 50,
|
|
125
72
|
// 使用函数型 createModelOptions,从父级上下文提取资源信息,直接注入到子模型的 resourceSettings.init
|
|
126
73
|
createModelOptions: (ctx) => {
|
|
127
|
-
const dsKey = ctx.collection.dataSourceKey;
|
|
128
|
-
const collName = ctx.collection.name;
|
|
129
|
-
const init = dsKey && collName ? { dataSourceKey: dsKey, collectionName: collName } : undefined;
|
|
130
74
|
return {
|
|
131
75
|
subModels: {
|
|
132
|
-
assignForm:
|
|
133
|
-
async: true,
|
|
134
|
-
use: 'AssignFormModel',
|
|
135
|
-
stepParams: { resourceSettings: { init } },
|
|
136
|
-
},
|
|
76
|
+
assignForm: createAssignFormSubModelOptions(ctx),
|
|
137
77
|
},
|
|
138
78
|
};
|
|
139
79
|
},
|
|
@@ -155,32 +95,12 @@ UpdateRecordActionModel.registerFlow({
|
|
|
155
95
|
content: tExpr('Are you sure you want to perform the Update record action?'),
|
|
156
96
|
},
|
|
157
97
|
},
|
|
158
|
-
assignFieldValues: {
|
|
98
|
+
assignFieldValues: createAssignFieldValuesStep({
|
|
99
|
+
settingsFlowKey: SETTINGS_FLOW_KEY,
|
|
159
100
|
title: tExpr('Field values'),
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
'x-decorator': 'FormItem',
|
|
164
|
-
'x-component': () => <Info />,
|
|
165
|
-
},
|
|
166
|
-
editor: {
|
|
167
|
-
'x-decorator': 'FormItem',
|
|
168
|
-
'x-component': () => <AssignFieldsEditor />,
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
},
|
|
172
|
-
async beforeParamsSave(ctx) {
|
|
173
|
-
const m = ctx.model as UpdateRecordActionModel;
|
|
174
|
-
// 跨视图栈按 uid 定位到设置面板中的真实 AssignForm 实例
|
|
175
|
-
const form: AssignFormModel = m?.assignFormUid && ctx.engine.getModel?.(m.assignFormUid, true);
|
|
176
|
-
if (!form) return;
|
|
177
|
-
await form?.form?.validateFields?.();
|
|
178
|
-
const assignedValues = form?.getAssignedValues?.() || {};
|
|
179
|
-
const grid = form?.subModels?.grid;
|
|
180
|
-
const items = grid?.subModels?.items || [];
|
|
181
|
-
ctx.model.setStepParams(SETTINGS_FLOW_KEY, 'assignFieldValues', { assignedValues });
|
|
182
|
-
},
|
|
183
|
-
},
|
|
101
|
+
tipComponent: Info,
|
|
102
|
+
validateBeforeSave: true,
|
|
103
|
+
}),
|
|
184
104
|
},
|
|
185
105
|
});
|
|
186
106
|
|
|
@@ -190,8 +110,7 @@ UpdateRecordActionModel.registerFlow({
|
|
|
190
110
|
steps: {
|
|
191
111
|
apply: {
|
|
192
112
|
async defaultParams(ctx) {
|
|
193
|
-
|
|
194
|
-
return { assignedValues: step?.assignedValues || {} };
|
|
113
|
+
return getAssignFieldValuesDefaultParams(ctx, SETTINGS_FLOW_KEY);
|
|
195
114
|
},
|
|
196
115
|
async handler(ctx, params) {
|
|
197
116
|
await applyUpdateRecordAction(ctx, params, { settingsFlowKey: SETTINGS_FLOW_KEY });
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { MultiRecordResource, SingleRecordResource
|
|
10
|
+
import { MultiRecordResource, SingleRecordResource } from '@nocobase/flow-engine';
|
|
11
11
|
import { dispatchEventDeep } from '../../utils';
|
|
12
|
+
import { resolveAssignFieldValues } from '../blocks/assign-form/assignFieldValuesFlow';
|
|
12
13
|
|
|
13
14
|
export async function refreshLinkageRulesAfterUpdate(ctx: any) {
|
|
14
15
|
const blockModel = ctx?.blockModel || ctx?.model?.context?.blockModel || ctx?.model;
|
|
@@ -59,12 +60,8 @@ export async function applyUpdateRecordAction(
|
|
|
59
60
|
const confirmParams = savedConfirm && typeof savedConfirm === 'object' ? savedConfirm : { enable: false };
|
|
60
61
|
await ctx.runAction('confirm', confirmParams);
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
assignedValues = await resolveRunJSObjectValues(ctx, params?.assignedValues);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error('[UpdateRecordAction] RunJS execution failed', error);
|
|
67
|
-
ctx.message.error(ctx.t('RunJS execution failed'));
|
|
63
|
+
const assignedValues = await resolveAssignFieldValues(ctx, params?.assignedValues, 'UpdateRecordAction');
|
|
64
|
+
if (!assignedValues) {
|
|
68
65
|
return;
|
|
69
66
|
}
|
|
70
67
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect } from 'vitest';
|
|
11
11
|
import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
|
|
12
|
-
import { UpdateRecordActionModel } from '../../..'; // 这样可以解决循环依赖问题
|
|
12
|
+
import { FormSubmitActionModel, UpdateRecordActionModel } from '../../..'; // 这样可以解决循环依赖问题
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 精简版 AssignFormModel(仅用于单测):
|
|
@@ -52,4 +52,29 @@ describe('AssignForm value refill and save (beforeParamsSave)', () => {
|
|
|
52
52
|
const saved = action.getStepParams('assignSettings', 'assignFieldValues');
|
|
53
53
|
expect(saved?.assignedValues).toEqual({ nickname: 'Alice', score: 99 });
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
it('FormSubmitActionModel: reuses assignFieldValues step and saves assignedValues from AssignForm', async () => {
|
|
57
|
+
const root = new FlowEngine();
|
|
58
|
+
|
|
59
|
+
root.registerModels({ FormSubmitActionModel, AssignFormModel: TestAssignFormModel });
|
|
60
|
+
|
|
61
|
+
const action = root.createModel<FormSubmitActionModel>({ use: 'FormSubmitActionModel', uid: 'submit-u' });
|
|
62
|
+
const form = root.createModel<TestAssignFormModel>({
|
|
63
|
+
use: 'AssignFormModel',
|
|
64
|
+
uid: 'submit-form-u',
|
|
65
|
+
parentId: action.uid,
|
|
66
|
+
subKey: 'assignForm',
|
|
67
|
+
});
|
|
68
|
+
form.setAssignedValues({ status: 'published' });
|
|
69
|
+
action.assignFormUid = form.uid;
|
|
70
|
+
|
|
71
|
+
const flow = action.getFlow('submitSettings') as any;
|
|
72
|
+
const step = flow?.steps?.assignFieldValues;
|
|
73
|
+
expect(step?.beforeParamsSave).toBeTypeOf('function');
|
|
74
|
+
|
|
75
|
+
await step.beforeParamsSave({ engine: root, model: action });
|
|
76
|
+
|
|
77
|
+
const saved = action.getStepParams('submitSettings', 'assignFieldValues');
|
|
78
|
+
expect(saved?.assignedValues).toEqual({ status: 'published' });
|
|
79
|
+
});
|
|
55
80
|
});
|
|
@@ -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
|
}
|