优化样式

This commit is contained in:
北枳 2025-08-27 22:57:52 +08:00
parent df7d8c8a0d
commit 6022c6ec25
7 changed files with 109 additions and 200 deletions

View File

@ -104,7 +104,7 @@ export async function streamJsonPost<T = any>(
onJson: (json: T) => void onJson: (json: T) => void
) { ) {
try { try {
const token = localStorage?.getItem('token') || 'mock-token'; const token = localStorage?.getItem('token') || '';
const response = await fetch(`${BASE_URL}${url}`, { const response = await fetch(`${BASE_URL}${url}`, {
method: 'POST', method: 'POST',
headers: { headers: {

View File

@ -133,8 +133,8 @@ export default function SmartChatBox({
<span>Chat</span> <span>Chat</span>
{/* System push toggle */} {/* System push toggle */}
<Switch <Switch
checkedChildren="系统推送:开" checkedChildren="System push: On"
unCheckedChildren="系统推送:关" unCheckedChildren="System push: Off"
checked={systemPush} checked={systemPush}
onChange={toggleSystemPush} onChange={toggleSystemPush}
className="ml-2" className="ml-2"

View File

@ -13,7 +13,6 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
return ( return (
<div className="min-h-screen bg-background" id="app"> <div className="min-h-screen bg-background" id="app">
<Sidebar collapsed={sidebarCollapsed} onToggle={setSidebarCollapsed} />
<TopBar collapsed={sidebarCollapsed} onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)} /> <TopBar collapsed={sidebarCollapsed} onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)} />
{children} {children}
</div> </div>

View File

@ -10,40 +10,73 @@ import {
User, User,
Sparkles, Sparkles,
LogOut, LogOut,
Bell,
PanelsLeftBottom,
Library
} from 'lucide-react'; } 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 { 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 }) { export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onToggleSidebar: () => void }) {
const { theme, setTheme } = useTheme();
const router = useRouter(); const router = useRouter();
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null); const buttonRef = useRef<HTMLButtonElement>(null);
const currentUser = localStorage.getItem('currentUser'); const currentUser: User = JSON.parse(localStorage.getItem('currentUser') || '{}');
const [openModal, setOpenModal] = React.useState(false); const [mounted, setMounted] = React.useState(false);
useLayoutEffect(() => {
console.log('Setting mounted state');
setMounted(true);
return () => console.log('Cleanup mounted effect');
}, []);
// 处理点击事件
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent) => { if (!isOpen) return;
if (
menuRef.current && let isClickStartedInside = false;
!menuRef.current.contains(event.target as Node) &&
buttonRef.current && const handleMouseDown = (event: MouseEvent) => {
!buttonRef.current.contains(event.target as Node) 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); setIsOpen(false);
} }
isClickStartedInside = false;
}; };
document.addEventListener('mousedown', handleClickOutside); // 在冒泡阶段处理事件
document.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mouseup', handleMouseUp);
return () => { return () => {
document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('mouseup', handleMouseUp);
}; };
}, []); }, [isOpen]);
const handleAnimationEnd = (event: React.AnimationEvent<HTMLDivElement>) => { const handleAnimationEnd = (event: React.AnimationEvent<HTMLDivElement>) => {
const element = event.currentTarget; const element = event.currentTarget;
@ -56,14 +89,11 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT
}; };
return ( return (
<div className="fixed right-0 top-0 left-0 h-16 header z-50"> <div className="fixed right-0 top-0 left-0 h-16 header z-[999]" style={{ isolation: 'isolate' }}>
<div className="h-full flex items-center justify-between pr-6 pl-2"> <div className="h-full flex items-center justify-between pr-6 pl-6">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Button className='button-NxtqWZ' variant="ghost" size="sm" onClick={onToggleSidebar}>
<PanelsLeftBottom className="h-4 w-4" />
</Button>
<div <div
className={`flex items-center cursor-pointer space-x-4 link-logo roll event-on`} className={`flex items-center cursor-pointer space-x-1 link-logo roll event-on`}
onClick={() => router.push('/')} onClick={() => router.push('/')}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onAnimationEnd={handleAnimationEnd} onAnimationEnd={handleAnimationEnd}
@ -88,6 +118,12 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT
</h1> </h1>
</span> </span>
</span> </span>
{/* beta标签 */}
<div className="relative transform translate-y-[-1px]">
<span className="inline-flex items-center px-1.5 py-0.5 text-[10px] font-semibold tracking-wider text-[rgb(212 202 202)] border border-[rgba(106,244,249,0.2)] rounded-full shadow-[0_0_10px_rgba(106,244,249,0.1)]">
Beta
</span>
</div>
</div> </div>
</div> </div>
@ -118,37 +154,56 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT
</Button> */} </Button> */}
{/* User Menu */} {/* User Menu */}
<div className="relative"> <div className="relative" style={{ isolation: 'isolate' }}>
<Button <Button
ref={buttonRef} ref={buttonRef}
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => setIsOpen(!isOpen)} onClick={() => {
console.log('Button clicked, current isOpen:', isOpen);
setIsOpen(!isOpen);
}}
data-alt="user-menu-trigger" data-alt="user-menu-trigger"
> >
<User className="h-4 w-4" /> <User className="h-4 w-4" />
</Button> </Button>
<AnimatePresence> {mounted && isOpen ? ReactDOM.createPortal(
{isOpen && (
<motion.div <motion.div
ref={menuRef} ref={menuRef}
initial={{ opacity: 0, scale: 0.95, y: -20 }} initial={{ opacity: 0, scale: 0.95, y: -20 }}
animate={{ opacity: 1, scale: 1, y: 0 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -20 }} exit={{ opacity: 0, scale: 0.95, y: -20 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="absolute right-0 mt-2 w-56 bg-[#1E1E1E] rounded-lg shadow-lg overflow-hidden z-50" style={{
position: 'fixed',
top: '4rem',
right: '1rem',
width: '18rem',
zIndex: 9999
}}
className="bg-[#1E1E1E] rounded-lg shadow-lg overflow-hidden"
data-alt="user-menu-dropdown" data-alt="user-menu-dropdown"
onClick={(e) => e.stopPropagation()}
> >
{/* User Info */} {/* User Info */}
<div className="p-4"> <div className="p-4">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="h-10 w-10 rounded-full bg-[#1E4D3E] flex items-center justify-center text-white font-semibold"> <div className="h-10 w-10 rounded-full bg-[#1E4D3E] flex items-center justify-center text-white font-semibold">
A {currentUser.name ? currentUser.name.charAt(0) : ''}
</div> </div>
<div> <div className='flex-1'>
<p className="text-sm font-medium">admin-live</p> <p className="text-sm font-medium">{currentUser.name}</p>
<p className="text-xs text-gray-500">admin-live.com</p> <p className="text-xs text-gray-500">{currentUser.email}</p>
</div>
<div
className='cursor-pointer hover:text-red-400 transition-colors duration-200'
onClick={() => {
logoutUser();
}}
title="退出登录"
>
<LogOut className="h-4 w-4" />
</div> </div>
</div> </div>
</div> </div>
@ -157,27 +212,28 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT
<div className="px-4 py-3 flex items-center justify-between"> <div className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Sparkles className="h-4 w-4" /> <Sparkles className="h-4 w-4" />
<span className="text-white underline text-sm">100 </span> <span className="text-white underline text-sm">100 credits</span>
</div> </div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="text-white border-white hover:bg-white/10 rounded-full px-8" className="text-white border-white hover:bg-white/10 rounded-full px-8"
onClick={() => router.push('/pricing')}
> >
Upgrade
</Button> </Button>
</div> </div>
{/* Menu Items */} {/* Menu Items */}
<div className="p-2"> <div className="p-2">
<motion.button {/* <motion.button
whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }} whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white" className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white"
onClick={() => router.push('/my-library')} onClick={() => router.push('/my-library')}
data-alt="my-library-button" data-alt="my-library-button"
> >
<Library className="h-4 w-4" /> <Library className="h-4 w-4" />
<span></span> <span>My Library</span>
</motion.button> </motion.button>
<motion.button <motion.button
@ -190,21 +246,22 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT
data-alt="logout-button" data-alt="logout-button"
> >
<LogOut className="h-4 w-4" /> <LogOut className="h-4 w-4" />
<span>退</span> <span>Logout</span>
</motion.button> </motion.button> */}
{/* Footer */} {/* Footer */}
<div className="mt-4 px-3 py-2 text-xs text-gray-400 text-center"> <div className="mt-4 px-3 py-2 text-xs text-gray-400 text-center">
<div> · </div> <div>Privacy Policy · Terms of Service</div>
<div>250819215404 | 2025/8/20 06:00:50</div> <div>250819215404 | 2025/8/20 06:00:50</div>
</div> </div>
</div> </div>
</motion.div> </motion.div>
)} , document.body)
</AnimatePresence> : null}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
} }

View File

@ -81,6 +81,10 @@ export default function Login() {
endPercentage={70} endPercentage={70}
/> />
</span> </span>
{/* beta标签 */}
<span className="inline-flex items-center px-1.5 py-0.5 text-[8px] font-semibold tracking-wider text-[rgb(212 202 202)] border border-[rgba(106,244,249,0.2)] rounded-full shadow-[0_0_10px_rgba(106,244,249,0.1)]">
Beta
</span>
</div> </div>
<div className="left-panel"> <div className="left-panel">

View File

@ -11,9 +11,10 @@ type FloatingGlassPanelProps = {
width?: string; width?: string;
r_key?: string | number; r_key?: string | number;
panel_style?: React.CSSProperties; 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 = { const bounceAnimation = {
scale: [0.95, 1.02, 0.98, 1], scale: [0.95, 1.02, 0.98, 1],
@ -23,7 +24,7 @@ export default function FloatingGlassPanel({ open, onClose, children, width = '3
return ( return (
<AnimatePresence> <AnimatePresence>
{open && ( {open && (
<div className="fixed inset-0 z-50 flex items-center justify-center"> <div className={`fixed inset-0 z-50 flex items-center justify-center ${className}`}>
<motion.div <motion.div
key={r_key} key={r_key}
className="cursor-grab active:cursor-grabbing" className="cursor-grab active:cursor-grabbing"

View File

@ -1,152 +0,0 @@
import { getToken, clearAuthData } from './auth';
// API基础URL
const API_BASE_URL = 'https://77.smartvideo.py.qikongjian.com';
/**
* API请求方法
*/
export const apiRequest = async (
endpoint: string,
options: RequestInit = {}
): Promise<any> => {
const token = getToken();
// 构建请求头
const headers: Record<string, string> = {
'Accept': 'application/json',
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
};
// 添加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<string, string> = {
'Accept': 'application/json',
...(options.headers as Record<string, string>),
};
// 添加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;
}
};