import React, { useCallback, useState } from 'react'; import { Box, Flex, Text, Button, Container, useDisclosure, Drawer, DrawerBody, DrawerHeader, DrawerOverlay, DrawerContent, DrawerCloseButton, VStack, HStack, Icon, Menu, MenuButton, MenuList, MenuItem, MenuDivider, Badge, Grid, IconButton, useBreakpointValue, Link, Divider, Avatar, Spinner, useColorMode, useColorModeValue, useToast, } from '@chakra-ui/react'; import { ChevronDownIcon, HamburgerIcon, SunIcon, MoonIcon } from '@chakra-ui/icons'; import { FiStar, FiCalendar } from 'react-icons/fi'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; import { useAuthModal } from '../../contexts/AuthModalContext'; /** 桌面端导航 - 完全按照原网站 * @TODO 添加逻辑 不展示导航case * 1.未登陆状态 && 是首页 * 2. !isMobile */ const NavItems = ({ isAuthenticated, user }) => { const navigate = useNavigate(); if (isAuthenticated && user) { return ( }> 高频跟踪 navigate('/community')} py={2} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 新闻催化分析 HOT NEW navigate('/concepts')} py={2} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 概念中心 NEW }> 行情复盘 navigate('/limit-analyse')} py={2} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 涨停分析 FREE navigate('/stocks')} py={2} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 个股中心 HOT 模拟盘 NEW {false && isAuthenticated && ( }>自选股 )} }> AGENT社群 今日热议 个股社区 {false && isAuthenticated && ( }>关注的事件 )} }> 联系我们 敬请期待 ) } else { return null; } } export default function HomeNavbar() { const { isOpen, onOpen, onClose } = useDisclosure(); const navigate = useNavigate(); const isMobile = useBreakpointValue({ base: true, md: false }); const { user, isAuthenticated, logout, isLoading } = useAuth(); const { openAuthModal } = useAuthModal(); const { colorMode, toggleColorMode } = useColorMode(); const navbarBg = useColorModeValue('white', 'gray.800'); const navbarBorder = useColorModeValue('gray.200', 'gray.700'); const brandText = useColorModeValue('gray.800', 'white'); const brandHover = useColorModeValue('blue.600', 'blue.300'); const toast = useToast(); // 添加调试信息 console.log('HomeNavbar Debug:', { user, isAuthenticated, isLoading, userKeys: user ? Object.keys(user) : 'no user' }); // 获取显示名称的函数 const getDisplayName = () => { if (!user) return ''; return user.nickname || user.username || user.name || user.email || '用户'; }; // 处理登出 const handleLogout = async () => { try { await logout(); // logout函数已经包含了跳转逻辑,这里不需要额外处理 } catch (error) { console.error('Logout error:', error); } }; // 检查是否为禁用的链接(没有NEW标签的链接) // const isDisabledLink = true; // 自选股 / 关注事件 下拉所需状态 const [watchlistQuotes, setWatchlistQuotes] = useState([]); const [watchlistLoading, setWatchlistLoading] = useState(false); const [followingEvents, setFollowingEvents] = useState([]); const [eventsLoading, setEventsLoading] = useState(false); const [watchlistPage, setWatchlistPage] = useState(1); const [eventsPage, setEventsPage] = useState(1); const WATCHLIST_PAGE_SIZE = 10; const EVENTS_PAGE_SIZE = 8; // 用户信息完整性状态 const [profileCompleteness, setProfileCompleteness] = useState(null); const [showCompletenessAlert, setShowCompletenessAlert] = useState(false); // 计算 API 基础地址(与 Center.js 一致的策略) const getApiBase = () => (process.env.NODE_ENV === 'production' ? '' : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001')); const loadWatchlistQuotes = useCallback(async () => { try { setWatchlistLoading(true); const base = getApiBase(); const resp = await fetch(base + '/api/account/watchlist/realtime', { credentials: 'include', cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } }); if (resp.ok) { const data = await resp.json(); if (data && data.success && Array.isArray(data.data)) { setWatchlistQuotes(data.data); } else { setWatchlistQuotes([]); } } else { setWatchlistQuotes([]); } } catch (e) { console.warn('加载自选股实时行情失败:', e); setWatchlistQuotes([]); } finally { setWatchlistLoading(false); } }, []); const loadFollowingEvents = useCallback(async () => { try { setEventsLoading(true); const base = getApiBase(); const resp = await fetch(base + '/api/account/events/following', { credentials: 'include', cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } }); if (resp.ok) { const data = await resp.json(); if (data && data.success && Array.isArray(data.data)) { const ids = data.data.map((e) => e.id).filter(Boolean); if (ids.length === 0) { setFollowingEvents([]); } else { // 并行请求详情以获取涨幅字段 const detailResponses = await Promise.all(ids.map((id) => fetch(base + `/api/events/${id}`, { credentials: 'include', cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } }))); const detailJsons = await Promise.all(detailResponses.map((r) => r.ok ? r.json() : Promise.resolve({ success: false }))); const details = detailJsons .filter((j) => j && j.success && j.data) .map((j) => j.data); // 以原顺序合并,缺失则回退基础信息 const merged = ids.map((id) => { const d = details.find((x) => x.id === id); const baseItem = (data.data || []).find((x) => x.id === id) || {}; return d ? d : baseItem; }); setFollowingEvents(merged); } } else { setFollowingEvents([]); } } else { setFollowingEvents([]); } } catch (e) { console.warn('加载关注事件失败:', e); setFollowingEvents([]); } finally { setEventsLoading(false); } }, []); // 从自选股移除 const handleRemoveFromWatchlist = useCallback(async (stockCode) => { try { const base = getApiBase(); const resp = await fetch(base + `/api/account/watchlist/${stockCode}`, { method: 'DELETE', credentials: 'include' }); const data = await resp.json().catch(() => ({})); if (resp.ok && data && data.success !== false) { setWatchlistQuotes((prev) => { const normalize6 = (code) => { const m = String(code || '').match(/(\d{6})/); return m ? m[1] : String(code || ''); }; const target = normalize6(stockCode); const updated = (prev || []).filter((x) => normalize6(x.stock_code) !== target); const newMaxPage = Math.max(1, Math.ceil((updated.length || 0) / WATCHLIST_PAGE_SIZE)); setWatchlistPage((p) => Math.min(p, newMaxPage)); return updated; }); toast({ title: '已从自选股移除', status: 'info', duration: 1500 }); } else { toast({ title: '移除失败', status: 'error', duration: 2000 }); } } catch (e) { toast({ title: '网络错误,移除失败', status: 'error', duration: 2000 }); } }, [getApiBase, toast, WATCHLIST_PAGE_SIZE]); // 取消关注事件 const handleUnfollowEvent = useCallback(async (eventId) => { try { const base = getApiBase(); const resp = await fetch(base + `/api/events/${eventId}/follow`, { method: 'POST', credentials: 'include' }); const data = await resp.json().catch(() => ({})); if (resp.ok && data && data.success !== false) { setFollowingEvents((prev) => { const updated = (prev || []).filter((x) => x.id !== eventId); const newMaxPage = Math.max(1, Math.ceil((updated.length || 0) / EVENTS_PAGE_SIZE)); setEventsPage((p) => Math.min(p, newMaxPage)); return updated; }); toast({ title: '已取消关注该事件', status: 'info', duration: 1500 }); } else { toast({ title: '操作失败', status: 'error', duration: 2000 }); } } catch (e) { toast({ title: '网络错误,操作失败', status: 'error', duration: 2000 }); } }, [getApiBase, toast, EVENTS_PAGE_SIZE]); // 检查用户资料完整性 const checkProfileCompleteness = useCallback(async () => { if (!isAuthenticated || !user) return; try { const base = getApiBase(); const resp = await fetch(base + '/api/account/profile-completeness', { credentials: 'include' }); if (resp.ok) { const data = await resp.json(); if (data.success) { setProfileCompleteness(data.data); // 只有微信用户且资料不完整时才显示提醒 setShowCompletenessAlert(data.data.needsAttention); } } } catch (error) { console.warn('检查资料完整性失败:', error); } }, [isAuthenticated, user, getApiBase]); // 用户登录后检查资料完整性 React.useEffect(() => { if (isAuthenticated && user) { // 延迟检查,避免过于频繁 const timer = setTimeout(checkProfileCompleteness, 1000); return () => clearTimeout(timer); } }, [isAuthenticated, user, checkProfileCompleteness]); return ( <> {/* 资料完整性提醒横幅 */} {showCompletenessAlert && profileCompleteness && ( 完善资料,享受更好服务 您还需要设置:{profileCompleteness.missingItems.join('、')} {profileCompleteness.completenessPercentage}% 完成 ×} onClick={() => setShowCompletenessAlert(false)} aria-label="关闭提醒" /> )} {/* Logo - 价小前投研 */} navigate('/home')} style={{ minWidth: '140px' }} > 价小前投研 {/* 移动端菜单按钮 */} {isMobile ? ( } variant="ghost" onClick={onOpen} aria-label="Open menu" /> ) : } {/* 右侧:日夜模式切换 + 登录/用户区 */} : } onClick={toggleColorMode} variant="ghost" size="sm" /> {/* 显示加载状态 */} {isLoading ? ( 检查登录状态... ) : isAuthenticated && user ? ( // 已登录状态 - 用户菜单 + 功能菜单排列 } > {getDisplayName()} {getDisplayName()} {user.email} {user.phone && ( {user.phone} )} {user.has_wechat && ( 微信已绑定 )} navigate('/home/profile')}> 👤 个人资料 navigate('/home/pages/account/subscription')}> 💎 订阅管理 navigate('/home/settings')}> ⚙️ 账户设置 navigate('/home/center')}> 🏠 个人中心 🚪 退出登录 {/* 自选股 - 头像右侧 */} } leftIcon={} > 自选股 {watchlistQuotes && watchlistQuotes.length > 0 && ( {watchlistQuotes.length} )} 我的自选股 {watchlistLoading ? ( 加载中... ) : ( <> {(!watchlistQuotes || watchlistQuotes.length === 0) ? ( 暂无自选股 ) : ( {watchlistQuotes .slice((watchlistPage - 1) * WATCHLIST_PAGE_SIZE, watchlistPage * WATCHLIST_PAGE_SIZE) .map((item) => ( navigate(`/company?scode=${item.stock_code}`)}> {item.stock_name || item.stock_code} {item.stock_code} 0 ? 'red' : ((item.change_percent || 0) < 0 ? 'green' : 'gray')} fontSize="xs" > {(item.change_percent || 0) > 0 ? '+' : ''}{(item.change_percent || 0).toFixed(2)}% {item.current_price?.toFixed ? item.current_price.toFixed(2) : (item.current_price || '-')} ))} )} {watchlistPage} / {Math.max(1, Math.ceil((watchlistQuotes?.length || 0) / WATCHLIST_PAGE_SIZE))} )} {/* 关注的事件 - 头像右侧 */} } leftIcon={} > 自选事件 {followingEvents && followingEvents.length > 0 && ( {followingEvents.length} )} 我关注的事件 {eventsLoading ? ( 加载中... ) : ( <> {(!followingEvents || followingEvents.length === 0) ? ( 暂未关注任何事件 ) : ( {followingEvents .slice((eventsPage - 1) * EVENTS_PAGE_SIZE, eventsPage * EVENTS_PAGE_SIZE) .map((ev) => ( navigate(`/event-detail/${ev.id}`)}> {ev.title} {ev.event_type && ( {ev.event_type} )} {ev.start_time && ( {new Date(ev.start_time).toLocaleString('zh-CN')} )} {typeof ev.related_avg_chg === 'number' && ( 0 ? 'red' : (ev.related_avg_chg < 0 ? 'green' : 'gray')} fontSize="xs">日均 {ev.related_avg_chg > 0 ? '+' : ''}{ev.related_avg_chg.toFixed(2)}% )} {typeof ev.related_week_chg === 'number' && ( 0 ? 'red' : (ev.related_week_chg < 0 ? 'green' : 'gray')} fontSize="xs">周涨 {ev.related_week_chg > 0 ? '+' : ''}{ev.related_week_chg.toFixed(2)}% )} ))} )} {eventsPage} / {Math.max(1, Math.ceil((followingEvents?.length || 0) / EVENTS_PAGE_SIZE))} )} ) : ( // 未登录状态 - 单一按钮 )} {/* 移动端抽屉菜单 */} 菜单 {isAuthenticated && user && ( 已登录 )} {/* 移动端:日夜模式切换 */} {/* 移动端用户信息 */} {isAuthenticated && user && ( <> {getDisplayName()} {user.email} )} {/* 首页链接 */} { navigate('/home'); onClose(); }} py={2} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" color="blue.500" fontWeight="bold" > 🏠 首页 高频跟踪 { navigate('/community'); onClose(); }} py={1} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 新闻催化分析 HOT NEW { navigate('/concepts'); onClose(); }} py={1} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 概念中心 NEW 行情复盘 { navigate('/limit-analyse'); onClose(); }} py={1} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 涨停分析 FREE { navigate('/stocks'); onClose(); }} py={1} px={3} borderRadius="md" _hover={{ bg: 'gray.100' }} cursor="pointer" > 个股中心 HOT 模拟盘 NEW AGENT社群 今日热议 个股社区 联系我们 敬请期待 {/* 移动端登录/登出按钮 */} {isAuthenticated && user ? ( ) : ( )} ); }