Compare commits

...

6 Commits

Author SHA1 Message Date
zdl
1ecd3e6d10 feat(Concept): 股票数量和更多按钮可点击弹出股票列表
- 卡片视图:右上角"X只股票"徽章可点击
- 列表视图:"X只股票"文字可点击
- 列表视图:"+X更多"文字可点击
- 点击后弹出股票列表弹窗,与"查看个股"按钮行为一致

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 17:21:53 +08:00
zdl
59fdb150a9 fix(UI): 优化市值热力图和概念统计列表交互
- StockOverview: 启用热力图面包屑导航,支持返回上一级
- ConceptStatsPanel: 移除统计列表 400px 高度限制,改为自适应高度

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 17:21:53 +08:00
zdl
293886b54a feat(Navbar): 订阅提示文案可点击跳转
- "点击管理订阅"文字改为可点击链接,直接跳转订阅页面
- 文案简化:移除"头像"二字
- 链接样式:蓝色文字 + 悬停下划线

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 17:21:53 +08:00
zdl
f304268af9 fix(Navbar): 优化用户头像悬停交互和订阅信息显示
- 将 Tooltip 改为 Popover 组件,支持鼠标悬停到弹出内容上
- 用户现在可以正常悬停到"点击头像管理订阅"提示
- 订阅信息新增到期日期显示,用户无需自己计算到期时间

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 17:21:53 +08:00
zdl
cbef50c3e5 ui: 优化使用说明弹窗提示文字样式
- 提示文字颜色从 whiteAlpha.400 改为 yellow.300,更醒目
- 字号从 11px 增加到 12px,增加 fontWeight
- 边框颜色调整为金色调

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 17:21:53 +08:00
zdl
9990d95e28 fix(Community): 优化搜索和时间筛选交互
- 搜索框添加清空按钮(allowClear)
- 自定义时间范围限制不能超过当前时间(精确到分钟)
- 相关概念移除"相关度"显示

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 17:21:53 +08:00
10 changed files with 183 additions and 72 deletions

View File

@@ -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>
);
});

View File

@@ -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,
};

View File

@@ -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>

View File

@@ -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] }} />}

View File

@@ -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>
);

View File

@@ -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)}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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: [