496 lines
20 KiB
TypeScript
496 lines
20 KiB
TypeScript
"use client"
|
||
|
||
import { useState, useEffect } from "react"
|
||
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"
|
||
import { Slider } from "@/components/ui/slider"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import { useStore } from "@/lib/store"
|
||
import { Save, Settings, Users, DollarSign, UserCircle, Calendar, MapPin, BookOpen, Gift, X, Plus, Smartphone } from "lucide-react"
|
||
|
||
export default function SettingsPage() {
|
||
const { settings, updateSettings } = useStore()
|
||
const [localSettings, setLocalSettings] = useState({
|
||
sectionPrice: settings.sectionPrice,
|
||
baseBookPrice: settings.baseBookPrice,
|
||
distributorShare: settings.distributorShare,
|
||
authorInfo: {
|
||
...settings.authorInfo,
|
||
startDate: settings.authorInfo?.startDate || "2025年10月15日",
|
||
bio: settings.authorInfo?.bio || "连续创业者,私域运营专家,每天早上6-9点在Soul派对房分享真实商业故事",
|
||
},
|
||
})
|
||
const [isSaving, setIsSaving] = useState(false)
|
||
|
||
// 免费章节配置
|
||
const [freeChapters, setFreeChapters] = useState<string[]>(['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'])
|
||
const [newFreeChapter, setNewFreeChapter] = useState('')
|
||
|
||
// 小程序配置
|
||
const [mpConfig, setMpConfig] = useState({
|
||
appId: 'wxb8bbb2b10dec74aa',
|
||
apiDomain: 'https://soul.quwanzhi.com',
|
||
buyerDiscount: 5, // 购买者优惠比例
|
||
referralBindDays: 30, // 推荐绑定天数
|
||
minWithdraw: 10, // 最低提现金额
|
||
})
|
||
|
||
// 加载配置
|
||
useEffect(() => {
|
||
const loadConfig = async () => {
|
||
try {
|
||
const res = await fetch('/api/db/config')
|
||
if (res.ok) {
|
||
const data = await res.json()
|
||
if (data.freeChapters) setFreeChapters(data.freeChapters)
|
||
if (data.mpConfig) setMpConfig(prev => ({ ...prev, ...data.mpConfig }))
|
||
}
|
||
} catch (e) {
|
||
console.log('Load config error:', e)
|
||
}
|
||
}
|
||
loadConfig()
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
setLocalSettings({
|
||
sectionPrice: settings.sectionPrice,
|
||
baseBookPrice: settings.baseBookPrice,
|
||
distributorShare: settings.distributorShare,
|
||
authorInfo: {
|
||
...settings.authorInfo,
|
||
startDate: settings.authorInfo?.startDate || "2025年10月15日",
|
||
bio: settings.authorInfo?.bio || "连续创业者,私域运营专家,每天早上6-9点在Soul派对房分享真实商业故事",
|
||
},
|
||
})
|
||
}, [settings])
|
||
|
||
const handleSave = async () => {
|
||
setIsSaving(true)
|
||
try {
|
||
updateSettings(localSettings)
|
||
|
||
// 同时保存到数据库
|
||
await fetch('/api/db/settings', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(localSettings)
|
||
})
|
||
|
||
// 保存免费章节和小程序配置
|
||
await fetch('/api/db/config', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ freeChapters, mpConfig })
|
||
})
|
||
|
||
alert("设置已保存!")
|
||
} catch (error) {
|
||
console.error('Save settings error:', error)
|
||
alert("保存失败")
|
||
} finally {
|
||
setIsSaving(false)
|
||
}
|
||
}
|
||
|
||
// 添加免费章节
|
||
const addFreeChapter = () => {
|
||
if (newFreeChapter && !freeChapters.includes(newFreeChapter)) {
|
||
setFreeChapters([...freeChapters, newFreeChapter])
|
||
setNewFreeChapter('')
|
||
}
|
||
}
|
||
|
||
// 移除免费章节
|
||
const removeFreeChapter = (chapter: string) => {
|
||
setFreeChapters(freeChapters.filter(c => c !== chapter))
|
||
}
|
||
|
||
return (
|
||
<div className="p-8 max-w-4xl mx-auto">
|
||
<div className="flex justify-between items-center mb-8">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-white">系统设置</h2>
|
||
<p className="text-gray-400 mt-1">配置全站基础参数与开关</p>
|
||
</div>
|
||
<Button
|
||
onClick={handleSave}
|
||
disabled={isSaving}
|
||
className="bg-[#38bdac] hover:bg-[#2da396] text-white"
|
||
>
|
||
<Save className="w-4 h-4 mr-2" />
|
||
{isSaving ? "保存中..." : "保存设置"}
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
{/* 作者信息 - 重点增强 */}
|
||
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
|
||
<CardHeader>
|
||
<CardTitle className="text-white flex items-center gap-2">
|
||
<UserCircle className="w-5 h-5 text-[#38bdac]" />
|
||
关于作者
|
||
</CardTitle>
|
||
<CardDescription className="text-gray-400">配置作者信息,将在"关于作者"页面显示</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="author-name" className="text-gray-300 flex items-center gap-1">
|
||
<UserCircle className="w-3 h-3" />
|
||
主理人名称
|
||
</Label>
|
||
<Input
|
||
id="author-name"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={localSettings.authorInfo.name}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
authorInfo: { ...prev.authorInfo, name: e.target.value },
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="start-date" className="text-gray-300 flex items-center gap-1">
|
||
<Calendar className="w-3 h-3" />
|
||
开播日期
|
||
</Label>
|
||
<Input
|
||
id="start-date"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
placeholder="例如: 2025年10月15日"
|
||
value={localSettings.authorInfo.startDate || ""}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
authorInfo: { ...prev.authorInfo, startDate: e.target.value },
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="live-time" className="text-gray-300 flex items-center gap-1">
|
||
<Calendar className="w-3 h-3" />
|
||
直播时间
|
||
</Label>
|
||
<Input
|
||
id="live-time"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
placeholder="例如: 06:00-09:00"
|
||
value={localSettings.authorInfo.liveTime}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
authorInfo: { ...prev.authorInfo, liveTime: e.target.value },
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="platform" className="text-gray-300 flex items-center gap-1">
|
||
<MapPin className="w-3 h-3" />
|
||
直播平台
|
||
</Label>
|
||
<Input
|
||
id="platform"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
placeholder="例如: Soul派对房"
|
||
value={localSettings.authorInfo.platform}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
authorInfo: { ...prev.authorInfo, platform: e.target.value },
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="description" className="text-gray-300 flex items-center gap-1">
|
||
<BookOpen className="w-3 h-3" />
|
||
简介描述
|
||
</Label>
|
||
<Input
|
||
id="description"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={localSettings.authorInfo.description}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
authorInfo: { ...prev.authorInfo, description: e.target.value },
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="bio" className="text-gray-300">详细介绍</Label>
|
||
<Textarea
|
||
id="bio"
|
||
className="bg-[#0a1628] border-gray-700 text-white min-h-[100px]"
|
||
placeholder="输入作者详细介绍..."
|
||
value={localSettings.authorInfo.bio || ""}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
authorInfo: { ...prev.authorInfo, bio: e.target.value },
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
|
||
{/* 预览卡片 */}
|
||
<div className="mt-4 p-4 rounded-xl bg-[#0a1628] border border-[#38bdac]/30">
|
||
<p className="text-xs text-gray-500 mb-2">预览效果</p>
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-[#00CED1] to-[#20B2AA] flex items-center justify-center text-xl font-bold text-white">
|
||
{localSettings.authorInfo.name?.charAt(0) || "K"}
|
||
</div>
|
||
<div>
|
||
<p className="text-white font-semibold">{localSettings.authorInfo.name}</p>
|
||
<p className="text-gray-400 text-xs">{localSettings.authorInfo.description}</p>
|
||
<p className="text-[#38bdac] text-xs mt-1">
|
||
每日 {localSettings.authorInfo.liveTime} · {localSettings.authorInfo.platform}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 价格设置 */}
|
||
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
|
||
<CardHeader>
|
||
<CardTitle className="text-white flex items-center gap-2">
|
||
<DollarSign className="w-5 h-5 text-[#38bdac]" />
|
||
价格设置
|
||
</CardTitle>
|
||
<CardDescription className="text-gray-400">配置书籍和章节的定价</CardDescription>
|
||
</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
|
||
type="number"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={localSettings.sectionPrice}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
sectionPrice: Number.parseFloat(e.target.value) || 1,
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label className="text-gray-300">整本价格 (元)</Label>
|
||
<Input
|
||
type="number"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={localSettings.baseBookPrice}
|
||
onChange={(e) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
baseBookPrice: Number.parseFloat(e.target.value) || 9.9,
|
||
}))
|
||
}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 免费章节设置 */}
|
||
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
|
||
<CardHeader>
|
||
<CardTitle className="text-white flex items-center gap-2">
|
||
<Gift className="w-5 h-5 text-[#38bdac]" />
|
||
免费章节
|
||
</CardTitle>
|
||
<CardDescription className="text-gray-400">设置哪些章节对所有用户免费开放</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="flex flex-wrap gap-2">
|
||
{freeChapters.map((chapter) => (
|
||
<Badge
|
||
key={chapter}
|
||
variant="secondary"
|
||
className="bg-[#38bdac]/20 text-[#38bdac] border border-[#38bdac]/30 px-3 py-1 text-sm"
|
||
>
|
||
{chapter}
|
||
<button
|
||
onClick={() => removeFreeChapter(chapter)}
|
||
className="ml-2 hover:text-red-400"
|
||
>
|
||
<X className="w-3 h-3" />
|
||
</button>
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<Input
|
||
className="bg-[#0a1628] border-gray-700 text-white flex-1"
|
||
placeholder="输入章节ID,如 1.2、2.1、preface"
|
||
value={newFreeChapter}
|
||
onChange={(e) => setNewFreeChapter(e.target.value)}
|
||
onKeyDown={(e) => e.key === 'Enter' && addFreeChapter()}
|
||
/>
|
||
<Button
|
||
onClick={addFreeChapter}
|
||
className="bg-[#38bdac] hover:bg-[#2da396]"
|
||
>
|
||
<Plus className="w-4 h-4 mr-1" />
|
||
添加
|
||
</Button>
|
||
</div>
|
||
<p className="text-xs text-gray-500">
|
||
常用ID: preface(序言), epilogue(尾声), appendix-1/2/3(附录), 1.1/1.2等(章节)
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 小程序配置 */}
|
||
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
|
||
<CardHeader>
|
||
<CardTitle className="text-white flex items-center gap-2">
|
||
<Smartphone className="w-5 h-5 text-[#38bdac]" />
|
||
小程序配置
|
||
</CardTitle>
|
||
<CardDescription className="text-gray-400">微信小程序相关参数设置</CardDescription>
|
||
</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-[#0a1628] border-gray-700 text-white"
|
||
value={mpConfig.appId}
|
||
onChange={(e) => setMpConfig(prev => ({ ...prev, appId: e.target.value }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label className="text-gray-300">API域名</Label>
|
||
<Input
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={mpConfig.apiDomain}
|
||
onChange={(e) => setMpConfig(prev => ({ ...prev, apiDomain: e.target.value }))}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<div className="space-y-2">
|
||
<Label className="text-gray-300">购买者优惠 (%)</Label>
|
||
<Input
|
||
type="number"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={mpConfig.buyerDiscount}
|
||
onChange={(e) => setMpConfig(prev => ({ ...prev, buyerDiscount: Number(e.target.value) }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label className="text-gray-300">推荐绑定天数</Label>
|
||
<Input
|
||
type="number"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={mpConfig.referralBindDays}
|
||
onChange={(e) => setMpConfig(prev => ({ ...prev, referralBindDays: Number(e.target.value) }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label className="text-gray-300">最低提现 (元)</Label>
|
||
<Input
|
||
type="number"
|
||
className="bg-[#0a1628] border-gray-700 text-white"
|
||
value={mpConfig.minWithdraw}
|
||
onChange={(e) => setMpConfig(prev => ({ ...prev, minWithdraw: Number(e.target.value) }))}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 分销设置 */}
|
||
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
|
||
<CardHeader>
|
||
<CardTitle className="text-white flex items-center gap-2">
|
||
<Users className="w-5 h-5 text-[#38bdac]" />
|
||
分销设置
|
||
</CardTitle>
|
||
<CardDescription className="text-gray-400">配置分销比例和奖励规则</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center">
|
||
<Label className="text-gray-300">分销者分成比例</Label>
|
||
<span className="text-2xl font-bold text-[#38bdac]">{localSettings.distributorShare}%</span>
|
||
</div>
|
||
<Slider
|
||
value={[localSettings.distributorShare]}
|
||
onValueChange={([value]) =>
|
||
setLocalSettings((prev) => ({
|
||
...prev,
|
||
distributorShare: value,
|
||
}))
|
||
}
|
||
max={100}
|
||
step={5}
|
||
className="w-full"
|
||
/>
|
||
<div className="flex justify-between text-sm text-gray-400">
|
||
<span>作者获得: {100 - localSettings.distributorShare}%</span>
|
||
<span>分销者获得: {localSettings.distributorShare}%</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 功能开关 */}
|
||
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
|
||
<CardHeader>
|
||
<CardTitle className="text-white">功能开关</CardTitle>
|
||
<CardDescription className="text-gray-400">控制系统核心模块的启用状态</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<Label htmlFor="maintenance-mode" className="flex flex-col space-y-1">
|
||
<span className="text-white">维护模式</span>
|
||
<span className="font-normal text-xs text-gray-500">启用后前台将显示维护中页面</span>
|
||
</Label>
|
||
<Switch id="maintenance-mode" />
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<Label htmlFor="payment-enabled" className="flex flex-col space-y-1">
|
||
<span className="text-white">全站支付</span>
|
||
<span className="font-normal text-xs text-gray-500">关闭后所有支付功能将暂停</span>
|
||
</Label>
|
||
<Switch id="payment-enabled" defaultChecked />
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<Label htmlFor="referral-enabled" className="flex flex-col space-y-1">
|
||
<span className="text-white">分销系统</span>
|
||
<span className="font-normal text-xs text-gray-500">是否允许用户生成邀请链接</span>
|
||
</Label>
|
||
<Switch id="referral-enabled" defaultChecked />
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<Label htmlFor="match-enabled" className="flex flex-col space-y-1">
|
||
<span className="text-white">找伙伴功能</span>
|
||
<span className="font-normal text-xs text-gray-500">是否启用找伙伴匹配功能</span>
|
||
</Label>
|
||
<Switch id="match-enabled" defaultChecked />
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|