deploy-ease-platform/frontend/src/pages/Form/Definition/components/CreateModal.tsx
2025-10-25 15:13:13 +08:00

281 lines
9.4 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 } from 'react';
import { useNavigate } from 'react-router-dom';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Separator } from '@/components/ui/separator';
import { FileText, Tag, Folder, AlignLeft, Copy, Loader2 } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
import { createDefinition } from '../service';
import { getEnabledCategories } from '../../Category/service';
import type { FormDefinitionRequest } from '../types';
import type { FormCategoryResponse } from '../../Category/types';
interface CreateModalProps {
visible: boolean;
onClose: () => void;
onSuccess: (id: number) => void;
}
/**
* 表单定义创建弹窗(第一步:输入基本信息)
*/
const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }) => {
const navigate = useNavigate();
const { toast } = useToast();
const [submitting, setSubmitting] = useState(false);
const [categories, setCategories] = useState<FormCategoryResponse[]>([]);
const [formData, setFormData] = useState({
name: '',
key: '',
categoryId: undefined as number | undefined,
description: '',
isTemplate: false,
});
// 加载分类列表
useEffect(() => {
if (visible) {
loadCategories();
}
}, [visible]);
const loadCategories = async () => {
try {
const result = await getEnabledCategories();
setCategories(result || []);
} catch (error) {
console.error('加载分类失败:', error);
}
};
// 重置表单
const resetForm = () => {
setFormData({
name: '',
key: '',
categoryId: undefined,
description: '',
isTemplate: false,
});
};
// 关闭弹窗
const handleClose = () => {
resetForm();
onClose();
};
// 提交表单
const handleSubmit = async () => {
// 验证
if (!formData.name.trim()) {
toast({
title: '验证失败',
description: '请输入表单名称',
variant: 'destructive'
});
return;
}
if (!formData.key.trim()) {
toast({
title: '验证失败',
description: '请输入表单标识',
variant: 'destructive'
});
return;
}
// 验证表单标识格式(英文字母、数字、中划线)
if (!/^[a-zA-Z0-9-]+$/.test(formData.key)) {
toast({
title: '验证失败',
description: '表单标识只能包含英文字母、数字和中划线',
variant: 'destructive'
});
return;
}
setSubmitting(true);
try {
// 创建表单定义只保存基本信息schema 为空)
const request: FormDefinitionRequest = {
name: formData.name,
key: formData.key,
categoryId: formData.categoryId,
description: formData.description,
isTemplate: formData.isTemplate,
status: 'DRAFT',
schema: {
version: '1.0',
formConfig: {
labelAlign: 'right',
size: 'middle'
},
fields: []
}
};
const result = await createDefinition(request);
toast({
title: '创建成功',
description: '表单基本信息已保存,现在可以开始设计表单'
});
// 跳转到设计器页面
navigate(`/form/definitions/${result.id}/design`);
handleClose();
onSuccess(result.id);
} catch (error) {
console.error('创建表单失败:', error);
toast({
title: '创建失败',
description: error instanceof Error ? error.message : '创建表单失败',
variant: 'destructive'
});
} finally {
setSubmitting(false);
}
};
return (
<Dialog open={visible} onOpenChange={handleClose}>
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
"下一步"
</DialogDescription>
</DialogHeader>
<Separator />
<div className="space-y-6">
{/* 第一行:表单名称 + 表单标识 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="create-name" className="flex items-center gap-2">
<FileText className="h-4 w-4 text-muted-foreground" />
<span className="text-destructive">*</span>
</Label>
<Input
id="create-name"
placeholder="例如:员工请假申请表"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
className="h-10"
/>
<p className="text-xs text-muted-foreground">
</p>
</div>
<div className="space-y-2">
<Label htmlFor="create-key" className="flex items-center gap-2">
<Tag className="h-4 w-4 text-muted-foreground" />
<span className="text-destructive">*</span>
</Label>
<Input
id="create-key"
placeholder="例如employee-leave-form"
value={formData.key}
onChange={(e) => setFormData(prev => ({ ...prev, key: e.target.value }))}
className="h-10 font-mono"
/>
<p className="text-xs text-muted-foreground">
线 API
</p>
</div>
</div>
{/* 第二行:分类 + 设为模板 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="create-category" className="flex items-center gap-2">
<Folder className="h-4 w-4 text-muted-foreground" />
</Label>
<Select
value={formData.categoryId?.toString() || undefined}
onValueChange={(value) => setFormData(prev => ({ ...prev, categoryId: Number(value) }))}
>
<SelectTrigger id="create-category" className="h-10">
<SelectValue placeholder="选择表单所属分类" />
</SelectTrigger>
<SelectContent>
{categories.map(cat => (
<SelectItem key={cat.id} value={cat.id.toString()}>
{cat.name}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
</p>
</div>
<div className="space-y-2">
<Label htmlFor="create-isTemplate" className="flex items-center gap-2">
<Copy className="h-4 w-4 text-muted-foreground" />
</Label>
<div className="flex items-center h-10 rounded-lg border px-4 bg-muted/30">
<div className="flex-1">
<span className="text-sm"></span>
</div>
<Switch
id="create-isTemplate"
checked={formData.isTemplate}
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, isTemplate: checked }))}
/>
</div>
<p className="text-xs text-muted-foreground">
</p>
</div>
</div>
{/* 第三行:描述 */}
<div className="space-y-2">
<Label htmlFor="create-description" className="flex items-center gap-2">
<AlignLeft className="h-4 w-4 text-muted-foreground" />
</Label>
<Textarea
id="create-description"
placeholder="简要说明此表单的用途和填写注意事项..."
value={formData.description}
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
className="min-h-[100px] resize-none"
/>
<p className="text-xs text-muted-foreground">
</p>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose} disabled={submitting}>
</Button>
<Button onClick={handleSubmit} disabled={submitting}>
{submitting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export default CreateModal;