"use client" import { useState, useEffect, useRef } from "react" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { DashboardShell } from "@/components/dashboard-shell" import { DashboardHeader } from "@/components/dashboard-header" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { ArrowLeft, Edit, AlertTriangle, FileText, Play, Pause, Volume2, VolumeX, Music, Download, Factory } from "lucide-react" import Link from "next/link" import { AddPrintBatchDialog } from "@/components/songs/add-print-batch-dialog" import { ExportCardsDialog } from "@/components/songs/export-cards-dialog" import { ExportBatchDialog } from "@/components/songs/export-batch-dialog" import { useToast } from "@/components/ui/use-toast" import { getSong, getSongBatches, markBatchAsProduced, SongBatch } from "@/lib/api/songs" import { Song } from "@/lib/api/types" import { Skeleton } from "@/components/ui/skeleton" import { Slider } from "@/components/ui/slider" import { useParams } from "next/navigation" import { format } from "date-fns" export default function SongDetailPage() { const params = useParams() const songId = params.id as string const { toast } = useToast() const [song, setSong] = useState(null) const [batches, setBatches] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isBatchesLoading, setIsBatchesLoading] = useState(true) const [error, setError] = useState(null) const [isPlaying, setIsPlaying] = useState(false) const [isAudioLoading, setIsAudioLoading] = useState(false) const [volume, setVolume] = useState(80) const [isMuted, setIsMuted] = useState(false) const audioRef = useRef(null) // 加载歌曲数据 useEffect(() => { const fetchSong = async () => { if (!songId) return setIsLoading(true) try { const songData = await getSong(songId) setSong(songData) setError(null) } catch (error) { console.error("获取歌曲失败:", error) setError(error instanceof Error ? error.message : "未知错误") toast({ title: "获取歌曲失败", description: error instanceof Error ? error.message : "无法加载歌曲数据,请稍后重试", variant: "destructive", }) } finally { setIsLoading(false) } } fetchSong() }, [songId, toast]) // 加载批次数据 useEffect(() => { const fetchBatches = async () => { if (!songId) return setIsBatchesLoading(true) try { const batchData = await getSongBatches(songId) setBatches(batchData) } catch (error) { console.error("获取批次数据失败:", error) toast({ title: "获取批次数据失败", description: error instanceof Error ? error.message : "无法加载批次数据,请稍后重试", variant: "destructive", }) } finally { setIsBatchesLoading(false) } } fetchBatches() }, [songId, toast]) // 刷新批次数据的函数 const refreshBatches = async () => { if (!songId) return setIsBatchesLoading(true) try { const batchData = await getSongBatches(songId) setBatches(batchData) } catch (error) { console.error("刷新批次数据失败:", error) } finally { setIsBatchesLoading(false) } } // 初始化音频播放器 - 现在只创建但不自动播放 useEffect(() => { if (song?.audioUrl && !audioRef.current) { audioRef.current = new Audio(song.audioUrl) const audio = audioRef.current // 音频加载完成时 - 不再自动播放 audio.addEventListener('canplay', () => { setIsAudioLoading(false) }) // 音频播放结束时 audio.addEventListener('ended', () => { setIsPlaying(false) }) // 音频播放错误时 audio.addEventListener('error', () => { setIsAudioLoading(false) setIsPlaying(false) toast({ title: "播放错误", description: "无法播放该音频文件", variant: "destructive", }) }) // 设置音量 audio.volume = volume / 100 audio.muted = isMuted } return () => { if (audioRef.current) { const audio = audioRef.current audio.pause() audio.src = '' audio.removeEventListener('canplay', () => {}) audio.removeEventListener('ended', () => {}) audio.removeEventListener('error', () => {}) audioRef.current = null } } }, [song?.audioUrl, toast, volume, isMuted]) // 监听音量变化 useEffect(() => { if (audioRef.current) { audioRef.current.volume = volume / 100 } }, [volume]) // 监听静音状态 useEffect(() => { if (audioRef.current) { audioRef.current.muted = isMuted } }, [isMuted]) // 处理播放/暂停 const togglePlay = () => { if (!song?.audioUrl) { toast({ title: "无法播放", description: "该歌曲没有可播放的音频文件", variant: "destructive", }) return } if (isPlaying) { audioRef.current?.pause() setIsPlaying(false) } else { setIsAudioLoading(true) if (audioRef.current) { audioRef.current.play() .then(() => { setIsPlaying(true) setIsAudioLoading(false) }) .catch((error) => { console.error('播放失败:', error) setIsAudioLoading(false) toast({ title: "播放失败", description: "无法播放音频,请稍后重试", variant: "destructive", }) }) } } } // 切换静音状态 const toggleMute = () => { setIsMuted(!isMuted) } // 处理音量变化 const handleVolumeChange = (values: number[]) => { const newVolume = values[0] setVolume(newVolume) // 如果音量从0调高,则取消静音 if (newVolume > 0 && isMuted) { setIsMuted(false) } // 如果音量调到0,则设置为静音 else if (newVolume === 0 && !isMuted) { setIsMuted(true) } } // 标记批次为已生产 const handleMarkAsProduced = async (batchId: number) => { try { setIsBatchesLoading(true); await markBatchAsProduced(batchId); toast({ title: "操作成功", description: "批次已成功标记为已生产", }); await refreshBatches(); } catch (error) { console.error("标记批次为已生产失败:", error); toast({ title: "操作失败", description: error instanceof Error ? error.message : "无法标记批次为已生产,请稍后重试", variant: "destructive", }); } finally { setIsBatchesLoading(false); } }; if (error) { return (

歌曲不存在

找不到ID为 {songId} 的歌曲

) } if (isLoading || !song) { return (
) } const isPublished = song.status === "已发布"; return (
{song.audioUrl && ( )} {!isPublished && ( )}
{/* 音频控制器 - 仅当歌曲有音频URL且正在播放时显示 */} {song.audioUrl && isPlaying && (

正在播放: {song.name}

{song.composer}

)} 歌曲详情 批次管理 数据分析
歌曲预览
{song.name}
{song.status} {song.rarity && {song.rarity}} {song.cardType && {song.cardType}}
歌曲详情

作曲

{song.composer}

作词

{song.lyricist}

{song.arrangement && (

编曲

{song.arrangement}

)}

时长

{song.duration}

发布日期

{song.releaseDate || "尚未发布"}

{song.genre && (

风格

{song.genre}

)} {song.bpm && (

BPM

{song.bpm}

)} {song.price && (

价格

¥{song.price}

)}

激活数量

{song.cardsCount || 0}

印刷批次

{song.batchesCount || 0}

{song.description && (

描述

{song.description}

)} {song.lyrics && (

歌词

{song.lyrics}
)} {isPublished && (

该歌曲已发布,基本属性不可修改。您仍可以增加印刷数量。

)}
印刷批次 管理歌曲卡牌的印刷批次和卡牌ID
{/* 如果没有批次数据,显示空状态 */} {isBatchesLoading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
{Array.from({ length: 3 }).map((_, i) => (
{Array.from({ length: 6 }).map((_, j) => ( ))}
))}
) : batches.length === 0 ? (

暂无批次数据

) : (
{batches.map((batch) => ( ))}
批次ID 创建日期 数量 起始ID 结束ID 状态 操作
{batch.id} {format(new Date(batch.created_at), 'yyyy-MM-dd')} {batch.quantity} {batch.start_id} {batch.end_id} {batch.status_display || getBatchStatusText(batch.status)}
{batch.status !== 'produced' && batch.status !== 'published' && ( )}
)}
数据分析 查看歌曲卡牌的激活数据和使用情况

激活数据图表

地区分布图表

时间趋势图表

) } // 获取批次状态颜色 function getBatchStatusColor(status: string): string { switch (status) { case 'draft': return 'bg-gray-500'; case 'exported': return 'bg-blue-500'; case 'in_production': return 'bg-amber-500'; case 'produced': return 'bg-purple-500'; case 'published': return 'bg-green-500'; default: return 'bg-gray-500'; } } // 获取批次状态文本 function getBatchStatusText(status: string): string { switch (status) { case 'draft': return '草稿'; case 'exported': return '已导出'; case 'in_production': return '生产中'; case 'produced': return '已生产'; case 'published': return '已发布'; default: return status; } }