update pay ui
This commit is contained in:
@@ -81,11 +81,13 @@ import {
|
||||
useBreakpointValue,
|
||||
} from '@chakra-ui/react';
|
||||
import { SearchIcon, ViewIcon, CalendarIcon, ExternalLinkIcon, StarIcon, ChevronDownIcon, InfoIcon, CloseIcon, ChevronRightIcon } from '@chakra-ui/icons';
|
||||
import { FaThLarge, FaList, FaTags, FaChartLine, FaRobot, FaTable, FaHistory, FaBrain, FaLightbulb, FaRocket, FaShieldAlt, FaCalendarAlt, FaArrowUp, FaArrowDown, FaNewspaper, FaFileAlt, FaExpand, FaCompress, FaClock, FaLock } from 'react-icons/fa';
|
||||
import { FaThLarge, FaList, FaTags, FaChartLine, FaRobot, FaTable, FaHistory, FaBrain, FaLightbulb, FaRocket, FaShieldAlt, FaCalendarAlt, FaArrowUp, FaArrowDown, FaNewspaper, FaFileAlt, FaExpand, FaCompress, FaClock, FaLock, FaSitemap, FaLayerGroup } from 'react-icons/fa';
|
||||
import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
|
||||
import { keyframes } from '@emotion/react';
|
||||
import ConceptTimelineModal from './ConceptTimelineModal';
|
||||
import ConceptStatsPanel from './components/ConceptStatsPanel';
|
||||
import HierarchyView from './components/HierarchyView';
|
||||
import BreadcrumbNav from './components/BreadcrumbNav';
|
||||
import ConceptStocksModal from '@components/ConceptStocksModal';
|
||||
import TradeDatePicker from '@components/TradeDatePicker';
|
||||
// 导航栏已由 MainLayout 提供,无需在此导入
|
||||
@@ -161,7 +163,10 @@ const ConceptCenter = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalConcepts, setTotalConcepts] = useState(0);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [viewMode, setViewMode] = useState('grid');
|
||||
const [viewMode, setViewMode] = useState('list'); // 默认列表视图
|
||||
|
||||
// 层级筛选状态
|
||||
const [hierarchyFilter, setHierarchyFilter] = useState({ lv1: null, lv2: null, lv3: null });
|
||||
|
||||
// 日期相关状态
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
@@ -253,7 +258,11 @@ const ConceptCenter = () => {
|
||||
sort: searchParams.get('sort') || defaultSort,
|
||||
page: parseInt(searchParams.get('page') || '1', 10),
|
||||
date: searchParams.get('date') || null,
|
||||
size: 12
|
||||
size: 12,
|
||||
// 层级筛选参数
|
||||
lv1: searchParams.get('lv1') || null,
|
||||
lv2: searchParams.get('lv2') || null,
|
||||
lv3: searchParams.get('lv3') || null,
|
||||
};
|
||||
}, [searchParams]);
|
||||
|
||||
@@ -271,7 +280,7 @@ const ConceptCenter = () => {
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
// 获取概念数据
|
||||
const fetchConcepts = useCallback(async (query = '', page = 1, date = selectedDate, customSortBy = null) => {
|
||||
const fetchConcepts = useCallback(async (query = '', page = 1, date = selectedDate, customSortBy = null, filter = hierarchyFilter) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const sortToUse = customSortBy !== null ? customSortBy : sortBy;
|
||||
@@ -287,6 +296,14 @@ const ConceptCenter = () => {
|
||||
requestBody.trade_date = date.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// 添加层级筛选参数
|
||||
if (filter?.lv1) {
|
||||
requestBody.filter_lv1 = filter.lv1;
|
||||
}
|
||||
if (filter?.lv2) {
|
||||
requestBody.filter_lv2 = filter.lv2;
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -308,24 +325,75 @@ const ConceptCenter = () => {
|
||||
setSelectedDate(new Date(data.price_date));
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ConceptCenter', 'fetchConcepts', error, { query, page, date: date?.toISOString(), sortToUse });
|
||||
logger.error('ConceptCenter', 'fetchConcepts', error, { query, page, date: date?.toISOString(), sortToUse, filter });
|
||||
|
||||
// ❌ 移除获取数据失败toast
|
||||
// toast({ title: '获取数据失败', description: error.message, status: 'error', duration: 3000, isClosable: true });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [pageSize, sortBy]);
|
||||
}, [pageSize, sortBy, hierarchyFilter]);
|
||||
|
||||
// 清除搜索
|
||||
const handleClearSearch = () => {
|
||||
setSearchQuery('');
|
||||
setSortBy('change_pct');
|
||||
setCurrentPage(1);
|
||||
updateUrlParams({ q: '', page: 1, sort: 'change_pct' });
|
||||
fetchConcepts('', 1, selectedDate, 'change_pct');
|
||||
setHierarchyFilter({ lv1: null, lv2: null, lv3: null });
|
||||
updateUrlParams({ q: '', page: 1, sort: 'change_pct', lv1: '', lv2: '', lv3: '' });
|
||||
fetchConcepts('', 1, selectedDate, 'change_pct', { lv1: null, lv2: null, lv3: null });
|
||||
};
|
||||
|
||||
// 处理层级筛选选择(从 HierarchyView 点击分类)
|
||||
const handleHierarchySelect = useCallback((filter) => {
|
||||
logger.info('ConceptCenter', '层级筛选选择', filter);
|
||||
|
||||
setHierarchyFilter(filter);
|
||||
setCurrentPage(1);
|
||||
setViewMode('list'); // 切换到列表视图
|
||||
|
||||
// 更新 URL 参数
|
||||
updateUrlParams({
|
||||
lv1: filter.lv1 || '',
|
||||
lv2: filter.lv2 || '',
|
||||
lv3: filter.lv3 || '',
|
||||
page: 1
|
||||
});
|
||||
|
||||
// 重新获取数据
|
||||
fetchConcepts(searchQuery, 1, selectedDate, sortBy, filter);
|
||||
|
||||
// 显示提示
|
||||
toast({
|
||||
title: '已应用筛选',
|
||||
description: `正在显示「${[filter.lv1, filter.lv2, filter.lv3].filter(Boolean).join(' > ')}」分类下的概念`,
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
}, [searchQuery, selectedDate, sortBy, updateUrlParams, fetchConcepts, toast]);
|
||||
|
||||
// 清除层级筛选
|
||||
const handleClearHierarchyFilter = useCallback(() => {
|
||||
setHierarchyFilter({ lv1: null, lv2: null, lv3: null });
|
||||
setCurrentPage(1);
|
||||
updateUrlParams({ lv1: '', lv2: '', lv3: '', page: 1 });
|
||||
fetchConcepts(searchQuery, 1, selectedDate, sortBy, { lv1: null, lv2: null, lv3: null });
|
||||
}, [searchQuery, selectedDate, sortBy, updateUrlParams, fetchConcepts]);
|
||||
|
||||
// 导航到特定层级
|
||||
const handleNavigateHierarchy = useCallback((filter) => {
|
||||
setHierarchyFilter(filter);
|
||||
setCurrentPage(1);
|
||||
updateUrlParams({
|
||||
lv1: filter.lv1 || '',
|
||||
lv2: filter.lv2 || '',
|
||||
lv3: filter.lv3 || '',
|
||||
page: 1
|
||||
});
|
||||
fetchConcepts(searchQuery, 1, selectedDate, sortBy, filter);
|
||||
}, [searchQuery, selectedDate, sortBy, updateUrlParams, fetchConcepts]);
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
setCurrentPage(1);
|
||||
@@ -556,12 +624,20 @@ const ConceptCenter = () => {
|
||||
setSortBy(filters.sort);
|
||||
setCurrentPage(filters.page);
|
||||
|
||||
// 恢复层级筛选状态
|
||||
const hierarchyFilterFromUrl = {
|
||||
lv1: filters.lv1,
|
||||
lv2: filters.lv2,
|
||||
lv3: filters.lv3,
|
||||
};
|
||||
setHierarchyFilter(hierarchyFilterFromUrl);
|
||||
|
||||
const dateToUse = filters.date ? new Date(filters.date) : latestDate;
|
||||
if (dateToUse) {
|
||||
setSelectedDate(dateToUse);
|
||||
fetchConcepts(filters.q, filters.page, dateToUse, filters.sort);
|
||||
fetchConcepts(filters.q, filters.page, dateToUse, filters.sort, hierarchyFilterFromUrl);
|
||||
} else {
|
||||
fetchConcepts(filters.q, filters.page, null, filters.sort);
|
||||
fetchConcepts(filters.q, filters.page, null, filters.sort, hierarchyFilterFromUrl);
|
||||
}
|
||||
};
|
||||
init();
|
||||
@@ -1431,46 +1507,76 @@ const ConceptCenter = () => {
|
||||
</HStack>
|
||||
|
||||
<ButtonGroup size="sm" isAttached variant="outline">
|
||||
<IconButton
|
||||
icon={<FaThLarge />}
|
||||
onClick={() => {
|
||||
if (viewMode !== 'grid') {
|
||||
trackViewModeChanged('grid', viewMode);
|
||||
setViewMode('grid');
|
||||
}
|
||||
}}
|
||||
bg={viewMode === 'grid' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'}
|
||||
color={viewMode === 'grid' ? 'white' : 'purple.500'}
|
||||
borderColor="purple.500"
|
||||
_hover={{
|
||||
bg: viewMode === 'grid' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'purple.50',
|
||||
boxShadow: viewMode === 'grid' ? '0 0 10px rgba(139, 92, 246, 0.3)' : 'none',
|
||||
}}
|
||||
aria-label="网格视图"
|
||||
/>
|
||||
<IconButton
|
||||
icon={<FaList />}
|
||||
onClick={() => {
|
||||
if (viewMode !== 'list') {
|
||||
trackViewModeChanged('list', viewMode);
|
||||
setViewMode('list');
|
||||
}
|
||||
}}
|
||||
bg={viewMode === 'list' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'}
|
||||
color={viewMode === 'list' ? 'white' : 'purple.500'}
|
||||
borderColor="purple.500"
|
||||
_hover={{
|
||||
bg: viewMode === 'list' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'purple.50',
|
||||
boxShadow: viewMode === 'list' ? '0 0 10px rgba(139, 92, 246, 0.3)' : 'none',
|
||||
}}
|
||||
aria-label="列表视图"
|
||||
/>
|
||||
<Tooltip label="层级图" placement="top">
|
||||
<IconButton
|
||||
icon={<FaSitemap />}
|
||||
onClick={() => {
|
||||
if (viewMode !== 'hierarchy') {
|
||||
trackViewModeChanged('hierarchy', viewMode);
|
||||
setViewMode('hierarchy');
|
||||
}
|
||||
}}
|
||||
bg={viewMode === 'hierarchy' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'}
|
||||
color={viewMode === 'hierarchy' ? 'white' : 'purple.500'}
|
||||
borderColor="purple.500"
|
||||
_hover={{
|
||||
bg: viewMode === 'hierarchy' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'purple.50',
|
||||
boxShadow: viewMode === 'hierarchy' ? '0 0 10px rgba(139, 92, 246, 0.3)' : 'none',
|
||||
}}
|
||||
aria-label="层级图"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label="网格视图" placement="top">
|
||||
<IconButton
|
||||
icon={<FaThLarge />}
|
||||
onClick={() => {
|
||||
if (viewMode !== 'grid') {
|
||||
trackViewModeChanged('grid', viewMode);
|
||||
setViewMode('grid');
|
||||
}
|
||||
}}
|
||||
bg={viewMode === 'grid' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'}
|
||||
color={viewMode === 'grid' ? 'white' : 'purple.500'}
|
||||
borderColor="purple.500"
|
||||
_hover={{
|
||||
bg: viewMode === 'grid' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'purple.50',
|
||||
boxShadow: viewMode === 'grid' ? '0 0 10px rgba(139, 92, 246, 0.3)' : 'none',
|
||||
}}
|
||||
aria-label="网格视图"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label="列表视图" placement="top">
|
||||
<IconButton
|
||||
icon={<FaList />}
|
||||
onClick={() => {
|
||||
if (viewMode !== 'list') {
|
||||
trackViewModeChanged('list', viewMode);
|
||||
setViewMode('list');
|
||||
}
|
||||
}}
|
||||
bg={viewMode === 'list' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'}
|
||||
color={viewMode === 'list' ? 'white' : 'purple.500'}
|
||||
borderColor="purple.500"
|
||||
_hover={{
|
||||
bg: viewMode === 'list' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'purple.50',
|
||||
boxShadow: viewMode === 'list' ? '0 0 10px rgba(139, 92, 246, 0.3)' : 'none',
|
||||
}}
|
||||
aria-label="列表视图"
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{selectedDate && (
|
||||
{/* 面包屑导航 - 显示当前层级筛选 */}
|
||||
<BreadcrumbNav
|
||||
filter={hierarchyFilter}
|
||||
onClearFilter={handleClearHierarchyFilter}
|
||||
onNavigate={handleNavigateHierarchy}
|
||||
/>
|
||||
|
||||
{selectedDate && viewMode !== 'hierarchy' && (
|
||||
<Box mb={4} p={3} bg="blue.50" borderRadius="md" borderLeft="4px solid" borderColor="blue.500">
|
||||
<HStack>
|
||||
<Icon as={InfoIcon} color="blue.500" />
|
||||
@@ -1482,7 +1588,14 @@ const ConceptCenter = () => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
{/* 层级图视图 */}
|
||||
{viewMode === 'hierarchy' ? (
|
||||
<HierarchyView
|
||||
apiBaseUrl={API_BASE_URL}
|
||||
onSelectCategory={handleHierarchySelect}
|
||||
selectedDate={selectedDate}
|
||||
/>
|
||||
) : loading ? (
|
||||
<SimpleGrid columns={{ base: 2, md: 2, lg: 3 }} spacing={{ base: 3, md: 6 }}>
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<SkeletonCard key={i} />
|
||||
@@ -1590,17 +1703,32 @@ const ConceptCenter = () => {
|
||||
</HStack>
|
||||
</Center>
|
||||
</>
|
||||
) : (
|
||||
) : viewMode !== 'hierarchy' ? (
|
||||
<Center h="400px">
|
||||
<VStack spacing={6}>
|
||||
<Icon as={FaTags} boxSize={20} color="gray.300" />
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="xl" color="gray.600" fontWeight="medium">暂无概念数据</Text>
|
||||
<Text color="gray.500">请尝试其他搜索关键词或选择其他日期</Text>
|
||||
<Text color="gray.500">
|
||||
{hierarchyFilter?.lv1
|
||||
? `「${[hierarchyFilter.lv1, hierarchyFilter.lv2, hierarchyFilter.lv3].filter(Boolean).join(' > ')}」分类下暂无数据`
|
||||
: '请尝试其他搜索关键词或选择其他日期'
|
||||
}
|
||||
</Text>
|
||||
{hierarchyFilter?.lv1 && (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="purple"
|
||||
variant="outline"
|
||||
onClick={handleClearHierarchyFilter}
|
||||
>
|
||||
清除筛选
|
||||
</Button>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
) : null}
|
||||
</Box>
|
||||
|
||||
{/* 右侧统计面板 */}
|
||||
|
||||
Reference in New Issue
Block a user