perf: 优化热点穿透加载速度 + Canvas 渲染 + 触控板/手机交互
后端加载优化(盘中原本每次访问都重新聚合,耗时 10-30 秒): - 图聚合结果与题材股票子层盘中加 60 秒缓存 - 缓存过期时返回旧数据并后台幂等重建(stale-while-revalidate),打开即秒开 - 缓存基础支持秒级 TTL(set_cache 新增 ttl_seconds) - 响应体瘦身:移除 edges(前端从 stocks[].themeCodes 重建)、 themes 精简字段、stocks 按 limit 裁剪,JSON 从数 MB 降至数百 KB 前端 Canvas 渲染重构: - d3-force 布局保留,SVG 渲染层替换为 Canvas 双缓冲(静止态离屏层 drawImage) - tick 由每帧 setState 改为 rAF 合帧,拖拽/缩放期间零 React 重渲染,800 节点流畅 交互优化: - 手机:新增 +/−/适应 缩放按钮、命中半径放大至 22px、tap/drag 6px 阈值区分、 双指捏合缩放、触屏禁用 hover 避免与选中冲突 - Mac 触控板:双指滚动=平移、捏合(ctrlKey)=缩放、滚轮缩放保留、点空白取消选中 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+20
-11
@@ -164,9 +164,6 @@ export async function fetchThemeStocks(themeCode: string): Promise<ThemeStocksRe
|
||||
export interface GraphTheme {
|
||||
themeCode: string;
|
||||
themeName: string;
|
||||
bf3: number | null; // 题材涨幅
|
||||
hotValue: number;
|
||||
strengthValue: number | null;
|
||||
stockCount: number; // 题材内股票数
|
||||
}
|
||||
|
||||
@@ -181,11 +178,6 @@ export interface GraphStock {
|
||||
themeCodes: string[]; // 所属题材代码
|
||||
}
|
||||
|
||||
export interface GraphEdge {
|
||||
themeCode: string;
|
||||
securityCode: string;
|
||||
}
|
||||
|
||||
export interface GraphStats {
|
||||
themeCount: number;
|
||||
stockCount: number;
|
||||
@@ -196,18 +188,35 @@ export interface GraphStats {
|
||||
export interface ThemeGraph {
|
||||
themes: GraphTheme[];
|
||||
stocks: GraphStock[];
|
||||
edges: GraphEdge[];
|
||||
stats: GraphStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从股票覆盖题材重建关系边(后端不再下发 edges,体积减半以上)
|
||||
* 返回 d3-force 可直接使用的 source/target 节点 id("t:题材code" / "s:股票code")
|
||||
*/
|
||||
export function buildEdgesFromStocks(stocks: GraphStock[]): { source: string; target: string }[] {
|
||||
const edges: { source: string; target: string }[] = [];
|
||||
for (const s of stocks) {
|
||||
const target = `s:${s.securityCode}`;
|
||||
for (const tc of s.themeCodes) edges.push({ source: `t:${tc}`, target });
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热点穿透图数据(题材-股票 M:N 网状关系)
|
||||
* @param sortField 1=涨幅 4=热度
|
||||
* @param top 题材数量
|
||||
* @param limit 下发的股票节点上限(按穿透度取前 N 只)
|
||||
*/
|
||||
export async function fetchThemeGraph(sortField: 1 | 4 = 1, top: number = 30): Promise<ThemeGraph | null> {
|
||||
export async function fetchThemeGraph(
|
||||
sortField: 1 | 4 = 1,
|
||||
top: number = 30,
|
||||
limit: number = 1000,
|
||||
): Promise<ThemeGraph | null> {
|
||||
const baseUrl = getApiBaseUrl();
|
||||
const url = `${baseUrl}/api/themes/graph?sort_field=${sortField}&top=${top}`;
|
||||
const url = `${baseUrl}/api/themes/graph?sort_field=${sortField}&top=${top}&limit=${limit}`;
|
||||
|
||||
try {
|
||||
const resp = await fetch(url, { method: "GET", cache: "no-store" });
|
||||
|
||||
Reference in New Issue
Block a user