import { lazy, Suspense, useState } from "react" import { Button } from "@/shared/ui/button" import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger, } from "@/shared/ui/dialog" import { useSectorRadarDetail } from "../api/sector-radar.query" import type { RadarDetailResponse, RadarMemberMetric, RadarMetricKind, RadarRankingRow, RadarSectorQuery, } from "../api/sector-radar.types" import { formatRadarValue, radarMembersCsv, radarValueTone, } from "./radar-format" import { RadarHistoryGrid } from "./radar-history" const RadarRankChart = lazy(() => import("./radar-rank-chart").then((module) => ({ default: module.RadarRankChart, })), ) const metricLabels = { swing: "波段资金率", ratio: "单日流入率", amount: "单日净额", } as const const memberLabels = { pct_change: "涨跌幅", net_amount_yuan: "主力净额", active_buy_net_amount_yuan: "主买净额", } as const /** Keep the trigger inside the dialog primitive so Escape and close restore its focus. */ export function RadarDetailDialog({ row }: { row: RadarRankingRow }) { const [open, setOpen] = useState(false) return ( {row.sector_name} {row.sector_name} {row.sector_type === "concept" ? "概念" : "行业"} · {row.sector_code}{" "} · {row.trade_date} {open ? ( ) : null} ) } function RadarDetail({ query, initialMetric, }: { query: RadarSectorQuery initialMetric: RadarMetricKind }) { const detail = useSectorRadarDetail(query) if (detail.isPending) return

正在加载板块详情…

if (detail.isError) return (
板块详情加载失败。
) if (!detail.data || detail.data.status === "no_data") return

暂无板块详情数据。

return } function RadarDetailBody({ data, initialMetric, }: { data: RadarDetailResponse initialMetric: RadarMetricKind }) { const [metric, setMetric] = useState(initialMetric) const [memberMetric, setMemberMetric] = useState("pct_change") const [memberSide, setMemberSide] = useState<"top" | "bottom">("top") const [exportStatus, setExportStatus] = useState("") const leaders = data.leaders[memberMetric] async function copyMembers() { try { await navigator.clipboard.writeText( data.members .map((member) => `${member.ts_code}\t${member.name}`) .join("\n"), ) setExportStatus(`已复制 ${data.members.length} 只成员。`) } catch { setExportStatus("复制失败,请使用导出成员。") } } function exportMembers() { const url = URL.createObjectURL( new Blob(["\ufeff", radarMembersCsv(data.members)], { type: "text/csv;charset=utf-8", }), ) const anchor = document.createElement("a") anchor.href = url anchor.download = `${data.sector_code}-${data.trade_date}-members.csv` anchor.click() URL.revokeObjectURL(url) setExportStatus(`已导出 ${data.members.length} 只成员。`) } return (
{exportStatus ?

{exportStatus}

: null}

实际交易日:{data.trade_date ?? "—"}

当日涨跌幅: {formatRadarValue(data.pct_change, "percent")}

领涨股: {data.leading_stock ? `${data.leading_stock.name ?? ""} ${data.leading_stock.ts_code}` : "暂无数据"}

{(["swing", "ratio", "amount"] as const).map((key) => ( ))}
{data.history.points.some( (point) => !point[metric].missing && point[metric].rank_position !== null, ) ? ( 正在加载排名曲线…

}>
) : (

暂无{metricLabels[metric]}排名轨迹,当前历史记录均缺失。

)}

成分强弱

{(Object.keys(memberLabels) as RadarMemberMetric[]).map((key) => ( ))}
{(["top", "bottom"] as const).map((side) => ( ))}
{[memberSide].map((side) => (

{side === "top" ? "前 5 名" : "后 5 名"}

{leaders[side].length ? (
    {leaders[side].map((member) => (
  1. {member.name}{" "} {member.ts_code} {formatRadarValue( member[memberMetric], memberMetric === "pct_change" ? "percent" : "yuan", )}
  2. ))}
) : (

暂无有效数据

)}
))}

仅展示有该指标的成员,不足 5 只按实际数量显示。主买净额与主力净额为不同来源指标。

相似板块

本系统口径:同日可确认成员交集 / 并集(Jaccard 重合度)。

{data.similar_sectors.length ? (
{data.similar_sectors.map((sector) => (
{sector.sector_name}{" "} {sector.sector_type === "concept" ? "概念" : "行业"} {(sector.overlap_ratio * 100).toFixed(1)}%{" "} ({sector.intersection_count}/{sector.union_count})
))}
) : (

暂无相似板块数据

)}

板块成员({data.members.length})

{data.members.length ? data.members .map((member) => `${member.name} (${member.ts_code})`) .join("、") : "暂无成员数据"}

) }