"use client" import { useState, useEffect, useRef } from "react" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" 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 { Plus, Loader2, Music, Trash2 } from "lucide-react" import { FileUpload } from "@/components/ui/file-upload" import { Switch } from "@/components/ui/switch" import type { Song } from "./song-detail-dialog" import { uploadSongFile, createSong, updateSong } from "@/lib/api/songs" type AddSongDialogProps = { mode?: "create" | "edit" initialSong?: Song open?: boolean onOpenChange?: (open: boolean) => void onSave?: (song: Song) => void } export function AddSongDialog({ mode = "create", initialSong, open: controlledOpen, onOpenChange: setControlledOpen, onSave, }: AddSongDialogProps) { const [open, setOpen] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) // 表单状态 const [name, setName] = useState("") const [songType, setSongType] = useState("") const [composer, setComposer] = useState("") const [lyricist, setLyricist] = useState("") const [duration, setDuration] = useState("") const [bpm, setBpm] = useState("") const [description, setDescription] = useState("") const [isOriginal, setIsOriginal] = useState(true) const [previewId, setPreviewId] = useState( "SNG" + Math.floor(Math.random() * 1000) .toString() .padStart(3, "0"), ) const [coverUrl, setCoverUrl] = useState("") const [audioFile, setAudioFile] = useState(null) const [audioUrl, setAudioUrl] = useState("") const [isUploading, setIsUploading] = useState(false) const [uploadError, setUploadError] = useState("") const audioInputRef = useRef(null) // 当编辑模式且有歌曲数据时,初始化表单 useEffect(() => { if (mode === "edit" && initialSong) { setName(initialSong.name) setComposer(initialSong.composer) setLyricist(initialSong.lyricist) setDuration(initialSong.duration) setIsOriginal(true) // 假设所有编辑的歌曲都是原创 setPreviewId(initialSong.id) // 初始化歌曲类型 if (initialSong.genre) { setSongType(initialSong.genre) } // 初始化BPM if (initialSong.bpm) { setBpm(String(initialSong.bpm)) } // 初始化描述 if (initialSong.description) { setDescription(initialSong.description) } // 初始化封面URL if (initialSong.image) { setCoverUrl(initialSong.image) } // 初始化音频URL if (initialSong.audioUrl) { setAudioUrl(initialSong.audioUrl) } } }, [mode, initialSong]) // 处理受控和非受控开关状态 useEffect(() => { if (controlledOpen !== undefined) { setOpen(controlledOpen) } }, [controlledOpen]) // 处理对话框关闭 const handleOpenChange = (newOpen: boolean) => { setOpen(newOpen) if (setControlledOpen) { setControlledOpen(newOpen) } // 如果关闭对话框,重置表单(仅在创建模式下) if (!newOpen && mode === "create") { resetForm() } } // 重置表单 const resetForm = () => { if (mode === "create") { setName("") setSongType("") setComposer("") setLyricist("") setDuration("") setBpm("") setDescription("") setIsOriginal(true) setPreviewId( "SNG" + Math.floor(Math.random() * 1000) .toString() .padStart(3, "0"), ) } } // 歌曲文件选择和上传 const handleAudioFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setAudioFile(file) setIsUploading(true) setUploadError("") try { const res = await uploadSongFile({ file, isPermanent: true, filename: file.name }) setAudioUrl(res.url) } catch (err: any) { setUploadError(err.message || "上传失败") setAudioUrl("") } finally { setIsUploading(false) } } const handleSubmit = async () => { // 表单验证 if (!name || !composer || !lyricist || !duration) { alert("请填写所有必填字段!") return } // 创建模式下必须上传音频 if (mode === "create" && !audioUrl) { alert("请上传歌曲文件!") return } setIsSubmitting(true) try { // 构造基本的请求数据 const songAttributes: any = { genre: songType || undefined, duration, composer, lyricist, arrangement: isOriginal ? undefined : "", lyrics: description || undefined, } // 只有在有音频URL时才添加 if (audioUrl) { songAttributes.audio_file = encodeURI(audioUrl) } // 只有在有BPM时才添加 if (bpm) { songAttributes.bpm = Number(bpm) } if (mode === "create") { // 创建模式 const payload: any = { name, category: "song", card_type: "regular", rarity: "epic", price: 39.99, status: "draft", description: description || undefined, song_attributes: songAttributes } if (coverUrl) { payload.image_url = coverUrl } const created = await createSong(payload) if (onSave) onSave(created) } else if (mode === "edit" && initialSong) { // 编辑模式 const payload: any = { name, category: "song", song_attributes: songAttributes } if (coverUrl) { payload.image_url = coverUrl } const updated = await updateSong(initialSong.id, payload) if (onSave) onSave(updated) } handleOpenChange(false) } catch (error: any) { alert(error.message || (mode === "create" ? "创建歌曲失败" : "更新歌曲失败")) } finally { setIsSubmitting(false) } } return ( {mode === "create" && ( )} {mode === "create" ? "添加新歌曲" : "编辑歌曲"} {mode === "create" ? "填写歌曲信息以创建新的歌曲。" : "修改歌曲信息。"}
setName(e.target.value)} required />
setComposer(e.target.value)} required />
setLyricist(e.target.value)} required />
setDuration(e.target.value)} required />
setBpm(e.target.value)} />
!isUploading && !isSubmitting && audioInputRef.current?.click()} style={{ position: 'relative' }} > {audioFile ? audioFile.name : "上传"} {isUploading && 上传中...} {audioUrl && !isUploading && 已上传} {uploadError && {uploadError}}
{coverUrl && !coverUrl.includes("placeholder") ? (
歌曲封面 已上传
) : ( { if (files.length > 0) { setCoverUrl(files[0].url) } }} /> )}
{/* 可折叠描述和预览信息 */}
歌曲描述