334 lines
10 KiB
JavaScript
334 lines
10 KiB
JavaScript
// src/views/Community/components/DynamicNewsDetail/StockListItem.js
|
||
// 股票卡片组件(融合表格功能的卡片样式)
|
||
|
||
import React, { useState } from 'react';
|
||
import {
|
||
Box,
|
||
Flex,
|
||
VStack,
|
||
HStack,
|
||
Text,
|
||
Button,
|
||
IconButton,
|
||
Collapse,
|
||
Tooltip,
|
||
Badge,
|
||
useColorModeValue,
|
||
} from '@chakra-ui/react';
|
||
import { StarIcon } from '@chakra-ui/icons';
|
||
import MiniTimelineChart from '../StockDetailPanel/components/MiniTimelineChart';
|
||
import MiniKLineChart from './MiniKLineChart';
|
||
import StockChartModal from '../../../../components/StockChart/StockChartModal';
|
||
import CitedContent from '../../../../components/Citation/CitedContent';
|
||
import { getChangeColor } from '../../../../utils/colorUtils';
|
||
|
||
/**
|
||
* 股票卡片组件
|
||
* @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 cardBg = useColorModeValue('white', 'gray.800');
|
||
const borderColor = useColorModeValue('gray.200', 'gray.700');
|
||
const codeColor = useColorModeValue('blue.600', 'blue.300');
|
||
const nameColor = useColorModeValue('gray.700', 'gray.300');
|
||
const descColor = useColorModeValue('gray.600', 'gray.400');
|
||
const dividerColor = useColorModeValue('gray.200', 'gray.600');
|
||
|
||
const [isDescExpanded, setIsDescExpanded] = useState(false);
|
||
const [isModalOpen, setIsModalOpen] = 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 (
|
||
<>
|
||
<Box
|
||
bg={cardBg}
|
||
borderWidth="1px"
|
||
borderColor={borderColor}
|
||
borderRadius="lg"
|
||
p={3}
|
||
position="relative"
|
||
overflow="visible"
|
||
_before={{
|
||
content: '""',
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
height: '3px',
|
||
bgGradient: 'linear(to-r, blue.400, purple.500, pink.500)',
|
||
borderTopLeftRadius: 'lg',
|
||
borderTopRightRadius: 'lg',
|
||
}}
|
||
_hover={{
|
||
boxShadow: 'lg',
|
||
borderColor: 'blue.300',
|
||
}}
|
||
transition="all 0.2s"
|
||
>
|
||
{/* 单行紧凑布局:名称+涨跌幅 | 分时图 | K线图 | 关联描述 */}
|
||
<HStack spacing={3} align="stretch">
|
||
{/* 左侧:股票名称 + 涨跌幅(垂直排列) - 收窄 */}
|
||
<VStack
|
||
align="stretch"
|
||
spacing={1}
|
||
minW="100px"
|
||
maxW="120px"
|
||
justify="center"
|
||
flexShrink={0}
|
||
>
|
||
<Tooltip
|
||
label="点击查看股票详情"
|
||
placement="top"
|
||
hasArrow
|
||
bg="blue.600"
|
||
color="white"
|
||
fontSize="xs"
|
||
>
|
||
<Text
|
||
fontSize="sm"
|
||
fontWeight="bold"
|
||
color={codeColor}
|
||
noOfLines={1}
|
||
cursor="pointer"
|
||
onClick={handleViewDetail}
|
||
_hover={{ textDecoration: 'underline' }}
|
||
>
|
||
{stock.stock_name}
|
||
</Text>
|
||
</Tooltip>
|
||
<HStack spacing={1} align="center">
|
||
<Text
|
||
fontSize="lg"
|
||
fontWeight="bold"
|
||
color={getChangeColor(change)}
|
||
>
|
||
{formatChange(change)}
|
||
</Text>
|
||
{onWatchlistToggle && (
|
||
<IconButton
|
||
size="xs"
|
||
variant={isInWatchlist ? 'solid' : 'ghost'}
|
||
colorScheme={isInWatchlist ? 'yellow' : 'gray'}
|
||
icon={<StarIcon />}
|
||
onClick={handleWatchlistClick}
|
||
aria-label={isInWatchlist ? '已关注' : '加自选'}
|
||
borderRadius="full"
|
||
/>
|
||
)}
|
||
</HStack>
|
||
</VStack>
|
||
|
||
{/* 分时图 - 固定宽度 */}
|
||
<Box
|
||
w="160px"
|
||
borderWidth="1px"
|
||
borderColor={useColorModeValue('blue.100', 'blue.700')}
|
||
borderRadius="md"
|
||
p={2}
|
||
bg={useColorModeValue('blue.50', 'blue.900')}
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
setIsModalOpen(true);
|
||
}}
|
||
cursor="pointer"
|
||
flexShrink={0}
|
||
_hover={{
|
||
borderColor: useColorModeValue('blue.300', 'blue.500'),
|
||
boxShadow: 'sm'
|
||
}}
|
||
transition="all 0.2s"
|
||
>
|
||
<Text
|
||
fontSize="xs"
|
||
color={useColorModeValue('blue.700', 'blue.200')}
|
||
mb={1}
|
||
fontWeight="semibold"
|
||
>
|
||
📈 分时
|
||
</Text>
|
||
<MiniTimelineChart
|
||
stockCode={stock.stock_code}
|
||
eventTime={eventTime}
|
||
/>
|
||
</Box>
|
||
|
||
{/* K线图 - 固定宽度 */}
|
||
<Box
|
||
w="160px"
|
||
borderWidth="1px"
|
||
borderColor={useColorModeValue('purple.100', 'purple.700')}
|
||
borderRadius="md"
|
||
p={2}
|
||
bg={useColorModeValue('purple.50', 'purple.900')}
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
setIsModalOpen(true);
|
||
}}
|
||
cursor="pointer"
|
||
flexShrink={0}
|
||
_hover={{
|
||
borderColor: useColorModeValue('purple.300', 'purple.500'),
|
||
boxShadow: 'sm'
|
||
}}
|
||
transition="all 0.2s"
|
||
>
|
||
<Text
|
||
fontSize="xs"
|
||
color={useColorModeValue('purple.700', 'purple.200')}
|
||
mb={1}
|
||
fontWeight="semibold"
|
||
>
|
||
📊 日线
|
||
</Text>
|
||
<MiniKLineChart
|
||
stockCode={stock.stock_code}
|
||
eventTime={eventTime}
|
||
/>
|
||
</Box>
|
||
|
||
{/* 关联描述 - 升级和降级处理 */}
|
||
{stock.relation_desc && (
|
||
<Box flex={1} minW={0}>
|
||
{stock.relation_desc?.data ? (
|
||
// 升级:带引用来源的版本
|
||
<CitedContent
|
||
data={stock.relation_desc}
|
||
title=""
|
||
showAIBadge={true}
|
||
containerStyle={{
|
||
backgroundColor: useColorModeValue('#f7fafc', 'rgba(45, 55, 72, 0.6)'),
|
||
borderRadius: '8px',
|
||
padding: '0',
|
||
}}
|
||
/>
|
||
) : (
|
||
// 降级:纯文本版本(保留展开/收起功能)
|
||
<Tooltip
|
||
label={isDescExpanded ? "点击收起" : "点击展开完整描述"}
|
||
placement="top"
|
||
hasArrow
|
||
bg="gray.600"
|
||
color="white"
|
||
fontSize="xs"
|
||
>
|
||
<Box
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
setIsDescExpanded(!isDescExpanded);
|
||
}}
|
||
cursor="pointer"
|
||
px={3}
|
||
py={2}
|
||
bg={useColorModeValue('gray.50', 'gray.700')}
|
||
borderRadius="md"
|
||
_hover={{
|
||
bg: useColorModeValue('gray.100', 'gray.600'),
|
||
}}
|
||
transition="background 0.2s"
|
||
position="relative"
|
||
>
|
||
{/* 去掉"关联描述"标题 */}
|
||
<Collapse in={isDescExpanded} startingHeight={40}>
|
||
<Text
|
||
fontSize="sm"
|
||
color={nameColor}
|
||
lineHeight="1.6"
|
||
>
|
||
{relationText}
|
||
</Text>
|
||
</Collapse>
|
||
|
||
{/* 提示信息 */}
|
||
{isDescExpanded && (
|
||
<Text
|
||
fontSize="xs"
|
||
color="gray.500"
|
||
mt={2}
|
||
fontStyle="italic"
|
||
>
|
||
⚠️ AI生成,仅供参考
|
||
</Text>
|
||
)}
|
||
</Box>
|
||
</Tooltip>
|
||
)}
|
||
</Box>
|
||
)}
|
||
</HStack>
|
||
</Box>
|
||
|
||
{/* 股票详情弹窗 - 未打开时不渲染 */}
|
||
{isModalOpen && (
|
||
<StockChartModal
|
||
isOpen={isModalOpen}
|
||
onClose={() => setIsModalOpen(false)}
|
||
stock={stock}
|
||
eventTime={eventTime}
|
||
size="6xl"
|
||
/>
|
||
)}
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default StockListItem;
|