@nocobase/flow-engine 2.1.0-alpha.1 → 2.1.0-alpha.10

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.
Files changed (283) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/BlockScopedFlowEngine.js +0 -1
  4. package/lib/FlowDefinition.d.ts +2 -0
  5. package/lib/JSRunner.d.ts +15 -0
  6. package/lib/JSRunner.js +82 -7
  7. package/lib/ViewScopedFlowEngine.js +8 -1
  8. package/lib/acl/Acl.js +13 -3
  9. package/lib/components/FlowContextSelector.js +155 -10
  10. package/lib/components/MobilePopup.js +6 -5
  11. package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
  12. package/lib/components/dnd/gridDragPlanner.js +59 -3
  13. package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
  14. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
  15. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
  16. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +21 -3
  17. package/lib/components/subModel/AddSubModelButton.js +16 -1
  18. package/lib/components/subModel/utils.js +2 -2
  19. package/lib/components/variables/VariableInput.js +9 -4
  20. package/lib/components/variables/VariableTag.js +46 -39
  21. package/lib/components/variables/utils.d.ts +7 -0
  22. package/lib/components/variables/utils.js +42 -2
  23. package/lib/data-source/index.d.ts +7 -27
  24. package/lib/data-source/index.js +84 -51
  25. package/lib/executor/FlowExecutor.d.ts +2 -1
  26. package/lib/executor/FlowExecutor.js +190 -26
  27. package/lib/flowContext.d.ts +230 -7
  28. package/lib/flowContext.js +2270 -148
  29. package/lib/flowEngine.d.ts +160 -1
  30. package/lib/flowEngine.js +383 -26
  31. package/lib/flowI18n.js +6 -4
  32. package/lib/flowSettings.d.ts +14 -6
  33. package/lib/flowSettings.js +51 -17
  34. package/lib/index.d.ts +7 -1
  35. package/lib/index.js +21 -0
  36. package/lib/lazy-helper.d.ts +14 -0
  37. package/lib/lazy-helper.js +71 -0
  38. package/lib/locale/en-US.json +9 -2
  39. package/lib/locale/index.d.ts +14 -0
  40. package/lib/locale/zh-CN.json +8 -1
  41. package/lib/models/CollectionFieldModel.d.ts +1 -0
  42. package/lib/models/CollectionFieldModel.js +3 -2
  43. package/lib/models/flowModel.d.ts +7 -0
  44. package/lib/models/flowModel.js +83 -8
  45. package/lib/provider.js +7 -6
  46. package/lib/resources/baseRecordResource.d.ts +5 -0
  47. package/lib/resources/baseRecordResource.js +24 -0
  48. package/lib/resources/multiRecordResource.d.ts +1 -0
  49. package/lib/resources/multiRecordResource.js +11 -4
  50. package/lib/resources/singleRecordResource.js +2 -0
  51. package/lib/resources/sqlResource.d.ts +4 -3
  52. package/lib/resources/sqlResource.js +8 -3
  53. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
  54. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
  55. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
  56. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
  57. package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
  58. package/lib/runjs-context/contexts/base.js +706 -41
  59. package/lib/runjs-context/contributions.d.ts +33 -0
  60. package/lib/runjs-context/contributions.js +88 -0
  61. package/lib/runjs-context/helpers.js +12 -1
  62. package/lib/runjs-context/registry.d.ts +1 -1
  63. package/lib/runjs-context/setup.js +22 -9
  64. package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
  65. package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
  66. package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
  67. package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
  68. package/lib/runjs-context/snippets/index.d.ts +11 -1
  69. package/lib/runjs-context/snippets/index.js +61 -40
  70. package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
  71. package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
  72. package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
  73. package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
  74. package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
  75. package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
  76. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
  77. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
  78. package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
  79. package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
  80. package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
  81. package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
  82. package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
  83. package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
  84. package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
  85. package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
  86. package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
  87. package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
  88. package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
  89. package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
  90. package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
  91. package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
  92. package/lib/runjsLibs.d.ts +28 -0
  93. package/lib/runjsLibs.js +532 -0
  94. package/lib/scheduler/ModelOperationScheduler.d.ts +7 -1
  95. package/lib/scheduler/ModelOperationScheduler.js +28 -23
  96. package/lib/types.d.ts +63 -1
  97. package/lib/utils/associationObjectVariable.d.ts +2 -2
  98. package/lib/utils/createCollectionContextMeta.js +1 -0
  99. package/lib/utils/createEphemeralContext.js +2 -2
  100. package/lib/utils/dateVariable.d.ts +16 -0
  101. package/lib/utils/dateVariable.js +380 -0
  102. package/lib/utils/exceptions.d.ts +7 -0
  103. package/lib/utils/exceptions.js +10 -0
  104. package/lib/utils/index.d.ts +8 -3
  105. package/lib/utils/index.js +49 -0
  106. package/lib/utils/params-resolvers.js +16 -9
  107. package/lib/utils/parsePathnameToViewParams.js +1 -1
  108. package/lib/utils/resolveModuleUrl.d.ts +58 -0
  109. package/lib/utils/resolveModuleUrl.js +65 -0
  110. package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
  111. package/lib/utils/resolveRunJSObjectValues.js +61 -0
  112. package/lib/utils/runjsModuleLoader.d.ts +58 -0
  113. package/lib/utils/runjsModuleLoader.js +422 -0
  114. package/lib/utils/runjsTemplateCompat.d.ts +35 -0
  115. package/lib/utils/runjsTemplateCompat.js +743 -0
  116. package/lib/utils/runjsValue.d.ts +29 -0
  117. package/lib/utils/runjsValue.js +275 -0
  118. package/lib/utils/safeGlobals.d.ts +18 -8
  119. package/lib/utils/safeGlobals.js +164 -17
  120. package/lib/utils/schema-utils.d.ts +17 -1
  121. package/lib/utils/schema-utils.js +80 -0
  122. package/lib/views/FlowView.d.ts +7 -1
  123. package/lib/views/createViewMeta.d.ts +0 -7
  124. package/lib/views/createViewMeta.js +19 -70
  125. package/lib/views/index.d.ts +1 -2
  126. package/lib/views/index.js +4 -3
  127. package/lib/views/runViewBeforeClose.d.ts +10 -0
  128. package/lib/views/runViewBeforeClose.js +45 -0
  129. package/lib/views/useDialog.d.ts +2 -1
  130. package/lib/views/useDialog.js +28 -6
  131. package/lib/views/useDrawer.d.ts +2 -1
  132. package/lib/views/useDrawer.js +27 -5
  133. package/lib/views/usePage.d.ts +6 -1
  134. package/lib/views/usePage.js +53 -9
  135. package/lib/views/usePopover.js +4 -1
  136. package/lib/views/viewEvents.d.ts +17 -0
  137. package/lib/views/viewEvents.js +90 -0
  138. package/package.json +5 -5
  139. package/src/BlockScopedFlowEngine.ts +2 -5
  140. package/src/JSRunner.ts +111 -5
  141. package/src/ViewScopedFlowEngine.ts +8 -0
  142. package/src/__tests__/JSRunner.test.ts +91 -1
  143. package/src/__tests__/createViewMeta.popup.test.ts +62 -1
  144. package/src/__tests__/flowContext.test.ts +693 -1
  145. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
  146. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  147. package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
  148. package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
  149. package/src/__tests__/flowRuntimeContext.test.ts +2 -1
  150. package/src/__tests__/flowSettings.open.test.tsx +123 -19
  151. package/src/__tests__/flowSettings.test.ts +94 -15
  152. package/src/__tests__/provider.test.tsx +0 -5
  153. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  154. package/src/__tests__/runjsContext.test.ts +23 -7
  155. package/src/__tests__/runjsContextImplementations.test.ts +34 -3
  156. package/src/__tests__/runjsContextRuntime.test.ts +3 -3
  157. package/src/__tests__/runjsContributions.test.ts +89 -0
  158. package/src/__tests__/runjsExternalLibs.test.ts +242 -0
  159. package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
  160. package/src/__tests__/runjsLocales.test.ts +4 -1
  161. package/src/__tests__/runjsPreprocessDefault.test.ts +72 -0
  162. package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
  163. package/src/__tests__/runjsSnippets.test.ts +40 -3
  164. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  165. package/src/acl/Acl.tsx +3 -3
  166. package/src/components/FlowContextSelector.tsx +208 -12
  167. package/src/components/MobilePopup.tsx +4 -2
  168. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +3 -3
  169. package/src/components/__tests__/gridDragPlanner.test.ts +229 -1
  170. package/src/components/dnd/gridDragPlanner.ts +68 -2
  171. package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
  172. package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
  173. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
  174. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
  175. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +31 -4
  176. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
  177. package/src/components/subModel/AddSubModelButton.tsx +17 -1
  178. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
  179. package/src/components/subModel/utils.ts +1 -1
  180. package/src/components/variables/VariableInput.tsx +12 -4
  181. package/src/components/variables/VariableTag.tsx +54 -45
  182. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
  183. package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
  184. package/src/components/variables/__tests__/utils.test.ts +81 -3
  185. package/src/components/variables/utils.ts +67 -6
  186. package/src/data-source/index.ts +88 -110
  187. package/src/executor/FlowExecutor.ts +230 -28
  188. package/src/executor/__tests__/flowExecutor.test.ts +123 -0
  189. package/src/flowContext.ts +2989 -212
  190. package/src/flowEngine.ts +427 -22
  191. package/src/flowI18n.ts +7 -5
  192. package/src/flowSettings.ts +58 -18
  193. package/src/index.ts +14 -1
  194. package/src/lazy-helper.tsx +57 -0
  195. package/src/locale/en-US.json +9 -2
  196. package/src/locale/zh-CN.json +8 -1
  197. package/src/models/CollectionFieldModel.tsx +3 -1
  198. package/src/models/__tests__/dispatchEvent.when.test.ts +768 -0
  199. package/src/models/__tests__/flowModel.clone.test.ts +416 -0
  200. package/src/models/__tests__/flowModel.test.ts +20 -4
  201. package/src/models/flowModel.tsx +112 -7
  202. package/src/provider.tsx +9 -7
  203. package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
  204. package/src/resources/__tests__/sqlResource.test.ts +60 -0
  205. package/src/resources/baseRecordResource.ts +31 -0
  206. package/src/resources/multiRecordResource.ts +11 -4
  207. package/src/resources/singleRecordResource.ts +3 -0
  208. package/src/resources/sqlResource.ts +11 -6
  209. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
  210. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
  211. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
  212. package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
  213. package/src/runjs-context/contexts/base.ts +715 -44
  214. package/src/runjs-context/contributions.ts +88 -0
  215. package/src/runjs-context/helpers.ts +11 -1
  216. package/src/runjs-context/registry.ts +1 -1
  217. package/src/runjs-context/setup.ts +24 -9
  218. package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
  219. package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
  220. package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
  221. package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
  222. package/src/runjs-context/snippets/index.ts +75 -41
  223. package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
  224. package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
  225. package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
  226. package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
  227. package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
  228. package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
  229. package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
  230. package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
  231. package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
  232. package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
  233. package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
  234. package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
  235. package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
  236. package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
  237. package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
  238. package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
  239. package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
  240. package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
  241. package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
  242. package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
  243. package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
  244. package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
  245. package/src/runjsLibs.ts +622 -0
  246. package/src/scheduler/ModelOperationScheduler.ts +41 -24
  247. package/src/types.ts +86 -1
  248. package/src/utils/__tests__/dateVariable.test.ts +101 -0
  249. package/src/utils/__tests__/params-resolvers.test.ts +40 -0
  250. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
  251. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
  252. package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
  253. package/src/utils/__tests__/runjsValue.test.ts +44 -0
  254. package/src/utils/__tests__/safeGlobals.test.ts +57 -2
  255. package/src/utils/__tests__/utils.test.ts +157 -0
  256. package/src/utils/associationObjectVariable.ts +2 -2
  257. package/src/utils/createCollectionContextMeta.ts +1 -0
  258. package/src/utils/createEphemeralContext.ts +5 -4
  259. package/src/utils/dateVariable.ts +397 -0
  260. package/src/utils/exceptions.ts +11 -0
  261. package/src/utils/index.ts +38 -3
  262. package/src/utils/params-resolvers.ts +23 -9
  263. package/src/utils/parsePathnameToViewParams.ts +2 -2
  264. package/src/utils/resolveModuleUrl.ts +91 -0
  265. package/src/utils/resolveRunJSObjectValues.ts +46 -0
  266. package/src/utils/runjsModuleLoader.ts +553 -0
  267. package/src/utils/runjsTemplateCompat.ts +828 -0
  268. package/src/utils/runjsValue.ts +287 -0
  269. package/src/utils/safeGlobals.ts +188 -17
  270. package/src/utils/schema-utils.ts +109 -1
  271. package/src/views/FlowView.tsx +11 -1
  272. package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
  273. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  274. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +44 -16
  275. package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
  276. package/src/views/createViewMeta.ts +22 -75
  277. package/src/views/index.tsx +1 -2
  278. package/src/views/runViewBeforeClose.ts +19 -0
  279. package/src/views/useDialog.tsx +34 -5
  280. package/src/views/useDrawer.tsx +33 -4
  281. package/src/views/usePage.tsx +63 -8
  282. package/src/views/usePopover.tsx +4 -1
  283. package/src/views/viewEvents.ts +55 -0
@@ -46,6 +46,7 @@ export interface Point {
46
46
  export interface GridLayoutData {
47
47
  rows: Record<string, string[][]>;
48
48
  sizes: Record<string, number[]>;
49
+ rowOrder?: string[];
49
50
  }
50
51
 
51
52
  export interface ColumnSlot {
@@ -142,6 +143,49 @@ export interface LayoutSnapshot {
142
143
  containerRect: Rect;
143
144
  }
144
145
 
146
+ const deriveRowOrder = (rows: Record<string, string[][]>, provided?: string[]) => {
147
+ const order: string[] = [];
148
+ const used = new Set<string>();
149
+
150
+ (provided || Object.keys(rows)).forEach((rowId) => {
151
+ if (rows[rowId] && !used.has(rowId)) {
152
+ order.push(rowId);
153
+ used.add(rowId);
154
+ }
155
+ });
156
+
157
+ Object.keys(rows).forEach((rowId) => {
158
+ if (!used.has(rowId)) {
159
+ order.push(rowId);
160
+ used.add(rowId);
161
+ }
162
+ });
163
+
164
+ return order;
165
+ };
166
+
167
+ const normalizeRowsWithOrder = (rows: Record<string, string[][]>, order: string[]) => {
168
+ const next: Record<string, string[][]> = {};
169
+ order.forEach((rowId) => {
170
+ if (rows[rowId]) {
171
+ next[rowId] = rows[rowId];
172
+ }
173
+ });
174
+ Object.keys(rows).forEach((rowId) => {
175
+ if (!next[rowId]) {
176
+ next[rowId] = rows[rowId];
177
+ }
178
+ });
179
+ return next;
180
+ };
181
+
182
+ const ensureRowOrder = (layout: GridLayoutData) => {
183
+ const order = deriveRowOrder(layout.rows, layout.rowOrder);
184
+ layout.rowOrder = order;
185
+ layout.rows = normalizeRowsWithOrder(layout.rows, order);
186
+ return order;
187
+ };
188
+
145
189
  export interface BuildLayoutSnapshotOptions {
146
190
  container: HTMLElement | null;
147
191
  }
@@ -289,7 +333,10 @@ export const buildLayoutSnapshot = ({ container }: BuildLayoutSnapshotOptions):
289
333
 
290
334
  const columnElements = Array.from(
291
335
  container.querySelectorAll(`[data-grid-column-row-id="${rowId}"][data-grid-column-index]`),
292
- ) as HTMLElement[];
336
+ ).filter((el) => {
337
+ // 只保留当前 row 下的直接列,避免嵌套 Grid 中相同 rowId 的列混入
338
+ return (el as HTMLElement).closest('[data-grid-row-id]') === rowElement;
339
+ }) as HTMLElement[];
293
340
 
294
341
  const sortedColumns = columnElements.sort((a, b) => {
295
342
  const indexA = Number(a.dataset.gridColumnIndex || 0);
@@ -319,7 +366,10 @@ export const buildLayoutSnapshot = ({ container }: BuildLayoutSnapshotOptions):
319
366
 
320
367
  const itemElements = Array.from(
321
368
  columnElement.querySelectorAll(`[data-grid-item-row-id="${rowId}"][data-grid-column-index="${columnIndex}"]`),
322
- ) as HTMLElement[];
369
+ ).filter((el) => {
370
+ // 只保留当前 column 下的直接 item,避免命中更深层嵌套 column 的 item
371
+ return (el as HTMLElement).closest('[data-grid-column-row-id][data-grid-column-index]') === columnElement;
372
+ }) as HTMLElement[];
323
373
 
324
374
  const sortedItems = itemElements.sort((a, b) => {
325
375
  const indexA = Number(a.dataset.gridItemIndex || 0);
@@ -465,10 +515,12 @@ const removeItemFromLayout = (layout: GridLayoutData, uidValue: string) => {
465
515
  if (columns.length === 0) {
466
516
  delete layout.rows[rowId];
467
517
  delete layout.sizes[rowId];
518
+ ensureRowOrder(layout);
468
519
  return;
469
520
  }
470
521
 
471
522
  normalizeRowSizes(rowId, layout);
523
+ ensureRowOrder(layout);
472
524
  };
473
525
 
474
526
  const toIntSizes = (weights: number[], count: number): number[] => {
@@ -592,8 +644,10 @@ export const simulateLayoutForSlot = ({
592
644
  const cloned: GridLayoutData = {
593
645
  rows: _.cloneDeep(layout.rows),
594
646
  sizes: _.cloneDeep(layout.sizes),
647
+ rowOrder: layout.rowOrder ? [...layout.rowOrder] : undefined,
595
648
  };
596
649
 
650
+ ensureRowOrder(cloned);
597
651
  removeItemFromLayout(cloned, sourceUid);
598
652
 
599
653
  const createRowId = generateRowId ?? uid;
@@ -638,8 +692,16 @@ export const simulateLayoutForSlot = ({
638
692
  case 'row-gap': {
639
693
  const newRowId = createRowId();
640
694
  const rowPosition: 'before' | 'after' = slot.position === 'above' ? 'before' : 'after';
695
+ const currentOrder = deriveRowOrder(cloned.rows, cloned.rowOrder);
641
696
  cloned.rows = insertRow(cloned.rows, slot.targetRowId, newRowId, rowPosition, [[sourceUid]]);
642
697
  cloned.sizes[newRowId] = [DEFAULT_GRID_COLUMNS];
698
+ const targetIndex = currentOrder.indexOf(slot.targetRowId);
699
+ const insertIndex =
700
+ targetIndex === -1 ? currentOrder.length : rowPosition === 'before' ? targetIndex : targetIndex + 1;
701
+ const nextOrder = [...currentOrder];
702
+ nextOrder.splice(insertIndex, 0, newRowId);
703
+ cloned.rowOrder = nextOrder;
704
+ cloned.rows = normalizeRowsWithOrder(cloned.rows, nextOrder);
643
705
  break;
644
706
  }
645
707
  case 'empty-row': {
@@ -649,11 +711,15 @@ export const simulateLayoutForSlot = ({
649
711
  [newRowId]: [[sourceUid]],
650
712
  };
651
713
  cloned.sizes[newRowId] = [DEFAULT_GRID_COLUMNS];
714
+ const currentOrder = deriveRowOrder(cloned.rows, cloned.rowOrder);
715
+ cloned.rowOrder = [...currentOrder.filter((id) => id !== newRowId), newRowId];
716
+ cloned.rows = normalizeRowsWithOrder(cloned.rows, cloned.rowOrder);
652
717
  break;
653
718
  }
654
719
  default:
655
720
  break;
656
721
  }
657
722
 
723
+ ensureRowOrder(cloned);
658
724
  return cloned;
659
725
  };
@@ -52,7 +52,8 @@ export const SwitchWithTitle: FC = observer(
52
52
  };
53
53
 
54
54
  // 点击整个容器时触发
55
- const handleWrapperClick = () => {
55
+ const handleWrapperClick = (e: React.MouseEvent) => {
56
+ e.stopPropagation();
56
57
  if (disabled) return;
57
58
  handleChange(!checked);
58
59
  };
@@ -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
- if (!Component) {
155
- return <>{title}</>;
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 <Component title={title} {...itemProps} />;
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, message]);
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
- [handleCopyUid, handleDelete, handleStepConfiguration, handleCopyPopupUid, extraMenuItems],
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={t(stepInfo.title)} uiMode={uiMode} itemProps={itemProps} />,
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: t(stepInfo.title),
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: t(stepInfo.title),
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']> => {
@@ -7,14 +7,19 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { DeleteOutlined, ExclamationCircleOutlined, SettingOutlined } from '@ant-design/icons';
10
+ import { DeleteOutlined, ExclamationCircleOutlined, QuestionCircleOutlined, SettingOutlined } from '@ant-design/icons';
11
11
  import type { MenuProps } from 'antd';
12
- import { Alert, Dropdown, Modal } from 'antd';
12
+ import { Alert, Dropdown, Modal, Tooltip, theme } from 'antd';
13
13
  import React, { useCallback, useEffect, useState } from 'react';
14
14
  import { FlowRuntimeContext } from '../../../../flowContext';
15
15
  import { useFlowModelById } from '../../../../hooks';
16
16
  import { FlowModel } from '../../../../models';
17
- import { getT, setupRuntimeContextSteps, shouldHideStepInSettings } from '../../../../utils';
17
+ import {
18
+ getT,
19
+ resolveStepDisabledInSettings,
20
+ setupRuntimeContextSteps,
21
+ shouldHideStepInSettings,
22
+ } from '../../../../utils';
18
23
  import { openStepSettingsDialog } from './StepSettingsDialog';
19
24
  import { ActionDefinition } from '../../../../types';
20
25
  import { observer } from '../../../../reactive';
@@ -74,6 +79,21 @@ const FlowsContextMenu: React.FC<FlowsContextMenuProps> = (props) => {
74
79
  const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
75
80
  ({ model, children, enabled = true, position = 'right', showDeleteButton = true }) => {
76
81
  const t = getT(model);
82
+ const { token } = theme.useToken();
83
+ const disabledIconColor = token?.colorTextTertiary || token?.colorTextDescription || token?.colorTextSecondary;
84
+ const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = useState<any[]>([]);
85
+ const isStepMenuItemDisabled = useCallback(
86
+ (key: string) => {
87
+ const [flowKey, stepKey] = key.split(':');
88
+ if (!flowKey || !stepKey) return false;
89
+ return configurableFlowsAndSteps.some(({ flow, steps }) => {
90
+ if (flow.key !== flowKey) return false;
91
+ return steps.some((stepInfo) => stepInfo.stepKey === stepKey && !!stepInfo.disabled);
92
+ });
93
+ },
94
+ [configurableFlowsAndSteps],
95
+ );
96
+
77
97
  const handleMenuClick = useCallback(
78
98
  ({ key }: { key: string }) => {
79
99
  if (key === 'delete') {
@@ -99,6 +119,9 @@ const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
99
119
  },
100
120
  });
101
121
  } else {
122
+ if (isStepMenuItemDisabled(key)) {
123
+ return;
124
+ }
102
125
  // 处理step配置,key格式为 "flowKey:stepKey"
103
126
  const [flowKey, stepKey] = key.split(':');
104
127
  try {
@@ -127,7 +150,7 @@ const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
127
150
  }
128
151
  }
129
152
  },
130
- [model],
153
+ [isStepMenuItemDisabled, model],
131
154
  );
132
155
 
133
156
  if (!model) {
@@ -156,6 +179,7 @@ const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
156
179
  if (await shouldHideStepInSettings(model as FlowModel, flow, actionStep)) {
157
180
  return null;
158
181
  }
182
+ const disabledState = await resolveStepDisabledInSettings(model as FlowModel, flow, actionStep);
159
183
 
160
184
  // 从step获取uiSchema(如果存在)
161
185
  const stepUiSchema: ActionDefinition['uiSchema'] = actionStep.uiSchema || {};
@@ -191,6 +215,8 @@ const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
191
215
  step: actionStep,
192
216
  uiSchema: mergedUiSchema,
193
217
  title: actionStep.title || stepKey,
218
+ disabled: disabledState.disabled,
219
+ disabledReason: disabledState.reason,
194
220
  };
195
221
  }),
196
222
  ).then((steps) => steps.filter(Boolean));
@@ -206,8 +232,6 @@ const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
206
232
  }
207
233
  }, [model]);
208
234
 
209
- const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = useState<any[]>([]);
210
-
211
235
  useEffect(() => {
212
236
  let mounted = true;
213
237
  (async () => {
@@ -243,7 +267,17 @@ const FlowsContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
243
267
  menuItems.push({
244
268
  key: `${flow.key}:${stepInfo.stepKey}`,
245
269
  icon: <SettingOutlined />,
246
- label: stepInfo.title,
270
+ label: stepInfo.disabled ? (
271
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
272
+ {stepInfo.title}
273
+ <Tooltip title={stepInfo.disabledReason} placement="right" destroyTooltipOnHide>
274
+ <QuestionCircleOutlined style={{ color: disabledIconColor }} />
275
+ </Tooltip>
276
+ </span>
277
+ ) : (
278
+ stepInfo.title
279
+ ),
280
+ disabled: !!stepInfo.disabled,
247
281
  });
248
282
  });
249
283
  });
@@ -14,7 +14,15 @@ import { Button, Space } from 'antd';
14
14
  import React, { useEffect } from 'react';
15
15
  import { FlowSettingsContextProvider, useFlowSettingsContext } from '../../../../hooks/useFlowSettingsContext';
16
16
  import { StepSettingsDialogProps } from '../../../../types';
17
- import { compileUiSchema, FlowExitException, getT, resolveDefaultParams, resolveStepUiSchema } from '../../../../utils';
17
+ import {
18
+ compileUiSchema,
19
+ FlowExitException,
20
+ getT,
21
+ resolveDefaultParams,
22
+ resolveStepUiSchema,
23
+ FlowCancelSaveException,
24
+ } from '../../../../utils';
25
+ import { FlowExitAllException } from '../../../../utils/exceptions';
18
26
  import { observer } from '../../../../reactive';
19
27
 
20
28
  const SchemaField = createSchemaField();
@@ -126,6 +134,17 @@ const openStepSettingsDialog = async ({
126
134
  };
127
135
 
128
136
  const openView = model.context.viewer[mode].bind(model.context.viewer);
137
+ const resolvedUiModeProps = toJS(uiModeProps) || {};
138
+ const { zIndex: uiModeZIndex, ...restUiModeProps } = resolvedUiModeProps;
139
+ const resolveDialogZIndex = (rawZIndex?: number) => {
140
+ const nextZIndex =
141
+ typeof model.context.viewer?.getNextZIndex === 'function'
142
+ ? model.context.viewer.getNextZIndex()
143
+ : (model.context.themeToken?.zIndexPopupBase || 1000) + 1;
144
+ const inputZIndex = Number(rawZIndex) || 0;
145
+ return Math.max(nextZIndex, inputZIndex);
146
+ };
147
+ const mergedZIndex = resolveDialogZIndex(uiModeZIndex);
129
148
 
130
149
  const form = createForm({
131
150
  initialValues: compileUiSchema(scopes, initialValues),
@@ -144,7 +163,8 @@ const openStepSettingsDialog = async ({
144
163
  title: dialogTitle || t(title),
145
164
  width: dialogWidth,
146
165
  destroyOnClose: true,
147
- ...toJS(uiModeProps),
166
+ ...restUiModeProps,
167
+ zIndex: mergedZIndex,
148
168
  // 透传 navigation,便于变量元信息根据真实视图栈推断父级弹窗
149
169
  inputArgs,
150
170
  onClose: () => {
@@ -157,7 +177,11 @@ const openStepSettingsDialog = async ({
157
177
  useEffect(() => {
158
178
  return autorun(() => {
159
179
  const dynamicProps = toJS(uiModeProps);
160
- currentDialog.update(dynamicProps);
180
+ const { zIndex, ...restDynamicProps } = dynamicProps || {};
181
+ currentDialog.update({
182
+ ...restDynamicProps,
183
+ zIndex: resolveDialogZIndex(zIndex),
184
+ });
161
185
  });
162
186
  }, []);
163
187
 
@@ -203,7 +227,10 @@ const openStepSettingsDialog = async ({
203
227
  await afterParamsSave(flowRuntimeContext, currentValues, previousParams);
204
228
  }
205
229
  } catch (error) {
206
- if (error instanceof FlowExitException) {
230
+ if (error instanceof FlowCancelSaveException) {
231
+ return;
232
+ }
233
+ if (error instanceof FlowExitException || error instanceof FlowExitAllException) {
207
234
  currentDialog.close();
208
235
  return;
209
236
  }