forked from 77media/video-flow
优化样式
This commit is contained in:
parent
df7d8c8a0d
commit
6022c6ec25
@ -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: {
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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">
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
152
lib/api.ts
152
lib/api.ts
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user