- API 路径前缀改为 /v1/admin - 分类管理改用服务端分页(page/limit),移除未定义的 search/status 筛选 - 知识卡字段重命名:basic→summary、deep→deepDive - 各页面移除不必要的 limit 参数
224 lines
6.0 KiB
TypeScript
224 lines
6.0 KiB
TypeScript
import { useCallback, useEffect, useState } from "react"
|
||
import {
|
||
useReactTable,
|
||
getCoreRowModel,
|
||
flexRender,
|
||
} from "@tanstack/react-table"
|
||
import { Plus } from "lucide-react"
|
||
import { Button } from "@/components/ui/button"
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/components/ui/table"
|
||
import { CategoryFormDialog } from "@/components/category/CategoryFormDialog"
|
||
import { DeleteCategoryDialog } from "@/components/category/DeleteCategoryDialog"
|
||
import { getColumns } from "@/components/category/columns"
|
||
import {
|
||
fetchCategories,
|
||
createCategory,
|
||
updateCategory,
|
||
deleteCategory,
|
||
} from "@/lib/api/category-api"
|
||
import type { Category, CategoryFormData } from "@/types/category"
|
||
|
||
const PAGE_SIZE = 20
|
||
|
||
export default function CategoriesPage() {
|
||
const [categories, setCategories] = useState<Category[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [total, setTotal] = useState(0)
|
||
const [page, setPage] = useState(1)
|
||
|
||
// 对话框状态
|
||
const [formOpen, setFormOpen] = useState(false)
|
||
const [editingCategory, setEditingCategory] = useState<Category | null>(null)
|
||
const [deleteOpen, setDeleteOpen] = useState(false)
|
||
const [deletingCategory, setDeletingCategory] = useState<Category | null>(null)
|
||
|
||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
||
|
||
const loadCategories = useCallback(async () => {
|
||
setLoading(true)
|
||
try {
|
||
const res = await fetchCategories({
|
||
page,
|
||
limit: PAGE_SIZE,
|
||
})
|
||
setCategories(res.data)
|
||
setTotal(res.pagination.total)
|
||
} catch {
|
||
setCategories([])
|
||
setTotal(0)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}, [page])
|
||
|
||
useEffect(() => {
|
||
loadCategories()
|
||
}, [loadCategories])
|
||
|
||
// 新建 / 编辑提交
|
||
async function handleFormSubmit(data: CategoryFormData) {
|
||
if (editingCategory) {
|
||
await updateCategory(editingCategory.id, data)
|
||
} else {
|
||
await createCategory(data)
|
||
}
|
||
await loadCategories()
|
||
}
|
||
|
||
// 删除提交
|
||
async function handleDelete() {
|
||
if (!deletingCategory) return
|
||
await deleteCategory(deletingCategory.id)
|
||
setDeleteOpen(false)
|
||
setDeletingCategory(null)
|
||
await loadCategories()
|
||
}
|
||
|
||
// 打开编辑对话框
|
||
function openEdit(category: Category) {
|
||
setEditingCategory(category)
|
||
setFormOpen(true)
|
||
}
|
||
|
||
// 打开新建对话框
|
||
function openCreate() {
|
||
setEditingCategory(null)
|
||
setFormOpen(true)
|
||
}
|
||
|
||
// 打开删除对话框
|
||
function openDelete(category: Category) {
|
||
setDeletingCategory(category)
|
||
setDeleteOpen(true)
|
||
}
|
||
|
||
const columns = getColumns({
|
||
onEdit: openEdit,
|
||
onDelete: openDelete,
|
||
})
|
||
|
||
const table = useReactTable({
|
||
data: categories,
|
||
columns,
|
||
getCoreRowModel: getCoreRowModel(),
|
||
})
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* 页面头部 */}
|
||
<div className="flex items-center justify-between">
|
||
<h1 className="text-2xl font-bold">分类管理</h1>
|
||
<Button onClick={openCreate}>
|
||
<Plus className="size-4" />
|
||
新建分类
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 表格 */}
|
||
<div className="rounded-lg 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>
|
||
{loading ? (
|
||
<TableRow>
|
||
<TableCell
|
||
colSpan={columns.length}
|
||
className="h-24 text-center text-muted-foreground"
|
||
>
|
||
加载中...
|
||
</TableCell>
|
||
</TableRow>
|
||
) : categories.length === 0 ? (
|
||
<TableRow>
|
||
<TableCell
|
||
colSpan={columns.length}
|
||
className="h-24 text-center text-muted-foreground"
|
||
>
|
||
暂无分类数据
|
||
</TableCell>
|
||
</TableRow>
|
||
) : (
|
||
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>
|
||
|
||
{/* 分页 */}
|
||
{totalPages > 1 && (
|
||
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
||
<span>
|
||
共 {total} 条,第 {page}/{totalPages} 页
|
||
</span>
|
||
<div className="flex gap-2">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
disabled={page <= 1}
|
||
onClick={() => setPage((p) => p - 1)}
|
||
>
|
||
上一页
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
disabled={page >= totalPages}
|
||
onClick={() => setPage((p) => p + 1)}
|
||
>
|
||
下一页
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 对话框 */}
|
||
<CategoryFormDialog
|
||
open={formOpen}
|
||
onOpenChange={setFormOpen}
|
||
category={editingCategory}
|
||
onSubmit={handleFormSubmit}
|
||
/>
|
||
|
||
<DeleteCategoryDialog
|
||
open={deleteOpen}
|
||
onOpenChange={setDeleteOpen}
|
||
category={deletingCategory}
|
||
onConfirm={handleDelete}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|