feat: 10.10线上最新代码提交

This commit is contained in:
zdl
2025-10-11 16:16:02 +08:00
parent 4d0dc109bc
commit 495ad758ea
3338 changed files with 460147 additions and 152745 deletions

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { useMemo, useState, useEffect } from 'react';
import {
Box,
Card,
@@ -36,7 +36,9 @@ import {
WrapItem,
useColorModeValue,
} from '@chakra-ui/react';
import { getFormattedTextProps } from '../../../utils/textUtils';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import './WordCloud.css';
import {
BarChart, Bar,
PieChart, Pie, Cell,
@@ -48,7 +50,7 @@ import {
Treemap,
Area, AreaChart,
} from 'recharts';
import ReactWordcloud from 'react-wordcloud';
// 颜色配置
const CHART_COLORS = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEEAD',
@@ -67,70 +69,34 @@ const WordCloud = ({ data }) => {
);
}
const treemapData = data.slice(0, 30).map((item, index) => ({
name: item.name,
size: item.value,
color: CHART_COLORS[index % CHART_COLORS.length]
const words = data.slice(0, 100).map(item => ({
text: item.name || item.text,
value: item.value || item.count || 1
}));
const CustomContent = (props) => {
const { x, y, width, height, name, size, color } = props;
const fontSize = Math.min(Math.max(Math.sqrt(width * height) / 4, 10), 24);
const options = {
rotations: 2,
rotationAngles: [-90, 0],
fontFamily: 'Microsoft YaHei, sans-serif',
fontSizes: [16, 80],
fontWeight: 'bold',
padding: 3,
scale: 'sqrt',
};
if (width < 30 || height < 20) return null;
return (
<g>
<rect
x={x}
y={y}
width={width}
height={height}
style={{
fill: color,
stroke: '#fff',
strokeWidth: 2,
strokeOpacity: 1,
}}
/>
<text
x={x + width / 2}
y={y + height / 2}
textAnchor="middle"
dominantBaseline="central"
fill="white"
fontSize={fontSize}
fontWeight="bold"
>
{name}
</text>
{width > 50 && height > 40 && (
<text
x={x + width / 2}
y={y + height / 2 + fontSize + 2}
textAnchor="middle"
dominantBaseline="central"
fill="white"
fontSize={fontSize * 0.6}
opacity={0.8}
>
{size}
</text>
)}
</g>
);
const callbacks = {
getWordColor: () => {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEEAD'];
return colors[Math.floor(Math.random() * colors.length)];
}
};
return (
<ResponsiveContainer width="100%" height={400}>
<Treemap
data={treemapData}
dataKey="size"
aspectRatio={4 / 3}
stroke="#fff"
content={<CustomContent />}
/>
</ResponsiveContainer>
<ReactWordcloud
words={words}
options={options}
callbacks={callbacks}
/>
);
};
@@ -610,7 +576,9 @@ export const StockDetailModal = ({ isOpen, onClose, selectedStock }) => {
<Box>
<Text fontWeight="bold" mb={2}>详细分析</Text>
<Box p={4} bg="gray.50" borderRadius="md">
<Text whiteSpace="pre-wrap">{selectedStock.summary}</Text>
<Text {...getFormattedTextProps(selectedStock.summary).props}>
{getFormattedTextProps(selectedStock.summary).children}
</Text>
</Box>
</Box>
</>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
@@ -18,9 +18,30 @@ import {
} from '@chakra-ui/react';
import { ChevronLeftIcon, ChevronRightIcon, CalendarIcon } from '@chakra-ui/icons';
const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
const EnhancedCalendar = ({
selectedDate,
onDateChange,
availableDates,
compact = false,
hideSelectionInfo = false,
hideLegend = false,
width,
cellHeight,
}) => {
const [currentMonth, setCurrentMonth] = useState(new Date());
// 当外部选择日期变化时,如果不在当前月,则切换到对应月份
useEffect(() => {
if (selectedDate) {
const isSameMonth =
currentMonth.getFullYear() === selectedDate.getFullYear() &&
currentMonth.getMonth() === selectedDate.getMonth();
if (!isSameMonth) {
setCurrentMonth(new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
}
}
}, [selectedDate]);
const getDaysInMonth = (date) => {
const year = date.getFullYear();
const month = date.getMonth();
@@ -79,34 +100,38 @@ const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
const calendarWidth = width ? width : (compact ? '420px' : '500px');
const dayCellHeight = cellHeight ? cellHeight : (compact ? 12 : 16); // Chakra units
const headerSize = compact ? 'md' : 'lg';
return (
<Card bg="white" boxShadow="2xl" borderRadius="xl" w="500px">
<CardHeader pb={3}>
<Card bg="white" boxShadow="2xl" borderRadius="xl" w={calendarWidth}>
<CardHeader pb={compact ? 2 : 3}>
<VStack spacing={3}>
<HStack justify="space-between" w="full">
<IconButton
icon={<ChevronLeftIcon />}
size="md"
size={compact ? 'sm' : 'md'}
variant="ghost"
onClick={() => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1))}
aria-label="上个月"
/>
<HStack spacing={2}>
<CalendarIcon boxSize={5} />
<Heading size="lg">
<Heading size={headerSize}>
{currentMonth.getFullYear()} {monthNames[currentMonth.getMonth()]}
</Heading>
</HStack>
<IconButton
icon={<ChevronRightIcon />}
size="md"
size={compact ? 'sm' : 'md'}
variant="ghost"
onClick={() => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1))}
aria-label="下个月"
/>
</HStack>
{selectedDate && (
{!hideSelectionInfo && selectedDate && (
<Alert status="info" borderRadius="md" fontSize="sm">
<AlertIcon />
<Text>
@@ -121,10 +146,10 @@ const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
</CardHeader>
<CardBody pt={2}>
<SimpleGrid columns={7} spacing={2}>
<SimpleGrid columns={7} spacing={compact ? 1 : 2}>
{weekDays.map(day => (
<Box key={day} textAlign="center" p={3}>
<Text fontSize="md" fontWeight="bold" color="gray.600">
<Box key={day} textAlign="center" p={compact ? 2 : 3}>
<Text fontSize={compact ? 'sm' : 'md'} fontWeight="bold" color="gray.600">
{day}
</Text>
</Box>
@@ -155,7 +180,7 @@ const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
<Box
as="button"
w="full"
h={16}
h={dayCellHeight}
borderRadius="lg"
position="relative"
bg={hasData ? getDateColor(dateData.count) : 'transparent'}
@@ -172,7 +197,7 @@ const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
cursor="pointer"
>
<Text
fontSize="lg"
fontSize={compact ? 'md' : 'lg'}
fontWeight={isToday || isSelected ? 'bold' : 'normal'}
color={isSelected ? 'blue.600' : 'gray.700'}
>
@@ -183,11 +208,11 @@ const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
position="absolute"
top="2px"
right="2px"
size="md"
size={compact ? 'sm' : 'md'}
colorScheme={getDateBadgeColor(dateData.count)}
fontSize="11px"
px={2}
minW="28px"
fontSize={compact ? '10px' : '11px'}
px={compact ? 1 : 2}
minW={compact ? '22px' : '28px'}
borderRadius="full"
>
{dateData.count}
@@ -199,7 +224,7 @@ const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
bottom="2px"
left="50%"
transform="translateX(-50%)"
fontSize="10px"
fontSize={compact ? '9px' : '10px'}
color="blue.500"
fontWeight="bold"
>
@@ -214,25 +239,28 @@ const EnhancedCalendar = ({ selectedDate, onDateChange, availableDates }) => {
})}
</SimpleGrid>
<Divider my={4} />
<VStack spacing={3}>
<Text fontSize="sm" fontWeight="bold" color="gray.600">涨停数量图例</Text>
<HStack justify="center" spacing={4}>
<HStack spacing={2}>
<Box w={5} h={5} bg="green.100" borderRadius="md" border="1px solid" borderColor="green.300" />
<Text fontSize="sm">少量 (50)</Text>
</HStack>
<HStack spacing={2}>
<Box w={5} h={5} bg="yellow.100" borderRadius="md" border="1px solid" borderColor="yellow.400" />
<Text fontSize="sm">中等 (51-80)</Text>
</HStack>
<HStack spacing={2}>
<Box w={5} h={5} bg="red.100" borderRadius="md" border="1px solid" borderColor="red.300" />
<Text fontSize="sm">大量 (>80)</Text>
</HStack>
</HStack>
</VStack>
{!hideLegend && (
<>
<Divider my={4} />
<VStack spacing={3}>
<Text fontSize="sm" fontWeight="bold" color="gray.600">涨停数量图例</Text>
<HStack justify="center" spacing={4}>
<HStack spacing={2}>
<Box w={5} h={5} bg="green.100" borderRadius="md" border="1px solid" borderColor="green.300" />
<Text fontSize="sm">少量 (50)</Text>
</HStack>
<HStack spacing={2}>
<Box w={5} h={5} bg="yellow.100" borderRadius="md" border="1px solid" borderColor="yellow.400" />
<Text fontSize="sm">中等 (51-80)</Text>
</HStack>
<HStack spacing={2}>
<Box w={5} h={5} bg="red.100" borderRadius="md" border="1px solid" borderColor="red.300" />
<Text fontSize="sm">大量 (&gt;80)</Text>
</HStack>
</HStack>
</VStack>
</>
)}
</CardBody>
</Card>
);

View File

@@ -39,6 +39,7 @@ import {
Alert,
AlertIcon,
} from '@chakra-ui/react';
import { formatTooltipText, getFormattedTextProps } from '../../../utils/textUtils';
import { SearchIcon, CalendarIcon, ViewIcon, ExternalLinkIcon, DownloadIcon } from '@chakra-ui/icons';
// 高级搜索组件
@@ -286,9 +287,26 @@ export const SearchResultsModal = ({ isOpen, onClose, searchResults, onStockClic
)}
</Td>
<Td maxW="300px">
<Tooltip label={stock.brief || stock.summary} placement="top">
<Text fontSize="sm" noOfLines={2}>
{stock.brief || stock.summary || '-'}
<Tooltip
label={formatTooltipText(stock.brief || stock.summary)}
placement="top"
hasArrow
bg="gray.800"
color="white"
px={3}
py={2}
borderRadius="md"
fontSize="sm"
maxW="400px"
whiteSpace="pre-line"
>
<Text
fontSize="sm"
noOfLines={2}
{...getFormattedTextProps(stock.brief || stock.summary || '-').props}
_hover={{ cursor: 'help' }}
>
{getFormattedTextProps(stock.brief || stock.summary || '-').children}
</Text>
</Tooltip>
</Td>

View File

@@ -23,14 +23,19 @@ import {
WrapItem,
Button,
useColorModeValue,
Collapse,
useDisclosure,
} from '@chakra-ui/react';
import { StarIcon, ViewIcon, TimeIcon, ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons';
import { getFormattedTextProps } from '../../../utils/textUtils';
const SectorDetails = ({ sortedSectors, totalStocks, onStockClick }) => {
const SectorDetails = ({ sortedSectors, totalStocks }) => {
// 使用 useRef 来维持展开状态,避免重新渲染时重置
const expandedSectorsRef = useRef([]);
const [expandedSectors, setExpandedSectors] = useState([]);
const [isInitialized, setIsInitialized] = useState(false);
// 新增:管理每个股票涨停原因的展开状态
const [expandedStockReasons, setExpandedStockReasons] = useState({});
const cardBg = useColorModeValue('white', 'gray.800');
@@ -56,6 +61,14 @@ const SectorDetails = ({ sortedSectors, totalStocks, onStockClick }) => {
}
};
// 新增:切换股票涨停原因的展开状态
const toggleStockReason = (stockCode) => {
setExpandedStockReasons(prev => ({
...prev,
[stockCode]: !prev[stockCode]
}));
};
const getSectorColorScheme = (sector) => {
if (sector === '公告') return 'orange';
if (sector === '其他') return 'gray';
@@ -91,15 +104,6 @@ const SectorDetails = ({ sortedSectors, totalStocks, onStockClick }) => {
<Flex justify="space-between" align="center">
<HStack spacing={3}>
<Heading size="md">板块详情</Heading>
<Button
size="sm"
variant="outline"
colorScheme="whiteAlpha"
onClick={toggleAllSectors}
leftIcon={expandedSectors.length === sortedSectors.length ? <ChevronUpIcon /> : <ChevronDownIcon />}
>
{expandedSectors.length === sortedSectors.length ? '全部收起' : '全部展开'}
</Button>
</HStack>
<HStack spacing={2}>
<Badge bg="whiteAlpha.900" color="blue.500" fontSize="md" px={3}>
@@ -190,8 +194,6 @@ const SectorDetails = ({ sortedSectors, totalStocks, onStockClick }) => {
bg: 'gray.50'
}}
transition="all 0.2s"
cursor="pointer"
onClick={() => onStockClick(stock)}
>
<Flex justify="space-between" align="start">
<VStack align="start" spacing={2} flex={1}>
@@ -209,9 +211,21 @@ const SectorDetails = ({ sortedSectors, totalStocks, onStockClick }) => {
)}
</HStack>
<Text fontSize="sm" color="gray.600" noOfLines={2}>
{stock.brief || stock.summary || '暂无涨停原因'}
</Text>
<Collapse in={expandedStockReasons[stock.scode]}>
<Box mt={2} p={3} bg="gray.50" borderRadius="md" border="1px solid" borderColor="gray.200">
<Text fontSize="sm" color="gray.700" fontWeight="bold">
涨停原因:
</Text>
<Text
fontSize="sm"
color="gray.600"
noOfLines={3}
{...getFormattedTextProps(stock.brief || stock.summary || '暂无涨停原因').props}
>
{getFormattedTextProps(stock.brief || stock.summary || '暂无涨停原因').children}
</Text>
</Box>
</Collapse>
<HStack spacing={4} fontSize="xs" color="gray.500">
<HStack spacing={1}>
@@ -255,11 +269,12 @@ const SectorDetails = ({ sortedSectors, totalStocks, onStockClick }) => {
</VStack>
<IconButton
icon={<ViewIcon />}
icon={expandedStockReasons[stock.scode] ? <ChevronUpIcon /> : <ChevronDownIcon />}
size="sm"
variant="ghost"
colorScheme={colorScheme}
aria-label="查看详情"
aria-label={expandedStockReasons[stock.scode] ? "收起原因" : "展开原因"}
onClick={() => toggleStockReason(stock.scode)}
/>
</Flex>
</Box>