mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
feat: support switch menu when add sub models
This commit is contained in:
parent
3dbd75b9e4
commit
dec669a94a
@ -14,7 +14,7 @@ import { FlowModel } from '../../models';
|
|||||||
import { FlowModelOptions, ModelConstructor } from '../../types';
|
import { FlowModelOptions, ModelConstructor } from '../../types';
|
||||||
import { FlowSettingsButton } from '../common/FlowSettingsButton';
|
import { FlowSettingsButton } from '../common/FlowSettingsButton';
|
||||||
import { withFlowDesignMode } from '../common/withFlowDesignMode';
|
import { withFlowDesignMode } from '../common/withFlowDesignMode';
|
||||||
import { AddSubModelButton, SubModelItemsType, mergeSubModelItems } from './AddSubModelButton';
|
import { AddSubModelButton, SubModelItemsType, mergeSubModelItems, AddSubModelContext } from './AddSubModelButton';
|
||||||
|
|
||||||
export type BuildCreateModelOptionsType = {
|
export type BuildCreateModelOptionsType = {
|
||||||
defaultOptions: FlowModelOptions;
|
defaultOptions: FlowModelOptions;
|
||||||
@ -129,12 +129,47 @@ const AddFieldButtonCore: React.FC<AddFieldButtonProps> = ({
|
|||||||
key: field.name,
|
key: field.name,
|
||||||
label: field.title,
|
label: field.title,
|
||||||
icon: fieldClass.meta?.icon,
|
icon: fieldClass.meta?.icon,
|
||||||
|
unique: true,
|
||||||
createModelOptions: buildCreateModelOptions({
|
createModelOptions: buildCreateModelOptions({
|
||||||
defaultOptions,
|
defaultOptions,
|
||||||
collectionField: field,
|
collectionField: field,
|
||||||
fieldPath: field.name,
|
fieldPath: field.name,
|
||||||
fieldModelClass: fieldClass,
|
fieldModelClass: fieldClass,
|
||||||
}),
|
}),
|
||||||
|
toggleDetector: (ctx: AddSubModelContext) => {
|
||||||
|
// 检测是否已存在该字段的子模型
|
||||||
|
const subModels = ctx.model.subModels[subModelKey];
|
||||||
|
|
||||||
|
const checkFieldInStepParams = (subModel: FlowModel): boolean => {
|
||||||
|
const stepParams = subModel.stepParams;
|
||||||
|
|
||||||
|
// 快速检查:如果 stepParams 为空,直接返回 false
|
||||||
|
if (!stepParams || Object.keys(stepParams).length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有 flow 和 step 来查找 fieldPath 或 field 参数
|
||||||
|
for (const flowKey in stepParams) {
|
||||||
|
const flowSteps = stepParams[flowKey];
|
||||||
|
if (!flowSteps) continue;
|
||||||
|
|
||||||
|
for (const stepKey in flowSteps) {
|
||||||
|
const stepData = flowSteps[stepKey];
|
||||||
|
if (stepData?.fieldPath === field.name || stepData?.field === field.name) {
|
||||||
|
return true; // 找到匹配,立即返回
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(subModels)) {
|
||||||
|
return subModels.some(checkFieldInStepParams);
|
||||||
|
} else if (subModels) {
|
||||||
|
return checkFieldInStepParams(subModels);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
allFields.push(fieldItem);
|
allFields.push(fieldItem);
|
||||||
}
|
}
|
||||||
@ -151,7 +186,7 @@ const AddFieldButtonCore: React.FC<AddFieldButtonProps> = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}, [model, subModelBaseClass, fields, buildCreateModelOptions]);
|
}, [model, subModelBaseClass, fields, buildCreateModelOptions, subModelKey]);
|
||||||
|
|
||||||
const fieldItems = useMemo(() => {
|
const fieldItems = useMemo(() => {
|
||||||
return mergeSubModelItems([buildFieldItems, appendItems], { addDividers: true });
|
return mergeSubModelItems([buildFieldItems, appendItems], { addDividers: true });
|
||||||
|
@ -8,12 +8,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { Switch } from 'antd';
|
||||||
import { FlowModel } from '../../models';
|
import { FlowModel } from '../../models';
|
||||||
import { ModelConstructor } from '../../types';
|
import { ModelConstructor } from '../../types';
|
||||||
import { withFlowDesignMode } from '../common/withFlowDesignMode';
|
import { withFlowDesignMode } from '../common/withFlowDesignMode';
|
||||||
import LazyDropdown, { Item, ItemsType } from './LazyDropdown';
|
import LazyDropdown, { Item, ItemsType } from './LazyDropdown';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 类型定义
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
export interface AddSubModelContext {
|
export interface AddSubModelContext {
|
||||||
model: FlowModel;
|
model: FlowModel;
|
||||||
globals: Record<string, any>;
|
globals: Record<string, any>;
|
||||||
@ -22,85 +27,6 @@ export interface AddSubModelContext {
|
|||||||
subModelBaseClass?: ModelConstructor;
|
subModelBaseClass?: ModelConstructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SubModelItemsType =
|
|
||||||
| SubModelItem[]
|
|
||||||
| ((ctx: AddSubModelContext) => SubModelItem[] | Promise<SubModelItem[]>);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并多个 SubModelItemsType 的选项
|
|
||||||
*/
|
|
||||||
export interface MergeSubModelItemsOptions {
|
|
||||||
/**
|
|
||||||
* 是否在不同来源之间添加分割线
|
|
||||||
*/
|
|
||||||
addDividers?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并多个不同来源的 SubModelItemsType 成一个
|
|
||||||
*
|
|
||||||
* 支持静态数组和异步函数的混合合并,统一返回异步函数处理所有情况
|
|
||||||
*
|
|
||||||
* @param sources - 要合并的 SubModelItemsType 数组,支持 undefined 和 null(会被过滤)
|
|
||||||
* @param options - 合并选项
|
|
||||||
* @returns 合并后的 SubModelItemsType(如果有多个来源则返回异步函数)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* const mergedItems = mergeSubModelItems([
|
|
||||||
* fieldItems, // 字段 items(静态数组)
|
|
||||||
* customItems, // 自定义 items(静态数组)
|
|
||||||
* async (ctx) => [...], // 动态 items(异步函数)
|
|
||||||
* condition ? extraItems : null, // 条件性 items
|
|
||||||
* ], { addDividers: true });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function mergeSubModelItems(
|
|
||||||
sources: (SubModelItemsType | undefined | null)[],
|
|
||||||
options: MergeSubModelItemsOptions = {},
|
|
||||||
): SubModelItemsType {
|
|
||||||
const { addDividers = false } = options;
|
|
||||||
|
|
||||||
// 过滤掉空值
|
|
||||||
const validSources = sources.filter((source): source is SubModelItemsType => source !== undefined && source !== null);
|
|
||||||
|
|
||||||
if (validSources.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validSources.length === 1) {
|
|
||||||
return validSources[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统一返回异步函数处理所有情况
|
|
||||||
return async (ctx: AddSubModelContext) => {
|
|
||||||
const result: SubModelItem[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < validSources.length; i++) {
|
|
||||||
const source = validSources[i];
|
|
||||||
let items: SubModelItem[] = [];
|
|
||||||
|
|
||||||
if (Array.isArray(source)) {
|
|
||||||
items = source;
|
|
||||||
} else {
|
|
||||||
items = await source(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加分割线(除了第一个来源)
|
|
||||||
if (i > 0 && addDividers && items.length > 0) {
|
|
||||||
result.push({
|
|
||||||
key: `divider-${i}`,
|
|
||||||
type: 'divider',
|
|
||||||
} as SubModelItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(...items);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SubModelItem {
|
export interface SubModelItem {
|
||||||
key?: string;
|
key?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -111,68 +37,53 @@ export interface SubModelItem {
|
|||||||
createModelOptions?:
|
createModelOptions?:
|
||||||
| { use: string; stepParams?: Record<string, any> }
|
| { use: string; stepParams?: Record<string, any> }
|
||||||
| ((item: SubModelItem) => { use: string; stepParams?: Record<string, any> });
|
| ((item: SubModelItem) => { use: string; stepParams?: Record<string, any> });
|
||||||
/**
|
|
||||||
* 是否在 group 内启用搜索功能(仅对 group 类型有效)
|
|
||||||
*/
|
|
||||||
searchable?: boolean;
|
searchable?: boolean;
|
||||||
/**
|
|
||||||
* 搜索占位符文本(仅对启用搜索的 group 有效)
|
|
||||||
*/
|
|
||||||
searchPlaceholder?: string;
|
searchPlaceholder?: string;
|
||||||
/**
|
|
||||||
* 点击后是否保持下拉菜单打开状态
|
|
||||||
*/
|
|
||||||
keepDropdownOpen?: boolean;
|
keepDropdownOpen?: boolean;
|
||||||
|
unique?: boolean;
|
||||||
|
toggleDetector?: (ctx: AddSubModelContext) => boolean | Promise<boolean>;
|
||||||
|
removeModelOptions?: {
|
||||||
|
customRemove?: (ctx: AddSubModelContext, item: SubModelItem) => Promise<void>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SubModelItemsType =
|
||||||
|
| SubModelItem[]
|
||||||
|
| ((ctx: AddSubModelContext) => SubModelItem[] | Promise<SubModelItem[]>);
|
||||||
|
|
||||||
|
export interface MergeSubModelItemsOptions {
|
||||||
|
addDividers?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddSubModelButtonProps {
|
interface AddSubModelButtonProps {
|
||||||
/**
|
|
||||||
* 父模型实例
|
|
||||||
*/
|
|
||||||
model: FlowModel;
|
model: FlowModel;
|
||||||
|
|
||||||
/**
|
|
||||||
* 子模型类型列表
|
|
||||||
*/
|
|
||||||
items: SubModelItemsType;
|
items: SubModelItemsType;
|
||||||
|
|
||||||
/**
|
|
||||||
* 子模型基类,传递给 context 供 items 函数使用
|
|
||||||
*/
|
|
||||||
subModelBaseClass?: string | ModelConstructor;
|
subModelBaseClass?: string | ModelConstructor;
|
||||||
|
|
||||||
/**
|
|
||||||
* 子模型类型:'object' 表示单个子模型,'array' 表示子模型数组
|
|
||||||
*/
|
|
||||||
subModelType?: 'object' | 'array';
|
subModelType?: 'object' | 'array';
|
||||||
|
|
||||||
/**
|
|
||||||
* 子模型在父模型中的键名
|
|
||||||
*/
|
|
||||||
subModelKey: string;
|
subModelKey: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建后的回调函数
|
|
||||||
*/
|
|
||||||
onModelCreated?: (subModel: FlowModel) => Promise<void>;
|
onModelCreated?: (subModel: FlowModel) => Promise<void>;
|
||||||
/**
|
|
||||||
* 添加到父模型后的回调函数
|
|
||||||
*/
|
|
||||||
onSubModelAdded?: (subModel: FlowModel) => Promise<void>;
|
onSubModelAdded?: (subModel: FlowModel) => Promise<void>;
|
||||||
|
|
||||||
/**
|
|
||||||
* 按钮文本,默认为 "Add"
|
|
||||||
*/
|
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局设置:点击菜单项后是否保持下拉菜单打开状态
|
|
||||||
* 如果设置为 true,所有菜单项点击后都不会关闭下拉菜单
|
|
||||||
* 单个菜单项的 keepDropdownOpen 属性会覆盖此全局设置
|
|
||||||
*/
|
|
||||||
keepDropdownOpen?: boolean;
|
keepDropdownOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 工具函数
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 预定义样式对象,避免重复创建
|
||||||
|
const SWITCH_CONTAINER_STYLE = {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
padding: '0 4px',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const SWITCH_STYLE = {
|
||||||
|
pointerEvents: 'none' as const,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证 createModelOptions 的有效性
|
* 验证 createModelOptions 的有效性
|
||||||
*/
|
*/
|
||||||
@ -183,12 +94,10 @@ const validateCreateModelOptions = (
|
|||||||
console.warn('No createModelOptions found for item');
|
console.warn('No createModelOptions found for item');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!createOpts.use) {
|
if (!createOpts.use) {
|
||||||
console.warn('createModelOptions must specify "use" property:', createOpts);
|
console.warn('createModelOptions must specify "use" property:', createOpts);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,22 +115,143 @@ const handleModelCreationError = async (error: any, addedModel?: FlowModel) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地获取菜单项的创建选项
|
||||||
|
*/
|
||||||
|
const getCreateModelOptions = (item: SubModelItem) => {
|
||||||
|
let createOpts = item.createModelOptions;
|
||||||
|
if (typeof createOpts === 'function') {
|
||||||
|
createOpts = createOpts(item);
|
||||||
|
}
|
||||||
|
return createOpts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建构建上下文的工厂函数
|
||||||
|
*/
|
||||||
|
const createBuildContext = (model: FlowModel, subModelBaseClass?: string | ModelConstructor): AddSubModelContext => {
|
||||||
|
const globalContext = model.flowEngine.getContext();
|
||||||
|
return {
|
||||||
|
model,
|
||||||
|
globals: globalContext,
|
||||||
|
subModelBaseClass:
|
||||||
|
typeof subModelBaseClass === 'string' ? model.flowEngine.getModelClass(subModelBaseClass) : subModelBaseClass,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并多个不同来源的 SubModelItemsType 成一个
|
||||||
|
*/
|
||||||
|
export function mergeSubModelItems(
|
||||||
|
sources: (SubModelItemsType | undefined | null)[],
|
||||||
|
options: MergeSubModelItemsOptions = {},
|
||||||
|
): SubModelItemsType {
|
||||||
|
const { addDividers = false } = options;
|
||||||
|
const validSources = sources.filter((source): source is SubModelItemsType => source !== undefined && source !== null);
|
||||||
|
|
||||||
|
if (validSources.length === 0) return [];
|
||||||
|
if (validSources.length === 1) return validSources[0];
|
||||||
|
|
||||||
|
return async (ctx: AddSubModelContext) => {
|
||||||
|
const result: SubModelItem[] = [];
|
||||||
|
for (let i = 0; i < validSources.length; i++) {
|
||||||
|
const source = validSources[i];
|
||||||
|
const items: SubModelItem[] = Array.isArray(source) ? source : await source(ctx);
|
||||||
|
|
||||||
|
if (i > 0 && addDividers && items.length > 0) {
|
||||||
|
result.push({ key: `divider-${i}`, type: 'divider' } as SubModelItem);
|
||||||
|
}
|
||||||
|
result.push(...items);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 转换器函数
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Switch 标签的工厂函数
|
||||||
|
*/
|
||||||
|
const createSwitchLabel = (originalLabel: string, isToggled: boolean) => (
|
||||||
|
<div style={SWITCH_CONTAINER_STYLE}>
|
||||||
|
<span>{originalLabel}</span>
|
||||||
|
<Switch size="small" checked={isToggled} style={SWITCH_STYLE} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否包含 unique 项
|
||||||
|
*/
|
||||||
|
const hasUniqueItems = (items: SubModelItem[]): boolean => {
|
||||||
|
return items.some((item) => item.unique && item.toggleDetector && !item.children);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归转换 SubModelItem 数组为 LazyDropdown 的 Item 格式
|
* 递归转换 SubModelItem 数组为 LazyDropdown 的 Item 格式
|
||||||
*/
|
*/
|
||||||
const transformSubModelItems = (items: SubModelItem[], context: AddSubModelContext): Item[] => {
|
const transformSubModelItems = async (items: SubModelItem[], context: AddSubModelContext): Promise<Item[]> => {
|
||||||
return items.map((item) => ({
|
if (items.length === 0) return [];
|
||||||
...item,
|
|
||||||
children: item.children
|
// 批量收集需要异步检测的 unique 项
|
||||||
? typeof item.children === 'function'
|
const uniqueItems: Array<{ item: SubModelItem; index: number }> = [];
|
||||||
? async () => {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const childrenFn = item.children as (ctx: AddSubModelContext) => SubModelItem[] | Promise<SubModelItem[]>;
|
const item = items[i];
|
||||||
const result = await childrenFn(context);
|
if (item.unique && item.toggleDetector && !item.children) {
|
||||||
return transformSubModelItems(result, context);
|
uniqueItems.push({ item, index: i });
|
||||||
}
|
}
|
||||||
: transformSubModelItems(item.children as SubModelItem[], context)
|
}
|
||||||
: undefined,
|
|
||||||
}));
|
// 批量执行 toggleDetector
|
||||||
|
const toggleResults = await Promise.allSettled(uniqueItems.map(({ item }) => item.toggleDetector!(context)));
|
||||||
|
|
||||||
|
const toggleMap = new Map<number, boolean>();
|
||||||
|
uniqueItems.forEach(({ index }, i) => {
|
||||||
|
const result = toggleResults[i];
|
||||||
|
toggleMap.set(index, result.status === 'fulfilled' ? result.value : false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 并发转换所有项目
|
||||||
|
const transformPromises = items.map(async (item, index) => {
|
||||||
|
const transformedItem: Item = {
|
||||||
|
key: item.key,
|
||||||
|
label: item.label,
|
||||||
|
type: item.type,
|
||||||
|
disabled: item.disabled,
|
||||||
|
icon: item.icon,
|
||||||
|
searchable: item.searchable,
|
||||||
|
searchPlaceholder: item.searchPlaceholder,
|
||||||
|
keepDropdownOpen: item.keepDropdownOpen,
|
||||||
|
originalItem: item,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理 children
|
||||||
|
if (item.children) {
|
||||||
|
if (typeof item.children === 'function') {
|
||||||
|
transformedItem.children = async () => {
|
||||||
|
const childrenFn = item.children as (ctx: AddSubModelContext) => SubModelItem[] | Promise<SubModelItem[]>;
|
||||||
|
const childrenResult = await childrenFn(context);
|
||||||
|
return transformSubModelItems(childrenResult, context);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
transformedItem.children = await transformSubModelItems(item.children as SubModelItem[], context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理开关式菜单项
|
||||||
|
if (item.unique && item.toggleDetector && !item.children) {
|
||||||
|
const isToggled = toggleMap.get(index) || false;
|
||||||
|
const originalLabel = item.label || '';
|
||||||
|
transformedItem.label = createSwitchLabel(originalLabel, isToggled);
|
||||||
|
transformedItem.isToggled = isToggled;
|
||||||
|
transformedItem.unique = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedItem;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(transformPromises);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,9 +264,88 @@ const transformItems = (items: SubModelItemsType, context: AddSubModelContext):
|
|||||||
return transformSubModelItems(result, context);
|
return transformSubModelItems(result, context);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return transformSubModelItems(items, context);
|
|
||||||
|
const hasUnique = hasUniqueItems(items as SubModelItem[]);
|
||||||
|
if (hasUnique) {
|
||||||
|
return () => transformSubModelItems(items as SubModelItem[], context);
|
||||||
|
} else {
|
||||||
|
let cachedResult: Item[] | null = null;
|
||||||
|
return async () => {
|
||||||
|
if (!cachedResult) {
|
||||||
|
cachedResult = await transformSubModelItems(items as SubModelItem[], context);
|
||||||
|
}
|
||||||
|
return cachedResult;
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 删除处理器
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 stepParams 中查找字段路径匹配
|
||||||
|
*/
|
||||||
|
const findFieldInStepParams = (subModel: FlowModel, fieldKey: string): boolean => {
|
||||||
|
const stepParams = subModel.stepParams;
|
||||||
|
if (!stepParams || Object.keys(stepParams).length === 0) return false;
|
||||||
|
|
||||||
|
for (const flowKey in stepParams) {
|
||||||
|
const flowSteps = stepParams[flowKey];
|
||||||
|
if (!flowSteps) continue;
|
||||||
|
|
||||||
|
for (const stepKey in flowSteps) {
|
||||||
|
const stepData = flowSteps[stepKey];
|
||||||
|
if (stepData?.fieldPath === fieldKey || stepData?.field === fieldKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建默认删除处理器
|
||||||
|
*/
|
||||||
|
const createDefaultRemoveHandler = (config: {
|
||||||
|
model: FlowModel;
|
||||||
|
subModelKey: string;
|
||||||
|
subModelType: 'object' | 'array';
|
||||||
|
}) => {
|
||||||
|
return async (item: SubModelItem, _context: AddSubModelContext): Promise<void> => {
|
||||||
|
const { model, subModelKey, subModelType } = config;
|
||||||
|
|
||||||
|
if (subModelType === 'array') {
|
||||||
|
const subModels = (model.subModels as any)[subModelKey] as FlowModel[];
|
||||||
|
if (Array.isArray(subModels)) {
|
||||||
|
const createOpts = getCreateModelOptions(item);
|
||||||
|
const targetModel = subModels.find((subModel) => {
|
||||||
|
if (item.key && findFieldInStepParams(subModel, item.key)) return true;
|
||||||
|
return (
|
||||||
|
(subModel as any).constructor.name === createOpts?.use || (subModel as any).uid.includes(createOpts?.use)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetModel) {
|
||||||
|
targetModel.remove();
|
||||||
|
const index = subModels.indexOf(targetModel);
|
||||||
|
if (index > -1) subModels.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const subModel = (model.subModels as any)[subModelKey] as FlowModel;
|
||||||
|
if (subModel) {
|
||||||
|
subModel.remove();
|
||||||
|
(model.subModels as any)[subModelKey] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 主组件
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为 FlowModel 实例添加子模型的通用按钮组件
|
* 为 FlowModel 实例添加子模型的通用按钮组件
|
||||||
*
|
*
|
||||||
@ -244,7 +353,7 @@ const transformItems = (items: SubModelItemsType, context: AddSubModelContext):
|
|||||||
* - 支持异步加载 items
|
* - 支持异步加载 items
|
||||||
* - 支持多层级嵌套菜单
|
* - 支持多层级嵌套菜单
|
||||||
* - 支持从 flowEngine 全局上下文获取服务
|
* - 支持从 flowEngine 全局上下文获取服务
|
||||||
*
|
* - 支持 unique 菜单项的开关切换
|
||||||
*/
|
*/
|
||||||
const AddSubModelButtonCore = function AddSubModelButton({
|
const AddSubModelButtonCore = function AddSubModelButton({
|
||||||
model,
|
model,
|
||||||
@ -257,27 +366,47 @@ const AddSubModelButtonCore = function AddSubModelButton({
|
|||||||
children = 'Add',
|
children = 'Add',
|
||||||
keepDropdownOpen = false,
|
keepDropdownOpen = false,
|
||||||
}: AddSubModelButtonProps) {
|
}: AddSubModelButtonProps) {
|
||||||
// 构建上下文对象,从 flowEngine 的全局上下文中获取服务
|
// 构建上下文对象
|
||||||
const buildContext = useMemo((): AddSubModelContext => {
|
const buildContext = useMemo(
|
||||||
const globalContext = model.flowEngine.getContext();
|
() => createBuildContext(model, subModelBaseClass),
|
||||||
return {
|
[model, model.flowEngine, subModelBaseClass],
|
||||||
model,
|
);
|
||||||
globals: globalContext,
|
|
||||||
subModelBaseClass:
|
|
||||||
typeof subModelBaseClass === 'string' ? model.flowEngine.getModelClass(subModelBaseClass) : subModelBaseClass,
|
|
||||||
};
|
|
||||||
}, [model, model.flowEngine, subModelBaseClass]);
|
|
||||||
|
|
||||||
|
// 创建删除处理器
|
||||||
|
const removeHandler = useMemo(
|
||||||
|
() =>
|
||||||
|
createDefaultRemoveHandler({
|
||||||
|
model,
|
||||||
|
subModelKey,
|
||||||
|
subModelType,
|
||||||
|
}),
|
||||||
|
[model, subModelKey, subModelType],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 点击处理逻辑
|
||||||
const onClick = async (info: any) => {
|
const onClick = async (info: any) => {
|
||||||
const item = info.originalItem as SubModelItem;
|
const clickedItem = info.originalItem || info;
|
||||||
let createOpts = item.createModelOptions;
|
const item = clickedItem.originalItem || (clickedItem as SubModelItem);
|
||||||
|
const isToggled = clickedItem.isToggled;
|
||||||
|
const isUnique = clickedItem.unique || item.unique;
|
||||||
|
|
||||||
// 如果 createModelOptions 是函数,则调用它获取实际的选项
|
// 处理 unique 菜单项的开关操作
|
||||||
if (typeof createOpts === 'function') {
|
if (isUnique && item.toggleDetector && isToggled) {
|
||||||
createOpts = createOpts(item);
|
try {
|
||||||
|
if (item.removeModelOptions?.customRemove) {
|
||||||
|
await item.removeModelOptions.customRemove(buildContext, item);
|
||||||
|
} else {
|
||||||
|
await removeHandler(item, buildContext);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to remove sub model:', error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证 createModelOptions 的有效性
|
// 处理添加操作
|
||||||
|
const createOpts = getCreateModelOptions(item);
|
||||||
|
|
||||||
if (!validateCreateModelOptions(createOpts)) {
|
if (!validateCreateModelOptions(createOpts)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -293,7 +422,6 @@ const AddSubModelButtonCore = function AddSubModelButton({
|
|||||||
});
|
});
|
||||||
|
|
||||||
addedModel.setParent(model);
|
addedModel.setParent(model);
|
||||||
|
|
||||||
await addedModel.configureRequiredSteps();
|
await addedModel.configureRequiredSteps();
|
||||||
|
|
||||||
if (onModelCreated) {
|
if (onModelCreated) {
|
||||||
|
@ -22,6 +22,18 @@ export type Item = {
|
|||||||
searchable?: boolean;
|
searchable?: boolean;
|
||||||
searchPlaceholder?: string;
|
searchPlaceholder?: string;
|
||||||
keepDropdownOpen?: boolean;
|
keepDropdownOpen?: boolean;
|
||||||
|
/**
|
||||||
|
* 开关状态标记(内部使用)
|
||||||
|
*/
|
||||||
|
isToggled?: boolean;
|
||||||
|
/**
|
||||||
|
* 原始菜单项数据(内部使用)
|
||||||
|
*/
|
||||||
|
originalItem?: any;
|
||||||
|
/**
|
||||||
|
* 是否为唯一项标记(内部使用)
|
||||||
|
*/
|
||||||
|
unique?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user