lty/qy-lty-admin/components/songs/song-detail-dialog.tsx
2026-03-17 13:17:02 +08:00

300 lines
9.2 KiB
TypeScript
Raw Permalink 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.

"use client"
import type React from "react"
import { useState, useRef, useEffect } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Badge } from "@/components/ui/badge"
import { Eye, Edit, Play, Pause, Volume2, VolumeX } from "lucide-react"
import { Slider } from "@/components/ui/slider"
export type Song = {
id: string
name: string
composer: string
lyricist: string
duration: string
releaseDate?: string
status: string
image?: string
audioUrl?: string
description?: string
rarity?: string
cardType?: string
genre?: string
lyrics?: string
bpm?: string | number
}
type SongDetailDialogProps = {
song: Song
onEdit?: () => void
}
export function SongDetailDialog({ song, onEdit }: SongDetailDialogProps) {
const [open, setOpen] = useState(false)
const [isPlaying, setIsPlaying] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [volume, setVolume] = useState(80)
const [isMuted, setIsMuted] = useState(false)
const audioRef = useRef<HTMLAudioElement | null>(null)
// 初始化音频播放器
useEffect(() => {
if (open && !audioRef.current && song.audioUrl) {
audioRef.current = new Audio(song.audioUrl)
const audio = audioRef.current
// 设置事件监听器
audio.addEventListener('canplay', () => {
setIsLoading(false)
})
audio.addEventListener('ended', () => {
setIsPlaying(false)
})
audio.addEventListener('error', () => {
setIsPlaying(false)
setIsLoading(false)
})
// 设置音量
audio.volume = volume / 100
audio.muted = isMuted
}
// 关闭对话框时清理
if (!open && audioRef.current) {
const audio = audioRef.current
audio.pause()
audio.src = ''
audioRef.current = null
setIsPlaying(false)
}
return () => {
if (audioRef.current) {
const audio = audioRef.current
audio.pause()
audio.src = ''
audio.removeEventListener('canplay', () => {})
audio.removeEventListener('ended', () => {})
audio.removeEventListener('error', () => {})
}
}
}, [open, song.audioUrl, volume, isMuted])
// 监听音量变化
useEffect(() => {
if (audioRef.current) {
audioRef.current.volume = volume / 100
}
}, [volume])
// 监听静音状态
useEffect(() => {
if (audioRef.current) {
audioRef.current.muted = isMuted
}
}, [isMuted])
const togglePlay = (e: React.MouseEvent) => {
e.stopPropagation()
if (!song.audioUrl) return
if (isPlaying) {
audioRef.current?.pause()
setIsPlaying(false)
} else {
setIsLoading(true)
if (audioRef.current) {
audioRef.current.play()
.then(() => {
setIsPlaying(true)
setIsLoading(false)
})
.catch((error) => {
console.error('播放失败:', error)
setIsLoading(false)
})
}
}
}
// 切换静音状态
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)
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="hover:bg-blue-50 hover:text-blue-600">
<Eye className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[550px]">
<DialogHeader>
<DialogTitle className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600">
</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="grid gap-6 py-4">
<div className="flex items-center gap-4">
<div className="relative h-24 w-24 rounded-lg overflow-hidden bg-gray-100 flex items-center justify-center group">
<img
src={song.image || "/placeholder.svg?height=96&width=96"}
alt={song.name}
className="object-cover h-full w-full"
/>
{song.audioUrl && (
<div
className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer"
onClick={togglePlay}
>
{isLoading ? (
<div className="h-10 w-10 flex items-center justify-center">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-white border-t-transparent" />
</div>
) : isPlaying ? (
<Pause className="h-10 w-10 text-white" />
) : (
<Play className="h-10 w-10 text-white" />
)}
</div>
)}
</div>
<div>
<h3 className="text-lg font-bold">{song.name}</h3>
<p className="text-sm text-gray-500">ID: {song.id}</p>
<div className="flex gap-2 mt-2">
<Badge className={song.status === "已发布" ? "bg-green-500" : "bg-gray-500"}>{song.status}</Badge>
{song.rarity && <Badge className="bg-purple-500">{song.rarity}</Badge>}
{song.cardType && <Badge className="bg-blue-500">{song.cardType}</Badge>}
</div>
</div>
</div>
{/* 音频控制器 - 仅当歌曲有音频URL且正在播放时显示 */}
{song.audioUrl && isPlaying && (
<div className="flex items-center space-x-4 bg-pink-50 p-3 rounded-lg">
<Button
variant="ghost"
size="icon"
className="h-7 w-7 rounded-full hover:bg-pink-100"
onClick={toggleMute}
>
{isMuted ? <VolumeX className="h-3.5 w-3.5 text-pink-500" /> : <Volume2 className="h-3.5 w-3.5 text-pink-500" />}
</Button>
<div className="flex-1">
<Slider
value={[volume]}
min={0}
max={100}
step={1}
onValueChange={handleVolumeChange}
className="h-1"
/>
</div>
</div>
)}
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className="text-sm font-medium text-gray-500 mb-1"></h4>
<p className="text-sm">{song.composer}</p>
</div>
<div>
<h4 className="text-sm font-medium text-gray-500 mb-1"></h4>
<p className="text-sm">{song.lyricist}</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className="text-sm font-medium text-gray-500 mb-1"></h4>
<p className="text-sm">{song.duration}</p>
</div>
<div>
<h4 className="text-sm font-medium text-gray-500 mb-1"></h4>
<p className="text-sm">{song.releaseDate || "未发布"}</p>
</div>
</div>
{song.genre && (
<div>
<h4 className="text-sm font-medium text-gray-500 mb-1"></h4>
<p className="text-sm">{song.genre}</p>
</div>
)}
{song.description && (
<div>
<h4 className="text-sm font-medium text-gray-500 mb-1"></h4>
<p className="text-sm">{song.description}</p>
</div>
)}
{song.lyrics && (
<div>
<h4 className="text-sm font-medium text-gray-500 mb-1"></h4>
<div className="text-sm max-h-40 overflow-y-auto p-2 bg-gray-50 rounded">
<pre className="whitespace-pre-wrap font-sans">{song.lyrics}</pre>
</div>
</div>
)}
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
</Button>
{song.status !== "已发布" && onEdit && (
<Button
className="bg-gradient-to-r from-pink-500 to-purple-600 hover:from-pink-600 hover:to-purple-700"
onClick={() => {
setOpen(false)
onEdit()
}}
>
<Edit className="mr-2 h-4 w-4" />
</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
)
}