From bc9ae8370cb2d575f8fd8ae3e6610b8832a13029 Mon Sep 17 00:00:00 2001 From: erio Date: Fri, 13 Mar 2026 22:15:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20/admin=20=E7=9B=B4=E6=8E=A5=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=95=B0=E6=8D=AE=E6=A6=82=E8=A7=88=EF=BC=8C=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=E7=AE=A1=E7=90=86=E9=A6=96=E9=A1=B5=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/layout.tsx | 5 +- src/app/admin/page.tsx | 242 ++++++++++++++++++++++----------------- 2 files changed, 141 insertions(+), 106 deletions(-) diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index f2cdfa2..733f70c 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -5,8 +5,7 @@ import { Suspense } from 'react'; import { resolveLocale } from '@/lib/locale'; const NAV_ITEMS = [ - { path: '/admin', label: { zh: '管理首页', en: 'Home' } }, - { path: '/admin/dashboard', label: { zh: '数据概览', en: 'Dashboard' } }, + { path: '/admin', label: { zh: '数据概览', en: 'Dashboard' } }, { path: '/admin/orders', label: { zh: '订单管理', en: 'Orders' } }, { path: '/admin/channels', label: { zh: '渠道管理', en: 'Channels' } }, { path: '/admin/subscriptions', label: { zh: '订阅管理', en: 'Subscriptions' } }, @@ -31,7 +30,7 @@ function AdminNav() { }; const isActive = (navPath: string) => { - if (navPath === '/admin') return pathname === '/admin'; + if (navPath === '/admin') return pathname === '/admin' || pathname === '/admin/dashboard'; return pathname.startsWith(navPath); }; diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 91cad3f..374deef 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,54 +1,36 @@ 'use client'; import { useSearchParams } from 'next/navigation'; -import { Suspense } from 'react'; +import { useState, useEffect, useCallback, Suspense } from 'react'; import PayPageLayout from '@/components/PayPageLayout'; -import { resolveLocale } from '@/lib/locale'; +import DashboardStats from '@/components/admin/DashboardStats'; +import DailyChart from '@/components/admin/DailyChart'; +import Leaderboard from '@/components/admin/Leaderboard'; +import PaymentMethodChart from '@/components/admin/PaymentMethodChart'; +import { resolveLocale, type Locale } from '@/lib/locale'; -const MODULES = [ - { - path: '/admin/dashboard', - label: { zh: '数据概览', en: 'Dashboard' }, - desc: { zh: '收入统计与订单趋势', en: 'Revenue statistics and order trends' }, - icon: ( - - - - ), - }, - { - path: '/admin/orders', - label: { zh: '订单管理', en: 'Order Management' }, - desc: { zh: '查看和管理所有充值订单', en: 'View and manage all recharge orders' }, - icon: ( - - - - ), - }, - { - path: '/admin/channels', - label: { zh: '渠道管理', en: 'Channel Management' }, - desc: { zh: '配置 API 渠道与倍率', en: 'Configure API channels and rate multipliers' }, - icon: ( - - - - ), - }, - { - path: '/admin/subscriptions', - label: { zh: '订阅管理', en: 'Subscription Management' }, - desc: { zh: '管理订阅套餐与用户订阅', en: 'Manage subscription plans and user subscriptions' }, - icon: ( - - - - ), - }, -]; +interface DashboardData { + summary: { + today: { amount: number; orderCount: number; paidCount: number }; + total: { amount: number; orderCount: number; paidCount: number }; + successRate: number; + avgAmount: number; + }; + dailySeries: { date: string; amount: number; count: number }[]; + leaderboard: { + userId: number; + userName: string | null; + userEmail: string | null; + totalAmount: number; + orderCount: number; + }[]; + paymentMethods: { paymentType: string; amount: number; count: number; percentage: number }[]; + meta: { days: number; generatedAt: string }; +} -function AdminOverviewContent() { +const DAYS_OPTIONS = [7, 30, 90] as const; + +function DashboardContent() { const searchParams = useSearchParams(); const token = searchParams.get('token'); const theme = searchParams.get('theme') === 'dark' ? 'dark' : 'light'; @@ -62,16 +44,60 @@ function AdminOverviewContent() { ? { missingToken: 'Missing admin token', missingTokenHint: 'Please access the admin page from the Sub2API platform.', - title: 'Admin Panel', - subtitle: 'Manage orders, analytics, channels and subscriptions', + invalidToken: 'Invalid admin token', + requestFailed: 'Request failed', + loadFailed: 'Failed to load data', + title: 'Dashboard', + subtitle: 'Recharge order analytics and insights', + daySuffix: 'd', + orders: 'Order Management', + refresh: 'Refresh', + loading: 'Loading...', } : { missingToken: '缺少管理员凭证', missingTokenHint: '请从 Sub2API 平台正确访问管理页面', - title: '管理后台', - subtitle: '订单、数据、渠道与订阅的统一管理入口', + invalidToken: '管理员凭证无效', + requestFailed: '请求失败', + loadFailed: '加载数据失败', + title: '数据概览', + subtitle: '充值订单统计与分析', + daySuffix: '天', + orders: '订单管理', + refresh: '刷新', + loading: '加载中...', }; + const [days, setDays] = useState(30); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + const fetchData = useCallback(async () => { + if (!token) return; + setLoading(true); + setError(''); + try { + const res = await fetch(`/api/admin/dashboard?token=${encodeURIComponent(token)}&days=${days}`); + if (!res.ok) { + if (res.status === 401) { + setError(text.invalidToken); + return; + } + throw new Error(text.requestFailed); + } + setData(await res.json()); + } catch { + setError(text.loadFailed); + } finally { + setLoading(false); + } + }, [token, days]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + if (!token) { return (
@@ -84,66 +110,76 @@ function AdminOverviewContent() { } const navParams = new URLSearchParams(); - if (token) navParams.set('token', token); + navParams.set('token', token); if (locale === 'en') navParams.set('lang', 'en'); - if (isDark) navParams.set('theme', 'dark'); + if (theme === 'dark') navParams.set('theme', 'dark'); if (isEmbedded) navParams.set('ui_mode', 'embedded'); + const btnBase = [ + 'inline-flex items-center rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors', + isDark + ? 'border-slate-600 text-slate-200 hover:bg-slate-800' + : 'border-slate-300 text-slate-700 hover:bg-slate-100', + ].join(' '); + + const btnActive = [ + 'inline-flex items-center rounded-lg px-3 py-1.5 text-xs font-medium', + isDark ? 'bg-indigo-500/30 text-indigo-200 ring-1 ring-indigo-400/40' : 'bg-blue-600 text-white', + ].join(' '); + return ( - - + + + } + > + {error && ( +
+ {error} + +
+ )} + + {loading ? ( +
{text.loading}
+ ) : data ? ( +
+ + +
+ + +
+
+ ) : null}
); } -function AdminOverviewFallback() { +function DashboardPageFallback() { const searchParams = useSearchParams(); const locale = resolveLocale(searchParams.get('lang')); @@ -154,10 +190,10 @@ function AdminOverviewFallback() { ); } -export default function AdminPage() { +export default function DashboardPage() { return ( - }> - + }> + ); }