feat: 支持批量上传关卡

This commit is contained in:
richarjiang
2026-05-01 08:44:56 +08:00
parent f3f27def2b
commit 66a9ee2950
27 changed files with 5262 additions and 515 deletions

View File

@@ -1,61 +1,40 @@
'use client'
import { useMemo } from 'react'
import {
useReactTable,
getCoreRowModel,
getPaginationRowModel,
flexRender,
} from '@tanstack/react-table'
import { useRef } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { Level } from '@/types'
import { createColumns } from './level-columns'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { Button } from '@/components/ui/button'
import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from 'lucide-react'
import { GRID_TEMPLATE, LevelRow } from './level-row'
interface LevelTableProps {
levels: Level[]
onEdit: (level: Level) => void
onDelete: (id: string) => void
deleteConfirmId: string | null
}
export function LevelTable({
levels,
onEdit,
onDelete,
deleteConfirmId,
}: LevelTableProps) {
const columns = useMemo(
() => createColumns({ onEdit, onDelete, deleteConfirmId }),
[onEdit, onDelete, deleteConfirmId]
)
const HEADER_COLUMNS = [
'序号',
'图片',
'答案',
'谐音梗',
'提示',
'创建时间',
'操作',
]
const table = useReactTable({
data: levels,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageSize: 10,
},
},
export function LevelTable({ levels, onEdit, onDelete }: LevelTableProps) {
const scrollRef = useRef<HTMLDivElement>(null)
const items = levels
const rowVirtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 64,
overscan: 10,
})
if (levels.length === 0) {
if (items.length === 0) {
return (
<div className="text-center py-12 text-gray-500">
<p></p>
@@ -67,105 +46,60 @@ export function LevelTable({
}
return (
<div className="space-y-4">
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="space-y-2">
<div className="rounded-md border overflow-hidden bg-background">
{/* 表头 */}
<div
className="grid items-center gap-3 px-3 py-2 bg-muted/40 text-xs font-medium text-muted-foreground border-b"
style={{ gridTemplateColumns: GRID_TEMPLATE }}
>
{HEADER_COLUMNS.map((h, i) => (
<span key={i}>{h}</span>
))}
</div>
{/* 分页控制栏 */}
<div className="flex items-center justify-between px-2">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span></span>
<select
className="h-8 rounded-md border border-input bg-background px-2 text-sm"
value={table.getState().pagination.pageSize}
onChange={(e) => {
table.setPageSize(Number(e.target.value))
{/* 虚拟滚动 */}
<div
ref={scrollRef}
className="overflow-auto"
style={{ height: 'calc(100vh - 260px)' }}
>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
position: 'relative',
width: '100%',
}}
>
{[10, 20, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
{pageSize}
</option>
))}
</select>
<span></span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">
{table.getState().pagination.pageIndex + 1} /{' '}
{table.getPageCount()} {levels.length}
</span>
<div className="flex items-center gap-1">
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<ChevronsRight className="h-4 w-4" />
</Button>
{rowVirtualizer.getVirtualItems().map((v) => {
const level = items[v.index]
return (
<div
key={level.id}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${v.start}px)`,
}}
>
<LevelRow
level={level}
index={v.index}
onEdit={onEdit}
onDelete={onDelete}
/>
</div>
)
})}
</div>
</div>
</div>
<div className="px-2 text-xs text-muted-foreground">
{items.length} · /
</div>
</div>
)
}