import { FormEvent, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { Globe, Plus, Trash2, Wifi, RefreshCw, X, Edit3, CheckCircle, XCircle, HelpCircle } from 'lucide-react' import { sitesAPI } from '../api/sites' import type { Site } from '../types' // ── 站点类型映射 ── const SITE_TYPE_LABELS: Record = { nexusphp: 'NexusPHP', gazelle: 'Gazelle', unit3d: 'UNIT3D', mteam: 'M-Team', discuz: 'Discuz', custom_rss: '自定义 RSS', } const SITE_TYPE_ABBR: Record = { nexusphp: 'NP', gazelle: 'GZ', unit3d: 'U3', mteam: 'MT', discuz: 'DZ', custom_rss: 'RS', } const SITE_TYPE_COLORS: Record = { nexusphp: 'bg-blue-500/15 text-blue-400', gazelle: 'bg-purple-500/15 text-purple-400', unit3d: 'bg-orange-500/15 text-orange-400', mteam: 'bg-green-500/15 text-green-400', discuz: 'bg-yellow-500/15 text-yellow-400', custom_rss: 'bg-slate-500/15 text-slate-400', } const AUTH_TYPE_LABELS: Record = { cookie: 'Cookie', api_key: 'API Key', auth_header: 'Auth Header', } // ── 默认表单 ── const defaultForm = () => ({ name: '', url: '', type: 'nexusphp', auth_type: 'cookie', cookie: '', api_key: '', auth_header: '', enabled: true, is_default: false, extra: '', // 高级设置 user_agent: '', rss_url: '', timeout: 15, priority: 50, use_proxy: false, rate_limit: false, browser_emulation: false, downloader: '', }) export function SitesPage() { const [sites, setSites] = useState([]) const [loading, setLoading] = useState(true) const [showModal, setShowModal] = useState(false) const [editingId, setEditingId] = useState(null) const [form, setForm] = useState(defaultForm()) const [saving, setSaving] = useState(false) const [testingId, setTestingId] = useState(null) const [advancedOpen, setAdvancedOpen] = useState(false) const loadSites = async () => { setLoading(true) try { const res = await sitesAPI.list() setSites(Array.isArray(res.data) ? res.data : []) } catch { toast.error('加载站点列表失败') } finally { setLoading(false) } } useEffect(() => { loadSites() }, []) // ── 弹窗操作 ── const openCreate = () => { setEditingId(null) setForm(defaultForm()) setAdvancedOpen(false) setShowModal(true) } const openEdit = async (id: string) => { try { const res = await sitesAPI.get(id) const s = res.data as Site setEditingId(id) setForm({ name: s.name || '', url: s.url || '', type: s.type || 'nexusphp', auth_type: s.auth_type || 'cookie', cookie: s.cookie || '', api_key: s.api_key || '', auth_header: s.auth_header || '', enabled: s.enabled !== false, is_default: s.is_default || false, extra: s.extra || '', // 高级设置 user_agent: s.user_agent || '', rss_url: s.rss_url || '', timeout: s.timeout ?? 15, priority: s.priority ?? 50, use_proxy: s.use_proxy || false, rate_limit: s.rate_limit || false, browser_emulation: s.browser_emulation || false, downloader: s.downloader || '', }) setAdvancedOpen(false) setShowModal(true) } catch { toast.error('获取站点详情失败') } } const closeModal = () => { setShowModal(false) setEditingId(null) } // ── 保存(支持静默模式)── const silentSave = async (): Promise => { if (!form.name.trim() || !form.url.trim()) return false const payload: Record = { name: form.name.trim(), url: form.url.trim(), type: form.type, auth_type: form.auth_type, cookie: form.cookie || '', api_key: form.api_key || '', auth_header: form.auth_header || '', enabled: form.enabled, is_default: form.is_default, extra: form.extra || '', user_agent: form.user_agent || '', rss_url: form.rss_url || '', timeout: Number(form.timeout) || 15, priority: Number(form.priority) || 50, use_proxy: !!form.use_proxy, rate_limit: !!form.rate_limit, browser_emulation: !!form.browser_emulation, downloader: form.downloader || '', } try { if (editingId) { await sitesAPI.update(editingId, payload) } else { const res = await sitesAPI.create(payload) setEditingId((res.data as Site)?.id ?? null) } return true } catch { return false } } const handleSave = async (e: FormEvent) => { e.preventDefault() setSaving(true) const ok = await silentSave() if (ok) { toast.success(editingId ? '站点已更新' : '站点已添加') closeModal() await loadSites() } else { const msg = '保存失败' toast.error(msg) } setSaving(false) } // 测试(先自动保存) const handleTest = async (id: string) => { // 如果正在编辑当前站点,先保存 if (editingId === id) { setSaving(true) const ok = await silentSave() setSaving(false) if (!ok) { toast.error('保存失败,无法测试') return } } setTestingId(id) try { const res = await sitesAPI.test(id) const msg = res?.message || '连接测试成功' toast.success(msg) } catch (err: unknown) { const msg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message ?? '连接测试失败' toast.error(msg) } finally { setTestingId(null) } } // ── 删除 ── const handleDelete = async (site: Site) => { if (!confirm(`确定要删除站点「${site.name}」吗?此操作不可撤销。`)) return try { await sitesAPI.remove(site.id) toast.success('站点已删除') await loadSites() } catch { toast.error('删除站点失败') } } // ── 站点类型切换时自动切换认证方式 ── const handleTypeChange = (t: string) => { setForm((f) => ({ ...f, type: t, auth_type: t === 'mteam' ? 'api_key' : f.auth_type, })) } return (
{/* 页头 */}

站点管理

{/* 站点卡片网格 */}
{sites.map((site) => (
{/* 头部 */}
{SITE_TYPE_ABBR[site.type] || '?'}
{site.name}
{site.url}
{/* 状态指示 */}
{site.last_check_at ? ( site.last_error ? ( ) : ( ) ) : ( )} {!site.enabled && 已停用}
{/* 标签 */}
{SITE_TYPE_LABELS[site.type] || site.type} {AUTH_TYPE_LABELS[site.auth_type] || site.auth_type} {site.is_default && ( 默认 )} {site.use_proxy && ( 代理 )} {site.rate_limit && ( 限流 )} {site.browser_emulation && ( 浏览器 )}
{/* 状态与统计(只读) */}
状态: {site.login_status || 'unknown'}
{(site.upload_bytes || 0) > 0 && (
↑ {Math.round((site.upload_bytes ?? 0) / 1073741824 * 100) / 100} GB / ↓ {Math.round((site.download_bytes ?? 0) / 1073741824 * 100) / 100} GB
)} {site.priority !== 50 && (
优先级:{site.priority}
)}
{/* 操作按钮 */}
))} {/* 空状态 */} {!loading && sites.length === 0 && (

暂无站点

点击「添加站点」添加 PT/BT 站点

)} {/* 加载中 */} {loading && (

加载中...

)}
{/* ── 创建/编辑弹窗 ── */} {showModal && (
e.stopPropagation()} > {/* 标题栏 */}

{editingId ? '编辑站点' : '添加站点'}

{/* 名称 */}
setForm((f) => ({ ...f, name: e.target.value }))} />
{/* 地址 */}
setForm((f) => ({ ...f, url: e.target.value }))} />

格式: https://www.example.com/

{/* 站点类型 + 状态 */}
{form.enabled ? '启用' : '停用'}
{/* 馒头提示 */} {form.type === 'mteam' && (
馒头站点配置指南
站点地址:https://api2.m-team.cc
认证方式:推荐使用「API Key / Passkey」
1. 登录馒头站 → 控制台 → 实验室 → 存取令牌
2. 点击「创建令牌」,复制生成的 Token
3. 将 Token 填入下方「令牌」输入框
)} {/* 认证方式 */}
{[ { value: 'cookie', label: 'Cookie' }, { value: 'api_key', label: 'API Key' }, { value: 'auth_header', label: 'Auth Header' }, ].map((opt) => ( ))}
{form.auth_type === 'cookie' && (