Spaces:
Paused
Paused
| "use client"; | |
| import { useState } from "react"; | |
| import { Link2, LoaderCircle, PlugZap, Save } from "lucide-react"; | |
| import { toast } from "sonner"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Card, CardContent } from "@/components/ui/card"; | |
| import { Input } from "@/components/ui/input"; | |
| import { testProxy, type ProxyTestResult } from "@/lib/api"; | |
| import { useSettingsStore } from "../store"; | |
| export function ProxySettingsCard() { | |
| const [isTesting, setIsTesting] = useState(false); | |
| const [testResult, setTestResult] = useState<ProxyTestResult | null>(null); | |
| const config = useSettingsStore((state) => state.config); | |
| const isLoadingConfig = useSettingsStore((state) => state.isLoadingConfig); | |
| const isSavingConfig = useSettingsStore((state) => state.isSavingConfig); | |
| const setProxy = useSettingsStore((state) => state.setProxy); | |
| const saveConfig = useSettingsStore((state) => state.saveConfig); | |
| const proxy = config?.proxy ?? ""; | |
| const handleTest = async () => { | |
| const candidate = proxy.trim(); | |
| if (!candidate) { | |
| toast.error("请先填写代理地址"); | |
| return; | |
| } | |
| setIsTesting(true); | |
| setTestResult(null); | |
| try { | |
| const data = await testProxy(candidate); | |
| setTestResult(data.result); | |
| if (data.result.ok) { | |
| toast.success(`代理可用(${data.result.latency_ms} ms,HTTP ${data.result.status})`); | |
| } else { | |
| toast.error(`代理不可用:${data.result.error ?? "未知错误"}`); | |
| } | |
| } catch (error) { | |
| toast.error(error instanceof Error ? error.message : "测试代理失败"); | |
| } finally { | |
| setIsTesting(false); | |
| } | |
| }; | |
| return ( | |
| <Card className="rounded-2xl border-white/80 bg-white/90 shadow-sm"> | |
| <CardContent className="space-y-6 p-6"> | |
| <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between"> | |
| <div className="flex items-center gap-3"> | |
| <div className="flex size-10 items-center justify-center rounded-xl bg-stone-100"> | |
| <Link2 className="size-5 text-stone-600" /> | |
| </div> | |
| <div> | |
| <h2 className="text-lg font-semibold tracking-tight">全局代理</h2> | |
| <p className="text-sm text-stone-500">为系统中的出站请求配置统一代理,保存后会立即生效。</p> | |
| </div> | |
| </div> | |
| <Badge variant={proxy.trim() ? "success" : "secondary"} className="w-fit rounded-md px-2.5 py-1"> | |
| {proxy.trim() ? "已配置" : "未配置"} | |
| </Badge> | |
| </div> | |
| {isLoadingConfig ? ( | |
| <div className="flex items-center justify-center py-10"> | |
| <LoaderCircle className="size-5 animate-spin text-stone-400" /> | |
| </div> | |
| ) : ( | |
| <> | |
| <div className="space-y-2"> | |
| <label className="text-sm font-medium text-stone-700">代理地址</label> | |
| <Input | |
| value={proxy} | |
| onChange={(event) => { | |
| setProxy(event.target.value); | |
| setTestResult(null); | |
| }} | |
| placeholder="http://user:pass@127.0.0.1:7890" | |
| className="h-11 rounded-xl border-stone-200 bg-white" | |
| /> | |
| <p className="text-sm text-stone-500"> | |
| 留空表示不使用代理。请按完整地址填写,例如 `http://127.0.0.1:7890`、`http://用户名:密码@127.0.0.1:7890` 或 `socks5://127.0.0.1:7890`。 | |
| </p> | |
| </div> | |
| {testResult ? ( | |
| <div | |
| className={`rounded-xl border px-4 py-3 text-sm leading-6 ${ | |
| testResult.ok | |
| ? "border-emerald-200 bg-emerald-50 text-emerald-800" | |
| : "border-rose-200 bg-rose-50 text-rose-800" | |
| }`} | |
| > | |
| {testResult.ok | |
| ? `代理可用:HTTP ${testResult.status},用时 ${testResult.latency_ms} ms` | |
| : `代理不可用:${testResult.error ?? "未知错误"}(用时 ${testResult.latency_ms} ms)`} | |
| </div> | |
| ) : null} | |
| <div className="flex justify-end gap-2"> | |
| <Button | |
| variant="outline" | |
| className="h-10 rounded-xl border-stone-200 bg-white px-5 text-stone-700" | |
| onClick={() => void handleTest()} | |
| disabled={isTesting || isLoadingConfig} | |
| > | |
| {isTesting ? <LoaderCircle className="size-4 animate-spin" /> : <PlugZap className="size-4" />} | |
| 测试代理 | |
| </Button> | |
| <Button | |
| className="h-10 rounded-xl bg-stone-950 px-5 text-white hover:bg-stone-800" | |
| onClick={() => void saveConfig()} | |
| disabled={isSavingConfig} | |
| > | |
| {isSavingConfig ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />} | |
| 保存配置 | |
| </Button> | |
| </div> | |
| </> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |