178 lines
6.5 KiB
TypeScript
178 lines
6.5 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||
import VariableInput from '@/components/VariableInput';
|
||
import { Button } from '@/components/ui/button';
|
||
import { ArrowLeftRight } from 'lucide-react';
|
||
import type { FlowNode, FlowEdge } from '@/pages/Workflow/Design/types';
|
||
import type { FormField } from '@/components/VariableInput/types';
|
||
|
||
export interface SelectOrVariableInputProps {
|
||
// 基本属性
|
||
value: string | number | undefined;
|
||
onChange: (value: string | number) => void;
|
||
placeholder?: string;
|
||
disabled?: boolean;
|
||
|
||
// 下拉选项
|
||
options: Array<{ label: string; value: any }>;
|
||
|
||
// 变量输入配置
|
||
allNodes: FlowNode[];
|
||
allEdges: FlowEdge[];
|
||
currentNodeId: string;
|
||
formFields?: FormField[];
|
||
}
|
||
|
||
/**
|
||
* 混合输入组件:支持下拉选择 + 变量输入
|
||
*
|
||
* 使用场景:
|
||
* - 固定值场景:用户从下拉框选择(如:选择 Jenkins 服务器)
|
||
* - 动态值场景:用户输入变量(如:${approval.jenkinsServerId})
|
||
*
|
||
* 特性:
|
||
* - 自动识别值类型(包含 ${} → 变量模式)
|
||
* - 一键切换模式
|
||
* - 保留变量输入的自动补全功能
|
||
*/
|
||
export const SelectOrVariableInput: React.FC<SelectOrVariableInputProps> = ({
|
||
value,
|
||
onChange,
|
||
placeholder = '请选择或输入变量',
|
||
disabled = false,
|
||
options,
|
||
allNodes,
|
||
allEdges,
|
||
currentNodeId,
|
||
formFields
|
||
}) => {
|
||
// 判断当前值是否为变量
|
||
const isVariableValue = (val: any): boolean => {
|
||
return typeof val === 'string' && val.includes('${');
|
||
};
|
||
|
||
// 模式:'select' 下拉选择 | 'variable' 变量输入
|
||
const [mode, setMode] = useState<'select' | 'variable'>(() => {
|
||
return isVariableValue(value) ? 'variable' : 'select';
|
||
});
|
||
|
||
// 内部显示值(用于切换时立即清空显示)
|
||
const [internalValue, setInternalValue] = useState<string | number | undefined>(value);
|
||
|
||
// 是否是手动切换(防止自动模式识别覆盖手动切换)
|
||
const isManualToggleRef = useRef(false);
|
||
|
||
// 同步外部 value 到内部 internalValue(仅在非手动切换时)
|
||
useEffect(() => {
|
||
if (!isManualToggleRef.current) {
|
||
setInternalValue(value);
|
||
|
||
// 自动模式识别
|
||
if (isVariableValue(value) && mode === 'select') {
|
||
setMode('variable');
|
||
} else if (typeof value === 'number' && mode === 'variable') {
|
||
setMode('select');
|
||
}
|
||
}
|
||
}, [value, mode]);
|
||
|
||
// 切换模式
|
||
const handleToggleMode = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||
// 阻止默认行为和冒泡(防止触发表单提交)
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
const newMode = mode === 'select' ? 'variable' : 'select';
|
||
|
||
// 标记为手动切换
|
||
isManualToggleRef.current = true;
|
||
|
||
// 立即切换模式和清空值
|
||
setMode(newMode);
|
||
setInternalValue(undefined);
|
||
onChange(undefined as any);
|
||
|
||
// 短暂延迟后重置标记,允许后续同步
|
||
requestAnimationFrame(() => {
|
||
isManualToggleRef.current = false;
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="flex items-center gap-2">
|
||
{/* 主输入区域 */}
|
||
<div className="flex-1">
|
||
{mode === 'select' ? (
|
||
// 下拉选择模式
|
||
<Select
|
||
disabled={disabled}
|
||
value={internalValue?.toString() || ''}
|
||
onValueChange={(v) => {
|
||
// 尝试转换为数字(如果原值是number类型)
|
||
const numValue = Number(v);
|
||
const newValue = isNaN(numValue) ? v : numValue;
|
||
setInternalValue(newValue);
|
||
onChange(newValue);
|
||
}}
|
||
>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={placeholder} />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{options.map((option) => (
|
||
<SelectItem key={option.value} value={option.value.toString()}>
|
||
{option.label}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
) : (
|
||
// 变量输入模式
|
||
<VariableInput
|
||
value={String(internalValue || '')}
|
||
onChange={(v) => {
|
||
// 更新内部值
|
||
setInternalValue(v || undefined);
|
||
|
||
// 如果包含变量语法(${),认为是变量表达式
|
||
if (v.includes('${')) {
|
||
// 变量表达式始终保持字符串类型
|
||
onChange(v);
|
||
} else if (v === '') {
|
||
// 空值传递 undefined,符合表单 number 类型验证
|
||
onChange(undefined as any);
|
||
} else {
|
||
// 普通文本:尝试转换为数字
|
||
const numValue = Number(v);
|
||
onChange(isNaN(numValue) ? v : numValue);
|
||
}
|
||
}}
|
||
allNodes={allNodes}
|
||
allEdges={allEdges}
|
||
currentNodeId={currentNodeId}
|
||
formFields={formFields}
|
||
placeholder={placeholder}
|
||
disabled={disabled}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
{/* 模式切换按钮 */}
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
size="icon"
|
||
onClick={handleToggleMode}
|
||
disabled={disabled}
|
||
title={mode === 'select' ? '切换到变量输入' : '切换到下拉选择'}
|
||
className="shrink-0"
|
||
>
|
||
<ArrowLeftRight className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default SelectOrVariableInput;
|
||
|