feat: 我的页整合扫一扫/设置与提现、all-chapters去重、内容上传API、文档与后台登录

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
卡若
2026-02-20 18:50:16 +08:00
parent 09fb67d2af
commit 0e4baa4b7f
27 changed files with 1526 additions and 347 deletions

41
app/admin/error.tsx Normal file
View 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>
)
}

View File

@@ -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>
))}