feat: 新增热点穿透网状关系图 + 移动端优化

- 新增 /hot-map 热点穿透页:d3-force 力导向网状图展示题材-股票 M:N 关系
  - 股票节点面积/颜色按覆盖题材数分级,穿透核心股高亮
  - 图/核心股列表双视图切换,列表覆盖题材>2 公司降序展示
  - 悬停信息卡、缩放平移、点击跳转股票/题材详情
- 数据采样改为涨幅+热度双榜合并(各 Top50 去重=82 题材),修复覆盖数低估
  (有研新材覆盖数 2→10,核心股 66→1124 家)
- 移动端适配:触屏 tap 选中底部浮层、标签缩放阈值防重叠、自动 fit、紧凑图例
- 主页/题材列表增加热点穿透入口

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-06 20:34:14 +08:00
co-authored by Claude
parent 45836f4a30
commit e28d1a2fd0
9 changed files with 1209 additions and 206 deletions
+60
View File
@@ -158,3 +158,63 @@ export async function fetchThemeStocks(themeCode: string): Promise<ThemeStocksRe
return null;
}
}
/* ── 热点穿透:题材-股票 网状关系图 ── */
export interface GraphTheme {
themeCode: string;
themeName: string;
bf3: number | null; // 题材涨幅
hotValue: number;
strengthValue: number | null;
stockCount: number; // 题材内股票数
}
export interface GraphStock {
securityCode: string;
securityName: string;
coverCount: number; // 覆盖题材数(穿透强度)
f3: number | null; // 涨幅
f2: number | null; // 现价
f62: number | null; // 主力净流入
f100: string; // 行业
themeCodes: string[]; // 所属题材代码
}
export interface GraphEdge {
themeCode: string;
securityCode: string;
}
export interface GraphStats {
themeCount: number;
stockCount: number;
coreCount: number; // 覆盖≥2 的核心股数
maxCover: number; // 最大覆盖题材数
}
export interface ThemeGraph {
themes: GraphTheme[];
stocks: GraphStock[];
edges: GraphEdge[];
stats: GraphStats;
}
/**
* 获取热点穿透图数据(题材-股票 M:N 网状关系)
* @param sortField 1=涨幅 4=热度
* @param top 题材数量
*/
export async function fetchThemeGraph(sortField: 1 | 4 = 1, top: number = 30): Promise<ThemeGraph | null> {
const baseUrl = getApiBaseUrl();
const url = `${baseUrl}/api/themes/graph?sort_field=${sortField}&top=${top}`;
try {
const resp = await fetch(url, { method: "GET", cache: "no-store" });
if (!resp.ok) return null;
return await resp.json();
} catch (err) {
console.error("[theme-api] 获取热点穿透图数据失败:", err);
return null;
}
}