Compare commits
6 Commits
afe1180736
...
1ecd3e6d10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ecd3e6d10 | ||
|
|
59fdb150a9 | ||
|
|
293886b54a | ||
|
|
f304268af9 | ||
|
|
cbef50c3e5 | ||
|
|
9990d95e28 |
@@ -2,7 +2,13 @@
|
||||
// 桌面版用户菜单 - 头像点击跳转到订阅页面
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Tooltip, useColorModeValue } from '@chakra-ui/react';
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import UserAvatar from './UserAvatar';
|
||||
import { TooltipContent } from '../../../Subscription/CrownTooltip';
|
||||
@@ -19,33 +25,47 @@ const DesktopUserMenu = memo(({ user }) => {
|
||||
const navigate = useNavigate();
|
||||
const { subscriptionInfo } = useSubscription();
|
||||
|
||||
const tooltipBg = useColorModeValue('white', 'gray.800');
|
||||
const tooltipBorderColor = useColorModeValue('gray.200', 'gray.600');
|
||||
const popoverBg = useColorModeValue('white', 'gray.800');
|
||||
const popoverBorderColor = useColorModeValue('gray.200', 'gray.600');
|
||||
|
||||
const handleAvatarClick = () => {
|
||||
navigate('/home/pages/account/subscription');
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={<TooltipContent subscriptionInfo={subscriptionInfo} />}
|
||||
placement="bottom"
|
||||
hasArrow
|
||||
bg={tooltipBg}
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor={tooltipBorderColor}
|
||||
boxShadow="lg"
|
||||
p={3}
|
||||
<Popover
|
||||
trigger="hover"
|
||||
placement="bottom-end"
|
||||
openDelay={100}
|
||||
closeDelay={200}
|
||||
gutter={8}
|
||||
>
|
||||
<span>
|
||||
<UserAvatar
|
||||
user={user}
|
||||
<PopoverTrigger>
|
||||
<span>
|
||||
<UserAvatar
|
||||
user={user}
|
||||
subscriptionInfo={subscriptionInfo}
|
||||
onClick={handleAvatarClick}
|
||||
/>
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
bg={popoverBg}
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor={popoverBorderColor}
|
||||
boxShadow="lg"
|
||||
p={3}
|
||||
w="auto"
|
||||
_focus={{ outline: 'none' }}
|
||||
>
|
||||
<PopoverArrow bg={popoverBg} />
|
||||
<TooltipContent
|
||||
subscriptionInfo={subscriptionInfo}
|
||||
onClick={handleAvatarClick}
|
||||
onNavigate={handleAvatarClick}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -13,11 +13,31 @@ import PropTypes from 'prop-types';
|
||||
/**
|
||||
* Tooltip 内容组件 - 显示详细的会员信息
|
||||
* 导出此组件供头像也使用相同的 Tooltip 内容
|
||||
*
|
||||
* @param {Object} subscriptionInfo - 订阅信息
|
||||
* @param {Function} onNavigate - 点击跳转回调(可选)
|
||||
*/
|
||||
export const TooltipContent = ({ subscriptionInfo }) => {
|
||||
export const TooltipContent = ({ subscriptionInfo, onNavigate }) => {
|
||||
const tooltipText = useColorModeValue('gray.700', 'gray.100');
|
||||
const dividerColor = useColorModeValue('gray.200', 'gray.600');
|
||||
const { type, days_left, is_active } = subscriptionInfo;
|
||||
const linkColor = useColorModeValue('blue.500', 'blue.300');
|
||||
const { type, days_left, is_active, end_date } = subscriptionInfo;
|
||||
|
||||
// 格式化到期日期
|
||||
const formatEndDate = (dateStr) => {
|
||||
if (!dateStr) return null;
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const formattedEndDate = formatEndDate(end_date);
|
||||
|
||||
// 基础版用户
|
||||
if (type === 'free') {
|
||||
@@ -30,8 +50,16 @@ export const TooltipContent = ({ subscriptionInfo }) => {
|
||||
<Text fontSize="sm" color={tooltipText} opacity={0.8}>
|
||||
解锁更多高级功能
|
||||
</Text>
|
||||
<Text fontSize="xs" color={tooltipText} opacity={0.6} textAlign="center" mt={1}>
|
||||
点击头像升级会员
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={linkColor}
|
||||
textAlign="center"
|
||||
mt={1}
|
||||
cursor="pointer"
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
onClick={onNavigate}
|
||||
>
|
||||
点击升级会员
|
||||
</Text>
|
||||
</VStack>
|
||||
);
|
||||
@@ -72,15 +100,28 @@ export const TooltipContent = ({ subscriptionInfo }) => {
|
||||
还有 <Text as="span" fontWeight="600" color={isUrgent ? 'red.500' : isWarning ? 'orange.500' : tooltipText}>{days_left}</Text> 天到期
|
||||
</Text>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color={tooltipText} opacity={0.7} pl={6}>
|
||||
{formattedEndDate && (
|
||||
<Text fontSize="xs" color={tooltipText} opacity={0.8} pl={6}>
|
||||
到期日:<Text as="span" fontWeight="500">{formattedEndDate}</Text>
|
||||
</Text>
|
||||
)}
|
||||
<Text fontSize="xs" color={tooltipText} opacity={0.6} pl={6}>
|
||||
享受全部高级功能
|
||||
</Text>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{/* 操作提示 */}
|
||||
<Text fontSize="xs" color={tooltipText} opacity={0.6} textAlign="center" mt={1}>
|
||||
{isExpired || isUrgent ? '点击头像立即续费' : '点击头像管理订阅'}
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={linkColor}
|
||||
textAlign="center"
|
||||
mt={1}
|
||||
cursor="pointer"
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{isExpired || isUrgent ? '点击立即续费' : '点击管理订阅'}
|
||||
</Text>
|
||||
</VStack>
|
||||
);
|
||||
@@ -133,5 +174,7 @@ TooltipContent.propTypes = {
|
||||
type: PropTypes.oneOf(['free', 'pro', 'max']).isRequired,
|
||||
days_left: PropTypes.number,
|
||||
is_active: PropTypes.bool,
|
||||
end_date: PropTypes.string,
|
||||
}).isRequired,
|
||||
onNavigate: PropTypes.func,
|
||||
};
|
||||
|
||||
@@ -751,10 +751,11 @@ const InfoModal = () => {
|
||||
|
||||
{/* 操作提示 */}
|
||||
<Box
|
||||
pt={2}
|
||||
borderTop="1px solid rgba(255,255,255,0.1)"
|
||||
pt={3}
|
||||
mt={2}
|
||||
borderTop="1px solid rgba(255,215,0,0.2)"
|
||||
>
|
||||
<Text fontSize="11px" color="whiteAlpha.400" textAlign="center">
|
||||
<Text fontSize="12px" color="yellow.300" textAlign="center" fontWeight="medium">
|
||||
💡 点击事件卡片查看详情 · K线图支持滚轮缩放和拖动
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -493,6 +493,9 @@ const CompactSearchBox = ({
|
||||
}}
|
||||
style={{ flex: 1, minWidth: isMobile ? 100 : 200 }}
|
||||
className="gold-placeholder"
|
||||
allowClear={{
|
||||
clearIcon: <CloseCircleOutlined style={{ color: PROFESSIONAL_COLORS.text.muted, fontSize: 14 }} />
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
prefix={<SearchOutlined style={{ color: PROFESSIONAL_COLORS.gold[500] }} />}
|
||||
|
||||
@@ -340,6 +340,44 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
|
||||
}
|
||||
};
|
||||
|
||||
// 禁用未来日期
|
||||
const disabledDate = (current) => {
|
||||
return current && current > dayjs().endOf('day');
|
||||
};
|
||||
|
||||
// 禁用未来时间(精确到分钟)
|
||||
const disabledTime = (current) => {
|
||||
if (!current) return {};
|
||||
|
||||
const now = dayjs();
|
||||
const isToday = current.isSame(now, 'day');
|
||||
|
||||
if (!isToday) return {};
|
||||
|
||||
const currentHour = now.hour();
|
||||
const currentMinute = now.minute();
|
||||
|
||||
return {
|
||||
disabledHours: () => {
|
||||
const hours = [];
|
||||
for (let i = currentHour + 1; i < 24; i++) {
|
||||
hours.push(i);
|
||||
}
|
||||
return hours;
|
||||
},
|
||||
disabledMinutes: (selectedHour) => {
|
||||
if (selectedHour === currentHour) {
|
||||
const minutes = [];
|
||||
for (let i = currentMinute + 1; i < 60; i++) {
|
||||
minutes.push(i);
|
||||
}
|
||||
return minutes;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// "更多时间" 按钮内容
|
||||
const customRangeContent = (
|
||||
<div style={{ padding: 8 }}>
|
||||
@@ -350,10 +388,12 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
|
||||
placeholder={['开始时间', '结束时间']}
|
||||
onChange={handleCustomRangeOk}
|
||||
value={customRange}
|
||||
disabledDate={disabledDate}
|
||||
disabledTime={disabledTime}
|
||||
style={{ marginBottom: 8 }}
|
||||
/>
|
||||
<div style={{ fontSize: 12, color: '#999', marginTop: 4 }}>
|
||||
支持精确到分钟的时间范围选择
|
||||
支持精确到分钟的时间范围选择(不能超过当前时间)
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -260,24 +260,7 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
))}
|
||||
</VStack>
|
||||
) : (
|
||||
<VStack spacing={2} align="stretch" maxH="400px" overflowY="auto"
|
||||
css={{
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: 'rgba(255,255,255,0.2)',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
background: 'rgba(255,255,255,0.3)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<VStack spacing={2} align="stretch">
|
||||
{data?.map((item, index) => (
|
||||
<Box key={index}>
|
||||
{renderItem(item, index)}
|
||||
|
||||
@@ -963,7 +963,7 @@ const ConceptCenter = () => {
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{/* 右上角股票数量徽章 */}
|
||||
{/* 右上角股票数量徽章 - 可点击 */}
|
||||
<Badge
|
||||
position="absolute"
|
||||
top={3}
|
||||
@@ -978,6 +978,13 @@ const ConceptCenter = () => {
|
||||
fontWeight="medium"
|
||||
border="1px solid"
|
||||
borderColor="whiteAlpha.200"
|
||||
cursor="pointer"
|
||||
_hover={{
|
||||
bg: 'rgba(139, 92, 246, 0.6)',
|
||||
borderColor: 'purple.400',
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
onClick={(e) => handleViewStocks(e, concept)}
|
||||
>
|
||||
{concept.stock_count || 0} 只股票
|
||||
</Badge>
|
||||
@@ -1233,9 +1240,15 @@ const ConceptCenter = () => {
|
||||
</Text>
|
||||
|
||||
<HStack spacing={4} flexWrap="wrap">
|
||||
<HStack spacing={1}>
|
||||
<HStack
|
||||
spacing={1}
|
||||
cursor="pointer"
|
||||
onClick={(e) => handleViewStocks(e, concept)}
|
||||
_hover={{ color: 'purple.300' }}
|
||||
transition="color 0.2s"
|
||||
>
|
||||
<Icon as={FaChartLine} boxSize={4} color="purple.300" />
|
||||
<Text fontSize="sm" fontWeight="medium" color="whiteAlpha.800">
|
||||
<Text fontSize="sm" fontWeight="medium" color="whiteAlpha.800" _groupHover={{ color: 'purple.300' }}>
|
||||
{concept.stock_count || 0} 只股票
|
||||
</Text>
|
||||
</HStack>
|
||||
@@ -1332,7 +1345,14 @@ const ConceptCenter = () => {
|
||||
))}
|
||||
{concept.stocks.length > 3 && (
|
||||
<WrapItem>
|
||||
<Text fontSize="xs" color="purple.300" fontWeight="medium">
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color="purple.300"
|
||||
fontWeight="medium"
|
||||
cursor="pointer"
|
||||
_hover={{ color: 'purple.200', textDecoration: 'underline' }}
|
||||
onClick={(e) => handleViewStocks(e, concept)}
|
||||
>
|
||||
+{concept.stocks.length - 3}更多
|
||||
</Text>
|
||||
</WrapItem>
|
||||
|
||||
@@ -86,15 +86,6 @@ const ConceptCard = ({ concept, tradingDate, onViewDetails }) => {
|
||||
{concept.concept}
|
||||
</Text>
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Badge
|
||||
bg="rgba(168, 85, 247, 0.15)"
|
||||
color="#A855F7"
|
||||
borderWidth="1px"
|
||||
borderColor="#A855F7"
|
||||
fontSize="xs"
|
||||
>
|
||||
相关度: {concept.score.toFixed(2)}
|
||||
</Badge>
|
||||
<Badge
|
||||
bg="rgba(20, 184, 166, 0.15)"
|
||||
color="#14B8A6"
|
||||
@@ -618,9 +609,6 @@ const RelatedConcepts = ({ eventTitle, eventTime, eventId, loading: externalLoad
|
||||
<VStack align="start" spacing={1}>
|
||||
<Text fontSize="xl">{selectedConcept?.concept}</Text>
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="purple">
|
||||
相关度: {selectedConcept?.score?.toFixed(2)}
|
||||
</Badge>
|
||||
<Badge colorScheme="teal">
|
||||
{selectedConcept?.stock_count} 只股票
|
||||
</Badge>
|
||||
|
||||
@@ -620,13 +620,6 @@ export default function ProfilePage() {
|
||||
{user?.last_seen ? new Date(user.last_seen).toLocaleDateString() : '未知'}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
<HStack justify="space-between" w="full">
|
||||
<Text fontSize="sm" color="gray.600">账户状态</Text>
|
||||
<Badge colorScheme={user?.status === 'active' ? 'green' : 'gray'}>
|
||||
{user?.status === 'active' ? '正常' : '未激活'}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -392,7 +392,27 @@ const StockOverview = () => {
|
||||
leafDepth: 1,
|
||||
roam: false,
|
||||
breadcrumb: {
|
||||
show: false
|
||||
show: true,
|
||||
top: 10,
|
||||
left: 10,
|
||||
itemStyle: {
|
||||
color: colorMode === 'dark' ? '#1a1a2e' : '#f0f0f0',
|
||||
borderColor: colorMode === 'dark' ? goldColor : '#ccc',
|
||||
borderWidth: 1,
|
||||
shadowBlur: colorMode === 'dark' ? 5 : 0,
|
||||
shadowColor: colorMode === 'dark' ? `${goldColor}40` : 'transparent',
|
||||
textStyle: {
|
||||
color: colorMode === 'dark' ? goldColor : '#333'
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: colorMode === 'dark' ? goldColor : '#e0e0e0',
|
||||
textStyle: {
|
||||
color: colorMode === 'dark' ? '#0a0a0a' : '#333'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
levels: [
|
||||
|
||||
Reference in New Issue
Block a user