diff --git a/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx b/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx index 054cf73e27..165a88a3f0 100644 --- a/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx +++ b/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx @@ -165,6 +165,7 @@ const AddFieldButtonCore: React.FC = ({ items={items ?? fieldItems} onModelCreated={onModelCreated} onSubModelAdded={onSubModelAdded} + keepDropdownOpen > {children || defaultChildren} diff --git a/packages/core/flow-engine/src/components/subModel/AddSubModelButton.tsx b/packages/core/flow-engine/src/components/subModel/AddSubModelButton.tsx index 5502354e6e..b701868822 100644 --- a/packages/core/flow-engine/src/components/subModel/AddSubModelButton.tsx +++ b/packages/core/flow-engine/src/components/subModel/AddSubModelButton.tsx @@ -119,6 +119,10 @@ export interface SubModelItem { * 搜索占位符文本(仅对启用搜索的 group 有效) */ searchPlaceholder?: string; + /** + * 点击后是否保持下拉菜单打开状态 + */ + keepDropdownOpen?: boolean; } interface AddSubModelButtonProps { @@ -160,6 +164,13 @@ interface AddSubModelButtonProps { * 按钮文本,默认为 "Add" */ children?: React.ReactNode; + + /** + * 全局设置:点击菜单项后是否保持下拉菜单打开状态 + * 如果设置为 true,所有菜单项点击后都不会关闭下拉菜单 + * 单个菜单项的 keepDropdownOpen 属性会覆盖此全局设置 + */ + keepDropdownOpen?: boolean; } /** @@ -244,6 +255,7 @@ const AddSubModelButtonCore = function AddSubModelButton({ onModelCreated, onSubModelAdded, children = 'Add', + keepDropdownOpen = false, }: AddSubModelButtonProps) { // 构建上下文对象,从 flowEngine 的全局上下文中获取服务 const buildContext = useMemo((): AddSubModelContext => { @@ -304,7 +316,17 @@ const AddSubModelButtonCore = function AddSubModelButton({ } }; - return {children}; + return ( + + {children} + + ); }; export const AddSubModelButton = withFlowDesignMode(AddSubModelButtonCore); diff --git a/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx b/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx index 5d86876a5e..a974991934 100644 --- a/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx +++ b/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx @@ -9,8 +9,7 @@ import { css } from '@emotion/css'; import { Dropdown, DropdownProps, Empty, Input, InputProps, Spin } from 'antd'; -import React, { FC, useEffect, useMemo, useRef, useState } from 'react'; -import { useFlowModel } from '../../hooks'; +import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useFlowEngine } from '../../provider'; // ==================== Types ==================== @@ -22,6 +21,7 @@ export type Item = { children?: Item[] | (() => Item[] | Promise); searchable?: boolean; searchPlaceholder?: string; + keepDropdownOpen?: boolean; [key: string]: any; }; @@ -29,6 +29,16 @@ export type ItemsType = Item[] | (() => Item[] | Promise); interface LazyDropdownMenuProps extends Omit { items: ItemsType; + keepDropdownOpen?: boolean; +} + +interface ExtendedMenuInfo { + key: string; + keyPath: string[]; + item: any; + domEvent: React.MouseEvent | React.KeyboardEvent; + originalItem: Item; + keepDropdownOpen: boolean; } // ==================== Custom Hooks ==================== @@ -102,6 +112,34 @@ const useAsyncMenuItems = (menuVisible: boolean, rootItems: Item[]) => { }; }; +/** + * 处理保持下拉菜单打开状态的逻辑 + */ +const useKeepDropdownOpen = () => { + const shouldKeepOpenRef = useRef(false); + const [forceKeepOpen, setForceKeepOpen] = useState(false); + + const requestKeepOpen = useCallback(() => { + shouldKeepOpenRef.current = true; + setForceKeepOpen(true); + + // 使用 requestAnimationFrame 来确保在下一个渲染周期后重置 + requestAnimationFrame(() => { + shouldKeepOpenRef.current = false; + setForceKeepOpen(false); + }); + }, []); + + const shouldPreventClose = useCallback(() => { + return shouldKeepOpenRef.current || forceKeepOpen; + }, [forceKeepOpen]); + + return { + requestKeepOpen, + shouldPreventClose, + }; +}; + /** * 处理菜单搜索状态 */ @@ -269,6 +307,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM // 使用自定义 hooks const { loadedChildren, loadingKeys, handleLoadChildren } = useAsyncMenuItems(menuVisible, rootItems); const { searchValues, isSearching, updateSearchValue } = useMenuSearch(); + const { requestKeepOpen, shouldPreventClose } = useKeepDropdownOpen(); useSubmenuStyles(menuVisible, dropdownMaxHeight); // 加载根 items,支持同步/异步函数 @@ -376,10 +415,23 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM if (children) { return; } - menu.onClick?.({ + + // 检查是否应该保持下拉菜单打开 + const itemShouldKeepOpen = item.keepDropdownOpen ?? menu.keepDropdownOpen ?? false; + + // 如果需要保持菜单打开,请求保持打开状态 + if (itemShouldKeepOpen) { + requestKeepOpen(); + } + + const extendedInfo: ExtendedMenuInfo = { ...info, + item: info.item || item, originalItem: item, - } as any); + keepDropdownOpen: itemShouldKeepOpen, + }; + + menu.onClick?.(extendedInfo); }, onMouseEnter: () => { setOpenKeys((prev) => { @@ -461,9 +513,16 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM }, }} onOpenChange={(visible) => { + // 阻止在搜索时关闭菜单 if (!visible && isSearching) { return; } + + // 阻止在需要保持打开时关闭菜单 + if (!visible && shouldPreventClose()) { + return; + } + setMenuVisible(visible); }} >