diff --git a/SuperAdmin/app/layout.tsx b/SuperAdmin/app/layout.tsx
index cf84e55d..298e2c51 100644
--- a/SuperAdmin/app/layout.tsx
+++ b/SuperAdmin/app/layout.tsx
@@ -4,6 +4,7 @@ import { Inter } from "next/font/google"
import "./globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { ToastProvider } from "@/components/ui/use-toast"
+import ErrorBoundary from "@/components/ErrorBoundary"
const inter = Inter({ subsets: ["latin"] })
@@ -23,7 +24,9 @@ export default function RootLayout({
- {children}
+
+ {children}
+
diff --git a/SuperAdmin/app/login/page.tsx b/SuperAdmin/app/login/page.tsx
index 5188527b..47dc57fe 100644
--- a/SuperAdmin/app/login/page.tsx
+++ b/SuperAdmin/app/login/page.tsx
@@ -11,11 +11,22 @@ import { Label } from "@/components/ui/label"
import { md5, saveAdminInfo } from "@/lib/utils"
import { login } from "@/lib/admin-api"
import { useToast } from "@/components/ui/use-toast"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog"
export default function LoginPage() {
const [account, setAccount] = useState("")
const [password, setPassword] = useState("")
const [isLoading, setIsLoading] = useState(false)
+ const [errorDialogOpen, setErrorDialogOpen] = useState(false)
+ const [errorMessage, setErrorMessage] = useState("")
const router = useRouter()
const { toast } = useToast()
@@ -44,22 +55,16 @@ export default function LoginPage() {
// 跳转到仪表盘
router.push("/dashboard")
} else {
- // 显示错误提示
- toast({
- title: "登录失败",
- description: result.msg || "账号或密码错误",
- variant: "destructive",
- })
+ // 显示错误弹窗
+ setErrorMessage(result.msg || "账号或密码错误")
+ setErrorDialogOpen(true)
}
- } catch (err) {
+ } catch (err: any) {
console.error("登录失败:", err)
- // 显示错误提示
- toast({
- title: "登录失败",
- description: "网络错误,请稍后再试",
- variant: "destructive",
- })
+ // 显示错误弹窗
+ setErrorMessage(err.msg || "网络错误,请稍后再试")
+ setErrorDialogOpen(true)
} finally {
setIsLoading(false)
}
@@ -103,6 +108,23 @@ export default function LoginPage() {
+
+ {/* 错误提示弹窗 */}
+
+
+
+ 登录失败
+
+ {errorMessage}
+
+
+
+ setErrorDialogOpen(false)}>
+ 确定
+
+
+
+
)
}
diff --git a/SuperAdmin/components/ErrorBoundary.tsx b/SuperAdmin/components/ErrorBoundary.tsx
new file mode 100644
index 00000000..d2f86cf4
--- /dev/null
+++ b/SuperAdmin/components/ErrorBoundary.tsx
@@ -0,0 +1,100 @@
+"use client"
+
+import React, { type ErrorInfo } from "react"
+import { Card, CardContent, CardFooter } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { useRouter } from "next/navigation"
+import { useToast } from "@/components/ui/use-toast"
+
+interface ErrorBoundaryProps {
+ children: React.ReactNode
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean
+ error?: Error
+}
+
+class ErrorBoundary extends React.Component {
+ constructor(props: ErrorBoundaryProps) {
+ super(props)
+ this.state = { hasError: false }
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ // 更新state使下一次渲染可以显示错误界面
+ return { hasError: true, error }
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ // 记录错误信息
+ console.error("错误边界捕获到错误:", error, errorInfo)
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return this.setState({ hasError: false })} />
+ }
+
+ return this.props.children
+ }
+}
+
+// 错误显示界面
+function ErrorScreen({ error, onReset }: { error?: Error; onReset: () => void }) {
+ const router = useRouter()
+ const { toast } = useToast()
+
+ // 导航到主页
+ const goHome = () => {
+ router.push("/dashboard")
+ onReset()
+ }
+
+ // 刷新当前页面
+ const refreshPage = () => {
+ if (typeof window !== "undefined") {
+ toast({
+ title: "正在刷新页面",
+ description: "正在重新加载页面内容...",
+ variant: "default",
+ })
+ setTimeout(() => {
+ window.location.reload()
+ }, 500)
+ }
+ }
+
+ return (
+
+
+
+
+
+
页面出错了
+
+ 很抱歉,页面加载过程中遇到了问题。
+
+ {error && (
+
+ {error.message}
+
+ )}
+
+
+ 您可以尝试刷新页面或返回首页。如果问题持续存在,请联系系统管理员。
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default ErrorBoundary
\ No newline at end of file
diff --git a/SuperAdmin/lib/api-utils.ts b/SuperAdmin/lib/api-utils.ts
index 8ddd25d9..087707d0 100644
--- a/SuperAdmin/lib/api-utils.ts
+++ b/SuperAdmin/lib/api-utils.ts
@@ -1,8 +1,7 @@
import { getConfig } from './config';
-import { getAdminInfo, clearAdminInfo } from './utils';
/**
- * API响应数据结构
+ * API响应接口
*/
export interface ApiResponse {
code: number;
@@ -11,7 +10,7 @@ export interface ApiResponse {
}
/**
- * 通用API请求函数
+ * API请求函数
* @param endpoint API端点
* @param method HTTP方法
* @param data 请求数据
@@ -22,62 +21,61 @@ export async function apiRequest(
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
data?: any
): Promise> {
+ // 获取API基础URL
const { apiBaseUrl } = getConfig();
const url = `${apiBaseUrl}${endpoint}`;
- // 获取认证信息
- const adminInfo = getAdminInfo();
-
- // 请求头
+ // 构建请求头
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
- // 如果有认证信息,添加Cookie头
- if (adminInfo?.token) {
- // 添加认证令牌,作为Cookie发送
- document.cookie = `admin_id=${adminInfo.id}; path=/`;
- document.cookie = `admin_token=${adminInfo.token}; path=/`;
+ // 添加认证信息(如果有)
+ if (typeof window !== 'undefined') {
+ const token = localStorage.getItem('admin_token');
+ if (token) {
+ // 设置Cookie中的认证信息
+ document.cookie = `admin_token=${token}; path=/`;
+ }
}
- // 请求配置
- const config: RequestInit = {
+ // 构建请求选项
+ const options: RequestInit = {
method,
headers,
credentials: 'include', // 包含跨域请求的Cookie
};
- // 如果有请求数据,转换为JSON
- if (data && method !== 'GET') {
- config.body = JSON.stringify(data);
+ // 添加请求体(针对POST、PUT请求)
+ if (method !== 'GET' && data) {
+ options.body = JSON.stringify(data);
}
try {
- const response = await fetch(url, config);
+ // 发送请求
+ const response = await fetch(url, options);
- if (!response.ok) {
- throw new Error(`请求失败: ${response.status} ${response.statusText}`);
- }
+ // 解析响应
+ const result = await response.json();
- const result = await response.json() as ApiResponse;
-
- // 如果返回未授权错误,清除登录信息
- if (result.code === 401) {
- clearAdminInfo();
- // 如果在浏览器环境,跳转到登录页
- if (typeof window !== 'undefined') {
- window.location.href = '/login';
+ // 如果响应状态码不是2xx,或者接口返回的code不是200,抛出错误
+ if (!response.ok || (result && result.code !== 200)) {
+ // 如果是认证错误,清除登录信息
+ if (result.code === 401) {
+ if (typeof window !== 'undefined') {
+ localStorage.removeItem('admin_id');
+ localStorage.removeItem('admin_name');
+ localStorage.removeItem('admin_account');
+ localStorage.removeItem('admin_token');
+ }
}
+
+ throw result; // 抛出响应结果作为错误
}
return result;
} catch (error) {
- console.error('API请求错误:', error);
-
- return {
- code: 500,
- msg: error instanceof Error ? error.message : '未知错误',
- data: null
- };
+ // 直接抛出错误,由调用方处理
+ throw error;
}
}
\ No newline at end of file
diff --git a/SuperAdmin/lib/error-handler.ts b/SuperAdmin/lib/error-handler.ts
new file mode 100644
index 00000000..0778c298
--- /dev/null
+++ b/SuperAdmin/lib/error-handler.ts
@@ -0,0 +1,153 @@
+"use client"
+
+import { useToast } from "@/components/ui/use-toast"
+
+// 错误类型
+type ErrorType = 'api' | 'auth' | 'network' | 'validation' | 'unknown';
+
+// 错误处理配置
+interface ErrorConfig {
+ title: string;
+ variant: "default" | "destructive" | "success";
+ defaultMessage: string;
+}
+
+// 不同类型错误的配置
+const errorConfigs: Record = {
+ api: {
+ title: '接口错误',
+ variant: 'destructive',
+ defaultMessage: '服务器处理请求失败,请稍后再试',
+ },
+ auth: {
+ title: '认证错误',
+ variant: 'destructive',
+ defaultMessage: '您的登录状态已失效,请重新登录',
+ },
+ network: {
+ title: '网络错误',
+ variant: 'destructive',
+ defaultMessage: '网络连接失败,请检查您的网络状态',
+ },
+ validation: {
+ title: '数据验证错误',
+ variant: 'destructive',
+ defaultMessage: '输入数据不正确,请检查后重试',
+ },
+ unknown: {
+ title: '未知错误',
+ variant: 'destructive',
+ defaultMessage: '发生未知错误,请刷新页面后重试',
+ },
+};
+
+/**
+ * 全局错误处理工具,使用React Hook方式调用
+ */
+export function useErrorHandler() {
+ const { toast } = useToast();
+
+ /**
+ * 处理API响应错误
+ * @param error 错误对象
+ * @param customMessage 自定义错误消息
+ * @param errorType 错误类型
+ */
+ const handleError = (
+ error: any,
+ customMessage?: string,
+ errorType: ErrorType = 'api'
+ ) => {
+ let message = customMessage;
+ let type = errorType;
+
+ // 如果是API错误响应
+ if (error && error.code !== undefined) {
+ switch (error.code) {
+ case 401:
+ type = 'auth';
+ message = error.msg || errorConfigs.auth.defaultMessage;
+
+ // 清除登录信息并跳转到登录页
+ if (typeof window !== 'undefined') {
+ localStorage.removeItem('admin_id');
+ localStorage.removeItem('admin_name');
+ localStorage.removeItem('admin_account');
+ localStorage.removeItem('admin_token');
+
+ // 延迟跳转,确保用户能看到错误提示
+ setTimeout(() => {
+ window.location.href = '/login';
+ }, 1500);
+ }
+ break;
+ case 400:
+ type = 'validation';
+ message = error.msg || errorConfigs.validation.defaultMessage;
+ break;
+ case 500:
+ message = error.msg || errorConfigs.api.defaultMessage;
+ break;
+ default:
+ message = error.msg || message || errorConfigs[type].defaultMessage;
+ }
+ } else if (error instanceof Error) {
+ // 如果是普通Error对象
+ if (error.message.includes('network') || error.message.includes('fetch')) {
+ type = 'network';
+ message = errorConfigs.network.defaultMessage;
+ } else {
+ message = error.message || errorConfigs.unknown.defaultMessage;
+ }
+ }
+
+ // 使用Toast显示错误
+ toast({
+ title: errorConfigs[type].title,
+ description: message || errorConfigs[type].defaultMessage,
+ variant: errorConfigs[type].variant,
+ });
+
+ // 将错误信息记录到控制台,方便调试
+ console.error('Error:', error);
+ };
+
+ return { handleError };
+}
+
+/**
+ * 封装错误处理的高阶函数,用于API请求
+ * @param apiFn API函数
+ * @param errorMessage 自定义错误消息
+ * @param onError 错误发生时的回调函数
+ */
+export function withErrorHandling(
+ apiFn: (...args: Args) => Promise,
+ errorMessage?: string,
+ onError?: (error: any) => void
+) {
+ return async (...args: Args): Promise => {
+ try {
+ return await apiFn(...args);
+ } catch (error) {
+ if (typeof window !== 'undefined') {
+ // 创建一个临时div来获取toast函数
+ const div = document.createElement('div');
+ div.style.display = 'none';
+ document.body.appendChild(div);
+
+ const { handleError } = useErrorHandler();
+ handleError(error, errorMessage);
+
+ document.body.removeChild(div);
+ }
+
+ // 调用外部错误处理函数
+ if (onError) {
+ onError(error);
+ }
+
+ return null;
+ }
+ };
+}
\ No newline at end of file