202 lines
7.8 KiB
JavaScript
202 lines
7.8 KiB
JavaScript
// src/views/Community/components/PopularKeywords.js
|
||
import React, { useState, useEffect } from 'react';
|
||
import { Tag, Space, Button } from 'antd';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { RightOutlined } from '@ant-design/icons';
|
||
import { logger } from '../../../utils/logger';
|
||
|
||
// 使用相对路径,让 MSW 在开发环境可以拦截请求
|
||
const API_BASE_URL = '/concept-api';
|
||
|
||
// 获取域名前缀
|
||
const DOMAIN_PREFIX = process.env.NODE_ENV === 'production'
|
||
? ''
|
||
: 'https://valuefrontier.cn';
|
||
|
||
const PopularKeywords = ({ onKeywordClick, keywords: propKeywords }) => {
|
||
const [keywords, setKeywords] = useState([]);
|
||
const navigate = useNavigate();
|
||
|
||
// 加载热门概念(涨幅前20)
|
||
const loadPopularConcepts = async () => {
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/search`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
query: '', // 空查询获取所有概念
|
||
size: 20, // 获取前20个
|
||
page: 1,
|
||
sort_by: 'change_pct' // 按涨跌幅排序
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.results) {
|
||
// 转换数据格式
|
||
const formattedData = data.results.map(item => ({
|
||
keyword: item.concept,
|
||
count: item.stock_count,
|
||
change_pct: item.price_info?.avg_change_pct || 0,
|
||
concept_id: item.concept_id
|
||
}));
|
||
setKeywords(formattedData);
|
||
logger.debug('PopularKeywords', '热门概念加载成功(自己请求)', {
|
||
count: formattedData.length
|
||
});
|
||
}
|
||
} catch (error) {
|
||
logger.error('PopularKeywords', 'loadPopularConcepts', error);
|
||
setKeywords([]);
|
||
}
|
||
};
|
||
|
||
// 处理从父组件传入的数据
|
||
useEffect(() => {
|
||
if (propKeywords && propKeywords.length > 0) {
|
||
// 使用父组件传入的数据
|
||
setKeywords(propKeywords);
|
||
logger.debug('PopularKeywords', '使用父组件传入的数据', {
|
||
count: propKeywords.length
|
||
});
|
||
} else {
|
||
// 没有 prop 数据,自己加载
|
||
loadPopularConcepts();
|
||
}
|
||
}, [propKeywords]);
|
||
|
||
// 根据涨跌幅获取标签颜色
|
||
const getTagColor = (changePct) => {
|
||
if (changePct > 5) return 'red';
|
||
if (changePct > 3) return 'volcano';
|
||
if (changePct > 1) return 'orange';
|
||
if (changePct > 0) return 'gold';
|
||
if (changePct === 0) return 'default';
|
||
if (changePct > -1) return 'lime';
|
||
if (changePct > -3) return 'green';
|
||
if (changePct > -5) return 'cyan';
|
||
return 'blue';
|
||
};
|
||
|
||
// 格式化涨跌幅显示
|
||
const formatChangePct = (pct) => {
|
||
if (pct === null || pct === undefined) return '';
|
||
const formatted = pct.toFixed(2);
|
||
if (pct > 0) return `+${formatted}%`;
|
||
return `${formatted}%`;
|
||
};
|
||
|
||
// ✅ 修复:处理概念标签点击
|
||
const handleConceptClick = (concept) => {
|
||
// 优先调用父组件传入的回调(用于搜索框显示和触发搜索)
|
||
if (onKeywordClick) {
|
||
onKeywordClick(concept.keyword);
|
||
logger.debug('PopularKeywords', '调用 onKeywordClick 回调', {
|
||
keyword: concept.keyword
|
||
});
|
||
} else {
|
||
// 如果没有回调,则跳转到对应概念的页面(原有行为)
|
||
const url = `${DOMAIN_PREFIX}/htmls/${encodeURIComponent(concept.keyword)}.html`;
|
||
window.open(url, '_blank');
|
||
logger.debug('PopularKeywords', '跳转到概念页面', {
|
||
keyword: concept.keyword,
|
||
url
|
||
});
|
||
}
|
||
};
|
||
|
||
// 处理"更多概念"按钮点击 - 跳转到概念中心
|
||
const handleMoreClick = () => {
|
||
navigate('/concepts');
|
||
};
|
||
|
||
return (
|
||
<>
|
||
{keywords && keywords.length > 0 && (
|
||
<div style={{ position: 'relative' }}>
|
||
<Space
|
||
size={[6, 6]}
|
||
wrap
|
||
style={{
|
||
alignItems: 'center',
|
||
maxHeight: '29px', // 约两行的高度 (每行约28-30px)
|
||
overflow: 'hidden',
|
||
paddingRight: '90px' // 为右侧按钮留出空间
|
||
}}
|
||
>
|
||
{/* 标题 */}
|
||
<span style={{
|
||
color: '#ff4d4f',
|
||
fontSize: 13,
|
||
fontWeight: 500,
|
||
marginRight: 4
|
||
}}>
|
||
热门概念:
|
||
</span>
|
||
|
||
{/* 所有标签 */}
|
||
{keywords.map((item, index) => (
|
||
<Tag
|
||
key={item.concept_id || `keyword-${index}`}
|
||
color={getTagColor(item.change_pct)}
|
||
style={{
|
||
cursor: 'pointer',
|
||
padding: '1px 6px',
|
||
fontSize: 12,
|
||
transition: 'all 0.3s',
|
||
margin: 0
|
||
}}
|
||
onClick={() => handleConceptClick(item)}
|
||
onMouseEnter={(e) => {
|
||
e.currentTarget.style.transform = 'scale(1.05)';
|
||
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
|
||
}}
|
||
onMouseLeave={(e) => {
|
||
e.currentTarget.style.transform = 'scale(1)';
|
||
e.currentTarget.style.boxShadow = 'none';
|
||
}}
|
||
>
|
||
<span>{item.keyword}</span>
|
||
<span style={{
|
||
marginLeft: 4,
|
||
fontWeight: 'bold'
|
||
}}>
|
||
{formatChangePct(item.change_pct)}
|
||
</span>
|
||
{/* <span style={{
|
||
marginLeft: 3,
|
||
fontSize: 10,
|
||
opacity: 0.75
|
||
}}> */}
|
||
{/* ({item.count}股) */}
|
||
{/* </span> */}
|
||
</Tag>
|
||
))}
|
||
</Space>
|
||
|
||
{/* 更多概念按钮 - 固定在第二行右侧 */}
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
onClick={handleMoreClick}
|
||
style={{
|
||
position: 'absolute',
|
||
top: 0,
|
||
right: 0,
|
||
fontSize: 12,
|
||
padding: '0 4px',
|
||
height: 'auto'
|
||
}}
|
||
>
|
||
更多概念 <RightOutlined style={{ fontSize: 10 }} />
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default PopularKeywords; |