feat: 新增题材页面(列表 + 详情)

- 主页增加「题材热点」入口,跳转题材列表页
- 题材列表页:展示全部题材,支持按涨幅/强度/热度/成交额排序
- 题材详情页:简介、热点事件、相关新闻、板块涨跌统计、全部相关股票(含入选理由默认展开)
- 后端逆向封装东方财富题材接口 getThemeList/getDetail/getStockList,含交易时段感知缓存

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-06 17:22:04 +08:00
co-authored by Claude
parent b107a12798
commit 45836f4a30
8 changed files with 1100 additions and 11 deletions
+160
View File
@@ -0,0 +1,160 @@
// 题材数据获取工具:通过 Python 后端代理调用东方财富题材接口
import { getApiBaseUrl } from "@/lib/api-client";
/* ── 题材列表 ── */
export interface ThemeItem {
themeCode: string;
themeName: string;
securityName: string; // 领涨股名称
securityCode: string; // 领涨股代码
codeWithSuffix: string;
hotRank: number; // 热度排名
f3: number | null; // 领涨股涨幅
bf3: number | null; // 题材涨幅
hotValue: number; // 热度值
hotValueUpLimit: number; // 热度上限
strengthValue: number | null; // 强度值
fex5: number | null; // 成交额
fex3: number | null;
label: string | null; // 标签(如"超级爆点")
}
export type ThemeSortField = 1 | 3 | 4 | 5; // 1=涨幅 3=强度 4=热度排名 5=成交额
export interface ThemeListResponse {
data: ThemeItem[];
count: number;
sort_field: number;
asc: boolean;
}
/**
* 获取全部题材列表
* @param sortField 1=涨幅 3=强度 4=热度排名 5=成交额
* @param asc true=升序
*/
export async function fetchThemes(sortField: ThemeSortField = 1, asc: boolean = false): Promise<ThemeItem[]> {
const baseUrl = getApiBaseUrl();
const url = `${baseUrl}/api/themes?sort_field=${sortField}&asc=${asc}`;
try {
const resp = await fetch(url, { method: "GET", cache: "no-store" });
if (!resp.ok) return [];
const result: ThemeListResponse = await resp.json();
return result.data || [];
} catch (err) {
console.error("[theme-api] 获取题材列表失败:", err);
return [];
}
}
/* ── 题材详情 ── */
export interface ThemeHotEvent {
newsTitle: string | null;
newsSummary: string | null;
newsMediaName: string | null;
newsPublishTimeFormat: string | null;
newsCode: string | null;
}
export interface ThemeNews {
newsCode: string;
newsTitle: string;
newsMediaName: string;
newsPublishTime: number | null;
}
export interface ThemeBaseInfo {
themeCode: string;
themeName: string;
introduction: string;
explainImgUrl: string | null;
themeLevel: number;
isShowRank: number;
}
export interface ThemeDetail {
baseInfo: ThemeBaseInfo;
hotEvent: ThemeHotEvent | null;
eventHistory: ThemeNews[];
topicId: string | null;
}
/**
* 获取题材详情(简介 + 热点事件 + 相关新闻)
*/
export async function fetchThemeDetail(themeCode: string): Promise<ThemeDetail | null> {
const baseUrl = getApiBaseUrl();
const url = `${baseUrl}/api/themes/${themeCode}/detail`;
try {
const resp = await fetch(url, { method: "GET", cache: "no-store" });
if (!resp.ok) return null;
const result = await resp.json();
return result.data || null;
} catch (err) {
console.error("[theme-api] 获取题材详情失败:", err);
return null;
}
}
/* ── 题材相关股票 ── */
export interface ThemeStockKeyword {
keywordCode: string;
keyword: string;
introduction: string;
}
export interface ThemeStock {
securityName: string;
securityCode: string;
codeSuffix: string;
f2: number; // 现价
f3: number; // 涨跌幅%
f5: number; // 成交量
f6: number; // 成交额
f8: number; // 换手率%
f20: number; // 总市值
f21: number; // 流通市值
f62: number; // 主力净流入
f100: string; // 所属行业
f265: string; // 板块代码
label: string | null; // 涨停标签
rank: number;
dragonStockLabel: number;
keywordList: ThemeStockKeyword[]; // 入选理由
}
export interface ThemeStatistic {
f3: number | null; // 板块涨幅
f104: number | null; // 上涨家数
f105: number | null; // 下跌家数
f106: number | null; // 平盘家数
fex5: number | null; // 板块成交额
}
export interface ThemeStocksResponse {
data: ThemeStock[];
statistic: ThemeStatistic;
total: number;
}
/**
* 获取题材下全部相关股票
*/
export async function fetchThemeStocks(themeCode: string): Promise<ThemeStocksResponse | null> {
const baseUrl = getApiBaseUrl();
const url = `${baseUrl}/api/themes/${themeCode}/stocks`;
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;
}
}