// src/routes/domain.tsx (最终 Bug 修复版) import { useState, useEffect } from 'react' import { PlusCircle, RefreshCw, MoreVertical, Trash2, Edit, CheckCircle, RefreshCcw } from 'lucide-react' // 导入 shadcn/ui 组件 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Label } from '@/components/ui/label' import { Switch } from "@/components/ui/switch" import { toast } from 'sonner' // 导入 API 类型和函数 import type { Domain, BillingDataMod } from '@/types/api' import { useDomainList, addDomain, verifyDomain, deleteDomain, updateDomain, syncDomainWHOIS } from '@/api/domain' 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} 添加或修改详细信息。