'use client'; import type { ReactNode } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import dynamic from 'next/dynamic'; import { AlertTriangle, BarChart3, Bell, CheckCircle2, Coins, CreditCard, Database, Eye, Home, Key, LayoutDashboard, ListChecks, Logs, Loader2, Menu, Package, PlugZap, Receipt, RefreshCw, Settings, Shield, Sparkles, Users, X, type LucideIcon, } from 'lucide-react'; import { toast } from 'sonner'; import { useAuth } from '@/lib/auth-store'; import { useSiteConfig } from '@/lib/site-config'; import { cn } from '@/lib/utils'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; const ApiManagementTab = dynamic(() => import('@/components/admin/api-management-tab'), { ssr: false }); const UserManagementTab = dynamic(() => import('@/components/admin/user-management-tab'), { ssr: false }); const PricingTab = dynamic(() => import('@/components/admin/pricing-tab'), { ssr: false }); const OrderManagementTab = dynamic(() => import('@/components/admin/order-management-tab'), { ssr: false }); const PaymentTab = dynamic(() => import('@/components/admin/payment-tab'), { ssr: false }); const AnnouncementTab = dynamic(() => import('@/components/admin/announcement-tab'), { ssr: false }); const DataManagementTab = dynamic(() => import('@/components/admin/data-management-tab'), { ssr: false }); const SystemUpgradeTab = dynamic(() => import('@/components/admin/system-upgrade-tab'), { ssr: false }); const TaskManagementTab = dynamic(() => import('@/components/admin/task-management-tab'), { ssr: false }); const LogManagementTab = dynamic(() => import('@/components/admin/log-management-tab'), { ssr: false }); const SettingsTab = dynamic(() => import('@/components/admin/settings-tab'), { ssr: false }); type ConsoleView = | 'dashboard' | 'api' | 'users' | 'pricing' | 'orders' | 'payment' | 'announcements' | 'data' | 'upgrade' | 'tasks' | 'logs' | 'settings'; type NavItem = { value: ConsoleView; label: string; icon: LucideIcon; hidden?: boolean; }; type NavGroup = { label: string; items: NavItem[]; }; type DashboardSummary = { generatedAt: string | null; platform: { totalVisits: number; databaseTime: string | null; }; users: { total: number; active: number; disabled: number; admins: number; members: number; created7d: number; }; works: { total: number; public: number; private: number; completed: number; failed: number; withResultUrl: number; created7d: number; resultUrlCoverage: number; byType: { text2img: number; img2img: number; text2video: number; img2video: number; }; }; tasks: { total: number; queued: number; running: number; succeeded: number; failed: number; latest: Array<{ id: string; type: string; status: string; error: string | null; createdAt: string | null; updatedAt: string | null; }>; }; orders: { total: number; pending: number; paid: number; cancelled: number; refunded: number; paidRevenue: number; paidRevenue7d: number; latest: Array<{ id: string; orderNo: string; productName: string; amount: number; status: string; createdAt: string | null; }>; }; providers: { total: number; active: number; inactive: number; image: number; video: number; text: number; incomplete: number; recommendationsTotal: number; recommendationsActive: number; userApiKeysTotal: number; userApiKeysActive: number; }; announcements: { total: number; active: number; scheduled: number; expired: number; }; system: { apiHealth: boolean; databaseHealth: boolean; storageHealth?: boolean; storageDirConfigured?: boolean; worksPersisted?: number; worksTotal?: number; logsTotal?: number; logsErrors?: number; logsCreated24h?: number; }; }; const EMPTY_DASHBOARD_SUMMARY: DashboardSummary = { generatedAt: null, platform: { totalVisits: 0, databaseTime: null }, users: { total: 0, active: 0, disabled: 0, admins: 0, members: 0, created7d: 0 }, works: { total: 0, public: 0, private: 0, completed: 0, failed: 0, withResultUrl: 0, created7d: 0, resultUrlCoverage: 1, byType: { text2img: 0, img2img: 0, text2video: 0, img2video: 0 }, }, tasks: { total: 0, queued: 0, running: 0, succeeded: 0, failed: 0, latest: [] }, orders: { total: 0, pending: 0, paid: 0, cancelled: 0, refunded: 0, paidRevenue: 0, paidRevenue7d: 0, latest: [] }, providers: { total: 0, active: 0, inactive: 0, image: 0, video: 0, text: 0, incomplete: 0, recommendationsTotal: 0, recommendationsActive: 0, userApiKeysTotal: 0, userApiKeysActive: 0, }, announcements: { total: 0, active: 0, scheduled: 0, expired: 0 }, system: { apiHealth: false, databaseHealth: false, storageHealth: false, storageDirConfigured: false, worksPersisted: 0, worksTotal: 0, logsTotal: 0, logsErrors: 0, logsCreated24h: 0, }, }; const VIEW_TITLES: Record = { dashboard: { title: '仪表盘', description: '查看运营、任务、支付、模型和系统健康概览' }, api: { title: 'API 管理', description: '配置供应商、模型推荐与系统 API' }, users: { title: '用户管理', description: '管理用户、额度、会员与账号状态' }, pricing: { title: '价格设置', description: '维护套餐价格与积分规则' }, orders: { title: '订单管理', description: '查看订单并处理支付状态' }, payment: { title: '支付配置', description: '配置可用支付方式' }, announcements: { title: '公告管理', description: '创建和维护站点弹窗公告' }, data: { title: '数据管理', description: '导出、导入与恢复业务数据' }, upgrade: { title: '系统升级', description: '上传升级包,执行热更新、冷更新与失败自动回滚' }, tasks: { title: '任务管理', description: '查看生成任务状态并清理任务' }, logs: { title: '系统日志', description: '查看平台运行、登录、安全和管理操作日志' }, settings: { title: '系统设置', description: '维护站点信息、邮箱与通知设置' }, }; function useAdminDashboard(accessToken: string | null) { const [summary, setSummary] = useState(EMPTY_DASHBOARD_SUMMARY); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const load = useCallback(async ({ silent = false, showSuccess = false }: { silent?: boolean; showSuccess?: boolean } = {}) => { if (!accessToken) { setLoading(false); return; } if (silent) { setRefreshing(true); } else { setLoading(true); } setError(null); try { const res = await fetch('/api/admin/dashboard', { headers: { Authorization: `Bearer ${accessToken}` }, cache: 'no-store', }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || '仪表盘数据加载失败'); setSummary({ ...EMPTY_DASHBOARD_SUMMARY, ...data }); if (showSuccess) toast.success('仪表盘已刷新'); } catch (err) { const message = err instanceof Error ? err.message : '仪表盘数据加载失败'; setError(message); if (!silent || showSuccess) toast.error(message); } finally { setLoading(false); setRefreshing(false); } }, [accessToken]); useEffect(() => { let cancelled = false; async function initialLoad() { if (!accessToken) { setLoading(false); return; } setLoading(true); setError(null); try { const res = await fetch('/api/admin/dashboard', { headers: { Authorization: `Bearer ${accessToken}` }, cache: 'no-store', }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || '仪表盘数据加载失败'); if (!cancelled) setSummary({ ...EMPTY_DASHBOARD_SUMMARY, ...data }); } catch (err) { const message = err instanceof Error ? err.message : '仪表盘数据加载失败'; if (!cancelled) { setError(message); toast.error(message); } } finally { if (!cancelled) setLoading(false); } } initialLoad(); const timer = window.setInterval(() => { if (!cancelled) load({ silent: true }); }, 30000); return () => { cancelled = true; window.clearInterval(timer); }; }, [accessToken, load]); return { summary, loading, refreshing, error, refresh: () => load({ silent: true, showSuccess: true }) }; } export default function ConsoleDashboardPage() { const { isLoggedIn, isAdmin, user, accessToken, logout } = useAuth(); const { config: siteConfig } = useSiteConfig(); const router = useRouter(); const [mounted, setMounted] = useState(false); const [activeView, setActiveView] = useState('dashboard'); const [mobileNavOpen, setMobileNavOpen] = useState(false); const membershipEnabled = siteConfig.membershipEnabled !== false; const navGroups = useMemo(() => { const groups: NavGroup[] = [ { label: '总览', items: [{ value: 'dashboard', label: '仪表盘', icon: LayoutDashboard }], }, { label: '运营', items: [ { value: 'users', label: '用户管理', icon: Users }, { value: 'pricing', label: '价格设置', icon: Coins, hidden: !membershipEnabled }, { value: 'orders', label: '订单管理', icon: Receipt, hidden: !membershipEnabled }, { value: 'payment', label: '支付配置', icon: CreditCard, hidden: !membershipEnabled }, ], }, { label: '创作', items: [ { value: 'api', label: 'API 管理', icon: Key }, { value: 'tasks', label: '任务管理', icon: ListChecks }, { value: 'announcements', label: '公告管理', icon: Bell }, ], }, { label: '系统', items: [ { value: 'data', label: '数据管理', icon: Database }, { value: 'upgrade', label: '系统升级', icon: Package }, { value: 'logs', label: '系统日志', icon: Logs }, { value: 'settings', label: '系统设置', icon: Settings }, ], }, ]; return groups .map(group => ({ ...group, items: group.items.filter(item => !item.hidden) })) .filter(group => group.items.length > 0); }, [membershipEnabled]); useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!membershipEnabled && ['pricing', 'orders', 'payment'].includes(activeView)) { setActiveView('dashboard'); } }, [membershipEnabled, activeView]); useEffect(() => { if (!mounted) return; if (!isLoggedIn || !isAdmin || !accessToken) { router.replace('/console'); } }, [mounted, isLoggedIn, isAdmin, accessToken, router]); if (!mounted) { return
; } if (!isLoggedIn || !isAdmin || !accessToken) { return
; } const title = VIEW_TITLES[activeView]; const handleLogout = () => { logout(); router.replace('/console'); }; const navigateToView = (view: ConsoleView) => { setActiveView(view); setMobileNavOpen(false); }; return (
{mobileNavOpen && (
} />
)}

{title.title}

{title.description}

管理员
); } function ConsoleSidebar({ activeView, navGroups, onNavigate, onBackHome, onLogout, userName, userEmail, closeButton, }: { activeView: ConsoleView; navGroups: NavGroup[]; onNavigate: (view: ConsoleView) => void; onBackHome: () => void; onLogout: () => void; userName: string; userEmail: string; closeButton?: ReactNode; }) { return ( <>
妙境 Console
{userEmail || userName}
{closeButton}
{userName}
后台管理权限
); } function ConsoleContent({ activeView, setActiveView, }: { activeView: ConsoleView; setActiveView: (view: ConsoleView) => void; }) { switch (activeView) { case 'dashboard': return ; case 'api': return ; case 'users': return ; case 'pricing': return ; case 'orders': return ; case 'payment': return ; case 'announcements': return ; case 'data': return ; case 'upgrade': return ; case 'tasks': return ; case 'logs': return ; case 'settings': return ; default: return ; } } function DashboardView({ setActiveView }: { setActiveView: (view: ConsoleView) => void }) { const { accessToken } = useAuth(); const { summary, loading, refreshing, error, refresh } = useAdminDashboard(accessToken); const persistedWorks = summary.system.worksPersisted ?? summary.works.withResultUrl; const totalWorksForCoverage = summary.system.worksTotal ?? summary.works.total; const resultUrlCoverage = totalWorksForCoverage > 0 ? persistedWorks / totalWorksForCoverage : 1; const riskItems = [ { label: '失败任务', value: summary.tasks.failed, detail: summary.tasks.failed > 0 ? '需要检查任务错误并清理' : '任务队列无失败积压', view: 'tasks' as ConsoleView, severity: summary.tasks.failed > 0 ? 'warning' : 'ok', }, { label: '待处理订单', value: summary.orders.pending, detail: summary.orders.pending > 0 ? '存在待处理支付订单' : '订单状态正常', view: 'orders' as ConsoleView, severity: summary.orders.pending > 0 ? 'warning' : 'ok', }, { label: '未完整供应商', value: summary.providers.incomplete, detail: summary.providers.incomplete > 0 ? '供应商缺少地址或默认模型' : '供应商配置完整', view: 'api' as ConsoleView, severity: summary.providers.incomplete > 0 ? 'warning' : 'ok', }, { label: '作品结果覆盖', value: `${Math.round(resultUrlCoverage * 100)}%`, detail: resultUrlCoverage < 1 ? '存在缺少结果链接的作品' : '持久化结果链接完整', view: 'data' as ConsoleView, severity: resultUrlCoverage < 1 ? 'warning' : 'ok', }, ]; const statCards = [ { label: '总访问量', value: summary.platform.totalVisits, sub: '站点累计访问', icon: Eye, tone: 'text-sky-500' }, { label: '注册用户', value: summary.users.total, sub: `7日新增 ${formatNumber(summary.users.created7d)}`, icon: Users, tone: 'text-emerald-500' }, { label: '公开作品', value: summary.works.public, sub: `总作品 ${formatNumber(summary.works.total)}`, icon: BarChart3, tone: 'text-amber-500' }, { label: '任务总数', value: summary.tasks.total, sub: `运行 ${formatNumber(summary.tasks.running)} / 排队 ${formatNumber(summary.tasks.queued)}`, icon: ListChecks, tone: 'text-violet-500' }, { label: '支付收入', value: formatCurrency(summary.orders.paidRevenue), sub: `7日 ${formatCurrency(summary.orders.paidRevenue7d)}`, icon: Receipt, tone: 'text-rose-500' }, { label: '启用模型源', value: `${formatNumber(summary.providers.active)}/${formatNumber(summary.providers.total)}`, sub: `推荐模型 ${formatNumber(summary.providers.recommendationsActive)}`, icon: PlugZap, tone: 'text-cyan-500' }, ]; const quickActions: Array<{ label: string; description: string; view: ConsoleView; icon: LucideIcon }> = [ { label: '配置模型 API', description: '维护供应商、推荐模型和默认能力', view: 'api', icon: Key }, { label: '查看任务队列', description: '排查失败、排队和运行中的生成任务', view: 'tasks', icon: ListChecks }, { label: '查看系统日志', description: '筛选登录、安全、生成和管理操作日志', view: 'logs', icon: Logs }, { label: '管理用户额度', description: '调整会员、积分和账号状态', view: 'users', icon: Users }, { label: '导出数据备份', description: '下载当前业务数据并确认恢复入口', view: 'data', icon: Database }, ]; return (

仪表盘

{summary.generatedAt ? `最后更新 ${formatDateTime(summary.generatedAt)}` : '正在读取系统概览'}

{error && (
{error}
)}
{statCards.map(item => { const Icon = item.icon; return (

{item.label}

{loading ? : item.value}

{item.sub}

); })}
待处理事项 按上线运营风险优先展示需要管理员处理的项目 {riskItems.map(item => ( ))} 系统健康 服务、数据库和持久化状态
任务队列 生成任务的实时状态分布
({ key: task.id, title: `${task.type || 'generation'} · ${statusText(task.status)}`, meta: task.error || formatDateTime(task.createdAt), tone: task.status === 'failed' ? 'warning' : undefined, }))} />
支付与订单 订单处理和营收概览
({ key: order.id, title: order.productName || order.orderNo || order.id.slice(0, 8), meta: `${formatCurrency(order.amount)} · ${statusText(order.status)}`, tone: order.status === 'pending' ? 'warning' : undefined, }))} />
模型与内容 创作能力、公告和作品结构
快捷操作 进入常用管理功能 {quickActions.map(action => { const Icon = action.icon; return ( ); })}
} title="AI 平台后台" description="仪表盘聚焦模型源、用量、任务状态和失败项,便于第一时间定位生成链路问题。" /> } title="交易后台" description="订单状态、待处理支付和营收数据前置,保持支付和会员运营可追踪。" /> } title="云控制台" description="数据库健康、持久化链接和备份入口放在首屏,支撑上线后的运维检查。" />
); } function SummaryBlock({ label, value }: { label: string; value: number }) { return (

{label}

{formatNumber(value)}

); } function SummaryLine({ label, value }: { label: string; value: number | string }) { return (
{label} {typeof value === 'number' ? formatNumber(value) : value}
); } function HealthLine({ label, ok }: { label: string; ok: boolean }) { return (
{label} {ok ? : } {ok ? '正常' : '异常'}
); } function RecentList({ items, emptyText, }: { items: Array<{ key: string; title: string; meta: string; tone?: 'warning' }>; emptyText: string; }) { if (items.length === 0) { return
{emptyText}
; } return (
{items.slice(0, 4).map(item => (
{item.title}
{item.meta}
))}
); } function InsightCard({ icon, title, description }: { icon: ReactNode; title: string; description: string }) { return (
{icon}
{title}

{description}

); } function formatNumber(value: number): string { return Number(value || 0).toLocaleString('zh-CN'); } function formatCurrency(value: number): string { return `¥${Number(value || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } function formatDateTime(value: string | null): string { if (!value) return '暂无时间'; const date = new Date(value); if (Number.isNaN(date.getTime())) return '暂无时间'; return date.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); } function statusText(status: string): string { const map: Record = { queued: '排队', running: '运行中', succeeded: '已完成', failed: '失败', pending: '待支付', paid: '已支付', cancelled: '已取消', refunded: '已退款', completed: '已完成', }; return map[status] || status || '未知'; }