feat: 支持批量上传关卡
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user