// 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;