echoes/frontend/app/dashboard/posts.tsx

247 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
});