2026-03-17 13:17:02 +08:00

467 lines
19 KiB
TypeScript
Raw 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 { useState, useEffect } from "react"
import { useParams, useRouter } from "next/navigation"
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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Badge } from "@/components/ui/badge"
import { useToast } from "@/hooks/use-toast"
import { AddDanceDialog } from "@/components/dances/add-dance-dialog"
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"
import { ArrowLeft, Download, Edit, Play, Upload, FileText, AlertTriangle } from "lucide-react"
import type { Dance } from "@/lib/api/types"
// 模拟获取舞蹈详情的API
const getDanceById = async (id: string): Promise<Dance | null> => {
// 模拟API延迟
await new Promise((resolve) => setTimeout(resolve, 500))
// 模拟舞蹈数据
const mockDances: Dance[] = [
{
id: "1",
name: "千本樱",
choreographer: "洛天依工作室",
duration: "3:45",
difficulty: "中等",
videoUrl: "/placeholder.svg?height=300&width=400",
coverUrl: "/placeholder.svg?height=300&width=400",
description: "基于《千本樱》歌曲的经典舞蹈编排,动作流畅优美,适合中等水平的舞者。",
motionFile: "senbonzakura_motion.fbx",
category: "日式",
tags: ["经典", "流行", "日式"],
createdAt: "2023-01-15T08:30:00Z",
updatedAt: "2023-02-20T14:15:00Z",
status: "已发布",
activatedCount: 1245,
printedCount: 2000,
},
{
id: "2",
name: "权御天下",
choreographer: "洛天依动作组",
duration: "4:20",
difficulty: "高级",
videoUrl: "/placeholder.svg?height=300&width=400",
coverUrl: "/placeholder.svg?height=300&width=400",
description: "中国风舞蹈,动作幅度大,需要较高的舞蹈基础,展现古风韵味。",
motionFile: "quanyutianxia_motion.fbx",
category: "中国风",
tags: ["高难度", "中国风", "古风"],
createdAt: "2023-03-10T10:45:00Z",
updatedAt: "2023-04-05T16:30:00Z",
status: "已发布",
activatedCount: 1823,
printedCount: 3000,
},
{
id: "3",
name: "达拉崩吧",
choreographer: "洛天依舞蹈工作室",
duration: "3:10",
difficulty: "初级",
videoUrl: "/placeholder.svg?height=300&width=400",
coverUrl: "/placeholder.svg?height=300&width=400",
description: "轻松欢快的舞蹈,适合初学者,动作简单易学。",
motionFile: "dalabengba_motion.fbx",
category: "流行",
tags: ["简单", "欢快", "流行"],
createdAt: "2023-05-20T09:15:00Z",
updatedAt: "2023-05-25T11:20:00Z",
status: "已发布",
activatedCount: 1356,
printedCount: 2500,
},
{
id: "4",
name: "普通DISCO",
choreographer: "洛天依动作设计组",
duration: "3:30",
difficulty: "中等",
videoUrl: "/placeholder.svg?height=300&width=400",
coverUrl: "/placeholder.svg?height=300&width=400",
description: "现代disco风格舞蹈节奏感强动作活力四射。",
motionFile: "disco_motion.fbx",
category: "现代",
tags: ["活力", "现代", "disco"],
createdAt: "2023-06-05T14:30:00Z",
updatedAt: "2023-06-10T17:45:00Z",
status: "已发布",
activatedCount: 1578,
printedCount: 3000,
},
{
id: "5",
name: "华灯宴",
choreographer: "古风舞蹈团队",
duration: "4:50",
difficulty: "高级",
videoUrl: "/placeholder.svg?height=300&width=400",
coverUrl: "/placeholder.svg?height=300&width=400",
description: "古风舞蹈,动作优美细腻,需要较高的舞蹈技巧和表现力。",
motionFile: "hualangyan_motion.fbx",
category: "中国风",
tags: ["古风", "优美", "高难度"],
createdAt: "2023-07-12T11:20:00Z",
updatedAt: "2023-07-18T13:40:00Z",
status: "未发布",
activatedCount: 0,
printedCount: 2000,
},
]
const dance = mockDances.find((dance) => dance.id === id)
return dance || null
}
export default function DanceDetailPage() {
const params = useParams()
const router = useRouter()
const { toast } = useToast()
const [dance, setDance] = useState<Dance | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [isPlaying, setIsPlaying] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const id = params.id as string
useEffect(() => {
const loadDance = async () => {
setIsLoading(true)
try {
const data = await getDanceById(id)
setDance(data)
} catch (error) {
toast({
title: "加载失败",
description: "无法加载舞蹈详情,请重试。",
variant: "destructive",
})
} finally {
setIsLoading(false)
}
}
loadDance()
}, [id, toast])
const handleEditDance = (updatedDance: Dance) => {
setDance(updatedDance)
toast({
title: "更新成功",
description: `舞蹈 ${updatedDance.name} 已成功更新`,
})
}
const handleDeleteDance = async () => {
// 模拟删除API
await new Promise((resolve) => setTimeout(resolve, 500))
toast({
title: "删除成功",
description: `舞蹈 ${dance?.name} 已成功删除`,
variant: "destructive",
})
// 返回舞蹈列表页
router.push("/dances")
}
const togglePlay = () => {
setIsPlaying(!isPlaying)
}
if (isLoading) {
return (
<DashboardShell>
<DashboardHeader heading="舞蹈详情" text="加载中...">
<Button variant="outline" onClick={() => router.back()}>
<ArrowLeft className="mr-2 h-4 w-4" />
</Button>
</DashboardHeader>
<div className="grid gap-4">
<Card>
<CardHeader>
<CardTitle>
<div className="h-6 w-[200px] bg-gray-200 rounded animate-pulse"></div>
</CardTitle>
<CardDescription>
<div className="h-4 w-[300px] bg-gray-200 rounded animate-pulse"></div>
</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[400px] bg-gray-200 rounded animate-pulse"></div>
</CardContent>
</Card>
</div>
</DashboardShell>
)
}
if (!dance) {
return (
<DashboardShell>
<div className="flex flex-col items-center justify-center h-[60vh]">
<AlertTriangle className="h-16 w-16 text-red-500 mb-4" />
<h1 className="text-2xl font-bold mb-2"></h1>
<p className="text-gray-500 mb-6">ID为 {id} </p>
<Button asChild>
<a href="/dances">
<ArrowLeft className="mr-2 h-4 w-4" />
</a>
</Button>
</div>
</DashboardShell>
)
}
const isPublished = dance.status === "已发布"
return (
<DashboardShell>
<div className="absolute top-0 right-0 w-1/2 h-48 bg-gradient-to-bl from-purple-200 via-pink-200 to-transparent opacity-20 rounded-bl-full" />
<div className="flex items-center mb-6">
<Button variant="ghost" size="sm" className="mr-4" asChild>
<a href="/dances">
<ArrowLeft className="mr-2 h-4 w-4" />
</a>
</Button>
<DashboardHeader heading={dance.name} text={`舞蹈ID: ${dance.id}`}>
<div className="flex space-x-2 ml-auto">
{!isPublished && (
<Button
asChild
className="bg-gradient-to-r from-pink-500 to-purple-600 hover:from-pink-600 hover:to-purple-700 transition-all duration-300 shadow-md hover:shadow-lg"
>
<a href={`/dances/edit/${params.id}`}>
<Edit className="mr-2 h-4 w-4" />
</a>
</Button>
)}
<Button
variant="outline"
onClick={() => {
// 模拟下载动作文件
toast({
title: "开始下载",
description: `正在下载舞蹈动作文件`,
})
}}
>
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</DashboardHeader>
</div>
<Tabs defaultValue="details" className="space-y-4">
<TabsList>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="batches"></TabsTrigger>
<TabsTrigger value="analytics"></TabsTrigger>
</TabsList>
<TabsContent value="details" className="space-y-4">
<div className="grid gap-6 md:grid-cols-3">
<Card className="md:col-span-1 border-none shadow-lg bg-white">
<CardHeader>
<CardTitle className="text-lg font-bold"></CardTitle>
</CardHeader>
<CardContent className="flex justify-center">
<div className="relative w-full aspect-square max-w-[300px] rounded-lg overflow-hidden bg-gray-100 flex items-center justify-center group">
<img
src={dance.coverUrl || "/placeholder.svg?height=300&width=300"}
alt={dance.name}
className="object-cover"
/>
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer">
<Play className="h-16 w-16 text-white" />
</div>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-3">
<Badge className={`${isPublished ? "bg-green-500" : "bg-gray-500"}`}>{dance.status}</Badge>
</div>
</div>
</CardContent>
</Card>
<Card className="md:col-span-2 border-none shadow-lg bg-gradient-to-br from-white to-purple-50">
<CardHeader>
<CardTitle className="text-lg font-bold"></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<p className="text-sm font-medium text-gray-500"></p>
<p className="font-medium">{dance.choreographer}</p>
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-gray-500"></p>
<p className="font-medium">{dance.category}</p>
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-gray-500"></p>
<p className="font-medium">{dance.duration}</p>
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-gray-500"></p>
<p className="font-medium">{dance.difficulty}</p>
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-gray-500"></p>
<p className="font-medium">{dance.activatedCount}</p>
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-gray-500"></p>
<p className="font-medium">{dance.printedCount}</p>
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-gray-500"></p>
<p className="font-medium">{dance.printedCount - dance.activatedCount}</p>
</div>
</div>
<div className="mt-4">
<p className="text-sm font-medium text-gray-500 mb-2"></p>
<div className="flex flex-wrap gap-2">
{dance.tags?.map((tag, index) => (
<Badge key={index} variant="outline" className="bg-purple-50 text-purple-700">
{tag}
</Badge>
))}
</div>
</div>
{isPublished && (
<div className="mt-6 p-3 bg-amber-50 border border-amber-200 rounded-lg flex items-start">
<AlertTriangle className="h-5 w-5 text-amber-500 mr-2 flex-shrink-0 mt-0.5" />
<p className="text-sm text-amber-700"></p>
</div>
)}
</CardContent>
</Card>
</div>
<Card className="border-none shadow-lg bg-gradient-to-br from-white to-blue-50">
<CardHeader>
<CardTitle className="text-lg font-bold"></CardTitle>
</CardHeader>
<CardContent>
<p className="text-gray-700">{dance.description || "暂无描述"}</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="batches" className="space-y-4">
<Card className="border-none shadow-lg bg-gradient-to-br from-white to-blue-50">
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle className="text-lg font-bold"></CardTitle>
<CardDescription>ID</CardDescription>
</div>
<Button className="bg-gradient-to-r from-pink-500 to-purple-600 hover:from-pink-600 hover:to-purple-700">
<Upload className="mr-2 h-4 w-4" />
</Button>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<table className="w-full">
<thead>
<tr className="bg-gray-50 border-b">
<th className="py-3 px-4 text-left text-sm font-medium text-gray-500">ID</th>
<th className="py-3 px-4 text-left text-sm font-medium text-gray-500"></th>
<th className="py-3 px-4 text-left text-sm font-medium text-gray-500"></th>
<th className="py-3 px-4 text-left text-sm font-medium text-gray-500">ID</th>
<th className="py-3 px-4 text-left text-sm font-medium text-gray-500">ID</th>
<th className="py-3 px-4 text-right text-sm font-medium text-gray-500"></th>
</tr>
</thead>
<tbody>
<tr className="border-b hover:bg-gray-50">
<td className="py-3 px-4 text-sm font-medium">B001</td>
<td className="py-3 px-4 text-sm">2023-04-01</td>
<td className="py-3 px-4 text-sm">1000</td>
<td className="py-3 px-4 text-sm font-mono text-xs">DNC{dance.id}-0001</td>
<td className="py-3 px-4 text-sm font-mono text-xs">DNC{dance.id}-1000</td>
<td className="py-3 px-4 text-right">
<Button variant="ghost" size="sm" className="h-8 hover:bg-blue-50 hover:text-blue-600">
<FileText className="h-4 w-4 mr-1" />
ID
</Button>
</td>
</tr>
{isPublished && (
<tr className="border-b hover:bg-gray-50">
<td className="py-3 px-4 text-sm font-medium">B002</td>
<td className="py-3 px-4 text-sm">2023-08-15</td>
<td className="py-3 px-4 text-sm">1000</td>
<td className="py-3 px-4 text-sm font-mono text-xs">DNC{dance.id}-1001</td>
<td className="py-3 px-4 text-sm font-mono text-xs">DNC{dance.id}-2000</td>
<td className="py-3 px-4 text-right">
<Button variant="ghost" size="sm" className="h-8 hover:bg-blue-50 hover:text-blue-600">
<FileText className="h-4 w-4 mr-1" />
ID
</Button>
</td>
</tr>
)}
</tbody>
</table>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="analytics" className="space-y-4">
<Card className="border-none shadow-lg bg-gradient-to-br from-white to-green-50">
<CardHeader>
<CardTitle className="text-lg font-bold"></CardTitle>
<CardDescription>使</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-6 md:grid-cols-2">
<div className="h-[300px] bg-gray-50 rounded-lg flex items-center justify-center">
<p className="text-gray-500"></p>
</div>
<div className="h-[300px] bg-gray-50 rounded-lg flex items-center justify-center">
<p className="text-gray-500"></p>
</div>
<div className="md:col-span-2 h-[300px] bg-gray-50 rounded-lg flex items-center justify-center">
<p className="text-gray-500"></p>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 编辑舞蹈对话框 */}
<AddDanceDialog
open={isEditDialogOpen}
onOpenChange={setIsEditDialogOpen}
onDanceAdded={handleEditDance}
editDance={dance}
/>
{/* 删除确认对话框 */}
<DeleteConfirmationDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
onConfirm={handleDeleteDance}
title="删除舞蹈"
description={`确定要删除舞蹈 "${dance.name}" 吗?此操作无法撤销。`}
/>
</DashboardShell>
)
}