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

353 lines
14 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 } from "react"
import { 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, 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, Video, Eye, Plus, Trash2 } from "lucide-react"
import { AddDanceDialog } from "@/components/dances/add-dance-dialog"
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"
import { useToast } from "@/hooks/use-toast"
import type { Dance } from "@/lib/api/types"
// 初始舞蹈数据
const initialDances: 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",
},
{
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",
},
{
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",
},
{
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",
},
{
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",
},
]
export default function DancesPage() {
const { toast } = useToast()
const router = useRouter()
const [dances, setDances] = useState<Dance[]>(initialDances)
const [searchTerm, setSearchTerm] = useState("")
const [currentPage, setCurrentPage] = useState(1)
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
const [danceToEdit, setDanceToEdit] = useState<Dance | undefined>(undefined)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [danceToDelete, setDanceToDelete] = useState<Dance | null>(null)
const itemsPerPage = 5
// 过滤和分页
const filteredDances = dances.filter(
(dance) =>
dance.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
dance.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
(dance.choreographer && dance.choreographer.toLowerCase().includes(searchTerm.toLowerCase())) ||
(dance.category && dance.category.toLowerCase().includes(searchTerm.toLowerCase())) ||
(dance.tags && dance.tags.some((tag) => tag.toLowerCase().includes(searchTerm.toLowerCase()))),
)
const totalPages = Math.ceil(filteredDances.length / itemsPerPage)
const paginatedDances = filteredDances.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
// 处理添加舞蹈
const handleAddDance = (newDance: Dance) => {
setDances((prevDances) => [...prevDances, newDance])
toast({
title: "添加成功",
description: `舞蹈 ${newDance.name} 已成功添加`,
})
}
// 处理编辑舞蹈
const handleEditDance = (updatedDance: Dance) => {
setDances((prevDances) => prevDances.map((dance) => (dance.id === updatedDance.id ? updatedDance : dance)))
setDanceToEdit(undefined)
setIsAddDialogOpen(false)
toast({
title: "更新成功",
description: `舞蹈 ${updatedDance.name} 已成功更新`,
})
}
// 处理删除舞蹈
const handleDeleteDance = () => {
if (!danceToDelete) return
setDances((prevDances) => prevDances.filter((dance) => dance.id !== danceToDelete.id))
setDanceToDelete(null)
setIsDeleteDialogOpen(false)
toast({
title: "删除成功",
description: `舞蹈 ${danceToDelete.name} 已成功删除`,
variant: "destructive",
})
}
// 打开编辑对话框
const openEditDialog = (dance: Dance) => {
setDanceToEdit(dance)
setIsAddDialogOpen(true)
}
// 查看舞蹈详情
const viewDanceDetails = (dance: Dance) => {
router.push(`/dances/${dance.id}`)
}
// 打开删除确认对话框
const openDeleteDialog = (dance: Dance) => {
setDanceToDelete(dance)
setIsDeleteDialogOpen(true)
}
return (
<DashboardShell>
<DashboardHeader heading="舞蹈管理" text="管理洛天依的舞蹈库">
<Button
onClick={() => {
setDanceToEdit(undefined)
setIsAddDialogOpen(true)
}}
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600"
>
<Plus className="mr-2 h-4 w-4" />
</Button>
</DashboardHeader>
<div className="flex items-center justify-between space-y-2 mb-6">
<div className="flex items-center space-x-2">
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="搜索舞蹈..."
className="w-[300px] pl-8 border-none bg-white shadow-md focus-visible:ring-purple-500"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setCurrentPage(1) // 重置到第一页
}}
/>
</div>
</div>
</div>
<Card className="border-none shadow-lg bg-gradient-to-br from-white to-purple-50">
<CardHeader>
<CardTitle className="text-xl font-bold flex items-center">
<span className="bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600"></span>
<div className="ml-2 h-1 w-10 bg-gradient-to-r from-purple-600 to-pink-600 rounded-full"></div>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader className="bg-gray-50">
<TableRow>
<TableHead className="w-[50px]"></TableHead>
<TableHead className="w-[80px]">ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{paginatedDances.map((dance) => (
<TableRow key={dance.id} className="hover:bg-gray-50 transition-colors">
<TableCell>
<div className="h-8 w-8 rounded-full bg-purple-100 flex items-center justify-center text-purple-600">
<Video className="h-4 w-4" />
</div>
</TableCell>
<TableCell className="font-medium">{dance.id}</TableCell>
<TableCell className="font-medium text-purple-600">{dance.name}</TableCell>
<TableCell>{dance.choreographer || "未知"}</TableCell>
<TableCell>
<Badge
variant="outline"
className={
dance.difficulty === "初级"
? "bg-green-100 text-green-800"
: dance.difficulty === "中等"
? "bg-blue-100 text-blue-800"
: dance.difficulty === "中高级"
? "bg-yellow-100 text-yellow-800"
: dance.difficulty === "高级"
? "bg-orange-100 text-orange-800"
: dance.difficulty === "专业"
? "bg-red-100 text-red-800"
: "bg-gray-100 text-gray-800"
}
>
{dance.difficulty || "未知"}
</Badge>
</TableCell>
<TableCell>{dance.category || "未分类"}</TableCell>
<TableCell>{dance.duration || "未知"}</TableCell>
<TableCell className="text-right">
<div className="flex justify-end space-x-1">
<Button
variant="ghost"
size="icon"
className="hover:bg-purple-50 hover:text-purple-600"
onClick={() => viewDanceDetails(dance)}
>
<Eye className="h-4 w-4" />
<span className="sr-only"></span>
</Button>
<Button
variant="ghost"
size="icon"
className="hover:bg-purple-50 hover:text-purple-600"
onClick={() => openEditDialog(dance)}
>
<Edit className="h-4 w-4" />
<span className="sr-only"></span>
</Button>
<Button
variant="ghost"
size="icon"
className="hover:bg-red-50 hover:text-red-600"
onClick={() => openDeleteDialog(dance)}
>
<Trash2 className="h-4 w-4" />
<span className="sr-only"></span>
</Button>
</div>
</TableCell>
</TableRow>
))}
{paginatedDances.length === 0 && (
<TableRow>
<TableCell colSpan={8} className="h-24 text-center">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
<CardFooter className="flex justify-between">
<div className="text-sm text-muted-foreground">
{paginatedDances.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0}-
{Math.min(currentPage * itemsPerPage, filteredDances.length)} {filteredDances.length}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
className="hover:bg-purple-50 hover:text-purple-700 transition-all duration-200"
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
disabled={currentPage === 1}
>
</Button>
<Button
variant="outline"
size="sm"
className="hover:bg-purple-50 hover:text-purple-700 transition-all duration-200"
onClick={() => setCurrentPage((prev) => Math.min(prev + 1, totalPages))}
disabled={currentPage === totalPages || totalPages === 0}
>
</Button>
</div>
</CardFooter>
</Card>
{/* 添加/编辑舞蹈对话框 */}
<AddDanceDialog
open={isAddDialogOpen}
onOpenChange={setIsAddDialogOpen}
onDanceAdded={danceToEdit ? handleEditDance : handleAddDance}
editDance={danceToEdit}
/>
{/* 删除确认对话框 */}
<DeleteConfirmationDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
onConfirm={handleDeleteDance}
title="删除舞蹈"
description={`确定要删除舞蹈 "${danceToDelete?.name}" 吗?此操作无法撤销。`}
/>
</DashboardShell>
)
}