/** * 项目-模板绑定表单组件 * 遵循前端开发规范的组件设计原则 */ import React, { useState, useEffect } from 'react'; import { X, Save } from 'lucide-react'; import { ProjectTemplateBinding, CreateProjectTemplateBindingRequest, UpdateProjectTemplateBindingRequest, BindingType, BINDING_TYPE_OPTIONS, validateBindingFormData, ProjectTemplateBindingFormData, } from '../types/projectTemplateBinding'; import { Template } from '../types/template'; import { CustomSelect } from './CustomSelect'; import { LoadingSpinner } from './LoadingSpinner'; import { ErrorMessage } from './ErrorMessage'; interface ProjectTemplateBindingFormProps { isOpen: boolean; onClose: () => void; onSubmit: (data: CreateProjectTemplateBindingRequest | UpdateProjectTemplateBindingRequest) => Promise; projectId: string; templates: Template[]; binding?: ProjectTemplateBinding; loading?: boolean; error?: string | null; } export const ProjectTemplateBindingForm: React.FC = ({ isOpen, onClose, onSubmit, projectId, templates, binding, loading = false, error = null, }) => { const [formData, setFormData] = useState({ template_id: '', binding_name: '', description: '', priority: 0, binding_type: BindingType.Secondary, }); const [validationErrors, setValidationErrors] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); // 初始化表单数据 useEffect(() => { if (binding) { setFormData({ template_id: binding.template_id, binding_name: binding.binding_name || '', description: binding.description || '', priority: binding.priority, binding_type: binding.binding_type, }); } else { setFormData({ template_id: '', binding_name: '', description: '', priority: 0, binding_type: BindingType.Secondary, }); } setValidationErrors([]); }, [binding, isOpen]); // 处理表单字段变化 const handleFieldChange = (field: keyof ProjectTemplateBindingFormData, value: any) => { setFormData(prev => ({ ...prev, [field]: value })); // 清除相关的验证错误 setValidationErrors([]); }; // 验证表单 const validateForm = (): boolean => { const errors = validateBindingFormData(formData); setValidationErrors(errors); return errors.length === 0; }; // 处理表单提交 const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { return; } setIsSubmitting(true); try { if (binding) { // 更新模式 const updateData: UpdateProjectTemplateBindingRequest = { binding_name: formData.binding_name || undefined, description: formData.description || undefined, priority: formData.priority, binding_type: formData.binding_type, }; await onSubmit(updateData); } else { // 创建模式 const createData: CreateProjectTemplateBindingRequest = { project_id: projectId, template_id: formData.template_id, binding_name: formData.binding_name || undefined, description: formData.description || undefined, priority: formData.priority, binding_type: formData.binding_type, }; await onSubmit(createData); } onClose(); } catch (error) { // 错误由父组件处理 } finally { setIsSubmitting(false); } }; // 获取可用的模板选项 const getTemplateOptions = () => { return templates.map(template => ({ value: template.id, label: template.name, description: template.description, })); }; // 获取选中的模板信息 const getSelectedTemplate = () => { return templates.find(t => t.id === formData.template_id); }; if (!isOpen) return null; return (
{/* 头部 */}

{binding ? '编辑模板绑定' : '添加模板绑定'}

{/* 表单内容 */}
{/* 错误信息 */} {(error || validationErrors.length > 0) && (
{error && } {validationErrors.map((err, index) => ( ))}
)} {/* 模板选择 */}
handleFieldChange('template_id', value)} options={getTemplateOptions()} placeholder="请选择模板" disabled={!!binding || isSubmitting} className="w-full" /> {formData.template_id && (
{getSelectedTemplate()?.description && (

{getSelectedTemplate()?.description}

)}
)}
{/* 绑定名称 */}
handleFieldChange('binding_name', e.target.value)} placeholder="为此绑定设置一个自定义名称(可选)" className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" disabled={isSubmitting} maxLength={100} />

{formData.binding_name.length}/100 字符

{/* 绑定类型 */}
handleFieldChange('binding_type', value as BindingType)} options={BINDING_TYPE_OPTIONS.map(option => ({ value: option.value, label: option.label, description: option.description, }))} disabled={isSubmitting} className="w-full" />
{/* 优先级 */}
handleFieldChange('priority', parseInt(e.target.value) || 0)} min="0" max="999" className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" disabled={isSubmitting} />

数值越小优先级越高,范围:0-999

{/* 描述 */}