echoes/frontend/app/dashboard/files.tsx

303 lines
9.9 KiB
TypeScript
Raw Normal View History

import { Template } from "interface/template";
import {
Container,
Heading,
Text,
Box,
Flex,
Table,
Button,
TextField,
DropdownMenu,
ScrollArea,
Dialog,
DataList,
} from "@radix-ui/themes";
import {
PlusIcon,
MagnifyingGlassIcon,
FileIcon,
TrashIcon,
DownloadIcon,
DotsHorizontalIcon,
FileTextIcon,
ImageIcon,
VideoIcon,
} from "@radix-ui/react-icons";
import { useState } from "react";
import type { Resource } from "interface/fields";
// 模拟文件数据
const mockFiles: Resource[] = [
{
id: 1,
authorId: "1",
name: "前端开发规范.pdf",
sizeBytes: 1024 * 1024 * 2, // 2MB
storagePath: "/files/frontend-guide.pdf",
fileType: "application/pdf",
category: "documents",
description: "前端开发规范文档",
createdAt: new Date("2024-03-15"),
},
{
id: 2,
authorId: "1",
name: "项目架构图.png",
sizeBytes: 1024 * 512, // 512KB
storagePath: "/files/architecture.png",
fileType: "image/png",
category: "images",
description: "项目整体架构示意图",
createdAt: new Date("2024-03-14"),
},
{
id: 3,
authorId: "1",
name: "API文档.md",
sizeBytes: 1024 * 256, // 256KB
storagePath: "/files/api-doc.md",
fileType: "text/markdown",
category: "documents",
description: "API接口文档",
createdAt: new Date("2024-03-13"),
},
];
export default new Template({}, ({ http, args }) => {
const [searchTerm, setSearchTerm] = useState("");
const [selectedType, setSelectedType] = useState<string>("all");
const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false);
// 格式化文件大小
const formatFileSize = (bytes: number) => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
// 获取文件图标
const getFileIcon = (fileType: string) => {
if (fileType.startsWith("image/")) return <ImageIcon className="w-4 h-4" />;
if (fileType.startsWith("video/")) return <VideoIcon className="w-4 h-4" />;
if (fileType.startsWith("text/"))
return <FileTextIcon className="w-4 h-4" />;
return <FileIcon className="w-4 h-4" />;
};
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]"> {mockFiles.length} </Text>
</Box>
<Button
className="bg-[--accent-9]"
onClick={() => setIsUploadDialogOpen(true)}
>
<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>
<MagnifyingGlassIcon height="16" width="16" />
</TextField.Slot>
</TextField.Root>
</Box>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="surface">
: {selectedType === "all" ? "全部" : selectedType}
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item onClick={() => setSelectedType("all")}>
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => setSelectedType("documents")}>
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => setSelectedType("images")}>
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => setSelectedType("others")}>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Flex>
{/* 文件列表 */}
<Box className="border border-[--gray-6] rounded-lg overflow-hidden">
<ScrollArea>
{/* 桌面端表格视图 */}
<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.Row>
</Table.Header>
<Table.Body>
{mockFiles.map((file) => (
<Table.Row key={file.id}>
<Table.Cell>
<Flex align="center" gap="2">
{getFileIcon(file.fileType)}
<Text className="font-medium">{file.name}</Text>
</Flex>
</Table.Cell>
<Table.Cell>{formatFileSize(file.sizeBytes)}</Table.Cell>
<Table.Cell>
<Text className="text-[--gray-11]">
{file.fileType.split("/")[1].toUpperCase()}
</Text>
</Table.Cell>
<Table.Cell>
{file.createdAt.toLocaleDateString()}
</Table.Cell>
<Table.Cell>
<Flex gap="2">
<Button variant="ghost" size="1">
<DownloadIcon 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">
{mockFiles.map((file) => (
<DataList.Root
key={file.id}
className="p-4 border-b border-[--gray-6] last:border-b-0"
>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
<Flex align="center" gap="2">
{getFileIcon(file.fileType)}
<Text className="font-medium">{file.name}</Text>
</Flex>
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
{formatFileSize(file.sizeBytes)}
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
{file.fileType.split("/")[1].toUpperCase()}
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
{file.createdAt.toLocaleDateString()}
</DataList.Value>
</DataList.Item>
<DataList.Item>
<DataList.Label minWidth="88px"></DataList.Label>
<DataList.Value>
<Flex gap="2">
<Button variant="ghost" size="1">
<DownloadIcon 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>
{/* 上传对话框 */}
<Dialog.Root
open={isUploadDialogOpen}
onOpenChange={setIsUploadDialogOpen}
>
<Dialog.Content style={{ maxWidth: 450 }}>
<Dialog.Title></Dialog.Title>
<Dialog.Description size="2" mb="4">
</Dialog.Description>
<Box className="border-2 border-dashed border-[--gray-6] rounded-lg p-8 text-center">
<input
type="file"
className="hidden"
id="file-upload"
multiple
onChange={(e) => {
// 处理文件上传
console.log(e.target.files);
}}
/>
<label htmlFor="file-upload" className="cursor-pointer">
<FileIcon className="w-12 h-12 mx-auto mb-4 text-[--gray-9]" />
<Text className="text-[--gray-11] mb-2">
</Text>
<Text size="1" className="text-[--gray-10]">
</Text>
</label>
</Box>
<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>
);
});