| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- import React, { useState, useEffect, useCallback, useRef } from 'react';
- import { useNavigate } from 'react-router-dom';
- import { LogIn, User, LogOut, ChevronDown, ShieldCheck, ShieldAlert } from '../icons/commonIcons';
- import { Lightbulb } from '../icons/commonIcons';
- import { authService, UserInfo } from '../services/authService';
- import { BrandingContext } from '../App';
- const Navbar: React.FC = () => {
- const navigate = useNavigate();
- const branding = React.useContext(BrandingContext);
- const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
- const [isAuthenticated, setIsAuthenticated] = useState(false);
- const [, forceUpdate] = useState({});
- const [showDropdown, setShowDropdown] = useState(false);
- const dropdownRef = useRef<HTMLDivElement>(null);
- const checkAuthStatus = useCallback(() => {
- const authenticated = authService.isAuthenticated();
- const user = authService.getUserInfo();
- console.log('[Navbar] checkAuthStatus - authenticated:', authenticated, 'user:', user);
- setIsAuthenticated(authenticated);
- setUserInfo(authenticated ? user : null);
- forceUpdate({});
- }, []);
- useEffect(() => {
- // 初始检查
- checkAuthStatus();
- // 订阅认证状态变化
- const unsubscribe = authService.subscribe(() => {
- console.log('[Navbar] auth_state_change event received');
- checkAuthStatus();
- });
- // 监听存储变化(其他标签页)
- const handleStorageChange = (e: StorageEvent) => {
- if (e.key === 'aigc_space_token' || e.key === 'aigc_space_user_info' || e.key === null) {
- checkAuthStatus();
- }
- };
- window.addEventListener('storage', handleStorageChange);
- return () => {
- unsubscribe();
- window.removeEventListener('storage', handleStorageChange);
- };
- }, [checkAuthStatus]);
- const handleLogin = () => {
- navigate('/login');
- };
- const handleProfile = () => {
- setShowDropdown(false);
- navigate('/profile');
- };
- const handleLogout = async () => {
- setShowDropdown(false);
- // 先本地清理登录状态
- try {
- if (typeof window !== 'undefined') {
- localStorage.removeItem('aigc_space_token');
- localStorage.removeItem('aigc_space_refresh_token');
- localStorage.removeItem('aigc_space_user_info');
- }
- } catch (e) {
- // ignore
- }
- if (typeof window === 'undefined') return;
- // 查询后端 SSO 配置,决定跳转目标
- try {
- const cfg = await authService.getSsoConfig();
- if (cfg?.sso_enabled) {
- // SSO 启用:跳 CAS 注销页
- const casLogoutUrl = cfg.cas_login_url.replace(/\/login$/, '/logout');
- try {
- if (window.top && window.top !== window) {
- window.top.location.replace(casLogoutUrl);
- } else {
- window.location.replace(casLogoutUrl);
- }
- } catch {
- window.location.href = casLogoutUrl;
- }
- } else {
- // SSO 未启用:直接跳普通登录页
- window.location.replace('/');
- }
- } catch {
- window.location.replace('/');
- }
- };
- // 点击外部关闭下拉菜单
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
- setShowDropdown(false);
- }
- };
- document.addEventListener('mousedown', handleClickOutside);
- return () => document.removeEventListener('mousedown', handleClickOutside);
- }, []);
- return (
- <header className="fixed top-0 left-0 right-0 h-14 sm:h-16 bg-white border-b border-gray-100 flex items-center justify-between px-4 sm:px-6 z-50">
- {/* 左侧:标题 */}
- <div className="flex items-center space-x-2">
- <div className="w-7 h-7 sm:w-8 sm:h-8 bg-blue-600 rounded-lg flex items-center justify-center flex-shrink-0 overflow-hidden">
- {branding.system_logo
- ? <img src={branding.system_logo} alt="logo" className="w-full h-full object-contain" />
- : <Lightbulb className="text-white w-4 h-4 sm:w-5 sm:h-5" />}
- </div>
- <span className="text-base sm:text-xl font-bold text-blue-600 tracking-tight hidden xs:inline sm:inline">
- {branding.system_name}
- </span>
- </div>
- {/* 右侧:用户操作 */}
- <div className="flex items-center space-x-2 sm:space-x-4">
- {isAuthenticated ? (
- <>
- {/* 用户头像和昵称 - 下拉菜单 */}
- <div className="relative" ref={dropdownRef}>
- <div
- onClick={() => setShowDropdown(!showDropdown)}
- className="flex items-center space-x-1 sm:space-x-2 cursor-pointer group hover:bg-gray-50 rounded-lg px-1 sm:px-2 py-1 transition-colors"
- >
- <div className="w-7 h-7 sm:w-8 sm:h-8 rounded-full overflow-hidden border border-gray-200 flex-shrink-0">
- {userInfo?.avatar ? (
- <img
- src={userInfo.avatar}
- alt="用户头像"
- className="w-full h-full object-cover"
- />
- ) : (
- <div className="w-full h-full bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center text-white text-xs sm:text-sm font-bold">
- {userInfo?.nickname?.charAt(0)?.toUpperCase() || 'U'}
- </div>
- )}
- </div>
- <div className="hidden sm:flex items-center">
- <span className="text-sm font-medium text-gray-700 group-hover:text-blue-600 transition-colors">
- {userInfo?.nickname || '用户'}
- </span>
- <ChevronDown className={`w-4 h-4 ml-1 text-gray-400 transition-transform ${showDropdown ? 'rotate-180' : ''}`} />
- </div>
- <ChevronDown className={`w-3 h-3 sm:hidden text-gray-400 transition-transform ${showDropdown ? 'rotate-180' : ''}`} />
- </div>
-
- {/* 下拉菜单 */}
- {showDropdown && (
- <div className="absolute right-0 top-full mt-2 w-40 bg-white rounded-lg shadow-lg border border-gray-100 py-1 z-50">
- <button
- onClick={handleProfile}
- className="w-full flex items-center space-x-2 px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 hover:text-blue-600 transition-colors"
- >
- <User className="w-4 h-4" />
- <span>个人中心</span>
- </button>
- <button
- onClick={handleLogout}
- className="w-full flex items-center space-x-2 px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 hover:text-red-600 transition-colors"
- >
- <LogOut className="w-4 h-4" />
- <span>退出登录</span>
- </button>
- </div>
- )}
- </div>
- </>
- ) : (
- /* 未登录状态 */
- <div className="flex items-center space-x-3">
- <button
- onClick={handleLogin}
- className="flex items-center space-x-1 sm:space-x-2 px-3 sm:px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium"
- >
- <LogIn className="w-4 h-4" />
- <span>登录</span>
- </button>
- </div>
- )}
- </div>
- </header>
- );
- };
- export default Navbar;
|