echoes/frontend/app/dashboard/categories.tsx

209 lines
6.1 KiB
TypeScript

import { Template } from "interface/template";
import {
Container,
Heading,
Text,
Box,
Flex,
Table,
Button,
TextField,
ScrollArea,
Dialog,
IconButton
} from "@radix-ui/themes";
import {
PlusIcon,
MagnifyingGlassIcon,
Pencil1Icon,
TrashIcon,
ChevronRightIcon,
} from "@radix-ui/react-icons";
import { useState } from "react";
import type { Category } from "interface/fields";
// 模拟分类数据
const mockCategories: (Category & { id: number; count: number })[] = [
{
id: 1,
name: "前端开发",
parentId: undefined,
count: 15
},
{
id: 2,
name: "React",
parentId: "1",
count: 8
},
{
id: 3,
name: "Vue",
parentId: "1",
count: 5
},
{
id: 4,
name: "后端开发",
parentId: undefined,
count: 12
},
{
id: 5,
name: "Node.js",
parentId: "4",
count: 6
}
];
export default new Template({}, ({ http, args }) => {
const [searchTerm, setSearchTerm] = useState("");
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [newCategoryName, setNewCategoryName] = useState("");
const [selectedParentId, setSelectedParentId] = useState<string | undefined>();
return (
<Box>
{/* 页面标题和操作栏 */}
<Flex justify="between" align="center" className="mb-6">
<Box>
<Heading size="6" className="text-[--gray-12] mb-2">
</Heading>
<Text className="text-[--gray-11]">
{mockCategories.length}
</Text>
</Box>
<Button
className="bg-[--accent-9]"
onClick={() => setIsAddDialogOpen(true)}
>
<PlusIcon className="w-4 h-4" />
</Button>
</Flex>
{/* 搜索栏 */}
<Box className="w-full sm:w-64 mb-6">
<TextField.Root
placeholder="搜索分类..."
value={searchTerm}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
>
<TextField.Slot>
<MagnifyingGlassIcon height="16" width="16" />
</TextField.Slot>
</TextField.Root>
</Box>
{/* 分类列表 */}
<Box className="border border-[--gray-6] rounded-lg overflow-hidden">
<ScrollArea>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{mockCategories.map((category) => (
<Table.Row key={category.id}>
<Table.Cell>
<Flex align="center" gap="2">
{category.parentId && (
<ChevronRightIcon className="w-4 h-4 text-[--gray-11]" />
)}
<Text>{category.name}</Text>
</Flex>
</Table.Cell>
<Table.Cell>
<Text>{category.count} </Text>
</Table.Cell>
<Table.Cell>
<Text>
{category.parentId
? mockCategories.find(c => c.id.toString() === category.parentId)?.name
: '-'
}
</Text>
</Table.Cell>
<Table.Cell>
<Flex gap="2">
<Button variant="ghost" size="1">
<Pencil1Icon className="w-4 h-4" />
</Button>
<Button variant="ghost" size="1" color="red">
<TrashIcon className="w-4 h-4" />
</Button>
</Flex>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</ScrollArea>
</Box>
{/* 新建分类对话框 */}
<Dialog.Root open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
<Dialog.Content style={{ maxWidth: 450 }}>
<Dialog.Title></Dialog.Title>
<Dialog.Description size="2" mb="4">
</Dialog.Description>
<Flex direction="column" gap="3">
<Box>
<Text as="label" size="2" mb="1" weight="bold">
</Text>
<TextField.Root
placeholder="输入分类名称"
value={newCategoryName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setNewCategoryName(e.target.value)}
/>
</Box>
<Box>
<Text as="label" size="2" mb="1" weight="bold">
</Text>
<select
className="w-full h-9 px-3 rounded-md bg-[--gray-1] border border-[--gray-6] text-[--gray-12]"
value={selectedParentId}
onChange={(e) => setSelectedParentId(e.target.value)}
>
<option value=""></option>
{mockCategories
.filter(c => !c.parentId)
.map(category => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))
}
</select>
</Box>
</Flex>
<Flex gap="3" mt="4" justify="end">
<Dialog.Close>
<Button variant="soft" color="gray">
</Button>
</Dialog.Close>
<Dialog.Close>
<Button className="bg-[--accent-9]">
</Button>
</Dialog.Close>
</Flex>
</Dialog.Content>
</Dialog.Root>
</Box>
);
});