feat: 集成错误处理与网络状态管理
- 在 Providers 组件中引入 ErrorProvider 以处理全局错误状态 - 更新 PlayerAppShell 组件的注释,说明网络状态横幅的用途 - 在 lotteryHttp 中添加对 500、502、503 错误的处理,更新全局错误状态 - 导出 useNetworkStatus 和 useIsOffline 钩子以支持网络状态管理
This commit is contained in:
130
src/app/error.tsx
Normal file
130
src/app/error.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { AlertTriangle, Home, RotateCcw } from "lucide-react";
|
||||
|
||||
import { ERROR_COLORS } from "@/stores/error-store";
|
||||
|
||||
/**
|
||||
* Next.js 错误边界组件
|
||||
* 当页面渲染过程中发生错误时显示
|
||||
* 自动捕获 React 错误和客户端异常
|
||||
*/
|
||||
export default function ErrorBoundary({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}): React.ReactElement {
|
||||
useEffect(() => {
|
||||
// 记录错误日志
|
||||
console.error("[ErrorBoundary] Application error:", error);
|
||||
}, [error]);
|
||||
|
||||
// 判断是否是 500 服务器错误
|
||||
const isServerError = error.message?.includes("500") ||
|
||||
error.message?.includes("服务器") ||
|
||||
error.name === "LotteryApiBizError";
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-background p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
{/* 错误图标 */}
|
||||
<div
|
||||
className="mb-6 flex size-24 items-center justify-center rounded-full"
|
||||
style={{
|
||||
backgroundColor: isServerError
|
||||
? `${ERROR_COLORS.error}15`
|
||||
: `${ERROR_COLORS.warning}15`,
|
||||
}}
|
||||
>
|
||||
<AlertTriangle
|
||||
className="size-12"
|
||||
style={{
|
||||
color: isServerError ? ERROR_COLORS.error : ERROR_COLORS.warning,
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 错误标题 */}
|
||||
<h1 className="mb-2 text-2xl font-bold text-foreground">
|
||||
{isServerError ? "服务器暂时不可用" : "应用发生错误"}
|
||||
</h1>
|
||||
|
||||
{/* 错误消息 */}
|
||||
<p className="mb-2 text-base text-muted-foreground">
|
||||
{isServerError
|
||||
? "服务器暂时不可用,请稍后重试"
|
||||
: "抱歉,应用遇到了问题,请尝试刷新页面"}
|
||||
</p>
|
||||
|
||||
{/* 技术详情(开发模式) */}
|
||||
{process.env.NODE_ENV === "development" && (
|
||||
<div className="mb-6 w-full rounded-lg bg-muted p-3 text-left">
|
||||
<p className="mb-1 text-xs font-medium text-muted-foreground">错误详情:</p>
|
||||
<code className="block max-h-32 overflow-auto whitespace-pre-wrap break-words text-xs text-destructive">
|
||||
{error.message}
|
||||
</code>
|
||||
{error.digest && (
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
错误 ID: {error.digest}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<button
|
||||
onClick={reset}
|
||||
className="inline-flex items-center justify-center gap-2 rounded-lg border border-current px-4 py-2 text-sm font-medium transition-colors hover:bg-current/5"
|
||||
style={{ borderColor: isServerError ? ERROR_COLORS.error : ERROR_COLORS.warning, color: isServerError ? ERROR_COLORS.error : ERROR_COLORS.warning }}
|
||||
>
|
||||
<RotateCcw className="size-4" />
|
||||
重试
|
||||
</button>
|
||||
|
||||
<Link
|
||||
href="/hall"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white"
|
||||
style={{
|
||||
backgroundColor: isServerError ? ERROR_COLORS.error : ERROR_COLORS.warning,
|
||||
}}
|
||||
>
|
||||
<Home className="size-4" />
|
||||
返回首页
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 辅助链接 */}
|
||||
<div className="mt-8 flex gap-4 text-sm text-muted-foreground">
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="hover:text-foreground hover:underline"
|
||||
>
|
||||
刷新页面
|
||||
</button>
|
||||
<span>|</span>
|
||||
<Link
|
||||
href="/hall"
|
||||
className="hover:text-foreground hover:underline"
|
||||
>
|
||||
投注大厅
|
||||
</Link>
|
||||
<span>|</span>
|
||||
<Link
|
||||
href="/wallet"
|
||||
className="hover:text-foreground hover:underline"
|
||||
>
|
||||
我的钱包
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
86
src/app/not-found.tsx
Normal file
86
src/app/not-found.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { FileQuestion, Home } from "lucide-react";
|
||||
|
||||
import { ERROR_COLORS } from "@/stores/error-store";
|
||||
|
||||
/**
|
||||
* 全局 404 页面
|
||||
* 当访问不存在的路由时显示
|
||||
* 消息:"页面不存在"
|
||||
* 包含 "返回首页" 按钮
|
||||
* 使用中性颜色 #d9d9d9 作为背景/插图
|
||||
*/
|
||||
export default function NotFoundPage(): React.ReactElement {
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-screen flex-col items-center justify-center p-4"
|
||||
style={{ backgroundColor: ERROR_COLORS.neutral }}
|
||||
>
|
||||
<div className="w-full max-w-md rounded-2xl bg-white p-8 shadow-lg">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
{/* 404 数字 */}
|
||||
<div
|
||||
className="mb-6 text-8xl font-bold tracking-tighter"
|
||||
style={{ color: ERROR_COLORS.neutral }}
|
||||
>
|
||||
404
|
||||
</div>
|
||||
|
||||
{/* 图标 */}
|
||||
<div
|
||||
className="mb-6 flex size-20 items-center justify-center rounded-full"
|
||||
style={{ backgroundColor: `${ERROR_COLORS.neutral}40` }}
|
||||
>
|
||||
<FileQuestion
|
||||
className="size-10"
|
||||
style={{ color: "#666" }}
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 标题 */}
|
||||
<h1 className="mb-3 text-2xl font-bold text-gray-800">
|
||||
页面不存在
|
||||
</h1>
|
||||
|
||||
{/* 描述 */}
|
||||
<p className="mb-8 text-base text-gray-500">
|
||||
抱歉,您访问的页面可能已被删除、移动或从未存在过。
|
||||
</p>
|
||||
|
||||
{/* 返回首页按钮 */}
|
||||
<Link
|
||||
href="/hall"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-lg px-8 py-3 text-base font-medium text-white"
|
||||
style={{ backgroundColor: "#333" }}
|
||||
>
|
||||
<Home className="size-5" />
|
||||
返回首页
|
||||
</Link>
|
||||
|
||||
{/* 帮助链接 */}
|
||||
<div className="mt-8 flex gap-4 text-sm text-gray-400">
|
||||
<Link href="/hall" className="hover:text-gray-600 hover:underline">
|
||||
投注大厅
|
||||
</Link>
|
||||
<span>|</span>
|
||||
<Link href="/results" className="hover:text-gray-600 hover:underline">
|
||||
开奖结果
|
||||
</Link>
|
||||
<span>|</span>
|
||||
<Link href="/wallet" className="hover:text-gray-600 hover:underline">
|
||||
我的钱包
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 品牌信息 */}
|
||||
<p className="mt-8 text-sm text-gray-500">
|
||||
Lottery © {new Date().getFullYear()}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user