108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState, useCallback } from "react";
|
||
import { api, RatesResponse, StatsResponse, HistoryResponse } from "@/lib/api";
|
||
import RateCard from "@/components/RateCard";
|
||
import StatsCard from "@/components/StatsCard";
|
||
import FundingChart from "@/components/FundingChart";
|
||
|
||
export default function Dashboard() {
|
||
const [rates, setRates] = useState<RatesResponse | null>(null);
|
||
const [stats, setStats] = useState<StatsResponse | null>(null);
|
||
const [history, setHistory] = useState<HistoryResponse | null>(null);
|
||
const [status, setStatus] = useState<"loading" | "running" | "error">("loading");
|
||
const [lastUpdate, setLastUpdate] = useState<string>("");
|
||
|
||
const fetchAll = useCallback(async () => {
|
||
try {
|
||
const [r, s, h] = await Promise.all([api.rates(), api.stats(), api.history()]);
|
||
setRates(r);
|
||
setStats(s);
|
||
setHistory(h);
|
||
setStatus("running");
|
||
setLastUpdate(new Date().toLocaleTimeString("zh-CN"));
|
||
} catch {
|
||
setStatus("error");
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
fetchAll();
|
||
const interval = setInterval(fetchAll, 2_000);
|
||
return () => clearInterval(interval);
|
||
}, [fetchAll]);
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-slate-100">资金费率套利监控</h1>
|
||
<p className="text-slate-400 text-sm mt-1">实时监控 BTC / ETH 永续合约资金费率</p>
|
||
</div>
|
||
<div className="flex items-center gap-2 text-sm">
|
||
<span
|
||
className={`w-2 h-2 rounded-full ${
|
||
status === "running"
|
||
? "bg-emerald-400 animate-pulse"
|
||
: status === "error"
|
||
? "bg-red-400"
|
||
: "bg-slate-500"
|
||
}`}
|
||
/>
|
||
<span className={status === "running" ? "text-emerald-400" : status === "error" ? "text-red-400" : "text-slate-400"}>
|
||
{status === "running" ? "运行中" : status === "error" ? "连接失败" : "加载中..."}
|
||
</span>
|
||
{lastUpdate && (
|
||
<span className="text-slate-500 ml-2">更新于 {lastUpdate}</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Rate Cards */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<RateCard asset="BTC" data={rates?.BTC ?? null} />
|
||
<RateCard asset="ETH" data={rates?.ETH ?? null} />
|
||
</div>
|
||
|
||
{/* Stats Cards */}
|
||
{stats && (
|
||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||
<StatsCard
|
||
title="BTC 套利"
|
||
mean7d={stats.BTC.mean7d}
|
||
annualized={stats.BTC.annualized}
|
||
accent="cyan"
|
||
/>
|
||
<StatsCard
|
||
title="ETH 套利"
|
||
mean7d={stats.ETH.mean7d}
|
||
annualized={stats.ETH.annualized}
|
||
accent="violet"
|
||
/>
|
||
<StatsCard
|
||
title="50/50 组合"
|
||
mean7d={stats.combo.mean7d}
|
||
annualized={stats.combo.annualized}
|
||
accent="emerald"
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* History Chart */}
|
||
{history && (
|
||
<div className="rounded-xl border border-slate-700 bg-slate-800/50 p-6">
|
||
<h2 className="text-slate-200 font-semibold mb-4">过去7天资金费率走势</h2>
|
||
<FundingChart history={history} />
|
||
</div>
|
||
)}
|
||
|
||
{/* Strategy note */}
|
||
<div className="rounded-lg border border-slate-700 bg-slate-800/30 px-5 py-3 text-sm text-slate-400">
|
||
<span className="text-cyan-400 font-medium">策略原理:</span>
|
||
持有现货多头 + 永续空头,每8小时收取资金费率,赚取无方向风险的稳定收益。
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|