mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
feat: support extends toolbar setting icon
This commit is contained in:
parent
2e27ee90c0
commit
a6d061a192
@ -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: <ExclamationCircleOutlined />,
|
||||
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: <SettingOutlined />,
|
||||
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: <CopyOutlined />,
|
||||
label: '复制 UID',
|
||||
});
|
||||
}
|
||||
|
||||
// 添加删除按钮
|
||||
if (showDeleteButton) {
|
||||
menuItems.push({
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
label: '删除',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: menuItems,
|
||||
onClick: handleMenuClick,
|
||||
}}
|
||||
trigger={['hover']}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<MenuOutlined role="button" aria-label="flows-settings" style={{ cursor: 'pointer', fontSize: 12 }} />
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
@ -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 (
|
||||
<ItemComponent
|
||||
key={itemConfig.key}
|
||||
model={model}
|
||||
showDeleteButton={showDeleteButton}
|
||||
showCopyUidButton={showCopyUidButton}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 其他项目只传递 model
|
||||
return <ItemComponent key={itemConfig.key} model={model} />;
|
||||
});
|
||||
};
|
||||
|
||||
// 使用与 NocoBase 一致的悬浮工具栏样式
|
||||
const floatContainerStyles = ({ showBackground, showBorder }) => css`
|
||||
position: relative;
|
||||
@ -196,7 +223,7 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
||||
const [hideMenu, setHideMenu] = useState<boolean>(false);
|
||||
const [hasButton, setHasButton] = useState<boolean>(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { message } = App.useApp();
|
||||
const flowEngine = useFlowEngine();
|
||||
|
||||
// 检测DOM中是否包含button元素
|
||||
useEffect(() => {
|
||||
@ -241,58 +268,6 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = 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: <ExclamationCircleOutlined />,
|
||||
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 <Alert message="提供的模型无效" type="error" />;
|
||||
}
|
||||
@ -302,130 +277,6 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = 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: <SettingOutlined />,
|
||||
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: <CopyOutlined />,
|
||||
label: '复制 UID',
|
||||
});
|
||||
}
|
||||
|
||||
// 添加删除按钮
|
||||
if (showDeleteButton) {
|
||||
menuItems.push({
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
label: '删除',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
@ -442,16 +293,7 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
||||
<div className="general-schema-designer">
|
||||
<div className="general-schema-designer-icons">
|
||||
<Space size={3} align="center">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: menuItems,
|
||||
onClick: handleMenuClick,
|
||||
}}
|
||||
trigger={['hover']}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<MenuOutlined role="button" aria-label="flows-settings" style={{ cursor: 'pointer', fontSize: 12 }} />
|
||||
</Dropdown>
|
||||
{renderToolbarItems(model, showDeleteButton, showCopyUidButton, flowEngine)}
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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 (
|
||||
<div style={{ padding: 20 }}>
|
||||
<h3>最小化抽屉测试</h3>
|
||||
<p>这是一个最小化的抽屉测试,用于验证基本功能。</p>
|
||||
<Button type="primary" onClick={handleOpenDrawer}>
|
||||
打开抽屉配置
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MinimalDrawerTest;
|
@ -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 (
|
||||
<div style={{ padding: 20, border: '1px solid #ccc', borderRadius: 4 }}>
|
||||
<h3>简单抽屉测试</h3>
|
||||
<p>点击下面的按钮测试抽屉配置功能</p>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
openStepSettings({
|
||||
model: this,
|
||||
flowKey: 'testFlow',
|
||||
stepKey: 'drawerStep',
|
||||
})
|
||||
.then((values) => {
|
||||
console.log('保存的配置:', values);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('用户取消或出错:', error);
|
||||
});
|
||||
}}
|
||||
>
|
||||
打开抽屉配置
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册简单的测试流程
|
||||
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 };
|
@ -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 (
|
||||
<div style={{ padding: 20, border: '1px solid #ccc', borderRadius: 4 }}>
|
||||
<h3>示例模型</h3>
|
||||
<p>这是一个配置了不同 settingMode 的示例模型</p>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
openStepSettings({
|
||||
model: this,
|
||||
flowKey: 'exampleFlow',
|
||||
stepKey: 'dialogStep',
|
||||
});
|
||||
}}
|
||||
>
|
||||
打开对话框配置 (Dialog)
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
openStepSettings({
|
||||
model: this,
|
||||
flowKey: 'exampleFlow',
|
||||
stepKey: 'drawerStep',
|
||||
});
|
||||
}}
|
||||
>
|
||||
打开抽屉配置 (Drawer)
|
||||
</Button>
|
||||
</Space>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<p>Dialog Step 模式: {getStepSettingMode(this, 'exampleFlow', 'dialogStep')}</p>
|
||||
<p>Drawer Step 模式: {getStepSettingMode(this, 'exampleFlow', 'drawerStep')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册示例流程
|
||||
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 };
|
@ -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 (
|
||||
<div style={{ padding: 20, border: '1px solid #ccc', borderRadius: 4 }}>
|
||||
<h3>测试修复后的抽屉功能</h3>
|
||||
<p>点击下面的按钮测试抽屉配置功能</p>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
openStepSettings({
|
||||
model: this,
|
||||
flowKey: 'testFlow',
|
||||
stepKey: 'drawerStep',
|
||||
})
|
||||
.then((values) => {
|
||||
console.log('保存的配置:', values);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('用户取消或出错:', error);
|
||||
});
|
||||
}}
|
||||
>
|
||||
打开抽屉配置 (修复版)
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册测试流程
|
||||
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 };
|
@ -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<string, any> = {};
|
||||
public scopes: Record<string, any> = {};
|
||||
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<void>}
|
||||
@ -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 (
|
||||
* <Tooltip title="复制">
|
||||
* <CopyOutlined onClick={handleCopy} style={{ cursor: 'pointer', fontSize: 12 }} />
|
||||
* </Tooltip>
|
||||
* );
|
||||
* };
|
||||
*
|
||||
* 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 (
|
||||
* <Dropdown menu={{ items: menuItems }} trigger={['hover']}>
|
||||
* <MoreOutlined style={{ cursor: 'pointer', fontSize: 12 }} />
|
||||
* </Dropdown>
|
||||
* );
|
||||
* };
|
||||
*
|
||||
* 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 步骤设置对话框的属性
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user