fix: 修复 Mock 环境相关概念返回空结果问题

问题分析:
- 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 <noreply@anthropic.com>
This commit is contained in:
zdl
2025-11-03 16:40:25 +08:00
parent b95607e9b4
commit b14eb175f5
3 changed files with 207 additions and 21 deletions

View File

@@ -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<Object>} 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 (
<Box bg={sectionBg} p={3} borderRadius="md">
<Center py={4}>
<Spinner size="md" color="blue.500" mr={2} />
<Text color={textColor} fontSize="sm">加载相关概念中...</Text>
</Center>
</Box>
);
}
// 如果没有概念,不渲染
if (!concepts || concepts.length === 0) {
return null;
}
@@ -86,7 +216,7 @@ const RelatedConceptsSection = ({ keywords, effectiveTradingDate, eventTime }) =
{/* 简单模式:横向卡片列表(总是显示) */}
<Flex gap={2} flexWrap="wrap" mb={isExpanded ? 3 : 0}>
{keywords.map((concept, index) => (
{concepts.map((concept, index) => (
<SimpleConceptCard
key={index}
concept={concept}
@@ -106,7 +236,7 @@ const RelatedConceptsSection = ({ keywords, effectiveTradingDate, eventTime }) =
<Collapse in={isExpanded} animateOpacity>
{/* 详细概念卡片网格 */}
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
{keywords.map((concept, index) => (
{concepts.map((concept, index) => (
<DetailedConceptCard
key={index}
concept={concept}