Initial commit
This commit is contained in:
486
src/views/Community/index.js
Normal file
486
src/views/Community/index.js
Normal file
@@ -0,0 +1,486 @@
|
||||
// src/views/Community/index.js
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Grid,
|
||||
GridItem,
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Button,
|
||||
Text,
|
||||
Heading,
|
||||
VStack,
|
||||
HStack,
|
||||
Badge,
|
||||
Spinner,
|
||||
useToast,
|
||||
Flex,
|
||||
Tag,
|
||||
TagLabel,
|
||||
TagCloseButton,
|
||||
IconButton,
|
||||
Wrap,
|
||||
WrapItem,
|
||||
Stat,
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
StatHelpText,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
DrawerHeader,
|
||||
DrawerOverlay,
|
||||
DrawerContent,
|
||||
DrawerCloseButton,
|
||||
useDisclosure,
|
||||
Center,
|
||||
Image,
|
||||
Divider,
|
||||
useColorModeValue,
|
||||
Link,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
RepeatIcon,
|
||||
TimeIcon,
|
||||
InfoIcon,
|
||||
SearchIcon,
|
||||
CalendarIcon,
|
||||
StarIcon,
|
||||
ChevronRightIcon,
|
||||
CloseIcon,
|
||||
} from '@chakra-ui/icons';
|
||||
|
||||
|
||||
// 导入组件
|
||||
import MidjourneyHeroSection from './components/MidjourneyHeroSection';
|
||||
import EventFilters from './components/EventFilters';
|
||||
import EventList from './components/EventList';
|
||||
import EventDetailModal from './components/EventDetailModal';
|
||||
import StockDetailPanel from './components/StockDetailPanel';
|
||||
import SearchBox from './components/SearchBox';
|
||||
import PopularKeywords from './components/PopularKeywords';
|
||||
import HotEvents from './components/HotEvents';
|
||||
import ImportanceLegend from './components/ImportanceLegend';
|
||||
import InvestmentCalendar from './components/InvestmentCalendar';
|
||||
import { eventService } from '../../services/eventService';
|
||||
|
||||
// 导入导航栏组件 (如果需要保留原有的导航栏)
|
||||
import HomeNavbar from '../../components/Navbars/HomeNavbar';
|
||||
|
||||
const filterLabelMap = {
|
||||
date_range: v => v ? `日期: ${v}` : '',
|
||||
sort: v => v ? `排序: ${v === 'new' ? '最新' : v === 'hot' ? '热门' : v === 'returns' ? '收益率' : v}` : '',
|
||||
importance: v => v && v !== 'all' ? `重要性: ${v}` : '',
|
||||
industry_classification: v => v ? `行业: ${v}` : '',
|
||||
industry_code: v => v ? `行业代码: ${v}` : '',
|
||||
q: v => v ? `关键词: ${v}` : '',
|
||||
};
|
||||
|
||||
const Community = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
|
||||
// Chakra UI hooks
|
||||
const bgColor = useColorModeValue('gray.50', 'gray.900');
|
||||
const cardBg = useColorModeValue('white', 'gray.800');
|
||||
const borderColor = useColorModeValue('gray.200', 'gray.700');
|
||||
|
||||
// Modal/Drawer控制
|
||||
const { isOpen: isEventModalOpen, onOpen: onEventModalOpen, onClose: onEventModalClose } = useDisclosure();
|
||||
const { isOpen: isStockDrawerOpen, onOpen: onStockDrawerOpen, onClose: onStockDrawerClose } = useDisclosure();
|
||||
|
||||
// 状态管理
|
||||
const [events, setEvents] = useState([]);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||
const [selectedEventForStock, setSelectedEventForStock] = useState(null);
|
||||
const [popularKeywords, setPopularKeywords] = useState([]);
|
||||
const [hotEvents, setHotEvents] = useState([]);
|
||||
const [lastUpdateTime, setLastUpdateTime] = useState(new Date());
|
||||
|
||||
// 从URL获取筛选参数
|
||||
const getFiltersFromUrl = useCallback(() => {
|
||||
return {
|
||||
sort: searchParams.get('sort') || 'new',
|
||||
importance: searchParams.get('importance') || 'all',
|
||||
date_range: searchParams.get('date_range') || '',
|
||||
q: searchParams.get('q') || '',
|
||||
search_type: searchParams.get('search_type') || 'topic',
|
||||
industry_classification: searchParams.get('industry_classification') || '',
|
||||
industry_code: searchParams.get('industry_code') || '',
|
||||
page: parseInt(searchParams.get('page') || '1', 10)
|
||||
};
|
||||
}, [searchParams]);
|
||||
|
||||
// 更新URL参数
|
||||
const updateUrlParams = useCallback((params) => {
|
||||
const newParams = new URLSearchParams(searchParams);
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
newParams.set(key, value);
|
||||
} else {
|
||||
newParams.delete(key);
|
||||
}
|
||||
});
|
||||
setSearchParams(newParams);
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
// 加载事件列表
|
||||
const loadEvents = useCallback(async (page = 1) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const filters = getFiltersFromUrl();
|
||||
const response = await eventService.getEvents({
|
||||
...filters,
|
||||
page,
|
||||
per_page: pagination.pageSize
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setEvents(response.data.events);
|
||||
setPagination({
|
||||
current: response.data.pagination.page,
|
||||
pageSize: response.data.pagination.per_page,
|
||||
total: response.data.pagination.total
|
||||
});
|
||||
setLastUpdateTime(new Date());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load events:', error);
|
||||
toast({
|
||||
title: '加载失败',
|
||||
description: '无法加载事件列表',
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [getFiltersFromUrl, pagination.pageSize, toast]);
|
||||
|
||||
// 加载热门关键词
|
||||
const loadPopularKeywords = useCallback(async () => {
|
||||
try {
|
||||
const response = await eventService.getPopularKeywords(20);
|
||||
if (response.success) {
|
||||
setPopularKeywords(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load popular keywords:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 加载热点事件
|
||||
const loadHotEvents = useCallback(async () => {
|
||||
try {
|
||||
const response = await eventService.getHotEvents({ days: 5, limit: 4 });
|
||||
if (response.success) {
|
||||
setHotEvents(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load hot events:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
// 处理筛选变化
|
||||
const handleFilterChange = useCallback((filterType, value) => {
|
||||
updateUrlParams({ [filterType]: value, page: 1 });
|
||||
}, [updateUrlParams]);
|
||||
|
||||
// 处理分页变化
|
||||
const handlePageChange = useCallback((page) => {
|
||||
updateUrlParams({ page });
|
||||
loadEvents(page);
|
||||
window.scrollTo(0, 0);
|
||||
}, [updateUrlParams, loadEvents]);
|
||||
|
||||
// 处理事件点击
|
||||
const handleEventClick = useCallback((event) => {
|
||||
setSelectedEventForStock(event);
|
||||
onStockDrawerOpen();
|
||||
}, [onStockDrawerOpen]);
|
||||
|
||||
// 处理查看详情
|
||||
const handleViewDetail = useCallback((eventId) => {
|
||||
navigate(`/event-detail/${eventId}`);
|
||||
}, [navigate]);
|
||||
|
||||
// 处理关键词点击
|
||||
const handleKeywordClick = useCallback((keyword) => {
|
||||
updateUrlParams({ q: keyword, page: 1 });
|
||||
}, [updateUrlParams]);
|
||||
|
||||
|
||||
|
||||
// 处理标签删除
|
||||
const handleRemoveFilterTag = (key) => {
|
||||
let reset = '';
|
||||
if (key === 'sort') reset = 'new';
|
||||
if (key === 'importance') reset = 'all';
|
||||
updateUrlParams({ [key]: reset, page: 1 });
|
||||
loadEvents(1);
|
||||
};
|
||||
|
||||
// 获取筛选标签
|
||||
const filters = getFiltersFromUrl();
|
||||
const filterTags = Object.entries(filters)
|
||||
.filter(([key, value]) => {
|
||||
if (key === 'industry_code') return !!value;
|
||||
if (key === 'importance') return value && value !== 'all';
|
||||
if (key === 'sort') return value && value !== 'new';
|
||||
if (key === 'date_range') return !!value;
|
||||
if (key === 'q') return !!value;
|
||||
return false;
|
||||
})
|
||||
.map(([key, value]) => {
|
||||
if (key === 'industry_code') return { key, label: `行业代码: ${value}` };
|
||||
return { key, label: filterLabelMap[key] ? filterLabelMap[key](value) : `${key}: ${value}` };
|
||||
});
|
||||
|
||||
// 初始化加载
|
||||
useEffect(() => {
|
||||
const page = parseInt(searchParams.get('page') || '1', 10);
|
||||
loadEvents(page);
|
||||
loadPopularKeywords();
|
||||
loadHotEvents();
|
||||
}, [searchParams, loadEvents, loadPopularKeywords, loadHotEvents]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box minH="100vh" bg={bgColor}>
|
||||
{/* 导航栏 - 可以保留原有的或改用Chakra UI版本 */}
|
||||
<HomeNavbar />
|
||||
|
||||
{/* Midjourney风格英雄区域 */}
|
||||
<MidjourneyHeroSection />
|
||||
|
||||
{/* 主内容区域 */}
|
||||
<Container maxW="container.xl" py={8}>
|
||||
<Grid templateColumns={{ base: '1fr', lg: '2fr 1fr' }} gap={6}>
|
||||
|
||||
{/* 左侧主要内容 */}
|
||||
<GridItem>
|
||||
{/* 筛选器 - 需要改造为Chakra UI版本 */}
|
||||
<Card mb={4} bg={cardBg} borderColor={borderColor}>
|
||||
<CardBody>
|
||||
<EventFilters
|
||||
filters={filters}
|
||||
onFilterChange={handleFilterChange}
|
||||
loading={loading}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* 筛选标签 */}
|
||||
{filterTags.length > 0 && (
|
||||
<Wrap spacing={2} mb={4}>
|
||||
{filterTags.map(tag => (
|
||||
<WrapItem key={tag.key}>
|
||||
<Tag size="md" variant="solid" colorScheme="blue">
|
||||
<TagLabel>{tag.label}</TagLabel>
|
||||
<TagCloseButton onClick={() => handleRemoveFilterTag(tag.key)} />
|
||||
</Tag>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
)}
|
||||
|
||||
{/* 事件列表卡片 */}
|
||||
<Card bg={cardBg} borderColor={borderColor}>
|
||||
<CardHeader>
|
||||
<Flex justify="space-between" align="center">
|
||||
<VStack align="start" spacing={1}>
|
||||
<Heading size="md">
|
||||
<HStack>
|
||||
<TimeIcon />
|
||||
<Text>实时事件时间轴</Text>
|
||||
</HStack>
|
||||
</Heading>
|
||||
<HStack fontSize="sm" color="gray.500">
|
||||
<Badge colorScheme="green">全网监控</Badge>
|
||||
<Badge colorScheme="orange">智能捕获</Badge>
|
||||
<Badge colorScheme="purple">深度分析</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
最后更新: {lastUpdateTime.toLocaleTimeString()}
|
||||
</Text>
|
||||
</Flex>
|
||||
</CardHeader>
|
||||
|
||||
<CardBody>
|
||||
{loading ? (
|
||||
<Center py={10}>
|
||||
<VStack>
|
||||
<Spinner size="xl" color="blue.500" thickness="4px" />
|
||||
<Text color="gray.500">正在加载最新事件...</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
) : events.length > 0 ? (
|
||||
<EventList
|
||||
events={events}
|
||||
pagination={pagination}
|
||||
onPageChange={handlePageChange}
|
||||
onEventClick={handleEventClick}
|
||||
onViewDetail={handleViewDetail}
|
||||
/>
|
||||
) : (
|
||||
<Center py={10}>
|
||||
<VStack>
|
||||
<Text fontSize="lg" color="gray.500">暂无事件数据</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</GridItem>
|
||||
|
||||
{/* 右侧侧边栏 */}
|
||||
<GridItem>
|
||||
<VStack spacing={4}>
|
||||
{/* 搜索框 - 需要改造为Chakra UI版本 */}
|
||||
<Card w="full" bg={cardBg}>
|
||||
<CardBody>
|
||||
<SearchBox
|
||||
onSearch={(values) => {
|
||||
updateUrlParams({ ...values, page: 1 });
|
||||
}}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* 投资日历 - 需要改造为Chakra UI版本 */}
|
||||
<Card w="full" bg={cardBg}>
|
||||
<CardHeader>
|
||||
<Heading size="sm">
|
||||
<HStack>
|
||||
<CalendarIcon />
|
||||
<Text>投资日历</Text>
|
||||
</HStack>
|
||||
</Heading>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<InvestmentCalendar />
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* 热门关键词 - 需要改造为Chakra UI版本 */}
|
||||
<Card w="full" bg={cardBg}>
|
||||
<CardHeader>
|
||||
<Heading size="sm">
|
||||
<HStack>
|
||||
<StarIcon />
|
||||
<Text>热门关键词</Text>
|
||||
</HStack>
|
||||
</Heading>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<PopularKeywords
|
||||
keywords={popularKeywords}
|
||||
onKeywordClick={handleKeywordClick}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* 重要性说明 - 需要改造为Chakra UI版本 */}
|
||||
<Card w="full" bg={cardBg}>
|
||||
<CardHeader>
|
||||
<Heading size="sm">
|
||||
<HStack>
|
||||
<InfoIcon />
|
||||
<Text>重要性说明</Text>
|
||||
</HStack>
|
||||
</Heading>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<ImportanceLegend />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</VStack>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
|
||||
{/* 热点事件 - 需要改造为Chakra UI版本 */}
|
||||
{hotEvents.length > 0 && (
|
||||
<Card mt={8} bg={cardBg}>
|
||||
<CardHeader>
|
||||
<Heading size="md">🔥 热点事件</Heading>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<HotEvents events={hotEvents} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
{/* Footer区域 */}
|
||||
<Box bg={useColorModeValue('gray.100', 'gray.800')} py={6} mt={8}>
|
||||
<Container maxW="container.xl">
|
||||
<VStack spacing={2}>
|
||||
<Text color="gray.500" fontSize="sm">
|
||||
© 2024 价值前沿. 保留所有权利.
|
||||
</Text>
|
||||
<HStack spacing={4} fontSize="xs" color="gray.400">
|
||||
<Link
|
||||
href="https://beian.mps.gov.cn/#/query/webSearch?code=11010802046286"
|
||||
isExternal
|
||||
_hover={{ color: 'gray.600' }}
|
||||
>
|
||||
京公网安备11010802046286号
|
||||
</Link>
|
||||
<Text>京ICP备2025107343号-1</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
{/* 事件详情模态框 - 使用Chakra UI Modal */}
|
||||
<Modal isOpen={isEventModalOpen && selectedEvent} onClose={onEventModalClose} size="xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>事件详情</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody pb={6}>
|
||||
<EventDetailModal
|
||||
event={selectedEvent}
|
||||
onClose={() => {
|
||||
setSelectedEvent(null);
|
||||
onEventModalClose();
|
||||
}}
|
||||
/>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
{/* 股票详情抽屉 - 使用原组件自带的 Antd Drawer,避免与 Chakra Drawer 重叠导致空白 */}
|
||||
<StockDetailPanel
|
||||
visible={!!selectedEventForStock}
|
||||
event={selectedEventForStock}
|
||||
onClose={() => {
|
||||
setSelectedEventForStock(null);
|
||||
onStockDrawerClose();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Community;
|
||||
Reference in New Issue
Block a user