"use client" import { useState, useEffect, useRef } from "react" import { DashboardShell } from "@/components/dashboard-shell" import { DashboardHeader } from "@/components/dashboard-header" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" import { Search, Edit, Play, Pause, Eye, Volume2, VolumeX, Archive } from "lucide-react" import { AddSongDialog } from "@/components/songs/add-song-dialog" import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" import { PublishConfirmationDialog } from "@/components/publish-confirmation-dialog" import { useToast } from "@/components/ui/use-toast" import Link from "next/link" import { isSuperUser } from "@/lib/api/auth" import { getSongs, publishSong, deleteSong, archiveSong } from "@/lib/api/songs" import type { Song } from "@/lib/api/types" import type { Song as ComponentSong } from "@/components/songs/song-detail-dialog" import { apiSongToComponentSong, componentSongToApiSong } from "@/lib/api/adapters" import { Slider } from "@/components/ui/slider" export default function SongsPage() { const { toast } = useToast() // 存储API返回的完整歌曲数据,包括原始数据结构,便于后续更新操作 const [songs, setSongs] = useState([]) const [isLoading, setIsLoading] = useState(true) const [searchTerm, setSearchTerm] = useState("") const [currentPage, setCurrentPage] = useState(1) const [selectedSong, setSelectedSong] = useState(null) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) const [playingSongId, setPlayingSongId] = useState(null) const [totalSongs, setTotalSongs] = useState(0) const [totalPages, setTotalPages] = useState(1) const [volume, setVolume] = useState(80) const [isMuted, setIsMuted] = useState(false) const [isAudioLoading, setIsAudioLoading] = useState(false) const [refreshTrigger, setRefreshTrigger] = useState(0) // 音频播放器引用 const audioRef = useRef(null) const currentSongUrlRef = useRef(null) const itemsPerPage = 10 // 加载歌曲数据 useEffect(() => { const fetchSongs = async () => { setIsLoading(true) try { const response = await getSongs({ page: currentPage, pageSize: itemsPerPage, search: searchTerm, }) // 直接使用API返回的完整歌曲数据 // 但确保所有必需属性都有默认值 const songItems = response.items.map(song => ({ ...song, id: song.id || "", name: song.name || "", composer: song.composer || "", lyricist: song.lyricist || "", duration: song.duration || "", status: song.status || "未知" })) setSongs(songItems) setTotalSongs(response.total) setTotalPages(response.totalPages) } catch (error) { console.error("获取歌曲失败:", error) toast({ title: "获取歌曲失败", description: "无法加载歌曲数据,请稍后重试", variant: "destructive", }) } finally { setIsLoading(false) } } fetchSongs() }, [currentPage, searchTerm, toast, refreshTrigger]) // 创建音频元素 useEffect(() => { audioRef.current = new Audio() // 设置音频事件监听器 const audio = audioRef.current // 音频加载完成时 audio.addEventListener('canplay', () => { setIsAudioLoading(false) audio.play().catch(err => { console.error("播放失败:", err) toast({ title: "播放失败", description: "无法播放音频,请稍后重试", variant: "destructive", }) setPlayingSongId(null) }) }) // 音频播放结束时 audio.addEventListener('ended', () => { setPlayingSongId(null) }) // 音频播放错误时 audio.addEventListener('error', () => { setIsAudioLoading(false) setPlayingSongId(null) toast({ title: "播放错误", description: "无法播放该音频文件", variant: "destructive", }) }) // 清理函数 return () => { if (audio) { audio.pause() audio.src = '' audio.removeEventListener('canplay', () => {}) audio.removeEventListener('ended', () => {}) audio.removeEventListener('error', () => {}) } } }, [toast]) // 监听音量变化 useEffect(() => { if (audioRef.current) { audioRef.current.volume = volume / 100 } }, [volume]) // 监听静音状态变化 useEffect(() => { if (audioRef.current) { audioRef.current.muted = isMuted } }, [isMuted]) // 处理添加歌曲 const handleAddSong = (newSong: ComponentSong) => { // 转换为API Song类型 const apiSong = componentSongToApiSong(newSong) setSongs((prevSongs) => [...prevSongs, apiSong]) setRefreshTrigger(prev => prev + 1) // 触发刷新 toast({ title: "添加成功", description: `歌曲 ${newSong.name} 已成功添加`, }) } // 处理编辑歌曲 const handleEditSong = (updatedSong: ComponentSong) => { console.log('保存编辑后的歌曲', updatedSong); // 找到原始歌曲 const originalSong = songs.find(s => s.id === updatedSong.id); console.log('原始歌曲数据', originalSong); if (!originalSong) { console.error('找不到原始歌曲数据'); return; } // 转换为API Song类型,并保留原始数据 const apiSong = componentSongToApiSong(updatedSong, originalSong); console.log('转换后的API歌曲数据', apiSong); // 更新本地歌曲数据 setSongs((prevSongs) => prevSongs.map((song) => { if (song.id === apiSong.id) { return apiSong; } return song; })); setSelectedSong(null); setIsEditDialogOpen(false); setRefreshTrigger(prev => prev + 1); // 触发刷新 toast({ title: "更新成功", description: `歌曲 ${updatedSong.name} 已成功更新`, }); } // 处理删除歌曲 const handleDeleteSong = async (songId: string) => { try { // 如果正在播放该歌曲,先停止播放 if (playingSongId === songId && audioRef.current) { audioRef.current.pause() setPlayingSongId(null) } await deleteSong(songId) setRefreshTrigger(prev => prev + 1) toast({ title: "删除成功", description: "歌曲已成功删除", variant: "destructive", }) } catch (error) { console.error("删除歌曲失败:", error) toast({ title: "删除失败", description: "无法删除该歌曲,请稍后重试", variant: "destructive", }) } } // 归档歌曲 const handleArchiveSong = async (songId: string, songName: string) => { try { await archiveSong(songId) setRefreshTrigger(prev => prev + 1) toast({ title: "归档成功", description: `歌曲 "${songName}" 已归档`, }) } catch (error) { console.error("归档歌曲失败:", error) toast({ title: "归档失败", description: "无法归档歌曲,请稍后重试", variant: "destructive", }) } } // 打开编辑对话框 const openEditDialog = (song: Song) => { console.log('编辑歌曲', song); // 确保从rawData中获取最新的数据 if (song.rawData) { console.log('使用完整原始数据', song.rawData); // 转换为组件所需的Song类型,优先使用rawData中的完整数据 const attributes = song.rawData.attributes || {}; const componentSong: ComponentSong = { id: song.id, name: song.name, composer: attributes.composer || song.composer || "", lyricist: attributes.lyricist || song.lyricist || "", duration: attributes.duration || song.duration || "", releaseDate: song.releaseDate, status: song.rawData.status_display || song.status || "", image: song.rawData.image_url || song.image, audioUrl: attributes.audio_file || song.audioUrl || "", description: song.rawData.description || song.description || "", rarity: song.rawData.rarity_display || song.rarity || "", cardType: song.rawData.card_type_display || song.cardType || "", genre: attributes.genre || song.genre || "", lyrics: attributes.lyrics || song.lyrics || "", bpm: attributes.bpm || song.bpm || 0 }; setSelectedSong(componentSong); } else { // 使用适配器功能 const componentSong = apiSongToComponentSong(song); setSelectedSong(componentSong); } setIsEditDialogOpen(true); } // 处理播放/暂停 const togglePlay = (songId: string) => { const song = songs.find(s => s.id === songId) // 如果找不到歌曲或没有音频URL,则显示提示f if (!song || !song.audioUrl) { toast({ title: "无法播放", description: "该歌曲没有可播放的音频文件", variant: "destructive", }) return } // 如果已经在播放这首歌,则暂停 if (playingSongId === songId) { if (audioRef.current) { audioRef.current.pause() } setPlayingSongId(null) return } // 如果正在播放其他歌曲,先停止 if (playingSongId && audioRef.current) { audioRef.current.pause() } // 播放新歌曲 setIsAudioLoading(true) setPlayingSongId(songId) // 如果是新的URL,则设置src if (currentSongUrlRef.current !== song.audioUrl && audioRef.current) { currentSongUrlRef.current = song.audioUrl audioRef.current.src = song.audioUrl audioRef.current.load() } else if (audioRef.current) { // 如果是相同的URL,直接播放 audioRef.current.play().catch(err => { console.error("播放失败:", err) setPlayingSongId(null) setIsAudioLoading(false) }) } } // 处理音量变化 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 toggleMute = () => { setIsMuted(!isMuted) } // 处理发布歌曲 const handlePublishSong = async (songId: string) => { try { // 找到要发布的歌曲 const songToPublish = songs.find(s => s.id === songId); if (!songToPublish) { throw new Error('找不到要发布的歌曲'); } setIsLoading(true); console.log('正在发布歌曲', songId, songToPublish.name); // 调用发布API const publishedSong = await publishSong(songId); console.log('发布成功', publishedSong); // 更新本地歌曲数据 setSongs((prevSongs) => prevSongs.map((song) => { if (song.id === songId) { return { ...song, status: "已发布", releaseDate: new Date().toISOString().split('T')[0], rawData: publishedSong.rawData }; } return song; })); // 触发刷新 setRefreshTrigger(prev => prev + 1); toast({ title: "发布成功", description: `歌曲 "${songToPublish.name}" 已成功发布`, variant: "default", }); } catch (error) { console.error('发布歌曲失败:', error); toast({ title: "发布失败", description: error instanceof Error ? error.message : "无法发布歌曲,请稍后重试", variant: "destructive", }); } finally { setIsLoading(false); } }; return (
{ setSearchTerm(e.target.value) setCurrentPage(1) // 重置到第一页 }} />
{/* 音频控制面板 - 当有歌曲正在播放时显示 */} {playingSongId && (
{songs.find(s => s.id === playingSongId)?.name}
{songs.find(s => s.id === playingSongId)?.composer}
)}
歌曲列表
管理洛天依可以演唱的歌曲
ID 歌曲名称 作曲 作词 时长 状态 操作 {isLoading ? ( 加载中... ) : songs.length > 0 ? ( songs.map((song) => ( {song.id} {song.name} {song.composer} {song.lyricist} {song.duration} {song.status} {/* 草稿状态:显示发布按钮 */} {song.status !== "已发布" && song.status !== "已归档" && ( handlePublishSong(song.id)} /> )} {/* 已发布状态:显示归档按钮 */} {song.status === "已发布" && ( )} {(song.status !== "已发布" || isSuperUser()) && ( <> handleDeleteSong(song.id)} /> )} )) ) : ( 没有找到匹配的歌曲 )}
显示 {songs.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0}- {Math.min(currentPage * itemsPerPage, totalSongs)} 共 {totalSongs} 首歌曲
{/* 编辑歌曲对话框 - 当选中歌曲时显示 */} {selectedSong && isEditDialogOpen && ( )}
) }