2026-03-17 13:17:02 +08:00

381 lines
13 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { DashboardShell } from "@/components/dashboard-shell"
import { DashboardHeader } from "@/components/dashboard-header"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Search, Edit, Loader2 } from "lucide-react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { UserDetailDialog } from "@/components/users/user-detail-dialog"
import { UserFormDialog } from "@/components/users/user-form-dialog"
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"
import { useToast } from "@/components/ui/use-toast"
import { usersApi, rolesApi, handleApiError } from "@/lib/api"
export default function UsersPage() {
const [users, setUsers] = useState([])
const [roles, setRoles] = useState([])
const [searchQuery, setSearchQuery] = useState("")
const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
const [totalUsers, setTotalUsers] = useState(0)
const [editingUser, setEditingUser] = useState(null)
const [isLoading, setIsLoading] = useState(true)
const { toast } = useToast()
const itemsPerPage = 5
// 获取用户列表
const fetchUsers = async () => {
setIsLoading(true)
try {
const response = await usersApi.getUsers({
page: currentPage,
pageSize: itemsPerPage,
search: searchQuery,
})
setUsers(response.items)
setTotalPages(response.totalPages)
setTotalUsers(response.total)
} catch (error) {
toast({
title: "获取用户列表失败",
description: handleApiError(error),
variant: "destructive",
})
} finally {
setIsLoading(false)
}
}
// 获取角色列表
const fetchRoles = async () => {
try {
const response = await rolesApi.getRoles()
setRoles(response.items)
} catch (error) {
toast({
title: "获取角色列表失败",
description: handleApiError(error),
variant: "destructive",
})
}
}
// 初始加载
useEffect(() => {
fetchRoles()
}, [])
// 当页码、搜索条件变化时重新获取数据
useEffect(() => {
fetchUsers()
}, [currentPage, searchQuery])
// 添加用户
const handleAddUser = async (data) => {
try {
await usersApi.createUser({
name: data.name,
email: data.email,
role: data.role,
status: data.status,
phone: data.phone,
address: data.address,
})
toast({
title: "用户创建成功",
description: `用户 "${data.name}" 已成功创建`,
variant: "default",
})
fetchUsers() // 刷新用户列表
} catch (error) {
toast({
title: "创建用户失败",
description: handleApiError(error),
variant: "destructive",
})
throw error // 重新抛出错误,让表单组件处理
}
}
// 编辑用户
const handleEditUser = async (data) => {
if (!editingUser) return
try {
await usersApi.updateUser(editingUser.id, {
name: data.name,
email: data.email,
role: data.role,
status: data.status,
phone: data.phone,
address: data.address,
})
toast({
title: "用户更新成功",
description: `用户 "${data.name}" 已成功更新`,
variant: "default",
})
setEditingUser(null)
fetchUsers() // 刷新用户列表
} catch (error) {
toast({
title: "更新用户失败",
description: handleApiError(error),
variant: "destructive",
})
throw error // 重新抛出错误,让表单组件处理
}
}
// 删除用户
const handleDeleteUser = async (userId) => {
try {
await usersApi.deleteUser(userId)
toast({
title: "用户删除成功",
description: "用户已成功删除",
variant: "default",
})
fetchUsers() // 刷新用户列表
} catch (error) {
toast({
title: "删除用户失败",
description: handleApiError(error),
variant: "destructive",
})
}
}
// 开始编辑用户
const startEditUser = (user) => {
setEditingUser(user)
}
// 获取头像颜色
const getAvatarColor = (name) => {
const colors = [
"from-pink-500 to-purple-500",
"from-blue-500 to-teal-500",
"from-red-500 to-orange-500",
"from-green-500 to-emerald-500",
"from-purple-500 to-indigo-500",
]
// Simple hash function to pick a color based on name
const hash = name.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)
return colors[hash % colors.length]
}
// 获取用户名首字母
const getInitials = (name) => {
return name
.split(" ")
.map((part) => part[0])
.join("")
.toUpperCase()
.substring(0, 2)
}
return (
<DashboardShell>
<DashboardHeader heading="用户管理" text="管理系统用户和权限">
<UserFormDialog
mode="add"
onSubmit={handleAddUser}
roles={roles.map((role) => ({ id: role.id, name: role.name }))}
/>
</DashboardHeader>
<div className="flex items-center justify-between space-y-2 mb-6">
<div className="flex items-center space-x-2">
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="搜索用户..."
className="w-[300px] pl-8 border-none bg-white shadow-md focus-visible:ring-pink-500"
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value)
setCurrentPage(1) // 重置到第一页
}}
/>
</div>
</div>
</div>
<Card className="border-none shadow-lg bg-gradient-to-br from-white to-purple-50">
<CardHeader>
<CardTitle className="text-xl font-bold flex items-center">
<span className="bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600"></span>
<div className="ml-2 h-1 w-10 bg-gradient-to-r from-purple-600 to-pink-600 rounded-full"></div>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader className="bg-gray-50">
<TableRow>
<TableHead className="w-[50px]"></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={8} className="h-24 text-center">
<div className="flex justify-center items-center">
<Loader2 className="h-6 w-6 animate-spin text-purple-500 mr-2" />
<span>...</span>
</div>
</TableCell>
</TableRow>
) : users.length === 0 ? (
<TableRow>
<TableCell colSpan={8} className="h-24 text-center text-muted-foreground">
</TableCell>
</TableRow>
) : (
users.map((user) => (
<TableRow key={user.id} className="hover:bg-gray-50 transition-colors">
<TableCell>
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className={`bg-gradient-to-br ${getAvatarColor(user.name)} text-white`}>
{getInitials(user.name)}
</AvatarFallback>
</Avatar>
</TableCell>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Badge
className={
user.role === "超级管理员"
? "bg-purple-500"
: user.role === "内容管理员"
? "bg-blue-500"
: user.role === "AI模型管理员"
? "bg-teal-500"
: user.role === "卡牌管理员"
? "bg-pink-500"
: "bg-orange-500"
}
>
{user.role}
</Badge>
</TableCell>
<TableCell>
<Badge
className={
user.status === "活跃"
? "bg-green-500"
: user.status === "未激活"
? "bg-gray-500"
: "bg-red-500"
}
>
{user.status}
</Badge>
</TableCell>
<TableCell>{user.registeredAt}</TableCell>
<TableCell>{user.lastLogin || "-"}</TableCell>
<TableCell className="text-right">
<UserDetailDialog user={user} onEdit={() => startEditUser(user)} />
<Button
variant="ghost"
size="icon"
className="hover:bg-pink-50 hover:text-pink-600"
onClick={() => startEditUser(user)}
>
<Edit className="h-4 w-4" />
</Button>
{user.role !== "超级管理员" && (
<DeleteConfirmationDialog
title="删除用户"
description="此操作将永久删除该用户,且无法恢复。"
itemName={user.name}
onDelete={() => handleDeleteUser(user.id)}
/>
)}
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
<CardFooter className="flex justify-between">
<div className="text-sm text-muted-foreground">
{users.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0}-
{Math.min(currentPage * itemsPerPage, totalUsers)} {totalUsers}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
className="hover:bg-pink-50 hover:text-pink-700 transition-all duration-200"
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
disabled={currentPage === 1 || isLoading}
>
</Button>
<Button
variant="outline"
size="sm"
className="hover:bg-pink-50 hover:text-pink-700 transition-all duration-200"
onClick={() => setCurrentPage((prev) => Math.min(prev + 1, totalPages))}
disabled={currentPage === totalPages || totalPages === 0 || isLoading}
>
</Button>
</div>
</CardFooter>
</Card>
{/* 编辑用户对话框 */}
{editingUser && (
<UserFormDialog
mode="edit"
open={!!editingUser}
onOpenChange={(open) => {
if (!open) setEditingUser(null)
}}
onSubmit={handleEditUser}
defaultValues={{
name: editingUser.name,
email: editingUser.email,
role: editingUser.role,
status: editingUser.status,
phone: editingUser.phone,
address: editingUser.address,
}}
roles={roles.map((role) => ({ id: role.id, name: role.name }))}
/>
)}
</DashboardShell>
)
}