update pay ui

This commit is contained in:
2025-12-11 07:39:34 +08:00
parent 2c5b3b7b50
commit 8748e81a7b
5 changed files with 120 additions and 78 deletions

74
app.py
View File

@@ -12507,69 +12507,57 @@ def get_market_statistics():
@app.route('/api/concepts/daily-top', methods=['GET']) @app.route('/api/concepts/daily-top', methods=['GET'])
def get_daily_top_concepts(): def get_daily_top_concepts():
"""获取每日涨幅靠前的概念板块""" """获取每日涨幅靠前的概念板块
修复:使用 /price/list 接口获取全部概念的正确排序(原 /search 接口只取前500个
"""
try: try:
# 获取交易日期参数 # 获取交易日期参数
trade_date = request.args.get('date') trade_date = request.args.get('date')
limit = request.args.get('limit', 6, type=int) limit = request.args.get('limit', 6, type=int)
# 构建概念中心API的URL # 使用 /price/list 接口获取正确排序的涨幅数据
concept_api_url = 'http://222.128.1.157:16801/search' concept_api_url = 'http://222.128.1.157:16801/price/list'
# 准备请求数据 params = {
request_data = { 'sort_by': 'change_desc',
'query': '', 'limit': limit,
'size': limit, 'offset': 0,
'page': 1, 'concept_type': 'leaf' # 只获取叶子概念
'sort_by': 'change_pct'
} }
if trade_date: if trade_date:
request_data['trade_date'] = trade_date params['trade_date'] = trade_date
# 调用概念中心API response = requests.get(concept_api_url, params=params, timeout=10)
response = requests.post(concept_api_url, json=request_data, timeout=10)
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
top_concepts = [] top_concepts = []
for concept in data.get('results', []): for concept in data.get('concepts', []):
# 处理 stocks 字段:兼容 {name, code} 和 {stock_name, stock_code} 两种格式 # /price/list 返回的字段较少,适配为前端需要的格式
raw_stocks = concept.get('stocks', [])
formatted_stocks = []
for stock in raw_stocks:
# 优先使用 stock_name其次使用 name
stock_name = stock.get('stock_name') or stock.get('name', '')
stock_code = stock.get('stock_code') or stock.get('code', '')
formatted_stocks.append({
'stock_name': stock_name,
'stock_code': stock_code,
'name': stock_name, # 兼容旧格式
'code': stock_code # 兼容旧格式
})
# 保持与 /concept-api/search 相同的字段结构,并添加新字段
top_concepts.append({ top_concepts.append({
'concept_id': concept.get('concept_id'), 'concept_id': concept.get('concept_id'),
'concept': concept.get('concept'), # 原始字段名 'concept': concept.get('concept_name'),
'concept_name': concept.get('concept'), # 兼容旧字段名 'concept_name': concept.get('concept_name'),
'description': concept.get('description'), 'description': None, # /price/list 不返回此字段
'stock_count': concept.get('stock_count', 0), 'stock_count': concept.get('stock_count', 0),
'score': concept.get('score'), 'score': None,
'match_type': concept.get('match_type'), 'match_type': None,
'price_info': concept.get('price_info', {}), # 完整的价格信息 'price_info': {
'change_percent': concept.get('price_info', {}).get('avg_change_pct', 0), # 兼容旧字段 'avg_change_pct': concept.get('avg_change_pct')
'tags': concept.get('tags', []), # 标签列表 },
'outbreak_dates': concept.get('outbreak_dates', []), # 爆发日期列表 'change_percent': concept.get('avg_change_pct', 0),
'hierarchy': concept.get('hierarchy'), # 层级信息 {lv1, lv2, lv3} 'tags': [], # /price/list 不返回此字段
'stocks': formatted_stocks, # 返回格式化后的股票列表 'outbreak_dates': [], # /price/list 不返回此字段
'hot_score': concept.get('hot_score') 'hierarchy': concept.get('hierarchy'),
'stocks': [], # /price/list 不返回此字段
'hot_score': None
}) })
# 格式化日期为 YYYY-MM-DD # 格式化日期为 YYYY-MM-DD
price_date = data.get('price_date', '') price_date = data.get('trade_date', '')
formatted_date = str(price_date).split(' ')[0][:10] if price_date else '' formatted_date = str(price_date)[:10] if price_date else ''
return jsonify({ return jsonify({
'success': True, 'success': True,

View File

@@ -449,48 +449,102 @@ const ConceptCenter = () => {
setLoading(true); setLoading(true);
try { try {
const sortToUse = customSortBy !== null ? customSortBy : sortBy; const sortToUse = customSortBy !== null ? customSortBy : sortBy;
const dateStr = date ? date.toISOString().split('T')[0] : null;
const requestBody = { // 判断是否使用 /price/list 接口
query: query, // 条件:无搜索词 + 按涨跌幅排序(升序或降序)+ 无层级筛选
size: pageSize, const isChangePctSort = sortToUse === 'change_pct' || sortToUse === 'change_pct_asc';
page: page, const hasNoFilter = !filter?.lv1 && !filter?.lv2;
sort_by: sortToUse const shouldUsePriceList = !query && isChangePctSort && hasNoFilter;
};
if (date) { if (shouldUsePriceList) {
requestBody.trade_date = date.toISOString().split('T')[0]; // 使用 /price/list 接口获取全部概念的正确排序
} const sortParam = sortToUse === 'change_pct_asc' ? 'change_asc' : 'change_desc';
const offset = (page - 1) * pageSize;
const params = new URLSearchParams({
sort_by: sortParam,
limit: pageSize.toString(),
offset: offset.toString(),
concept_type: 'leaf' // 只获取叶子概念
});
if (dateStr) {
params.append('trade_date', dateStr);
}
// 添加层级筛选参数 const response = await fetch(`${API_BASE_URL}/price/list?${params}`);
if (filter?.lv1) { if (!response.ok) throw new Error('获取涨跌幅数据失败');
requestBody.filter_lv1 = filter.lv1;
}
if (filter?.lv2) {
requestBody.filter_lv2 = filter.lv2;
}
const response = await fetch(`${API_BASE_URL}/search`, { const data = await response.json();
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (!response.ok) throw new Error('搜索失败'); // 适配 /price/list 返回数据为 /search 格式
const adaptedResults = data.concepts.map(item => ({
concept_id: item.concept_id,
concept: item.concept_name,
concept_name: item.concept_name,
price_info: {
avg_change_pct: item.avg_change_pct
},
stock_count: item.stock_count,
hierarchy: item.hierarchy,
// 以下字段 /price/list 不返回,填入默认值
description: null,
tags: [],
outbreak_dates: [],
stocks: []
}));
const data = await response.json(); setConcepts(adaptedResults);
setTotalConcepts(data.total || 0);
setTotalPages(Math.ceil((data.total || 0) / pageSize));
setCurrentPage(page);
setConcepts(data.results || []); if (data.trade_date) {
setTotalConcepts(data.total || 0); setSelectedDate(new Date(data.trade_date));
setTotalPages(data.total_pages || 1); }
setCurrentPage(data.page || 1); } else {
// 使用原有的 /search 接口
const requestBody = {
query: query,
size: pageSize,
page: page,
sort_by: sortToUse
};
if (data.price_date) { if (dateStr) {
setSelectedDate(new Date(data.price_date)); requestBody.trade_date = dateStr;
}
// 添加层级筛选参数
if (filter?.lv1) {
requestBody.filter_lv1 = filter.lv1;
}
if (filter?.lv2) {
requestBody.filter_lv2 = filter.lv2;
}
const response = await fetch(`${API_BASE_URL}/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (!response.ok) throw new Error('搜索失败');
const data = await response.json();
setConcepts(data.results || []);
setTotalConcepts(data.total || 0);
setTotalPages(data.total_pages || 1);
setCurrentPage(data.page || 1);
if (data.price_date) {
setSelectedDate(new Date(data.price_date));
}
} }
} catch (error) { } catch (error) {
logger.error('ConceptCenter', 'fetchConcepts', error, { query, page, date: date?.toISOString(), sortToUse, filter }); logger.error('ConceptCenter', 'fetchConcepts', error, { query, page, date: date?.toISOString(), customSortBy, filter });
// ❌ 移除获取数据失败toast // ❌ 移除获取数据失败toast
// toast({ title: '获取数据失败', description: error.message, status: 'error', duration: 3000, isClosable: true }); // toast({ title: '获取数据失败', description: error.message, status: 'error', duration: 3000, isClosable: true });

View File

@@ -19,8 +19,8 @@ import {
useColorModeValue, useColorModeValue,
Tooltip, Tooltip,
Flex, Flex,
keyframes,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { keyframes } from '@emotion/react';
import { import {
TrendingUp, TrendingUp,
TrendingDown, TrendingDown,

View File

@@ -28,8 +28,8 @@ import {
PopoverBody, PopoverBody,
Portal, Portal,
chakra, chakra,
keyframes,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { keyframes } from '@emotion/react';
import { import {
TrendingUp, TrendingUp,
TrendingDown, TrendingDown,

View File

@@ -27,8 +27,8 @@ import {
GridItem, GridItem,
IconButton, IconButton,
Collapse, Collapse,
keyframes,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { keyframes } from '@emotion/react';
import { import {
Flame, Flame,
List, List,