From b14eb175f5d4d330a6cedabdfe06789ec13c0ca5 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Mon, 3 Nov 2025 16:40:25 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Mock=20=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E7=9B=B8=E5=85=B3=E6=A6=82=E5=BF=B5=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=A9=BA=E7=BB=93=E6=9E=9C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题分析: - Mock handler 的过滤逻辑过于严格 - 只保留概念名包含查询关键词的结果 - 导致大部分查询返回空数组 解决方案: ✅ 移除字符串匹配过滤逻辑 - Mock 环境直接返回热门概念 - 模拟真实 API 的语义搜索行为 - 确保每次搜索都有结果展示 ✅ 添加详细调试日志 - RelatedConceptsSection 组件渲染日志 - useEffect 触发和参数日志 - 请求发送和响应详情 - 数据处理过程追踪 ✅ 完善 Mock 数据结构 - 添加 score, match_type, happened_times, stocks - 支持详细卡片展示 - 数据结构与线上完全一致 修改文件: - src/mocks/handlers/concept.js - src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/mocks/handlers/concept.js | 76 +++++++-- .../DynamicNewsDetailPanel.js | 2 +- .../RelatedConceptsSection/index.js | 150 ++++++++++++++++-- 3 files changed, 207 insertions(+), 21 deletions(-) diff --git a/src/mocks/handlers/concept.js b/src/mocks/handlers/concept.js index 20d96f37..546043e3 100644 --- a/src/mocks/handlers/concept.js +++ b/src/mocks/handlers/concept.js @@ -6,6 +6,48 @@ import { http, HttpResponse } from 'msw'; // 模拟延迟 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +// 生成历史触发时间(3-5个历史日期) +const generateHappenedTimes = (seed) => { + const times = []; + const count = 3 + (seed % 3); // 3-5个时间点 + for (let i = 0; i < count; i++) { + const daysAgo = 30 + (seed * 7 + i * 11) % 330; // 30-360天前 + const date = new Date(); + date.setDate(date.getDate() - daysAgo); + times.push(date.toISOString().split('T')[0]); + } + return times.sort().reverse(); // 降序排列 +}; + +// 生成核心相关股票 +const generateStocksForConcept = (seed, count = 4) => { + const stockPool = [ + { name: '贵州茅台', code: '600519' }, + { name: '宁德时代', code: '300750' }, + { name: '中国平安', code: '601318' }, + { name: '比亚迪', code: '002594' }, + { name: '隆基绿能', code: '601012' }, + { name: '阳光电源', code: '300274' }, + { name: '三一重工', code: '600031' }, + { name: '中芯国际', code: '688981' }, + { name: '京东方A', code: '000725' }, + { name: '立讯精密', code: '002475' } + ]; + + const stocks = []; + for (let i = 0; i < count; i++) { + const stockIndex = (seed + i * 7) % stockPool.length; + const stock = stockPool[stockIndex]; + stocks.push({ + stock_name: stock.name, + stock_code: stock.code, + reason: `作为行业龙头企业,${stock.name}在该领域具有核心竞争优势,市场份额领先,技术实力雄厚。`, + change_pct: parseFloat((Math.random() * 15 - 5).toFixed(2)) // -5% ~ +10% + }); + } + return stocks; +}; + // 生成热门概念数据 const generatePopularConcepts = (size = 20) => { const concepts = [ @@ -22,21 +64,38 @@ const generatePopularConcepts = (size = 20) => { '疫苗', '中药', '医疗信息化', '智慧医疗', '基因测序' ]; + const conceptDescriptions = { + '人工智能': '人工智能是"技术突破+政策扶持"双轮驱动的硬科技主题。随着大模型技术的突破,AI应用场景不断拓展,预计将催化算力、数据、应用三大产业链。', + '新能源汽车': '新能源汽车行业景气度持续向好,渗透率不断提升。政策支持力度大,产业链上下游企业均受益明显。', + '半导体': '国产半导体替代加速,自主可控需求强烈。政策和资金支持力度大,行业迎来黄金发展期。', + '光伏': '光伏装机量快速增长,成本持续下降,行业景气度维持高位。双碳目标下,光伏行业前景广阔。', + '锂电池': '锂电池技术进步,成本优势扩大,下游应用领域持续扩张。新能源汽车和储能需求旺盛。', + '储能': '储能市场爆发式增长,政策支持力度大,应用场景不断拓展。未来市场空间巨大。', + '默认': '该概念市场关注度较高,具有一定的投资价值。相关企业技术实力雄厚,市场前景广阔。' + }; + + const matchTypes = ['hybrid_knn', 'keyword', 'semantic']; + const results = []; for (let i = 0; i < Math.min(size, concepts.length); i++) { const changePct = (Math.random() * 12 - 2).toFixed(2); // -2% 到 +10% const stockCount = Math.floor(Math.random() * 50) + 10; // 10-60 只股票 + const score = parseFloat((Math.random() * 5 + 3).toFixed(2)); // 3-8 分数范围 results.push({ concept: concepts[i], concept_id: `CONCEPT_${1000 + i}`, stock_count: stockCount, + score: score, // 相关度分数 + match_type: matchTypes[i % 3], // 匹配类型 + description: conceptDescriptions[concepts[i]] || conceptDescriptions['默认'], price_info: { avg_change_pct: parseFloat(changePct), - avg_price: (Math.random() * 100 + 10).toFixed(2), - total_market_cap: (Math.random() * 1000 + 100).toFixed(2) + avg_price: parseFloat((Math.random() * 100 + 10).toFixed(2)), + total_market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2)) }, - description: `${concepts[i]}相关概念股`, + happened_times: generateHappenedTimes(i), // 历史触发时间 + stocks: generateStocksForConcept(i, 4), // 核心相关股票 hot_score: Math.floor(Math.random() * 100) }); } @@ -115,15 +174,12 @@ export const conceptHandlers = [ console.log('[Mock Concept] 搜索概念:', { query, size, page, sort_by }); - // 生成数据 + // 生成数据(不过滤,模拟真实 API 的语义搜索返回热门概念) let results = generatePopularConcepts(size); + console.log('[Mock Concept] 生成概念数量:', results.length); - // 如果有查询关键词,过滤结果 - if (query) { - results = results.filter(item => - item.concept.toLowerCase().includes(query.toLowerCase()) - ); - } + // Mock 环境下不做过滤,直接返回热门概念 + // 真实环境会根据 query 进行语义搜索 // 根据排序字段排序 if (sort_by === 'change_pct') { diff --git a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js index 3926e314..94108439 100644 --- a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js +++ b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js @@ -152,7 +152,7 @@ const DynamicNewsDetailPanel = ({ event }) => { {/* 相关概念 */} diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js index acc9e471..33c6ebb6 100644 --- a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js +++ b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js @@ -1,7 +1,7 @@ // src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js // 相关概念区组件(主组件) -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Box, SimpleGrid, @@ -9,34 +9,164 @@ import { Button, Collapse, Heading, + Center, + Spinner, + Text, useColorModeValue, } from '@chakra-ui/react'; import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; import { useNavigate } from 'react-router-dom'; +import moment from 'moment'; import SimpleConceptCard from './SimpleConceptCard'; import DetailedConceptCard from './DetailedConceptCard'; import TradingDateInfo from './TradingDateInfo'; +import { logger } from '../../../../../utils/logger'; /** * 相关概念区组件 * @param {Object} props - * @param {Array} props.keywords - 相关概念数组 - * - name: 概念名称 - * - stock_count: 相关股票数量 - * - relevance: 相关度(0-100) + * @param {string} props.eventTitle - 事件标题(用于搜索概念) * @param {string} props.effectiveTradingDate - 有效交易日期(涨跌幅数据日期) * @param {string|Object} props.eventTime - 事件发生时间 */ -const RelatedConceptsSection = ({ keywords, effectiveTradingDate, eventTime }) => { +const RelatedConceptsSection = ({ eventTitle, effectiveTradingDate, eventTime }) => { const [isExpanded, setIsExpanded] = useState(false); + const [concepts, setConcepts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); const navigate = useNavigate(); // 颜色配置 const sectionBg = useColorModeValue('gray.50', 'gray.750'); const headingColor = useColorModeValue('gray.700', 'gray.200'); + const textColor = useColorModeValue('gray.600', 'gray.400'); - // 如果没有关键词,不渲染 - if (!keywords || keywords.length === 0) { + console.log('[RelatedConceptsSection] 组件渲染', { + eventTitle, + effectiveTradingDate, + eventTime, + loading, + conceptsCount: concepts?.length || 0, + error + }); + + // 搜索相关概念 + useEffect(() => { + const searchConcepts = async () => { + console.log('[RelatedConceptsSection] useEffect 触发', { + eventTitle, + effectiveTradingDate + }); + + if (!eventTitle || !effectiveTradingDate) { + console.log('[RelatedConceptsSection] 缺少必要参数,跳过搜索', { + hasEventTitle: !!eventTitle, + hasEffectiveTradingDate: !!effectiveTradingDate + }); + setLoading(false); + return; + } + + try { + setLoading(true); + setError(null); + + // 格式化交易日期 + let formattedTradeDate; + if (typeof effectiveTradingDate === 'string') { + formattedTradeDate = effectiveTradingDate; + } else if (effectiveTradingDate instanceof Date) { + formattedTradeDate = moment(effectiveTradingDate).format('YYYY-MM-DD'); + } else if (moment.isMoment(effectiveTradingDate)) { + formattedTradeDate = effectiveTradingDate.format('YYYY-MM-DD'); + } else { + formattedTradeDate = moment().format('YYYY-MM-DD'); + } + + const requestBody = { + query: eventTitle, + size: 5, + page: 1, + sort_by: "_score", + trade_date: formattedTradeDate + }; + + console.log('[RelatedConceptsSection] 发送请求', { + url: '/concept-api/search', + requestBody + }); + logger.debug('RelatedConceptsSection', '搜索概念', requestBody); + + const response = await fetch('/concept-api/search', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody) + }); + + console.log('[RelatedConceptsSection] 响应状态', { + ok: response.ok, + status: response.status, + statusText: response.statusText + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log('[RelatedConceptsSection] 响应数据', { + hasResults: !!data.results, + resultsCount: data.results?.length || 0, + hasDataConcepts: !!(data.data && data.data.concepts), + data: data + }); + logger.debug('RelatedConceptsSection', '概念搜索响应', { + hasResults: !!data.results, + resultsCount: data.results?.length || 0 + }); + + // 设置概念数据 + if (data.results && Array.isArray(data.results)) { + console.log('[RelatedConceptsSection] 设置概念数据 (results)', data.results); + setConcepts(data.results); + } else if (data.data && data.data.concepts) { + // 向后兼容 + console.log('[RelatedConceptsSection] 设置概念数据 (data.concepts)', data.data.concepts); + setConcepts(data.data.concepts); + } else { + console.log('[RelatedConceptsSection] 没有找到概念数据,设置为空数组'); + setConcepts([]); + } + } catch (err) { + console.error('[RelatedConceptsSection] 搜索概念失败', err); + logger.error('RelatedConceptsSection', 'searchConcepts', err); + setError('加载概念数据失败'); + setConcepts([]); + } finally { + console.log('[RelatedConceptsSection] 加载完成'); + setLoading(false); + } + }; + + searchConcepts(); + }, [eventTitle, effectiveTradingDate]); + + // 加载中状态 + if (loading) { + return ( + +
+ + 加载相关概念中... +
+
+ ); + } + + // 如果没有概念,不渲染 + if (!concepts || concepts.length === 0) { return null; } @@ -86,7 +216,7 @@ const RelatedConceptsSection = ({ keywords, effectiveTradingDate, eventTime }) = {/* 简单模式:横向卡片列表(总是显示) */} - {keywords.map((concept, index) => ( + {concepts.map((concept, index) => ( {/* 详细概念卡片网格 */} - {keywords.map((concept, index) => ( + {concepts.map((concept, index) => (