Next.js 14 App Router application for managing homophone pun game levels: - Better Auth with Prisma adapter for authentication - MySQL database with Prisma ORM - Level CRUD operations with drag-and-drop reordering - Tencent COS integration for image uploads - shadcn/ui components with Tailwind CSS - TanStack Query for server state management
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
'use client'
|
|
|
|
import { Level } from '@/types'
|
|
import { Card, CardContent } from '@/components/ui/card'
|
|
import { Button } from '@/components/ui/button'
|
|
import { GripVertical, Pencil, Trash2 } from 'lucide-react'
|
|
import Image from 'next/image'
|
|
|
|
interface LevelCardProps {
|
|
level: Level
|
|
onEdit: (level: Level) => void
|
|
onDelete: (id: string) => void
|
|
isDragging?: boolean
|
|
}
|
|
|
|
export function LevelCard({ level, onEdit, onDelete, isDragging }: LevelCardProps) {
|
|
return (
|
|
<Card
|
|
className={`cursor-grab transition-shadow ${
|
|
isDragging ? 'shadow-lg opacity-90' : 'hover:shadow-md'
|
|
}`}
|
|
>
|
|
<CardContent className="p-4">
|
|
<div className="flex items-start gap-4">
|
|
<div className="flex items-center justify-center w-8 h-8 rounded bg-gray-100 text-gray-500 cursor-grab">
|
|
<GripVertical className="h-4 w-4" />
|
|
</div>
|
|
<div className="relative w-20 h-20 rounded-md overflow-hidden bg-gray-100 flex-shrink-0">
|
|
{level.imageUrl ? (
|
|
<Image
|
|
src={level.imageUrl}
|
|
alt="关卡图片"
|
|
fill
|
|
className="object-cover"
|
|
sizes="80px"
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center text-gray-400 text-xs">
|
|
无图片
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="font-medium text-lg truncate">{level.answer}</p>
|
|
<div className="mt-1 space-y-0.5">
|
|
{level.hint1 && (
|
|
<p className="text-sm text-gray-500 truncate">提示1: {level.hint1}</p>
|
|
)}
|
|
{level.hint2 && (
|
|
<p className="text-sm text-gray-500 truncate">提示2: {level.hint2}</p>
|
|
)}
|
|
{level.hint3 && (
|
|
<p className="text-sm text-gray-500 truncate">提示3: {level.hint3}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={() => onEdit(level)}
|
|
>
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={() => onDelete(level.id)}
|
|
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|