Merge branch 'feature' of https://git.valuefrontier.cn/vf/vf_react into feature
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
// import {
|
||||||
Box,
|
// Box,
|
||||||
Alert,
|
// Alert,
|
||||||
AlertIcon,
|
// AlertIcon,
|
||||||
AlertTitle,
|
// AlertTitle,
|
||||||
AlertDescription,
|
// AlertDescription,
|
||||||
Button,
|
// Button,
|
||||||
VStack,
|
// VStack,
|
||||||
Container
|
// Container
|
||||||
} from '@chakra-ui/react';
|
// } from '@chakra-ui/react';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
@@ -40,66 +40,68 @@ class ErrorBoundary extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// 如果有错误,显示错误边界(所有环境)
|
// 静默模式:捕获错误并记录日志(已在 componentDidCatch 中完成)
|
||||||
if (this.state.hasError) {
|
// 但继续渲染子组件,不显示错误页面
|
||||||
return (
|
// 注意:如果组件因错误无法渲染,该区域可能显示为空白
|
||||||
<Container maxW="lg" py={20}>
|
// // 如果有错误,显示错误边界(所有环境)
|
||||||
<VStack spacing={6}>
|
// if (this.state.hasError) {
|
||||||
<Alert status="error" borderRadius="lg" p={6}>
|
// return (
|
||||||
<AlertIcon boxSize="24px" />
|
// <Container maxW="lg" py={20}>
|
||||||
<Box>
|
// <VStack spacing={6}>
|
||||||
<AlertTitle fontSize="lg" mb={2}>
|
// <Alert status="error" borderRadius="lg" p={6}>
|
||||||
页面出现错误!
|
// <AlertIcon boxSize="24px" />
|
||||||
</AlertTitle>
|
// <Box>
|
||||||
<AlertDescription>
|
// <AlertTitle fontSize="lg" mb={2}>
|
||||||
{process.env.NODE_ENV === 'development'
|
// 页面出现错误!
|
||||||
? '组件渲染时发生错误,请查看下方详情和控制台日志。'
|
// </AlertTitle>
|
||||||
: '页面加载时发生了未预期的错误,请尝试刷新页面。'}
|
// <AlertDescription>
|
||||||
</AlertDescription>
|
// {process.env.NODE_ENV === 'development'
|
||||||
</Box>
|
// ? '组件渲染时发生错误,请查看下方详情和控制台日志。'
|
||||||
</Alert>
|
// : '页面加载时发生了未预期的错误,请尝试刷新页面。'}
|
||||||
|
// </AlertDescription>
|
||||||
|
// </Box>
|
||||||
|
// </Alert>
|
||||||
|
|
||||||
{/* 开发环境显示详细错误信息 */}
|
// {/* 开发环境显示详细错误信息 */}
|
||||||
{process.env.NODE_ENV === 'development' && this.state.error && (
|
// {process.env.NODE_ENV === 'development' && this.state.error && (
|
||||||
<Box
|
// <Box
|
||||||
w="100%"
|
// w="100%"
|
||||||
bg="red.50"
|
// bg="red.50"
|
||||||
p={4}
|
// p={4}
|
||||||
borderRadius="lg"
|
// borderRadius="lg"
|
||||||
fontSize="sm"
|
// fontSize="sm"
|
||||||
overflow="auto"
|
// overflow="auto"
|
||||||
maxH="400px"
|
// maxH="400px"
|
||||||
border="1px"
|
// border="1px"
|
||||||
borderColor="red.200"
|
// borderColor="red.200"
|
||||||
>
|
// >
|
||||||
<Box fontWeight="bold" mb={2} color="red.700">错误详情:</Box>
|
// <Box fontWeight="bold" mb={2} color="red.700">错误详情:</Box>
|
||||||
<Box as="pre" whiteSpace="pre-wrap" color="red.900" fontSize="xs">
|
// <Box as="pre" whiteSpace="pre-wrap" color="red.900" fontSize="xs">
|
||||||
<Box fontWeight="bold" mb={1}>{this.state.error.name}: {this.state.error.message}</Box>
|
// <Box fontWeight="bold" mb={1}>{this.state.error.name}: {this.state.error.message}</Box>
|
||||||
{this.state.error.stack && (
|
// {this.state.error.stack && (
|
||||||
<Box mt={2} color="gray.700">{this.state.error.stack}</Box>
|
// <Box mt={2} color="gray.700">{this.state.error.stack}</Box>
|
||||||
)}
|
// )}
|
||||||
{this.state.errorInfo && this.state.errorInfo.componentStack && (
|
// {this.state.errorInfo && this.state.errorInfo.componentStack && (
|
||||||
<>
|
// <>
|
||||||
<Box fontWeight="bold" mt={3} mb={1} color="red.700">组件堆栈:</Box>
|
// <Box fontWeight="bold" mt={3} mb={1} color="red.700">组件堆栈:</Box>
|
||||||
<Box color="gray.700">{this.state.errorInfo.componentStack}</Box>
|
// <Box color="gray.700">{this.state.errorInfo.componentStack}</Box>
|
||||||
</>
|
// </>
|
||||||
)}
|
// )}
|
||||||
</Box>
|
// </Box>
|
||||||
</Box>
|
// </Box>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
<Button
|
|
||||||
colorScheme="blue"
|
|
||||||
size="lg"
|
|
||||||
onClick={() => window.location.reload()}
|
|
||||||
>
|
|
||||||
重新加载页面
|
|
||||||
</Button>
|
|
||||||
</VStack>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// <Button
|
||||||
|
// colorScheme="blue"
|
||||||
|
// size="lg"
|
||||||
|
// onClick={() => window.location.reload()}
|
||||||
|
// >
|
||||||
|
// 重新加载页面
|
||||||
|
// </Button>
|
||||||
|
// </VStack>
|
||||||
|
// </Container>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -480,9 +480,10 @@ export default function MidjourneyHeroSection() {
|
|||||||
minH="100vh"
|
minH="100vh"
|
||||||
bg="linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 50%, #000000 100%)"
|
bg="linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 50%, #000000 100%)"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
|
pointerEvents="none"
|
||||||
>
|
>
|
||||||
{/* 粒子背景 */}
|
{/* 粒子背景 */}
|
||||||
<Box position="absolute" inset={0} zIndex={0}>
|
<Box position="absolute" inset={0} zIndex={-1} pointerEvents="none">
|
||||||
<Particles
|
<Particles
|
||||||
id="tsparticles"
|
id="tsparticles"
|
||||||
init={particlesInit}
|
init={particlesInit}
|
||||||
@@ -499,7 +500,7 @@ export default function MidjourneyHeroSection() {
|
|||||||
<DataStreams />
|
<DataStreams />
|
||||||
|
|
||||||
{/* 内容容器 */}
|
{/* 内容容器 */}
|
||||||
<Container maxW="7xl" position="relative" zIndex={20} pt={20} pb={20}>
|
<Container maxW="7xl" position="relative" zIndex={1} pt={20} pb={20}>
|
||||||
<Grid templateColumns={{ base: '1fr', lg: 'repeat(2, 1fr)' }} gap={12} alignItems="center" minH="70vh">
|
<Grid templateColumns={{ base: '1fr', lg: 'repeat(2, 1fr)' }} gap={12} alignItems="center" minH="70vh">
|
||||||
|
|
||||||
{/* 左侧文本内容 */}
|
{/* 左侧文本内容 */}
|
||||||
@@ -776,7 +777,7 @@ export default function MidjourneyHeroSection() {
|
|||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
filter="blur(40px)"
|
filter="blur(40px)"
|
||||||
animation="pulse 4s ease-in-out infinite"
|
animation="pulse 4s ease-in-out infinite"
|
||||||
animationDelay="2s"
|
sx={{ animationDelay: '2s' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -793,7 +794,7 @@ export default function MidjourneyHeroSection() {
|
|||||||
right={0}
|
right={0}
|
||||||
h="128px"
|
h="128px"
|
||||||
bgGradient="linear(to-t, black, transparent)"
|
bgGradient="linear(to-t, black, transparent)"
|
||||||
zIndex={10}
|
zIndex={-1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 全局样式 */}
|
{/* 全局样式 */}
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ const PopularKeywords = ({ onKeywordClick, keywords: propKeywords }) => {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* 所有标签 */}
|
{/* 所有标签 */}
|
||||||
{keywords.map((item) => (
|
{keywords.map((item, index) => (
|
||||||
<Tag
|
<Tag
|
||||||
key={item.concept_id}
|
key={item.concept_id || `keyword-${index}`}
|
||||||
color={getTagColor(item.change_pct)}
|
color={getTagColor(item.change_pct)}
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|||||||
@@ -523,7 +523,11 @@ const UnifiedSearchBox = ({
|
|||||||
onFocus={onSearchFocus}
|
onFocus={onSearchFocus}
|
||||||
options={stockOptions}
|
options={stockOptions}
|
||||||
placeholder="请输入股票代码/股票名称/相关话题"
|
placeholder="请输入股票代码/股票名称/相关话题"
|
||||||
onPressEnter={handleMainSearch}
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleMainSearch();
|
||||||
|
}
|
||||||
|
}}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
size="large"
|
size="large"
|
||||||
notFoundContent={inputValue && stockOptions.length === 0 ? "未找到匹配的股票" : null}
|
notFoundContent={inputValue && stockOptions.length === 0 ? "未找到匹配的股票" : null}
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ export default function HomePage() {
|
|||||||
const coreFeatures = [
|
const coreFeatures = [
|
||||||
{
|
{
|
||||||
id: 'news-catalyst',
|
id: 'news-catalyst',
|
||||||
title: '新闻催化分析',
|
title: '新闻中心',
|
||||||
description: '实时新闻事件分析,捕捉市场催化因子',
|
description: '实时新闻事件分析,捕捉市场催化因子',
|
||||||
icon: '📊',
|
icon: '📊',
|
||||||
color: 'yellow',
|
color: 'yellow',
|
||||||
url: 'https://valuefrontier.cn/community',
|
url: '/community',
|
||||||
badge: '核心',
|
badge: '核心',
|
||||||
featured: true
|
featured: true
|
||||||
},
|
},
|
||||||
@@ -65,7 +65,7 @@ export default function HomePage() {
|
|||||||
description: '热门概念与主题投资分析追踪',
|
description: '热门概念与主题投资分析追踪',
|
||||||
icon: '🎯',
|
icon: '🎯',
|
||||||
color: 'purple',
|
color: 'purple',
|
||||||
url: 'https://valuefrontier.cn/concepts',
|
url: '/concepts',
|
||||||
badge: '热门'
|
badge: '热门'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -74,7 +74,7 @@ export default function HomePage() {
|
|||||||
description: '全面的个股基本面信息整合',
|
description: '全面的个股基本面信息整合',
|
||||||
icon: '📈',
|
icon: '📈',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
url: 'https://valuefrontier.cn/stocks',
|
url: '/stocks',
|
||||||
badge: '全面'
|
badge: '全面'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -83,7 +83,7 @@ export default function HomePage() {
|
|||||||
description: '涨停板数据深度分析与规律挖掘',
|
description: '涨停板数据深度分析与规律挖掘',
|
||||||
icon: '🚀',
|
icon: '🚀',
|
||||||
color: 'green',
|
color: 'green',
|
||||||
url: 'https://valuefrontier.cn/limit-analyse',
|
url: '/limit-analyse',
|
||||||
badge: '精准'
|
badge: '精准'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -92,7 +92,7 @@ export default function HomePage() {
|
|||||||
description: '个股全方位分析与投资决策支持',
|
description: '个股全方位分析与投资决策支持',
|
||||||
icon: '🧭',
|
icon: '🧭',
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
url: 'https://valuefrontier.cn/company?scode=688256',
|
url: '/company?scode=688256',
|
||||||
badge: '专业'
|
badge: '专业'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -106,15 +106,6 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 个人中心配置
|
|
||||||
// const personalCenter = {
|
|
||||||
// title: '个人中心',
|
|
||||||
// description: '账户管理与个人设置',
|
|
||||||
// icon: '👤',
|
|
||||||
// color: 'gray',
|
|
||||||
// url: 'https://valuefrontier.cn/home/center'
|
|
||||||
// };
|
|
||||||
|
|
||||||
// @TODO 如何区分内部链接和外部链接?
|
// @TODO 如何区分内部链接和外部链接?
|
||||||
const handleProductClick = (url) => {
|
const handleProductClick = (url) => {
|
||||||
if (url.startsWith('http')) {
|
if (url.startsWith('http')) {
|
||||||
@@ -202,7 +193,7 @@ export default function HomePage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Container maxW="7xl" position="relative" zIndex={2} px={containerPx}>
|
<Container maxW="7xl" position="relative" zIndex={30} px={containerPx}>
|
||||||
<VStack spacing={{ base: 8, md: 12, lg: 16 }} align="stretch" minH={heroHeight} justify="center">
|
<VStack spacing={{ base: 8, md: 12, lg: 16 }} align="stretch" minH={heroHeight} justify="center">
|
||||||
{/* 主标题区域 */}
|
{/* 主标题区域 */}
|
||||||
<VStack spacing={{ base: 4, md: 5, lg: 6 }} textAlign="center" pt={{ base: 4, md: 6, lg: 8 }}>
|
<VStack spacing={{ base: 4, md: 5, lg: 6 }} textAlign="center" pt={{ base: 4, md: 6, lg: 8 }}>
|
||||||
@@ -225,7 +216,7 @@ export default function HomePage() {
|
|||||||
<Box pb={{ base: 8, md: 12 }}>
|
<Box pb={{ base: 8, md: 12 }}>
|
||||||
<VStack spacing={{ base: 6, md: 8 }}>
|
<VStack spacing={{ base: 6, md: 8 }}>
|
||||||
|
|
||||||
{/* 新闻催化分析 - 突出显示 */}
|
{/* 新闻中心 - 突出显示 */}
|
||||||
<Card
|
<Card
|
||||||
bg="transparent"
|
bg="transparent"
|
||||||
border="2px solid"
|
border="2px solid"
|
||||||
@@ -326,29 +317,28 @@ export default function HomePage() {
|
|||||||
{/* 其他5个功能 */}
|
{/* 其他5个功能 */}
|
||||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={{ base: 4, md: 5, lg: 6 }} w="100%">
|
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={{ base: 4, md: 5, lg: 6 }} w="100%">
|
||||||
{coreFeatures.slice(1).map((feature) => (
|
{coreFeatures.slice(1).map((feature) => (
|
||||||
<Card
|
<Card
|
||||||
key={feature.id}
|
key={feature.id}
|
||||||
bg="whiteAlpha.100"
|
bg="whiteAlpha.100"
|
||||||
backdropFilter="blur(10px)"
|
backdropFilter="blur(10px)"
|
||||||
border="1px solid"
|
border="1px solid"
|
||||||
borderColor="whiteAlpha.200"
|
borderColor="whiteAlpha.200"
|
||||||
borderRadius={{ base: 'xl', md: '2xl' }}
|
borderRadius={{ base: 'xl', md: '2xl' }}
|
||||||
cursor="pointer"
|
transition="all 0.3s ease"
|
||||||
transition="all 0.3s ease"
|
_hover={{
|
||||||
_hover={{
|
bg: 'whiteAlpha.200',
|
||||||
bg: 'whiteAlpha.200',
|
borderColor: `${feature.color}.400`,
|
||||||
borderColor: `${feature.color}.400`,
|
transform: 'translateY(-4px)',
|
||||||
transform: 'translateY(-4px)',
|
shadow: '2xl'
|
||||||
shadow: '2xl'
|
}}
|
||||||
}}
|
_active={{
|
||||||
_active={{
|
bg: 'whiteAlpha.200',
|
||||||
bg: 'whiteAlpha.200',
|
borderColor: `${feature.color}.400`,
|
||||||
borderColor: `${feature.color}.400`,
|
transform: 'translateY(-2px)'
|
||||||
transform: 'translateY(-2px)'
|
}}
|
||||||
}}
|
onClick={() => handleProductClick(feature.url)}
|
||||||
onClick={() => handleProductClick(feature.url)}
|
minH={{ base: 'auto', md: '180px' }}
|
||||||
minH={{ base: 'auto', md: '180px' }}
|
>
|
||||||
>
|
|
||||||
<CardBody p={{ base: 5, md: 6 }}>
|
<CardBody p={{ base: 5, md: 6 }}>
|
||||||
<VStack spacing={{ base: 3, md: 4 }} align="start" h="100%">
|
<VStack spacing={{ base: 3, md: 4 }} align="start" h="100%">
|
||||||
<HStack>
|
<HStack>
|
||||||
|
|||||||
Reference in New Issue
Block a user