// src/routes/domain.tsx (最终 Bug 修复版) import { addDomain, deleteDomain, syncDomainWHOIS, updateDomain, useDomainList, verifyDomain, } from "@/api/domain" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" // 导入 shadcn/ui 组件 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Textarea } from "@/components/ui/textarea" // 导入 API 类型和函数 import type { BillingDataMod, Domain } from "@/types/domain" import { CheckCircle, Edit, MoreVertical, PlusCircle, RefreshCcw, RefreshCw, Trash2, } from "lucide-react" import { useEffect, useState } from "react" import { toast } from "sonner" import useSWR from "swr" export default function DomainPage() { // --- React State Hooks --- const [domains, setDomains] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isAddModalOpen, setIsAddModalOpen] = useState(false) const [newDomainName, setNewDomainName] = useState("") const [verificationToken, setVerificationToken] = useState("") const [isVerificationInfoModalOpen, setIsVerificationInfoModalOpen] = useState(false) const [isEditModalOpen, setIsEditModalOpen] = useState(false) const [currentDomain, setCurrentDomain] = useState(null) const [editFormData, setEditFormData] = useState>({}) // --- 数据获取 (使用 SWR) --- const { data: domainData, error, mutate, } = useSWR("/api/v1/domains", useDomainList, { revalidateOnFocus: false }) useEffect(() => { if (domainData) { setDomains(domainData) setIsLoading(false) } if (error) { toast.error("无法加载域名列表,请检查后端服务是否正常。") setIsLoading(false) } }, [domainData, error]) const handleAddDomain = async () => { if (!newDomainName) { toast.error("请输入域名") return } try { const response = await addDomain(newDomainName) setVerificationToken(response.VerifyToken) setIsAddModalOpen(false) setIsVerificationInfoModalOpen(true) setNewDomainName("") mutate() } catch (err) { toast.error("添加失败", { description: (err as Error).message }) } } const handleVerify = async (domainId: number) => { try { const response = await verifyDomain(domainId) if (response.success) { toast.success("验证成功", { description: response.message }) } else { toast.warning("验证失败", { description: response.message }) } setTimeout(() => mutate(), 2000) } catch (err) { toast.error("操作失败", { description: (err as Error).message }) } } const handleSyncWhois = async (domainId: number) => { const loadingToast = toast.loading("正在同步 Whois 信息...") try { await syncDomainWHOIS(domainId) toast.success("同步成功", { id: loadingToast, description: "域名 Whois 信息已更新。" }) mutate() } catch (err) { toast.error("同步失败", { id: loadingToast, description: (err as Error).message }) } } const handleDelete = async (domainId: number, domainName: string) => { if (window.confirm(`确定要删除域名 ${domainName} 吗?`)) { try { await deleteDomain(domainId) toast.success("删除成功", { description: `域名 ${domainName} 已被删除。` }) mutate() } catch (err) { toast.error("删除失败", { description: (err as Error).message }) } } } const handlePublicToggle = async (domain: Domain) => { try { await updateDomain(domain.ID, { is_public: !domain.IsPublic, billing_data: domain.BillingData as BillingDataMod, }) toast.success(`域名 ${domain.Domain} 的可见状态已更新`) mutate() } catch (err) { toast.error("更新失败", { description: (err as Error).message }) } } const handleEditClick = (domain: Domain) => { setCurrentDomain(domain) setEditFormData(domain.BillingData || {}) setIsEditModalOpen(true) } const handleEditFormChange = (e: React.ChangeEvent) => { setEditFormData({ ...editFormData, [e.target.name]: e.target.value, }) } const handleUpdateDomain = async () => { if (!currentDomain) return try { const dataToSend = { ...editFormData } if (dataToSend.registeredDate) { dataToSend.registeredDate = new Date(dataToSend.registeredDate).toISOString() } if (dataToSend.endDate) { dataToSend.endDate = new Date(dataToSend.endDate).toISOString() } await updateDomain(currentDomain.ID, { is_public: currentDomain.IsPublic, billing_data: dataToSend as BillingDataMod, }) toast.success("更新成功", { description: `域名 ${currentDomain.Domain} 的配置已保存。`, }) setIsEditModalOpen(false) mutate() } catch (err) { toast.error("更新失败", { description: (err as Error).message }) } } const getStatusVariant = ( status: string, ): "default" | "secondary" | "destructive" | "outline" => { switch (status) { case "verified": return "default" case "pending": return "secondary" case "expired": return "destructive" default: return "outline" } } // --- JSX 渲染 (保持不变) --- return ( <>
域名监控 管理并监控您的域名到期状态。
添加新域名 输入您需要监控的域名,例如 "example.com"。
setNewDomainName(e.target.value)} placeholder="your-domain.com" onKeyUp={(e) => e.key === "Enter" && handleAddDomain()} />
{isLoading ? (
加载中...
) : ( 域名 状态 剩余天数 公开 操作 {domains.map((domain) => ( {domain.Domain} {domain.Status} {domain.expires_in_days ?? "N/A"} handlePublicToggle(domain)} /> {domain.Status === "pending" && ( handleVerify(domain.ID)} > {" "} 验证 )} {domain.Status === "verified" && ( handleSyncWhois(domain.ID) } > {" "} 同步 Whois )} handleEditClick(domain)} > 编辑 handleDelete(domain.ID, domain.Domain) } > 删除 ))}
)}
{/* 验证信息弹窗 */} 请验证域名所有权 为了开始监控,请为您的域名添加一条 DNS TXT 记录。

请将以下内容添加到您的 DNS 解析记录中:

类型: TXT

主机/名称: @

记录值:

{verificationToken}

DNS 记录生效可能需要几分钟到几小时不等。生效后,请回到域名列表点击“验证”。

{/* 编辑弹窗 */} 编辑域名信息 {currentDomain?.Domain}{" "} 添加或修改详细信息。