echoes/frontend/app/dashboard/posts.tsx

256 lines
9.7 KiB
TypeScript
Raw Normal View History

import { Template } from "interface/template";
import {
Container,
Heading,
Text,
Box,
Flex,
Table,
Button,
TextField,
DropdownMenu,
ScrollArea,
DataList,
Badge,
} from "@radix-ui/themes";
import {
PlusIcon,
MagnifyingGlassIcon,
DotsHorizontalIcon,
Pencil1Icon,
TrashIcon,
EyeOpenIcon,
ReaderIcon,
} from "@radix-ui/react-icons";
import { useState } from "react";
import type { PostDisplay } from "interface/fields";
// 模拟文章数据
const mockPosts: PostDisplay[] = [
{
id: 1,
title: "构建现代化的前端开发工作流",
content: "在现代前端开发中...",
authorName: "张三",
publishedAt: new Date("2024-03-15"),
status: "published",
isEditor: false,
createdAt: new Date("2024-03-15"),
updatedAt: new Date("2024-03-15"),
metaKeywords: "",
metaDescription: "",
categories: [{ name: "前端开发" }],
tags: [{ name: "工程化" }, { name: "效率提升" }],
},
// ... 可以添加更多模拟数据
];
export default new Template({}, ({ http, args }) => {
const [searchTerm, setSearchTerm] = useState("");
const [selectedStatus, setSelectedStatus] = useState<string>("all");
return (
<Box>
{/* 页面标题和操作栏 */}
<Flex justify="between" align="center" className="mb-6">
<Heading size="6" className="text-[--gray-12]">
</Heading>
<Button className="bg-[--accent-9]">
<PlusIcon className="w-4 h-4" />
</Button>
</Flex>
{/* 搜索和筛选栏 */}
<Flex
gap="4"
className="mb-6 flex-col sm:flex-row" // 移动端垂直布局,桌面端水平布局
>
<Box className="w-full sm:w-64">
<TextField.Root
placeholder="搜索文章..."
value={searchTerm}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setSearchTerm(e.target.value)
}
>
<TextField.Slot side="right">
<MagnifyingGlassIcon height="16" width="16" />
</TextField.Slot>
</TextField.Root>
</Box>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="surface">
: {selectedStatus === "all" ? "全部" : selectedStatus}
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item onClick={() => setSelectedStatus("all")}>
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => setSelectedStatus("published")}>
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => setSelectedStatus("draft")}>
稿
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Flex>
{/* 文章列表 */}
<Box className="border border-[--gray-6] rounded-lg overflow-hidden">
<ScrollArea className="w-full">
{/* 桌面端表格视图 */}
<div className="hidden sm:block">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{mockPosts.map((post) => (
<Table.Row
key={post.id}
className="hover:bg-[--gray-3] block sm:table-row mb-4 sm:mb-0"
>
<Table.Cell className="font-medium block sm:table-cell py-2 sm:py-3 before:content-['标题:'] before:inline-block before:w-20 before:font-normal sm:before:content-none">
{post.title}
</Table.Cell>
<Table.Cell className="block sm:table-cell py-2 sm:py-3 before:content-['作者:'] before:inline-block before:w-20 before:font-normal sm:before:content-none">
{post.authorName}
</Table.Cell>
<Table.Cell className="block sm:table-cell py-2 sm:py-3 before:content-['分类:'] before:inline-block before:w-20 before:font-normal sm:before:content-none">
<Flex gap="2" className="inline-flex">
{post.categories?.map((category) => (
<Text
key={category.name}
size="1"
className="px-2 py-0.5 bg-[--gray-4] rounded"
>
{category.name}
</Text>
))}
</Flex>
</Table.Cell>
<Table.Cell className="block sm:table-cell py-2 sm:py-3 before:content-['状态:'] before:inline-block before:w-20 before:font-normal sm:before:content-none">
<Flex gap="2">
{post.status === "published" ? (
<Badge color="green"></Badge>
) : (
<Badge color="orange">稿</Badge>
)}
</Flex>
</Table.Cell>
<Table.Cell className="block sm:table-cell py-2 sm:py-3 before:content-['发布时间:'] before:inline-block before:w-20 before:font-normal sm:before:content-none">
{post.publishedAt?.toLocaleDateString()}
</Table.Cell>
<Table.Cell className="block sm:table-cell py-2 sm:py-3 border-b sm:border-b-0 before:content-['操作:'] before:inline-block before:w-20 before:font-normal sm:before:content-none">
<Flex gap="2">
<Button variant="ghost" size="1">
<Pencil1Icon className="w-4 h-4" />
</Button>
<Button variant="ghost" size="1">
<EyeOpenIcon 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>
</div>
{/* 移动端列表视图 */}
<div className="block sm:hidden">
{mockPosts.map((post) => (
<DataList.Root
key={post.id}
className="p-4 border-b border-[--gray-6] last:border-b-0"
>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
<Text weight="medium">{post.title}</Text>
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>{post.authorName}</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
<Flex gap="2">
{post.categories?.map((category) => (
<Text
key={category.name}
size="1"
className="px-2 py-0.5 bg-[--gray-4] rounded"
>
{category.name}
</Text>
))}
</Flex>
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
<Flex gap="2">
{post.status === "published" ? (
<Badge color="green"></Badge>
) : (
<Badge color="orange">稿</Badge>
)}
</Flex>
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
{post.publishedAt?.toLocaleDateString()}
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
<Flex gap="2">
<Button variant="ghost" size="1">
<Pencil1Icon className="w-4 h-4" />
</Button>
<Button variant="ghost" size="1">
<EyeOpenIcon className="w-4 h-4" />
</Button>
<Button variant="ghost" size="1" color="red">
<TrashIcon className="w-4 h-4" />
</Button>
</Flex>
</DataList.Value>
</DataList.Item>
</DataList.Root>
))}
</div>
</ScrollArea>
</Box>
</Box>
);
});