feat: 我的页整合扫一扫/设置与提现、all-chapters去重、内容上传API、文档与后台登录
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
41
app/admin/error.tsx
Normal file
41
app/admin/error.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function AdminError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
reset: () => void
|
||||
}) {
|
||||
useEffect(() => {
|
||||
console.error('[Admin] 页面错误:', error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-[#0a1628]">
|
||||
<div className="bg-[#0f2137] border border-gray-700/50 rounded-2xl p-8 max-w-md w-full mx-4 shadow-xl">
|
||||
<div className="text-center">
|
||||
<div className="text-5xl mb-4">😞</div>
|
||||
<h2 className="text-xl font-bold text-white mb-2">哎呀,出错了</h2>
|
||||
<p className="text-gray-400 text-sm mb-6">页面遇到了一些问题,请稍后再试</p>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={reset}
|
||||
className="flex-1 py-2.5 rounded-lg bg-[#38bdac] hover:bg-[#2da396] text-white text-sm font-medium"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
<a
|
||||
href="/admin"
|
||||
className="flex-1 py-2.5 rounded-lg bg-gray-700 hover:bg-gray-600 text-gray-200 text-sm font-medium text-center"
|
||||
>
|
||||
返回后台
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -11,24 +11,25 @@ export default function AdminDashboard() {
|
||||
const [users, setUsers] = useState<any[]>([])
|
||||
const [purchases, setPurchases] = useState<any[]>([])
|
||||
|
||||
// 从API获取数据
|
||||
// 从API获取数据(任意接口失败时仍保持页面可展示,不抛错)
|
||||
async function loadData() {
|
||||
try {
|
||||
// 获取用户数据
|
||||
const usersRes = await fetch('/api/db/users')
|
||||
const usersData = await usersRes.json()
|
||||
if (usersData.success && usersData.users) {
|
||||
const usersData = await usersRes.ok ? usersRes.json().catch(() => ({})) : { success: false }
|
||||
if (usersData.success && Array.isArray(usersData.users)) {
|
||||
setUsers(usersData.users)
|
||||
}
|
||||
|
||||
// 获取订单数据
|
||||
} catch (e) {
|
||||
console.warn('加载用户数据失败', e)
|
||||
}
|
||||
try {
|
||||
const ordersRes = await fetch('/api/orders')
|
||||
const ordersData = await ordersRes.json()
|
||||
if (ordersData.success && ordersData.orders) {
|
||||
const ordersData = await ordersRes.ok ? ordersRes.json().catch(() => ({})) : { success: false }
|
||||
if (ordersData.success && Array.isArray(ordersData.orders)) {
|
||||
setPurchases(ordersData.orders)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('加载数据失败', e)
|
||||
console.warn('加载订单数据失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ export default function AdminDashboard() {
|
||||
)
|
||||
}
|
||||
|
||||
const totalRevenue = purchases.reduce((sum, p) => sum + (p.amount || 0), 0)
|
||||
const totalRevenue = purchases.reduce((sum, p) => sum + (Number(p?.amount) || 0), 0)
|
||||
const totalUsers = users.length
|
||||
const totalPurchases = purchases.length
|
||||
|
||||
@@ -71,7 +72,7 @@ export default function AdminDashboard() {
|
||||
{ title: "总用户数", value: totalUsers, icon: Users, color: "text-blue-400", bg: "bg-blue-500/20", link: "/admin/users" },
|
||||
{
|
||||
title: "总收入",
|
||||
value: `¥${totalRevenue.toFixed(2)}`,
|
||||
value: `¥${Number(totalRevenue).toFixed(2)}`,
|
||||
icon: TrendingUp,
|
||||
color: "text-[#38bdac]",
|
||||
bg: "bg-[#38bdac]/20",
|
||||
@@ -80,7 +81,7 @@ export default function AdminDashboard() {
|
||||
{ title: "订单数", value: totalPurchases, icon: ShoppingBag, color: "text-purple-400", bg: "bg-purple-500/20", link: "/admin/orders" },
|
||||
{
|
||||
title: "转化率",
|
||||
value: `${totalUsers > 0 ? ((totalPurchases / totalUsers) * 100).toFixed(1) : 0}%`,
|
||||
value: `${totalUsers > 0 ? (Number(totalPurchases) / Number(totalUsers) * 100).toFixed(1) : 0}%`,
|
||||
icon: BookOpen,
|
||||
color: "text-orange-400",
|
||||
bg: "bg-orange-500/20",
|
||||
@@ -132,11 +133,11 @@ export default function AdminDashboard() {
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">{p.sectionTitle || "整本购买"}</p>
|
||||
<p className="text-xs text-gray-500">{new Date(p.createdAt).toLocaleString()}</p>
|
||||
<p className="text-xs text-gray-500">{p?.createdAt ? new Date(p.createdAt).toLocaleString() : "-"}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-bold text-[#38bdac]">+¥{p.amount}</p>
|
||||
<p className="text-xs text-gray-400">{p.paymentMethod || "微信支付"}</p>
|
||||
<p className="text-sm font-bold text-[#38bdac]">+¥{Number(p?.amount) || 0}</p>
|
||||
<p className="text-xs text-gray-400">{p?.paymentMethod || "微信支付"}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -169,7 +170,7 @@ export default function AdminDashboard() {
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400">
|
||||
{u.createdAt ? new Date(u.createdAt).toLocaleDateString() : "-"}
|
||||
{u?.createdAt ? new Date(u.createdAt).toLocaleDateString() : "-"}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user