Initial commit: GovAI 政务AI平台
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import api from "@/lib/api";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
avatar_url?: string;
|
||||
role: string;
|
||||
status: string;
|
||||
employee_id?: string;
|
||||
last_login_at?: string;
|
||||
login_count: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
const roleLabels: Record<string, string> = {
|
||||
super_admin: "平台管理员",
|
||||
admin: "机构管理员",
|
||||
creator: "创作者",
|
||||
user: "普通用户",
|
||||
};
|
||||
|
||||
const roleColors: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
|
||||
super_admin: "destructive",
|
||||
admin: "default",
|
||||
creator: "secondary",
|
||||
user: "outline",
|
||||
};
|
||||
|
||||
export default function UsersPage() {
|
||||
const [search, setSearch] = useState("");
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: ["adminUsers", search],
|
||||
queryFn: () => api.get<{ items: User[] }>(`/api/v1/admin/users?q=${search}`),
|
||||
});
|
||||
|
||||
const updateRole = useMutation({
|
||||
mutationFn: ({ id, role }: { id: string; role: string }) =>
|
||||
api.put(`/api/v1/admin/users/${id}/role`, { role }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["adminUsers"] });
|
||||
toast.success("角色更新成功");
|
||||
},
|
||||
onError: (err: Error) => toast.error(err.message),
|
||||
});
|
||||
|
||||
const updateStatus = useMutation({
|
||||
mutationFn: ({ id, status }: { id: string; status: string }) =>
|
||||
api.put(`/api/v1/admin/users/${id}/status`, { status }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["adminUsers"] });
|
||||
toast.success("状态更新成功");
|
||||
},
|
||||
onError: (err: Error) => toast.error(err.message),
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-2xl font-bold">用户管理</h1>
|
||||
<Input
|
||||
placeholder="搜索姓名或邮箱..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-64"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50">
|
||||
<tr>
|
||||
<th className="text-left p-3">用户</th>
|
||||
<th className="text-left p-3">角色</th>
|
||||
<th className="text-left p-3">状态</th>
|
||||
<th className="text-left p-3">登录次数</th>
|
||||
<th className="text-left p-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.items?.map((user) => (
|
||||
<tr key={user.id} className="border-t">
|
||||
<td className="p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage src={user.avatar_url} />
|
||||
<AvatarFallback>{user.name.charAt(0)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">{user.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{user.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<Badge variant={roleColors[user.role]}>
|
||||
{roleLabels[user.role]}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<Badge variant={user.status === "active" ? "default" : "destructive"}>
|
||||
{user.status === "active" ? "正常" : "禁用"}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-3 text-muted-foreground">{user.login_count}</td>
|
||||
<td className="p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
defaultValue={user.role}
|
||||
onValueChange={(role) => role && updateRole.mutate({ id: user.id, role })}
|
||||
>
|
||||
<SelectTrigger className="w-28 h-8">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="user">普通用户</SelectItem>
|
||||
<SelectItem value="creator">创作者</SelectItem>
|
||||
<SelectItem value="admin">管理员</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
updateStatus.mutate({
|
||||
id: user.id,
|
||||
status: user.status === "active" ? "disabled" : "active",
|
||||
})
|
||||
}
|
||||
>
|
||||
{user.status === "active" ? "禁用" : "启用"}
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user