- 新增 /hot-map 热点穿透页:d3-force 力导向网状图展示题材-股票 M:N 关系 - 股票节点面积/颜色按覆盖题材数分级,穿透核心股高亮 - 图/核心股列表双视图切换,列表覆盖题材>2 公司降序展示 - 悬停信息卡、缩放平移、点击跳转股票/题材详情 - 数据采样改为涨幅+热度双榜合并(各 Top50 去重=82 题材),修复覆盖数低估 (有研新材覆盖数 2→10,核心股 66→1124 家) - 移动端适配:触屏 tap 选中底部浮层、标签缩放阈值防重叠、自动 fit、紧凑图例 - 主页/题材列表增加热点穿透入口 Co-Authored-By: Claude <noreply@anthropic.com>
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
"""题材数据路由:题材列表、题材详情、题材相关股票"""
|
|
|
|
from fastapi import APIRouter, Query, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
from services import themes
|
|
|
|
router = APIRouter()
|
|
|
|
# 上游反代/CDN 可能按 path 缓存,显式禁止缓存
|
|
_NO_CACHE_HEADERS = {"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0"}
|
|
|
|
|
|
@router.get("", summary="题材列表")
|
|
async def theme_list(
|
|
sort_field: int = Query(1, ge=1, le=5, description="排序字段:1=涨幅 3=强度 4=热度排名 5=成交额"),
|
|
asc: bool = Query(False, description="True=升序, False=降序"),
|
|
):
|
|
if sort_field not in (1, 3, 4, 5):
|
|
raise HTTPException(status_code=400, detail="排序字段仅支持 1/3/4/5")
|
|
|
|
data = await themes.fetch_theme_list(sort_field, asc)
|
|
return JSONResponse(
|
|
{"data": data, "count": len(data), "sort_field": sort_field, "asc": asc},
|
|
headers=_NO_CACHE_HEADERS,
|
|
)
|
|
|
|
|
|
@router.get("/graph", summary="热点穿透:题材-股票网状关系图")
|
|
async def theme_graph(
|
|
sort_field: int = Query(1, description="题材排序:1=涨幅 4=热度"),
|
|
top: int = Query(30, ge=1, le=60, description="题材数量"),
|
|
):
|
|
if sort_field not in (1, 4):
|
|
raise HTTPException(status_code=400, detail="排序字段仅支持 1(涨幅)/4(热度)")
|
|
|
|
result = await themes.fetch_theme_graph(sort_field, top)
|
|
return JSONResponse(result, headers=_NO_CACHE_HEADERS)
|
|
|
|
|
|
@router.get("/{theme_code}/detail", summary="题材详情")
|
|
async def theme_detail(theme_code: str):
|
|
data = await themes.fetch_theme_detail(theme_code)
|
|
if not data:
|
|
return JSONResponse(
|
|
{"data": None, "theme_code": theme_code},
|
|
headers=_NO_CACHE_HEADERS,
|
|
)
|
|
return JSONResponse(
|
|
{"data": data, "theme_code": theme_code},
|
|
headers=_NO_CACHE_HEADERS,
|
|
)
|
|
|
|
|
|
@router.get("/{theme_code}/stocks", summary="题材相关股票(全部)")
|
|
async def theme_stocks(theme_code: str):
|
|
result = await themes.fetch_theme_stocks(theme_code)
|
|
return JSONResponse(
|
|
{
|
|
"data": result.get("stockList", []),
|
|
"statistic": result.get("statistic", {}),
|
|
"total": result.get("total", 0),
|
|
"theme_code": theme_code,
|
|
},
|
|
headers=_NO_CACHE_HEADERS,
|
|
)
|