From 1a95aee112b2d056882ad2e3279e388d49b31cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Thu, 5 Feb 2026 18:45:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=94=AF=E4=BB=98=E5=92=8C?= =?UTF-8?q?=E6=8E=A8=E8=8D=90=E7=B3=BB=E7=BB=9F=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=A0=B9=E6=8D=AE=E6=8E=A8=E8=8D=90=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=AE=A1=E7=AE=97=E6=94=AF=E4=BB=98=E9=87=91=E9=A2=9D?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BA=AB=E5=8F=97=E4=BC=98=E6=83=A0=E3=80=82=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E7=BB=91=E5=AE=9A=E6=8E=A8=E8=8D=90=E5=85=B3=E7=B3=BB?= =?UTF-8?q?=E7=9A=84=E6=9C=89=E6=95=88=E6=9C=9F=E8=AF=BB=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=EF=BC=8C=E6=94=AF=E6=8C=81=E4=BB=8E=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=B8=AD=E8=8E=B7=E5=8F=96=E3=80=82=E4=BC=98=E5=8C=96=E6=8F=90?= =?UTF-8?q?=E7=8E=B0=E6=B5=81=E7=A8=8B=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=9C=80?= =?UTF-8?q?=E4=BD=8E=E6=8F=90=E7=8E=B0=E9=87=91=E9=A2=9D=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=AF=BB=E5=8F=96=EF=BC=8C=E6=8F=90=E5=8D=87=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=81=B5=E6=B4=BB=E6=80=A7=E5=92=8C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E3=80=82=E5=90=8C=E6=97=B6=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=AE=A1=E7=90=86=E7=95=8C=E9=9D=A2=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E8=AE=BE=E7=BD=AE=E9=93=BE=E6=8E=A5=EF=BC=8C?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E4=B8=80=E8=87=B4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/layout.tsx | 2 +- app/admin/referral-settings/page.tsx | 272 +++++++++++++ app/api/miniprogram/pay/route.ts | 38 +- app/api/referral/bind/route.ts | 19 +- app/api/withdraw/route.ts | 21 +- miniprogram/pages/referral/referral.wxml | 11 - next-env.d.ts | 2 +- 开发文档/8、部署/推广设置功能-完整修复清单.md | 369 ++++++++++++++++++ 开发文档/8、部署/推广设置页面测试清单.md | 148 +++++++ 9 files changed, 858 insertions(+), 24 deletions(-) create mode 100644 app/admin/referral-settings/page.tsx create mode 100644 开发文档/8、部署/推广设置功能-完整修复清单.md create mode 100644 开发文档/8、部署/推广设置页面测试清单.md diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index f49cae51..baf45d6b 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -47,7 +47,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) { icon: BookOpen, label: "内容管理", href: "/admin/content" }, { icon: Users, label: "用户管理", href: "/admin/users" }, { icon: Wallet, label: "交易中心", href: "/admin/distribution" }, // 合并:分销+订单+提现 - { icon: CreditCard, label: "支付设置", href: "/admin/payment" }, + { icon: CreditCard, label: "推广设置", href: "/admin/referral-settings" }, // 单独入口,集中管理分销配置 { icon: Settings, label: "系统设置", href: "/admin/settings" }, ] diff --git a/app/admin/referral-settings/page.tsx b/app/admin/referral-settings/page.tsx new file mode 100644 index 00000000..f8645cd3 --- /dev/null +++ b/app/admin/referral-settings/page.tsx @@ -0,0 +1,272 @@ +"use client" + +import { useEffect, useState } 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 { Badge } from "@/components/ui/badge" +import { Save, Percent, Users, Wallet, Info } from "lucide-react" + +type ReferralConfig = { + distributorShare: number + minWithdrawAmount: number + bindingDays: number + userDiscount: number + enableAutoWithdraw: boolean +} + +const DEFAULT_REFERRAL_CONFIG: ReferralConfig = { + distributorShare: 90, + minWithdrawAmount: 10, + bindingDays: 30, + userDiscount: 5, + enableAutoWithdraw: false, +} + +export default function ReferralSettingsPage() { + const [config, setConfig] = useState(DEFAULT_REFERRAL_CONFIG) + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + + useEffect(() => { + const loadConfig = async () => { + try { + const res = await fetch("/api/db/config?key=referral_config") + if (res.ok) { + const data = await res.json() + if (data?.success && data.config) { + setConfig({ + distributorShare: data.config.distributorShare ?? 90, + minWithdrawAmount: data.config.minWithdrawAmount ?? 10, + bindingDays: data.config.bindingDays ?? 30, + userDiscount: data.config.userDiscount ?? 5, + enableAutoWithdraw: data.config.enableAutoWithdraw ?? false, + }) + } + } + } catch (e) { + console.error("加载 referral_config 失败:", e) + } finally { + setLoading(false) + } + } + loadConfig() + }, []) + + const handleSave = async () => { + setSaving(true) + try { + // 确保所有字段都是正确类型(防止字符串导致计算错误) + const safeConfig = { + distributorShare: Number(config.distributorShare) || 0, + minWithdrawAmount: Number(config.minWithdrawAmount) || 0, + bindingDays: Number(config.bindingDays) || 0, + userDiscount: Number(config.userDiscount) || 0, + enableAutoWithdraw: Boolean(config.enableAutoWithdraw), + } + + const body = { + key: "referral_config", + config: safeConfig, + description: "分销 / 推广规则配置", + } + const res = await fetch("/api/db/config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }) + const data = await res.json() + if (!res.ok || !data?.success) { + alert("保存失败: " + (data?.error || res.statusText)) + return + } + + alert("✅ 分销配置已保存成功!\n\n• 小程序与网站的推广规则会一起生效\n• 绑定关系会使用新的天数配置\n• 佣金比例会立即应用到新订单\n\n如有缓存,请刷新前台/小程序页面。") + } catch (e: any) { + console.error("保存 referral_config 失败:", e) + alert("保存失败: " + (e?.message || String(e))) + } finally { + setSaving(false) + } + } + + const handleNumberChange = (field: keyof ReferralConfig) => (e: React.ChangeEvent) => { + const value = parseFloat(e.target.value || "0") + setConfig((prev) => ({ ...prev, [field]: isNaN(value) ? 0 : value })) + } + + return ( +
+
+
+

+ + 推广 / 分销设置 +

+

+ 统一管理「好友优惠」「你得 90% 收益」「绑定期 30 天」「提现门槛」等规则,小程序和 Web 共用这套配置。 +

+
+ +
+ +
+ {/* 核心规则卡片 */} + + + + + 推广规则 + + + 这三项会直接体现在小程序「推广规则」卡片上,同时影响实收佣金计算。 + + + +
+
+ + +

例如 5 表示好友立减 5%(在价格配置基础上生效)。

+
+ +
+ +
+ setConfig((prev) => ({ ...prev, distributorShare: val }))} + /> + +
+

+ 实际佣金 = 订单金额 × {" "} + {config.distributorShare}%,支付回调和分销统计都会用这个值。 +

+
+ +
+ + +

好友通过你的链接进来并登录后,绑定在你名下的天数。

+
+
+
+
+ + {/* 提现与自动提现 */} + + + + + 提现规则 + + + 与「提现中心」「自动提现」相关的参数,影响推广者看到的可提现金额和最低门槛。 + + + +
+
+ + +

小程序「满 X 元可提现」展示的门槛,同时用于后端接口校验。

+
+ +
+ +
+ setConfig((prev) => ({ ...prev, enableAutoWithdraw: checked }))} + /> + + 开启后,可结合定时任务实现「收益自动打款到微信零钱」。 + +
+
+
+
+
+ + {/* 提示卡片 */} + + + + + 使用说明 + + + +

+ 1. 以上配置会写入 system_config.referral_config,小程序「推广中心」、 + Web 推广页以及支付回调都会读取同一份配置。 +

+

+ 2. 修改后新订单立即生效;旧订单的历史佣金不会自动重算,只影响之后产生的订单。 +

+

+ 3. 如遇前端展示与实际结算不一致,优先以此处配置为准,再排查缓存和小程序版本。 +

+
+
+
+
+ ) +} diff --git a/app/api/miniprogram/pay/route.ts b/app/api/miniprogram/pay/route.ts index 30707051..ce189869 100644 --- a/app/api/miniprogram/pay/route.ts +++ b/app/api/miniprogram/pay/route.ts @@ -10,7 +10,7 @@ import { NextResponse } from 'next/server' import crypto from 'crypto' -import { query } from '@/lib/db' +import { query, getConfig } from '@/lib/db' // 微信支付配置 - 2026-01-25 更新 // 小程序支付绑定状态: 审核中(申请单ID: 201554696918) @@ -101,8 +101,33 @@ export async function POST(request: Request) { }, { status: 400 }) } + // === 根据推广配置计算好友优惠后的实际支付金额 === + let finalAmount = amount + try { + // 读取推广/分销配置,获取好友优惠比例(如 5 表示 5%) + const referralConfig = await getConfig('referral_config') + const userDiscount = referralConfig?.userDiscount ? Number(referralConfig.userDiscount) : 0 + + // 若存在有效的推荐码且配置了优惠比例,则给好友打折 + if (userDiscount > 0 && body.referralCode) { + const discountRate = userDiscount / 100 + const discounted = amount * (1 - discountRate) + // 保证至少 0.01 元,并保留两位小数 + finalAmount = Math.max(0.01, Math.round(discounted * 100) / 100) + console.log('[MiniPay] 应用好友优惠:', { + originalAmount: amount, + discountPercent: userDiscount, + finalAmount, + referralCode: body.referralCode, + }) + } + } catch (e) { + console.warn('[MiniPay] 读取 referral_config.userDiscount 失败,使用原价金额:', e) + finalAmount = amount + } + const orderSn = generateOrderSn() - const totalFee = Math.round(amount * 100) // 转换为分 + const totalFee = Math.round(finalAmount * 100) // 转换为分(单位分) const goodsBody = description || (productType === 'fullbook' ? '《一场Soul的创业实验》全书' : `章节购买-${productId}`) // 获取客户端IP @@ -207,7 +232,7 @@ export async function POST(request: Request) { ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) `, [ orderSn, orderSn, userId, openId, - productType, productId || 'fullbook', amount, goodsBody, + productType, productId || 'fullbook', finalAmount, goodsBody, 'created', null, referrerId, orderReferralCode ]) } catch (insertErr: any) { @@ -224,7 +249,7 @@ export async function POST(request: Request) { ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) `, [ orderSn, orderSn, userId, openId, - productType, productId || 'fullbook', amount, goodsBody, + productType, productId || 'fullbook', finalAmount, goodsBody, 'created', null, referrerId ]) console.log('[MiniPay] 订单已插入(未含 referral_code,请执行 scripts/add_orders_referral_code.py)') @@ -238,7 +263,7 @@ export async function POST(request: Request) { ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) `, [ orderSn, orderSn, userId, openId, - productType, productId || 'fullbook', amount, goodsBody, + productType, productId || 'fullbook', finalAmount, goodsBody, 'created', null ]) console.log('[MiniPay] 订单已插入(未含 referrer_id/referral_code,请执行迁移脚本)') @@ -257,7 +282,8 @@ export async function POST(request: Request) { userId, productType, productId, - amount + originalAmount: amount, + finalAmount, }) } catch (dbError) { console.error('[MiniPay] ❌ 插入订单失败:', dbError) diff --git a/app/api/referral/bind/route.ts b/app/api/referral/bind/route.ts index 758e9899..68a1ee34 100644 --- a/app/api/referral/bind/route.ts +++ b/app/api/referral/bind/route.ts @@ -12,8 +12,8 @@ import { NextRequest, NextResponse } from 'next/server' import { query, getConfig } from '@/lib/db' -// 绑定有效期(天) -const BINDING_DAYS = 30 +// 绑定有效期(天)- 默认值,优先从配置读取 +const DEFAULT_BINDING_DAYS = 30 /** * POST - 绑定推荐关系(支持抢夺机制) @@ -32,6 +32,17 @@ export async function POST(request: NextRequest) { }, { status: 400 }) } + // 获取绑定天数配置 + let bindingDays = DEFAULT_BINDING_DAYS + try { + const config = await getConfig('referral_config') + if (config?.bindingDays) { + bindingDays = Number(config.bindingDays) + } + } catch (e) { + console.warn('[Referral Bind] 读取配置失败,使用默认值', DEFAULT_BINDING_DAYS) + } + // 查找推荐人 const referrers = await query( 'SELECT id, nickname, referral_code FROM users WHERE referral_code = ?', @@ -111,9 +122,9 @@ export async function POST(request: NextRequest) { } } - // 计算新的过期时间(30天) + // 计算新的过期时间(从配置读取天数) const expiryDate = new Date() - expiryDate.setDate(expiryDate.getDate() + BINDING_DAYS) + expiryDate.setDate(expiryDate.getDate() + bindingDays) // 创建或更新绑定记录 const bindingId = 'bind_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 6) diff --git a/app/api/withdraw/route.ts b/app/api/withdraw/route.ts index 3cda38ec..e2e3aadc 100644 --- a/app/api/withdraw/route.ts +++ b/app/api/withdraw/route.ts @@ -4,7 +4,7 @@ */ import { NextRequest, NextResponse } from 'next/server' -import { query } from '@/lib/db' +import { query, getConfig } from '@/lib/db' // 确保提现表存在 async function ensureWithdrawalsTable() { @@ -41,6 +41,25 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: false, message: '提现金额无效' }, { status: 400 }) } + // 读取最低提现门槛 + let minWithdrawAmount = 10 // 默认值 + try { + const config = await getConfig('referral_config') + if (config?.minWithdrawAmount) { + minWithdrawAmount = Number(config.minWithdrawAmount) + } + } catch (e) { + console.warn('[Withdraw] 读取配置失败,使用默认值 10 元') + } + + // 检查最低提现门槛 + if (amount < minWithdrawAmount) { + return NextResponse.json({ + success: false, + message: `最低提现金额为 ¥${minWithdrawAmount},当前 ¥${amount}` + }, { status: 400 }) + } + // 确保表存在 await ensureWithdrawalsTable() diff --git a/miniprogram/pages/referral/referral.wxml b/miniprogram/pages/referral/referral.wxml index a7113790..95aa3b7d 100644 --- a/miniprogram/pages/referral/referral.wxml +++ b/miniprogram/pages/referral/referral.wxml @@ -161,17 +161,6 @@ - - - - 我的邀请码 - - {{referralCode}} - - - 好友通过你的链接购买立省5%,你获得{{shareRate}}%收益 - -