Update soul-content project
This commit is contained in:
BIN
app/admin/.DS_Store
vendored
Normal file
BIN
app/admin/.DS_Store
vendored
Normal file
Binary file not shown.
82
app/admin/content/page.tsx
Normal file
82
app/admin/content/page.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
|
||||
export default function ContentPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight">内容管理</h2>
|
||||
<Button>发布新内容</Button>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="chapters" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="chapters">章节管理</TabsTrigger>
|
||||
<TabsTrigger value="articles">文章管理</TabsTrigger>
|
||||
<TabsTrigger value="hooks">钩子配置</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="chapters" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>章节列表</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border p-4 text-center text-muted-foreground">
|
||||
暂无章节数据,请连接数据库同步
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="articles" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>文章列表</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border p-4 text-center text-muted-foreground">
|
||||
暂无文章数据
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="hooks" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>引流钩子配置</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label htmlFor="hook-chapter">触发章节</Label>
|
||||
<Select defaultValue="3">
|
||||
<SelectTrigger id="hook-chapter">
|
||||
<SelectValue placeholder="选择章节" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">第一章</SelectItem>
|
||||
<SelectItem value="2">第二章</SelectItem>
|
||||
<SelectItem value="3">第三章 (默认)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid w-full gap-1.5">
|
||||
<Label htmlFor="message">引流文案</Label>
|
||||
<Textarea placeholder="输入引导用户加群的文案..." id="message" defaultValue="阅读更多精彩内容,请加入Soul创业实验派对群..." />
|
||||
</div>
|
||||
<Button>保存配置</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
77
app/admin/layout.tsx
Normal file
77
app/admin/layout.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { LayoutDashboard, FileText, Users, CreditCard, QrCode, Settings, LogOut, ChevronLeft } from "lucide-react"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
|
||||
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const { user, isLoggedIn } = useStore()
|
||||
|
||||
// Simple admin check (in real app, use robust auth)
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn) {
|
||||
// router.push("/my") // Commented out for easier development access
|
||||
}
|
||||
}, [isLoggedIn, router])
|
||||
|
||||
const menuItems = [
|
||||
{ icon: LayoutDashboard, label: "数据概览", href: "/admin" },
|
||||
{ icon: FileText, label: "内容管理", href: "/admin/content" },
|
||||
{ icon: Users, label: "用户管理", href: "/admin/users" },
|
||||
{ icon: CreditCard, label: "支付配置", href: "/admin/payment" },
|
||||
{ icon: QrCode, label: "二维码", href: "/admin/qrcodes" },
|
||||
{ icon: Settings, label: "系统设置", href: "/admin/settings" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-[#0f172a]">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-[#1e293b] text-white flex flex-col border-r border-gray-800">
|
||||
<div className="p-6 border-b border-gray-800">
|
||||
<h1 className="text-xl font-bold text-[#38bdac]">管理后台</h1>
|
||||
<p className="text-xs text-gray-400 mt-1">Soul创业实验场</p>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 p-4 space-y-2">
|
||||
{menuItems.map((item) => {
|
||||
const isActive = pathname === item.href
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${
|
||||
isActive
|
||||
? "bg-[#38bdac]/10 text-[#38bdac] border border-[#38bdac]/20"
|
||||
: "text-gray-400 hover:bg-gray-800 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<item.icon className="w-5 h-5" />
|
||||
<span className="text-sm font-medium">{item.label}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="p-4 border-t border-gray-800">
|
||||
<Link
|
||||
href="/my"
|
||||
className="flex items-center gap-3 px-4 py-3 text-gray-400 hover:text-white rounded-lg hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<LogOut className="w-5 h-5" />
|
||||
<span className="text-sm font-medium">返回前台</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 overflow-auto bg-[#0f172a]">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
3
app/admin/loading.tsx
Normal file
3
app/admin/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
82
app/admin/login/page.tsx
Normal file
82
app/admin/login/page.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Lock, User } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { useStore } from "@/lib/store"
|
||||
|
||||
export default function AdminLoginPage() {
|
||||
const router = useRouter()
|
||||
const { adminLogin } = useStore()
|
||||
const [username, setUsername] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const handleLogin = () => {
|
||||
setError("")
|
||||
const success = adminLogin(username, password)
|
||||
if (success) {
|
||||
router.push("/admin")
|
||||
} else {
|
||||
setError("用户名或密码错误")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0a1628] text-white flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold text-white mb-2">管理后台</h1>
|
||||
<p className="text-gray-500">一场SOUL的创业实验场</p>
|
||||
</div>
|
||||
|
||||
{/* Login form */}
|
||||
<div className="bg-[#0f2137] rounded-2xl p-8 border border-gray-700/50">
|
||||
<h2 className="text-xl font-semibold text-white mb-6 text-center">管理员登录</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">用户名</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="请输入用户名"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">密码</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="请输入密码"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
onKeyDown={(e) => e.key === "Enter" && handleLogin()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-400 text-sm">{error}</p>}
|
||||
|
||||
<Button onClick={handleLogin} className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-5">
|
||||
登录
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-500 text-xs text-center mt-6">默认账号: admin / key123456</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
96
app/admin/page.tsx
Normal file
96
app/admin/page.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { Users, BookOpen, ShoppingBag, TrendingUp } from "lucide-react"
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const { getAllUsers, getAllPurchases } = useStore()
|
||||
const users = getAllUsers()
|
||||
const purchases = getAllPurchases()
|
||||
|
||||
const totalRevenue = purchases.reduce((sum, p) => sum + p.amount, 0)
|
||||
const totalUsers = users.length
|
||||
const totalPurchases = purchases.length
|
||||
|
||||
const stats = [
|
||||
{ title: "总用户数", value: totalUsers, icon: Users, color: "text-blue-500" },
|
||||
{ title: "总收入", value: `¥${totalRevenue.toFixed(2)}`, icon: TrendingUp, color: "text-green-500" },
|
||||
{ title: "订单数", value: totalPurchases, icon: ShoppingBag, color: "text-purple-500" },
|
||||
{ title: "转化率", value: `${totalUsers > 0 ? ((totalPurchases / totalUsers) * 100).toFixed(1) : 0}%`, icon: BookOpen, color: "text-orange-500" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-7xl mx-auto text-white">
|
||||
<h1 className="text-2xl font-bold mb-8">数据概览</h1>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{stats.map((stat, index) => (
|
||||
<Card key={index} className="bg-[#1e293b] border-gray-700">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium text-gray-400">{stat.title}</CardTitle>
|
||||
<stat.icon className={`w-4 h-4 ${stat.color}`} />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-white">{stat.value}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<Card className="bg-[#1e293b] border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">最近订单</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{purchases.slice(-5).reverse().map((p) => (
|
||||
<div key={p.id} className="flex items-center justify-between p-4 bg-[#0f172a] rounded-lg">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">{p.sectionTitle || "整本购买"}</p>
|
||||
<p className="text-xs text-gray-400">{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-500">{p.paymentMethod}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{purchases.length === 0 && (
|
||||
<p className="text-gray-500 text-center py-4">暂无订单数据</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-[#1e293b] border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">新注册用户</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{users.slice(-5).reverse().map((u) => (
|
||||
<div key={u.id} className="flex items-center justify-between p-4 bg-[#0f172a] rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center text-xs">
|
||||
{u.nickname.charAt(0)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">{u.nickname}</p>
|
||||
<p className="text-xs text-gray-400">{u.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">{new Date(u.createdAt).toLocaleDateString()}</p>
|
||||
</div>
|
||||
))}
|
||||
{users.length === 0 && (
|
||||
<p className="text-gray-500 text-center py-4">暂无用户数据</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
248
app/admin/payment/page.tsx
Normal file
248
app/admin/payment/page.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Save, RefreshCw } from "lucide-react"
|
||||
|
||||
export default function PaymentConfigPage() {
|
||||
const { settings, updateSettings, fetchSettings } = useStore()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [localSettings, setLocalSettings] = useState(settings.paymentMethods)
|
||||
|
||||
// Sync with store on mount
|
||||
useEffect(() => {
|
||||
setLocalSettings(settings.paymentMethods)
|
||||
}, [settings.paymentMethods])
|
||||
|
||||
const handleSave = async () => {
|
||||
setLoading(true)
|
||||
// Update store (and local storage)
|
||||
updateSettings({ paymentMethods: localSettings })
|
||||
|
||||
// Simulate API call delay
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
setLoading(false)
|
||||
alert("配置已保存!")
|
||||
}
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setLoading(true)
|
||||
await fetchSettings()
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const updateWechat = (field: string, value: any) => {
|
||||
setLocalSettings(prev => ({
|
||||
...prev,
|
||||
wechat: { ...prev.wechat, [field]: value }
|
||||
}))
|
||||
}
|
||||
|
||||
const updateAlipay = (field: string, value: any) => {
|
||||
setLocalSettings(prev => ({
|
||||
...prev,
|
||||
alipay: { ...prev.alipay, [field]: value }
|
||||
}))
|
||||
}
|
||||
|
||||
const updateUsdt = (field: string, value: any) => {
|
||||
setLocalSettings(prev => ({
|
||||
...prev,
|
||||
usdt: { ...prev.usdt, [field]: value }
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-5xl mx-auto text-white">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-2">支付配置</h1>
|
||||
<p className="text-gray-400">配置微信、支付宝及USDT支付参数</p>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Button variant="outline" onClick={handleRefresh} className="border-gray-600 text-gray-300 hover:text-white bg-transparent">
|
||||
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||
同步配置
|
||||
</Button>
|
||||
<Button onClick={handleSave} className="bg-[#38bdac] hover:bg-[#2da396] text-white">
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
保存配置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8">
|
||||
{/* Wechat Pay */}
|
||||
<Card className="bg-[#1e293b] border-gray-700">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<div className="space-y-1">
|
||||
<CardTitle className="text-[#07C160] flex items-center gap-2">
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.269-.03-.406-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z"/></svg>
|
||||
微信支付
|
||||
</CardTitle>
|
||||
<CardDescription className="text-gray-400">配置微信商户号及API密钥</CardDescription>
|
||||
</div>
|
||||
<Switch
|
||||
checked={localSettings.wechat.enabled}
|
||||
onCheckedChange={(c) => updateWechat('enabled', c)}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">网站应用 AppID</Label>
|
||||
<Input
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="wx..."
|
||||
value={localSettings.wechat.websiteAppId || ''}
|
||||
onChange={(e) => updateWechat('websiteAppId', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">网站应用 AppSecret</Label>
|
||||
<Input
|
||||
type="password"
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="......"
|
||||
value={localSettings.wechat.websiteAppSecret || ''}
|
||||
onChange={(e) => updateWechat('websiteAppSecret', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">商户号 (MchId)</Label>
|
||||
<Input
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="160..."
|
||||
value={localSettings.wechat.merchantId || ''}
|
||||
onChange={(e) => updateWechat('merchantId', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">API v3 密钥</Label>
|
||||
<Input
|
||||
type="password"
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="......"
|
||||
value={localSettings.wechat.apiKey || ''}
|
||||
onChange={(e) => updateWechat('apiKey', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">个人收款码 URL (兜底方案)</Label>
|
||||
<Input
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="/images/wechat-pay.png"
|
||||
value={localSettings.wechat.qrCode || ''}
|
||||
onChange={(e) => updateWechat('qrCode', e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-gray-500">当未配置企业支付参数时,将显示此二维码进行个人收款</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Alipay */}
|
||||
<Card className="bg-[#1e293b] border-gray-700">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<div className="space-y-1">
|
||||
<CardTitle className="text-[#1677FF] flex items-center gap-2">
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M8.77 20.62l9.92-4.33c-.12-.33-.24-.66-.38-.99-.14-.33-.3-.66-.47-.99H8.08c-2.2 0-3.99-1.79-3.99-3.99V8.08c0-2.2 1.79-3.99 3.99-3.99h7.84c2.2 0 3.99 1.79 3.99 3.99v2.24h-8.66c-.55 0-1 .45-1 1s.45 1 1 1h10.66c-.18 1.73-.71 3.36-1.53 4.83l-2.76 1.2c-.74-1.69-1.74-3.24-2.93-4.6-.52-.59-1.11-1.13-1.76-1.59H4.09v4.24c0 2.2 1.79 3.99 3.99 3.99h.69v.23z"/></svg>
|
||||
支付宝
|
||||
</CardTitle>
|
||||
<CardDescription className="text-gray-400">配置支付宝PID及密钥</CardDescription>
|
||||
</div>
|
||||
<Switch
|
||||
checked={localSettings.alipay.enabled}
|
||||
onCheckedChange={(c) => updateAlipay('enabled', c)}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">合作者身份 (PID)</Label>
|
||||
<Input
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="2088..."
|
||||
value={localSettings.alipay.partnerId || ''}
|
||||
onChange={(e) => updateAlipay('partnerId', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">安全校验码 (Key)</Label>
|
||||
<Input
|
||||
type="password"
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="......"
|
||||
value={localSettings.alipay.securityKey || ''}
|
||||
onChange={(e) => updateAlipay('securityKey', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">个人收款码 URL (兜底方案)</Label>
|
||||
<Input
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
placeholder="/images/alipay.png"
|
||||
value={localSettings.alipay.qrCode || ''}
|
||||
onChange={(e) => updateAlipay('qrCode', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* USDT */}
|
||||
<Card className="bg-[#1e293b] border-gray-700">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<div className="space-y-1">
|
||||
<CardTitle className="text-[#26A17B] flex items-center gap-2">
|
||||
<span className="font-bold">₮</span> USDT支付
|
||||
</CardTitle>
|
||||
<CardDescription className="text-gray-400">配置区块链钱包地址</CardDescription>
|
||||
</div>
|
||||
<Switch
|
||||
checked={localSettings.usdt.enabled}
|
||||
onCheckedChange={(c) => updateUsdt('enabled', c)}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">网络协议</Label>
|
||||
<Input
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
value={localSettings.usdt.network || 'TRC20'}
|
||||
onChange={(e) => updateUsdt('network', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">汇率 (USD -> CNY)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
className="bg-[#0f172a] border-gray-600 text-white"
|
||||
value={localSettings.usdt.exchangeRate || 7.2}
|
||||
onChange={(e) => updateUsdt('exchangeRate', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-gray-300">钱包地址</Label>
|
||||
<Input
|
||||
className="bg-[#0f172a] border-gray-600 text-white font-mono"
|
||||
placeholder="T..."
|
||||
value={localSettings.usdt.address || ''}
|
||||
onChange={(e) => updateUsdt('address', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
58
app/admin/qrcodes/page.tsx
Normal file
58
app/admin/qrcodes/page.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
|
||||
export default function QRCodesPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">二维码管理</h2>
|
||||
<p className="text-muted-foreground">管理用于引流和支付的二维码图片。</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>私域引流码</CardTitle>
|
||||
<CardDescription>用于文章底部、个人中心等位置的加群/加好友二维码。</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex flex-col items-center justify-center border-2 border-dashed rounded-lg p-6 h-48 bg-slate-50">
|
||||
<span className="text-muted-foreground">预览区域</span>
|
||||
</div>
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label htmlFor="traffic-qr">上传新图片</Label>
|
||||
<Input id="traffic-qr" type="file" />
|
||||
</div>
|
||||
<Button className="w-full">更新引流码</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>群活码</CardTitle>
|
||||
<CardDescription>用于派对活动引流的动态群活码。</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex flex-col items-center justify-center border-2 border-dashed rounded-lg p-6 h-48 bg-slate-50">
|
||||
<span className="text-muted-foreground">预览区域</span>
|
||||
</div>
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label htmlFor="group-qr">上传新图片</Label>
|
||||
<Input id="group-qr" type="file" />
|
||||
</div>
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label htmlFor="group-url">活码链接 (可选)</Label>
|
||||
<Input id="group-url" placeholder="https://..." defaultValue="https://soul.cn/party" />
|
||||
</div>
|
||||
<Button className="w-full">更新群活码</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
66
app/admin/settings/page.tsx
Normal file
66
app/admin/settings/page.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
|
||||
export default function SettingsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">系统设置</h2>
|
||||
<p className="text-muted-foreground">配置全站基础参数与开关。</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>基础信息</CardTitle>
|
||||
<CardDescription>网站显示的基本信息配置。</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="site-name">网站名称</Label>
|
||||
<Input id="site-name" defaultValue="一场Soul的创业实验" />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="author">主理人</Label>
|
||||
<Input id="author" defaultValue="卡若" />
|
||||
</div>
|
||||
<Button>保存基础信息</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>功能开关</CardTitle>
|
||||
<CardDescription>控制系统核心模块的启用状态。</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<Label htmlFor="maintenance-mode" className="flex flex-col space-y-1">
|
||||
<span>维护模式</span>
|
||||
<span className="font-normal text-xs text-muted-foreground">启用后前台将显示维护中页面</span>
|
||||
</Label>
|
||||
<Switch id="maintenance-mode" />
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<Label htmlFor="payment-enabled" className="flex flex-col space-y-1">
|
||||
<span>全站支付</span>
|
||||
<span className="font-normal text-xs text-muted-foreground">关闭后所有支付功能将暂停</span>
|
||||
</Label>
|
||||
<Switch id="payment-enabled" defaultChecked />
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<Label htmlFor="referral-enabled" className="flex flex-col space-y-1">
|
||||
<span>分销系统</span>
|
||||
<span className="font-normal text-xs text-muted-foreground">是否允许用户生成邀请链接</span>
|
||||
</Label>
|
||||
<Switch id="referral-enabled" defaultChecked />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
59
app/admin/users/page.tsx
Normal file
59
app/admin/users/page.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
|
||||
export default function UsersPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight">用户管理</h2>
|
||||
<div className="flex w-full max-w-sm items-center space-x-2">
|
||||
<Input type="text" placeholder="搜索用户..." />
|
||||
<Button type="submit">搜索</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>用户列表</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">ID</TableHead>
|
||||
<TableHead>用户名</TableHead>
|
||||
<TableHead>绑定状态</TableHead>
|
||||
<TableHead>注册时间</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">1001</TableCell>
|
||||
<TableCell>测试用户A</TableCell>
|
||||
<TableCell className="text-green-600">已绑定</TableCell>
|
||||
<TableCell>2025-12-28</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button variant="ghost" size="sm">查看</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">1002</TableCell>
|
||||
<TableCell>游客_8392</TableCell>
|
||||
<TableCell className="text-yellow-600">未绑定</TableCell>
|
||||
<TableCell>2025-12-29</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button variant="ghost" size="sm">查看</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
135
app/admin/withdrawals/page.tsx
Normal file
135
app/admin/withdrawals/page.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useStore, type Withdrawal } from "@/lib/store"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Check, X, Clock } from "lucide-react"
|
||||
|
||||
export default function WithdrawalsPage() {
|
||||
const { withdrawals, completeWithdrawal } = useStore()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
const pendingWithdrawals = withdrawals?.filter(w => w.status === "pending") || []
|
||||
const historyWithdrawals = withdrawals?.filter(w => w.status !== "pending").sort((a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
) || []
|
||||
|
||||
const handleApprove = (id: string) => {
|
||||
if (confirm("确认打款并完成此提现申请吗?")) {
|
||||
completeWithdrawal(id)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-6xl mx-auto">
|
||||
<h1 className="text-2xl font-bold mb-6">提现管理</h1>
|
||||
|
||||
<div className="grid gap-6">
|
||||
{/* Pending Requests */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Clock className="w-5 h-5 text-orange-500" />
|
||||
待处理申请 ({pendingWithdrawals.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{pendingWithdrawals.length === 0 ? (
|
||||
<p className="text-gray-500 text-center py-8">暂无待处理申请</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm text-left">
|
||||
<thead className="bg-gray-50 text-gray-700">
|
||||
<tr>
|
||||
<th className="p-3">申请时间</th>
|
||||
<th className="p-3">用户ID</th>
|
||||
<th className="p-3">姓名</th>
|
||||
<th className="p-3">渠道</th>
|
||||
<th className="p-3">账号</th>
|
||||
<th className="p-3">金额</th>
|
||||
<th className="p-3 text-right">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{pendingWithdrawals.map((w) => (
|
||||
<tr key={w.id} className="hover:bg-gray-50">
|
||||
<td className="p-3">{new Date(w.createdAt).toLocaleString()}</td>
|
||||
<td className="p-3 font-mono text-xs">{w.userId.slice(0, 8)}...</td>
|
||||
<td className="p-3 font-medium">{w.name}</td>
|
||||
<td className="p-3">
|
||||
<span className={`px-2 py-1 rounded-full text-xs text-white ${w.method === "wechat" ? "bg-green-600" : "bg-blue-600"}`}>
|
||||
{w.method === "wechat" ? "微信" : "支付宝"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-3 font-mono">{w.account}</td>
|
||||
<td className="p-3 font-bold text-orange-600">¥{w.amount.toFixed(2)}</td>
|
||||
<td className="p-3 text-right">
|
||||
<Button size="sm" onClick={() => handleApprove(w.id)} className="bg-green-600 hover:bg-green-700 text-white">
|
||||
<Check className="w-4 h-4 mr-1" />
|
||||
确认打款
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* History */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>处理历史</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{historyWithdrawals.length === 0 ? (
|
||||
<p className="text-gray-500 text-center py-8">暂无历史记录</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm text-left">
|
||||
<thead className="bg-gray-50 text-gray-700">
|
||||
<tr>
|
||||
<th className="p-3">申请时间</th>
|
||||
<th className="p-3">处理时间</th>
|
||||
<th className="p-3">姓名</th>
|
||||
<th className="p-3">渠道</th>
|
||||
<th className="p-3">金额</th>
|
||||
<th className="p-3">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{historyWithdrawals.map((w) => (
|
||||
<tr key={w.id} className="hover:bg-gray-50">
|
||||
<td className="p-3 text-gray-500">{new Date(w.createdAt).toLocaleString()}</td>
|
||||
<td className="p-3 text-gray-500">{w.completedAt ? new Date(w.completedAt).toLocaleString() : "-"}</td>
|
||||
<td className="p-3">{w.name}</td>
|
||||
<td className="p-3">
|
||||
{w.method === "wechat" ? "微信" : "支付宝"}
|
||||
</td>
|
||||
<td className="p-3 font-medium">¥{w.amount.toFixed(2)}</td>
|
||||
<td className="p-3">
|
||||
<span className="px-2 py-1 rounded-full text-xs text-green-600 border border-green-200 bg-green-50">
|
||||
已完成
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user