352 lines
12 KiB
TypeScript
352 lines
12 KiB
TypeScript
"use client"
|
||
|
||
import type React from "react"
|
||
|
||
import { useState, useEffect } from "react"
|
||
import type { Dance } from "@/lib/api/types"
|
||
import { useToast } from "@/hooks/use-toast"
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} 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 { Loader2, Upload, Video } from "lucide-react"
|
||
|
||
interface AddDanceDialogProps {
|
||
open: boolean
|
||
onOpenChange: (open: boolean) => void
|
||
onDanceAdded: (dance: Dance) => void
|
||
editDance?: Dance
|
||
}
|
||
|
||
export function AddDanceDialog({ open, onOpenChange, onDanceAdded, editDance }: AddDanceDialogProps) {
|
||
const { toast } = useToast()
|
||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||
|
||
// 表单状态
|
||
const [formData, setFormData] = useState<Partial<Dance>>({
|
||
name: "",
|
||
choreographer: "",
|
||
duration: "",
|
||
difficulty: "中等",
|
||
description: "",
|
||
category: "流行",
|
||
tags: [],
|
||
motionFile: "",
|
||
videoUrl: "",
|
||
})
|
||
|
||
// 预览ID
|
||
const [previewId, setPreviewId] = useState(
|
||
"DNC" +
|
||
Math.floor(Math.random() * 1000)
|
||
.toString()
|
||
.padStart(3, "0"),
|
||
)
|
||
|
||
// 当编辑模式且有舞蹈数据时,初始化表单
|
||
useEffect(() => {
|
||
if (editDance) {
|
||
setFormData({
|
||
name: editDance.name || "",
|
||
choreographer: editDance.choreographer || "",
|
||
duration: editDance.duration || "",
|
||
difficulty: editDance.difficulty || "中等",
|
||
description: editDance.description || "",
|
||
category: editDance.category || "流行",
|
||
tags: editDance.tags || [],
|
||
motionFile: editDance.motionFile || "",
|
||
videoUrl: editDance.videoUrl || "",
|
||
})
|
||
} else {
|
||
// 重置表单
|
||
setFormData({
|
||
name: "",
|
||
choreographer: "",
|
||
duration: "",
|
||
difficulty: "中等",
|
||
description: "",
|
||
category: "流行",
|
||
tags: [],
|
||
motionFile: "",
|
||
videoUrl: "",
|
||
})
|
||
// 生成新的预览ID
|
||
setPreviewId(
|
||
"DNC" +
|
||
Math.floor(Math.random() * 1000)
|
||
.toString()
|
||
.padStart(3, "0"),
|
||
)
|
||
}
|
||
}, [editDance, open])
|
||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||
const { name, value } = e.target
|
||
setFormData((prev) => ({ ...prev, [name]: value }))
|
||
}
|
||
|
||
const handleSelectChange = (name: string, value: string) => {
|
||
setFormData((prev) => ({ ...prev, [name]: value }))
|
||
}
|
||
|
||
const handleTagsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const tagsString = e.target.value
|
||
const tagsArray = tagsString
|
||
.split(",")
|
||
.map((tag) => tag.trim())
|
||
.filter((tag) => tag !== "")
|
||
setFormData((prev) => ({ ...prev, tags: tagsArray }))
|
||
}
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
|
||
// 表单验证
|
||
if (!formData.name) {
|
||
toast({
|
||
title: "表单不完整",
|
||
description: "请填写舞蹈名称",
|
||
variant: "destructive",
|
||
})
|
||
return
|
||
}
|
||
|
||
setIsSubmitting(true)
|
||
|
||
try {
|
||
// 模拟API请求延迟
|
||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||
|
||
const newDance: Dance = {
|
||
id: editDance?.id || previewId,
|
||
name: formData.name || "",
|
||
choreographer: formData.choreographer || "",
|
||
duration: formData.duration || "",
|
||
difficulty: formData.difficulty || "中等",
|
||
description: formData.description || "",
|
||
category: formData.category || "流行",
|
||
tags: formData.tags || [],
|
||
motionFile: formData.motionFile || "",
|
||
videoUrl: formData.videoUrl || "",
|
||
coverUrl: editDance?.coverUrl || "/placeholder.svg?height=300&width=400",
|
||
createdAt: editDance?.createdAt || new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
}
|
||
|
||
onDanceAdded(newDance)
|
||
onOpenChange(false)
|
||
} catch (error) {
|
||
toast({
|
||
title: "操作失败",
|
||
description: "添加或更新舞蹈时出现错误,请重试。",
|
||
variant: "destructive",
|
||
})
|
||
} finally {
|
||
setIsSubmitting(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600">
|
||
{editDance ? "编辑舞蹈" : "添加新舞蹈"}
|
||
</DialogTitle>
|
||
<DialogDescription>
|
||
{editDance ? "修改舞蹈信息,完成后点击保存。" : "填写舞蹈信息,完成后点击添加。"}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="name">
|
||
舞蹈名称 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<Input
|
||
id="name"
|
||
name="name"
|
||
value={formData.name || ""}
|
||
onChange={handleChange}
|
||
className="border-gray-300 focus-visible:ring-purple-500"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="choreographer">编舞者</Label>
|
||
<Input
|
||
id="choreographer"
|
||
name="choreographer"
|
||
value={formData.choreographer || ""}
|
||
onChange={handleChange}
|
||
className="border-gray-300 focus-visible:ring-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="duration">时长</Label>
|
||
<Input
|
||
id="duration"
|
||
name="duration"
|
||
value={formData.duration || ""}
|
||
onChange={handleChange}
|
||
placeholder="例如: 3:45"
|
||
className="border-gray-300 focus-visible:ring-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="difficulty">难度</Label>
|
||
<Select
|
||
value={formData.difficulty || "中等"}
|
||
onValueChange={(value) => handleSelectChange("difficulty", value)}
|
||
>
|
||
<SelectTrigger className="border-gray-300 focus:ring-purple-500">
|
||
<SelectValue placeholder="选择难度" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="初级">初级</SelectItem>
|
||
<SelectItem value="中等">中等</SelectItem>
|
||
<SelectItem value="中高级">中高级</SelectItem>
|
||
<SelectItem value="高级">高级</SelectItem>
|
||
<SelectItem value="专业">专业</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="category">分类</Label>
|
||
<Select
|
||
value={formData.category || "流行"}
|
||
onValueChange={(value) => handleSelectChange("category", value)}
|
||
>
|
||
<SelectTrigger className="border-gray-300 focus:ring-purple-500">
|
||
<SelectValue placeholder="选择分类" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="流行">流行</SelectItem>
|
||
<SelectItem value="中国风">中国风</SelectItem>
|
||
<SelectItem value="日式">日式</SelectItem>
|
||
<SelectItem value="现代">现代</SelectItem>
|
||
<SelectItem value="古典">古典</SelectItem>
|
||
<SelectItem value="街舞">街舞</SelectItem>
|
||
<SelectItem value="其他">其他</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="tags">标签</Label>
|
||
<Input
|
||
id="tags"
|
||
name="tags"
|
||
value={formData.tags?.join(", ") || ""}
|
||
onChange={handleTagsChange}
|
||
placeholder="用逗号分隔多个标签"
|
||
className="border-gray-300 focus-visible:ring-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="motionFile">动作文件</Label>
|
||
<Input
|
||
id="motionFile"
|
||
name="motionFile"
|
||
value={formData.motionFile || ""}
|
||
onChange={handleChange}
|
||
placeholder="动作文件名称,例如: dance_motion.fbx"
|
||
className="border-gray-300 focus-visible:ring-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="videoUrl">视频链接</Label>
|
||
<Input
|
||
id="videoUrl"
|
||
name="videoUrl"
|
||
value={formData.videoUrl || ""}
|
||
onChange={handleChange}
|
||
placeholder="舞蹈视频链接"
|
||
className="border-gray-300 focus-visible:ring-purple-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="description">舞蹈描述</Label>
|
||
<Textarea
|
||
id="description"
|
||
name="description"
|
||
value={formData.description || ""}
|
||
onChange={handleChange}
|
||
rows={4}
|
||
placeholder="请输入舞蹈的详细描述..."
|
||
className="min-h-[100px] border-gray-300 focus-visible:ring-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-right">舞蹈封面</Label>
|
||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center hover:border-purple-500 transition-colors cursor-pointer">
|
||
<Video className="h-8 w-8 text-gray-400 mb-2" />
|
||
<p className="text-sm text-gray-500">点击或拖拽上传舞蹈封面</p>
|
||
<p className="text-xs text-gray-400 mt-1">支持 PNG, JPG, JPEG 格式,最大 5MB</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label className="text-right">动作文件上传</Label>
|
||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center hover:border-purple-500 transition-colors cursor-pointer">
|
||
<Upload className="h-8 w-8 text-gray-400 mb-2" />
|
||
<p className="text-sm text-gray-500">点击或拖拽上传动作文件</p>
|
||
<p className="text-xs text-gray-400 mt-1">支持 FBX, BVH 格式,最大 20MB</p>
|
||
</div>
|
||
</div>
|
||
|
||
{!editDance && (
|
||
<div className="p-4 bg-gray-50 rounded-lg">
|
||
<h3 className="text-sm font-medium mb-2">预览信息</h3>
|
||
<p className="text-sm text-gray-600">
|
||
舞蹈ID: <span className="font-medium text-purple-600">{previewId}</span>
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
<DialogFooter>
|
||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={isSubmitting}>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
type="submit"
|
||
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600"
|
||
disabled={isSubmitting}
|
||
>
|
||
{isSubmitting ? (
|
||
<>
|
||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||
{editDance ? "更新中..." : "创建中..."}
|
||
</>
|
||
) : editDance ? (
|
||
"保存修改"
|
||
) : (
|
||
"添加舞蹈"
|
||
)}
|
||
</Button>
|
||
</DialogFooter>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
)
|
||
}
|