feat: 支持批量上传关卡
This commit is contained in:
119
components/levels/level-row.tsx
Normal file
119
components/levels/level-row.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
'use client'
|
||||
|
||||
import { Level } from '@/types'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Pencil, Trash2 } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
|
||||
// 列宽定义,header 和 row 必须保持一致。用 grid-template-columns 统一控制。
|
||||
// 序号 | 图片 | 答案 | 谐音梗 | 提示 | 创建时间 | 操作
|
||||
export const GRID_TEMPLATE =
|
||||
'minmax(60px,60px) minmax(120px,120px) minmax(80px,1fr) minmax(100px,1fr) minmax(160px,2fr) minmax(100px,100px) minmax(100px,100px)'
|
||||
|
||||
interface LevelRowProps {
|
||||
level: Level
|
||||
index: number
|
||||
onEdit: (level: Level) => void
|
||||
onDelete: (id: string) => void
|
||||
}
|
||||
|
||||
export function LevelRow({ level, index, onEdit, onDelete }: LevelRowProps) {
|
||||
return (
|
||||
<div
|
||||
style={{ gridTemplateColumns: GRID_TEMPLATE }}
|
||||
className="grid items-center gap-3 px-3 border-b bg-background text-sm min-h-[64px] hover:bg-muted/30 transition-colors"
|
||||
>
|
||||
{/* 序号:用数组 index + 1,而不是 DB 里已弃用的 sortOrder */}
|
||||
<span className="font-mono tabular-nums text-muted-foreground">
|
||||
{index + 1}
|
||||
</span>
|
||||
|
||||
{/* 图片 */}
|
||||
<div className="flex gap-1.5">
|
||||
<div className="relative w-12 h-12 rounded overflow-hidden bg-gray-100 flex-shrink-0">
|
||||
{level.image1Url ? (
|
||||
<Image
|
||||
src={level.image1Url}
|
||||
alt="图片1"
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="48px"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-gray-400 text-[10px]">
|
||||
无
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative w-12 h-12 rounded overflow-hidden bg-gray-100 flex-shrink-0">
|
||||
{level.image2Url ? (
|
||||
<Image
|
||||
src={level.image2Url}
|
||||
alt="图片2"
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="48px"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-gray-400 text-[10px]">
|
||||
无
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 答案 */}
|
||||
<span className="font-medium truncate">{level.answer}</span>
|
||||
|
||||
{/* 谐音梗 */}
|
||||
<span className="truncate">
|
||||
{level.punchline ? (
|
||||
<span className="text-orange-600">{level.punchline}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
{/* 提示 */}
|
||||
<span className="truncate text-muted-foreground">
|
||||
{(() => {
|
||||
const hints = [level.hint1, level.hint2, level.hint3].filter(Boolean)
|
||||
return hints.length > 0 ? hints.join('、') : '—'
|
||||
})()}
|
||||
</span>
|
||||
|
||||
{/* 创建时间 */}
|
||||
<span className="text-muted-foreground whitespace-nowrap">
|
||||
{new Date(level.createdAt).toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})}
|
||||
</span>
|
||||
|
||||
{/* 操作 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => onEdit(level)}
|
||||
aria-label="编辑关卡"
|
||||
title="编辑"
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 border-red-200 text-red-600 hover:border-red-600 hover:bg-red-600 hover:text-white focus-visible:ring-red-500"
|
||||
onClick={() => onDelete(level.id)}
|
||||
aria-label="删除关卡"
|
||||
title="删除"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user