deploy-ease-platform/frontend/src/components/SelectOrVariableInput/index.tsx
2025-11-03 17:27:48 +08:00

178 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;