From 6022c6ec252342f6f597c100c59cf165a2958b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Wed, 27 Aug 2025 22:57:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/request.ts | 2 +- components/SmartChatBox/SmartChatBox.tsx | 4 +- components/layout/dashboard-layout.tsx | 1 - components/layout/top-bar.tsx | 141 ++++++++++++++------- components/pages/login.tsx | 4 + components/ui/FloatingGlassPanel.tsx | 5 +- lib/api.ts | 152 ----------------------- 7 files changed, 109 insertions(+), 200 deletions(-) delete mode 100644 lib/api.ts diff --git a/api/request.ts b/api/request.ts index 73a19bd..d04ed06 100644 --- a/api/request.ts +++ b/api/request.ts @@ -104,7 +104,7 @@ export async function streamJsonPost( onJson: (json: T) => void ) { try { - const token = localStorage?.getItem('token') || 'mock-token'; + const token = localStorage?.getItem('token') || ''; const response = await fetch(`${BASE_URL}${url}`, { method: 'POST', headers: { diff --git a/components/SmartChatBox/SmartChatBox.tsx b/components/SmartChatBox/SmartChatBox.tsx index a9fae09..5c05de1 100644 --- a/components/SmartChatBox/SmartChatBox.tsx +++ b/components/SmartChatBox/SmartChatBox.tsx @@ -133,8 +133,8 @@ export default function SmartChatBox({ Chat {/* System push toggle */} - setSidebarCollapsed(!sidebarCollapsed)} /> {children} diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx index cf8082f..7e246fe 100644 --- a/components/layout/top-bar.tsx +++ b/components/layout/top-bar.tsx @@ -10,40 +10,73 @@ import { User, Sparkles, LogOut, - Bell, - PanelsLeftBottom, - Library } from 'lucide-react'; -import { motion, AnimatePresence } from 'framer-motion'; +import { motion } from 'framer-motion'; +import ReactDOM from 'react-dom'; import { useRouter } from 'next/navigation'; -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useLayoutEffect } from 'react'; +import { logoutUser } from '@/lib/auth'; + + +interface User { + id: string; + name: string; + email: string; + avatar: string; +} export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onToggleSidebar: () => void }) { - const { theme, setTheme } = useTheme(); const router = useRouter(); const [isOpen, setIsOpen] = React.useState(false); const menuRef = useRef(null); const buttonRef = useRef(null); - const currentUser = localStorage.getItem('currentUser'); - const [openModal, setOpenModal] = React.useState(false); + const currentUser: User = JSON.parse(localStorage.getItem('currentUser') || '{}'); + const [mounted, setMounted] = React.useState(false); + useLayoutEffect(() => { + console.log('Setting mounted state'); + setMounted(true); + return () => console.log('Cleanup mounted effect'); + }, []); + + + // 处理点击事件 useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - menuRef.current && - !menuRef.current.contains(event.target as Node) && - buttonRef.current && - !buttonRef.current.contains(event.target as Node) - ) { + if (!isOpen) return; + + let isClickStartedInside = false; + + const handleMouseDown = (event: MouseEvent) => { + const target = event.target as Node; + isClickStartedInside = !!( + menuRef.current?.contains(target) || + buttonRef.current?.contains(target) + ); + }; + + const handleMouseUp = (event: MouseEvent) => { + const target = event.target as Node; + const isClickEndedInside = !!( + menuRef.current?.contains(target) || + buttonRef.current?.contains(target) + ); + + // 只有当点击开始和结束都在外部时才关闭 + if (!isClickStartedInside && !isClickEndedInside) { setIsOpen(false); } + isClickStartedInside = false; }; - document.addEventListener('mousedown', handleClickOutside); + // 在冒泡阶段处理事件 + document.addEventListener('mousedown', handleMouseDown); + document.addEventListener('mouseup', handleMouseUp); + return () => { - document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('mousedown', handleMouseDown); + document.removeEventListener('mouseup', handleMouseUp); }; - }, []); + }, [isOpen]); const handleAnimationEnd = (event: React.AnimationEvent) => { const element = event.currentTarget; @@ -56,14 +89,11 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT }; return ( -
-
+
+
-
router.push('/')} onMouseEnter={handleMouseEnter} onAnimationEnd={handleAnimationEnd} @@ -88,6 +118,12 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT + {/* beta标签 */} +
+ + Beta + +
@@ -118,37 +154,56 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT */} {/* User Menu */} -
+
- - {isOpen && ( + {mounted && isOpen ? ReactDOM.createPortal( e.stopPropagation()} > {/* User Info */}
- A + {currentUser.name ? currentUser.name.charAt(0) : ''}
-
-

admin-live

-

admin-live.com

+
+

{currentUser.name}

+

{currentUser.email}

+
+
{ + logoutUser(); + }} + title="退出登录" + > +
@@ -157,27 +212,28 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT
- 100 点数 + 100 credits
{/* Menu Items */}
- router.push('/my-library')} data-alt="my-library-button" > - 我的库 + My Library - 退出账号 - + Logout + */} {/* Footer */}
-
隐私权和条款 · 许可
+
Privacy Policy · Terms of Service
250819215404 | 2025/8/20 06:00:50
- )} - + , document.body) + : null}
+
); } \ No newline at end of file diff --git a/components/pages/login.tsx b/components/pages/login.tsx index 2d806d4..2f58b39 100644 --- a/components/pages/login.tsx +++ b/components/pages/login.tsx @@ -81,6 +81,10 @@ export default function Login() { endPercentage={70} /> + {/* beta标签 */} + + Beta +
diff --git a/components/ui/FloatingGlassPanel.tsx b/components/ui/FloatingGlassPanel.tsx index 588efcf..f8470bc 100644 --- a/components/ui/FloatingGlassPanel.tsx +++ b/components/ui/FloatingGlassPanel.tsx @@ -11,9 +11,10 @@ type FloatingGlassPanelProps = { width?: string; r_key?: string | number; panel_style?: React.CSSProperties; + className?: string; }; -export default function FloatingGlassPanel({ open, onClose, children, width = '320px', r_key, panel_style, clickMaskClose = true }: FloatingGlassPanelProps) { +export default function FloatingGlassPanel({ open, onClose, children, width = '320px', r_key, panel_style, clickMaskClose = true, className }: FloatingGlassPanelProps) { // 定义弹出动画 const bounceAnimation = { scale: [0.95, 1.02, 0.98, 1], @@ -23,7 +24,7 @@ export default function FloatingGlassPanel({ open, onClose, children, width = '3 return ( {open && ( -
+
=> { - const token = getToken(); - - // 构建请求头 - const headers: Record = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - ...(options.headers as Record), - }; - - // 添加token到请求头(如果存在) - if (token) { - headers['X-EASE-ADMIN-TOKEN'] = token; - } - - try { - const response = await fetch(`${API_BASE_URL}${endpoint}`, { - ...options, - headers, - }); - - // 检查响应状态 - if (!response.ok) { - if (response.status === 401) { - // Token过期或无效 - clearAuthData(); - window.location.href = '/login'; - throw new Error('身份验证失败,请重新登录'); - } - throw new Error(`请求失败: ${response.status}`); - } - - const data = await response.json(); - - // 检查业务状态码 - if (data.code === '401' || data.status === 401) { - clearAuthData(); - window.location.href = '/login'; - throw new Error('身份验证失败,请重新登录'); - } - - return data; - } catch (error) { - console.error('API request failed:', error); - throw error; - } -}; - -/** - * GET请求 - */ -export const apiGet = (endpoint: string, options: RequestInit = {}) => { - return apiRequest(endpoint, { - ...options, - method: 'GET', - }); -}; - -/** - * POST请求 - */ -export const apiPost = (endpoint: string, data?: any, options: RequestInit = {}) => { - return apiRequest(endpoint, { - ...options, - method: 'POST', - body: data ? JSON.stringify(data) : undefined, - }); -}; - -/** - * PUT请求 - */ -export const apiPut = (endpoint: string, data?: any, options: RequestInit = {}) => { - return apiRequest(endpoint, { - ...options, - method: 'PUT', - body: data ? JSON.stringify(data) : undefined, - }); -}; - -/** - * DELETE请求 - */ -export const apiDelete = (endpoint: string, options: RequestInit = {}) => { - return apiRequest(endpoint, { - ...options, - method: 'DELETE', - }); -}; - -/** - * 文件上传请求 - */ -export const apiUpload = async (endpoint: string, formData: FormData, options: RequestInit = {}) => { - const token = getToken(); - - // 构建请求头(文件上传时不设置Content-Type,让浏览器自动设置) - const headers: Record = { - 'Accept': 'application/json', - ...(options.headers as Record), - }; - - // 添加token到请求头(如果存在) - if (token) { - headers['X-EASE-ADMIN-TOKEN'] = token; - } - - try { - const response = await fetch(`${API_BASE_URL}${endpoint}`, { - ...options, - method: 'POST', - headers, - body: formData, - }); - - // 检查响应状态 - if (!response.ok) { - if (response.status === 401) { - // Token过期或无效 - clearAuthData(); - window.location.href = '/login'; - throw new Error('身份验证失败,请重新登录'); - } - throw new Error(`请求失败: ${response.status}`); - } - - const data = await response.json(); - - // 检查业务状态码 - if (data.code === '401' || data.status === 401) { - clearAuthData(); - window.location.href = '/login'; - throw new Error('身份验证失败,请重新登录'); - } - - return data; - } catch (error) { - console.error('API request failed:', error); - throw error; - } -}; \ No newline at end of file