353 lines
14 KiB
TypeScript
353 lines
14 KiB
TypeScript
"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>
|
||
)
|
||
}
|