@nocobase/flow-engine 2.0.0-beta.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/BlockScopedFlowEngine.js +0 -1
- package/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.d.ts +6 -0
- package/lib/JSRunner.js +32 -2
- package/lib/ViewScopedFlowEngine.js +3 -0
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
- package/lib/components/variables/VariableInput.js +9 -4
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/components/variables/utils.d.ts +7 -0
- package/lib/components/variables/utils.js +42 -2
- package/lib/data-source/index.d.ts +7 -27
- package/lib/data-source/index.js +81 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +163 -22
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2267 -148
- package/lib/flowEngine.d.ts +21 -0
- package/lib/flowEngine.js +56 -8
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.js +17 -11
- package/lib/index.d.ts +7 -1
- package/lib/index.js +21 -0
- package/lib/locale/en-US.json +9 -2
- package/lib/locale/index.d.ts +14 -0
- package/lib/locale/zh-CN.json +8 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/flowModel.js +12 -1
- package/lib/provider.js +5 -5
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +4 -3
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/base.js +706 -41
- package/lib/runjs-context/contributions.d.ts +33 -0
- package/lib/runjs-context/contributions.js +88 -0
- package/lib/runjs-context/helpers.js +12 -1
- package/lib/runjs-context/setup.js +6 -0
- package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
- package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
- package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
- package/lib/runjs-context/snippets/index.d.ts +11 -1
- package/lib/runjs-context/snippets/index.js +61 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +25 -21
- package/lib/types.d.ts +27 -0
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/createEphemeralContext.js +2 -2
- package/lib/utils/dateVariable.d.ts +16 -0
- package/lib/utils/dateVariable.js +380 -0
- package/lib/utils/exceptions.d.ts +7 -0
- package/lib/utils/exceptions.js +10 -0
- package/lib/utils/index.d.ts +8 -3
- package/lib/utils/index.js +45 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +18 -8
- package/lib/utils/safeGlobals.js +164 -17
- package/lib/utils/schema-utils.d.ts +10 -0
- package/lib/utils/schema-utils.js +61 -0
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/useDialog.js +7 -2
- package/lib/views/useDrawer.js +7 -2
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +43 -6
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +4 -4
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +44 -2
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +64 -0
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flowContext.test.ts +693 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
- package/src/__tests__/flowRuntimeContext.test.ts +2 -1
- package/src/__tests__/flowSettings.open.test.tsx +123 -19
- package/src/__tests__/runjsContext.test.ts +10 -7
- package/src/__tests__/runjsContextImplementations.test.ts +34 -3
- package/src/__tests__/runjsContextRuntime.test.ts +3 -3
- package/src/__tests__/runjsContributions.test.ts +89 -0
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsLocales.test.ts +4 -1
- package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- package/src/components/variables/VariableInput.tsx +12 -4
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/components/variables/__tests__/utils.test.ts +81 -3
- package/src/components/variables/utils.ts +67 -6
- package/src/data-source/index.ts +85 -110
- package/src/executor/FlowExecutor.ts +200 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +2986 -211
- package/src/flowEngine.ts +59 -8
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +18 -12
- package/src/index.ts +14 -1
- package/src/locale/en-US.json +9 -2
- package/src/locale/zh-CN.json +8 -1
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +13 -1
- package/src/provider.tsx +7 -6
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +11 -6
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/base.ts +715 -44
- package/src/runjs-context/contributions.ts +88 -0
- package/src/runjs-context/helpers.ts +11 -1
- package/src/runjs-context/setup.ts +6 -0
- package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
- package/src/runjs-context/snippets/index.ts +75 -41
- package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +27 -21
- package/src/types.ts +38 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +57 -2
- package/src/utils/__tests__/utils.test.ts +95 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/createEphemeralContext.ts +5 -4
- package/src/utils/dateVariable.ts +397 -0
- package/src/utils/exceptions.ts +11 -0
- package/src/utils/index.ts +37 -3
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +188 -17
- package/src/utils/schema-utils.ts +79 -0
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/useDialog.tsx +8 -1
- package/src/views/useDrawer.tsx +8 -1
- package/src/views/usePage.tsx +51 -5
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import React, { useCallback, useRef, useMemo, useState, useEffect } from 'react';
|
|
11
|
-
import { Button, Cascader, Tooltip } from 'antd';
|
|
11
|
+
import { Button, Cascader, Input, Tooltip, theme } from 'antd';
|
|
12
12
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
|
13
13
|
import { cx, css } from '@emotion/css';
|
|
14
14
|
import type { ContextSelectorItem, FlowContextSelectorProps } from './variables/types';
|
|
15
15
|
import {
|
|
16
16
|
buildContextSelectorItems,
|
|
17
|
+
filterLoadedContextSelectorItems,
|
|
17
18
|
formatPathToValue,
|
|
18
19
|
parseValueToPath,
|
|
19
20
|
preloadContextSelectorPath,
|
|
@@ -34,6 +35,52 @@ const cascaderPopupAutoHeightClassName = css`
|
|
|
34
35
|
}
|
|
35
36
|
`;
|
|
36
37
|
|
|
38
|
+
type SelectedPathInfo = {
|
|
39
|
+
text: string;
|
|
40
|
+
meta?: ContextSelectorItem['meta'];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const normalizePath = (path: unknown): string[] | undefined => {
|
|
44
|
+
if (!Array.isArray(path)) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return path.map((segment) => String(segment));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getSelectedPathInfo = (path: string[] | undefined, options: ContextSelectorItem[]): SelectedPathInfo => {
|
|
52
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
53
|
+
return { text: '', meta: undefined };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const labels: string[] = [];
|
|
57
|
+
let currentOptions = options;
|
|
58
|
+
let selectedMeta: ContextSelectorItem['meta'] | undefined;
|
|
59
|
+
|
|
60
|
+
for (const segment of path) {
|
|
61
|
+
const matchedOption = currentOptions.find((item) => String(item.value) === String(segment));
|
|
62
|
+
if (!matchedOption) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const label =
|
|
67
|
+
typeof matchedOption.meta?.title === 'string'
|
|
68
|
+
? matchedOption.meta.title
|
|
69
|
+
: typeof matchedOption.label === 'string'
|
|
70
|
+
? matchedOption.label
|
|
71
|
+
: String(matchedOption.value);
|
|
72
|
+
|
|
73
|
+
labels.push(label);
|
|
74
|
+
selectedMeta = matchedOption.meta;
|
|
75
|
+
currentOptions = Array.isArray(matchedOption.children) ? matchedOption.children : [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
text: labels.join(' / '),
|
|
80
|
+
meta: selectedMeta,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
37
84
|
const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
38
85
|
value,
|
|
39
86
|
onChange,
|
|
@@ -47,13 +94,15 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
47
94
|
ignoreFieldNames,
|
|
48
95
|
...cascaderProps
|
|
49
96
|
}) => {
|
|
97
|
+
const { token } = theme.useToken();
|
|
98
|
+
|
|
50
99
|
// 记录最后点击的路径,用于双击检测
|
|
51
100
|
const lastSelectedRef = useRef<{ path: string; time: number } | null>(null);
|
|
52
101
|
|
|
53
102
|
const { resolvedMetaTree, loading } = useResolvedMetaTree(metaTree);
|
|
54
103
|
|
|
55
104
|
// 获取引擎上下文中的翻译函数,若不可用则回退为原文
|
|
56
|
-
const flowCtx = useFlowContext
|
|
105
|
+
const flowCtx = useFlowContext();
|
|
57
106
|
|
|
58
107
|
const translateOptions = useCallback(
|
|
59
108
|
(items: ContextSelectorItem[] | undefined): ContextSelectorItem[] => {
|
|
@@ -63,7 +112,9 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
63
112
|
const meta = o.meta;
|
|
64
113
|
const disabled = meta ? !!(typeof meta.disabled === 'function' ? meta.disabled() : meta.disabled) : false;
|
|
65
114
|
const disabledReason = meta
|
|
66
|
-
?
|
|
115
|
+
? typeof meta.disabledReason === 'function'
|
|
116
|
+
? meta.disabledReason()
|
|
117
|
+
: meta.disabledReason
|
|
67
118
|
: undefined;
|
|
68
119
|
|
|
69
120
|
// 文本国际化:仅当 label 为字符串时进行翻译
|
|
@@ -98,7 +149,11 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
98
149
|
|
|
99
150
|
// 用于强制重新渲染的状态
|
|
100
151
|
const [updateFlag, setUpdateFlag] = useState(0);
|
|
152
|
+
const [searchText, setSearchText] = useState('');
|
|
153
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
154
|
+
const inlineFocusByPointerRef = useRef(false);
|
|
101
155
|
const triggerUpdate = useCallback(() => setUpdateFlag((prev) => prev + 1), []);
|
|
156
|
+
const isSearchEnabled = showSearch || children === null;
|
|
102
157
|
|
|
103
158
|
// 构建选项
|
|
104
159
|
// 注意:rc-cascader 内部对 options 做了基于引用的缓存(useEntities)。
|
|
@@ -106,13 +161,22 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
106
161
|
// 触发 rc-cascader 重新构建 pathKeyEntities,避免二级节点未被索引导致的报错。
|
|
107
162
|
const options = useMemo(() => {
|
|
108
163
|
if (!resolvedMetaTree) return [];
|
|
164
|
+
const refreshSeq = updateFlag;
|
|
109
165
|
const base = buildContextSelectorItems(resolvedMetaTree);
|
|
110
|
-
|
|
166
|
+
const filtered = translateOptions(base).filter((item) => {
|
|
111
167
|
if (!ignoreFieldNames || ignoreFieldNames.length === 0) return true;
|
|
112
168
|
return !ignoreFieldNames.includes(item.meta?.name || '');
|
|
113
169
|
});
|
|
170
|
+
return refreshSeq >= 0 ? filtered : [];
|
|
114
171
|
}, [resolvedMetaTree, updateFlag, translateOptions, ignoreFieldNames]);
|
|
115
172
|
|
|
173
|
+
const displayOptions = useMemo(() => {
|
|
174
|
+
if (!isSearchEnabled || !searchText.trim()) {
|
|
175
|
+
return options;
|
|
176
|
+
}
|
|
177
|
+
return filterLoadedContextSelectorItems(options, searchText);
|
|
178
|
+
}, [isSearchEnabled, options, searchText]);
|
|
179
|
+
|
|
116
180
|
// 内部展开路径:在 onlyLeafSelectable=true 时,点击父节点不会触发 onChange,
|
|
117
181
|
// 但会触发 loadData。我们在此记录路径以在懒加载后保持展开。
|
|
118
182
|
const [tempSelectedPath, setTempSelectedPath] = useState<string[]>([]);
|
|
@@ -158,23 +222,36 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
158
222
|
triggerUpdate();
|
|
159
223
|
}
|
|
160
224
|
},
|
|
161
|
-
[triggerUpdate],
|
|
225
|
+
[triggerUpdate, translateOptions],
|
|
162
226
|
);
|
|
163
227
|
|
|
164
228
|
const currentPath = useMemo(() => {
|
|
165
|
-
return customParseValueToPath(value);
|
|
229
|
+
return normalizePath(customParseValueToPath(value));
|
|
166
230
|
}, [value, customParseValueToPath]);
|
|
167
231
|
|
|
168
232
|
// 当 metaTree 为子层(如 getPropertyMetaTree('{{ ctx.collection }}') 返回的是 collection 的子节点)
|
|
169
233
|
// 而 value path 仍包含根键(如 ['collection', 'field'])时,自动丢弃不存在的首段,确保级联能正确对齐。
|
|
170
234
|
const effectivePath = useMemo(() => {
|
|
171
235
|
if (!currentPath || currentPath.length === 0) return currentPath;
|
|
236
|
+
|
|
237
|
+
if (options.length === 0) {
|
|
238
|
+
return currentPath;
|
|
239
|
+
}
|
|
240
|
+
|
|
172
241
|
const topValues = new Set(options.map((o) => String(o.value)));
|
|
173
242
|
const needTrim = !topValues.has(String(currentPath[0]));
|
|
174
243
|
const fixed = needTrim ? currentPath.slice(1) : currentPath;
|
|
175
244
|
return fixed;
|
|
176
245
|
}, [currentPath, options]);
|
|
177
246
|
|
|
247
|
+
const cascaderValue = useMemo(() => {
|
|
248
|
+
if (tempSelectedPath.length > 0) {
|
|
249
|
+
return tempSelectedPath;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return Array.isArray(effectivePath) ? effectivePath : undefined;
|
|
253
|
+
}, [effectivePath, tempSelectedPath]);
|
|
254
|
+
|
|
178
255
|
// 预加载:当存在有效路径时,按路径逐级加载 children,保证默认展开和选中路径可用
|
|
179
256
|
const pathToPreload = useMemo(() => {
|
|
180
257
|
const finalPath = effectivePath && effectivePath.length > 0 ? effectivePath : tempSelectedPath;
|
|
@@ -251,21 +328,140 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
251
328
|
return cx(cascaderPopupAutoHeightClassName, cascaderProps.popupClassName);
|
|
252
329
|
}, [cascaderProps.popupClassName]);
|
|
253
330
|
|
|
331
|
+
const cascaderSearchInputClassName = useMemo(() => {
|
|
332
|
+
return css`
|
|
333
|
+
padding: 8px;
|
|
334
|
+
border-bottom: 1px solid ${token.colorSplit};
|
|
335
|
+
`;
|
|
336
|
+
}, [token.colorSplit]);
|
|
337
|
+
|
|
338
|
+
const {
|
|
339
|
+
onDropdownVisibleChange: cascaderOnDropdownVisibleChange,
|
|
340
|
+
dropdownRender: cascaderDropdownRender,
|
|
341
|
+
...restCascaderProps
|
|
342
|
+
} = cascaderProps;
|
|
343
|
+
|
|
344
|
+
const selectedPathInfo = useMemo(() => getSelectedPathInfo(effectivePath, options), [effectivePath, options]);
|
|
345
|
+
|
|
346
|
+
const mergedOpen = open !== undefined ? open : children === null ? dropdownOpen : undefined;
|
|
347
|
+
|
|
348
|
+
const isDropdownVisible = !!mergedOpen;
|
|
349
|
+
|
|
350
|
+
const handleDropdownVisibleChange = useCallback(
|
|
351
|
+
(visible: boolean) => {
|
|
352
|
+
if (open === undefined) {
|
|
353
|
+
setDropdownOpen(visible);
|
|
354
|
+
}
|
|
355
|
+
if (!visible) {
|
|
356
|
+
setSearchText('');
|
|
357
|
+
}
|
|
358
|
+
cascaderOnDropdownVisibleChange?.(visible);
|
|
359
|
+
},
|
|
360
|
+
[cascaderOnDropdownVisibleChange, open],
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const renderDropdown = useCallback(
|
|
364
|
+
(menu: React.ReactElement) => {
|
|
365
|
+
const cascaderMenuNode = cascaderDropdownRender ? cascaderDropdownRender(menu) : menu;
|
|
366
|
+
const cascaderMenu = React.isValidElement(cascaderMenuNode) ? cascaderMenuNode : <>{cascaderMenuNode}</>;
|
|
367
|
+
if (!isSearchEnabled || children === null) {
|
|
368
|
+
return cascaderMenu;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<>
|
|
373
|
+
<div className={cascaderSearchInputClassName}>
|
|
374
|
+
<Input
|
|
375
|
+
allowClear
|
|
376
|
+
size="small"
|
|
377
|
+
value={searchText}
|
|
378
|
+
placeholder={flowCtx.t('Search')}
|
|
379
|
+
onChange={(e) => setSearchText(e.target.value)}
|
|
380
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
381
|
+
/>
|
|
382
|
+
</div>
|
|
383
|
+
{cascaderMenu}
|
|
384
|
+
</>
|
|
385
|
+
);
|
|
386
|
+
},
|
|
387
|
+
[cascaderDropdownRender, cascaderSearchInputClassName, children, flowCtx, isSearchEnabled, searchText],
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const inlinePlaceholder =
|
|
391
|
+
typeof restCascaderProps.placeholder === 'string' ? restCascaderProps.placeholder : flowCtx.t('Search');
|
|
392
|
+
const hasSelectedPath = Array.isArray(effectivePath) && effectivePath.length > 0;
|
|
393
|
+
|
|
394
|
+
const handleInlineInputFocus = useCallback(() => {
|
|
395
|
+
if (open === undefined && !inlineFocusByPointerRef.current) {
|
|
396
|
+
setDropdownOpen(true);
|
|
397
|
+
}
|
|
398
|
+
}, [open]);
|
|
399
|
+
|
|
400
|
+
const markInlineFocusByPointer = useCallback(() => {
|
|
401
|
+
inlineFocusByPointerRef.current = true;
|
|
402
|
+
}, []);
|
|
403
|
+
|
|
404
|
+
const resetInlineFocusByPointer = useCallback(() => {
|
|
405
|
+
inlineFocusByPointerRef.current = false;
|
|
406
|
+
}, []);
|
|
407
|
+
|
|
408
|
+
const handleInlineInputChange = useCallback(
|
|
409
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
410
|
+
const nextValue = event.target.value;
|
|
411
|
+
|
|
412
|
+
// 下拉关闭态下点击清空:应清空真实已选值,而不是仅清空搜索词。
|
|
413
|
+
if (!isDropdownVisible && nextValue === '' && hasSelectedPath) {
|
|
414
|
+
setTempSelectedPath([]);
|
|
415
|
+
// 清空语义:传空 meta,确保上层(如 VariableInput)进入 clear 分支。
|
|
416
|
+
onChange?.('', undefined);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (open === undefined && !isDropdownVisible) {
|
|
421
|
+
setDropdownOpen(true);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
setSearchText(nextValue);
|
|
425
|
+
},
|
|
426
|
+
[hasSelectedPath, isDropdownVisible, onChange, open],
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const inlinePathText = Array.isArray(effectivePath) ? effectivePath.join(' / ') : '';
|
|
430
|
+
const inlineInputValue = isDropdownVisible ? searchText : selectedPathInfo.text || inlinePathText;
|
|
431
|
+
|
|
254
432
|
return (
|
|
255
433
|
<Cascader
|
|
256
|
-
{...
|
|
257
|
-
options={
|
|
258
|
-
value={
|
|
434
|
+
{...restCascaderProps}
|
|
435
|
+
options={displayOptions}
|
|
436
|
+
value={cascaderValue}
|
|
259
437
|
onChange={handleChange}
|
|
260
438
|
loadData={handleLoadData}
|
|
261
439
|
loading={loading}
|
|
262
440
|
changeOnSelect={!onlyLeafSelectable}
|
|
263
441
|
expandTrigger="click"
|
|
264
|
-
open={
|
|
265
|
-
showSearch={
|
|
442
|
+
open={mergedOpen}
|
|
443
|
+
showSearch={false}
|
|
266
444
|
popupClassName={mergedPopupClassName}
|
|
445
|
+
dropdownRender={renderDropdown}
|
|
446
|
+
onDropdownVisibleChange={handleDropdownVisibleChange}
|
|
267
447
|
>
|
|
268
|
-
{children === null ?
|
|
448
|
+
{children === null ? (
|
|
449
|
+
<Input
|
|
450
|
+
allowClear
|
|
451
|
+
value={inlineInputValue}
|
|
452
|
+
placeholder={inlinePlaceholder}
|
|
453
|
+
onMouseDown={markInlineFocusByPointer}
|
|
454
|
+
onMouseUp={resetInlineFocusByPointer}
|
|
455
|
+
onMouseLeave={resetInlineFocusByPointer}
|
|
456
|
+
onFocus={handleInlineInputFocus}
|
|
457
|
+
onBlur={resetInlineFocusByPointer}
|
|
458
|
+
onChange={handleInlineInputChange}
|
|
459
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
460
|
+
disabled={restCascaderProps.disabled}
|
|
461
|
+
/>
|
|
462
|
+
) : (
|
|
463
|
+
children || defaultChildren
|
|
464
|
+
)}
|
|
269
465
|
</Cascader>
|
|
270
466
|
);
|
|
271
467
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
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 React from 'react';
|
|
11
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
12
|
+
import { render, screen, fireEvent } from '@nocobase/test/client';
|
|
13
|
+
import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
14
|
+
|
|
15
|
+
import { SwitchWithTitle } from '../SwitchWithTitle';
|
|
16
|
+
import { SelectWithTitle } from '../SelectWithTitle';
|
|
17
|
+
|
|
18
|
+
vi.mock('antd', async (importOriginal) => {
|
|
19
|
+
const actual = (await importOriginal()) as any;
|
|
20
|
+
return {
|
|
21
|
+
...actual,
|
|
22
|
+
Select: ({
|
|
23
|
+
popupMatchSelectWidth,
|
|
24
|
+
bordered,
|
|
25
|
+
popupClassName,
|
|
26
|
+
fieldNames,
|
|
27
|
+
labelRender,
|
|
28
|
+
optionRender,
|
|
29
|
+
dropdownRender,
|
|
30
|
+
options,
|
|
31
|
+
...props
|
|
32
|
+
}: any) => React.createElement('select', props),
|
|
33
|
+
Switch: ({ checkedChildren, unCheckedChildren, size, ...props }: any) =>
|
|
34
|
+
React.createElement('input', { ...props, type: 'checkbox', readOnly: true }),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('Inline controls - stopPropagation', () => {
|
|
39
|
+
it('SwitchWithTitle click does not bubble to parent', async () => {
|
|
40
|
+
const engine = new FlowEngine();
|
|
41
|
+
const parentClick = vi.fn();
|
|
42
|
+
const onChange = vi.fn();
|
|
43
|
+
|
|
44
|
+
render(
|
|
45
|
+
<FlowEngineProvider engine={engine}>
|
|
46
|
+
<div onClick={parentClick}>
|
|
47
|
+
<SwitchWithTitle title="Enabled" itemKey="enabled" onChange={onChange} />
|
|
48
|
+
</div>
|
|
49
|
+
</FlowEngineProvider>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
fireEvent.click(screen.getByText('Enabled'));
|
|
53
|
+
|
|
54
|
+
expect(parentClick).not.toHaveBeenCalled();
|
|
55
|
+
expect(onChange).toHaveBeenCalledWith({ enabled: true });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('SelectWithTitle click does not bubble to parent', async () => {
|
|
59
|
+
const engine = new FlowEngine();
|
|
60
|
+
const parentClick = vi.fn();
|
|
61
|
+
|
|
62
|
+
render(
|
|
63
|
+
<FlowEngineProvider engine={engine}>
|
|
64
|
+
<div onClick={parentClick}>
|
|
65
|
+
<SelectWithTitle title="Mode" itemKey="mode" options={[{ label: 'A', value: 'a' }]} />
|
|
66
|
+
</div>
|
|
67
|
+
</FlowEngineProvider>,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
fireEvent.click(screen.getByText('Mode'));
|
|
71
|
+
|
|
72
|
+
expect(parentClick).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { ExclamationCircleOutlined, MenuOutlined } from '@ant-design/icons';
|
|
10
|
+
import { ExclamationCircleOutlined, MenuOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
|
11
11
|
import type { DropdownProps, MenuProps } from 'antd';
|
|
12
|
-
import { App, Dropdown, Modal } from 'antd';
|
|
12
|
+
import { App, Dropdown, Modal, Tooltip, theme } from 'antd';
|
|
13
13
|
import React, { startTransition, useCallback, useEffect, useMemo, useState, FC } from 'react';
|
|
14
14
|
import { FlowModel } from '../../../../models';
|
|
15
15
|
import type { FlowModelExtraMenuItem } from '../../../../models';
|
|
@@ -17,6 +17,7 @@ import type { StepDefinition, StepUIMode } from '../../../../types';
|
|
|
17
17
|
import {
|
|
18
18
|
getT,
|
|
19
19
|
resolveStepUiSchema,
|
|
20
|
+
resolveStepDisabledInSettings,
|
|
20
21
|
shouldHideStepInSettings,
|
|
21
22
|
resolveDefaultParams,
|
|
22
23
|
resolveUiMode,
|
|
@@ -33,6 +34,8 @@ interface StepInfo {
|
|
|
33
34
|
title: string;
|
|
34
35
|
modelKey?: string;
|
|
35
36
|
uiMode?: StepUIMode;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
disabledReason?: string;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
interface FlowInfo {
|
|
@@ -150,12 +153,29 @@ const componentMap = {
|
|
|
150
153
|
const MenuLabelItem = ({ title, uiMode, itemProps }) => {
|
|
151
154
|
const type = uiMode?.type || uiMode;
|
|
152
155
|
const Component = type ? componentMap[type] : null;
|
|
156
|
+
const disabled = !!itemProps?.disabled;
|
|
157
|
+
const disabledReason = itemProps?.disabledReason;
|
|
158
|
+
const disabledIconColor = itemProps?.disabledIconColor;
|
|
153
159
|
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
const content = (() => {
|
|
161
|
+
if (!Component) {
|
|
162
|
+
return <>{title}</>;
|
|
163
|
+
}
|
|
164
|
+
return <Component title={title} {...itemProps} />;
|
|
165
|
+
})();
|
|
166
|
+
|
|
167
|
+
if (!disabled) {
|
|
168
|
+
return content;
|
|
156
169
|
}
|
|
157
170
|
|
|
158
|
-
return
|
|
171
|
+
return (
|
|
172
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
|
173
|
+
{content}
|
|
174
|
+
<Tooltip title={disabledReason} placement="right" destroyTooltipOnHide>
|
|
175
|
+
<QuestionCircleOutlined style={{ color: disabledIconColor }} />
|
|
176
|
+
</Tooltip>
|
|
177
|
+
</span>
|
|
178
|
+
);
|
|
159
179
|
};
|
|
160
180
|
|
|
161
181
|
/**
|
|
@@ -180,10 +200,17 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
180
200
|
}) => {
|
|
181
201
|
const { message } = App.useApp();
|
|
182
202
|
const t = useMemo(() => getT(model), [model]);
|
|
203
|
+
const { token } = theme.useToken();
|
|
204
|
+
const disabledIconColor = token?.colorTextTertiary || token?.colorTextDescription || token?.colorTextSecondary;
|
|
183
205
|
const [visible, setVisible] = useState(false);
|
|
184
206
|
// 当模型发生子模型替换/增删等变化时,强制刷新菜单数据
|
|
185
207
|
const [refreshTick, setRefreshTick] = useState(0);
|
|
186
208
|
const [extraMenuItems, setExtraMenuItems] = useState<FlowModelExtraMenuItem[]>([]);
|
|
209
|
+
const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = useState<FlowInfo[]>([]);
|
|
210
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
211
|
+
const closeDropdown = useCallback(() => {
|
|
212
|
+
setVisible(false);
|
|
213
|
+
}, []);
|
|
187
214
|
const handleOpenChange: DropdownProps['onOpenChange'] = useCallback((nextOpen: boolean, info) => {
|
|
188
215
|
if (info.source === 'trigger' || nextOpen) {
|
|
189
216
|
// 当鼠标快速滑过时,终止菜单的渲染,防止卡顿
|
|
@@ -234,7 +261,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
234
261
|
return () => {
|
|
235
262
|
mounted = false;
|
|
236
263
|
};
|
|
237
|
-
}, [model, menuLevels, t, refreshTick, visible
|
|
264
|
+
}, [model, menuLevels, t, refreshTick, visible]);
|
|
238
265
|
|
|
239
266
|
// 统一的复制 UID 方法
|
|
240
267
|
const copyUidToClipboard = useCallback(
|
|
@@ -292,6 +319,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
292
319
|
);
|
|
293
320
|
|
|
294
321
|
const handleDelete = useCallback(() => {
|
|
322
|
+
closeDropdown();
|
|
295
323
|
Modal.confirm({
|
|
296
324
|
title: t('Confirm delete'),
|
|
297
325
|
icon: <ExclamationCircleOutlined />,
|
|
@@ -312,7 +340,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
312
340
|
}
|
|
313
341
|
},
|
|
314
342
|
});
|
|
315
|
-
}, [model]);
|
|
343
|
+
}, [closeDropdown, model, t]);
|
|
316
344
|
|
|
317
345
|
const handleStepConfiguration = useCallback(
|
|
318
346
|
(key: string) => {
|
|
@@ -345,6 +373,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
345
373
|
}
|
|
346
374
|
|
|
347
375
|
try {
|
|
376
|
+
closeDropdown();
|
|
348
377
|
targetModel.openFlowSettings({
|
|
349
378
|
flowKey,
|
|
350
379
|
stepKey,
|
|
@@ -353,7 +382,32 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
353
382
|
console.log(t('Configuration popup cancelled or error'), ':', error);
|
|
354
383
|
}
|
|
355
384
|
},
|
|
356
|
-
[model],
|
|
385
|
+
[closeDropdown, model, t],
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const isStepMenuItemDisabled = useCallback(
|
|
389
|
+
(key: string) => {
|
|
390
|
+
const cleanKey = key.includes('-') && /^(.+)-\d+$/.test(key) ? key.replace(/-\d+$/, '') : key;
|
|
391
|
+
const keys = cleanKey.split(':');
|
|
392
|
+
let modelKey: string | undefined;
|
|
393
|
+
let flowKey: string | undefined;
|
|
394
|
+
let stepKey: string | undefined;
|
|
395
|
+
|
|
396
|
+
if (keys.length === 3) {
|
|
397
|
+
[modelKey, flowKey, stepKey] = keys;
|
|
398
|
+
} else if (keys.length === 2) {
|
|
399
|
+
[flowKey, stepKey] = keys;
|
|
400
|
+
} else {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return configurableFlowsAndSteps.some(({ flow, steps, modelKey: flowModelKey }: FlowInfo) => {
|
|
405
|
+
const sameModel = (flowModelKey || undefined) === modelKey;
|
|
406
|
+
if (!sameModel || flow.key !== flowKey) return false;
|
|
407
|
+
return steps.some((stepInfo: StepInfo) => stepInfo.stepKey === stepKey && !!stepInfo.disabled);
|
|
408
|
+
});
|
|
409
|
+
},
|
|
410
|
+
[configurableFlowsAndSteps],
|
|
357
411
|
);
|
|
358
412
|
|
|
359
413
|
const handleMenuClick = useCallback(
|
|
@@ -363,18 +417,25 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
363
417
|
const cleanKey = key.includes('-') && /^(.+)-\d+$/.test(key) ? key.replace(/-\d+$/, '') : key;
|
|
364
418
|
|
|
365
419
|
if (cleanKey.startsWith('copy-pop-uid:')) {
|
|
420
|
+
closeDropdown();
|
|
366
421
|
handleCopyPopupUid(cleanKey);
|
|
367
422
|
return;
|
|
368
423
|
}
|
|
369
424
|
|
|
370
425
|
const extra = extraMenuItems.find((it) => it?.key === originalKey || it?.key === cleanKey);
|
|
371
426
|
if (extra?.onClick) {
|
|
427
|
+
closeDropdown();
|
|
372
428
|
extra.onClick();
|
|
373
429
|
return;
|
|
374
430
|
}
|
|
375
431
|
|
|
432
|
+
if (isStepMenuItemDisabled(cleanKey)) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
376
436
|
switch (cleanKey) {
|
|
377
437
|
case 'copy-uid':
|
|
438
|
+
closeDropdown();
|
|
378
439
|
handleCopyUid();
|
|
379
440
|
break;
|
|
380
441
|
case 'delete':
|
|
@@ -385,7 +446,15 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
385
446
|
break;
|
|
386
447
|
}
|
|
387
448
|
},
|
|
388
|
-
[
|
|
449
|
+
[
|
|
450
|
+
closeDropdown,
|
|
451
|
+
handleCopyUid,
|
|
452
|
+
handleDelete,
|
|
453
|
+
handleStepConfiguration,
|
|
454
|
+
handleCopyPopupUid,
|
|
455
|
+
extraMenuItems,
|
|
456
|
+
isStepMenuItemDisabled,
|
|
457
|
+
],
|
|
389
458
|
);
|
|
390
459
|
|
|
391
460
|
// 获取单个模型的可配置flows和steps
|
|
@@ -409,6 +478,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
409
478
|
if (await shouldHideStepInSettings(targetModel, flow, actionStep)) {
|
|
410
479
|
return null;
|
|
411
480
|
}
|
|
481
|
+
const disabledState = await resolveStepDisabledInSettings(targetModel, flow, actionStep as any);
|
|
412
482
|
let uiMode: any = await resolveUiMode(actionStep.uiMode, (targetModel as any).context);
|
|
413
483
|
// 检查是否有uiSchema(静态或动态)
|
|
414
484
|
const hasStepUiSchema = actionStep.uiSchema != null;
|
|
@@ -458,6 +528,8 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
458
528
|
title: t(stepTitle) || stepKey,
|
|
459
529
|
modelKey, // 添加模型标识
|
|
460
530
|
uiMode,
|
|
531
|
+
disabled: disabledState.disabled,
|
|
532
|
+
disabledReason: disabledState.reason,
|
|
461
533
|
};
|
|
462
534
|
}),
|
|
463
535
|
).then((steps) => steps.filter(Boolean));
|
|
@@ -494,9 +566,6 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
494
566
|
return result;
|
|
495
567
|
}, [model, menuLevels, getModelConfigurableFlowsAndSteps]);
|
|
496
568
|
|
|
497
|
-
const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = useState<FlowInfo[]>([]);
|
|
498
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
499
|
-
|
|
500
569
|
useEffect(() => {
|
|
501
570
|
const triggerRebuild = () => {
|
|
502
571
|
setRefreshTick((v) => v + 1);
|
|
@@ -603,10 +672,14 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
603
672
|
},
|
|
604
673
|
...((uiMode as any)?.props || {}),
|
|
605
674
|
itemKey: (uiMode as any)?.key,
|
|
675
|
+
disabled: !!stepInfo.disabled,
|
|
676
|
+
disabledReason: stepInfo.disabledReason,
|
|
677
|
+
disabledIconColor,
|
|
606
678
|
};
|
|
607
679
|
items.push({
|
|
608
680
|
key: uniqueKey,
|
|
609
|
-
label: <MenuLabelItem title={
|
|
681
|
+
label: <MenuLabelItem title={stepInfo.title} uiMode={uiMode} itemProps={itemProps} />,
|
|
682
|
+
disabled: !!stepInfo.disabled,
|
|
610
683
|
});
|
|
611
684
|
});
|
|
612
685
|
if (flow.options.divider === 'bottom') {
|
|
@@ -648,7 +721,17 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
648
721
|
|
|
649
722
|
items.push({
|
|
650
723
|
key: uniqueKey,
|
|
651
|
-
label:
|
|
724
|
+
label: stepInfo.disabled ? (
|
|
725
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
|
726
|
+
{stepInfo.title}
|
|
727
|
+
<Tooltip title={stepInfo.disabledReason} placement="right" destroyTooltipOnHide>
|
|
728
|
+
<QuestionCircleOutlined style={{ color: disabledIconColor }} />
|
|
729
|
+
</Tooltip>
|
|
730
|
+
</span>
|
|
731
|
+
) : (
|
|
732
|
+
stepInfo.title
|
|
733
|
+
),
|
|
734
|
+
disabled: !!stepInfo.disabled,
|
|
652
735
|
});
|
|
653
736
|
});
|
|
654
737
|
});
|
|
@@ -663,7 +746,17 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
663
746
|
|
|
664
747
|
subMenuChildren.push({
|
|
665
748
|
key: uniqueKey,
|
|
666
|
-
label:
|
|
749
|
+
label: stepInfo.disabled ? (
|
|
750
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
|
751
|
+
{stepInfo.title}
|
|
752
|
+
<Tooltip title={stepInfo.disabledReason} placement="right" destroyTooltipOnHide>
|
|
753
|
+
<QuestionCircleOutlined style={{ color: disabledIconColor }} />
|
|
754
|
+
</Tooltip>
|
|
755
|
+
</span>
|
|
756
|
+
) : (
|
|
757
|
+
stepInfo.title
|
|
758
|
+
),
|
|
759
|
+
disabled: !!stepInfo.disabled,
|
|
667
760
|
});
|
|
668
761
|
});
|
|
669
762
|
});
|
|
@@ -679,7 +772,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
679
772
|
}
|
|
680
773
|
|
|
681
774
|
return items;
|
|
682
|
-
}, [configurableFlowsAndSteps, flattenSubMenus, t]);
|
|
775
|
+
}, [configurableFlowsAndSteps, disabledIconColor, flattenSubMenus, t]);
|
|
683
776
|
|
|
684
777
|
// 向菜单项添加额外按钮
|
|
685
778
|
const finalMenuItems = useMemo((): NonNullable<MenuProps['items']> => {
|