update pay ui
This commit is contained in:
@@ -11,6 +11,7 @@ const DATA_BASE_URL = '/data/zt';
|
|||||||
const cache = {
|
const cache = {
|
||||||
dates: null,
|
dates: null,
|
||||||
daily: new Map(),
|
daily: new Map(),
|
||||||
|
stocksJsonl: null, // 缓存 stocks.jsonl 数据
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,80 +204,104 @@ const parseContinuousDays = (str) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关键词搜索股票
|
* 加载 stocks.jsonl 文件
|
||||||
* 从缓存的数据中搜索
|
* JSONL 格式:每行一个 JSON 对象
|
||||||
|
*/
|
||||||
|
const loadStocksJsonl = async () => {
|
||||||
|
try {
|
||||||
|
// 使用缓存
|
||||||
|
if (cache.stocksJsonl) {
|
||||||
|
return cache.stocksJsonl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${DATA_BASE_URL}/stocks.jsonl`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
const lines = text.trim().split('\n');
|
||||||
|
const stocks = lines
|
||||||
|
.filter(line => line.trim())
|
||||||
|
.map(line => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(line);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// 缓存结果
|
||||||
|
cache.stocksJsonl = stocks;
|
||||||
|
|
||||||
|
return stocks;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ztStaticService] loadStocksJsonl error:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化版搜索股票
|
||||||
|
* 仅支持股票代码或名称的精确/部分匹配
|
||||||
|
* 使用 stocks.jsonl 作为数据源
|
||||||
*/
|
*/
|
||||||
export const searchStocks = async (searchParams) => {
|
export const searchStocks = async (searchParams) => {
|
||||||
try {
|
try {
|
||||||
const { query, date, date_range, page = 1, page_size = 20 } = searchParams;
|
const { query, page = 1, page_size = 50 } = searchParams;
|
||||||
|
|
||||||
if (!query || query.trim() === '') {
|
if (!query || query.trim() === '') {
|
||||||
return { success: false, error: '搜索关键词不能为空' };
|
return { success: false, error: '请输入股票代码或名称' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryLower = query.toLowerCase().trim();
|
const queryLower = query.toLowerCase().trim();
|
||||||
let allStocks = [];
|
|
||||||
|
|
||||||
// 确定要搜索的日期范围
|
// 加载 stocks.jsonl 数据
|
||||||
let datesToSearch = [];
|
const allStocks = await loadStocksJsonl();
|
||||||
|
|
||||||
if (date) {
|
if (allStocks.length === 0) {
|
||||||
datesToSearch = [date];
|
return { success: false, error: '搜索数据暂未加载,请稍后重试' };
|
||||||
} else if (date_range?.start && date_range?.end) {
|
|
||||||
// 从缓存的日期中筛选
|
|
||||||
const datesResult = await fetchAvailableDates();
|
|
||||||
if (datesResult.success) {
|
|
||||||
datesToSearch = datesResult.events
|
|
||||||
.filter(d => d.date >= date_range.start && d.date <= date_range.end)
|
|
||||||
.map(d => d.date);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 默认搜索最近 30 天
|
|
||||||
const datesResult = await fetchAvailableDates();
|
|
||||||
if (datesResult.success) {
|
|
||||||
datesToSearch = datesResult.events.slice(0, 30).map(d => d.date);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从每个日期的数据中搜索
|
// 简单的股票代码/名称匹配
|
||||||
for (const d of datesToSearch) {
|
|
||||||
const result = await fetchDailyAnalysis(d);
|
|
||||||
if (result.success && result.data.stocks) {
|
|
||||||
const stocks = result.data.stocks.map(s => ({ ...s, date: d }));
|
|
||||||
allStocks = allStocks.concat(stocks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关键词匹配
|
|
||||||
const results = allStocks
|
const results = allStocks
|
||||||
.map(stock => {
|
.map(stock => {
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
const scode = (stock.scode || '').toLowerCase();
|
||||||
|
const sname = (stock.sname || '').toLowerCase();
|
||||||
|
|
||||||
// 精确匹配股票代码
|
// 精确匹配股票代码(最高优先级)
|
||||||
if (queryLower === (stock.scode || '').toLowerCase()) {
|
if (scode === queryLower) {
|
||||||
score = 100;
|
score = 100;
|
||||||
}
|
}
|
||||||
// 精确匹配股票名称
|
// 精确匹配股票名称
|
||||||
else if (queryLower === (stock.sname || '').toLowerCase()) {
|
else if (sname === queryLower) {
|
||||||
score = 90;
|
score = 90;
|
||||||
}
|
}
|
||||||
// 部分匹配股票名称
|
// 股票代码以搜索词开头
|
||||||
else if ((stock.sname || '').toLowerCase().includes(queryLower)) {
|
else if (scode.startsWith(queryLower)) {
|
||||||
score = 80;
|
score = 80;
|
||||||
}
|
}
|
||||||
// 匹配板块
|
// 股票名称包含搜索词
|
||||||
else if ((stock.core_sectors || []).some(s => s.toLowerCase().includes(queryLower))) {
|
else if (sname.includes(queryLower)) {
|
||||||
score = 70;
|
score = 70;
|
||||||
}
|
}
|
||||||
// 匹配涨停原因
|
// 股票代码包含搜索词
|
||||||
else if ((stock.brief || '').toLowerCase().includes(queryLower)) {
|
else if (scode.includes(queryLower)) {
|
||||||
score = 60;
|
score = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...stock, _score: score };
|
return { ...stock, _score: score };
|
||||||
})
|
})
|
||||||
.filter(s => s._score > 0)
|
.filter(s => s._score > 0)
|
||||||
.sort((a, b) => b._score - a._score || b.date.localeCompare(a.date));
|
.sort((a, b) => {
|
||||||
|
// 先按匹配度排序,再按日期降序
|
||||||
|
if (b._score !== a._score) {
|
||||||
|
return b._score - a._score;
|
||||||
|
}
|
||||||
|
return (b.date || '').localeCompare(a.date || '');
|
||||||
|
});
|
||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
const total = results.length;
|
const total = results.length;
|
||||||
@@ -291,7 +316,7 @@ export const searchStocks = async (searchParams) => {
|
|||||||
page,
|
page,
|
||||||
page_size,
|
page_size,
|
||||||
total_pages: Math.ceil(total / page_size),
|
total_pages: Math.ceil(total / page_size),
|
||||||
search_mode: 'keyword',
|
search_mode: 'exact',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -324,6 +349,7 @@ export const fetchStocksBatchDetail = async (codes, date) => {
|
|||||||
export const clearCache = () => {
|
export const clearCache = () => {
|
||||||
cache.dates = null;
|
cache.dates = null;
|
||||||
cache.daily.clear();
|
cache.daily.clear();
|
||||||
|
cache.stocksJsonl = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ const SectorRelationMap = ({ data }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 数据分析主组件
|
// 数据分析主组件
|
||||||
export const DataAnalysis = ({ dailyData, wordCloudData }) => {
|
export const DataAnalysis = ({ dailyData, wordCloudData, totalStocks, dateStr }) => {
|
||||||
const cardBg = useColorModeValue('white', 'gray.800');
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
|
|
||||||
const pieData = useMemo(() => {
|
const pieData = useMemo(() => {
|
||||||
@@ -538,10 +538,30 @@ export const DataAnalysis = ({ dailyData, wordCloudData }) => {
|
|||||||
return Object.values(dailyData.sector_data).flatMap(sector => sector.stocks || []);
|
return Object.values(dailyData.sector_data).flatMap(sector => sector.stocks || []);
|
||||||
}, [dailyData]);
|
}, [dailyData]);
|
||||||
|
|
||||||
|
// 格式化日期显示
|
||||||
|
const formatDate = (str) => {
|
||||||
|
if (!str || str.length !== 8) return '';
|
||||||
|
return `${str.slice(0, 4)}年${parseInt(str.slice(4, 6))}月${parseInt(str.slice(6, 8))}日`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card bg={cardBg} borderRadius="xl" boxShadow="xl">
|
<Card bg={cardBg} borderRadius="xl" boxShadow="xl">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<Heading size="md">数据分析</Heading>
|
<HStack justify="space-between" align="center" flexWrap="wrap" gap={2}>
|
||||||
|
<Heading size="md">数据分析</Heading>
|
||||||
|
{totalStocks !== undefined && (
|
||||||
|
<HStack spacing={3}>
|
||||||
|
{dateStr && (
|
||||||
|
<Badge colorScheme="gray" fontSize="sm" px={3} py={1}>
|
||||||
|
{formatDate(dateStr)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<Badge colorScheme="red" fontSize="lg" px={4} py={2} borderRadius="md">
|
||||||
|
今日涨停: {totalStocks} 只
|
||||||
|
</Badge>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Tabs variant="soft-rounded" colorScheme="blue" isLazy>
|
<Tabs variant="soft-rounded" colorScheme="blue" isLazy>
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
VStack,
|
|
||||||
HStack,
|
HStack,
|
||||||
Text,
|
Text,
|
||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputLeftElement,
|
InputLeftElement,
|
||||||
Select,
|
|
||||||
RadioGroup,
|
|
||||||
Radio,
|
|
||||||
Modal,
|
Modal,
|
||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
@@ -39,14 +34,11 @@ import {
|
|||||||
AlertIcon,
|
AlertIcon,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { formatTooltipText, getFormattedTextProps } from '../../../utils/textUtils';
|
import { formatTooltipText, getFormattedTextProps } from '../../../utils/textUtils';
|
||||||
import { SearchIcon, CalendarIcon, DownloadIcon } from '@chakra-ui/icons';
|
import { SearchIcon, DownloadIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
// 高级搜索组件
|
// 简化版搜索组件 - 仅支持股票代码/名称精确匹配
|
||||||
export const AdvancedSearch = ({ onSearch, loading }) => {
|
export const AdvancedSearch = ({ onSearch, loading }) => {
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
const [searchMode, setSearchMode] = useState('hybrid');
|
|
||||||
const [searchType, setSearchType] = useState('all');
|
|
||||||
const [dateRange, setDateRange] = useState({ start: '', end: '' });
|
|
||||||
|
|
||||||
const cardBg = useColorModeValue('white', 'gray.800');
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -54,7 +46,7 @@ export const AdvancedSearch = ({ onSearch, loading }) => {
|
|||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
if (!searchKeyword.trim()) {
|
if (!searchKeyword.trim()) {
|
||||||
toast({
|
toast({
|
||||||
title: '请输入搜索关键词',
|
title: '请输入股票代码或名称',
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
});
|
});
|
||||||
@@ -62,139 +54,54 @@ export const AdvancedSearch = ({ onSearch, loading }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchParams = {
|
const searchParams = {
|
||||||
query: searchKeyword,
|
query: searchKeyword.trim(),
|
||||||
mode: searchMode,
|
mode: 'exact', // 固定为精确匹配
|
||||||
type: searchType,
|
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 50,
|
page_size: 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加日期范围
|
|
||||||
if (dateRange.start || dateRange.end) {
|
|
||||||
searchParams.date_range = {};
|
|
||||||
if (dateRange.start) searchParams.date_range.start = dateRange.start.replace(/-/g, '');
|
|
||||||
if (dateRange.end) searchParams.date_range.end = dateRange.end.replace(/-/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearch(searchParams);
|
onSearch(searchParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSearch = () => {
|
const clearSearch = () => {
|
||||||
setSearchKeyword('');
|
setSearchKeyword('');
|
||||||
setDateRange({ start: '', end: '' });
|
|
||||||
setSearchType('all');
|
|
||||||
setSearchMode('hybrid');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card bg={cardBg} borderRadius="xl" boxShadow="xl" mb={6}>
|
<Card bg={cardBg} borderRadius="xl" boxShadow="xl" mb={6}>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<VStack spacing={4}>
|
<HStack w="full" spacing={3}>
|
||||||
<HStack w="full" spacing={3}>
|
<InputGroup size="lg" flex={1}>
|
||||||
<InputGroup size="lg" flex={1}>
|
<InputLeftElement>
|
||||||
<InputLeftElement>
|
<SearchIcon color="gray.400" />
|
||||||
<SearchIcon color="gray.400" />
|
</InputLeftElement>
|
||||||
</InputLeftElement>
|
<Input
|
||||||
<Input
|
placeholder="输入股票代码或名称搜索(如 600000 或 浦发银行)"
|
||||||
placeholder="搜索股票名称、代码或涨停原因..."
|
value={searchKeyword}
|
||||||
value={searchKeyword}
|
onChange={(e) => setSearchKeyword(e.target.value)}
|
||||||
onChange={(e) => setSearchKeyword(e.target.value)}
|
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
||||||
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
fontSize="md"
|
||||||
fontSize="md"
|
/>
|
||||||
/>
|
</InputGroup>
|
||||||
</InputGroup>
|
<Button
|
||||||
<Button
|
size="lg"
|
||||||
size="lg"
|
colorScheme="blue"
|
||||||
colorScheme="blue"
|
onClick={handleSearch}
|
||||||
onClick={handleSearch}
|
isLoading={loading}
|
||||||
isLoading={loading}
|
px={8}
|
||||||
px={8}
|
leftIcon={<SearchIcon />}
|
||||||
leftIcon={<SearchIcon />}
|
>
|
||||||
>
|
搜索
|
||||||
搜索
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
size="lg"
|
||||||
size="lg"
|
variant="outline"
|
||||||
variant="outline"
|
onClick={clearSearch}
|
||||||
onClick={clearSearch}
|
px={6}
|
||||||
px={6}
|
>
|
||||||
>
|
清空
|
||||||
清空
|
</Button>
|
||||||
</Button>
|
</HStack>
|
||||||
</HStack>
|
|
||||||
|
|
||||||
<HStack w="full" spacing={4} align="start">
|
|
||||||
<Box flex={1}>
|
|
||||||
<Text fontSize="sm" mb={2} fontWeight="bold">搜索类型</Text>
|
|
||||||
<RadioGroup value={searchType} onChange={setSearchType}>
|
|
||||||
<HStack spacing={4}>
|
|
||||||
<Radio value="all">全部</Radio>
|
|
||||||
<Radio value="stock">股票</Radio>
|
|
||||||
<Radio value="reason">涨停原因</Radio>
|
|
||||||
</HStack>
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box flex={1}>
|
|
||||||
<Text fontSize="sm" mb={2} fontWeight="bold">搜索模式</Text>
|
|
||||||
<Select
|
|
||||||
value={searchMode}
|
|
||||||
onChange={(e) => setSearchMode(e.target.value)}
|
|
||||||
bg="white"
|
|
||||||
_dark={{ bg: 'gray.700' }}
|
|
||||||
sx={{
|
|
||||||
'& option': {
|
|
||||||
bg: 'white',
|
|
||||||
color: 'gray.800',
|
|
||||||
_dark: {
|
|
||||||
bg: 'gray.700',
|
|
||||||
color: 'white'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="hybrid">智能搜索(推荐)</option>
|
|
||||||
<option value="text">精确匹配</option>
|
|
||||||
<option value="vector">语义搜索</option>
|
|
||||||
</Select>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box flex={2}>
|
|
||||||
<Text fontSize="sm" mb={2} fontWeight="bold">日期范围(可选)</Text>
|
|
||||||
<HStack>
|
|
||||||
<InputGroup size="md">
|
|
||||||
<InputLeftElement>
|
|
||||||
<CalendarIcon color="gray.400" boxSize={4} />
|
|
||||||
</InputLeftElement>
|
|
||||||
<Input
|
|
||||||
type="date"
|
|
||||||
value={dateRange.start}
|
|
||||||
onChange={(e) => setDateRange({...dateRange, start: e.target.value})}
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
<Text>至</Text>
|
|
||||||
<InputGroup size="md">
|
|
||||||
<InputLeftElement>
|
|
||||||
<CalendarIcon color="gray.400" boxSize={4} />
|
|
||||||
</InputLeftElement>
|
|
||||||
<Input
|
|
||||||
type="date"
|
|
||||||
value={dateRange.end}
|
|
||||||
onChange={(e) => setDateRange({...dateRange, end: e.target.value})}
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
</HStack>
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
<Alert status="info" borderRadius="md" fontSize="sm">
|
|
||||||
<AlertIcon />
|
|
||||||
<Text>
|
|
||||||
<strong>提示:</strong>搜索结果将在新窗口中显示,不会影响当前页面的数据展示。
|
|
||||||
您可以搜索不同日期范围内的涨停股票进行对比分析。
|
|
||||||
</Text>
|
|
||||||
</Alert>
|
|
||||||
</VStack>
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,13 +16,8 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
Stat,
|
Alert,
|
||||||
StatLabel,
|
AlertIcon,
|
||||||
StatNumber,
|
|
||||||
StatHelpText,
|
|
||||||
StatArrow,
|
|
||||||
Alert,
|
|
||||||
AlertIcon,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
RepeatIcon,
|
RepeatIcon,
|
||||||
@@ -247,61 +242,6 @@ export default function LimitAnalyse() {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染统计卡片
|
|
||||||
const StatsCards = () => (
|
|
||||||
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={4} mb={6}>
|
|
||||||
<Card bg={cardBg} borderRadius="lg" boxShadow="md">
|
|
||||||
<CardBody>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel fontSize="sm">今日涨停</StatLabel>
|
|
||||||
<StatNumber fontSize="2xl" color="red.500">
|
|
||||||
{dailyData?.total_stocks || 0}
|
|
||||||
</StatNumber>
|
|
||||||
<StatHelpText>
|
|
||||||
<StatArrow type="increase" />
|
|
||||||
较昨日 +23%
|
|
||||||
</StatHelpText>
|
|
||||||
</Stat>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card bg={cardBg} borderRadius="lg" boxShadow="md">
|
|
||||||
<CardBody>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel fontSize="sm">最热板块</StatLabel>
|
|
||||||
<StatNumber fontSize="xl" color={accentColor}>
|
|
||||||
{dailyData?.summary?.top_sector || '-'}
|
|
||||||
</StatNumber>
|
|
||||||
<StatHelpText>{dailyData?.summary?.top_sector_count || 0} 只</StatHelpText>
|
|
||||||
</Stat>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card bg={cardBg} borderRadius="lg" boxShadow="md">
|
|
||||||
<CardBody>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel fontSize="sm">公告涨停</StatLabel>
|
|
||||||
<StatNumber fontSize="2xl" color="orange.500">
|
|
||||||
{dailyData?.summary?.announcement_stocks || 0}
|
|
||||||
</StatNumber>
|
|
||||||
<StatHelpText>重大利好</StatHelpText>
|
|
||||||
</Stat>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card bg={cardBg} borderRadius="lg" boxShadow="md">
|
|
||||||
<CardBody>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel fontSize="sm">早盘强势</StatLabel>
|
|
||||||
<StatNumber fontSize="2xl" color="green.500">
|
|
||||||
{dailyData?.summary?.zt_time_distribution?.morning || 0}
|
|
||||||
</StatNumber>
|
|
||||||
<StatHelpText>开盘涨停</StatHelpText>
|
|
||||||
</Stat>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</SimpleGrid>
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatDisplayDate = (date) => {
|
const formatDisplayDate = (date) => {
|
||||||
if (!date) return '';
|
if (!date) return '';
|
||||||
@@ -431,21 +371,10 @@ export default function LimitAnalyse() {
|
|||||||
|
|
||||||
{/* 主内容区 */}
|
{/* 主内容区 */}
|
||||||
<Container maxW="container.xl" py={8}>
|
<Container maxW="container.xl" py={8}>
|
||||||
{/* 统计卡片 */}
|
{/* 搜索框 */}
|
||||||
{loading ? (
|
|
||||||
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={4} mb={6}>
|
|
||||||
{[...Array(4)].map((_, i) => (
|
|
||||||
<Skeleton key={i} height="100px" borderRadius="lg" />
|
|
||||||
))}
|
|
||||||
</SimpleGrid>
|
|
||||||
) : (
|
|
||||||
<StatsCards />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 高级搜索 */}
|
|
||||||
<AdvancedSearch onSearch={handleSearch} loading={loading} />
|
<AdvancedSearch onSearch={handleSearch} loading={loading} />
|
||||||
|
|
||||||
{/* 数据分析 - 移到板块详情上方 */}
|
{/* 数据分析(含涨停统计) */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Skeleton height="500px" borderRadius="xl" mb={6} />
|
<Skeleton height="500px" borderRadius="xl" mb={6} />
|
||||||
) : (
|
) : (
|
||||||
@@ -453,6 +382,8 @@ export default function LimitAnalyse() {
|
|||||||
<DataAnalysis
|
<DataAnalysis
|
||||||
dailyData={dailyData}
|
dailyData={dailyData}
|
||||||
wordCloudData={wordCloudData}
|
wordCloudData={wordCloudData}
|
||||||
|
totalStocks={dailyData?.total_stocks || 0}
|
||||||
|
dateStr={dateStr}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user