"use client"; import { useEffect, useState, useCallback } from "react"; import { useRouter } from "next/navigation"; import { api } from "@/lib/api"; import { useAuthStore, useConfigStore } from "@/lib/store"; import { Plus, LayoutDashboard, ShoppingCart, Settings, BarChart3, Download } from "lucide-react"; import RichTextEditor from "@/components/RichTextEditor"; import * as XLSX from 'xlsx'; interface Course { id: number; title: string; description?: string; coverImage?: string; driveLink?: string; price: number; category?: string; viewCount?: number; likeCount?: number; starCount?: number; } interface Order { id: number; orderNo: string; amount: number; status: string; createdAt: string; course?: Course; } export default function AdminDashboard() { const router = useRouter(); const { user, token } = useAuthStore(); const { uiConfig: globalUiConfig, setUiConfig: setGlobalUiConfig } = useConfigStore(); const [activeTab, setActiveTab] = useState<'courses' | 'orders' | 'settings' | 'statistics'>('courses'); const [courses, setCourses] = useState([]); const [orders, setOrders] = useState([]); // Statistics State const [statistics, setStatistics] = useState({ vipAmount: 0, donationAmount: 0, purchaseAmount: 0 }); const [statisticsDetails, setStatisticsDetails] = useState([]); const [activeStatTab, setActiveStatTab] = useState<'all' | 'vip' | 'donation' | 'purchase'>('all'); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); // UI Config Form const [uiConfig, setUiConfig] = useState({ siteName: '', logo: '', footerText: '', heroBackground: '', navLinks: [] as { label: string; value: string }[], banners: [] as { imageUrl: string; linkUrl: string }[], memberFee: 99, }); // Course Form const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [coverImage, setCoverImage] = useState(''); const [driveLink, setDriveLink] = useState(''); const [price, setPrice] = useState(''); const [category, setCategory] = useState(''); const [viewCount, setViewCount] = useState(0); const [likeCount, setLikeCount] = useState(0); const [starCount, setStarCount] = useState(0); const [showAddForm, setShowAddForm] = useState(false); const [editingCourseId, setEditingCourseId] = useState(null); const [showSettings, setShowSettings] = useState(true); const fetchData = useCallback(async () => { try { if (activeTab === 'courses') { const res = await api.get('/api/courses'); // In real app, might want a specific admin endpoint to see inactive ones too if (res.success) setCourses(res.data); } else if (activeTab === 'orders') { const res = await api.get('/api/admin/orders'); if (res.success) setOrders(res.data); } else if (activeTab === 'settings') { const res = await api.get('/api/config/ui'); if (res.success) setUiConfig(res.data); } else if (activeTab === 'statistics') { let url = '/api/admin/statistics'; const params = new URLSearchParams(); if (startDate) params.append('startDate', startDate); if (endDate) params.append('endDate', endDate); if (params.toString()) url += `?${params.toString()}`; const res = await api.get(url); if (res.success) setStatistics(res.data); // Fetch details let detailsUrl = '/api/admin/statistics/details'; const detailsParams = new URLSearchParams(); detailsParams.append('type', activeStatTab); if (startDate) detailsParams.append('startDate', startDate); if (endDate) detailsParams.append('endDate', endDate); detailsUrl += `?${detailsParams.toString()}`; const detailsRes = await api.get(detailsUrl); if (detailsRes.success) setStatisticsDetails(detailsRes.data); } } catch (err) { console.error(err); } }, [activeTab, startDate, endDate, activeStatTab]); const handleSaveUiConfig = async (e: React.FormEvent) => { e.preventDefault(); try { const res = await api.put('/api/config/ui', uiConfig); if (res.success) { alert('界面设置已保存,立即生效。'); setUiConfig(res.data); setGlobalUiConfig(res.data); } } catch (err) { console.error(err); alert('保存设置失败'); } }; const handleExportExcel = () => { if (statisticsDetails.length === 0) { alert('没有数据可导出'); return; } const dataToExport = statisticsDetails.map(item => { const baseData: any = { '时间': new Date(item.createdAt).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }), '订单号': item.orderNo, '用户昵称': item.user?.nickname || item.user?.email || '未知用户', '用户邮箱': item.user?.email || '-', }; if (activeStatTab === 'purchase') { baseData['分类'] = item.course?.category || '未分类'; baseData['课程名称'] = item.course?.title || '-'; } baseData['金额(元)'] = Number(item.amount).toFixed(2); return baseData; }); const worksheet = XLSX.utils.json_to_sheet(dataToExport); const workbook = XLSX.utils.book_new(); // Auto-size columns const colWidths = Object.keys(dataToExport[0]).map(key => ({ wch: Math.max(key.length * 2, 15) })); worksheet['!cols'] = colWidths; const sheetNameMap: Record = { 'all': '全部明细', 'vip': '会员费明细', 'donation': '打赏明细', 'purchase': '购买明细' }; XLSX.utils.book_append_sheet(workbook, worksheet, sheetNameMap[activeStatTab]); const fileName = `${sheetNameMap[activeStatTab]}_${new Date().toISOString().split('T')[0]}.xlsx`; XLSX.writeFile(workbook, fileName); }; useEffect(() => { if (!token || user?.role !== 'admin') { router.push('/login'); return; } fetchData(); }, [token, user, fetchData, router]); const handleSubmitCourse = async (e: React.FormEvent) => { e.preventDefault(); try { const payload = { title, description, coverImage, driveLink, price: Number(price), category, viewCount: Number(viewCount), likeCount: Number(likeCount), starCount: Number(starCount) }; let res; if (editingCourseId) { res = await api.put(`/api/admin/courses/${editingCourseId}`, payload); } else { res = await api.post('/api/admin/courses', payload); } if (res.success) { alert(editingCourseId ? '课程更新成功' : '课程创建成功'); setShowAddForm(false); setEditingCourseId(null); fetchData(); // Reset form setTitle(''); setDescription(''); setCoverImage(''); setDriveLink(''); setPrice(''); setCategory(''); setViewCount(0); setLikeCount(0); setStarCount(0); } } catch (err) { console.error(err); alert(editingCourseId ? '更新课程失败' : '创建课程失败'); } }; const handleEdit = (course: Course) => { setEditingCourseId(course.id); setTitle(course.title); setDescription(course.description || ''); setCoverImage(course.coverImage || ''); setDriveLink(course.driveLink || ''); setPrice(course.price ? course.price.toString() : ''); setCategory(course.category || ''); setViewCount(course.viewCount || 0); setLikeCount(course.likeCount || 0); setStarCount(course.starCount || 0); setShowAddForm(true); }; const handleDelete = async (id: number) => { if (!window.confirm('确定要删除该课程吗?删除后无法恢复。')) { return; } try { const res = await api.delete(`/api/admin/courses/${id}`); if (res.success) { alert('课程删除成功'); fetchData(); } else { alert('删除失败'); } } catch (err) { console.error(err); alert('删除失败'); } }; return (
{/* Sidebar */}
A

管理后台

管理您的店铺

{/* Main Content */}
{activeTab === 'courses' && (

课程管理

{showAddForm && (
{/* Main Editor Area */}
{/* Top Bar */}
setTitle(e.target.value)} placeholder="输入文章/课程标题..." className="w-full text-3xl font-bold text-gray-900 border-none outline-none focus:ring-0 placeholder:text-gray-300" />
{/* Rich Text Editor */}
{/* Sidebar Settings */}
文章设置
{/* Cover Image */}
{coverImage ? (
Cover
) : (
上传封面 { const file = e.target.files?.[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); try { const res = await api.post('/api/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); if (res.success) setCoverImage(res.url); else alert('上传失败'); } catch (err) { console.error(err); alert('上传失败'); } }} />
)} setCoverImage(e.target.value)} placeholder="或输入图片URL" className="w-full px-3 py-2 text-sm rounded-lg border border-gray-200 bg-white" />
{/* Price */}
setPrice(e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-200 bg-white" placeholder="0.00" />
{/* View Count */}
setViewCount(e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-200 bg-white" placeholder="0" />
{/* Like Count */}
setLikeCount(e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-200 bg-white" placeholder="0" />
{/* Star Count */}
setStarCount(e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-200 bg-white" placeholder="0" />
{/* Category */}
{/* Drive Link */}
)}
{courses.map(course => ( ))} {courses.length === 0 && ( )}
ID 课程标题 价格 浏览人数 分类 操作
#{course.id} {course.title} ¥{course.price} {course.viewCount || 0} {course.category || 'N/A'}
未找到课程
)} {activeTab === 'orders' && (

订单管理

{orders.map(order => ( ))} {orders.length === 0 && ( )}
订单号 课程 金额 状态 日期
{order.orderNo} {order.course?.title} ¥{order.amount} {order.status.toUpperCase()} {new Date(order.createdAt).toLocaleDateString()}
未找到订单
)} {activeTab === 'settings' && (

界面设置

基础信息

setUiConfig({ ...uiConfig, siteName: e.target.value })} className="w-full px-4 py-2 rounded-lg border border-gray-200" required />
setUiConfig({ ...uiConfig, logo: e.target.value })} className="w-full px-4 py-2 rounded-lg border border-gray-200" placeholder="输入Logo图片URL,若不填则显示站点名称" />
{uiConfig.heroBackground && (
Hero Background
)}
setUiConfig({ ...uiConfig, heroBackground: e.target.value })} className="flex-1 px-4 py-2 rounded-lg border border-gray-200" placeholder="输入图片URL,或使用右侧按钮上传" />
setUiConfig({ ...uiConfig, footerText: e.target.value })} className="w-full px-4 py-2 rounded-lg border border-gray-200" required />
setUiConfig({ ...uiConfig, memberFee: Number(e.target.value) })} className="w-full px-4 py-2 rounded-lg border border-gray-200" min="0" step="0.01" required />

导航栏与分类管理

{(uiConfig.navLinks || []).map((link, idx) => (
{ const newLinks = [...uiConfig.navLinks]; newLinks[idx].label = e.target.value; setUiConfig({ ...uiConfig, navLinks: newLinks }); }} className="flex-1 px-4 py-2 rounded-lg border border-gray-200" placeholder="显示名称 (如: AI新资讯)" required /> { const newLinks = [...uiConfig.navLinks]; newLinks[idx].value = e.target.value; setUiConfig({ ...uiConfig, navLinks: newLinks }); }} className="flex-1 px-4 py-2 rounded-lg border border-gray-200" placeholder="路由值 (如: ai-news)" required />
))}

广告图管理

{(uiConfig.banners || []).map((banner, idx) => (
{banner.imageUrl && ( banner preview )} { const newBanners = [...(uiConfig.banners || [])]; newBanners[idx].imageUrl = e.target.value; setUiConfig({ ...uiConfig, banners: newBanners }); }} className="flex-1 px-3 py-2 rounded-md border border-gray-200 text-sm" placeholder="图片 URL (或右侧上传)" required />
{ const newBanners = [...(uiConfig.banners || [])]; newBanners[idx].linkUrl = e.target.value; setUiConfig({ ...uiConfig, banners: newBanners }); }} className="w-full px-3 py-2 rounded-md border border-gray-200 text-sm" placeholder="跳转链接 URL" />
))} {(!uiConfig.banners || uiConfig.banners.length === 0) && (
暂无广告图,点击右上角添加
)}
)} {activeTab === 'statistics' && (

数据统计

setStartDate(e.target.value)} className="px-4 py-2 rounded-lg border border-gray-200" />
setEndDate(e.target.value)} className="px-4 py-2 rounded-lg border border-gray-200" />
setActiveStatTab('all')} >
总计收入
¥{(statistics.vipAmount + statistics.donationAmount + statistics.purchaseAmount).toFixed(2)}
setActiveStatTab('vip')} >
会员费统计
¥{statistics.vipAmount.toFixed(2)}
setActiveStatTab('donation')} >
打赏统计
¥{statistics.donationAmount.toFixed(2)}
setActiveStatTab('purchase')} >
购买统计
¥{statistics.purchaseAmount.toFixed(2)}

{activeStatTab === 'all' && '全部明细'} {activeStatTab === 'vip' && '会员费明细'} {activeStatTab === 'donation' && '打赏明细'} {activeStatTab === 'purchase' && '购买明细'}

{activeStatTab === 'purchase' && ( <> )} {statisticsDetails.map((item, idx) => ( {activeStatTab === 'purchase' && ( <> )} ))} {statisticsDetails.length === 0 && ( )}
时间 订单号 用户分类 课程名称金额
{new Date(item.createdAt).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })} {item.orderNo}
{item.user?.nickname?.charAt(0).toUpperCase() || 'U'}
{item.user?.nickname || item.user?.email || '未知用户'}
{item.course?.category || '未分类'} {item.course?.title || '-'} ¥{Number(item.amount).toFixed(2)}
📊

该时间段内暂无数据

)}
); }