echoes/frontend/app/dashboard/users.tsx

343 lines
11 KiB
TypeScript
Raw Normal View History

import { Template } from "interface/template";
import {
Container,
Heading,
Text,
Box,
Flex,
Table,
Button,
TextField,
ScrollArea,
Dialog,
Avatar,
DropdownMenu,
Badge
} from "@radix-ui/themes";
import {
PlusIcon,
MagnifyingGlassIcon,
Pencil1Icon,
TrashIcon,
PersonIcon,
DotsHorizontalIcon,
LockClosedIcon,
ExclamationTriangleIcon
} from "@radix-ui/react-icons";
import { useState } from "react";
import type { User } from "interface/fields";
// 模拟用户数据
const mockUsers: (User & { id: number })[] = [
{
id: 1,
username: "admin",
email: "admin@example.com",
avatarUrl: "https://api.dicebear.com/7.x/avataaars/svg?seed=1",
role: "admin",
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-03-15"),
lastLoginAt: new Date("2024-03-15"),
passwordHash: "",
},
{
id: 2,
username: "editor",
email: "editor@example.com",
avatarUrl: "https://api.dicebear.com/7.x/avataaars/svg?seed=2",
role: "editor",
createdAt: new Date("2024-02-01"),
updatedAt: new Date("2024-03-14"),
lastLoginAt: new Date("2024-03-14"),
passwordHash: "",
},
{
id: 3,
username: "user",
email: "user@example.com",
avatarUrl: "https://api.dicebear.com/7.x/avataaars/svg?seed=3",
role: "user",
createdAt: new Date("2024-03-01"),
updatedAt: new Date("2024-03-13"),
lastLoginAt: new Date("2024-03-13"),
passwordHash: "",
}
];
export default new Template({}, ({ http, args }) => {
const [searchTerm, setSearchTerm] = useState("");
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [selectedUser, setSelectedUser] = useState<typeof mockUsers[0] | null>(null);
const [newUserData, setNewUserData] = useState({
username: "",
email: "",
password: "",
role: "user"
});
// 获取角色标签样式
const getRoleBadgeColor = (role: string) => {
switch(role) {
case 'admin':
return 'red';
case 'editor':
return 'blue';
default:
return 'gray';
}
};
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]">
{mockUsers.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.ColumnHeaderCell></Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{mockUsers.map((user) => (
<Table.Row key={user.id}>
<Table.Cell>
<Flex align="center" gap="2">
<Avatar
src={user.avatarUrl}
fallback={user.username[0].toUpperCase()}
size="2"
radius="full"
/>
<Text weight="medium">{user.username}</Text>
</Flex>
</Table.Cell>
<Table.Cell>{user.email}</Table.Cell>
<Table.Cell>
<Badge color={getRoleBadgeColor(user.role)}>
{user.role}
</Badge>
</Table.Cell>
<Table.Cell>
{user.createdAt.toLocaleDateString()}
</Table.Cell>
<Table.Cell>
{user.lastLoginAt?.toLocaleDateString()}
</Table.Cell>
<Table.Cell>
<Flex gap="2">
<Button
variant="ghost"
size="1"
onClick={() => {
setSelectedUser(user);
setIsEditDialogOpen(true);
}}
>
<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="4">
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<TextField.Root
placeholder="输入用户名"
value={newUserData.username}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setNewUserData({...newUserData, username: e.target.value})
}
/>
</Box>
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<TextField.Root
placeholder="输入邮箱"
value={newUserData.email}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setNewUserData({...newUserData, email: e.target.value})
}
/>
</Box>
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<TextField.Root
type="password"
placeholder="输入密码"
value={newUserData.password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setNewUserData({...newUserData, password: e.target.value})
}
/>
</Box>
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<select
className="w-full h-9 px-3 rounded-md bg-[--gray-1] border border-[--gray-6] text-[--gray-12]"
value={newUserData.role}
onChange={(e) => setNewUserData({...newUserData, role: e.target.value})}
>
<option value="user"></option>
<option value="editor"></option>
<option value="admin"></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>
{/* 编辑用户对话框 */}
<Dialog.Root open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
<Dialog.Content style={{ maxWidth: 450 }}>
{selectedUser && (
<>
<Dialog.Title></Dialog.Title>
<Dialog.Description size="2" mb="4">
</Dialog.Description>
<Flex direction="column" gap="4">
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<TextField.Root
defaultValue={selectedUser.username}
/>
</Box>
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<TextField.Root
defaultValue={selectedUser.email}
/>
</Box>
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<select
className="w-full h-9 px-3 rounded-md bg-[--gray-1] border border-[--gray-6] text-[--gray-12]"
defaultValue={selectedUser.role}
>
<option value="user"></option>
<option value="editor"></option>
<option value="admin"></option>
</select>
</Box>
<Box>
<Text as="label" size="2" weight="bold" className="block mb-2">
</Text>
<TextField.Root
type="password"
placeholder="留空则不修改"
/>
</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>
);
});