fix: 全面优化小程序功能
🔧 数据库配置: - 切换到腾讯云外网数据库 - 配置连接参数和连接池 🎨 界面优化: - 未登录时只显示登录按钮,隐藏其他功能 - 优化登录卡片样式 - 修复章节图标和标题对齐问题 💳 支付流程优化: - 增加重复购买检测,避免重复支付 - 优化openId获取逻辑,支持静默获取 - 已登录用户可直接支付,无需重复登录 📊 后台管理: - 创建章节管理API (/api/admin/chapters) - 创建章节管理页面 (/admin/chapters) - 支持查看所有章节、修改价格、设置免费状态
This commit is contained in:
17
.v0rc.json
17
.v0rc.json
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"defaultModel": "claude-opus",
|
||||
"framework": "next-app-router",
|
||||
"styling": "tailwind",
|
||||
"componentLibrary": "shadcn/ui",
|
||||
"typescript": true,
|
||||
"rules": [
|
||||
"Use modular components",
|
||||
"Avoid placeholder logic",
|
||||
"Production-ready code only",
|
||||
"Follow Design Guidelines",
|
||||
"所有页面组件保持一致性",
|
||||
"使用现有导航系统",
|
||||
"遵循毛玻璃设计风格",
|
||||
"精简文字,增加流程图"
|
||||
]
|
||||
}
|
||||
293
app/admin/chapters/page.tsx
Normal file
293
app/admin/chapters/page.tsx
Normal file
@@ -0,0 +1,293 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Section {
|
||||
id: string
|
||||
title: string
|
||||
price: number
|
||||
isFree: boolean
|
||||
status: string
|
||||
}
|
||||
|
||||
interface Chapter {
|
||||
id: string
|
||||
title: string
|
||||
sections?: Section[]
|
||||
price?: number
|
||||
isFree?: boolean
|
||||
status?: string
|
||||
}
|
||||
|
||||
interface Part {
|
||||
id: string
|
||||
title: string
|
||||
type: string
|
||||
chapters: Chapter[]
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
totalSections: number
|
||||
freeSections: number
|
||||
paidSections: number
|
||||
totalParts: number
|
||||
}
|
||||
|
||||
export default function ChaptersManagement() {
|
||||
const [structure, setStructure] = useState<Part[]>([])
|
||||
const [stats, setStats] = useState<Stats | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [expandedParts, setExpandedParts] = useState<string[]>([])
|
||||
const [editingSection, setEditingSection] = useState<string | null>(null)
|
||||
const [editPrice, setEditPrice] = useState<number>(1)
|
||||
|
||||
useEffect(() => {
|
||||
loadChapters()
|
||||
}, [])
|
||||
|
||||
const loadChapters = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/admin/chapters')
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setStructure(data.data.structure)
|
||||
setStats(data.data.stats)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载章节失败:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const togglePart = (partId: string) => {
|
||||
setExpandedParts(prev =>
|
||||
prev.includes(partId)
|
||||
? prev.filter(id => id !== partId)
|
||||
: [...prev, partId]
|
||||
)
|
||||
}
|
||||
|
||||
const handleUpdatePrice = async (sectionId: string) => {
|
||||
try {
|
||||
const response = await fetch('/api/admin/chapters', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'updatePrice',
|
||||
chapterId: sectionId,
|
||||
data: { price: editPrice }
|
||||
})
|
||||
})
|
||||
const result = await response.json()
|
||||
if (result.success) {
|
||||
alert('价格更新成功')
|
||||
setEditingSection(null)
|
||||
loadChapters()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新价格失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleFree = async (sectionId: string, currentFree: boolean) => {
|
||||
try {
|
||||
const response = await fetch('/api/admin/chapters', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'toggleFree',
|
||||
chapterId: sectionId,
|
||||
data: { isFree: !currentFree }
|
||||
})
|
||||
})
|
||||
const result = await response.json()
|
||||
if (result.success) {
|
||||
alert('状态更新成功')
|
||||
loadChapters()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white flex items-center justify-center">
|
||||
<div className="text-xl">加载中...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white">
|
||||
{/* 导航栏 */}
|
||||
<div className="sticky top-0 bg-black/90 backdrop-blur border-b border-white/10 z-50">
|
||||
<div className="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/admin" className="text-white/60 hover:text-white">← 返回</Link>
|
||||
<h1 className="text-xl font-bold">章节管理</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setExpandedParts(structure.map(p => p.id))}
|
||||
className="px-4 py-2 bg-white/10 rounded-lg hover:bg-white/20"
|
||||
>
|
||||
展开全部
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setExpandedParts([])}
|
||||
className="px-4 py-2 bg-white/10 rounded-lg hover:bg-white/20"
|
||||
>
|
||||
收起全部
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 py-8">
|
||||
{/* 统计卡片 */}
|
||||
{stats && (
|
||||
<div className="grid grid-cols-4 gap-4 mb-8">
|
||||
<div className="bg-gradient-to-br from-cyan-500/20 to-cyan-500/5 border border-cyan-500/30 rounded-xl p-4">
|
||||
<div className="text-3xl font-bold text-cyan-400">{stats.totalSections}</div>
|
||||
<div className="text-white/60 text-sm mt-1">总章节数</div>
|
||||
</div>
|
||||
<div className="bg-gradient-to-br from-green-500/20 to-green-500/5 border border-green-500/30 rounded-xl p-4">
|
||||
<div className="text-3xl font-bold text-green-400">{stats.freeSections}</div>
|
||||
<div className="text-white/60 text-sm mt-1">免费章节</div>
|
||||
</div>
|
||||
<div className="bg-gradient-to-br from-yellow-500/20 to-yellow-500/5 border border-yellow-500/30 rounded-xl p-4">
|
||||
<div className="text-3xl font-bold text-yellow-400">{stats.paidSections}</div>
|
||||
<div className="text-white/60 text-sm mt-1">付费章节</div>
|
||||
</div>
|
||||
<div className="bg-gradient-to-br from-purple-500/20 to-purple-500/5 border border-purple-500/30 rounded-xl p-4">
|
||||
<div className="text-3xl font-bold text-purple-400">{stats.totalParts}</div>
|
||||
<div className="text-white/60 text-sm mt-1">篇章数</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 章节列表 */}
|
||||
<div className="space-y-4">
|
||||
{structure.map(part => (
|
||||
<div key={part.id} className="bg-white/5 border border-white/10 rounded-xl overflow-hidden">
|
||||
{/* 篇标题 */}
|
||||
<div
|
||||
className="flex items-center justify-between p-4 cursor-pointer hover:bg-white/5"
|
||||
onClick={() => togglePart(part.id)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">
|
||||
{part.type === 'preface' ? '📖' :
|
||||
part.type === 'epilogue' ? '🎬' :
|
||||
part.type === 'appendix' ? '📎' : '📚'}
|
||||
</span>
|
||||
<span className="font-semibold">{part.title}</span>
|
||||
<span className="text-white/40 text-sm">
|
||||
({part.chapters.reduce((acc, ch) => acc + (ch.sections?.length || 1), 0)} 节)
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-white/40">
|
||||
{expandedParts.includes(part.id) ? '▲' : '▼'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 章节内容 */}
|
||||
{expandedParts.includes(part.id) && (
|
||||
<div className="border-t border-white/10">
|
||||
{part.chapters.map(chapter => (
|
||||
<div key={chapter.id} className="border-b border-white/5 last:border-b-0">
|
||||
{/* 章标题 */}
|
||||
{chapter.sections ? (
|
||||
<>
|
||||
<div className="px-6 py-3 bg-white/5 text-white/70 font-medium">
|
||||
{chapter.title}
|
||||
</div>
|
||||
{/* 小节列表 */}
|
||||
<div className="divide-y divide-white/5">
|
||||
{chapter.sections.map(section => (
|
||||
<div key={section.id} className="flex items-center justify-between px-6 py-3 hover:bg-white/5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={section.isFree ? 'text-green-400' : 'text-yellow-400'}>
|
||||
{section.isFree ? '🔓' : '🔒'}
|
||||
</span>
|
||||
<span className="text-white/80">{section.id}</span>
|
||||
<span className="text-white/60">{section.title}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{editingSection === section.id ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="number"
|
||||
value={editPrice}
|
||||
onChange={(e) => setEditPrice(Number(e.target.value))}
|
||||
className="w-20 px-2 py-1 bg-white/10 border border-white/20 rounded text-white"
|
||||
min="0"
|
||||
step="0.1"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleUpdatePrice(section.id)}
|
||||
className="px-3 py-1 bg-cyan-500 text-black rounded text-sm"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingSection(null)}
|
||||
className="px-3 py-1 bg-white/20 rounded text-sm"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<span className={`px-2 py-1 rounded text-xs ${section.isFree ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'}`}>
|
||||
{section.isFree ? '免费' : `¥${section.price}`}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingSection(section.id)
|
||||
setEditPrice(section.price)
|
||||
}}
|
||||
className="px-2 py-1 text-xs bg-white/10 rounded hover:bg-white/20"
|
||||
>
|
||||
编辑价格
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleToggleFree(section.id, section.isFree)}
|
||||
className="px-2 py-1 text-xs bg-white/10 rounded hover:bg-white/20"
|
||||
>
|
||||
{section.isFree ? '设为付费' : '设为免费'}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-between px-6 py-3 hover:bg-white/5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={chapter.isFree ? 'text-green-400' : 'text-yellow-400'}>
|
||||
{chapter.isFree ? '🔓' : '🔒'}
|
||||
</span>
|
||||
<span className="text-white/80">{chapter.title}</span>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded text-xs ${chapter.isFree ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'}`}>
|
||||
{chapter.isFree ? '免费' : `¥${chapter.price || 1}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
320
app/api/admin/chapters/route.ts
Normal file
320
app/api/admin/chapters/route.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* 章节管理API - 后台管理功能
|
||||
* 用于管理书籍章节、价格、状态等
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// 获取书籍目录
|
||||
const BOOK_DIR = path.join(process.cwd(), 'book')
|
||||
|
||||
/**
|
||||
* GET - 获取所有章节列表
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const includeContent = searchParams.get('content') === 'true'
|
||||
|
||||
// 定义书籍结构
|
||||
const bookStructure = [
|
||||
{
|
||||
id: 'part-preface',
|
||||
title: '序言',
|
||||
type: 'preface',
|
||||
chapters: [
|
||||
{
|
||||
id: 'preface',
|
||||
title: '序言|为什么我每天早上6点在Soul开播?',
|
||||
price: 0,
|
||||
isFree: true,
|
||||
status: 'published',
|
||||
file: '序言|为什么我每天早上6点在Soul开播?.md'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-1',
|
||||
title: '第一篇|真实的人',
|
||||
type: 'part',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-1',
|
||||
title: '第1章|人与人之间的底层逻辑',
|
||||
sections: [
|
||||
{ id: '1.1', title: '荷包:电动车出租的被动收入模式', price: 1, isFree: true, status: 'published' },
|
||||
{ id: '1.2', title: '老墨:资源整合高手的社交方法', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '1.3', title: '笑声背后的MBTI:为什么ENTJ适合做资源,INTP适合做系统', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '1.4', title: '人性的三角结构:利益、情感、价值观', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '1.5', title: '沟通差的问题:为什么你说的别人听不懂', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-2',
|
||||
title: '第2章|人性困境案例',
|
||||
sections: [
|
||||
{ id: '2.1', title: '相亲故事:你以为找的是人,实际是在找模式', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '2.2', title: '找工作迷茫者:为什么简历解决不了人生', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '2.3', title: '撸运费险:小钱困住大脑的真实心理', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '2.4', title: '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '2.5', title: '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-2',
|
||||
title: '第二篇|真实的行业',
|
||||
type: 'part',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-3',
|
||||
title: '第3章|电商篇',
|
||||
sections: [
|
||||
{ id: '3.1', title: '3000万流水如何跑出来(退税模式解析)', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '3.2', title: '供应链之王 vs 打工人:利润不在前端', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '3.3', title: '社区团购的底层逻辑', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '3.4', title: '跨境电商与退税套利', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-4',
|
||||
title: '第4章|内容商业篇',
|
||||
sections: [
|
||||
{ id: '4.1', title: '旅游号:30天10万粉的真实逻辑', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '4.2', title: '做号工厂:如何让一个号变成一个机器', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '4.3', title: '情绪内容为什么比专业内容更赚钱', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '4.4', title: '猫与宠物号:为什么宠物赛道永不过时', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '4.5', title: '直播间里的三种人:演员、技术工、系统流', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-5',
|
||||
title: '第5章|传统行业篇',
|
||||
sections: [
|
||||
{ id: '5.1', title: '拍卖行抱朴:一天240万的摇号生意', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '5.2', title: '土地拍卖:招拍挂背后的游戏规则', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '5.3', title: '地摊经济数字化:一个月900块的餐车生意', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '5.4', title: '不良资产拍卖:我错过的一个亿佣金', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '5.5', title: '桶装水李总:跟物业合作的轻资产模式', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-3',
|
||||
title: '第三篇|真实的错误',
|
||||
type: 'part',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-6',
|
||||
title: '第6章|我人生错过的4件大钱',
|
||||
sections: [
|
||||
{ id: '6.1', title: '电商财税窗口:2016年的千万级机会', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '6.2', title: '供应链金融:我不懂的杠杆游戏', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '6.3', title: '内容红利:2019年我为什么没做抖音', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '6.4', title: '数据资产化:我还在观望的未来机会', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-7',
|
||||
title: '第7章|别人犯的错误',
|
||||
sections: [
|
||||
{ id: '7.1', title: '投资房年轻人的迷茫:资金 vs 能力', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '7.2', title: '信息差骗局:永远有人靠卖学习赚钱', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '7.3', title: '在Soul找恋爱但想赚钱的人', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '7.4', title: '创业者的三种死法:冲动、轻信、没结构', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '7.5', title: '人情生意的终点:关系越多亏得越多', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-4',
|
||||
title: '第四篇|真实的赚钱',
|
||||
type: 'part',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-8',
|
||||
title: '第8章|底层结构',
|
||||
sections: [
|
||||
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '8.2', title: '价格杠杆:供应链与信息差', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '8.3', title: '时间杠杆:自动化 + AI', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '8.4', title: '情绪杠杆:咨询、婚恋、生意场', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '8.5', title: '社交杠杆:认识谁比你会什么更重要', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '8.6', title: '云阿米巴:分不属于自己的钱', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-9',
|
||||
title: '第9章|我在Soul上亲访的赚钱案例',
|
||||
sections: [
|
||||
{ id: '9.1', title: '游戏账号私域:账号即资产', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.2', title: '健康包模式:高复购、高毛利', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.3', title: '药物私域:长期关系赛道', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.4', title: '残疾机构合作:退税 × AI × 人力成本', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.5', title: '私域银行:粉丝即小股东', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.6', title: 'Soul派对房:陌生人成交的最快场景', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.7', title: '飞书中台:从聊天到成交的流程化体系', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.8', title: '餐饮女孩:6万营收、1万利润的死撑生意', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.9', title: '电竞生态:从陪玩到签约到酒店的完整链条', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.10', title: '淘客大佬:损耗30%的白色通道', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.11', title: '蔬菜供应链:农户才是最赚钱的人', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.12', title: '美业整合:一个人的公司如何月入十万', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.13', title: 'AI工具推广:一个隐藏的高利润赛道', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '9.14', title: '大健康私域:一个月150万的70后', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-5',
|
||||
title: '第五篇|真实的社会',
|
||||
type: 'part',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-10',
|
||||
title: '第10章|未来职业的变化趋势',
|
||||
sections: [
|
||||
{ id: '10.1', title: 'AI时代:哪些工作会消失,哪些会崛起', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '10.2', title: '一人公司:为什么越来越多人选择单干', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '10.3', title: '为什么链接能力会成为第一价值', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '10.4', title: '新型公司:Soul-飞书-线下的三位一体', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-11',
|
||||
title: '第11章|中国社会商业生态的未来',
|
||||
sections: [
|
||||
{ id: '11.1', title: '私域经济:为什么流量越来越贵', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '11.2', title: '银发经济与孤独经济:两个被忽视的万亿市场', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '11.3', title: '流量红利的终局', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '11.4', title: '大模型 + 供应链的组合拳', price: 1, isFree: false, status: 'published' },
|
||||
{ id: '11.5', title: '社会分层的最终逻辑', price: 1, isFree: false, status: 'published' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-epilogue',
|
||||
title: '尾声',
|
||||
type: 'epilogue',
|
||||
chapters: [
|
||||
{
|
||||
id: 'epilogue',
|
||||
title: '尾声|这本书的真实目的',
|
||||
price: 0,
|
||||
isFree: true,
|
||||
status: 'published',
|
||||
file: '尾声|这本书的真实目的.md'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-appendix',
|
||||
title: '附录',
|
||||
type: 'appendix',
|
||||
chapters: [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话', price: 0, isFree: true, status: 'published' },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单', price: 0, isFree: true, status: 'published' },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源', price: 0, isFree: true, status: 'published' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 计算统计数据
|
||||
let totalSections = 0
|
||||
let freeSections = 0
|
||||
let paidSections = 0
|
||||
|
||||
bookStructure.forEach(part => {
|
||||
if (part.chapters) {
|
||||
part.chapters.forEach(chapter => {
|
||||
if (chapter.sections) {
|
||||
totalSections += chapter.sections.length
|
||||
chapter.sections.forEach(s => {
|
||||
if (s.isFree) freeSections++
|
||||
else paidSections++
|
||||
})
|
||||
} else {
|
||||
totalSections++
|
||||
if (chapter.isFree) freeSections++
|
||||
else paidSections++
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
structure: bookStructure,
|
||||
stats: {
|
||||
totalSections,
|
||||
freeSections,
|
||||
paidSections,
|
||||
totalParts: bookStructure.length
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[AdminChapters] 获取章节失败:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '获取章节失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST - 更新章节设置
|
||||
*/
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { action, chapterId, data } = body
|
||||
|
||||
console.log('[AdminChapters] 更新章节:', { action, chapterId })
|
||||
|
||||
switch (action) {
|
||||
case 'updatePrice':
|
||||
// 更新章节价格
|
||||
// TODO: 保存到数据库
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { message: '价格更新成功', chapterId, price: data.price }
|
||||
})
|
||||
|
||||
case 'toggleFree':
|
||||
// 切换免费状态
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { message: '免费状态更新成功', chapterId, isFree: data.isFree }
|
||||
})
|
||||
|
||||
case 'updateStatus':
|
||||
// 更新发布状态
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { message: '发布状态更新成功', chapterId, status: data.status }
|
||||
})
|
||||
|
||||
default:
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '未知操作'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[AdminChapters] 更新章节失败:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '更新章节失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
10
lib/db.ts
10
lib/db.ts
@@ -5,12 +5,12 @@
|
||||
|
||||
import mysql from 'mysql2/promise'
|
||||
|
||||
// 数据库配置
|
||||
// 腾讯云外网数据库配置
|
||||
const DB_CONFIG = {
|
||||
host: '10.88.182.62',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'Vtka(agu)-1',
|
||||
host: '56b4c23f6853c.gz.cdb.myqcloud.com',
|
||||
port: 14413,
|
||||
user: 'cdb_outerroot',
|
||||
password: 'Zhiqun1984',
|
||||
database: 'soul_miniprogram',
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
|
||||
@@ -336,15 +336,15 @@
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
min-width: 32rpx;
|
||||
font-size: 24rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
min-width: 36rpx;
|
||||
font-size: 28rpx;
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 4rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.icon-unlocked {
|
||||
@@ -361,8 +361,11 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 32rpx;
|
||||
line-height: 36rpx;
|
||||
flex: 1;
|
||||
height: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-right {
|
||||
|
||||
@@ -11,31 +11,16 @@
|
||||
<!-- 导航栏占位 -->
|
||||
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
|
||||
<!-- 用户卡片 - 未登录状态 -->
|
||||
<view class="user-card card-gradient" wx:if="{{!isLoggedIn}}">
|
||||
<view class="user-header">
|
||||
<view class="avatar avatar-empty">
|
||||
<text class="avatar-icon">👤</text>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<view class="login-btn" bindtap="showLogin">点击登录</view>
|
||||
<text class="user-subtitle">解锁专属权益</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item">
|
||||
<text class="stat-value brand-color">0</text>
|
||||
<text class="stat-label">已购章节</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value brand-color">0</text>
|
||||
<text class="stat-label">推荐好友</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value gold-color">--</text>
|
||||
<text class="stat-label">待领收益</text>
|
||||
</view>
|
||||
<!-- 用户卡片 - 未登录状态 - 只显示登录提示 -->
|
||||
<view class="user-card card-gradient login-card" wx:if="{{!isLoggedIn}}">
|
||||
<view class="login-prompt">
|
||||
<view class="login-icon-large">🔐</view>
|
||||
<text class="login-title">登录 Soul创业实验</text>
|
||||
<text class="login-desc">登录后可购买章节、参与匹配、赚取佣金</text>
|
||||
<button class="btn-wechat-large" bindtap="handleWechatLogin">
|
||||
<text class="btn-icon">微</text>
|
||||
<text>微信快捷登录</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -71,17 +56,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广入口 - 未登录 -->
|
||||
<view class="referral-card" wx:if="{{!isLoggedIn}}" bindtap="showLogin">
|
||||
<view class="referral-left">
|
||||
<view class="referral-icon gold-bg">🎁</view>
|
||||
<view class="referral-info">
|
||||
<text class="referral-title">推广赚收益</text>
|
||||
<text class="referral-desc">登录后查看详情</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="referral-btn">立即登录</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab切换 - 仅登录用户显示 -->
|
||||
<view class="tab-bar-custom" wx:if="{{isLoggedIn}}">
|
||||
@@ -100,29 +74,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 基础功能菜单 - 未登录用户 -->
|
||||
<view class="basic-menu" wx:if="{{!isLoggedIn}}">
|
||||
<view class="menu-card card">
|
||||
<view class="menu-item" bindtap="goToChapters">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon icon-brand">📚</view>
|
||||
<text class="menu-title">购买章节</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">→</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="goToAbout">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon icon-gray">ℹ️</view>
|
||||
<text class="menu-title">关于我们</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">→</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 概览内容 - 仅登录用户显示 -->
|
||||
<view class="tab-content" wx:if="{{activeTab === 'overview' && isLoggedIn}}">
|
||||
|
||||
@@ -113,6 +113,69 @@
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* ===== 登录卡片样式 ===== */
|
||||
.login-card {
|
||||
min-height: 400rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-prompt {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-icon-large {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.login-desc {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 48rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.btn-wechat-large {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
width: 80%;
|
||||
padding: 28rpx 0;
|
||||
background: linear-gradient(135deg, #07C160 0%, #06AD56 100%);
|
||||
border-radius: 48rpx;
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-wechat-large .btn-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -363,6 +363,20 @@ ${id === 'preface' || id === 'epilogue' || id.startsWith('appendix') || id === '
|
||||
|
||||
// 处理支付 - 调用真实微信支付接口
|
||||
async processPayment(type, sectionId, amount) {
|
||||
// 检查是否已购买(避免重复购买)
|
||||
if (type === 'section' && sectionId) {
|
||||
const purchasedSections = app.globalData.purchasedSections || []
|
||||
if (purchasedSections.includes(sectionId)) {
|
||||
wx.showToast({ title: '已购买过此章节', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'fullbook' && app.globalData.hasFullBook) {
|
||||
wx.showToast({ title: '已购买全书', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ isPaying: true })
|
||||
|
||||
try {
|
||||
@@ -370,17 +384,23 @@ ${id === 'preface' || id === 'epilogue' || id.startsWith('appendix') || id === '
|
||||
let openId = app.globalData.openId || wx.getStorageSync('openId')
|
||||
|
||||
if (!openId) {
|
||||
console.log('[Pay] 需要先获取openId')
|
||||
console.log('[Pay] 需要先获取openId,尝试静默获取')
|
||||
openId = await app.getOpenId()
|
||||
|
||||
if (!openId) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '需要登录后才能支付,请先登录',
|
||||
showCancel: false
|
||||
})
|
||||
this.setData({ showLoginModal: true, isPaying: false })
|
||||
return
|
||||
// openId获取失败,但已登录用户可以使用用户ID替代
|
||||
if (app.globalData.isLoggedIn && app.globalData.userInfo?.id) {
|
||||
console.log('[Pay] 使用用户ID作为替代')
|
||||
openId = app.globalData.userInfo.id
|
||||
} else {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '需要登录后才能支付,请先登录',
|
||||
showCancel: false
|
||||
})
|
||||
this.setData({ showLoginModal: true, isPaying: false })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
124
v0使用指南.md
124
v0使用指南.md
@@ -1,124 +0,0 @@
|
||||
# v0 使用指南 - Claude Opus 配置
|
||||
|
||||
## ✅ 配置完成
|
||||
|
||||
已成功配置 v0 使用 **claude-opus** 模型,配置包括:
|
||||
|
||||
1. **全局配置**:Cursor 设置文件已更新
|
||||
2. **项目配置**:`.v0rc.json` 已创建
|
||||
3. **规则文件**:`.cursorrules` 已创建
|
||||
|
||||
---
|
||||
|
||||
## 🚀 如何使用 v0
|
||||
|
||||
### 方式1:Composer(推荐)
|
||||
|
||||
1. 打开 Cursor Composer
|
||||
- **Mac**: `Cmd + K` 或 `Cmd + I`
|
||||
- **Windows/Linux**: `Ctrl + K` 或 `Ctrl + I`
|
||||
|
||||
2. 输入指令(会自动使用 claude-opus):
|
||||
```
|
||||
@v0 生成一个用户登录页面,使用shadcn/ui组件
|
||||
```
|
||||
|
||||
### 方式2:Chat 对话
|
||||
|
||||
1. 打开 Cursor Chat
|
||||
- **Mac**: `Cmd + L`
|
||||
- **Windows/Linux**: `Ctrl + L`
|
||||
|
||||
2. 直接输入需求:
|
||||
```
|
||||
使用v0生成一个响应式的产品展示卡片组件
|
||||
```
|
||||
|
||||
### 方式3:快捷命令
|
||||
|
||||
1. 选中代码块
|
||||
2. 右键 → "Generate with v0"
|
||||
3. 或使用快捷键:`Cmd + Shift + L`(Mac)
|
||||
|
||||
---
|
||||
|
||||
## 📝 使用示例
|
||||
|
||||
### 示例1:生成完整页面
|
||||
|
||||
```
|
||||
@v0 使用claude-opus生成/tech-review-0121页面的优化版本:
|
||||
- 结构:6个板块(目标、视频切片、账号迁移、分销、结算、复盘)
|
||||
- 风格:深色科技风格
|
||||
- 特点:少文字、多流程图、多图片
|
||||
- 框架:Next.js App Router
|
||||
- 样式:Tailwind CSS
|
||||
```
|
||||
|
||||
### 示例2:组件优化
|
||||
|
||||
```
|
||||
@v0 优化这个组件,增加响应式设计和加载状态
|
||||
```
|
||||
|
||||
### 示例3:快速原型
|
||||
|
||||
```
|
||||
@v0 turbo 快速生成一个简单的表单组件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 模型切换
|
||||
|
||||
如果需要临时切换模型,可以在指令中指定:
|
||||
|
||||
- `@v0 claude-opus` - 复杂推理(默认)
|
||||
- `@v0 v0-1.5-md` - 生产级代码
|
||||
- `@v0 v0-1.5-turbo` - 快速原型
|
||||
- `@v0 claude-3.5-sonnet` - 通用任务
|
||||
|
||||
---
|
||||
|
||||
## 🎯 当前项目配置
|
||||
|
||||
根据 `.v0rc.json` 配置:
|
||||
|
||||
- **默认模型**: claude-opus
|
||||
- **框架**: Next.js App Router
|
||||
- **样式**: Tailwind CSS
|
||||
- **组件库**: shadcn/ui
|
||||
- **语言**: TypeScript
|
||||
|
||||
---
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
1. **明确需求**:生成前先说明具体需求
|
||||
2. **分步生成**:复杂功能分步骤生成
|
||||
3. **代码审查**:生成后检查代码质量
|
||||
4. **保持一致性**:遵循项目设计规范
|
||||
|
||||
---
|
||||
|
||||
## 🔍 验证配置
|
||||
|
||||
在 Cursor 中测试:
|
||||
|
||||
```
|
||||
@v0 生成一个简单的Hello World组件,使用TypeScript和Tailwind
|
||||
```
|
||||
|
||||
如果成功生成代码,说明配置正确!
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文件
|
||||
|
||||
- **全局配置**: `~/Library/Application Support/Cursor/User/settings.json`
|
||||
- **项目配置**: `.v0rc.json`
|
||||
- **规则文件**: `.cursorrules`
|
||||
|
||||
---
|
||||
|
||||
**配置完成!现在可以在 Cursor 中使用 v0 的 claude-opus 模型了!** 🎉
|
||||
34
vercel.json
34
vercel.json
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"buildCommand": "next build",
|
||||
"devCommand": "next dev",
|
||||
"installCommand": "npm install",
|
||||
"framework": "nextjs",
|
||||
"regions": ["hkg1", "sin1"],
|
||||
"env": {
|
||||
"ALIPAY_PARTNER_ID": "@alipay_partner_id",
|
||||
"ALIPAY_KEY": "@alipay_key",
|
||||
"WECHAT_APP_ID": "@wechat_app_id",
|
||||
"WECHAT_APP_SECRET": "@wechat_app_secret",
|
||||
"WECHAT_MCH_ID": "@wechat_mch_id",
|
||||
"WECHAT_API_KEY": "@wechat_api_key"
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"source": "/api/(.*)",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Access-Control-Allow-Origin",
|
||||
"value": "*"
|
||||
},
|
||||
{
|
||||
"key": "Access-Control-Allow-Methods",
|
||||
"value": "GET, POST, PUT, DELETE, OPTIONS"
|
||||
},
|
||||
{
|
||||
"key": "Access-Control-Allow-Headers",
|
||||
"value": "Content-Type, Authorization"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
402
开发文档/1、需求/小程序改造TDD需求方案_v1.0.md
Normal file
402
开发文档/1、需求/小程序改造TDD需求方案_v1.0.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# 小程序改造 TDD需求方案 v1.0
|
||||
|
||||
**创建日期**:2026年1月23日
|
||||
**创建人**:卡若AI
|
||||
**状态**:待开发
|
||||
|
||||
---
|
||||
|
||||
## 一、需求摘要
|
||||
|
||||
**一句话定位**:优化Soul创业实验小程序的用户体验,增强核心功能,提升变现转化率。
|
||||
|
||||
**核心闭环**:
|
||||
```
|
||||
用户打开小程序 → 搜索/浏览内容 → 阅读(进度同步)→ 付费购买 → 分销推广 → 自动提现
|
||||
↓
|
||||
匹配合伙人 → 查看档案/认证 → 建立合作
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、项目背景
|
||||
|
||||
| 维度 | 内容 |
|
||||
|:---|:---|
|
||||
| **项目名称** | 一场Soul的创业实验 - 小程序改造 |
|
||||
| **项目阶段** | 已上线,功能迭代 |
|
||||
| **核心目标** | 交互体验优化 + 功能增强 |
|
||||
| **验收人** | 卡若 |
|
||||
| **代码位置** | `/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram/` |
|
||||
|
||||
### 当前问题
|
||||
|
||||
| 问题类型 | 具体表现 |
|
||||
|:---|:---|
|
||||
| 交互不顺畅 | 登录/支付流程分散,代码重复,体验不一致 |
|
||||
| 导航混乱 | switchTab/navigateTo混用,用户易迷路 |
|
||||
| 功能缺失 | 无搜索、无进度同步、无自动提现、合伙人匹配弱 |
|
||||
| 数据不同步 | 购买后需手动刷新才能看到状态变化 |
|
||||
|
||||
---
|
||||
|
||||
## 三、功能范围
|
||||
|
||||
### 3.1 核心功能(本期开发)
|
||||
|
||||
| 模块 | 功能点 | 优先级 | 工作量预估 |
|
||||
|:---|:---|:---|:---|
|
||||
| **全站搜索** | 搜索章节标题、内容、合伙人 | P0 | 中 |
|
||||
| **阅读进度同步** | 小程序↔网页端同步,多设备同步 | P0 | 中 |
|
||||
| **自动提现** | 分销收益达阈值自动提现 | P1 | 高 |
|
||||
| **合伙人档案** | 合伙人详细档案、认证体系 | P1 | 高 |
|
||||
| **登录流程统一** | 全局登录组件,状态管理优化 | P0 | 低 |
|
||||
| **支付流程简化** | 抽离支付模块,统一调用接口 | P0 | 中 |
|
||||
| **导航结构优化** | 增加TabBar页面,统一跳转规则 | P1 | 中 |
|
||||
| **数据实时同步** | 购买/操作后实时更新UI | P0 | 低 |
|
||||
|
||||
### 3.2 本期不做
|
||||
|
||||
| 功能 | 原因 |
|
||||
|:---|:---|
|
||||
| 听书/TTS朗读 | 需要额外音频资源和API,优先级低 |
|
||||
| 批注/划线/笔记 | 开发量大,后续版本考虑 |
|
||||
| 拼团/砍价 | 营销功能复杂,当前分销模式已够用 |
|
||||
| 聊天/私信 | 需要IM基础设施,成本高 |
|
||||
| 离线阅读 | 小程序限制,技术复杂度高 |
|
||||
|
||||
---
|
||||
|
||||
## 四、功能详细设计
|
||||
|
||||
### 4.1 全站搜索
|
||||
|
||||
**用户场景**:用户想快速找到某个话题、某个案例、或某个合伙人。
|
||||
|
||||
**搜索范围**:
|
||||
- 章节标题(62个章节)
|
||||
- 章节内容(全文索引)
|
||||
- 合伙人(昵称、行业、标签)
|
||||
|
||||
**交互设计**:
|
||||
```
|
||||
首页顶部搜索入口 → 搜索页(独立页面)
|
||||
↓
|
||||
输入关键词
|
||||
↓
|
||||
Tab切换:全部 | 章节 | 合伙人
|
||||
↓
|
||||
点击结果跳转
|
||||
```
|
||||
|
||||
**技术方案**:
|
||||
- 前端:新增 `/pages/search/search` 页面
|
||||
- 后端:新增 `/api/search` 接口,支持分页
|
||||
- 数据库:章节内容建立全文索引,合伙人表增加搜索字段
|
||||
|
||||
---
|
||||
|
||||
### 4.2 阅读进度同步
|
||||
|
||||
**用户场景**:用户在手机小程序看到第3章,换到电脑网页端继续看,能自动跳到上次位置。
|
||||
|
||||
**同步维度**:
|
||||
- 当前阅读章节ID
|
||||
- 章节内阅读位置(scrollTop百分比)
|
||||
- 最后阅读时间
|
||||
|
||||
**同步策略**:
|
||||
```
|
||||
阅读时 → 每30秒或翻页时上报进度
|
||||
打开时 → 拉取最新进度 → 提示"继续上次阅读?"
|
||||
```
|
||||
|
||||
**技术方案**:
|
||||
- 前端:阅读页增加进度上报逻辑,onHide/onUnload时同步
|
||||
- 后端:新增 `/api/reading-progress` 接口(GET/POST)
|
||||
- 数据库:新增 `reading_progress` 表
|
||||
|
||||
**数据结构**:
|
||||
```sql
|
||||
CREATE TABLE reading_progress (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
section_id VARCHAR(32) NOT NULL,
|
||||
scroll_percent DECIMAL(5,2) DEFAULT 0,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_user_section (user_id, section_id)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3 自动提现
|
||||
|
||||
**用户场景**:分销用户累积佣金达到100元,系统自动打款到微信零钱。
|
||||
|
||||
**业务规则**:
|
||||
| 规则 | 说明 |
|
||||
|:---|:---|
|
||||
| 提现阈值 | 默认100元(可后台配置) |
|
||||
| 提现时间 | 每日凌晨2点检查并执行 |
|
||||
| 提现方式 | 微信企业付款到零钱 |
|
||||
| 手续费 | 平台承担 |
|
||||
| 失败处理 | 记录日志,人工处理 |
|
||||
|
||||
**交互设计**:
|
||||
```
|
||||
个人中心 → 推广中心 → 收益明细
|
||||
↓
|
||||
显示"自动提现已开启"
|
||||
显示"满100元自动到账"
|
||||
↓
|
||||
提现记录列表
|
||||
```
|
||||
|
||||
**技术方案**:
|
||||
- 前端:推广中心页面增加自动提现开关和记录
|
||||
- 后端:
|
||||
- 新增 `/api/withdrawal/auto-config` 接口(配置)
|
||||
- 新增 `/api/withdrawal/records` 接口(记录)
|
||||
- 新增定时任务(每日检查并执行提现)
|
||||
- 微信支付:使用企业付款到零钱API
|
||||
|
||||
---
|
||||
|
||||
### 4.4 合伙人档案/认证
|
||||
|
||||
**用户场景**:用户想找靠谱的合伙人,需要看到对方的详细背景和资质认证。
|
||||
|
||||
**档案字段**:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| 基础信息 | 必填 | 昵称、头像、一句话介绍 |
|
||||
| 行业领域 | 必填 | 电商/内容/传统行业/技术/其他 |
|
||||
| 所在城市 | 必填 | 省市选择 |
|
||||
| 资源标签 | 多选 | 流量/供应链/资金/技术/人脉 |
|
||||
| 需求标签 | 多选 | 找流量/找供应链/找资金/找技术/找合伙人 |
|
||||
| 详细介绍 | 选填 | 200字以内 |
|
||||
| 联系方式 | 认证后可见 | 微信号 |
|
||||
|
||||
**认证体系**:
|
||||
| 认证等级 | 条件 | 权益 |
|
||||
|:---|:---|:---|
|
||||
| 未认证 | 默认 | 只能看基础信息 |
|
||||
| 手机认证 | 绑定手机号 | 可查看他人手机认证信息 |
|
||||
| 实名认证 | 提交身份证 | 显示"已实名"标识,可查看微信号 |
|
||||
| 付费认证 | 购买全书 | 显示"书友"标识,优先匹配 |
|
||||
|
||||
**交互设计**:
|
||||
```
|
||||
匹配页 → 合伙人列表(卡片式)
|
||||
↓
|
||||
点击查看档案
|
||||
↓
|
||||
档案详情页
|
||||
↓
|
||||
未认证提示 → 去认证 → 认证流程
|
||||
```
|
||||
|
||||
**技术方案**:
|
||||
- 前端:
|
||||
- 改造 `/pages/match/match` 为合伙人列表
|
||||
- 新增 `/pages/partner-profile/partner-profile` 档案详情页
|
||||
- 新增 `/pages/partner-edit/partner-edit` 档案编辑页
|
||||
- 后端:
|
||||
- 新增 `/api/partner/list` 接口(列表+筛选)
|
||||
- 新增 `/api/partner/profile` 接口(详情)
|
||||
- 新增 `/api/partner/update` 接口(更新档案)
|
||||
- 新增 `/api/partner/verify` 接口(认证)
|
||||
- 数据库:新增 `partner_profile` 表
|
||||
|
||||
---
|
||||
|
||||
### 4.5 交互优化
|
||||
|
||||
#### 4.5.1 登录流程统一
|
||||
|
||||
**当前问题**:
|
||||
- 首页、阅读页、我的页面各自实现登录弹窗
|
||||
- 代码重复,样式不一致
|
||||
|
||||
**改造方案**:
|
||||
```
|
||||
1. 新建全局登录组件 /components/global-login/
|
||||
2. app.js 增加全局登录方法 app.showLogin()
|
||||
3. 各页面调用 app.showLogin() 即可
|
||||
4. 登录成功后通过事件广播通知各页面刷新
|
||||
```
|
||||
|
||||
#### 4.5.2 支付流程简化
|
||||
|
||||
**当前问题**:
|
||||
- 阅读页 `read.js` 支付相关代码300+行
|
||||
- 真实支付和模拟支付逻辑混杂
|
||||
|
||||
**改造方案**:
|
||||
```
|
||||
1. 新建支付工具 /utils/payment.js(已有,需重构)
|
||||
2. 封装统一支付方法:
|
||||
- payment.paySection(sectionId) 购买章节
|
||||
- payment.payFullBook() 购买全书
|
||||
3. 内部处理所有逻辑(创建订单、调起支付、更新状态)
|
||||
4. 各页面只需一行调用
|
||||
```
|
||||
|
||||
#### 4.5.3 导航结构优化
|
||||
|
||||
**当前TabBar**:首页 | 匹配 | 我的(3个)
|
||||
|
||||
**优化后TabBar**:首页 | 目录 | 搜索 | 合伙人 | 我的(5个)
|
||||
|
||||
**页面层级规则**:
|
||||
| 类型 | 页面 | 跳转方式 |
|
||||
|:---|:---|:---|
|
||||
| TabBar页 | 首页、目录、搜索、合伙人、我的 | `wx.switchTab` |
|
||||
| 二级页 | 阅读、档案详情、推广中心、设置 | `wx.navigateTo` |
|
||||
| 弹窗 | 登录、支付确认、分享 | 组件弹窗 |
|
||||
|
||||
#### 4.5.4 数据实时同步
|
||||
|
||||
**改造方案**:
|
||||
```
|
||||
1. 使用全局状态管理(基于 app.globalData + 事件机制)
|
||||
2. 关键操作后广播事件:
|
||||
- 登录成功 → 'login-success'
|
||||
- 购买成功 → 'purchase-success'
|
||||
- 进度更新 → 'progress-update'
|
||||
3. 各页面监听事件,自动刷新UI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、技术约束
|
||||
|
||||
| 维度 | 约束 |
|
||||
|:---|:---|
|
||||
| **小程序框架** | 微信原生小程序(不使用uni-app/Taro) |
|
||||
| **后端框架** | Next.js API Routes |
|
||||
| **数据库** | MySQL(已有实例) |
|
||||
| **部署环境** | 宝塔面板 + GitHub Webhook自动部署 |
|
||||
| **微信支付** | 已对接,企业付款需单独申请 |
|
||||
|
||||
---
|
||||
|
||||
## 六、异常处理规则
|
||||
|
||||
| 异常场景 | 处理方式 |
|
||||
|:---|:---|
|
||||
| 搜索无结果 | 显示"暂无相关内容",推荐热门章节 |
|
||||
| 进度同步失败 | 本地缓存,下次启动重试 |
|
||||
| 自动提现失败 | 记录日志,标记待人工处理,通知用户 |
|
||||
| 合伙人认证失败 | 显示失败原因,引导重新提交 |
|
||||
| 网络异常 | 统一显示"网络异常,请重试",支持重试 |
|
||||
| 登录态过期 | 自动刷新token,失败则弹出登录框 |
|
||||
|
||||
---
|
||||
|
||||
## 七、测试用例清单
|
||||
|
||||
### 7.1 全站搜索
|
||||
|
||||
| 用例ID | 场景 | 输入 | 期望输出 |
|
||||
|:---|:---|:---|:---|
|
||||
| S01 | 搜索章节标题 | "电动车" | 返回1.1章节 |
|
||||
| S02 | 搜索章节内容 | "私域银行" | 返回包含该词的章节列表 |
|
||||
| S03 | 搜索合伙人 | "电商" | 返回行业为电商的合伙人 |
|
||||
| S04 | 空关键词搜索 | "" | 显示热门推荐 |
|
||||
| S05 | 无结果搜索 | "xyz123" | 显示"暂无相关内容" |
|
||||
|
||||
### 7.2 阅读进度同步
|
||||
|
||||
| 用例ID | 场景 | 操作 | 期望结果 |
|
||||
|:---|:---|:---|:---|
|
||||
| R01 | 进度上报 | 阅读到50%位置 | 服务端记录scroll_percent=50 |
|
||||
| R02 | 进度恢复 | 重新打开章节 | 自动滚动到50%位置 |
|
||||
| R03 | 跨设备同步 | 手机读到3章,电脑打开 | 电脑显示"继续阅读第3章?" |
|
||||
| R04 | 网络断开 | 离线阅读 | 本地缓存进度,联网后同步 |
|
||||
|
||||
### 7.3 自动提现
|
||||
|
||||
| 用例ID | 场景 | 条件 | 期望结果 |
|
||||
|:---|:---|:---|:---|
|
||||
| W01 | 达到阈值 | 余额=100元 | 凌晨自动提现到微信 |
|
||||
| W02 | 未达阈值 | 余额=50元 | 不触发提现 |
|
||||
| W03 | 提现失败 | 微信接口异常 | 记录失败日志,通知管理员 |
|
||||
| W04 | 关闭自动提现 | 用户手动关闭 | 不触发自动提现 |
|
||||
|
||||
### 7.4 合伙人档案
|
||||
|
||||
| 用例ID | 场景 | 操作 | 期望结果 |
|
||||
|:---|:---|:---|:---|
|
||||
| P01 | 创建档案 | 填写必填字段并保存 | 档案创建成功,显示在列表 |
|
||||
| P02 | 查看档案 | 点击合伙人卡片 | 跳转档案详情页 |
|
||||
| P03 | 未认证查看 | 未认证用户查看他人微信 | 提示"认证后可查看" |
|
||||
| P04 | 筛选合伙人 | 选择"电商"+"找流量" | 只显示符合条件的合伙人 |
|
||||
|
||||
### 7.5 交互优化
|
||||
|
||||
| 用例ID | 场景 | 操作 | 期望结果 |
|
||||
|:---|:---|:---|:---|
|
||||
| U01 | 全局登录 | 任意页面点击需登录功能 | 弹出统一登录框 |
|
||||
| U02 | 登录后刷新 | 登录成功 | 当前页面自动刷新用户状态 |
|
||||
| U03 | 购买后更新 | 购买章节成功 | 立即显示"已购买",无需刷新 |
|
||||
| U04 | TabBar切换 | 点击目录Tab | 切换到目录页,选中状态正确 |
|
||||
|
||||
---
|
||||
|
||||
## 八、验收标准
|
||||
|
||||
| 验收项 | 标准 | 验收人 |
|
||||
|:---|:---|:---|
|
||||
| 功能完整 | 全部P0功能可用,P1功能90%可用 | 卡若 |
|
||||
| 交互流畅 | 页面切换无卡顿,操作响应<500ms | 卡若 |
|
||||
| 数据准确 | 进度同步准确,提现金额正确 | 卡若 |
|
||||
| 无严重Bug | 不影响核心流程的使用 | 卡若 |
|
||||
| 代码规范 | 符合现有项目规范,有必要注释 | 卡若 |
|
||||
|
||||
---
|
||||
|
||||
## 九、开发任务优先级
|
||||
|
||||
### 第一阶段:交互优化(P0)
|
||||
|
||||
| 序号 | 任务 | 预估工作量 |
|
||||
|:---|:---|:---|
|
||||
| 1 | 全局登录组件封装 | 0.5天 |
|
||||
| 2 | 支付模块重构 | 1天 |
|
||||
| 3 | 数据实时同步机制 | 0.5天 |
|
||||
| 4 | 阅读进度同步(前后端) | 1天 |
|
||||
|
||||
### 第二阶段:功能增强(P0-P1)
|
||||
|
||||
| 序号 | 任务 | 预估工作量 |
|
||||
|:---|:---|:---|
|
||||
| 5 | 全站搜索(前后端) | 1.5天 |
|
||||
| 6 | 导航结构调整(5个Tab) | 1天 |
|
||||
| 7 | 合伙人档案系统(前后端) | 2天 |
|
||||
| 8 | 自动提现功能(前后端) | 1.5天 |
|
||||
|
||||
### 第三阶段:测试与优化
|
||||
|
||||
| 序号 | 任务 | 预估工作量 |
|
||||
|:---|:---|:---|
|
||||
| 9 | 全流程测试 | 1天 |
|
||||
| 10 | Bug修复与优化 | 1天 |
|
||||
|
||||
**总计预估**:约11天
|
||||
|
||||
---
|
||||
|
||||
## 十、下一步行动
|
||||
|
||||
1. **确认本方案**:如有调整请反馈
|
||||
2. **创建开发分支**:`feature/miniprogram-v2`
|
||||
3. **按优先级开发**:先完成第一阶段交互优化
|
||||
4. **每日更新进度**:更新到项目推进表
|
||||
|
||||
---
|
||||
|
||||
**方案版本**:v1.0
|
||||
**创建时间**:2026-01-23
|
||||
**下次评审**:开发完成后
|
||||
Reference in New Issue
Block a user