import { useState, useEffect, useMemo } from "react"; import toast from "react-hot-toast"; import { LayoutGrid, List } from "lucide-react"; import { AnimatedPage, StaggerList, StaggerItem, } from "@/components/animated-page"; import { SearchBar } from "@/components/search-bar"; import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card"; import { Button } from "@/shadcn-bridge/heroui/button"; import { Input } from "@/shadcn-bridge/heroui/input"; import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, } from "@/shadcn-bridge/heroui/modal"; import { Chip } from "@/shadcn-bridge/heroui/chip"; import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, } from "@/shadcn-bridge/heroui/table"; import { createSpeedLimit, getSpeedLimitList, updateSpeedLimit, deleteSpeedLimit, } from "@/api"; import { PageLoadingState } from "@/components/page-state"; import { useLocalStorageState } from "@/hooks/use-local-storage-state"; interface SpeedLimitRule { id: number; name: string; speed: number; status: number; createdTime: string; updatedTime: string; } interface SpeedLimitForm { id?: number; name: string; speed: number; status: number; } export default function LimitPage() { const [loading, setLoading] = useState(true); const [rules, setRules] = useState([]); const [searchKeyword, setSearchKeyword] = useLocalStorageState( "limit-search-keyword", "", ); const [isSearchVisible, setIsSearchVisible] = useState(false); const [viewMode, setViewMode] = useLocalStorageState<"list" | "grid">( "limit-view-mode", "grid", ); const filteredRules = useMemo(() => { if (!searchKeyword.trim()) return rules; const lowerKeyword = searchKeyword.toLowerCase(); return rules.filter( (r) => r.name && r.name.toLowerCase().includes(lowerKeyword), ); }, [rules, searchKeyword]); // 模态框状态 const [modalOpen, setModalOpen] = useState(false); const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [isEdit, setIsEdit] = useState(false); const [submitLoading, setSubmitLoading] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); const [ruleToDelete, setRuleToDelete] = useState(null); // 表单状态 const [form, setForm] = useState({ name: "", speed: 100, status: 1, }); // 表单验证错误 const [errors, setErrors] = useState<{ [key: string]: string }>({}); useEffect(() => { loadData(); }, []); // 加载所有数据 const loadData = async () => { setLoading(true); try { const rulesRes = await getSpeedLimitList(); if (rulesRes.code === 0) { setRules(rulesRes.data || []); } else { toast.error(rulesRes.msg || "获取限速规则失败"); } } catch { toast.error("加载数据失败"); } finally { setLoading(false); } }; // 表单验证 const validateForm = (): boolean => { const newErrors: { [key: string]: string } = {}; if (!form.name.trim()) { newErrors.name = "请输入规则名称"; } else if (form.name.length < 2 || form.name.length > 50) { newErrors.name = "规则名称长度应在2-50个字符之间"; } if (!form.speed || form.speed < 1) { newErrors.speed = "请输入有效的速度限制(≥1 Mbps)"; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // 新增规则 const handleAdd = () => { setIsEdit(false); setForm({ name: "", speed: 100, status: 1, }); setErrors({}); setModalOpen(true); }; // 编辑规则 const handleEdit = (rule: SpeedLimitRule) => { setIsEdit(true); setForm({ id: rule.id, name: rule.name, speed: rule.speed, status: rule.status, }); setErrors({}); setModalOpen(true); }; // 显示删除确认 const handleDelete = (rule: SpeedLimitRule) => { setRuleToDelete(rule); setDeleteModalOpen(true); }; // 确认删除规则 const confirmDelete = async () => { if (!ruleToDelete) return; setDeleteLoading(true); try { const res = await deleteSpeedLimit(ruleToDelete.id); if (res.code === 0) { toast.success("删除成功"); setDeleteModalOpen(false); loadData(); } else { toast.error(res.msg || "删除失败"); } } catch { toast.error("删除失败"); } finally { setDeleteLoading(false); } }; // 提交表单 const handleSubmit = async () => { if (!validateForm()) return; setSubmitLoading(true); try { let res: { code: number; msg: string }; const payload = { id: form.id, name: form.name, speed: form.speed, status: form.status, }; if (isEdit) { res = await updateSpeedLimit(payload); } else { const createData = { name: payload.name, speed: payload.speed, status: payload.status, }; res = await createSpeedLimit(createData); } if (res.code === 0) { toast.success(isEdit ? "修改成功" : "创建成功"); setModalOpen(false); loadData(); } else { toast.error(res.msg || "操作失败"); } } catch { toast.error("操作失败"); } finally { setSubmitLoading(false); } }; if (loading) { return ; } return (
setIsSearchVisible(false)} onOpen={() => setIsSearchVisible(true)} />
{/* 统一卡片网格 */} {filteredRules.length > 0 ? ( viewMode === "list" ? ( 规则名称 速度限制 操作 {(rule) => (
{rule.name}
{rule.speed} Mbps
)}
) : ( {filteredRules.map((rule) => (

{rule.name}

{rule.status === 1 ? "运行" : "异常"}
速度限制 {rule.speed} Mbps
))}
) ) : ( /* 空状态 */

暂无限速规则

还没有创建任何限速规则,点击上方按钮开始创建

)} {/* 新增/编辑模态框 */} {(onClose) => ( <>

{isEdit ? "编辑限速规则" : "新增限速规则"}

{isEdit ? "修改现有限速规则的配置信息" : "创建新的限速规则"}

setForm((prev) => ({ ...prev, name: e.target.value })) } /> Mbps
} errorMessage={errors.speed} isInvalid={!!errors.speed} label="速度限制" placeholder="请输入速度限制" type="number" value={form.speed.toString()} variant="bordered" onChange={(e) => setForm((prev) => ({ ...prev, speed: parseInt(e.target.value) || 0, })) } />
)}
{/* 删除确认模态框 */} {(onClose) => ( <>

确认删除

确定要删除限速规则{" "} "{ruleToDelete?.name}" {" "} 吗?

此操作无法撤销,删除后该规则将永久消失。

)}
); }