/**
* 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 { DownOutlined, SettingOutlined } from '@ant-design/icons';
import { Alert, Button, Dropdown, Space } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import { useFlowModelById } from '../../../../hooks';
import { FlowModel } from '../../../../models';
import { shouldHideStepInSettings } from '../../../../utils';
import { ActionDefinition } from '../../../../types';
import { observer } from '../../../../reactive';
// 支持两种使用方式的接口定义
interface ModelProvidedProps {
model: FlowModel;
text?: string;
icon?: React.ReactNode;
size?: 'small' | 'middle' | 'large';
type?: 'default' | 'primary' | 'dashed' | 'link' | 'text';
disabled?: boolean;
showDropdownIcon?: boolean;
onClick?: () => void;
style?: React.CSSProperties;
className?: string;
}
interface ModelByIdProps {
uid: string;
modelClassName: string;
text?: string;
icon?: React.ReactNode;
size?: 'small' | 'middle' | 'large';
type?: 'default' | 'primary' | 'dashed' | 'link' | 'text';
disabled?: boolean;
showDropdownIcon?: boolean;
onClick?: () => void;
style?: React.CSSProperties;
className?: string;
}
type FlowsDropdownButtonProps = ModelProvidedProps | ModelByIdProps;
// 判断是否是通过ID获取模型的props
const isModelByIdProps = (props: FlowsDropdownButtonProps): props is ModelByIdProps => {
return 'uid' in props && 'modelClassName' in props && Boolean(props.uid) && Boolean(props.modelClassName);
};
/**
* 可自定义的下拉菜单触发按钮组件
* 支持两种使用方式:
* 1. 直接提供model:
* 2. 通过uid和modelClassName获取model:
*
* 菜单结构:按flow分组显示steps
*/
export const FlowsDropdownButton: React.FC = (props) => {
if (isModelByIdProps(props)) {
const { uid, modelClassName, ...restProps } = props;
return ;
} else {
const { model, ...restProps } = props;
return ;
}
};
// 使用传入的model
const FlowsDropdownButtonWithModel: React.FC = observer(
({
model,
text = '流程配置',
icon = ,
size = 'middle',
type = 'default',
disabled = false,
showDropdownIcon = true,
onClick,
style,
className,
}) => {
const handleClick = () => {
onClick?.();
};
const handleMenuClick = useCallback(
({ key }: { key: string }) => {
// key格式为 "flowKey:stepKey"
const [flowKey, stepKey] = key.split(':');
try {
model.openFlowSettings({
flowKey,
stepKey,
});
} catch (error) {
// 用户取消或出错
console.log('配置弹窗已取消或出错:', error);
}
},
[model],
);
if (!model) {
return ;
}
// 获取可配置的flows和steps
const getConfigurableFlowsAndSteps = useCallback(async () => {
try {
// const ModelClass = model.constructor as typeof FlowModel;
const flows = model.getFlows();
const flowsArray = Array.from(flows.values());
const result = await Promise.all(
flowsArray.map(async (flow) => {
const configurableSteps = await Promise.all(
Object.entries(flow.steps).map(async ([stepKey, stepDefinition]) => {
const actionStep: any = stepDefinition;
// 支持静态与动态 hideInSettings
if (await shouldHideStepInSettings(model, flow, actionStep)) {
return null;
}
// 从step获取uiSchema(如果存在)
const stepUiSchema: ActionDefinition['uiSchema'] = actionStep.uiSchema || {};
// 如果step使用了action,也获取action的uiSchema
let actionUiSchema = {};
if (actionStep.use) {
const action = model.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,
};
}),
).then((steps) => steps.filter(Boolean));
return configurableSteps.length > 0 ? { flow, steps: configurableSteps } : null;
}),
);
return result.filter(Boolean);
} catch (error) {
console.warn('[FlowsDropdownButton] 获取可配置flows失败:', error);
return [];
}
}, [model]);
const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = useState([]);
useEffect(() => {
let mounted = true;
(async () => {
const flows = await getConfigurableFlowsAndSteps();
if (mounted) {
setConfigurableFlowsAndSteps(flows as any[]);
}
})();
return () => {
mounted = false;
};
}, [getConfigurableFlowsAndSteps]);
// 构建菜单项
const buildMenuItems = () => {
const items = [];
configurableFlowsAndSteps.forEach(({ flow, steps }) => {
// 始终按flow分组显示
items.push({
key: `flow-group-${flow.key}`,
label: flow.title || flow.key,
type: 'group',
});
steps.forEach((stepInfo) => {
items.push({
key: `${flow.key}:${stepInfo.stepKey}`,
icon: ,
label: stepInfo.title,
});
});
});
return items;
};
const menuItems = buildMenuItems();
const button = (
);
// 如果没有可配置的flows,返回普通按钮
if (configurableFlowsAndSteps.length === 0) {
return button;
}
return (
<>
{button}
>
);
},
);
// 通过useFlowModelById hook获取model
const FlowsDropdownButtonWithModelById: React.FC = observer(
({
uid,
modelClassName,
text = '流程配置',
icon = ,
size = 'middle',
type = 'default',
disabled = false,
showDropdownIcon = true,
onClick,
style,
className,
}) => {
const model = useFlowModelById(uid, modelClassName);
if (!model) {
return ;
}
return (
);
},
);