diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx new file mode 100644 index 0000000000..e45f936cde --- /dev/null +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx @@ -0,0 +1,224 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useCallback } from 'react'; +import { Dropdown, Modal, App } from 'antd'; +import type { MenuProps } from 'antd'; +import { + SettingOutlined, + DeleteOutlined, + ExclamationCircleOutlined, + MenuOutlined, + CopyOutlined, +} from '@ant-design/icons'; +import { FlowModel } from '../../../../models'; +import { ActionStepDefinition } from '../../../../types'; +import { openStepSettings } from './StepSettings'; + +/** + * 默认的设置菜单图标组件 + * 提供原有的配置菜单功能 + */ +export const DefaultSettingsIcon: React.FC<{ + model: FlowModel; + showDeleteButton?: boolean; + showCopyUidButton?: boolean; + [key: string]: any; // 允许额外的 props +}> = ({ model, showDeleteButton = true, showCopyUidButton = true }) => { + const { message } = App.useApp(); + + const handleMenuClick = useCallback( + ({ key }: { key: string }) => { + if (key === 'copy-uid') { + // 处理复制 uid 操作 + navigator.clipboard + .writeText(model.uid) + .then(() => { + message.success('UID 已复制到剪贴板'); + }) + .catch((error) => { + console.error('复制失败:', error); + message.error('复制失败,请重试'); + }); + } else if (key === 'delete') { + // 处理删除操作 + Modal.confirm({ + title: '确认删除', + icon: , + content: '确定要删除此项吗?此操作不可撤销。', + okText: '确认删除', + okType: 'primary', + cancelText: '取消', + async onOk() { + try { + await model.destroy(); + } catch (error) { + console.error('删除操作失败:', error); + Modal.error({ + title: '删除失败', + content: '删除操作失败,请检查控制台获取详细信息。', + }); + } + }, + }); + } else { + // 处理step配置,key格式为 "flowKey:stepKey" + const [flowKey, stepKey] = key.split(':'); + try { + openStepSettings({ + model, + flowKey, + stepKey, + }); + } catch (error) { + // 用户取消或出错,静默处理 + console.log('配置弹窗已取消或出错:', error); + } + } + }, + [model, message], + ); + + // 获取可配置的flows和steps + const getConfigurableFlowsAndSteps = useCallback(() => { + try { + const ModelClass = model.constructor as typeof FlowModel; + const flows = ModelClass.getFlows(); + + const flowsArray = Array.from(flows.values()); + + return flowsArray + .map((flow) => { + const configurableSteps = Object.entries(flow.steps) + .map(([stepKey, stepDefinition]) => { + const actionStep = stepDefinition as ActionStepDefinition; + + // 如果步骤设置了 hideInSettings: true,则跳过此步骤 + if (actionStep.hideInSettings) { + return null; + } + + // 从step获取uiSchema(如果存在) + const stepUiSchema = actionStep.uiSchema || {}; + + // 如果step使用了action,也获取action的uiSchema + let actionUiSchema = {}; + if (actionStep.use) { + const action = model.flowEngine?.getAction?.(actionStep.use); + if (action && action.uiSchema) { + actionUiSchema = action.uiSchema; + } + } + + // 合并uiSchema,确保step的uiSchema优先级更高 + const mergedUiSchema = { ...actionUiSchema }; + + // 将stepUiSchema中的字段合并到mergedUiSchema + Object.entries(stepUiSchema).forEach(([fieldKey, schema]) => { + if (mergedUiSchema[fieldKey]) { + mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema }; + } else { + mergedUiSchema[fieldKey] = schema; + } + }); + + // 如果没有可配置的UI Schema,返回null + if (Object.keys(mergedUiSchema).length === 0) { + return null; + } + + return { + stepKey, + step: actionStep, + uiSchema: mergedUiSchema, + title: actionStep.title || stepKey, + }; + }) + .filter(Boolean); + + return configurableSteps.length > 0 ? { flow, steps: configurableSteps } : null; + }) + .filter(Boolean); + } catch (error) { + console.warn('[DefaultSettingsIcon] 获取可配置flows失败:', error); + return []; + } + }, [model]); + + const configurableFlowsAndSteps = getConfigurableFlowsAndSteps(); + + // 如果没有可配置的flows且不显示删除按钮和复制UID按钮,不显示菜单 + if (configurableFlowsAndSteps.length === 0 && !showDeleteButton && !showCopyUidButton) { + return null; + } + + // 构建菜单项 + const menuItems: MenuProps['items'] = []; + + // 添加flows和steps配置项 + if (configurableFlowsAndSteps.length > 0) { + configurableFlowsAndSteps.forEach(({ flow, steps }) => { + // 始终按flow分组显示 + menuItems.push({ + key: `flow-group-${flow.key}`, + label: flow.title || flow.key, + type: 'group', + }); + + steps.forEach((stepInfo) => { + menuItems.push({ + key: `${flow.key}:${stepInfo.stepKey}`, + icon: , + label: stepInfo.title, + }); + }); + }); + } + + // 添加分割线、复制 uid 和删除按钮 + if (showCopyUidButton || showDeleteButton) { + // 如果有flows配置项,添加分割线 + if (configurableFlowsAndSteps.length > 0) { + menuItems.push({ + type: 'divider' as const, + }); + } + + // 添加复制 uid 按钮 + if (showCopyUidButton) { + menuItems.push({ + key: 'copy-uid', + icon: , + label: '复制 UID', + }); + } + + // 添加删除按钮 + if (showDeleteButton) { + menuItems.push({ + key: 'delete', + icon: , + label: '删除', + }); + } + } + + return ( + + + + ); +}; diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx index b349862afe..f4410ec3c9 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx @@ -8,22 +8,14 @@ */ import React, { useState, useCallback, useRef, useEffect } from 'react'; -import { Alert, Modal, Space, Dropdown, App } from 'antd'; -import type { MenuProps } from 'antd'; -import { - SettingOutlined, - DeleteOutlined, - ExclamationCircleOutlined, - MenuOutlined, - CopyOutlined, -} from '@ant-design/icons'; +import { Alert, Space } from 'antd'; import { observer } from '@formily/react'; import { css } from '@emotion/css'; import { FlowModel } from '../../../../models'; -import { ActionStepDefinition } from '../../../../types'; +import { ToolbarItemConfig } from '../../../../types'; import { useFlowModelById } from '../../../../hooks'; import { useFlowEngine } from '../../../../provider'; -import { openStepSettings } from './StepSettings'; +import { FlowEngine } from '../../../../flowEngine'; // 检测DOM中直接子元素是否包含button元素的辅助函数 const detectButtonInDOM = (container: HTMLElement): boolean => { @@ -42,6 +34,41 @@ const detectButtonInDOM = (container: HTMLElement): boolean => { return false; }; +// 渲染工具栏项目的辅助函数 +const renderToolbarItems = ( + model: FlowModel, + showDeleteButton: boolean, + showCopyUidButton: boolean, + flowEngine: FlowEngine, +) => { + const toolbarItems = flowEngine?.flowSettings?.getToolbarItems?.() || []; + + return toolbarItems + .filter((itemConfig: ToolbarItemConfig) => { + // 检查项目是否应该显示 + return itemConfig.visible ? itemConfig.visible(model) : true; + }) + .map((itemConfig: ToolbarItemConfig) => { + // 渲染项目组件 + const ItemComponent = itemConfig.component; + + // 对于默认设置项目,传递额外的 props + if (itemConfig.key === 'settings-menu') { + return ( + + ); + } + + // 其他项目只传递 model + return ; + }); +}; + // 使用与 NocoBase 一致的悬浮工具栏样式 const floatContainerStyles = ({ showBackground, showBorder }) => css` position: relative; @@ -196,7 +223,7 @@ const FlowsFloatContextMenuWithModel: React.FC = observer( const [hideMenu, setHideMenu] = useState(false); const [hasButton, setHasButton] = useState(false); const containerRef = useRef(null); - const { message } = App.useApp(); + const flowEngine = useFlowEngine(); // 检测DOM中是否包含button元素 useEffect(() => { @@ -241,58 +268,6 @@ const FlowsFloatContextMenuWithModel: React.FC = observer( } }, []); - const handleMenuClick = useCallback( - ({ key }: { key: string }) => { - if (key === 'copy-uid') { - // 处理复制 uid 操作 - navigator.clipboard - .writeText(model.uid) - .then(() => { - message.success('UID 已复制到剪贴板'); - }) - .catch((error) => { - console.error('复制失败:', error); - message.error('复制失败,请重试'); - }); - } else if (key === 'delete') { - // 处理删除操作 - Modal.confirm({ - title: '确认删除', - icon: , - content: '确定要删除此项吗?此操作不可撤销。', - okText: '确认删除', - okType: 'primary', - cancelText: '取消', - async onOk() { - try { - await model.destroy(); - } catch (error) { - console.error('删除操作失败:', error); - Modal.error({ - title: '删除失败', - content: '删除操作失败,请检查控制台获取详细信息。', - }); - } - }, - }); - } else { - // 处理step配置,key格式为 "flowKey:stepKey" - const [flowKey, stepKey] = key.split(':'); - try { - openStepSettings({ - model, - flowKey, - stepKey, - }); - } catch (error) { - // 用户取消或出错,静默处理 - console.log('配置弹窗已取消或出错:', error); - } - } - }, - [model, message], - ); - if (!model) { return ; } @@ -302,130 +277,6 @@ const FlowsFloatContextMenuWithModel: React.FC = observer( return <>{children}; } - // 获取可配置的flows和steps - const getConfigurableFlowsAndSteps = useCallback(() => { - try { - const ModelClass = model.constructor as typeof FlowModel; - const flows = ModelClass.getFlows(); - - const flowsArray = Array.from(flows.values()); - - return flowsArray - .map((flow) => { - const configurableSteps = Object.entries(flow.steps) - .map(([stepKey, stepDefinition]) => { - const actionStep = stepDefinition as ActionStepDefinition; - - // 如果步骤设置了 hideInSettings: true,则跳过此步骤 - if (actionStep.hideInSettings) { - return null; - } - - // 从step获取uiSchema(如果存在) - const stepUiSchema = actionStep.uiSchema || {}; - - // 如果step使用了action,也获取action的uiSchema - let actionUiSchema = {}; - if (actionStep.use) { - const action = model.flowEngine?.getAction?.(actionStep.use); - if (action && action.uiSchema) { - actionUiSchema = action.uiSchema; - } - } - - // 合并uiSchema,确保step的uiSchema优先级更高 - const mergedUiSchema = { ...actionUiSchema }; - - // 将stepUiSchema中的字段合并到mergedUiSchema - Object.entries(stepUiSchema).forEach(([fieldKey, schema]) => { - if (mergedUiSchema[fieldKey]) { - mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema }; - } else { - mergedUiSchema[fieldKey] = schema; - } - }); - - // 如果没有可配置的UI Schema,返回null - if (Object.keys(mergedUiSchema).length === 0) { - return null; - } - - return { - stepKey, - step: actionStep, - uiSchema: mergedUiSchema, - title: actionStep.title || stepKey, - }; - }) - .filter(Boolean); - - return configurableSteps.length > 0 ? { flow, steps: configurableSteps } : null; - }) - .filter(Boolean); - } catch (error) { - console.warn('[FlowsFloatContextMenu] 获取可配置flows失败:', error); - return []; - } - }, [model]); - - const configurableFlowsAndSteps = getConfigurableFlowsAndSteps(); - - // 如果没有可配置的flows且不显示删除按钮和复制UID按钮,直接返回children - if (configurableFlowsAndSteps.length === 0 && !showDeleteButton && !showCopyUidButton) { - return <>{children}; - } - - // 构建菜单项 - 使用与 FlowsContextMenu 相同的逻辑 - const menuItems: MenuProps['items'] = []; - - // 添加flows和steps配置项 - if (configurableFlowsAndSteps.length > 0) { - configurableFlowsAndSteps.forEach(({ flow, steps }) => { - // 始终按flow分组显示 - menuItems.push({ - key: `flow-group-${flow.key}`, - label: flow.title || flow.key, - type: 'group', - }); - - steps.forEach((stepInfo) => { - menuItems.push({ - key: `${flow.key}:${stepInfo.stepKey}`, - icon: , - label: stepInfo.title, - }); - }); - }); - } - - // 添加分割线、复制 uid 和删除按钮 - if (showCopyUidButton || showDeleteButton) { - // 如果有flows配置项,添加分割线 - if (configurableFlowsAndSteps.length > 0) { - menuItems.push({ - type: 'divider' as const, - }); - } - - // 添加复制 uid 按钮 - if (showCopyUidButton) { - menuItems.push({ - key: 'copy-uid', - icon: , - label: '复制 UID', - }); - } - - // 添加删除按钮 - if (showDeleteButton) { - menuItems.push({ - key: 'delete', - icon: , - label: '删除', - }); - } - } - return (
= observer(
- - - + {renderToolbarItems(model, showDeleteButton, showCopyUidButton, flowEngine)}
diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/MinimalDrawerTest.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/MinimalDrawerTest.tsx deleted file mode 100644 index 3d7167898e..0000000000 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/MinimalDrawerTest.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import React from 'react'; -import { Button } from 'antd'; -import { openStepSettingsDrawer } from '../StepSettingsDrawer'; - -/** - * 最小化的抽屉测试 - */ - -// 创建一个模拟的模型对象 -const mockModel = { - uid: 'test-model', - flowEngine: { - context: {}, - flowSettings: { - components: {}, - scopes: {}, - }, - }, - getFlow: (flowKey: string) => { - if (flowKey === 'testFlow') { - return { - key: 'testFlow', - title: '测试流程', - steps: { - drawerStep: { - title: '抽屉步骤', - settingMode: 'drawer', - uiSchema: { - title: { - type: 'string', - title: '标题', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - description: { - type: 'string', - title: '描述', - 'x-decorator': 'FormItem', - 'x-component': 'Input.TextArea', - }, - }, - defaultParams: { - title: '默认标题', - description: '默认描述', - }, - }, - }, - }; - } - return null; - }, - getStepParams: (flowKey: string, stepKey: string) => { - return { - title: '当前标题', - description: '当前描述', - }; - }, - setStepParams: (flowKey: string, stepKey: string, params: any) => { - console.log('设置步骤参数:', { flowKey, stepKey, params }); - }, - save: async () => { - console.log('保存模型'); - return Promise.resolve(); - }, -}; - -const MinimalDrawerTest: React.FC = () => { - const handleOpenDrawer = async () => { - try { - const result = await openStepSettingsDrawer({ - model: mockModel, - flowKey: 'testFlow', - stepKey: 'drawerStep', - drawerWidth: 600, - drawerTitle: '测试抽屉配置', - }); - console.log('抽屉配置结果:', result); - } catch (error) { - console.log('抽屉配置取消或出错:', error); - } - }; - - return ( -
-

最小化抽屉测试

-

这是一个最小化的抽屉测试,用于验证基本功能。

- -
- ); -}; - -export default MinimalDrawerTest; diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/SimpleDrawerTest.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/SimpleDrawerTest.tsx deleted file mode 100644 index aa7542f2ae..0000000000 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/SimpleDrawerTest.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import React from 'react'; -import { Button } from 'antd'; -import { FlowModel } from '../../../../../models'; -import { openStepSettings } from '../StepSettings'; - -/** - * 简单的抽屉测试示例 - */ - -// 创建一个简单的测试模型 -class SimpleTestModel extends FlowModel { - render() { - return ( -
-

简单抽屉测试

-

点击下面的按钮测试抽屉配置功能

- -
- ); - } -} - -// 注册简单的测试流程 -SimpleTestModel.registerFlow({ - key: 'testFlow', - title: '测试流程', - steps: { - drawerStep: { - title: '抽屉步骤', - settingMode: 'drawer', // 使用抽屉模式 - uiSchema: { - title: { - type: 'string', - title: '标题', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - 'x-component-props': { - placeholder: '请输入标题', - }, - }, - description: { - type: 'string', - title: '描述', - 'x-decorator': 'FormItem', - 'x-component': 'Input.TextArea', - 'x-component-props': { - placeholder: '请输入描述', - rows: 3, - }, - }, - enabled: { - type: 'boolean', - title: '启用', - 'x-decorator': 'FormItem', - 'x-component': 'Switch', - }, - priority: { - type: 'string', - title: '优先级', - enum: [ - { label: '低', value: 'low' }, - { label: '中', value: 'medium' }, - { label: '高', value: 'high' }, - ], - 'x-decorator': 'FormItem', - 'x-component': 'Select', - }, - }, - defaultParams: { - title: '默认标题', - description: '默认描述', - enabled: true, - priority: 'medium', - }, - // 简单的处理函数 - handler: (ctx, params) => { - console.log('执行抽屉步骤:', params); - return params; - }, - }, - }, -}); - -export { SimpleTestModel }; diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/StepSettingsDrawerExample.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/StepSettingsDrawerExample.tsx deleted file mode 100644 index 966279b088..0000000000 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/StepSettingsDrawerExample.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import React from 'react'; -import { Button, Space } from 'antd'; -import { FlowModel } from '../../../../../models'; -import { openStepSettings, getStepSettingMode } from '../StepSettings'; - -/** - * 示例:展示如何使用 settingMode: 'drawer' 配置 - */ - -// 创建一个示例模型类 -class ExampleModel extends FlowModel { - render() { - return ( -
-

示例模型

-

这是一个配置了不同 settingMode 的示例模型

- - - - -
-

Dialog Step 模式: {getStepSettingMode(this, 'exampleFlow', 'dialogStep')}

-

Drawer Step 模式: {getStepSettingMode(this, 'exampleFlow', 'drawerStep')}

-
-
- ); - } -} - -// 注册示例流程 -ExampleModel.registerFlow({ - key: 'exampleFlow', - title: '示例流程', - auto: true, - steps: { - // 使用默认的 dialog 模式 - dialogStep: { - title: '对话框步骤', - use: 'exampleAction', - // settingMode 默认为 'dialog' - uiSchema: { - message: { - type: 'string', - title: '消息内容', - 'x-decorator': 'FormItem', - 'x-component': 'Input.TextArea', - }, - type: { - type: 'string', - title: '消息类型', - enum: [ - { label: '信息', value: 'info' }, - { label: '成功', value: 'success' }, - { label: '警告', value: 'warning' }, - { label: '错误', value: 'error' }, - ], - 'x-decorator': 'FormItem', - 'x-component': 'Select', - }, - }, - defaultParams: { - message: '这是一个对话框配置的步骤', - type: 'info', - }, - }, - // 使用 drawer 模式 - drawerStep: { - title: '抽屉步骤', - use: 'exampleAction', - settingMode: 'drawer', // 配置为使用抽屉模式 - uiSchema: { - title: { - type: 'string', - title: '标题', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - description: { - type: 'string', - title: '描述', - 'x-decorator': 'FormItem', - 'x-component': 'Input.TextArea', - }, - priority: { - type: 'string', - title: '优先级', - enum: [ - { label: '低', value: 'low' }, - { label: '中', value: 'medium' }, - { label: '高', value: 'high' }, - ], - 'x-decorator': 'FormItem', - 'x-component': 'Radio.Group', - }, - enabled: { - type: 'boolean', - title: '启用', - 'x-decorator': 'FormItem', - 'x-component': 'Switch', - }, - }, - defaultParams: { - title: '这是一个抽屉配置的步骤', - description: '抽屉模式提供更大的配置空间', - priority: 'medium', - enabled: true, - }, - }, - }, -}); - -// 注册示例动作 (需要通过 FlowEngine 实例注册) -// ExampleModel.registerAction({ -// name: 'exampleAction', -// title: '示例动作', -// handler: (ctx, params) => { -// console.log('执行示例动作:', params); -// return params; -// }, -// }); - -export { ExampleModel }; diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/TestDrawerFixed.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/TestDrawerFixed.tsx deleted file mode 100644 index 6a38895244..0000000000 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/__examples__/TestDrawerFixed.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import React from 'react'; -import { Button } from 'antd'; -import { FlowModel } from '../../../../../models'; -import { openStepSettings } from '../StepSettings'; - -/** - * 测试修复后的抽屉功能 - */ - -// 创建一个测试模型 -class TestDrawerModel extends FlowModel { - render() { - return ( -
-

测试修复后的抽屉功能

-

点击下面的按钮测试抽屉配置功能

- -
- ); - } -} - -// 注册测试流程 -TestDrawerModel.registerFlow({ - key: 'testFlow', - title: '测试流程', - steps: { - drawerStep: { - title: '抽屉步骤', - settingMode: 'drawer', // 使用抽屉模式 - uiSchema: { - title: { - type: 'string', - title: '标题', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - 'x-component-props': { - placeholder: '请输入标题', - }, - }, - description: { - type: 'string', - title: '描述', - 'x-decorator': 'FormItem', - 'x-component': 'Input.TextArea', - 'x-component-props': { - placeholder: '请输入描述', - rows: 3, - }, - }, - enabled: { - type: 'boolean', - title: '启用', - 'x-decorator': 'FormItem', - 'x-component': 'Switch', - }, - priority: { - type: 'string', - title: '优先级', - enum: [ - { label: '低', value: 'low' }, - { label: '中', value: 'medium' }, - { label: '高', value: 'high' }, - ], - 'x-decorator': 'FormItem', - 'x-component': 'Select', - }, - tags: { - type: 'array', - title: '标签', - 'x-decorator': 'FormItem', - 'x-component': 'Select', - 'x-component-props': { - mode: 'tags', - placeholder: '请输入标签', - }, - }, - }, - defaultParams: { - title: '默认标题', - description: '默认描述', - enabled: true, - priority: 'medium', - tags: ['测试', '抽屉'], - }, - // 简单的处理函数 - handler: (ctx, params) => { - console.log('执行抽屉步骤:', params); - return params; - }, - }, - }, -}); - -export { TestDrawerModel }; diff --git a/packages/core/flow-engine/src/flowSettings.ts b/packages/core/flow-engine/src/flowSettings.ts index c6712228cc..82e4ef4576 100644 --- a/packages/core/flow-engine/src/flowSettings.ts +++ b/packages/core/flow-engine/src/flowSettings.ts @@ -9,23 +9,41 @@ import { define, observable } from '@formily/reactive'; import { openStepSettingsDialog } from './components/settings/wrappers/contextual/StepSettingsDialog'; -import { StepSettingsDialogProps } from './types'; +import { StepSettingsDialogProps, ToolbarItemConfig } from './types'; +import { DefaultSettingsIcon } from './components/settings/wrappers/contextual/DefaultSettingsIcon'; export class FlowSettings { public components: Record = {}; public scopes: Record = {}; private antdComponentsLoaded = false; public enabled: boolean; + public toolbarItems: ToolbarItemConfig[] = []; constructor() { // 初始默认为 false,由 SchemaComponentProvider 根据实际设计模式状态同步设置 this.enabled = false; + // 添加默认的配置项目 + this.addDefaultToolbarItems(); + define(this, { enabled: observable, }); } + /** + * 添加默认的工具栏项目 + * @private + */ + private addDefaultToolbarItems(): void { + // 添加基础的配置菜单项目(原有的菜单功能) + this.toolbarItems.push({ + key: 'settings-menu', + component: DefaultSettingsIcon, + sort: 0, // 默认为0,作为第一个添加的项目 + }); + } + /** * 加载 FlowSettings 所需的资源。 * @returns {Promise} @@ -193,6 +211,105 @@ export class FlowSettings { this.enabled = false; } + /** + * 添加扩展工具栏项目 + * @param {ToolbarItemConfig} config 项目配置 + * @example + * // 添加一个复制图标组件 + * const CopyIcon = ({ model }) => { + * const handleCopy = () => { + * navigator.clipboard.writeText(model.uid); + * }; + * return ( + * + * + * + * ); + * }; + * + * flowSettings.addToolbarItem({ + * key: 'copy', + * component: CopyIcon, + * sort: 10 // 数字越小越靠右 + * }); + * + * // 添加下拉菜单项目组件 + * const MoreActionsIcon = ({ model }) => { + * const menuItems = [ + * { key: 'action1', label: '操作1', onClick: () => console.log('操作1', model) }, + * { key: 'action2', label: '操作2', onClick: () => console.log('操作2', model) } + * ]; + * return ( + * + * + * + * ); + * }; + * + * flowSettings.addToolbarItem({ + * key: 'more-actions', + * component: MoreActionsIcon, + * visible: (model) => model.someCondition, + * sort: 20 // 数字越大越靠左 + * }); + */ + public addToolbarItem(config: ToolbarItemConfig): void { + // 检查是否已存在相同 key 的项目 + const existingIndex = this.toolbarItems.findIndex((item) => item.key === config.key); + if (existingIndex !== -1) { + console.warn(`FlowSettings: Toolbar item with key '${config.key}' already exists and will be replaced.`); + this.toolbarItems[existingIndex] = config; + } else { + this.toolbarItems.push(config); + } + + // 按 sort 字段反向排序,sort 越小越靠右(先添加的在右边) + this.toolbarItems.sort((a, b) => (b.sort || 0) - (a.sort || 0)); + } + + /** + * 批量添加工具栏项目 + * @param {ToolbarItemConfig[]} configs 项目配置数组 + * @example + * flowSettings.addToolbarItems([ + * { key: 'copy', component: CopyIcon, sort: 10 }, + * { key: 'edit', component: EditIcon, sort: 20 } + * ]); + */ + public addToolbarItems(configs: ToolbarItemConfig[]): void { + configs.forEach((config) => this.addToolbarItem(config)); + } + + /** + * 移除工具栏项目 + * @param {string} key 项目的唯一标识 + * @example + * flowSettings.removeToolbarItem('copy'); + */ + public removeToolbarItem(key: string): void { + const index = this.toolbarItems.findIndex((item) => item.key === key); + if (index !== -1) { + this.toolbarItems.splice(index, 1); + } + } + + /** + * 获取所有工具栏项目配置 + * @returns {ToolbarItemConfig[]} 所有项目配置 + */ + public getToolbarItems(): ToolbarItemConfig[] { + return [...this.toolbarItems]; + } + + /** + * 清空所有工具栏项目 + * @example + * flowSettings.clearToolbarItems(); + */ + public clearToolbarItems(): void { + this.toolbarItems = []; + } + /** * 显示单个步骤的配置界面 * @param {StepSettingsDialogProps} props 步骤设置对话框的属性 diff --git a/packages/core/flow-engine/src/types.ts b/packages/core/flow-engine/src/types.ts index 286c7b6248..4c959d0322 100644 --- a/packages/core/flow-engine/src/types.ts +++ b/packages/core/flow-engine/src/types.ts @@ -361,3 +361,17 @@ export interface FieldFlowModelMeta extends FlowModelMeta { } export type { ForkFlowModel } from './models'; + +/** + * 工具栏项目配置接口 + */ +export interface ToolbarItemConfig { + /** 项目的唯一标识 */ + key: string; + /** 项目组件,接收 model 作为 props,内部处理所有逻辑 */ + component: React.ComponentType<{ model: FlowModel; [key: string]: any }>; + /** 是否显示项目的条件函数 */ + visible?: (model: FlowModel) => boolean; + /** 排序权重,数字越小越靠右(先添加的在右边) */ + sort?: number; +}