diff --git a/app.py b/app.py index e480b333..0639dda8 100755 --- a/app.py +++ b/app.py @@ -12507,69 +12507,57 @@ def get_market_statistics(): @app.route('/api/concepts/daily-top', methods=['GET']) def get_daily_top_concepts(): - """获取每日涨幅靠前的概念板块""" + """获取每日涨幅靠前的概念板块 + + 修复:使用 /price/list 接口获取全部概念的正确排序(原 /search 接口只取前500个) + """ try: # 获取交易日期参数 trade_date = request.args.get('date') limit = request.args.get('limit', 6, type=int) - # 构建概念中心API的URL - concept_api_url = 'http://222.128.1.157:16801/search' + # 使用 /price/list 接口获取正确排序的涨幅数据 + concept_api_url = 'http://222.128.1.157:16801/price/list' - # 准备请求数据 - request_data = { - 'query': '', - 'size': limit, - 'page': 1, - 'sort_by': 'change_pct' + params = { + 'sort_by': 'change_desc', + 'limit': limit, + 'offset': 0, + 'concept_type': 'leaf' # 只获取叶子概念 } - if trade_date: - request_data['trade_date'] = trade_date + params['trade_date'] = trade_date - # 调用概念中心API - response = requests.post(concept_api_url, json=request_data, timeout=10) + response = requests.get(concept_api_url, params=params, timeout=10) if response.status_code == 200: data = response.json() top_concepts = [] - for concept in data.get('results', []): - # 处理 stocks 字段:兼容 {name, code} 和 {stock_name, stock_code} 两种格式 - 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 相同的字段结构,并添加新字段 + for concept in data.get('concepts', []): + # /price/list 返回的字段较少,适配为前端需要的格式 top_concepts.append({ 'concept_id': concept.get('concept_id'), - 'concept': concept.get('concept'), # 原始字段名 - 'concept_name': concept.get('concept'), # 兼容旧字段名 - 'description': concept.get('description'), + 'concept': concept.get('concept_name'), + 'concept_name': concept.get('concept_name'), + 'description': None, # /price/list 不返回此字段 'stock_count': concept.get('stock_count', 0), - 'score': concept.get('score'), - 'match_type': concept.get('match_type'), - 'price_info': concept.get('price_info', {}), # 完整的价格信息 - 'change_percent': concept.get('price_info', {}).get('avg_change_pct', 0), # 兼容旧字段 - 'tags': concept.get('tags', []), # 标签列表 - 'outbreak_dates': concept.get('outbreak_dates', []), # 爆发日期列表 - 'hierarchy': concept.get('hierarchy'), # 层级信息 {lv1, lv2, lv3} - 'stocks': formatted_stocks, # 返回格式化后的股票列表 - 'hot_score': concept.get('hot_score') + 'score': None, + 'match_type': None, + 'price_info': { + 'avg_change_pct': concept.get('avg_change_pct') + }, + 'change_percent': concept.get('avg_change_pct', 0), + 'tags': [], # /price/list 不返回此字段 + 'outbreak_dates': [], # /price/list 不返回此字段 + 'hierarchy': concept.get('hierarchy'), + 'stocks': [], # /price/list 不返回此字段 + 'hot_score': None }) # 格式化日期为 YYYY-MM-DD - price_date = data.get('price_date', '') - formatted_date = str(price_date).split(' ')[0][:10] if price_date else '' + price_date = data.get('trade_date', '') + formatted_date = str(price_date)[:10] if price_date else '' return jsonify({ 'success': True, diff --git a/src/views/Concept/index.js b/src/views/Concept/index.js index a6b24332..c7a22996 100644 --- a/src/views/Concept/index.js +++ b/src/views/Concept/index.js @@ -449,48 +449,102 @@ const ConceptCenter = () => { setLoading(true); try { const sortToUse = customSortBy !== null ? customSortBy : sortBy; + const dateStr = date ? date.toISOString().split('T')[0] : null; - const requestBody = { - query: query, - size: pageSize, - page: page, - sort_by: sortToUse - }; + // 判断是否使用 /price/list 接口 + // 条件:无搜索词 + 按涨跌幅排序(升序或降序)+ 无层级筛选 + const isChangePctSort = sortToUse === 'change_pct' || sortToUse === 'change_pct_asc'; + const hasNoFilter = !filter?.lv1 && !filter?.lv2; + const shouldUsePriceList = !query && isChangePctSort && hasNoFilter; - if (date) { - requestBody.trade_date = date.toISOString().split('T')[0]; - } + if (shouldUsePriceList) { + // 使用 /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); + } - // 添加层级筛选参数 - if (filter?.lv1) { - requestBody.filter_lv1 = filter.lv1; - } - if (filter?.lv2) { - requestBody.filter_lv2 = filter.lv2; - } + const response = await fetch(`${API_BASE_URL}/price/list?${params}`); + if (!response.ok) throw new Error('获取涨跌幅数据失败'); - const response = await fetch(`${API_BASE_URL}/search`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestBody), - }); + const data = await response.json(); - 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 || []); - setTotalConcepts(data.total || 0); - setTotalPages(data.total_pages || 1); - setCurrentPage(data.page || 1); + if (data.trade_date) { + setSelectedDate(new Date(data.trade_date)); + } + } else { + // 使用原有的 /search 接口 + const requestBody = { + query: query, + size: pageSize, + page: page, + sort_by: sortToUse + }; - if (data.price_date) { - setSelectedDate(new Date(data.price_date)); + if (dateStr) { + 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) { - 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({ title: '获取数据失败', description: error.message, status: 'error', duration: 3000, isClosable: true }); diff --git a/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js b/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js index 4837c6ed..7e1f77f3 100644 --- a/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js +++ b/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js @@ -19,8 +19,8 @@ import { useColorModeValue, Tooltip, Flex, - keyframes, } from '@chakra-ui/react'; +import { keyframes } from '@emotion/react'; import { TrendingUp, TrendingDown, diff --git a/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js b/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js index eaabc3ed..e026f155 100644 --- a/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js +++ b/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js @@ -28,8 +28,8 @@ import { PopoverBody, Portal, chakra, - keyframes, } from '@chakra-ui/react'; +import { keyframes } from '@emotion/react'; import { TrendingUp, TrendingDown, diff --git a/src/views/StockOverview/components/HotspotOverview/index.js b/src/views/StockOverview/components/HotspotOverview/index.js index 10dc7bf7..9a72db0c 100644 --- a/src/views/StockOverview/components/HotspotOverview/index.js +++ b/src/views/StockOverview/components/HotspotOverview/index.js @@ -27,8 +27,8 @@ import { GridItem, IconButton, Collapse, - keyframes, } from '@chakra-ui/react'; +import { keyframes } from '@emotion/react'; import { Flame, List,