// src/views/Community/components/DynamicNewsDetail/StockListItem.js // 股票卡片组件(融合表格功能的卡片样式) import React, { useState } from 'react'; import { useSelector } from 'react-redux'; import { Box, Flex, VStack, HStack, Text, Button, IconButton, Collapse, Tooltip, Badge, useColorModeValue, } from '@chakra-ui/react'; import { StarIcon } from '@chakra-ui/icons'; import { Tag } from 'antd'; import { RobotOutlined } from '@ant-design/icons'; import { selectIsMobile } from '@store/slices/deviceSlice'; import MiniTimelineChart from '../StockDetailPanel/components/MiniTimelineChart'; import MiniKLineChart from './MiniKLineChart'; import TimelineChartModal from '@components/StockChart/TimelineChartModal'; import KLineChartModal from '@components/StockChart/KLineChartModal'; import { getChangeColor } from '@utils/colorUtils'; import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; /** * 股票卡片组件 * @param {Object} props * @param {Object} props.stock - 股票对象 * @param {string} props.stock.stock_name - 股票名称 * @param {string} props.stock.stock_code - 股票代码 * @param {string} props.stock.relation_desc - 关联描述 * @param {Object} props.quote - 股票行情数据(可选) * @param {number} props.quote.change - 涨跌幅 * @param {string} props.eventTime - 事件时间(可选) * @param {boolean} props.isInWatchlist - 是否在自选股中 * @param {Function} props.onWatchlistToggle - 切换自选股回调 */ const StockListItem = ({ stock, quote = null, eventTime = null, isInWatchlist = false, onWatchlistToggle }) => { const isMobile = useSelector(selectIsMobile); const cardBg = PROFESSIONAL_COLORS.background.card; const borderColor = PROFESSIONAL_COLORS.border.default; const codeColor = '#3B82F6'; const nameColor = PROFESSIONAL_COLORS.text.primary; const descColor = PROFESSIONAL_COLORS.text.secondary; const dividerColor = PROFESSIONAL_COLORS.border.default; const [isDescExpanded, setIsDescExpanded] = useState(false); const [isTimelineModalOpen, setIsTimelineModalOpen] = useState(false); const [isKLineModalOpen, setIsKLineModalOpen] = useState(false); const handleViewDetail = () => { const stockCode = stock.stock_code.split('.')[0]; window.open(`https://valuefrontier.cn/company?scode=${stockCode}`, '_blank'); }; const handleWatchlistClick = (e) => { e.stopPropagation(); onWatchlistToggle?.(stock.stock_code, isInWatchlist); }; // 格式化涨跌幅显示 const formatChange = (value) => { if (value === null || value === undefined || isNaN(value)) return '--'; const prefix = value > 0 ? '+' : ''; return `${prefix}${parseFloat(value).toFixed(2)}%`; }; // 使用工具函数获取涨跌幅颜色(已从 colorUtils 导入) // 获取涨跌幅数据(优先使用 quote,fallback 到 stock) const change = quote?.change ?? stock.daily_change ?? null; // 处理关联描述 const getRelationDesc = () => { const relationDesc = stock.relation_desc; if (!relationDesc) return '--'; if (typeof relationDesc === 'string') { return relationDesc; } else if (typeof relationDesc === 'object' && relationDesc.data && Array.isArray(relationDesc.data)) { // 新格式:{data: [{query_part: "...", sentences: "..."}]} return relationDesc.data .map(item => item.query_part || item.sentences || '') .filter(s => s) .join(';') || '--'; } return '--'; }; const relationText = getRelationDesc(); const maxLength = 50; // 收缩时显示的最大字符数 const needTruncate = relationText && relationText !== '--' && relationText.length > maxLength; return ( <> {/* 单行紧凑布局:名称+涨跌幅 | 分时图 | K线图 | 关联描述 */} {/* 左侧:股票信息区 */} {/* 股票代码 + 名称 + 涨跌幅 */} {stock.stock_code} {stock.stock_name} {formatChange(change)} {onWatchlistToggle && ( } onClick={handleWatchlistClick} aria-label={isInWatchlist ? '已关注' : '加自选'} borderRadius="full" borderColor={isInWatchlist ? undefined : 'gray.300'} /> )} {/* 分时图 - 自适应 */} { e.stopPropagation(); setIsTimelineModalOpen(true); }} cursor="pointer" align="stretch" spacing={0} _hover={{ borderColor: '#3B82F6', boxShadow: '0 0 10px rgba(59, 130, 246, 0.3)', transform: 'translateY(-1px)' }} transition="all 0.2s" > 📈 分时 {/* K线图 - 自适应 */} { e.stopPropagation(); setIsKLineModalOpen(true); }} cursor="pointer" align="stretch" spacing={0} _hover={{ borderColor: '#A855F7', boxShadow: '0 0 10px rgba(168, 85, 247, 0.3)', transform: 'translateY(-1px)' }} transition="all 0.2s" > 📊 日线 {/* 关联描述 - 升级和降级处理 */} {stock.relation_desc && ( {Array.isArray(stock.relation_desc?.data) ? ( // 升级:带引用来源的版本 - 添加折叠功能 { e.stopPropagation(); setIsDescExpanded(!isDescExpanded); }} cursor="pointer" bg={PROFESSIONAL_COLORS.background.secondary} borderRadius="md" _hover={{ bg: PROFESSIONAL_COLORS.background.cardHover, }} transition="background 0.2s" position="relative" > {/* AI 标识 - 行内显示在文字前面 */} } color="purple" style={{ fontSize: 12, padding: '2px 8px', marginRight: 8, verticalAlign: 'middle', display: 'inline-flex', }} > AI合成 {/* 渲染 query_part,每句带来源悬停提示 */} {Array.isArray(stock.relation_desc?.data) && stock.relation_desc.data.filter(item => item.query_part).map((item, index, arr) => ( {item.sentences && ( {item.sentences} )} 来源:{item.organization || '未知'}{item.author ? ` / ${item.author}` : ''} {item.report_title && ( {item.report_title} )} {item.declare_date && ( {new Date(item.declare_date).toLocaleDateString('zh-CN')} )} } placement="top" hasArrow bg="rgba(20, 20, 20, 0.95)" color="white" maxW="420px" > {item.query_part} {index < arr.length - 1 && ';'} ))} ) : ( // 降级:纯文本版本(保留展开/收起功能) { e.stopPropagation(); setIsDescExpanded(!isDescExpanded); }} cursor="pointer" bg={PROFESSIONAL_COLORS.background.secondary} borderRadius="md" _hover={{ bg: PROFESSIONAL_COLORS.background.cardHover, }} transition="background 0.2s" position="relative" > {/* 去掉"关联描述"标题 */} {relationText} {/* 提示信息 */} {isDescExpanded && ( ⚠️ AI生成,仅供参考 )} )} )} {/* 分时图弹窗 */} {isTimelineModalOpen && ( setIsTimelineModalOpen(false)} stock={stock} eventTime={eventTime} /> )} {/* K线图弹窗 */} {isKLineModalOpen && ( setIsKLineModalOpen(false)} stock={stock} eventTime={eventTime} /> )} ); }; export default StockListItem;