@nocobase/flow-engine 2.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +30 -0
- package/lib/ContextPathProxy.d.ts +17 -0
- package/lib/ContextPathProxy.js +65 -0
- package/lib/ElementProxy.d.ts +17 -0
- package/lib/ElementProxy.js +93 -0
- package/lib/FlowContextProvider.d.ts +24 -0
- package/lib/FlowContextProvider.js +82 -0
- package/lib/FlowDefinition.d.ts +423 -0
- package/lib/FlowDefinition.js +257 -0
- package/lib/JSRunner.d.ts +32 -0
- package/lib/JSRunner.js +95 -0
- package/lib/ReactView.d.ts +20 -0
- package/lib/ReactView.js +120 -0
- package/lib/ViewScopedFlowEngine.d.ts +23 -0
- package/lib/ViewScopedFlowEngine.js +81 -0
- package/lib/acl/Acl.d.ts +31 -0
- package/lib/acl/Acl.js +115 -0
- package/lib/action-registry/BaseActionRegistry.d.ts +23 -0
- package/lib/action-registry/BaseActionRegistry.js +57 -0
- package/lib/action-registry/EngineActionRegistry.d.ts +20 -0
- package/lib/action-registry/EngineActionRegistry.js +47 -0
- package/lib/action-registry/ModelActionRegistry.d.ts +34 -0
- package/lib/action-registry/ModelActionRegistry.js +79 -0
- package/lib/components/DynamicFlowsEditor.d.ts +17 -0
- package/lib/components/DynamicFlowsEditor.js +49 -0
- package/lib/components/FieldModelRenderer.d.ts +10 -0
- package/lib/components/FieldModelRenderer.js +94 -0
- package/lib/components/FlowContextSelector.d.ts +11 -0
- package/lib/components/FlowContextSelector.js +221 -0
- package/lib/components/FlowErrorFallback.d.ts +25 -0
- package/lib/components/FlowErrorFallback.js +264 -0
- package/lib/components/FlowModelRenderer.d.ts +64 -0
- package/lib/components/FlowModelRenderer.js +254 -0
- package/lib/components/FormItem.d.ts +18 -0
- package/lib/components/FormItem.js +147 -0
- package/lib/components/common/FlowSettingsButton.d.ts +11 -0
- package/lib/components/common/FlowSettingsButton.js +66 -0
- package/lib/components/common/index.d.ts +9 -0
- package/lib/components/common/index.js +30 -0
- package/lib/components/common/withFlowDesignMode.d.ts +26 -0
- package/lib/components/common/withFlowDesignMode.js +61 -0
- package/lib/components/dnd/getMousePositionOnElement.d.ts +50 -0
- package/lib/components/dnd/getMousePositionOnElement.js +95 -0
- package/lib/components/dnd/index.d.ts +24 -0
- package/lib/components/dnd/index.js +164 -0
- package/lib/components/dnd/moveBlock.d.ts +33 -0
- package/lib/components/dnd/moveBlock.js +302 -0
- package/lib/components/index.d.ts +18 -0
- package/lib/components/index.js +48 -0
- package/lib/components/settings/independents/dropdown/FlowsDropdownButton.d.ts +46 -0
- package/lib/components/settings/independents/dropdown/FlowsDropdownButton.js +225 -0
- package/lib/components/settings/independents/dropdown/index.d.ts +9 -0
- package/lib/components/settings/independents/dropdown/index.js +30 -0
- package/lib/components/settings/independents/index.d.ts +1 -0
- package/lib/components/settings/independents/index.js +30 -0
- package/lib/components/settings/index.d.ts +10 -0
- package/lib/components/settings/index.js +32 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +24 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +501 -0
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.d.ts +45 -0
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +231 -0
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +111 -0
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +484 -0
- package/lib/components/settings/wrappers/contextual/StepRequiredSettingsDialog.d.ts +26 -0
- package/lib/components/settings/wrappers/contextual/StepRequiredSettingsDialog.js +342 -0
- package/lib/components/settings/wrappers/contextual/StepSettings.d.ts +23 -0
- package/lib/components/settings/wrappers/contextual/StepSettings.js +110 -0
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.d.ts +20 -0
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +206 -0
- package/lib/components/settings/wrappers/contextual/StepSettingsDrawer.d.ts +20 -0
- package/lib/components/settings/wrappers/contextual/StepSettingsDrawer.js +47 -0
- package/lib/components/settings/wrappers/contextual/index.d.ts +14 -0
- package/lib/components/settings/wrappers/contextual/index.js +40 -0
- package/lib/components/settings/wrappers/embedded/FlowSettings.d.ts +33 -0
- package/lib/components/settings/wrappers/embedded/FlowSettings.js +207 -0
- package/lib/components/settings/wrappers/embedded/FlowsSettings.d.ts +41 -0
- package/lib/components/settings/wrappers/embedded/FlowsSettings.js +84 -0
- package/lib/components/settings/wrappers/embedded/FlowsSettingsContent.d.ts +16 -0
- package/lib/components/settings/wrappers/embedded/FlowsSettingsContent.js +88 -0
- package/lib/components/settings/wrappers/embedded/index.d.ts +10 -0
- package/lib/components/settings/wrappers/embedded/index.js +32 -0
- package/lib/components/settings/wrappers/index.d.ts +2 -0
- package/lib/components/settings/wrappers/index.js +32 -0
- package/lib/components/subModel/AddSubModelButton.d.ts +62 -0
- package/lib/components/subModel/AddSubModelButton.js +415 -0
- package/lib/components/subModel/LazyDropdown.d.ts +55 -0
- package/lib/components/subModel/LazyDropdown.js +524 -0
- package/lib/components/subModel/index.d.ts +10 -0
- package/lib/components/subModel/index.js +32 -0
- package/lib/components/subModel/utils.d.ts +34 -0
- package/lib/components/subModel/utils.js +287 -0
- package/lib/components/variables/InlineVariableTag.d.ts +21 -0
- package/lib/components/variables/InlineVariableTag.js +123 -0
- package/lib/components/variables/SlateVariableEditor.d.ts +46 -0
- package/lib/components/variables/SlateVariableEditor.js +302 -0
- package/lib/components/variables/VariableInput.d.ts +11 -0
- package/lib/components/variables/VariableInput.js +322 -0
- package/lib/components/variables/VariableTag.d.ts +11 -0
- package/lib/components/variables/VariableTag.js +148 -0
- package/lib/components/variables/VariableTrigger.d.ts +20 -0
- package/lib/components/variables/VariableTrigger.js +136 -0
- package/lib/components/variables/index.d.ts +15 -0
- package/lib/components/variables/index.js +53 -0
- package/lib/components/variables/types.d.ts +93 -0
- package/lib/components/variables/types.js +24 -0
- package/lib/components/variables/useResolvedMetaTree.d.ts +19 -0
- package/lib/components/variables/useResolvedMetaTree.js +91 -0
- package/lib/components/variables/utils.d.ts +22 -0
- package/lib/components/variables/utils.js +177 -0
- package/lib/data-source/index.d.ts +180 -0
- package/lib/data-source/index.js +733 -0
- package/lib/data-source/jioToJoiSchema.d.ts +19 -0
- package/lib/data-source/jioToJoiSchema.js +114 -0
- package/lib/decorators/index.d.ts +9 -0
- package/lib/decorators/index.js +36 -0
- package/lib/decorators/largeField.d.ts +9 -0
- package/lib/decorators/largeField.js +42 -0
- package/lib/emitter.d.ts +16 -0
- package/lib/emitter.js +58 -0
- package/lib/event-registry/BaseEventRegistry.d.ts +22 -0
- package/lib/event-registry/BaseEventRegistry.js +57 -0
- package/lib/event-registry/EngineEventRegistry.d.ts +19 -0
- package/lib/event-registry/EngineEventRegistry.js +47 -0
- package/lib/event-registry/ModelEventRegistry.d.ts +33 -0
- package/lib/event-registry/ModelEventRegistry.js +79 -0
- package/lib/executor/FlowExecutor.d.ts +26 -0
- package/lib/executor/FlowExecutor.js +262 -0
- package/lib/flow-registry/BaseFlowRegistry.d.ts +46 -0
- package/lib/flow-registry/BaseFlowRegistry.js +86 -0
- package/lib/flow-registry/GlobalFlowRegistry.d.ts +22 -0
- package/lib/flow-registry/GlobalFlowRegistry.js +95 -0
- package/lib/flow-registry/InstanceFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/InstanceFlowRegistry.js +59 -0
- package/lib/flow-registry/index.d.ts +11 -0
- package/lib/flow-registry/index.js +34 -0
- package/lib/flowContext.d.ts +215 -0
- package/lib/flowContext.js +1266 -0
- package/lib/flowEngine.d.ts +340 -0
- package/lib/flowEngine.js +781 -0
- package/lib/flowI18n.d.ts +46 -0
- package/lib/flowI18n.js +117 -0
- package/lib/flowSettings.d.ts +266 -0
- package/lib/flowSettings.js +850 -0
- package/lib/hooks/index.d.ts +14 -0
- package/lib/hooks/index.js +40 -0
- package/lib/hooks/useApplyAutoFlows.d.ts +21 -0
- package/lib/hooks/useApplyAutoFlows.js +62 -0
- package/lib/hooks/useFlowModel.d.ts +29 -0
- package/lib/hooks/useFlowModel.js +72 -0
- package/lib/hooks/useFlowModelById.d.ts +11 -0
- package/lib/hooks/useFlowModelById.js +61 -0
- package/lib/hooks/useFlowSettingsContext.d.ts +20 -0
- package/lib/hooks/useFlowSettingsContext.js +61 -0
- package/lib/hooks/useFlowStep.d.ts +17 -0
- package/lib/hooks/useFlowStep.js +56 -0
- package/lib/hooks/useNiceDropdownMaxHeight.d.ts +13 -0
- package/lib/hooks/useNiceDropdownMaxHeight.js +52 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.js +73 -0
- package/lib/locale/en-US.json +61 -0
- package/lib/locale/index.d.ts +141 -0
- package/lib/locale/index.js +70 -0
- package/lib/locale/zh-CN.json +61 -0
- package/lib/models/CollectionFieldModel.d.ts +50 -0
- package/lib/models/CollectionFieldModel.js +242 -0
- package/lib/models/DisplayItemModel.d.ts +12 -0
- package/lib/models/DisplayItemModel.js +41 -0
- package/lib/models/EditableItemModel.d.ts +12 -0
- package/lib/models/EditableItemModel.js +41 -0
- package/lib/models/FilterableItemModel.d.ts +12 -0
- package/lib/models/FilterableItemModel.js +41 -0
- package/lib/models/flowModel.d.ts +344 -0
- package/lib/models/flowModel.js +1133 -0
- package/lib/models/forkFlowModel.d.ts +83 -0
- package/lib/models/forkFlowModel.js +257 -0
- package/lib/models/index.d.ts +14 -0
- package/lib/models/index.js +40 -0
- package/lib/provider.d.ts +22 -0
- package/lib/provider.js +114 -0
- package/lib/resources/apiResource.d.ts +34 -0
- package/lib/resources/apiResource.js +153 -0
- package/lib/resources/baseRecordResource.d.ts +61 -0
- package/lib/resources/baseRecordResource.js +264 -0
- package/lib/resources/filterItem.d.ts +33 -0
- package/lib/resources/filterItem.js +93 -0
- package/lib/resources/flowResource.d.ts +45 -0
- package/lib/resources/flowResource.js +146 -0
- package/lib/resources/index.d.ts +15 -0
- package/lib/resources/index.js +42 -0
- package/lib/resources/multiRecordResource.d.ts +53 -0
- package/lib/resources/multiRecordResource.js +230 -0
- package/lib/resources/singleRecordResource.d.ts +23 -0
- package/lib/resources/singleRecordResource.js +111 -0
- package/lib/resources/sqlResource.d.ts +73 -0
- package/lib/resources/sqlResource.js +294 -0
- package/lib/runjs-context/contexts/FlowRunJSContext.d.ts +38 -0
- package/lib/runjs-context/contexts/FlowRunJSContext.js +217 -0
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +66 -0
- package/lib/runjs-context/contexts/JSBlockRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +78 -0
- package/lib/runjs-context/contexts/JSCollectionActionRunJSContext.d.ts +12 -0
- package/lib/runjs-context/contexts/JSCollectionActionRunJSContext.js +59 -0
- package/lib/runjs-context/contexts/JSFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSFieldRunJSContext.js +70 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +66 -0
- package/lib/runjs-context/contexts/JSRecordActionRunJSContext.d.ts +12 -0
- package/lib/runjs-context/contexts/JSRecordActionRunJSContext.js +61 -0
- package/lib/runjs-context/contexts/LinkageRunJSContext.d.ts +12 -0
- package/lib/runjs-context/contexts/LinkageRunJSContext.js +62 -0
- package/lib/runjs-context/helpers.d.ts +17 -0
- package/lib/runjs-context/helpers.js +79 -0
- package/lib/runjs-context/index.d.ts +19 -0
- package/lib/runjs-context/index.js +57 -0
- package/lib/runjs-context/registry.d.ts +17 -0
- package/lib/runjs-context/registry.js +93 -0
- package/lib/runjs-context/snippets/global/api-request-get.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/api-request-get.snippet.js +42 -0
- package/lib/runjs-context/snippets/global/api-request-post.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/api-request-post.snippet.js +42 -0
- package/lib/runjs-context/snippets/global/console-log-ctx.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/console-log-ctx.snippet.js +41 -0
- package/lib/runjs-context/snippets/global/copy-record-json.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/copy-record-json.snippet.js +42 -0
- package/lib/runjs-context/snippets/global/copy-to-clipboard.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/copy-to-clipboard.snippet.js +42 -0
- package/lib/runjs-context/snippets/global/log-json-record.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/log-json-record.snippet.js +42 -0
- package/lib/runjs-context/snippets/global/message-error.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/message-error.snippet.js +41 -0
- package/lib/runjs-context/snippets/global/message-success.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/message-success.snippet.js +41 -0
- package/lib/runjs-context/snippets/global/notification-open.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/notification-open.snippet.js +43 -0
- package/lib/runjs-context/snippets/global/open-view-dialog.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/open-view-dialog.snippet.js +47 -0
- package/lib/runjs-context/snippets/global/open-view-drawer.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/open-view-drawer.snippet.js +47 -0
- package/lib/runjs-context/snippets/global/requireAsync.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/requireAsync.snippet.js +46 -0
- package/lib/runjs-context/snippets/global/sleep.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/sleep.snippet.js +43 -0
- package/lib/runjs-context/snippets/global/try-catch-async.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/try-catch-async.snippet.js +44 -0
- package/lib/runjs-context/snippets/global/view-navigation-push.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/view-navigation-push.snippet.js +44 -0
- package/lib/runjs-context/snippets/global/window-open.snippet.d.ts +16 -0
- package/lib/runjs-context/snippets/global/window-open.snippet.js +41 -0
- package/lib/runjs-context/snippets/index.d.ts +11 -0
- package/lib/runjs-context/snippets/index.js +94 -0
- package/lib/runjs-context/snippets/libs/echarts-init.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/libs/echarts-init.snippet.js +46 -0
- package/lib/runjs-context/snippets/scene/actions/collection-selected-count.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/actions/collection-selected-count.snippet.js +44 -0
- package/lib/runjs-context/snippets/scene/actions/iterate-selected-rows.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/actions/iterate-selected-rows.snippet.js +43 -0
- package/lib/runjs-context/snippets/scene/actions/record-id-message.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/actions/record-id-message.snippet.js +43 -0
- package/lib/runjs-context/snippets/scene/actions/run-action-basic.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/actions/run-action-basic.snippet.js +40 -0
- package/lib/runjs-context/snippets/scene/jsblock/add-event-listener.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsblock/add-event-listener.snippet.js +46 -0
- package/lib/runjs-context/snippets/scene/jsblock/append-style.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsblock/append-style.snippet.js +42 -0
- package/lib/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.js +46 -0
- package/lib/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.js +41 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-basic.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-basic.snippet.js +41 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-button-handler.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-button-handler.snippet.js +46 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-card.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-card.snippet.js +45 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-react.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsblock/render-react.snippet.js +56 -0
- package/lib/runjs-context/snippets/scene/jsfield/color-by-value.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsfield/color-by-value.snippet.js +42 -0
- package/lib/runjs-context/snippets/scene/jsfield/format-number.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsfield/format-number.snippet.js +41 -0
- package/lib/runjs-context/snippets/scene/jsfield/innerHTML-value.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsfield/innerHTML-value.snippet.js +40 -0
- package/lib/runjs-context/snippets/scene/jsitem/render-basic.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/jsitem/render-basic.snippet.js +44 -0
- package/lib/runjs-context/snippets/scene/linkage/set-disabled.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/linkage/set-disabled.snippet.js +60 -0
- package/lib/runjs-context/snippets/scene/linkage/set-field-value.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/linkage/set-field-value.snippet.js +59 -0
- package/lib/runjs-context/snippets/scene/linkage/set-required.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/linkage/set-required.snippet.js +60 -0
- package/lib/runjs-context/snippets/scene/linkage/toggle-visible.snippet.d.ts +15 -0
- package/lib/runjs-context/snippets/scene/linkage/toggle-visible.snippet.js +60 -0
- package/lib/runjs-context/snippets/types.d.ts +16 -0
- package/lib/runjs-context/snippets/types.js +24 -0
- package/lib/types.d.ts +398 -0
- package/lib/types.js +43 -0
- package/lib/utils/autoFlowError.d.ts +16 -0
- package/lib/utils/autoFlowError.js +53 -0
- package/lib/utils/constants.d.ts +29 -0
- package/lib/utils/constants.js +77 -0
- package/lib/utils/context.d.ts +40 -0
- package/lib/utils/context.js +63 -0
- package/lib/utils/createCollectionContextMeta.d.ts +11 -0
- package/lib/utils/createCollectionContextMeta.js +117 -0
- package/lib/utils/exceptions.d.ts +22 -0
- package/lib/utils/exceptions.js +62 -0
- package/lib/utils/flow-definitions.d.ts +11 -0
- package/lib/utils/flow-definitions.js +40 -0
- package/lib/utils/index.d.ts +22 -0
- package/lib/utils/index.js +117 -0
- package/lib/utils/inheritance.d.ts +16 -0
- package/lib/utils/inheritance.js +53 -0
- package/lib/utils/params-resolvers.d.ts +51 -0
- package/lib/utils/params-resolvers.js +309 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +34 -0
- package/lib/utils/parsePathnameToViewParams.js +84 -0
- package/lib/utils/safeGlobals.d.ts +16 -0
- package/lib/utils/safeGlobals.js +179 -0
- package/lib/utils/schema-utils.d.ts +40 -0
- package/lib/utils/schema-utils.js +161 -0
- package/lib/utils/serverContextParams.d.ts +28 -0
- package/lib/utils/serverContextParams.js +106 -0
- package/lib/utils/setupRuntimeContextSteps.d.ts +19 -0
- package/lib/utils/setupRuntimeContextSteps.js +88 -0
- package/lib/utils/translation.d.ts +18 -0
- package/lib/utils/translation.js +58 -0
- package/lib/utils/variablesParams.d.ts +51 -0
- package/lib/utils/variablesParams.js +150 -0
- package/lib/views/DialogComponent.d.ts +22 -0
- package/lib/views/DialogComponent.js +98 -0
- package/lib/views/DrawerComponent.d.ts +11 -0
- package/lib/views/DrawerComponent.js +101 -0
- package/lib/views/FlowView.d.ts +76 -0
- package/lib/views/FlowView.js +81 -0
- package/lib/views/PageComponent.d.ts +10 -0
- package/lib/views/PageComponent.js +167 -0
- package/lib/views/ViewNavigation.d.ts +45 -0
- package/lib/views/ViewNavigation.js +97 -0
- package/lib/views/createViewMeta.d.ts +16 -0
- package/lib/views/createViewMeta.js +171 -0
- package/lib/views/index.d.ts +13 -0
- package/lib/views/index.js +48 -0
- package/lib/views/useDialog.d.ts +32 -0
- package/lib/views/useDialog.js +199 -0
- package/lib/views/useDrawer.d.ts +33 -0
- package/lib/views/useDrawer.js +206 -0
- package/lib/views/usePage.d.ts +32 -0
- package/lib/views/usePage.js +193 -0
- package/lib/views/usePatchElement.d.ts +10 -0
- package/lib/views/usePatchElement.js +54 -0
- package/lib/views/usePopover.d.ts +17 -0
- package/lib/views/usePopover.js +159 -0
- package/package.json +37 -0
- package/src/ContextPathProxy.ts +45 -0
- package/src/ElementProxy.ts +69 -0
- package/src/FlowContextProvider.tsx +40 -0
- package/src/FlowDefinition.ts +275 -0
- package/src/JSRunner.ts +84 -0
- package/src/ReactView.tsx +104 -0
- package/src/ViewScopedFlowEngine.ts +75 -0
- package/src/__tests__/ElementProxy.test.ts +51 -0
- package/src/__tests__/JSRunner.test.ts +92 -0
- package/src/__tests__/ReactView.test.tsx +63 -0
- package/src/__tests__/context-path-proxy.test.ts +35 -0
- package/src/__tests__/flow-engine.test.ts +189 -0
- package/src/__tests__/flowContext.test.ts +2012 -0
- package/src/__tests__/flowEngine.saveModel.test.ts +171 -0
- package/src/__tests__/flowI18n.test.ts +28 -0
- package/src/__tests__/flowModel.getFlows.test.ts +61 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +78 -0
- package/src/__tests__/flowRuntimeContext.test.ts +187 -0
- package/src/__tests__/flowSettings.open.test.tsx +1920 -0
- package/src/__tests__/flowSettings.test.ts +566 -0
- package/src/__tests__/globalFlowRegistry.test.ts +77 -0
- package/src/__tests__/isFieldInterfaceMatch.test.ts +51 -0
- package/src/__tests__/metaTreeNodeCache.test.ts +234 -0
- package/src/__tests__/path-aggregation.test.ts +85 -0
- package/src/__tests__/provider.test.tsx +28 -0
- package/src/__tests__/renderHiddenInConfig.test.tsx +91 -0
- package/src/__tests__/runjsContext.test.ts +60 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +212 -0
- package/src/acl/Acl.tsx +109 -0
- package/src/acl/__tests__/Acl.test.tsx +72 -0
- package/src/action-registry/BaseActionRegistry.ts +46 -0
- package/src/action-registry/EngineActionRegistry.ts +32 -0
- package/src/action-registry/ModelActionRegistry.ts +75 -0
- package/src/action-registry/__tests__/engineActionRegistry.test.ts +43 -0
- package/src/action-registry/__tests__/modelActionRegistry.test.ts +107 -0
- package/src/components/DynamicFlowsEditor.tsx +318 -0
- package/src/components/FieldModelRenderer.tsx +62 -0
- package/src/components/FlowContextSelector.tsx +255 -0
- package/src/components/FlowErrorFallback.tsx +316 -0
- package/src/components/FlowModelRenderer.tsx +428 -0
- package/src/components/FormItem.tsx +130 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +226 -0
- package/src/components/common/FlowSettingsButton.tsx +30 -0
- package/src/components/common/index.ts +10 -0
- package/src/components/common/withFlowDesignMode.tsx +49 -0
- package/src/components/dnd/getMousePositionOnElement.ts +115 -0
- package/src/components/dnd/index.tsx +128 -0
- package/src/components/dnd/moveBlock.ts +379 -0
- package/src/components/index.ts +20 -0
- package/src/components/settings/independents/dropdown/FlowsDropdownButton.tsx +279 -0
- package/src/components/settings/independents/dropdown/index.ts +10 -0
- package/src/components/settings/independents/index.ts +2 -0
- package/src/components/settings/index.ts +11 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +617 -0
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +292 -0
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +655 -0
- package/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx +446 -0
- package/src/components/settings/wrappers/contextual/StepSettings.tsx +109 -0
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +217 -0
- package/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx +32 -0
- package/src/components/settings/wrappers/contextual/index.ts +15 -0
- package/src/components/settings/wrappers/embedded/FlowSettings.tsx +258 -0
- package/src/components/settings/wrappers/embedded/FlowsSettings.tsx +111 -0
- package/src/components/settings/wrappers/embedded/FlowsSettingsContent.tsx +96 -0
- package/src/components/settings/wrappers/embedded/index.ts +11 -0
- package/src/components/settings/wrappers/index.ts +5 -0
- package/src/components/subModel/AddSubModelButton.tsx +575 -0
- package/src/components/subModel/LazyDropdown.tsx +714 -0
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +1185 -0
- package/src/components/subModel/__tests__/buildWrapperFieldChildren.test.ts +192 -0
- package/src/components/subModel/__tests__/utils.test.ts +425 -0
- package/src/components/subModel/index.ts +12 -0
- package/src/components/subModel/utils.ts +278 -0
- package/src/components/variables/InlineVariableTag.tsx +97 -0
- package/src/components/variables/SlateVariableEditor.tsx +384 -0
- package/src/components/variables/VariableInput.tsx +342 -0
- package/src/components/variables/VariableTag.tsx +123 -0
- package/src/components/variables/VariableTrigger.tsx +116 -0
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +553 -0
- package/src/components/variables/__tests__/VariableInput.test.tsx +550 -0
- package/src/components/variables/__tests__/VariableTag.test.tsx +347 -0
- package/src/components/variables/__tests__/test-utils.tsx +62 -0
- package/src/components/variables/__tests__/utils.test.ts +310 -0
- package/src/components/variables/index.ts +16 -0
- package/src/components/variables/types.ts +100 -0
- package/src/components/variables/useResolvedMetaTree.ts +76 -0
- package/src/components/variables/utils.ts +192 -0
- package/src/data-source/__tests__/collection.test.ts +58 -0
- package/src/data-source/__tests__/index.test.ts +82 -0
- package/src/data-source/__tests__/jioToJoiSchema.test.ts +56 -0
- package/src/data-source/index.ts +812 -0
- package/src/data-source/jioToJoiSchema.ts +103 -0
- package/src/decorators/index.ts +10 -0
- package/src/decorators/largeField.ts +14 -0
- package/src/emitter.ts +33 -0
- package/src/event-registry/BaseEventRegistry.ts +40 -0
- package/src/event-registry/EngineEventRegistry.ts +26 -0
- package/src/event-registry/ModelEventRegistry.ts +69 -0
- package/src/event-registry/__tests__/engineEventRegistry.test.ts +48 -0
- package/src/executor/FlowExecutor.ts +256 -0
- package/src/executor/__tests__/eventStep.test.ts +157 -0
- package/src/executor/__tests__/flowExecutor.test.ts +163 -0
- package/src/flow-registry/BaseFlowRegistry.ts +91 -0
- package/src/flow-registry/GlobalFlowRegistry.ts +82 -0
- package/src/flow-registry/InstanceFlowRegistry.ts +39 -0
- package/src/flow-registry/__tests__/globalFlowRegistry.test.ts +141 -0
- package/src/flow-registry/__tests__/instance-and-global-registry.test.ts +67 -0
- package/src/flow-registry/__tests__/instanceFlowRegistry.test.ts +83 -0
- package/src/flow-registry/index.ts +12 -0
- package/src/flowContext.ts +1639 -0
- package/src/flowEngine.ts +905 -0
- package/src/flowI18n.ts +96 -0
- package/src/flowSettings.ts +1045 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/useApplyAutoFlows.ts +51 -0
- package/src/hooks/useFlowModel.tsx +59 -0
- package/src/hooks/useFlowModelById.ts +37 -0
- package/src/hooks/useFlowSettingsContext.tsx +37 -0
- package/src/hooks/useFlowStep.tsx +19 -0
- package/src/hooks/useNiceDropdownMaxHeight.ts +34 -0
- package/src/index.ts +38 -0
- package/src/locale/en-US.json +61 -0
- package/src/locale/index.ts +38 -0
- package/src/locale/zh-CN.json +61 -0
- package/src/models/CollectionFieldModel.tsx +269 -0
- package/src/models/DisplayItemModel.tsx +13 -0
- package/src/models/EditableItemModel.tsx +13 -0
- package/src/models/FilterableItemModel.tsx +13 -0
- package/src/models/__tests__/CollectionFieldModel.test.ts +122 -0
- package/src/models/__tests__/defaultParams-on-create.test.ts +83 -0
- package/src/models/__tests__/flow-model-oninit.test.ts +44 -0
- package/src/models/__tests__/flowModel.actions.integration.test.ts +100 -0
- package/src/models/__tests__/flowModel.getFlows.sort.test.ts +100 -0
- package/src/models/__tests__/flowModel.test.ts +2746 -0
- package/src/models/__tests__/flowRegistry.test.ts +512 -0
- package/src/models/__tests__/forkFlowModel.test.ts +1047 -0
- package/src/models/__tests__/model-actions.test.ts +70 -0
- package/src/models/__tests__/model-events.test.ts +69 -0
- package/src/models/flowModel.tsx +1398 -0
- package/src/models/forkFlowModel.ts +287 -0
- package/src/models/index.ts +17 -0
- package/src/provider.tsx +101 -0
- package/src/resources/__tests__/apiResource.test.ts +201 -0
- package/src/resources/__tests__/baseRecordResource.test.ts +262 -0
- package/src/resources/__tests__/filterItem.test.ts +260 -0
- package/src/resources/__tests__/flowResource.test.ts +127 -0
- package/src/resources/apiResource.ts +148 -0
- package/src/resources/baseRecordResource.ts +279 -0
- package/src/resources/filterItem.ts +74 -0
- package/src/resources/flowResource.ts +143 -0
- package/src/resources/index.ts +17 -0
- package/src/resources/multiRecordResource.ts +219 -0
- package/src/resources/singleRecordResource.ts +83 -0
- package/src/resources/sqlResource.ts +299 -0
- package/src/runjs-context/contexts/FlowRunJSContext.ts +190 -0
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +39 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +52 -0
- package/src/runjs-context/contexts/JSCollectionActionRunJSContext.ts +32 -0
- package/src/runjs-context/contexts/JSFieldRunJSContext.ts +43 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +39 -0
- package/src/runjs-context/contexts/JSRecordActionRunJSContext.ts +34 -0
- package/src/runjs-context/contexts/LinkageRunJSContext.ts +35 -0
- package/src/runjs-context/helpers.ts +56 -0
- package/src/runjs-context/index.ts +20 -0
- package/src/runjs-context/registry.ts +65 -0
- package/src/runjs-context/snippets/global/api-request-get.snippet.ts +20 -0
- package/src/runjs-context/snippets/global/api-request-post.snippet.ts +20 -0
- package/src/runjs-context/snippets/global/console-log-ctx.snippet.ts +19 -0
- package/src/runjs-context/snippets/global/copy-record-json.snippet.ts +21 -0
- package/src/runjs-context/snippets/global/copy-to-clipboard.snippet.ts +21 -0
- package/src/runjs-context/snippets/global/log-json-record.snippet.ts +21 -0
- package/src/runjs-context/snippets/global/message-error.snippet.ts +20 -0
- package/src/runjs-context/snippets/global/message-success.snippet.ts +20 -0
- package/src/runjs-context/snippets/global/notification-open.snippet.ts +21 -0
- package/src/runjs-context/snippets/global/open-view-dialog.snippet.ts +26 -0
- package/src/runjs-context/snippets/global/open-view-drawer.snippet.ts +26 -0
- package/src/runjs-context/snippets/global/requireAsync.snippet.ts +24 -0
- package/src/runjs-context/snippets/global/sleep.snippet.ts +21 -0
- package/src/runjs-context/snippets/global/try-catch-async.snippet.ts +22 -0
- package/src/runjs-context/snippets/global/view-navigation-push.snippet.ts +23 -0
- package/src/runjs-context/snippets/global/window-open.snippet.ts +19 -0
- package/src/runjs-context/snippets/index.ts +59 -0
- package/src/runjs-context/snippets/libs/echarts-init.snippet.ts +24 -0
- package/src/runjs-context/snippets/scene/actions/collection-selected-count.snippet.ts +22 -0
- package/src/runjs-context/snippets/scene/actions/iterate-selected-rows.snippet.ts +21 -0
- package/src/runjs-context/snippets/scene/actions/record-id-message.snippet.ts +21 -0
- package/src/runjs-context/snippets/scene/actions/run-action-basic.snippet.ts +18 -0
- package/src/runjs-context/snippets/scene/jsblock/add-event-listener.snippet.ts +29 -0
- package/src/runjs-context/snippets/scene/jsblock/append-style.snippet.ts +20 -0
- package/src/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.ts +24 -0
- package/src/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.ts +19 -0
- package/src/runjs-context/snippets/scene/jsblock/render-basic.snippet.ts +24 -0
- package/src/runjs-context/snippets/scene/jsblock/render-button-handler.snippet.ts +24 -0
- package/src/runjs-context/snippets/scene/jsblock/render-card.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/jsblock/render-react.snippet.ts +34 -0
- package/src/runjs-context/snippets/scene/jsfield/color-by-value.snippet.ts +20 -0
- package/src/runjs-context/snippets/scene/jsfield/format-number.snippet.ts +19 -0
- package/src/runjs-context/snippets/scene/jsfield/innerHTML-value.snippet.ts +18 -0
- package/src/runjs-context/snippets/scene/jsitem/render-basic.snippet.ts +27 -0
- package/src/runjs-context/snippets/scene/linkage/set-disabled.snippet.ts +38 -0
- package/src/runjs-context/snippets/scene/linkage/set-field-value.snippet.ts +37 -0
- package/src/runjs-context/snippets/scene/linkage/set-required.snippet.ts +38 -0
- package/src/runjs-context/snippets/scene/linkage/toggle-visible.snippet.ts +38 -0
- package/src/runjs-context/snippets/types.ts +17 -0
- package/src/types.ts +474 -0
- package/src/utils/__tests__/context.test.ts +93 -0
- package/src/utils/__tests__/params-resolvers.test.ts +652 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +104 -0
- package/src/utils/__tests__/safeGlobals.test.ts +29 -0
- package/src/utils/__tests__/utils.test.ts +1021 -0
- package/src/utils/__tests__/variablesParams.test.ts +52 -0
- package/src/utils/autoFlowError.ts +29 -0
- package/src/utils/constants.ts +60 -0
- package/src/utils/context.ts +70 -0
- package/src/utils/createCollectionContextMeta.ts +122 -0
- package/src/utils/exceptions.ts +36 -0
- package/src/utils/flow-definitions.ts +16 -0
- package/src/utils/index.ts +63 -0
- package/src/utils/inheritance.ts +39 -0
- package/src/utils/params-resolvers.ts +482 -0
- package/src/utils/parsePathnameToViewParams.ts +103 -0
- package/src/utils/safeGlobals.ts +188 -0
- package/src/utils/schema-utils.ts +201 -0
- package/src/utils/serverContextParams.ts +111 -0
- package/src/utils/setupRuntimeContextSteps.ts +89 -0
- package/src/utils/translation.ts +37 -0
- package/src/utils/variablesParams.ts +175 -0
- package/src/views/DialogComponent.tsx +79 -0
- package/src/views/DrawerComponent.tsx +72 -0
- package/src/views/FlowView.tsx +103 -0
- package/src/views/PageComponent.tsx +150 -0
- package/src/views/ViewNavigation.ts +122 -0
- package/src/views/__tests__/FlowView.test.ts +31 -0
- package/src/views/__tests__/ViewNavigation.test.ts +191 -0
- package/src/views/__tests__/usePatchElement.test.tsx +28 -0
- package/src/views/createViewMeta.ts +157 -0
- package/src/views/index.tsx +14 -0
- package/src/views/useDialog.tsx +192 -0
- package/src/views/useDrawer.tsx +205 -0
- package/src/views/usePage.tsx +182 -0
- package/src/views/usePatchElement.tsx +27 -0
- package/src/views/usePopover.tsx +131 -0
|
@@ -0,0 +1,1639 @@
|
|
|
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 { ISchema } from '@formily/json-schema';
|
|
11
|
+
import { observable } from '@formily/reactive';
|
|
12
|
+
import { APIClient } from '@nocobase/sdk';
|
|
13
|
+
import type { Router } from '@remix-run/router';
|
|
14
|
+
import * as antd from 'antd';
|
|
15
|
+
import { MessageInstance } from 'antd/es/message/interface';
|
|
16
|
+
import type { HookAPI } from 'antd/es/modal/useModal';
|
|
17
|
+
import { NotificationInstance } from 'antd/es/notification/interface';
|
|
18
|
+
import _ from 'lodash';
|
|
19
|
+
import pino from 'pino';
|
|
20
|
+
import qs from 'qs';
|
|
21
|
+
import React, { createRef } from 'react';
|
|
22
|
+
import type { Location } from 'react-router-dom';
|
|
23
|
+
import { ACL } from './acl/Acl';
|
|
24
|
+
import { ContextPathProxy } from './ContextPathProxy';
|
|
25
|
+
import { DataSource, DataSourceManager } from './data-source';
|
|
26
|
+
import { FlowEngine } from './flowEngine';
|
|
27
|
+
import { FlowI18n } from './flowI18n';
|
|
28
|
+
import { JSRunner, JSRunnerOptions } from './JSRunner';
|
|
29
|
+
import { createJSRunnerWithVersion } from './runjs-context';
|
|
30
|
+
import { FlowModel, ForkFlowModel } from './models';
|
|
31
|
+
import {
|
|
32
|
+
APIResource,
|
|
33
|
+
BaseRecordResource,
|
|
34
|
+
FlowResource,
|
|
35
|
+
FlowSQLRepository,
|
|
36
|
+
MultiRecordResource,
|
|
37
|
+
SingleRecordResource,
|
|
38
|
+
SQLResource,
|
|
39
|
+
} from './resources';
|
|
40
|
+
import type { ActionDefinition, EventDefinition, ResourceType } from './types';
|
|
41
|
+
import {
|
|
42
|
+
createSafeDocument,
|
|
43
|
+
createSafeWindow,
|
|
44
|
+
escapeT,
|
|
45
|
+
extractPropertyPath,
|
|
46
|
+
extractUsedVariablePaths,
|
|
47
|
+
FlowExitException,
|
|
48
|
+
resolveDefaultParams,
|
|
49
|
+
resolveExpressions,
|
|
50
|
+
} from './utils';
|
|
51
|
+
import { FlowExitAllException } from './utils/exceptions';
|
|
52
|
+
import { enqueueVariablesResolve, JSONValue } from './utils/params-resolvers';
|
|
53
|
+
import type { RecordRef } from './utils/serverContextParams';
|
|
54
|
+
import { buildServerContextParams as _buildServerContextParams } from './utils/serverContextParams';
|
|
55
|
+
import { FlowView, FlowViewer } from './views/FlowView';
|
|
56
|
+
|
|
57
|
+
// Helper: detect a RecordRef-like object
|
|
58
|
+
function isRecordRefLike(val: any): boolean {
|
|
59
|
+
return !!(val && typeof val === 'object' && 'collection' in val && 'filterByTk' in val);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Helper: Filter builder output by subpaths that need server resolution
|
|
63
|
+
// - built can be RecordRef (top-level var) or an object mapping subKey -> RecordRef (e.g., { record: ref })
|
|
64
|
+
function filterBuilderOutputByPaths(built: any, neededPaths: string[]): any {
|
|
65
|
+
if (!neededPaths || neededPaths.length === 0) return undefined;
|
|
66
|
+
if (isRecordRefLike(built)) return built;
|
|
67
|
+
if (built && typeof built === 'object' && !Array.isArray(built)) {
|
|
68
|
+
const out: Record<string, any> = {};
|
|
69
|
+
for (const [k, v] of Object.entries(built)) {
|
|
70
|
+
const hit = neededPaths.some((p) => p === k || p.startsWith(`${k}.`) || p.startsWith(`${k}[`));
|
|
71
|
+
if (hit) out[k] = v;
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type Getter<T = any> = (ctx: FlowContext) => T | Promise<T>;
|
|
79
|
+
|
|
80
|
+
export interface MetaTreeNode {
|
|
81
|
+
name: string;
|
|
82
|
+
title: string;
|
|
83
|
+
type: string;
|
|
84
|
+
interface?: string;
|
|
85
|
+
uiSchema?: ISchema;
|
|
86
|
+
render?: (props: any) => JSX.Element;
|
|
87
|
+
// display?: 'default' | 'flatten' | 'none'; // 显示模式:默认、平铺子菜单、完全隐藏, 用于简化meta树显示层级
|
|
88
|
+
paths: string[];
|
|
89
|
+
parentTitles?: string[]; // 父级标题数组,不包含自身title,第一层可省略
|
|
90
|
+
// 变量禁用状态与原因(用于变量选择器 UI 展示)
|
|
91
|
+
disabled?: boolean | (() => boolean);
|
|
92
|
+
disabledReason?: string | (() => string | undefined);
|
|
93
|
+
children?: MetaTreeNode[] | (() => Promise<MetaTreeNode[]>);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface PropertyMeta {
|
|
97
|
+
type: string;
|
|
98
|
+
title: string;
|
|
99
|
+
interface?: string;
|
|
100
|
+
uiSchema?: ISchema; // TODO: 这个是不是压根没必要啊?
|
|
101
|
+
render?: (props: any) => JSX.Element; // 自定义渲染函数
|
|
102
|
+
// 用于 VariableInput 的排序:数值越大,显示越靠前;相同值保持稳定顺序
|
|
103
|
+
sort?: number;
|
|
104
|
+
// display?: 'default' | 'flatten' | 'none'; // 显示模式:默认、平铺子菜单、完全隐藏, 用于简化meta树显示层级
|
|
105
|
+
properties?: Record<string, PropertyMeta> | (() => Promise<Record<string, PropertyMeta>>);
|
|
106
|
+
// 变量禁用控制:若 disabled 为真(或函数返回真)则禁用
|
|
107
|
+
disabled?: boolean | (() => boolean);
|
|
108
|
+
// 禁用原因(用于 UI 小问号提示),可为函数
|
|
109
|
+
disabledReason?: string | (() => string | undefined);
|
|
110
|
+
// 变量解析参数构造器(用于 variables:resolve 的 contextParams,按属性名归位)。
|
|
111
|
+
// 支持返回 RecordRef 或任意嵌套对象(将被 buildServerContextParams 扁平化,例如 { record: RecordRef } -> 'view.record')。
|
|
112
|
+
buildVariablesParams?: (
|
|
113
|
+
ctx: FlowContext,
|
|
114
|
+
) => RecordRef | Record<string, any> | Promise<RecordRef | Record<string, any> | undefined> | undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A factory function that lazily produces PropertyMeta, and may carry
|
|
118
|
+
// hint fields like `title` and `sort` for UI building before resolution.
|
|
119
|
+
export type PropertyMetaFactory = {
|
|
120
|
+
(): PropertyMeta | Promise<PropertyMeta | null> | null;
|
|
121
|
+
/**
|
|
122
|
+
* 仅作为“是否可能存在子节点”的提示,不影响 meta 工厂本身的惰性特性。
|
|
123
|
+
* - true(默认):视为可能有 children,节点会提供 children 懒加载器(用于级联展开加载子级)。
|
|
124
|
+
* - false:视为没有 children,不渲染展开箭头,且不提供 children 懒加载器;
|
|
125
|
+
* 但节点本身的 meta 工厂仍保持惰性(在需要时仍可解析出 title/type 等信息)。
|
|
126
|
+
*/
|
|
127
|
+
hasChildren?: boolean;
|
|
128
|
+
title?: string;
|
|
129
|
+
sort?: number;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type PropertyMetaOrFactory = PropertyMeta | PropertyMetaFactory;
|
|
133
|
+
|
|
134
|
+
export interface PropertyOptions {
|
|
135
|
+
value?: any;
|
|
136
|
+
once?: boolean; // 是否只定义一次
|
|
137
|
+
get?: Getter;
|
|
138
|
+
cache?: boolean;
|
|
139
|
+
observable?: boolean; // 是否为 observable 属性
|
|
140
|
+
meta?: PropertyMetaOrFactory; // 支持静态、函数和异步函数(工厂函数可带 title/sort)
|
|
141
|
+
// 标记该属性是否在服务端解析:
|
|
142
|
+
// - boolean: true 表示整个顶层变量交给服务端;false 表示仅前端解析
|
|
143
|
+
// - function: 根据子路径决定是否交给服务端(子路径示例:'record.roles[0].name'、'id'、'')
|
|
144
|
+
resolveOnServer?: boolean | ((subPath: string) => boolean);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type RouteOptions = {
|
|
148
|
+
name?: string; // 路由唯一标识
|
|
149
|
+
path?: string; // 路由模板
|
|
150
|
+
params?: Record<string, any>; // 路由参数
|
|
151
|
+
pathname?: string; // 路由的完整路径
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export class FlowContext {
|
|
155
|
+
_props: Record<string, PropertyOptions> = {};
|
|
156
|
+
_methods: Record<string, (...args: any[]) => any> = {};
|
|
157
|
+
protected _cache: Record<string, any> = {};
|
|
158
|
+
protected _observableCache: Record<string, any> = observable.shallow({});
|
|
159
|
+
protected _delegates: FlowContext[] = [];
|
|
160
|
+
protected _pending: Record<string, Promise<any>> = {};
|
|
161
|
+
[key: string]: any;
|
|
162
|
+
#proxy: FlowContext | null = null;
|
|
163
|
+
private _metaNodeCache: WeakMap<PropertyMetaOrFactory, MetaTreeNode> = new WeakMap();
|
|
164
|
+
|
|
165
|
+
createProxy() {
|
|
166
|
+
if (this.#proxy) {
|
|
167
|
+
return this.#proxy;
|
|
168
|
+
}
|
|
169
|
+
this.#proxy = new Proxy(this, {
|
|
170
|
+
get: (target, key, receiver) => {
|
|
171
|
+
if (typeof key === 'string') {
|
|
172
|
+
// 1. 检查是否为直接属性或方法,如果是则跳过委托链查找
|
|
173
|
+
if (Reflect.has(target, key)) {
|
|
174
|
+
const val = Reflect.get(target, key, receiver);
|
|
175
|
+
if (typeof val === 'function') return val.bind(target);
|
|
176
|
+
return val;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 2. 优先查找自身 _props
|
|
180
|
+
if (Object.prototype.hasOwnProperty.call(target._props, key)) {
|
|
181
|
+
return target._getOwnProperty(key, this.createProxy());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 3. 优先查找自身 _methods
|
|
185
|
+
if (Object.prototype.hasOwnProperty.call(target._methods, key)) {
|
|
186
|
+
return target._getOwnMethod(key, this.createProxy());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 4. 只有在自身没有该属性时才查找委托链
|
|
190
|
+
const found = this._findInDelegates(target._delegates, key);
|
|
191
|
+
if (found !== undefined) return found.result;
|
|
192
|
+
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
return Reflect.get(target, key, receiver);
|
|
196
|
+
},
|
|
197
|
+
has: (target, key) => {
|
|
198
|
+
if (typeof key === 'string') {
|
|
199
|
+
// 1. 检查直接属性
|
|
200
|
+
if (Reflect.has(target, key)) return true;
|
|
201
|
+
|
|
202
|
+
// 2. 检查 _props 和 _methods
|
|
203
|
+
if (Object.prototype.hasOwnProperty.call(target._props, key)) return true;
|
|
204
|
+
if (Object.prototype.hasOwnProperty.call(target._methods, key)) return true;
|
|
205
|
+
|
|
206
|
+
// 3. 检查委托链
|
|
207
|
+
if (this._hasInDelegates(target._delegates, key)) return true;
|
|
208
|
+
}
|
|
209
|
+
return Reflect.has(target, key);
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
return this.#proxy;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
constructor() {
|
|
216
|
+
return this.createProxy();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
defineProperty(key: string, options: PropertyOptions) {
|
|
220
|
+
if (this._props[key] && this._props[key]?.once) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 清除旧属性对应的缓存
|
|
225
|
+
const oldOptions = this._props[key];
|
|
226
|
+
if (oldOptions?.meta) {
|
|
227
|
+
this._clearMetaNodeCacheFor(oldOptions.meta);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this._props[key] = options;
|
|
231
|
+
delete this._observableCache[key]; // 清除旧的 observable 缓存
|
|
232
|
+
delete this._cache[key];
|
|
233
|
+
// 用 Object.defineProperty 挂载到实例上,便于 ctx.foo 直接访问
|
|
234
|
+
Object.defineProperty(this, key, {
|
|
235
|
+
configurable: true,
|
|
236
|
+
enumerable: true,
|
|
237
|
+
get: () => this._getOwnProperty(key, this.createProxy()),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
defineMethod(name: string, fn: (...args: any[]) => any, des?: string) {
|
|
242
|
+
this._methods[name] = fn;
|
|
243
|
+
Object.defineProperty(this, name, {
|
|
244
|
+
configurable: true,
|
|
245
|
+
enumerable: false,
|
|
246
|
+
writable: false,
|
|
247
|
+
value: fn.bind(this.createProxy()),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
removeCache(key: string) {
|
|
252
|
+
if (key in this._observableCache) {
|
|
253
|
+
delete this._observableCache[key];
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (key in this._cache) {
|
|
257
|
+
delete this._cache[key];
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
if (key in this._pending) {
|
|
261
|
+
delete this._pending[key];
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
// 递归清理委托链
|
|
265
|
+
for (const delegate of this._delegates) {
|
|
266
|
+
if (delegate.removeCache(key)) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
delegate(ctx: FlowContext) {
|
|
273
|
+
if (!(ctx instanceof FlowContext)) {
|
|
274
|
+
throw new Error('Delegate must be an instance of FlowContext');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 防止重复委托同一个 context
|
|
278
|
+
if (this._delegates.includes(ctx)) {
|
|
279
|
+
console.warn(`[FlowContext] delegate - skip duplicate delegate: ${this._delegates.length}`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this._delegates.unshift(ctx);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
addDelegate(ctx: FlowContext) {
|
|
287
|
+
if (!(ctx instanceof FlowContext)) {
|
|
288
|
+
throw new Error('Delegate must be an instance of FlowContext');
|
|
289
|
+
}
|
|
290
|
+
if (!this._delegates.includes(ctx)) {
|
|
291
|
+
this._delegates.unshift(ctx);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
clearDelegates() {
|
|
296
|
+
this._delegates = [];
|
|
297
|
+
this._metaNodeCache = new WeakMap(); // 清除缓存
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
removeDelegate(ctx: FlowContext) {
|
|
301
|
+
if (!(ctx instanceof FlowContext)) {
|
|
302
|
+
throw new Error('Delegate must be an instance of FlowContext');
|
|
303
|
+
}
|
|
304
|
+
const index = this._delegates.indexOf(ctx);
|
|
305
|
+
if (index !== -1) {
|
|
306
|
+
this._delegates.splice(index, 1);
|
|
307
|
+
// 不需要清除缓存:委托链变化不影响基于 meta 内容的缓存
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 清除特定 meta 对象的缓存
|
|
313
|
+
*/
|
|
314
|
+
private _clearMetaNodeCacheFor(meta: PropertyMetaOrFactory): void {
|
|
315
|
+
this._metaNodeCache.delete(meta);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
has(key: string) {
|
|
319
|
+
return !!this._props[key];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 获取属性元数据树
|
|
324
|
+
* 返回的 MetaTreeNode 中可能包含异步的延迟加载逻辑
|
|
325
|
+
* @param value 可选参数,指定要获取的属性路径,格式: "{{ ctx.propertyName }}"
|
|
326
|
+
* @returns MetaTreeNode[] 根级属性的元数据树,或指定路径的子树
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* // 同步调用,获取完整 meta tree
|
|
330
|
+
* const metaTree = flowContext.getPropertyMetaTree();
|
|
331
|
+
*
|
|
332
|
+
* // 获取指定属性的子树
|
|
333
|
+
* const subTree = flowContext.getPropertyMetaTree("{{ ctx.user }}");
|
|
334
|
+
*
|
|
335
|
+
* // 获取多层级属性的子树
|
|
336
|
+
* const profileTree = flowContext.getPropertyMetaTree("{{ ctx.user.profile }}");
|
|
337
|
+
*/
|
|
338
|
+
getPropertyMetaTree(value?: string, options?: { flatten?: boolean }): MetaTreeNode[] {
|
|
339
|
+
const metaMap = this._getPropertiesMeta();
|
|
340
|
+
|
|
341
|
+
// 如果有 value 参数,尝试返回对应属性的子树
|
|
342
|
+
if (value) {
|
|
343
|
+
const propertyPath = extractPropertyPath(value);
|
|
344
|
+
if (propertyPath && propertyPath.length > 0) {
|
|
345
|
+
const loadChildrenFrom = async (
|
|
346
|
+
metaOrFactory: PropertyMeta | (() => PropertyMeta | Promise<PropertyMeta>),
|
|
347
|
+
fullPath: string[],
|
|
348
|
+
finalKey: string,
|
|
349
|
+
): Promise<MetaTreeNode[]> => {
|
|
350
|
+
try {
|
|
351
|
+
const meta: PropertyMeta =
|
|
352
|
+
typeof metaOrFactory === 'function' ? await (metaOrFactory as any)() : (metaOrFactory as PropertyMeta);
|
|
353
|
+
if (!meta?.properties) return [];
|
|
354
|
+
let props = meta.properties;
|
|
355
|
+
if (typeof props === 'function') {
|
|
356
|
+
const resolved = await props();
|
|
357
|
+
meta.properties = resolved;
|
|
358
|
+
props = resolved;
|
|
359
|
+
}
|
|
360
|
+
const childNodes = this.#createChildNodes(props as Record<string, PropertyMeta>, fullPath, [], meta);
|
|
361
|
+
return Array.isArray(childNodes) ? childNodes : await childNodes();
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.warn(`Failed to load meta for ${finalKey}:`, error);
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const targetMeta = this.#findMetaByPath(propertyPath);
|
|
368
|
+
if (targetMeta) {
|
|
369
|
+
const [finalKey, metaOrFactory, fullPath] = targetMeta;
|
|
370
|
+
const depth = propertyPath.length;
|
|
371
|
+
|
|
372
|
+
if (depth === 1) {
|
|
373
|
+
if (typeof metaOrFactory === 'function') {
|
|
374
|
+
return (() => loadChildrenFrom(metaOrFactory, fullPath, finalKey)) as unknown as MetaTreeNode[];
|
|
375
|
+
}
|
|
376
|
+
if (metaOrFactory.properties) {
|
|
377
|
+
if (typeof metaOrFactory.properties === 'function') {
|
|
378
|
+
return (() => loadChildrenFrom(metaOrFactory, fullPath, finalKey)) as unknown as MetaTreeNode[];
|
|
379
|
+
}
|
|
380
|
+
const childNodes = this.#createChildNodes(metaOrFactory.properties, fullPath, [], metaOrFactory);
|
|
381
|
+
return Array.isArray(childNodes) ? childNodes : [];
|
|
382
|
+
}
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (typeof metaOrFactory === 'function') {
|
|
387
|
+
if (options?.flatten) {
|
|
388
|
+
// 统一语义:当请求子层路径且 flatten=true 时,直接返回其 children 列表
|
|
389
|
+
return (() => loadChildrenFrom(metaOrFactory, fullPath, finalKey)) as unknown as MetaTreeNode[];
|
|
390
|
+
}
|
|
391
|
+
const parentTitles = this.#buildParentTitles(fullPath);
|
|
392
|
+
return [this.#toTreeNode(finalKey, metaOrFactory, fullPath, parentTitles)];
|
|
393
|
+
}
|
|
394
|
+
if (metaOrFactory.properties) {
|
|
395
|
+
const parentTitles = [...this.#buildParentTitles(fullPath), metaOrFactory.title];
|
|
396
|
+
const childNodes = this.#createChildNodes(metaOrFactory.properties, fullPath, parentTitles, metaOrFactory);
|
|
397
|
+
return Array.isArray(childNodes) ? childNodes : [];
|
|
398
|
+
}
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
// 未找到目标路径,返回空数组
|
|
402
|
+
return [];
|
|
403
|
+
} else if (propertyPath === null) {
|
|
404
|
+
console.warn(
|
|
405
|
+
`[FlowContext] getPropertyMetaTree - unsupported value format: "${value}". Only "{{ ctx.propertyName }}" format is supported. Returning empty meta tree.`,
|
|
406
|
+
);
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 根级节点按 meta.sort 降序排列(未设置默认为 0)
|
|
412
|
+
const sorted = (Object.entries(metaMap) as [string, PropertyMetaOrFactory][]).sort(([, a], [, b]) => {
|
|
413
|
+
const sa = (typeof a === 'function' ? a.sort : a?.sort) ?? 0;
|
|
414
|
+
const sb = (typeof b === 'function' ? b.sort : b?.sort) ?? 0;
|
|
415
|
+
return sb - sa;
|
|
416
|
+
});
|
|
417
|
+
return sorted.map(([key, metaOrFactory]) => this.#toTreeNode(key, metaOrFactory, [key], []));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
#createChildNodes(
|
|
421
|
+
properties: Record<string, PropertyMeta> | (() => Promise<Record<string, PropertyMeta>>),
|
|
422
|
+
parentPaths: string[] = [],
|
|
423
|
+
parentTitles: string[] = [],
|
|
424
|
+
parentMeta?: PropertyMeta, // 传入父级 meta 以便缓存结果
|
|
425
|
+
): MetaTreeNode[] | (() => Promise<MetaTreeNode[]>) {
|
|
426
|
+
return typeof properties === 'function'
|
|
427
|
+
? async () => {
|
|
428
|
+
const resolved = await properties();
|
|
429
|
+
// 缓存解析结果,避免下次重复调用
|
|
430
|
+
if (parentMeta) {
|
|
431
|
+
parentMeta.properties = resolved;
|
|
432
|
+
}
|
|
433
|
+
const entries = Object.entries(resolved) as [string, PropertyMeta][];
|
|
434
|
+
entries.sort(([, a], [, b]) => (b?.sort ?? 0) - (a?.sort ?? 0));
|
|
435
|
+
return entries.map(([name, meta]) => this.#toTreeNode(name, meta, [...parentPaths, name], parentTitles));
|
|
436
|
+
}
|
|
437
|
+
: (Object.entries(properties) as [string, PropertyMeta][])
|
|
438
|
+
.sort(([, a], [, b]) => (b?.sort ?? 0) - (a?.sort ?? 0))
|
|
439
|
+
.map(([name, meta]) => this.#toTreeNode(name, meta, [...parentPaths, name], parentTitles));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* 根据属性路径查找对应的 meta
|
|
444
|
+
* @param propertyPath 属性路径数组,例如 ["aaa", "bbb"]
|
|
445
|
+
* @returns [finalKey, metaOrFactory, fullPath] 或 null
|
|
446
|
+
*/
|
|
447
|
+
#findMetaByPath(propertyPath: string[]): [string, PropertyMetaOrFactory, string[]] | null {
|
|
448
|
+
if (propertyPath.length === 0) return null;
|
|
449
|
+
|
|
450
|
+
const [firstKey, ...remainingPath] = propertyPath;
|
|
451
|
+
|
|
452
|
+
// 首先查找第一个属性,这里利用委托链机制
|
|
453
|
+
// 1. 查找自身的属性
|
|
454
|
+
const ownProperty = this._props[firstKey];
|
|
455
|
+
if (ownProperty?.meta) {
|
|
456
|
+
return this.#findMetaInProperty(firstKey, ownProperty.meta, remainingPath, [firstKey]);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// 2 进一步递归查找更深层委托链(_getPropertiesMeta 会递归收集,但此处原来仅检查了一层,导致不一致)
|
|
460
|
+
const deepMeta = this.#findMetaInDelegatesDeep(this._delegates, firstKey);
|
|
461
|
+
if (deepMeta) {
|
|
462
|
+
return this.#findMetaInProperty(firstKey, deepMeta, remainingPath, [firstKey]);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* 递归在委托链中查找指定 key 的 meta(只返回 metaOrFactory,不解析路径)。
|
|
470
|
+
*/
|
|
471
|
+
#findMetaInDelegatesDeep(delegates: FlowContext[], key: string): PropertyMetaOrFactory | null {
|
|
472
|
+
for (const delegate of delegates) {
|
|
473
|
+
const prop = delegate._props[key];
|
|
474
|
+
if (prop?.meta) return prop.meta;
|
|
475
|
+
const deeper = this.#findMetaInDelegatesDeep(delegate._delegates, key);
|
|
476
|
+
if (deeper) return deeper;
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 在给定属性的 meta 中查找剩余路径
|
|
483
|
+
*/
|
|
484
|
+
#findMetaInProperty(
|
|
485
|
+
currentKey: string,
|
|
486
|
+
metaOrFactory: PropertyMetaOrFactory,
|
|
487
|
+
remainingPath: string[],
|
|
488
|
+
currentPath: string[],
|
|
489
|
+
): [string, PropertyMetaOrFactory, string[]] | null {
|
|
490
|
+
// 如果已经到了最后一层,直接返回当前的 meta
|
|
491
|
+
if (remainingPath.length === 0) {
|
|
492
|
+
return [currentKey, metaOrFactory as any, currentPath];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// 如果还有剩余路径,但当前是函数类型,构建一个新的异步函数继续解析剩余路径
|
|
496
|
+
if (typeof metaOrFactory === 'function') {
|
|
497
|
+
const finalKey = remainingPath[remainingPath.length - 1];
|
|
498
|
+
const finalPath = [...currentPath, ...remainingPath];
|
|
499
|
+
|
|
500
|
+
const wrappedFactory = async (): Promise<PropertyMeta> => {
|
|
501
|
+
const resolvedMeta = await metaOrFactory();
|
|
502
|
+
const result = await this.#resolvePathInMetaAsync(resolvedMeta, remainingPath);
|
|
503
|
+
return result;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
return [finalKey, wrappedFactory, finalPath];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// 如果还有剩余路径,且是同步 meta,尝试继续查找下一层
|
|
510
|
+
if (metaOrFactory.properties) {
|
|
511
|
+
const [nextKey, ...restPath] = remainingPath;
|
|
512
|
+
const nextPath = [...currentPath, nextKey];
|
|
513
|
+
|
|
514
|
+
// properties 是异步的,构建新的异步函数继续解析
|
|
515
|
+
if (typeof metaOrFactory.properties === 'function') {
|
|
516
|
+
const finalKey = remainingPath[remainingPath.length - 1];
|
|
517
|
+
const finalPath = [...currentPath, ...remainingPath];
|
|
518
|
+
|
|
519
|
+
const wrappedFactory = async (): Promise<PropertyMeta> => {
|
|
520
|
+
const propertiesFactory = metaOrFactory.properties as () => Promise<Record<string, PropertyMeta>>;
|
|
521
|
+
const resolvedProperties = await propertiesFactory();
|
|
522
|
+
// 缓存解析结果,避免下次重复调用
|
|
523
|
+
metaOrFactory.properties = resolvedProperties;
|
|
524
|
+
const startMeta = resolvedProperties[nextKey];
|
|
525
|
+
if (!startMeta) {
|
|
526
|
+
throw new Error(`Property ${nextKey} not found in resolved properties`);
|
|
527
|
+
}
|
|
528
|
+
const result = await this.#resolvePathInMetaAsync(startMeta, restPath);
|
|
529
|
+
return result;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
return [finalKey, wrappedFactory, finalPath];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// properties 是同步的,继续查找
|
|
536
|
+
const nextMeta = metaOrFactory.properties[nextKey];
|
|
537
|
+
if (nextMeta) {
|
|
538
|
+
return this.#findMetaInProperty(nextKey, nextMeta, restPath, nextPath);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* 在给定的 meta 中递归解析路径
|
|
547
|
+
*/
|
|
548
|
+
#resolvePathInMeta(meta: PropertyMeta, path: string[]): PropertyMeta | null {
|
|
549
|
+
if (path.length === 0) {
|
|
550
|
+
return meta;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
let current = meta;
|
|
554
|
+
for (const key of path) {
|
|
555
|
+
const properties = _.get(current, 'properties');
|
|
556
|
+
if (!properties || typeof properties === 'function') {
|
|
557
|
+
return null; // 无法同步解析异步 properties
|
|
558
|
+
}
|
|
559
|
+
current = _.get(properties, key);
|
|
560
|
+
if (!current) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return current;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* 支持异步 properties 的路径解析:
|
|
570
|
+
* - 遇到 properties 为函数时会 await 并缓存其结果
|
|
571
|
+
* - 持续向下解析直到到达最终的 meta
|
|
572
|
+
* 若解析失败则抛出异常,由调用方自行处理
|
|
573
|
+
*/
|
|
574
|
+
async #resolvePathInMetaAsync(meta: PropertyMeta, path: string[]): Promise<PropertyMeta> {
|
|
575
|
+
if (path.length === 0) return meta;
|
|
576
|
+
|
|
577
|
+
let current: PropertyMeta = meta;
|
|
578
|
+
for (const key of path) {
|
|
579
|
+
let properties = _.get(current, 'properties');
|
|
580
|
+
|
|
581
|
+
if (!properties) {
|
|
582
|
+
throw new Error(`Property path not found: ${path.join('.')}`);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (typeof properties === 'function') {
|
|
586
|
+
const resolved = await properties();
|
|
587
|
+
current.properties = resolved;
|
|
588
|
+
properties = resolved;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const next = (properties as Record<string, PropertyMeta>)[key];
|
|
592
|
+
if (!next) {
|
|
593
|
+
throw new Error(`Property ${key} not found while resolving path: ${path.join('.')}`);
|
|
594
|
+
}
|
|
595
|
+
current = next;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return current;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* 构建 parentTitles 数组,通过递归查找每个路径层级对应的 meta title
|
|
603
|
+
* @param propertyPath 属性路径数组,例如 ['aaa', 'bbb', 'ccc']
|
|
604
|
+
* @param excludeLastLevel 是否排除最后一层,默认为 true(parentTitles 不包含当前节点)
|
|
605
|
+
* @returns string[] 父级标题数组
|
|
606
|
+
*/
|
|
607
|
+
#buildParentTitles(propertyPath: string[], excludeLastLevel = true): string[] {
|
|
608
|
+
if (propertyPath.length === 0) return [];
|
|
609
|
+
|
|
610
|
+
const pathToProcess = excludeLastLevel ? propertyPath.slice(0, -1) : propertyPath;
|
|
611
|
+
if (pathToProcess.length === 0) return [];
|
|
612
|
+
|
|
613
|
+
const parentTitles: string[] = [];
|
|
614
|
+
|
|
615
|
+
// 从根级开始逐层查找 meta title
|
|
616
|
+
let currentMetas = this._getPropertiesMeta();
|
|
617
|
+
|
|
618
|
+
for (let i = 0; i < pathToProcess.length; i++) {
|
|
619
|
+
const currentKey = pathToProcess[i];
|
|
620
|
+
const currentMeta = currentMetas[currentKey];
|
|
621
|
+
|
|
622
|
+
if (!currentMeta || typeof currentMeta === 'function') {
|
|
623
|
+
parentTitles.push(currentKey);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
// 同步 meta,使用 title
|
|
627
|
+
parentTitles.push(currentMeta.title || currentKey);
|
|
628
|
+
|
|
629
|
+
// 为下一层级准备 meta 映射
|
|
630
|
+
if (i < pathToProcess.length - 1 && currentMeta.properties && typeof currentMeta.properties !== 'function') {
|
|
631
|
+
currentMetas = currentMeta.properties as Record<string, PropertyMeta>;
|
|
632
|
+
} else if (i < pathToProcess.length - 1) {
|
|
633
|
+
// 如果下一层是异步的或者不存在,无法继续,使用路径名填充剩余部分
|
|
634
|
+
for (let j = i + 1; j < pathToProcess.length; j++) {
|
|
635
|
+
parentTitles.push(pathToProcess[j]);
|
|
636
|
+
}
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return parentTitles;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
#toTreeNode(
|
|
645
|
+
name: string,
|
|
646
|
+
metaOrFactory: PropertyMetaOrFactory,
|
|
647
|
+
paths: string[] = [name],
|
|
648
|
+
parentTitles: string[] = [],
|
|
649
|
+
): MetaTreeNode {
|
|
650
|
+
// 检查缓存
|
|
651
|
+
const cached = this._metaNodeCache.get(metaOrFactory);
|
|
652
|
+
if (cached) {
|
|
653
|
+
// 更新路径信息(因为同一个 meta 可能在不同路径下使用)
|
|
654
|
+
cached.paths = paths;
|
|
655
|
+
cached.parentTitles = parentTitles.length > 0 ? parentTitles : undefined;
|
|
656
|
+
return cached;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let node: MetaTreeNode;
|
|
660
|
+
|
|
661
|
+
// 计算禁用状态与原因的帮助函数
|
|
662
|
+
const computeDisabledFromMeta = (m: PropertyMeta): { disabled: boolean; reason?: string } => {
|
|
663
|
+
if (!m) return { disabled: false };
|
|
664
|
+
const disabledVal = typeof m.disabled === 'function' ? m.disabled() : m.disabled;
|
|
665
|
+
const reason = typeof m.disabledReason === 'function' ? m.disabledReason() : m.disabledReason;
|
|
666
|
+
return { disabled: !!disabledVal, reason };
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
if (typeof metaOrFactory === 'function') {
|
|
670
|
+
const initialTitle = name;
|
|
671
|
+
const hasChildrenHint = (metaOrFactory as PropertyMetaFactory).hasChildren;
|
|
672
|
+
node = {
|
|
673
|
+
name,
|
|
674
|
+
title: metaOrFactory.title || initialTitle, // 初始使用 name 作为 title
|
|
675
|
+
type: 'object', // 初始类型
|
|
676
|
+
interface: undefined,
|
|
677
|
+
uiSchema: undefined,
|
|
678
|
+
paths,
|
|
679
|
+
parentTitles: parentTitles.length > 0 ? parentTitles : undefined,
|
|
680
|
+
disabled: () => {
|
|
681
|
+
const maybe = metaOrFactory();
|
|
682
|
+
if (maybe && typeof maybe['then'] === 'function') return false;
|
|
683
|
+
return computeDisabledFromMeta(maybe as PropertyMeta).disabled;
|
|
684
|
+
},
|
|
685
|
+
disabledReason: () => {
|
|
686
|
+
const maybe = metaOrFactory();
|
|
687
|
+
if (maybe && typeof maybe['then'] === 'function') return undefined;
|
|
688
|
+
return computeDisabledFromMeta(maybe as PropertyMeta).reason;
|
|
689
|
+
},
|
|
690
|
+
// 注意:即便 hasChildren === false,也只是“没有子节点”的 UI 提示;
|
|
691
|
+
// 节点自身依然通过 meta 工厂保持惰性特性(需要时可解析出 title/type 等)。
|
|
692
|
+
// 这里仅在 hasChildren !== false 时,提供子节点的懒加载逻辑。
|
|
693
|
+
children:
|
|
694
|
+
hasChildrenHint === false
|
|
695
|
+
? undefined
|
|
696
|
+
: async () => {
|
|
697
|
+
try {
|
|
698
|
+
const meta = await metaOrFactory();
|
|
699
|
+
const finalTitle = meta?.title || name;
|
|
700
|
+
node.title = finalTitle;
|
|
701
|
+
node.type = meta?.type;
|
|
702
|
+
node.interface = meta?.interface;
|
|
703
|
+
node.uiSchema = meta?.uiSchema;
|
|
704
|
+
// parentTitles 保持不变,因为它不包含自身 title
|
|
705
|
+
|
|
706
|
+
if (!meta?.properties) return [];
|
|
707
|
+
|
|
708
|
+
const childNodes = this.#createChildNodes(
|
|
709
|
+
meta.properties,
|
|
710
|
+
paths,
|
|
711
|
+
[...parentTitles, finalTitle],
|
|
712
|
+
meta,
|
|
713
|
+
);
|
|
714
|
+
const resolvedChildren = Array.isArray(childNodes) ? childNodes : await childNodes();
|
|
715
|
+
|
|
716
|
+
// 更新 children 为解析后的结果
|
|
717
|
+
node.children = resolvedChildren;
|
|
718
|
+
|
|
719
|
+
return resolvedChildren;
|
|
720
|
+
} catch (error) {
|
|
721
|
+
console.warn(`Failed to load meta for ${name}:`, error);
|
|
722
|
+
return [];
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
} else {
|
|
727
|
+
// 同步 meta:直接创建节点
|
|
728
|
+
const nodeTitle = metaOrFactory.title;
|
|
729
|
+
const { disabled, reason } = computeDisabledFromMeta(metaOrFactory);
|
|
730
|
+
node = {
|
|
731
|
+
name,
|
|
732
|
+
title: nodeTitle,
|
|
733
|
+
type: metaOrFactory.type,
|
|
734
|
+
interface: metaOrFactory.interface,
|
|
735
|
+
uiSchema: metaOrFactory.uiSchema,
|
|
736
|
+
paths,
|
|
737
|
+
parentTitles: parentTitles.length > 0 ? parentTitles : undefined,
|
|
738
|
+
disabled,
|
|
739
|
+
disabledReason: reason,
|
|
740
|
+
children: metaOrFactory.properties
|
|
741
|
+
? this.#createChildNodes(metaOrFactory.properties, paths, [...parentTitles, nodeTitle], metaOrFactory)
|
|
742
|
+
: undefined,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// 缓存节点
|
|
747
|
+
this._metaNodeCache.set(metaOrFactory, node);
|
|
748
|
+
|
|
749
|
+
return node;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
_getPropertiesMeta(): Record<string, PropertyMetaOrFactory> {
|
|
753
|
+
const metaMap: Record<string, PropertyMetaOrFactory> = {};
|
|
754
|
+
|
|
755
|
+
// 先处理委托链(委托链的 meta 优先级较低)
|
|
756
|
+
for (const delegate of this._delegates) {
|
|
757
|
+
Object.assign(metaMap, delegate._getPropertiesMeta());
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// 处理自身属性(自身属性优先级较高)
|
|
761
|
+
for (const [key, options] of Object.entries(this._props)) {
|
|
762
|
+
if (options.meta) {
|
|
763
|
+
metaMap[key] = typeof options.meta === 'function' ? (options.meta as PropertyMetaFactory) : options.meta;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return metaMap;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// 只查找自身 _props
|
|
771
|
+
protected _getOwnProperty(key: string, currentContext): any {
|
|
772
|
+
const options = this._props[key];
|
|
773
|
+
if (!options) return undefined;
|
|
774
|
+
|
|
775
|
+
// 静态值
|
|
776
|
+
if ('value' in options) {
|
|
777
|
+
return options.value;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// get 方法
|
|
781
|
+
if (options.get) {
|
|
782
|
+
if (options.cache === false) {
|
|
783
|
+
return options.get(currentContext);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const cacheKey = options.observable ? '_observableCache' : '_cache';
|
|
787
|
+
|
|
788
|
+
if (key in this[cacheKey]) {
|
|
789
|
+
return this[cacheKey][key];
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (this._pending[key]) return this._pending[key];
|
|
793
|
+
|
|
794
|
+
// 支持 async getter 并发排队
|
|
795
|
+
const result = options.get(this.createProxy());
|
|
796
|
+
|
|
797
|
+
// 判断是否为 Promise/thenable
|
|
798
|
+
const isPromise =
|
|
799
|
+
(typeof result === 'object' && result !== null && typeof (result as any).then === 'function') ||
|
|
800
|
+
(typeof result === 'function' && typeof (result as any).then === 'function');
|
|
801
|
+
|
|
802
|
+
if (isPromise) {
|
|
803
|
+
this._pending[key] = (result as Promise<any>).then(
|
|
804
|
+
(v) => {
|
|
805
|
+
this[cacheKey][key] = v;
|
|
806
|
+
delete this._pending[key];
|
|
807
|
+
return v;
|
|
808
|
+
},
|
|
809
|
+
(err) => {
|
|
810
|
+
delete this._pending[key];
|
|
811
|
+
throw err;
|
|
812
|
+
},
|
|
813
|
+
);
|
|
814
|
+
return this._pending[key];
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// sync 直接缓存
|
|
818
|
+
this[cacheKey][key] = result;
|
|
819
|
+
return result;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// 只查找自身 _methods
|
|
826
|
+
protected _getOwnMethod(key: string, flowContext?: FlowContext): any {
|
|
827
|
+
const fn = this._methods[key];
|
|
828
|
+
if (typeof fn === 'function') {
|
|
829
|
+
return fn.bind(flowContext);
|
|
830
|
+
}
|
|
831
|
+
return fn;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
_findPropertyInDelegates(delegates: FlowContext[], key: string): PropertyOptions | undefined {
|
|
835
|
+
for (const delegate of delegates) {
|
|
836
|
+
// 1. 查找委托的 _props
|
|
837
|
+
if (Object.prototype.hasOwnProperty.call(delegate._props, key)) {
|
|
838
|
+
return delegate._props[key];
|
|
839
|
+
}
|
|
840
|
+
// 2. 递归查找更深层的委托链
|
|
841
|
+
const found = this._findPropertyInDelegates(delegate._delegates, key);
|
|
842
|
+
if (found !== undefined) return found;
|
|
843
|
+
}
|
|
844
|
+
return undefined;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
_findInDelegates(delegates: FlowContext[], key: string): any {
|
|
848
|
+
for (const delegate of delegates) {
|
|
849
|
+
// 1. 查找委托的 _props
|
|
850
|
+
if (Object.prototype.hasOwnProperty.call(delegate._props, key)) {
|
|
851
|
+
return {
|
|
852
|
+
result: delegate._getOwnProperty(key, this.createProxy()),
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
// 2. 查找委托的 _methods
|
|
856
|
+
if (Object.prototype.hasOwnProperty.call(delegate._methods, key)) {
|
|
857
|
+
return {
|
|
858
|
+
result: delegate._getOwnMethod(key, this.createProxy()),
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
// 3. 递归查找更深层的委托链
|
|
862
|
+
const found = this._findInDelegates(delegate._delegates, key);
|
|
863
|
+
if (found !== undefined) return found;
|
|
864
|
+
}
|
|
865
|
+
return undefined;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// 递归查找委托链
|
|
869
|
+
_hasInDelegates(delegates: FlowContext[], key: string): boolean {
|
|
870
|
+
for (const delegate of delegates) {
|
|
871
|
+
if (Object.prototype.hasOwnProperty.call(delegate._props, key)) return true;
|
|
872
|
+
if (Object.prototype.hasOwnProperty.call(delegate._methods, key)) return true;
|
|
873
|
+
if (this._hasInDelegates(delegate._delegates, key)) return true;
|
|
874
|
+
}
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* 获取属性定义选项(包含代理链)。
|
|
880
|
+
*
|
|
881
|
+
* - 优先查找当前上下文自身通过 defineProperty 注册的属性定义
|
|
882
|
+
* - 若自身不存在,则沿委托链(delegates)向上查找第一个命中的定义
|
|
883
|
+
*
|
|
884
|
+
* @param key 顶层属性名(例如 'user'、'view')
|
|
885
|
+
* @returns 属性定义选项,或 undefined(未定义)
|
|
886
|
+
*/
|
|
887
|
+
getPropertyOptions(key: string): PropertyOptions | undefined {
|
|
888
|
+
if (Object.prototype.hasOwnProperty.call(this._props, key)) {
|
|
889
|
+
return this._props[key];
|
|
890
|
+
}
|
|
891
|
+
return this._findPropertyInDelegates(this._delegates, key);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export class FlowRunjsContext extends FlowContext {
|
|
896
|
+
constructor(delegate: FlowContext) {
|
|
897
|
+
super();
|
|
898
|
+
this.addDelegate(delegate);
|
|
899
|
+
// Expose React and antd only within runjs context
|
|
900
|
+
// This keeps the scope minimal while enabling React/AntD rendering in scripts
|
|
901
|
+
this.defineProperty('React', { value: React });
|
|
902
|
+
this.defineProperty('antd', { value: antd });
|
|
903
|
+
this.defineMethod(
|
|
904
|
+
'dispatchModelEvent',
|
|
905
|
+
async (modelOrUid: FlowModel | string, eventName: string, inputArgs?: Record<string, any>) => {
|
|
906
|
+
let model: FlowModel | null = null;
|
|
907
|
+
if (typeof modelOrUid === 'string') {
|
|
908
|
+
model = await this.engine.loadModel({ uid: modelOrUid });
|
|
909
|
+
} else if (modelOrUid instanceof FlowModel) {
|
|
910
|
+
model = modelOrUid;
|
|
911
|
+
}
|
|
912
|
+
if (model) {
|
|
913
|
+
model.context.addDelegate(this);
|
|
914
|
+
model.dispatchEvent(eventName, { navigation: false, ...this.model?.['getInputArgs']?.(), ...inputArgs });
|
|
915
|
+
} else {
|
|
916
|
+
this.message.error(this.t('Model with ID {{uid}} not found', { uid: modelOrUid }));
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
class BaseFlowEngineContext extends FlowContext {
|
|
924
|
+
declare router: Router;
|
|
925
|
+
declare dataSourceManager: DataSourceManager;
|
|
926
|
+
declare requireAsync: (url: string) => Promise<any>;
|
|
927
|
+
declare createJSRunner: (options?: JSRunnerOptions) => JSRunner;
|
|
928
|
+
/**
|
|
929
|
+
* @deprecated use `resolveJsonTemplate` instead
|
|
930
|
+
*/
|
|
931
|
+
declare renderJson: (template: JSONValue) => Promise<any>;
|
|
932
|
+
declare resolveJsonTemplate: (template: JSONValue) => Promise<any>;
|
|
933
|
+
declare runjs: (code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<any>;
|
|
934
|
+
declare copyToClipboard: (text: string) => Promise<void>;
|
|
935
|
+
declare getAction: <TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext>(
|
|
936
|
+
name: string,
|
|
937
|
+
) => ActionDefinition<TModel, TCtx> | undefined;
|
|
938
|
+
declare getActions: <TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext>() => Map<
|
|
939
|
+
string,
|
|
940
|
+
ActionDefinition<TModel, TCtx>
|
|
941
|
+
>;
|
|
942
|
+
declare getEvents: <TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext>() => Map<
|
|
943
|
+
string,
|
|
944
|
+
EventDefinition<TModel, TCtx>
|
|
945
|
+
>;
|
|
946
|
+
declare runAction: (actionName: string, params?: Record<string, any>) => Promise<any> | any;
|
|
947
|
+
declare engine: FlowEngine;
|
|
948
|
+
declare api: APIClient;
|
|
949
|
+
declare viewer: FlowViewer;
|
|
950
|
+
declare view: FlowView;
|
|
951
|
+
declare modal: HookAPI;
|
|
952
|
+
declare message: MessageInstance;
|
|
953
|
+
declare notification: NotificationInstance;
|
|
954
|
+
declare route: RouteOptions;
|
|
955
|
+
declare location: Location;
|
|
956
|
+
declare sql: FlowSQLRepository;
|
|
957
|
+
declare logger: pino.Logger;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
class BaseFlowModelContext extends BaseFlowEngineContext {
|
|
961
|
+
declare model: FlowModel;
|
|
962
|
+
declare ref: React.RefObject<HTMLDivElement>;
|
|
963
|
+
declare getAction: <TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext>(
|
|
964
|
+
name: string,
|
|
965
|
+
) => ActionDefinition<TModel, TCtx> | undefined;
|
|
966
|
+
declare getActions: <TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext>() => Map<
|
|
967
|
+
string,
|
|
968
|
+
ActionDefinition<TModel, TCtx>
|
|
969
|
+
>;
|
|
970
|
+
declare getEvents: <TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext>() => Map<
|
|
971
|
+
string,
|
|
972
|
+
EventDefinition<TModel, TCtx>
|
|
973
|
+
>;
|
|
974
|
+
declare runAction: (actionName: string, params?: Record<string, any>) => Promise<any> | any;
|
|
975
|
+
declare createResource: <T extends FlowResource = FlowResource>(resourceType: ResourceType<T>) => T;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
export class FlowEngineContext extends BaseFlowEngineContext {
|
|
979
|
+
// public dataSourceManager: DataSourceManager;
|
|
980
|
+
constructor(public engine: FlowEngine) {
|
|
981
|
+
if (!(engine instanceof FlowEngine)) {
|
|
982
|
+
throw new Error('Invalid FlowEngine instance');
|
|
983
|
+
}
|
|
984
|
+
super();
|
|
985
|
+
this.engine = engine;
|
|
986
|
+
const dataSourceManager = new DataSourceManager();
|
|
987
|
+
dataSourceManager.setFlowEngine(this.engine);
|
|
988
|
+
const mainDataSource = new DataSource({
|
|
989
|
+
key: 'main',
|
|
990
|
+
displayName: 'Main',
|
|
991
|
+
});
|
|
992
|
+
dataSourceManager.addDataSource(mainDataSource);
|
|
993
|
+
this.defineProperty('engine', {
|
|
994
|
+
value: this.engine,
|
|
995
|
+
});
|
|
996
|
+
this.defineProperty('sql', {
|
|
997
|
+
get: () => new FlowSQLRepository(this),
|
|
998
|
+
});
|
|
999
|
+
this.defineProperty('dataSourceManager', {
|
|
1000
|
+
value: dataSourceManager,
|
|
1001
|
+
});
|
|
1002
|
+
const i18n = new FlowI18n(this);
|
|
1003
|
+
this.defineMethod('t', (keyOrTemplate: string, options?: any) => {
|
|
1004
|
+
return i18n.translate(keyOrTemplate, options);
|
|
1005
|
+
});
|
|
1006
|
+
this.defineMethod('runjs', async (code, variables, options?: JSRunnerOptions) => {
|
|
1007
|
+
const mergedGlobals = { ...(options?.globals || {}), ...(variables || {}) };
|
|
1008
|
+
const runner = this.createJSRunner({
|
|
1009
|
+
...(options || {}),
|
|
1010
|
+
globals: mergedGlobals,
|
|
1011
|
+
});
|
|
1012
|
+
return runner.run(code);
|
|
1013
|
+
});
|
|
1014
|
+
this.defineMethod('renderJson', function (template: any) {
|
|
1015
|
+
return this.resolveJsonTemplate(template);
|
|
1016
|
+
});
|
|
1017
|
+
this.defineMethod('resolveJsonTemplate', async function (this: BaseFlowEngineContext, template: any) {
|
|
1018
|
+
// 提取模板使用到的变量及其子路径
|
|
1019
|
+
const used = extractUsedVariablePaths(template);
|
|
1020
|
+
const usedVarNames = Object.keys(used || {});
|
|
1021
|
+
if (!usedVarNames.length) {
|
|
1022
|
+
// 模板未包含任何 ctx.* 变量,直接前端解析
|
|
1023
|
+
return resolveExpressions(template, this);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// 分流:根据 resolveOnServer 标记与子路径判断哪些交给后端
|
|
1027
|
+
const serverVarPaths: Record<string, string[]> = {};
|
|
1028
|
+
for (const varName of usedVarNames) {
|
|
1029
|
+
const paths = used[varName] || [];
|
|
1030
|
+
const opt = this.getPropertyOptions(varName);
|
|
1031
|
+
const mark = opt?.resolveOnServer;
|
|
1032
|
+
if (mark === true) {
|
|
1033
|
+
serverVarPaths[varName] = paths;
|
|
1034
|
+
} else if (typeof mark === 'function') {
|
|
1035
|
+
const filtered = paths.filter((p) => {
|
|
1036
|
+
try {
|
|
1037
|
+
return !!mark(p);
|
|
1038
|
+
} catch (_) {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
if (filtered.length) serverVarPaths[varName] = filtered;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const needServer = Object.keys(serverVarPaths).length > 0;
|
|
1047
|
+
let serverResolved = template;
|
|
1048
|
+
if (needServer) {
|
|
1049
|
+
const collectFromMeta = async (): Promise<Record<string, any>> => {
|
|
1050
|
+
const out: Record<string, any> = {};
|
|
1051
|
+
try {
|
|
1052
|
+
const metas = this._getPropertiesMeta?.() as Record<
|
|
1053
|
+
string,
|
|
1054
|
+
PropertyMeta | (() => Promise<PropertyMeta | null> | PropertyMeta | null)
|
|
1055
|
+
>;
|
|
1056
|
+
if (!metas || typeof metas !== 'object') return out;
|
|
1057
|
+
for (const [key, metaOrFactory] of Object.entries(metas)) {
|
|
1058
|
+
if (!serverVarPaths[key]) continue; // 仅处理需要后端解析的变量
|
|
1059
|
+
try {
|
|
1060
|
+
let meta: PropertyMeta | null;
|
|
1061
|
+
if (typeof metaOrFactory === 'function') {
|
|
1062
|
+
const fn = metaOrFactory as () => Promise<PropertyMeta | null>;
|
|
1063
|
+
meta = await fn();
|
|
1064
|
+
} else {
|
|
1065
|
+
meta = metaOrFactory as PropertyMeta;
|
|
1066
|
+
}
|
|
1067
|
+
if (!meta || typeof meta !== 'object') continue;
|
|
1068
|
+
const builder = meta.buildVariablesParams;
|
|
1069
|
+
if (typeof builder !== 'function') continue;
|
|
1070
|
+
const built = await builder(this);
|
|
1071
|
+
if (!built) continue;
|
|
1072
|
+
const neededPaths = serverVarPaths[key] || [];
|
|
1073
|
+
const filtered = filterBuilderOutputByPaths(built, neededPaths);
|
|
1074
|
+
if (filtered && (typeof filtered !== 'object' || Object.keys(filtered).length)) {
|
|
1075
|
+
out[key] = filtered;
|
|
1076
|
+
}
|
|
1077
|
+
} catch (_) {
|
|
1078
|
+
// 忽略单个属性的错误
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
} catch (_) {
|
|
1082
|
+
// ignore
|
|
1083
|
+
}
|
|
1084
|
+
return out;
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
const inputFromMeta = await collectFromMeta();
|
|
1088
|
+
const autoInput = { ...inputFromMeta };
|
|
1089
|
+
const autoContextParams = Object.keys(autoInput).length
|
|
1090
|
+
? _buildServerContextParams(this, autoInput)
|
|
1091
|
+
: undefined;
|
|
1092
|
+
|
|
1093
|
+
if (this.api) {
|
|
1094
|
+
try {
|
|
1095
|
+
serverResolved = await enqueueVariablesResolve(this as FlowRuntimeContext<FlowModel>, {
|
|
1096
|
+
template,
|
|
1097
|
+
contextParams: autoContextParams || {},
|
|
1098
|
+
});
|
|
1099
|
+
} catch (e) {
|
|
1100
|
+
this.logger?.warn?.({ err: e }, 'variables:resolve failed, fallback to client-only');
|
|
1101
|
+
serverResolved = template;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
return resolveExpressions(serverResolved, this);
|
|
1107
|
+
});
|
|
1108
|
+
this.defineProperty('requirejs', {
|
|
1109
|
+
get: () => this.app?.requirejs?.requirejs,
|
|
1110
|
+
});
|
|
1111
|
+
// Expose API token and current role as top-level variables for VariableInput.
|
|
1112
|
+
// Front-end only: no resolveOnServer flag. Mark cache: false to reflect runtime changes.
|
|
1113
|
+
this.defineProperty('token', {
|
|
1114
|
+
get: () => this.api?.auth?.token,
|
|
1115
|
+
cache: false,
|
|
1116
|
+
// 注意:使用惰性 meta 工厂,避免在 i18n 尚未注入时提前求值导致无法翻译
|
|
1117
|
+
meta: Object.assign(() => ({ type: 'string', title: this.t('API Token'), sort: 980 }), {
|
|
1118
|
+
title: 'API Token',
|
|
1119
|
+
sort: 980,
|
|
1120
|
+
hasChildren: false,
|
|
1121
|
+
}),
|
|
1122
|
+
});
|
|
1123
|
+
this.defineProperty('role', {
|
|
1124
|
+
get: () => this.api?.auth?.role,
|
|
1125
|
+
cache: false,
|
|
1126
|
+
// 注意:使用惰性 meta 工厂,避免在 i18n 尚未注入时提前求值导致无法翻译
|
|
1127
|
+
meta: Object.assign(() => ({ type: 'string', title: this.t('Current role'), sort: 990 }), {
|
|
1128
|
+
title: escapeT('Current role'),
|
|
1129
|
+
sort: 990,
|
|
1130
|
+
hasChildren: false,
|
|
1131
|
+
}),
|
|
1132
|
+
});
|
|
1133
|
+
// URL 查询参数(等价于 1.0 的 `$nURLSearchParams`)
|
|
1134
|
+
this.defineProperty('urlSearchParams', {
|
|
1135
|
+
// 不缓存,确保随 URL 变化实时生效
|
|
1136
|
+
cache: false,
|
|
1137
|
+
get: () => {
|
|
1138
|
+
const search = this.location?.search || '';
|
|
1139
|
+
const str = search.startsWith('?') ? search.slice(1) : search;
|
|
1140
|
+
return (qs.parse(str) as Record<string, any>) || {};
|
|
1141
|
+
},
|
|
1142
|
+
// 变量选择器中的元信息与动态子项
|
|
1143
|
+
meta: Object.assign(
|
|
1144
|
+
() => ({
|
|
1145
|
+
type: 'object',
|
|
1146
|
+
title: this.t('URL search params'),
|
|
1147
|
+
sort: 970,
|
|
1148
|
+
disabled: () => {
|
|
1149
|
+
const search = this.location?.search || '';
|
|
1150
|
+
const str = search.startsWith('?') ? search.slice(1) : search;
|
|
1151
|
+
const params = (qs.parse(str) as Record<string, any>) || {};
|
|
1152
|
+
return Object.keys(params).length === 0;
|
|
1153
|
+
},
|
|
1154
|
+
disabledReason: () =>
|
|
1155
|
+
this.t(
|
|
1156
|
+
'The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.',
|
|
1157
|
+
),
|
|
1158
|
+
properties: async () => {
|
|
1159
|
+
const search = this.location?.search || '';
|
|
1160
|
+
const str = search.startsWith('?') ? search.slice(1) : search;
|
|
1161
|
+
const params = (qs.parse(str) as Record<string, any>) || {};
|
|
1162
|
+
const props: Record<string, any> = {};
|
|
1163
|
+
for (const key of Object.keys(params)) {
|
|
1164
|
+
props[key] = { type: 'string', title: key };
|
|
1165
|
+
}
|
|
1166
|
+
return props;
|
|
1167
|
+
},
|
|
1168
|
+
}),
|
|
1169
|
+
{
|
|
1170
|
+
title: escapeT('URL search params'),
|
|
1171
|
+
sort: 970,
|
|
1172
|
+
hasChildren: true,
|
|
1173
|
+
},
|
|
1174
|
+
),
|
|
1175
|
+
});
|
|
1176
|
+
this.defineProperty('logger', {
|
|
1177
|
+
get: () => {
|
|
1178
|
+
return this.engine.logger.child({ module: 'flow-engine' });
|
|
1179
|
+
},
|
|
1180
|
+
});
|
|
1181
|
+
this.defineProperty('auth', {
|
|
1182
|
+
get: () => ({
|
|
1183
|
+
roleName: this.api.auth.role,
|
|
1184
|
+
locale: this.api.auth.locale,
|
|
1185
|
+
token: this.api.auth.token,
|
|
1186
|
+
user: this.user,
|
|
1187
|
+
}),
|
|
1188
|
+
});
|
|
1189
|
+
this.defineMethod('loadCSS', async (url: string) => {
|
|
1190
|
+
return new Promise((resolve, reject) => {
|
|
1191
|
+
// Check if CSS is already loaded
|
|
1192
|
+
const existingLink = document.querySelector(`link[href="${url}"]`);
|
|
1193
|
+
if (existingLink) {
|
|
1194
|
+
resolve(null);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const link = document.createElement('link');
|
|
1199
|
+
link.rel = 'stylesheet';
|
|
1200
|
+
link.href = url;
|
|
1201
|
+
link.onload = () => resolve(null);
|
|
1202
|
+
link.onerror = () => reject(new Error(`Failed to load CSS: ${url}`));
|
|
1203
|
+
document.head.appendChild(link);
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
this.defineMethod('requireAsync', async (url: string) => {
|
|
1207
|
+
return new Promise((resolve, reject) => {
|
|
1208
|
+
if (!this.requirejs) {
|
|
1209
|
+
reject(new Error('requirejs is not available'));
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
this.requirejs(
|
|
1213
|
+
[url],
|
|
1214
|
+
(...args: any[]) => {
|
|
1215
|
+
resolve(args[0]);
|
|
1216
|
+
},
|
|
1217
|
+
reject,
|
|
1218
|
+
);
|
|
1219
|
+
});
|
|
1220
|
+
});
|
|
1221
|
+
this.defineMethod('createJSRunner', function (options?: JSRunnerOptions) {
|
|
1222
|
+
// return createJSRunnerWithVersion.call(this, options as any);
|
|
1223
|
+
const runCtx = new FlowRunjsContext(this.createProxy());
|
|
1224
|
+
return new JSRunner({
|
|
1225
|
+
...options,
|
|
1226
|
+
globals: {
|
|
1227
|
+
ctx: runCtx,
|
|
1228
|
+
window: createSafeWindow(),
|
|
1229
|
+
document: createSafeDocument(),
|
|
1230
|
+
...options?.globals,
|
|
1231
|
+
},
|
|
1232
|
+
});
|
|
1233
|
+
});
|
|
1234
|
+
// 复制文本到剪贴板(优先使用 Clipboard API,降级到 execCommand)
|
|
1235
|
+
this.defineMethod('copyToClipboard', async (text: string) => {
|
|
1236
|
+
const content = String(text ?? '');
|
|
1237
|
+
try {
|
|
1238
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
|
|
1239
|
+
await navigator.clipboard.writeText(content);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
} catch (e) {
|
|
1243
|
+
// 忽略,尝试降级方案
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// 降级方案:创建临时 textarea + execCommand('copy')
|
|
1247
|
+
return new Promise<void>((resolve, reject) => {
|
|
1248
|
+
try {
|
|
1249
|
+
const ta = document.createElement('textarea');
|
|
1250
|
+
ta.value = content;
|
|
1251
|
+
ta.setAttribute('readonly', '');
|
|
1252
|
+
ta.style.position = 'fixed';
|
|
1253
|
+
ta.style.top = '-9999px';
|
|
1254
|
+
document.body.appendChild(ta);
|
|
1255
|
+
ta.focus();
|
|
1256
|
+
ta.select();
|
|
1257
|
+
const ok = document.execCommand('copy');
|
|
1258
|
+
document.body.removeChild(ta);
|
|
1259
|
+
if (ok) resolve();
|
|
1260
|
+
else reject(new Error('execCommand copy failed'));
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
reject(err);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
// Helper: build server contextParams for variables:resolve
|
|
1267
|
+
this.defineMethod('buildServerContextParams', function (this: BaseFlowEngineContext, input?: any) {
|
|
1268
|
+
return _buildServerContextParams(this, input);
|
|
1269
|
+
});
|
|
1270
|
+
this.defineMethod('getAction', function (this: BaseFlowEngineContext, name: string) {
|
|
1271
|
+
return this.engine.getAction(name);
|
|
1272
|
+
});
|
|
1273
|
+
this.defineMethod('getActions', function (this: BaseFlowEngineContext) {
|
|
1274
|
+
return this.engine.getActions();
|
|
1275
|
+
});
|
|
1276
|
+
this.defineMethod('getEvents', function (this: BaseFlowEngineContext) {
|
|
1277
|
+
return this.engine.getEvents();
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
// // Date variables (for variable selector meta tree)
|
|
1281
|
+
// this.defineProperty('date', {
|
|
1282
|
+
// get: () => {
|
|
1283
|
+
// const vars = getDateVars() as Record<string, any>;
|
|
1284
|
+
// // align with client options: add dayBeforeYesterday
|
|
1285
|
+
// vars.dayBeforeYesterday = toUnit('day', -2);
|
|
1286
|
+
// const now = new Date().toISOString();
|
|
1287
|
+
// const out: Record<string, any> = {};
|
|
1288
|
+
// for (const [k, v] of Object.entries(vars)) {
|
|
1289
|
+
// try {
|
|
1290
|
+
// out[k] = typeof v === 'function' ? v({ now }) : v;
|
|
1291
|
+
// } catch (e) {
|
|
1292
|
+
// // ignore
|
|
1293
|
+
// }
|
|
1294
|
+
// }
|
|
1295
|
+
// return out;
|
|
1296
|
+
// },
|
|
1297
|
+
// meta: () => {
|
|
1298
|
+
// const title = this.t('Date variables');
|
|
1299
|
+
// const mk = (t: string) => ({ type: 'any', title: this.t(t) });
|
|
1300
|
+
// return {
|
|
1301
|
+
// type: 'object',
|
|
1302
|
+
// title,
|
|
1303
|
+
// properties: {
|
|
1304
|
+
// now: mk('Current time'),
|
|
1305
|
+
// dayBeforeYesterday: mk('Day before yesterday'),
|
|
1306
|
+
// yesterday: mk('Yesterday'),
|
|
1307
|
+
// today: mk('Today'),
|
|
1308
|
+
// tomorrow: mk('Tomorrow'),
|
|
1309
|
+
// lastIsoWeek: mk('Last week'),
|
|
1310
|
+
// thisIsoWeek: mk('This week'),
|
|
1311
|
+
// nextIsoWeek: mk('Next week'),
|
|
1312
|
+
// lastMonth: mk('Last month'),
|
|
1313
|
+
// thisMonth: mk('This month'),
|
|
1314
|
+
// nextMonth: mk('Next month'),
|
|
1315
|
+
// lastQuarter: mk('Last quarter'),
|
|
1316
|
+
// thisQuarter: mk('This quarter'),
|
|
1317
|
+
// nextQuarter: mk('Next quarter'),
|
|
1318
|
+
// lastYear: mk('Last year'),
|
|
1319
|
+
// thisYear: mk('This year'),
|
|
1320
|
+
// nextYear: mk('Next year'),
|
|
1321
|
+
// last7Days: mk('Last 7 days'),
|
|
1322
|
+
// next7Days: mk('Next 7 days'),
|
|
1323
|
+
// last30Days: mk('Last 30 days'),
|
|
1324
|
+
// next30Days: mk('Next 30 days'),
|
|
1325
|
+
// last90Days: mk('Last 90 days'),
|
|
1326
|
+
// next90Days: mk('Next 90 days'),
|
|
1327
|
+
// },
|
|
1328
|
+
// } as PropertyMeta;
|
|
1329
|
+
// },
|
|
1330
|
+
// });
|
|
1331
|
+
this.defineMethod(
|
|
1332
|
+
'runAction',
|
|
1333
|
+
async function (this: BaseFlowEngineContext, actionName: string, params?: Record<string, any>) {
|
|
1334
|
+
const def = this.engine.getAction<FlowModel, FlowEngineContext>(actionName);
|
|
1335
|
+
const ctx = this.createProxy() as unknown as FlowEngineContext;
|
|
1336
|
+
if (!def) {
|
|
1337
|
+
throw new Error(`Action '${actionName}' not found.`);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
const defaultParams = await resolveDefaultParams(def.defaultParams, ctx);
|
|
1341
|
+
let combinedParams: Record<string, any> = { ...(defaultParams || {}), ...(params || {}) };
|
|
1342
|
+
|
|
1343
|
+
let useRawParams = def.useRawParams;
|
|
1344
|
+
if (typeof useRawParams === 'function') {
|
|
1345
|
+
useRawParams = await useRawParams(ctx);
|
|
1346
|
+
}
|
|
1347
|
+
if (!useRawParams) {
|
|
1348
|
+
// 先服务端解析,再前端补齐
|
|
1349
|
+
combinedParams = await (ctx as any).resolveJsonTemplate(combinedParams);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
if (!def.handler) {
|
|
1353
|
+
throw new Error(`Action '${actionName}' has no handler.`);
|
|
1354
|
+
}
|
|
1355
|
+
return def.handler(ctx, combinedParams);
|
|
1356
|
+
},
|
|
1357
|
+
);
|
|
1358
|
+
this.defineProperty('acl', {
|
|
1359
|
+
get: () => {
|
|
1360
|
+
const acl = new ACL(this.engine);
|
|
1361
|
+
return acl;
|
|
1362
|
+
},
|
|
1363
|
+
});
|
|
1364
|
+
this.defineMethod('aclCheck', function (params) {
|
|
1365
|
+
return this.acl.aclCheck(params);
|
|
1366
|
+
});
|
|
1367
|
+
this.defineMethod('createResource', function (this: BaseFlowEngineContext, resourceType) {
|
|
1368
|
+
return this.engine.createResource(resourceType, {
|
|
1369
|
+
context: this.createProxy(),
|
|
1370
|
+
});
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
export class FlowModelContext extends BaseFlowModelContext {
|
|
1376
|
+
constructor(model: FlowModel) {
|
|
1377
|
+
if (!(model instanceof FlowModel)) {
|
|
1378
|
+
throw new Error('Invalid FlowModel instance');
|
|
1379
|
+
}
|
|
1380
|
+
super();
|
|
1381
|
+
this.addDelegate(model.flowEngine.context);
|
|
1382
|
+
this.defineMethod('onRefReady', (ref, cb, timeout) => {
|
|
1383
|
+
this.engine.reactView.onRefReady(ref, cb, timeout);
|
|
1384
|
+
});
|
|
1385
|
+
this.defineMethod('runjs', async (code, variables, options?: { version?: string }) => {
|
|
1386
|
+
const runner = this.createJSRunner({
|
|
1387
|
+
globals: variables,
|
|
1388
|
+
version: options?.version,
|
|
1389
|
+
});
|
|
1390
|
+
return runner.run(code);
|
|
1391
|
+
});
|
|
1392
|
+
this.defineProperty('model', {
|
|
1393
|
+
value: model,
|
|
1394
|
+
});
|
|
1395
|
+
this.defineProperty('ref', {
|
|
1396
|
+
get: () => {
|
|
1397
|
+
this.model['_refCreated'] = true;
|
|
1398
|
+
return createRef<HTMLDivElement>();
|
|
1399
|
+
},
|
|
1400
|
+
});
|
|
1401
|
+
this.defineMethod('openView', async function (uid: string, options) {
|
|
1402
|
+
const opts = { ...options };
|
|
1403
|
+
if (opts.defineProperties || opts.defineMethod) {
|
|
1404
|
+
opts.navigation = false; // 强制不使用路由导航, 避免刷新页面时丢失上下文
|
|
1405
|
+
}
|
|
1406
|
+
let model: FlowModel | null = null;
|
|
1407
|
+
model = await this.engine.loadModel({ uid });
|
|
1408
|
+
if (!model) {
|
|
1409
|
+
model = this.engine.createModel({
|
|
1410
|
+
uid, // 注意: 新建的 model 应该使用 ${parentModel.uid}-xxx 形式的 uid
|
|
1411
|
+
use: 'PopupActionModel',
|
|
1412
|
+
parentId: this.model.uid,
|
|
1413
|
+
subType: 'object',
|
|
1414
|
+
subKey: uid,
|
|
1415
|
+
stepParams: {
|
|
1416
|
+
popupSettings: {
|
|
1417
|
+
openView: {
|
|
1418
|
+
..._.pick(opts, ['dataSourceKey', 'collectionName', 'associationName']),
|
|
1419
|
+
},
|
|
1420
|
+
},
|
|
1421
|
+
},
|
|
1422
|
+
});
|
|
1423
|
+
await model.save();
|
|
1424
|
+
}
|
|
1425
|
+
if (model.getStepParams('popupSettings')?.openView?.dataSourceKey) {
|
|
1426
|
+
model.setStepParams('popupSettings', {
|
|
1427
|
+
openView: {
|
|
1428
|
+
...model.getStepParams('popupSettings')?.openView,
|
|
1429
|
+
..._.pick(opts, ['dataSourceKey', 'collectionName', 'associationName']),
|
|
1430
|
+
},
|
|
1431
|
+
});
|
|
1432
|
+
await model.save();
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// 路由层级的 viewUid:优先使用 routeViewUid(仅用于路由展示);
|
|
1436
|
+
// 否则回退到 opts.viewUid;再否则沿用原有规则(若子模型具备弹窗配置则使用子模型 uid,否则使用发起者 uid)。
|
|
1437
|
+
const viewUid =
|
|
1438
|
+
(opts as any)?.routeViewUid ??
|
|
1439
|
+
opts?.viewUid ??
|
|
1440
|
+
(model.stepParams?.popupSettings?.openView ? model.uid : this.model.uid);
|
|
1441
|
+
// 不向子模型持久写入 context.inputArgs,避免后续“直接点击子模型按钮”读取到旧的 viewUid 造成路由污染。
|
|
1442
|
+
const parentView = this.view;
|
|
1443
|
+
// 统一语义:为即将打开的外部视图定义一个 PendingView(占位视图)
|
|
1444
|
+
const pendingType = (opts?.isMobileLayout ? 'embed' : opts?.mode || 'drawer') as any;
|
|
1445
|
+
const pendingInputArgs = { ...opts, viewUid, navigation: opts.navigation };
|
|
1446
|
+
pendingInputArgs.filterByTk = pendingInputArgs.filterByTk || this.inputArgs?.filterByTk;
|
|
1447
|
+
pendingInputArgs.sourceId = pendingInputArgs.sourceId || this.inputArgs?.sourceId;
|
|
1448
|
+
|
|
1449
|
+
const pendingView = {
|
|
1450
|
+
type: pendingType,
|
|
1451
|
+
inputArgs: pendingInputArgs,
|
|
1452
|
+
navigation: parentView?.navigation,
|
|
1453
|
+
preventClose: !!opts?.preventClose,
|
|
1454
|
+
engineCtx: this.engine.context,
|
|
1455
|
+
};
|
|
1456
|
+
model.context.defineProperty('view', { value: pendingView });
|
|
1457
|
+
await model.dispatchEvent('click', {
|
|
1458
|
+
// navigation: false, // TODO: 路由模式有bug,不支持多层同样viewId的弹窗,因此这里默认先用false
|
|
1459
|
+
// ...this.model?.['getInputArgs']?.(), // 避免部分关系字段信息丢失, 仿照 ClickableCollectionField 做法
|
|
1460
|
+
...opts,
|
|
1461
|
+
});
|
|
1462
|
+
// 清理 pending view,避免污染子模型 context(例如后续直接点击该模型按钮时被遗留 viewUid 干扰)
|
|
1463
|
+
model.context.defineProperty('view', { value: undefined });
|
|
1464
|
+
});
|
|
1465
|
+
this.defineMethod('getEvents', function (this: BaseFlowModelContext) {
|
|
1466
|
+
return this.model.getEvents();
|
|
1467
|
+
});
|
|
1468
|
+
this.defineMethod('getAction', function (this: BaseFlowModelContext, name: string) {
|
|
1469
|
+
return this.model.getAction(name);
|
|
1470
|
+
});
|
|
1471
|
+
this.defineMethod('getActions', function (this: BaseFlowModelContext) {
|
|
1472
|
+
return this.model.getActions();
|
|
1473
|
+
});
|
|
1474
|
+
this.defineMethod(
|
|
1475
|
+
'runAction',
|
|
1476
|
+
async function (this: BaseFlowModelContext, actionName: string, params?: Record<string, any>) {
|
|
1477
|
+
const def = this.model.getAction<FlowModel, FlowModelContext>(actionName);
|
|
1478
|
+
const ctx = this.createProxy() as unknown as FlowModelContext;
|
|
1479
|
+
if (!def) {
|
|
1480
|
+
throw new Error(`Action '${actionName}' not found.`);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
const defaultParams = await resolveDefaultParams(def.defaultParams, ctx);
|
|
1484
|
+
let combinedParams: Record<string, any> = { ...(defaultParams || {}), ...(params || {}) };
|
|
1485
|
+
|
|
1486
|
+
let useRawParams = def.useRawParams;
|
|
1487
|
+
if (typeof useRawParams === 'function') {
|
|
1488
|
+
useRawParams = await useRawParams(ctx);
|
|
1489
|
+
}
|
|
1490
|
+
if (!useRawParams) {
|
|
1491
|
+
combinedParams = await (ctx as any).resolveJsonTemplate(combinedParams);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
if (!def.handler) {
|
|
1495
|
+
throw new Error(`Action '${actionName}' has no handler.`);
|
|
1496
|
+
}
|
|
1497
|
+
return def.handler(ctx, combinedParams);
|
|
1498
|
+
},
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
export class FlowForkModelContext extends BaseFlowModelContext {
|
|
1504
|
+
constructor(
|
|
1505
|
+
public master: FlowModel,
|
|
1506
|
+
public fork: ForkFlowModel,
|
|
1507
|
+
) {
|
|
1508
|
+
if (!(master instanceof FlowModel)) {
|
|
1509
|
+
throw new Error('Invalid FlowModel instance');
|
|
1510
|
+
}
|
|
1511
|
+
super();
|
|
1512
|
+
this.addDelegate((this.master as any).context);
|
|
1513
|
+
this.defineMethod('onRefReady', (ref, cb, timeout) => {
|
|
1514
|
+
this.engine.reactView.onRefReady(ref, cb, timeout);
|
|
1515
|
+
});
|
|
1516
|
+
this.defineProperty('model', {
|
|
1517
|
+
get: () => this.fork,
|
|
1518
|
+
});
|
|
1519
|
+
this.defineProperty('ref', {
|
|
1520
|
+
get: () => {
|
|
1521
|
+
this.fork['_refCreated'] = true;
|
|
1522
|
+
return createRef<HTMLDivElement>();
|
|
1523
|
+
},
|
|
1524
|
+
});
|
|
1525
|
+
this.defineMethod('runjs', async (code, variables, options?: { version?: string }) => {
|
|
1526
|
+
const runner = this.createJSRunner({
|
|
1527
|
+
globals: variables,
|
|
1528
|
+
version: options?.version,
|
|
1529
|
+
});
|
|
1530
|
+
return runner.run(code);
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
export class FlowRuntimeContext<
|
|
1536
|
+
TModel extends FlowModel = FlowModel,
|
|
1537
|
+
TMode extends 'runtime' | 'settings' = any,
|
|
1538
|
+
> extends BaseFlowModelContext {
|
|
1539
|
+
declare steps: Record<string, { params: Record<string, any>; uiSchema?: any; result?: any }>;
|
|
1540
|
+
stepResults: Record<string, any> = {};
|
|
1541
|
+
declare useResource: (className: 'APIResource' | 'SingleRecordResource' | 'MultiRecordResource') => void;
|
|
1542
|
+
declare getStepParams: (stepKey: string) => Record<string, any>;
|
|
1543
|
+
declare setStepParams: (stepKey: string, params?: any) => void;
|
|
1544
|
+
declare getStepResults: (stepKey: string) => any;
|
|
1545
|
+
declare runAction: (actionName: string, params?: Record<string, any>) => Promise<any> | any;
|
|
1546
|
+
constructor(
|
|
1547
|
+
public model: TModel,
|
|
1548
|
+
public flowKey: string,
|
|
1549
|
+
protected _mode: TMode = 'runtime' as TMode,
|
|
1550
|
+
) {
|
|
1551
|
+
super();
|
|
1552
|
+
this.addDelegate(this.model.context);
|
|
1553
|
+
this.defineMethod('getStepParams', (stepKey: string) => {
|
|
1554
|
+
return model.getStepParams(flowKey, stepKey) || {};
|
|
1555
|
+
});
|
|
1556
|
+
this.defineMethod('setStepParams', (stepKey: string, params) => {
|
|
1557
|
+
return model.setStepParams(flowKey, stepKey, params);
|
|
1558
|
+
});
|
|
1559
|
+
this.defineMethod('getStepResults', (stepKey: string) => {
|
|
1560
|
+
return _.get(this.steps, [stepKey, 'result']);
|
|
1561
|
+
});
|
|
1562
|
+
this.defineMethod(
|
|
1563
|
+
'useResource',
|
|
1564
|
+
(className: 'APIResource' | 'SingleRecordResource' | 'MultiRecordResource' | 'SQLResource') => {
|
|
1565
|
+
if (model.context.has('resource')) {
|
|
1566
|
+
console.warn(`[FlowRuntimeContext] useResource - resource already defined in context: ${className}`);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
model.context.defineProperty('resource', {
|
|
1570
|
+
get: () => {
|
|
1571
|
+
return this.createResource(className);
|
|
1572
|
+
},
|
|
1573
|
+
});
|
|
1574
|
+
if (!model['resource']) {
|
|
1575
|
+
model['resource'] = model.context.resource;
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
);
|
|
1579
|
+
this.defineProperty('resource', {
|
|
1580
|
+
get: () => model['resource'] || model.context['resource'],
|
|
1581
|
+
cache: false,
|
|
1582
|
+
});
|
|
1583
|
+
this.defineMethod('onRefReady', (ref, cb, timeout) => {
|
|
1584
|
+
this.engine.reactView.onRefReady(ref, cb, timeout);
|
|
1585
|
+
});
|
|
1586
|
+
this.defineMethod('runjs', async (code, variables, options?: { version?: string }) => {
|
|
1587
|
+
const runner = this.createJSRunner({
|
|
1588
|
+
globals: variables,
|
|
1589
|
+
version: options?.version,
|
|
1590
|
+
});
|
|
1591
|
+
return runner.run(code);
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
protected _getOwnProperty(key: string): any {
|
|
1596
|
+
if (this.mode === 'runtime') {
|
|
1597
|
+
return super._getOwnProperty(key, this.createProxy());
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const options = this._props[key];
|
|
1601
|
+
if (!options) return undefined;
|
|
1602
|
+
|
|
1603
|
+
// 静态值
|
|
1604
|
+
if ('value' in options) {
|
|
1605
|
+
return ContextPathProxy.create()[key];
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// get 方法
|
|
1609
|
+
if (options.get) {
|
|
1610
|
+
if (options.cache === false) {
|
|
1611
|
+
return ContextPathProxy.create()[key];
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const cacheKey = options.observable ? '_observableCache' : '_cache';
|
|
1615
|
+
|
|
1616
|
+
if (!(key in this[cacheKey])) {
|
|
1617
|
+
this[cacheKey][key] = ContextPathProxy.create()[key];
|
|
1618
|
+
}
|
|
1619
|
+
return this[cacheKey][key];
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
return undefined;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
exit() {
|
|
1626
|
+
throw new FlowExitException(this.flowKey, this.model.uid);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
exitAll() {
|
|
1630
|
+
throw new FlowExitAllException(this.flowKey, this.model.uid);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
get mode() {
|
|
1634
|
+
return this._mode;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// 类型别名,方便使用
|
|
1639
|
+
export type FlowSettingsContext<TModel extends FlowModel = FlowModel> = FlowRuntimeContext<TModel, 'settings'>;
|