import { useState, useEffect } from 'react'; import { Button } from "@heroui/button"; import { Card, CardBody, CardHeader } from "@heroui/card"; import { Input } from "@heroui/input"; import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from "@heroui/table"; import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure } from "@heroui/modal"; import { Chip } from "@heroui/chip"; import { Pagination } from "@heroui/pagination"; import { Select, SelectItem } from "@heroui/select"; import { RadioGroup, Radio } from "@heroui/radio"; import { DatePicker } from "@heroui/date-picker"; import { Spinner } from "@heroui/spinner"; import { Progress } from "@heroui/progress"; import { Tabs, Tab } from "@heroui/tabs"; import toast from 'react-hot-toast'; import { User, UserForm, UserTunnel, UserTunnelForm, Tunnel, SpeedLimit, Pagination as PaginationType } from '@/types'; import { getAllUsers, createUser, updateUser, deleteUser, getTunnelList, assignUserTunnel, getUserTunnelList, removeUserTunnel, updateUserTunnel, getSpeedLimitList } from '@/api'; import { SearchIcon, PlusIcon, EditIcon, DeleteIcon, UserIcon, SettingsIcon } from '@/components/icons'; import { parseDate } from "@internationalized/date"; import AdminLayout from "@/layouts/admin"; // 工具函数 const formatFlow = (value: number, unit: string = 'bytes'): string => { if (unit === 'gb') { return `${value} GB`; } else { if (value === 0) return '0 B'; if (value < 1024) return `${value} B`; if (value < 1024 * 1024) return `${(value / 1024).toFixed(2)} KB`; if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(2)} MB`; return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GB`; } }; const formatDate = (timestamp: number): string => { return new Date(timestamp).toLocaleString(); }; const getExpireStatus = (expTime: number) => { const now = Date.now(); if (expTime < now) { return { color: 'danger' as const, text: '已过期' }; } const diffDays = Math.ceil((expTime - now) / (1000 * 60 * 60 * 24)); if (diffDays <= 7) { return { color: 'warning' as const, text: `${diffDays}天后过期` }; } return { color: 'success' as const, text: '正常' }; }; // 获取用户状态(根据status字段) const getUserStatus = (user: User) => { if (user.status === 1) { return { color: 'success' as const, text: '正常' }; } else { return { color: 'danger' as const, text: '禁用' }; } }; const calculateUserTotalUsedFlow = (user: User): number => { return (user.inFlow || 0) + (user.outFlow || 0); }; const calculateTunnelUsedFlow = (tunnel: UserTunnel): number => { const inFlow = tunnel.inFlow || 0; const outFlow = tunnel.outFlow || 0; if (tunnel.tunnelFlow === 1) { return outFlow; // 单向计算:只计算上传流量 } else { return inFlow + outFlow; // 双向计算或默认:计算上传和下载的总流量 } }; export default function UserPage() { // 状态管理 const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); const [searchKeyword, setSearchKeyword] = useState(''); const [pagination, setPagination] = useState({ current: 1, size: 10, total: 0 }); // 用户表单相关状态 const { isOpen: isUserModalOpen, onOpen: onUserModalOpen, onClose: onUserModalClose } = useDisclosure(); const [isEdit, setIsEdit] = useState(false); const [userForm, setUserForm] = useState({ user: '', pwd: '', status: 1, flow: 100, num: 10, expTime: null, flowResetTime: 0 }); const [userFormLoading, setUserFormLoading] = useState(false); // 隧道权限管理相关状态 const { isOpen: isTunnelModalOpen, onOpen: onTunnelModalOpen, onClose: onTunnelModalClose } = useDisclosure(); const [currentUser, setCurrentUser] = useState(null); const [userTunnels, setUserTunnels] = useState([]); const [tunnelListLoading, setTunnelListLoading] = useState(false); // 分配新隧道权限相关状态 const [tunnelForm, setTunnelForm] = useState({ tunnelId: null, flow: 100, num: 10, expTime: null, flowResetTime: 0, speedId: null }); const [assignLoading, setAssignLoading] = useState(false); // 编辑隧道权限相关状态 const { isOpen: isEditTunnelModalOpen, onOpen: onEditTunnelModalOpen, onClose: onEditTunnelModalClose } = useDisclosure(); const [editTunnelForm, setEditTunnelForm] = useState(null); const [editTunnelLoading, setEditTunnelLoading] = useState(false); // 删除确认相关状态 const { isOpen: isDeleteModalOpen, onOpen: onDeleteModalOpen, onClose: onDeleteModalClose } = useDisclosure(); const [userToDelete, setUserToDelete] = useState(null); // 删除隧道权限确认相关状态 const { isOpen: isDeleteTunnelModalOpen, onOpen: onDeleteTunnelModalOpen, onClose: onDeleteTunnelModalClose } = useDisclosure(); const [tunnelToDelete, setTunnelToDelete] = useState(null); // 其他数据 const [tunnels, setTunnels] = useState([]); const [speedLimits, setSpeedLimits] = useState([]); // 生命周期 useEffect(() => { loadUsers(); loadTunnels(); loadSpeedLimits(); }, [pagination.current, pagination.size, searchKeyword]); // 数据加载函数 const loadUsers = async () => { setLoading(true); try { const response = await getAllUsers({ current: pagination.current, size: pagination.size, keyword: searchKeyword }); if (response.code === 0) { const data = response.data || {}; setUsers(data.records || []); setPagination(prev => ({ ...prev, total: data.total || 0, current: data.current || 1, size: data.size || 10 })); } else { toast.error(response.msg || '获取用户列表失败'); } } catch (error) { toast.error('获取用户列表失败'); } finally { setLoading(false); } }; const loadTunnels = async () => { try { const response = await getTunnelList(); if (response.code === 0) { setTunnels(response.data || []); } } catch (error) { console.error('获取隧道列表失败:', error); } }; const loadSpeedLimits = async () => { try { const response = await getSpeedLimitList(); if (response.code === 0) { setSpeedLimits(response.data || []); } } catch (error) { console.error('获取限速规则列表失败:', error); } }; const loadUserTunnels = async (userId: number) => { setTunnelListLoading(true); try { const response = await getUserTunnelList({ userId }); if (response.code === 0) { setUserTunnels(response.data || []); } else { toast.error(response.msg || '获取隧道权限列表失败'); } } catch (error) { toast.error('获取隧道权限列表失败'); } finally { setTunnelListLoading(false); } }; // 用户管理操作 const handleSearch = () => { setPagination(prev => ({ ...prev, current: 1 })); loadUsers(); }; const handleAdd = () => { setIsEdit(false); setUserForm({ user: '', pwd: '', status: 1, flow: 100, num: 10, expTime: null, flowResetTime: 0 }); onUserModalOpen(); }; const handleEdit = (user: User) => { setIsEdit(true); setUserForm({ id: user.id, name: user.name, user: user.user, pwd: '', status: user.status, flow: user.flow, num: user.num, expTime: user.expTime ? new Date(user.expTime) : null, flowResetTime: user.flowResetTime ?? 0 }); onUserModalOpen(); }; const handleDelete = (user: User) => { setUserToDelete(user); onDeleteModalOpen(); }; const handleConfirmDelete = async () => { if (!userToDelete) return; try { const response = await deleteUser(userToDelete.id); if (response.code === 0) { toast.success('删除成功'); loadUsers(); onDeleteModalClose(); setUserToDelete(null); } else { toast.error(response.msg || '删除失败'); } } catch (error) { toast.error('删除失败'); } }; const handleSubmitUser = async () => { if (!userForm.user || (!userForm.pwd && !isEdit) || !userForm.expTime) { toast.error('请填写完整信息'); return; } setUserFormLoading(true); try { const submitData: any = { ...userForm, expTime: userForm.expTime.getTime() }; if (isEdit && !submitData.pwd) { delete submitData.pwd; } const response = isEdit ? await updateUser(submitData) : await createUser(submitData); if (response.code === 0) { toast.success(isEdit ? '更新成功' : '创建成功'); onUserModalClose(); loadUsers(); } else { toast.error(response.msg || (isEdit ? '更新失败' : '创建失败')); } } catch (error) { toast.error(isEdit ? '更新失败' : '创建失败'); } finally { setUserFormLoading(false); } }; // 隧道权限管理操作 const handleManageTunnels = (user: User) => { setCurrentUser(user); setTunnelForm({ tunnelId: null, flow: 100, num: 10, expTime: null, flowResetTime: 0, speedId: null }); onTunnelModalOpen(); loadUserTunnels(user.id); }; const handleAssignTunnel = async () => { if (!tunnelForm.tunnelId || !tunnelForm.expTime || !currentUser) { toast.error('请填写完整信息'); return; } setAssignLoading(true); try { const response = await assignUserTunnel({ userId: currentUser.id, tunnelId: tunnelForm.tunnelId, flow: tunnelForm.flow, num: tunnelForm.num, expTime: tunnelForm.expTime.getTime(), flowResetTime: tunnelForm.flowResetTime, speedId: tunnelForm.speedId }); if (response.code === 0) { toast.success('分配成功'); setTunnelForm({ tunnelId: null, flow: 100, num: 10, expTime: null, flowResetTime: 0, speedId: null }); loadUserTunnels(currentUser.id); } else { toast.error(response.msg || '分配失败'); } } catch (error) { toast.error('分配失败'); } finally { setAssignLoading(false); } }; const handleEditTunnel = (userTunnel: UserTunnel) => { setEditTunnelForm({ ...userTunnel, expTime: userTunnel.expTime }); onEditTunnelModalOpen(); }; const handleUpdateTunnel = async () => { if (!editTunnelForm) return; setEditTunnelLoading(true); try { const response = await updateUserTunnel({ id: editTunnelForm.id, flow: editTunnelForm.flow, num: editTunnelForm.num, expTime: editTunnelForm.expTime, flowResetTime: editTunnelForm.flowResetTime, speedId: editTunnelForm.speedId }); if (response.code === 0) { toast.success('更新成功'); onEditTunnelModalClose(); if (currentUser) { loadUserTunnels(currentUser.id); } } else { toast.error(response.msg || '更新失败'); } } catch (error) { toast.error('更新失败'); } finally { setEditTunnelLoading(false); } }; const handleRemoveTunnel = (userTunnel: UserTunnel) => { setTunnelToDelete(userTunnel); onDeleteTunnelModalOpen(); }; const handleConfirmRemoveTunnel = async () => { if (!tunnelToDelete) return; try { const response = await removeUserTunnel({ id: tunnelToDelete.id }); if (response.code === 0) { toast.success('删除成功'); if (currentUser) { loadUserTunnels(currentUser.id); } onDeleteTunnelModalClose(); setTunnelToDelete(null); } else { toast.error(response.msg || '删除失败'); } } catch (error) { toast.error('删除失败'); } }; // 过滤数据 const availableTunnels = tunnels.filter( tunnel => !userTunnels.some(ut => ut.tunnelId === tunnel.id) ); const availableSpeedLimits = speedLimits.filter( speedLimit => speedLimit.tunnelId === tunnelForm.tunnelId ); const editAvailableSpeedLimits = speedLimits.filter( speedLimit => speedLimit.tunnelId === editTunnelForm?.tunnelId ); return (
{/* 页面头部 */}

用户管理

setSearchKeyword(e.target.value)} placeholder="搜索用户名" startContent={} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} className="flex-1" classNames={{ base: "bg-default-100", input: "bg-transparent", inputWrapper: "bg-default-100 border-2 border-default-200 hover:border-default-300 focus-within:border-primary data-[hover=true]:border-default-300" }} />
{/* 用户列表 */} {loading ? (
正在加载...
) : users.length === 0 ? (

暂无用户数据

还没有创建任何用户,点击上方按钮开始创建

) : (
{users.map((user) => { const userStatus = getUserStatus(user); const expStatus = user.expTime ? getExpireStatus(user.expTime) : null; const usedFlow = calculateUserTotalUsedFlow(user); const flowPercent = user.flow > 0 ? Math.min((usedFlow / (user.flow * 1024 * 1024 * 1024)) * 100, 100) : 0; return (

{user.name || user.user}

@{user.user}

{userStatus.text}
{/* 流量信息 */}
流量限制 {formatFlow(user.flow, 'gb')}
已使用 {formatFlow(usedFlow)}
90 ? 'danger' : flowPercent > 70 ? 'warning' : 'success'} className="mt-1" aria-label={`流量使用 ${flowPercent.toFixed(1)}%`} />
{/* 其他信息 */}
转发数量 {user.num}
重置日期 {user.flowResetTime === 0 ? '不重置' : `每月${user.flowResetTime}号`}
{user.expTime && (
过期时间
{formatDate(user.expTime)}
{expStatus && ( {expStatus.text} )}
)}
); })}
)} {/* 分页 */} {pagination.total > 0 && (
setPagination(prev => ({ ...prev, current: page }))} showControls showShadow />
)} {/* 用户表单模态框 */} {isEdit ? '编辑用户' : '新增用户'}
setUserForm(prev => ({ ...prev, user: e.target.value }))} isRequired /> setUserForm(prev => ({ ...prev, pwd: e.target.value }))} placeholder={isEdit ? '留空则不修改密码' : '请输入密码'} isRequired={!isEdit} /> { const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); setUserForm(prev => ({ ...prev, flow: value })); }} min="1" max="99999" isRequired /> { const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); setUserForm(prev => ({ ...prev, num: value })); }} min="1" max="99999" isRequired /> { if (date) { const jsDate = new Date(date.year, date.month - 1, date.day, 23, 59, 59); setUserForm(prev => ({ ...prev, expTime: jsDate })); } else { setUserForm(prev => ({ ...prev, expTime: null })); } }} isRequired showMonthAndYearPickers className="cursor-pointer" />
setUserForm(prev => ({ ...prev, status: Number(value) }))} orientation="horizontal" > 正常 禁用
{/* 隧道权限管理模态框 */} 用户 {currentUser?.user} 的隧道权限管理
{ const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); setTunnelForm(prev => ({ ...prev, flow: value })); }} min="1" max="99999" /> { const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); setTunnelForm(prev => ({ ...prev, num: value })); }} min="1" max="99999" /> { if (date) { const jsDate = new Date(date.year, date.month - 1, date.day, 23, 59, 59); setTunnelForm(prev => ({ ...prev, expTime: jsDate })); } else { setTunnelForm(prev => ({ ...prev, expTime: null })); } }} showMonthAndYearPickers className="cursor-pointer" />
隧道名称 流量统计 转发数量 限速规则 重置时间 到期时间 操作 } emptyContent="暂无隧道权限" > {(userTunnel) => ( {userTunnel.tunnelName}
限制: {formatFlow(userTunnel.flow, 'gb')}
已用: {formatFlow(calculateTunnelUsedFlow(userTunnel))}
{userTunnel.num} {userTunnel.speedLimitName || '不限速'} {userTunnel.flowResetTime === 0 ? '不重置' : `每月${userTunnel.flowResetTime}号`} {formatDate(userTunnel.expTime)}
)}
{/* 编辑隧道权限模态框 */} 编辑隧道权限 - {editTunnelForm?.tunnelName} {editTunnelForm && (
{ const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); setEditTunnelForm(prev => prev ? { ...prev, flow: value } : null); }} min="1" max="99999" /> { const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); setEditTunnelForm(prev => prev ? { ...prev, num: value } : null); }} min="1" max="99999" /> { if (date) { const jsDate = new Date(date.year, date.month - 1, date.day, 23, 59, 59); setEditTunnelForm(prev => prev ? { ...prev, expTime: jsDate.getTime() } : null); } else { setEditTunnelForm(prev => prev ? { ...prev, expTime: Date.now() } : null); } }} showMonthAndYearPickers className="cursor-pointer" isRequired />
)}
{/* 删除确认对话框 */} 确认删除用户

确定要删除用户 "{userToDelete?.user}" 吗?

此操作不可撤销,用户的所有数据将被永久删除。

{/* 删除隧道权限确认对话框 */} 确认删除隧道权限

确定要删除用户 {currentUser?.user} 对隧道 "{tunnelToDelete?.tunnelName}" 的权限吗?

删除后该用户将无法使用此隧道创建转发,此操作不可撤销。

); }