移除天气合约市场的 Ticker/代码列及 ticker 函数

This commit is contained in:
2569718930@qq.com
2026-05-25 05:02:52 +08:00
parent 140a0f16ae
commit 6df18ec3f0
@@ -4,10 +4,7 @@ import clsx from "clsx";
import Link from "next/link"; import Link from "next/link";
import { import {
Activity, Activity,
BarChart3,
Bell,
ChevronLeft, ChevronLeft,
Gauge,
GraduationCap, GraduationCap,
LineChart, LineChart,
Menu, Menu,
@@ -47,6 +44,7 @@ import { Panel } from "@/components/dashboard/scan-terminal/Panel";
import { GroupedMarketTable } from "@/components/dashboard/scan-terminal/GroupedMarketTable"; import { GroupedMarketTable } from "@/components/dashboard/scan-terminal/GroupedMarketTable";
import { TrainingDashboard } from "@/components/dashboard/scan-terminal/TrainingDashboard"; import { TrainingDashboard } from "@/components/dashboard/scan-terminal/TrainingDashboard";
import { LiveTemperatureThresholdChart } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart"; import { LiveTemperatureThresholdChart } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart";
import { MarketOverviewView } from "@/components/dashboard/scan-terminal/MarketOverviewView";
import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils"; import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils";
function createEmptyAccess(loading = true): ProAccessState { function createEmptyAccess(loading = true): ProAccessState {
@@ -168,13 +166,6 @@ function tablePrice(row: ScanOpportunityRow) {
return formatPrice(row.midpoint, row.ask, row.bid); return formatPrice(row.midpoint, row.ask, row.bid);
} }
function ticker(row: ScanOpportunityRow) {
return String(row.airport || row.market_key || row.city || "--")
.replace(/[^A-Za-z0-9]/g, "")
.slice(0, 6)
.toUpperCase();
}
function KoyfinRowsTable({ function KoyfinRowsTable({
compact = false, compact = false,
isEn, isEn,
@@ -198,11 +189,6 @@ function KoyfinRowsTable({
<th className="px-1.5 py-1 text-left font-black"> <th className="px-1.5 py-1 text-left font-black">
{isEn ? "Weather Contract" : "天气合约"} {isEn ? "Weather Contract" : "天气合约"}
</th> </th>
{!compact && (
<th className="px-1.5 py-1 text-left font-black">
{isEn ? "Ticker" : "代码"}
</th>
)}
<th className="px-1.5 py-1 text-right font-black"> <th className="px-1.5 py-1 text-right font-black">
{isEn ? "Price" : "价格"} {isEn ? "Price" : "价格"}
</th> </th>
@@ -236,11 +222,6 @@ function KoyfinRowsTable({
{row.target_label || row.market_question || row.airport || "--"} {row.target_label || row.market_question || row.airport || "--"}
</div> </div>
</td> </td>
{!compact && (
<td className="px-1.5 py-1 font-mono font-bold text-slate-600">
{ticker(row)}
</td>
)}
<td className="px-1.5 py-1 text-right font-mono font-bold text-slate-800"> <td className="px-1.5 py-1 text-right font-mono font-bold text-slate-800">
{tablePrice(row)} {tablePrice(row)}
</td> </td>
@@ -353,9 +334,6 @@ function PolyWeatherTerminal({
const NAV_ITEMS = [ const NAV_ITEMS = [
{ key: "contracts", Icon: Table2, labelEn: "Contracts", labelZh: "天气合约" }, { key: "contracts", Icon: Table2, labelEn: "Contracts", labelZh: "天气合约" },
{ key: "signals", Icon: LineChart, labelEn: "Signals", labelZh: "交易信号" }, { key: "signals", Icon: LineChart, labelEn: "Signals", labelZh: "交易信号" },
{ key: "analytics", Icon: BarChart3, labelEn: "Analytics", labelZh: "分析图表" },
{ key: "watchlist", Icon: Gauge, labelEn: "Watchlist", labelZh: "自选监控" },
{ key: "alerts", Icon: Bell, labelEn: "Alerts", labelZh: "实时预警" },
{ key: "markets", Icon: Activity, labelEn: "Markets", labelZh: "市场概览" }, { key: "markets", Icon: Activity, labelEn: "Markets", labelZh: "市场概览" },
{ key: "training", Icon: GraduationCap, labelEn: "Training", labelZh: "训练数据" }, { key: "training", Icon: GraduationCap, labelEn: "Training", labelZh: "训练数据" },
]; ];
@@ -564,6 +542,21 @@ function PolyWeatherTerminal({
</header> </header>
<main className="min-h-0 flex-1 overflow-hidden flex flex-col p-2 bg-[#eef2f6]"> <main className="min-h-0 flex-1 overflow-hidden flex flex-col p-2 bg-[#eef2f6]">
{activeNavKey === "training" ? (
<TrainingDashboard isEn={isEn} />
) : activeNavKey === "markets" ? (
<MarketOverviewView
isEn={isEn}
rows={rows}
onSelectRow={(row) => {
const regionKey = String(row.trading_region || "").toLowerCase();
if (regionKey) setSelectedRegionKey(regionKey);
setSelectedRow(row);
setActiveNavKey("contracts");
}}
/>
) : (
<>
{/* Region tabs */} {/* Region tabs */}
<div className="flex shrink-0 items-center gap-1 overflow-x-auto rounded-[4px] border border-[#cfd6df] bg-white p-1 mb-2 scrollbar-none"> <div className="flex shrink-0 items-center gap-1 overflow-x-auto rounded-[4px] border border-[#cfd6df] bg-white p-1 mb-2 scrollbar-none">
{TRADING_REGIONS.map((r) => ({ {TRADING_REGIONS.map((r) => ({
@@ -668,110 +661,16 @@ function PolyWeatherTerminal({
selectedId={selectedRow?.id} selectedId={selectedRow?.id}
title={isEn ? "Watchlist & Risk" : "观察与风险"} title={isEn ? "Watchlist & Risk" : "观察与风险"}
/> />
<RegionalWhaleWatch isEn={isEn} rows={filteredRegionRows} />
</div> </div>
</div> </div>
</>
)}
</main> </main>
</div> </div>
</div> </div>
); );
} }
function RegionalWhaleWatch({
isEn,
rows,
}: {
isEn: boolean;
rows: ScanOpportunityRow[];
}) {
type HolderInfo = { city: string; holders: Array<{ proxyWallet?: string; amount?: number; outcomeIndex?: number; pseudonym?: string; name?: string }> | null; loading: boolean };
const [holderMap, setHolderMap] = useState<Record<string, HolderInfo>>({});
const regions = ["americas", "europe", "asia_pacific", "middle_east_africa"];
const regionLabels: Record<string, string> = {
americas: isEn ? "Americas" : "美洲",
europe: isEn ? "Europe" : "欧洲",
asia_pacific: isEn ? "Asia-Pacific" : "亚太",
middle_east_africa: isEn ? "ME & Africa" : "中东非洲",
};
const topByRegion = useMemo(() => {
return regions.map((region) => {
const regionRows = rows
.filter((row) => row.trading_region === region)
.sort((a, b) => (Number(b.volume || b.book_liquidity || 0)) - (Number(a.volume || a.book_liquidity || 0)))
.slice(0, 2);
return { region, label: regionLabels[region] || region, rows: regionRows };
}).filter((r) => r.rows.length > 0);
}, [rows, isEn]);
useEffect(() => {
topByRegion.forEach(({ rows: regionRows }) => {
regionRows.forEach((row) => {
const city = String(row.city || "").toLowerCase();
if (!city || holderMap[city]?.loading || holderMap[city]?.holders) return;
setHolderMap((prev) => ({ ...prev, [city]: { city, holders: null, loading: true } }));
fetch(`/api/city/${encodeURIComponent(city)}/holders?limit=6`, { cache: "no-store", headers: { Accept: "application/json" } })
.then(async (res) => {
const json = await res.json() as { holders?: Array<{ proxyWallet?: string; amount?: number; outcomeIndex?: number; pseudonym?: string; name?: string }>; available?: boolean };
setHolderMap((prev) => ({ ...prev, [city]: { city, holders: json.holders || [], loading: false } }));
})
.catch(() => setHolderMap((prev) => ({ ...prev, [city]: { city, holders: null, loading: false } })));
});
});
}, [topByRegion]);
if (!topByRegion.length) return null;
return (
<Panel title={isEn ? "Whale Watch" : "巨鲸盯盘"}>
<div className="divide-y divide-slate-100 text-[11px]">
{topByRegion.map(({ region, label, rows: regionRows }) => (
<div key={region} className="py-1 px-2">
<div className="mb-1 text-[10px] font-black uppercase text-slate-400">{label}</div>
{regionRows.map((row) => {
const city = String(row.city || "").toLowerCase();
const info = holderMap[city];
const vol = Number(row.volume || row.book_liquidity || 0);
return (
<div key={row.id}>
<div className="flex items-center justify-between py-0.5 border-b border-slate-50">
<span className="font-semibold text-slate-800 truncate max-w-[100px]">
{row.city_display_name || row.city}
</span>
<span className="text-[10px] text-slate-500 truncate max-w-[80px] text-right">
{row.target_label || "--"}
</span>
<span className="ml-1 font-mono text-[10px] font-bold text-blue-700 shrink-0">
{vol >= 1000 ? `$${(vol / 1000).toFixed(1)}K` : `$${Math.round(vol)}`}
</span>
</div>
{info?.loading ? (
<div className="text-[9px] text-slate-300 py-0.5 animate-pulse">{isEn ? "Loading holders..." : "加载持仓..."}</div>
) : info?.holders?.length ? (
info.holders.slice(0, 3).map((h, i) => (
<div key={i} className="flex items-center justify-between py-0.5 text-[10px] ml-2">
<span className="text-slate-500 truncate max-w-[130px]">
{h.name || h.pseudonym || (h.proxyWallet ? `${String(h.proxyWallet).slice(0, 6)}...${String(h.proxyWallet).slice(-4)}` : "--")}
</span>
<span className="font-mono text-slate-600 shrink-0">
{h.outcomeIndex === 0 ? "YES" : h.outcomeIndex === 1 ? "NO" : ""}{" "}
{h.amount != null ? `${Number(h.amount).toFixed(0)}` : ""}
</span>
</div>
))
) : info ? (
<div className="text-[9px] text-slate-300 py-0.5">{isEn ? "No holder data" : "无持仓数据"}</div>
) : null}
</div>
);
})}
</div>
))}
</div>
</Panel>
);
}
function ScanTerminalScreen() { function ScanTerminalScreen() {
const [proAccess, setProAccess] = useState<ProAccessState>(() => const [proAccess, setProAccess] = useState<ProAccessState>(() =>
createEmptyAccess(true), createEmptyAccess(true),