diff --git a/app/api/admin/logout/route.ts b/app/api/admin/logout/route.ts
new file mode 100644
index 00000000..9d287345
--- /dev/null
+++ b/app/api/admin/logout/route.ts
@@ -0,0 +1,9 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { getAdminCookieName, getAdminCookieOptions } from '@/lib/admin-auth'
+
+export async function POST(_req: NextRequest) {
+ const res = NextResponse.json({ success: true })
+ const opts = getAdminCookieOptions()
+ res.cookies.set(getAdminCookieName(), '', { ...opts, maxAge: 0 })
+ return res
+}
diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts
new file mode 100644
index 00000000..0335b177
--- /dev/null
+++ b/app/api/auth/login/route.ts
@@ -0,0 +1,72 @@
+/**
+ * Web 端登录:手机号 + 密码
+ * POST { phone, password } -> 校验后返回用户信息(不含密码)
+ */
+
+import { NextRequest, NextResponse } from 'next/server'
+import { query } from '@/lib/db'
+import { verifyPassword } from '@/lib/password'
+
+function mapRowToUser(r: any) {
+ return {
+ id: r.id,
+ phone: r.phone || '',
+ nickname: r.nickname || '',
+ isAdmin: !!r.is_admin,
+ purchasedSections: Array.isArray(r.purchased_sections)
+ ? r.purchased_sections
+ : (r.purchased_sections ? JSON.parse(String(r.purchased_sections)) : []) || [],
+ hasFullBook: !!r.has_full_book,
+ referralCode: r.referral_code || '',
+ earnings: parseFloat(String(r.earnings || 0)),
+ pendingEarnings: parseFloat(String(r.pending_earnings || 0)),
+ withdrawnEarnings: parseFloat(String(r.withdrawn_earnings || 0)),
+ referralCount: Number(r.referral_count) || 0,
+ createdAt: r.created_at || '',
+ }
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+ const { phone, password } = body
+
+ if (!phone || !password) {
+ return NextResponse.json(
+ { success: false, error: '请输入手机号和密码' },
+ { status: 400 }
+ )
+ }
+
+ const rows = await query(
+ 'SELECT id, phone, nickname, password, is_admin, has_full_book, referral_code, earnings, pending_earnings, withdrawn_earnings, referral_count, purchased_sections, created_at FROM users WHERE phone = ?',
+ [String(phone).trim()]
+ ) as any[]
+
+ if (!rows || rows.length === 0) {
+ return NextResponse.json(
+ { success: false, error: '用户不存在或密码错误' },
+ { status: 401 }
+ )
+ }
+
+ const row = rows[0]
+ const storedPassword = row.password == null ? '' : String(row.password)
+
+ if (!verifyPassword(String(password), storedPassword)) {
+ return NextResponse.json(
+ { success: false, error: '密码错误' },
+ { status: 401 }
+ )
+ }
+
+ const user = mapRowToUser(row)
+ return NextResponse.json({ success: true, user })
+ } catch (e) {
+ console.error('[Auth Login] error:', e)
+ return NextResponse.json(
+ { success: false, error: '登录失败' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/auth/reset-password/route.ts b/app/api/auth/reset-password/route.ts
new file mode 100644
index 00000000..3ba84902
--- /dev/null
+++ b/app/api/auth/reset-password/route.ts
@@ -0,0 +1,54 @@
+/**
+ * 忘记密码 / 重置密码(Web 端)
+ * POST { phone, newPassword } -> 按手机号更新密码(无验证码版本,适合内测/内部使用)
+ */
+
+import { NextRequest, NextResponse } from 'next/server'
+import { query } from '@/lib/db'
+import { hashPassword } from '@/lib/password'
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+ const { phone, newPassword } = body
+
+ if (!phone || !newPassword) {
+ return NextResponse.json(
+ { success: false, error: '请输入手机号和新密码' },
+ { status: 400 }
+ )
+ }
+
+ const trimmedPhone = String(phone).trim()
+ const trimmedPassword = String(newPassword).trim()
+
+ if (trimmedPassword.length < 6) {
+ return NextResponse.json(
+ { success: false, error: '密码至少 6 位' },
+ { status: 400 }
+ )
+ }
+
+ const rows = await query('SELECT id FROM users WHERE phone = ?', [trimmedPhone]) as any[]
+ if (!rows || rows.length === 0) {
+ return NextResponse.json(
+ { success: false, error: '该手机号未注册' },
+ { status: 404 }
+ )
+ }
+
+ const hashed = hashPassword(trimmedPassword)
+ await query('UPDATE users SET password = ?, updated_at = NOW() WHERE phone = ?', [
+ hashed,
+ trimmedPhone,
+ ])
+
+ return NextResponse.json({ success: true, message: '密码已重置,请使用新密码登录' })
+ } catch (e) {
+ console.error('[Auth ResetPassword] error:', e)
+ return NextResponse.json(
+ { success: false, error: '重置失败' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/content/upload/route.ts b/app/api/content/upload/route.ts
new file mode 100644
index 00000000..1ad32433
--- /dev/null
+++ b/app/api/content/upload/route.ts
@@ -0,0 +1,97 @@
+/**
+ * 内容上传 API
+ * 供科室/Skill 直接上传单篇文章到书籍内容,写入 chapters 表
+ * 字段:标题、定价、内容、格式、插入内容中的图片(URL 列表)
+ */
+
+import { NextRequest, NextResponse } from 'next/server'
+import { query } from '@/lib/db'
+
+function slug(id: string): string {
+ return id.replace(/\s+/g, '-').replace(/[^\w\u4e00-\u9fa5-]/g, '').slice(0, 30) || 'section'
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+ const {
+ title,
+ price = 1,
+ content = '',
+ format = 'markdown',
+ images = [],
+ partId = 'part-1',
+ partTitle = '真实的人',
+ chapterId = 'chapter-1',
+ chapterTitle = '未分类',
+ isFree = false,
+ sectionId
+ } = body
+
+ if (!title || typeof title !== 'string') {
+ return NextResponse.json(
+ { success: false, error: '标题 title 不能为空' },
+ { status: 400 }
+ )
+ }
+
+ // 若内容中含占位符 {{image_0}} {{image_1}},用 images 数组替换
+ let finalContent = typeof content === 'string' ? content : ''
+ if (Array.isArray(images) && images.length > 0) {
+ images.forEach((url: string, i: number) => {
+ finalContent = finalContent.replace(
+ new RegExp(`\\{\\{image_${i}\\}\\}`, 'g'),
+ url.startsWith('http') ? `` : url
+ )
+ })
+ }
+ // 未替换的占位符去掉
+ finalContent = finalContent.replace(/\{\{image_\d+\}\}/g, '')
+
+ const wordCount = (finalContent || '').length
+ const id = sectionId || `upload.${slug(title)}.${Date.now()}`
+
+ await query(
+ `INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, sort_order, status)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 9999, 'published')
+ ON DUPLICATE KEY UPDATE
+ section_title = VALUES(section_title),
+ content = VALUES(content),
+ word_count = VALUES(word_count),
+ is_free = VALUES(is_free),
+ price = VALUES(price),
+ updated_at = CURRENT_TIMESTAMP`,
+ [
+ id,
+ partId,
+ partTitle,
+ chapterId,
+ chapterTitle,
+ title,
+ finalContent,
+ wordCount,
+ !!isFree,
+ Number(price) || 1
+ ]
+ )
+
+ return NextResponse.json({
+ success: true,
+ id,
+ message: '内容已上传并写入 chapters 表',
+ title,
+ price: Number(price) || 1,
+ isFree: !!isFree,
+ wordCount
+ })
+ } catch (error) {
+ console.error('[Content Upload]', error)
+ return NextResponse.json(
+ {
+ success: false,
+ error: '上传失败: ' + (error as Error).message
+ },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/vip/members/route.ts b/app/api/vip/members/route.ts
new file mode 100644
index 00000000..370fd98c
--- /dev/null
+++ b/app/api/vip/members/route.ts
@@ -0,0 +1,67 @@
+/**
+ * VIP会员列表 - 用于「创业老板排行」展示
+ */
+import { NextRequest, NextResponse } from 'next/server'
+import { query } from '@/lib/db'
+
+export async function GET(request: NextRequest) {
+ const limit = parseInt(new URL(request.url).searchParams.get('limit') || '20')
+ const memberId = new URL(request.url).searchParams.get('id')
+
+ try {
+ // 查询单个会员详情
+ if (memberId) {
+ const rows = await query(
+ `SELECT id, nickname, avatar, vip_name, vip_project, vip_contact, vip_avatar, vip_bio,
+ is_vip, vip_expire_date, created_at
+ FROM users WHERE id = ? AND is_vip = TRUE AND vip_expire_date > NOW()`,
+ [memberId]
+ ) as any[]
+
+ if (!rows.length) {
+ return NextResponse.json({ success: false, error: '会员不存在或已过期' }, { status: 404 })
+ }
+
+ const m = rows[0]
+ return NextResponse.json({
+ success: true,
+ data: {
+ id: m.id,
+ name: m.vip_name || m.nickname || '创业者',
+ avatar: m.vip_avatar || m.avatar || '',
+ project: m.vip_project || '',
+ contact: m.vip_contact || '',
+ bio: m.vip_bio || '',
+ joinDate: m.created_at
+ }
+ })
+ }
+
+ // 获取VIP会员列表(已填写资料的优先排前面)
+ const members = await query(
+ `SELECT id, nickname, avatar, vip_name, vip_project, vip_avatar, vip_bio
+ FROM users
+ WHERE is_vip = TRUE AND vip_expire_date > NOW()
+ ORDER BY
+ CASE WHEN vip_name IS NOT NULL AND vip_name != '' THEN 0 ELSE 1 END,
+ vip_expire_date DESC
+ LIMIT ?`,
+ [limit]
+ ) as any[]
+
+ return NextResponse.json({
+ success: true,
+ data: members.map((m: any) => ({
+ id: m.id,
+ name: m.vip_name || m.nickname || '创业者',
+ avatar: m.vip_avatar || m.avatar || '',
+ project: m.vip_project || '',
+ bio: m.vip_bio || ''
+ })),
+ total: members.length
+ })
+ } catch (error) {
+ console.error('[VIP Members]', error)
+ return NextResponse.json({ success: false, error: '查询失败', data: [], total: 0 })
+ }
+}
diff --git a/app/api/vip/profile/route.ts b/app/api/vip/profile/route.ts
new file mode 100644
index 00000000..1cd0df34
--- /dev/null
+++ b/app/api/vip/profile/route.ts
@@ -0,0 +1,77 @@
+/**
+ * VIP会员资料填写/更新
+ */
+import { NextRequest, NextResponse } from 'next/server'
+import { query } from '@/lib/db'
+
+export async function POST(request: NextRequest) {
+ try {
+ const { userId, name, project, contact, avatar, bio } = await request.json()
+ if (!userId) {
+ return NextResponse.json({ success: false, error: '缺少userId' }, { status: 400 })
+ }
+
+ const users = await query('SELECT is_vip, vip_expire_date FROM users WHERE id = ?', [userId]) as any[]
+ if (!users.length) {
+ return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 })
+ }
+
+ const user = users[0]
+ if (!user.is_vip || !user.vip_expire_date || new Date(user.vip_expire_date) <= new Date()) {
+ return NextResponse.json({ success: false, error: '仅VIP会员可填写资料' }, { status: 403 })
+ }
+
+ const updates: string[] = []
+ const params: any[] = []
+
+ if (name !== undefined) { updates.push('vip_name = ?'); params.push(name) }
+ if (project !== undefined) { updates.push('vip_project = ?'); params.push(project) }
+ if (contact !== undefined) { updates.push('vip_contact = ?'); params.push(contact) }
+ if (avatar !== undefined) { updates.push('vip_avatar = ?'); params.push(avatar) }
+ if (bio !== undefined) { updates.push('vip_bio = ?'); params.push(bio) }
+
+ if (!updates.length) {
+ return NextResponse.json({ success: false, error: '无更新内容' }, { status: 400 })
+ }
+
+ params.push(userId)
+ await query(`UPDATE users SET ${updates.join(', ')} WHERE id = ?`, params)
+
+ return NextResponse.json({ success: true, message: '资料已更新' })
+ } catch (error) {
+ console.error('[VIP Profile]', error)
+ return NextResponse.json({ success: false, error: '更新失败' }, { status: 500 })
+ }
+}
+
+export async function GET(request: NextRequest) {
+ const userId = new URL(request.url).searchParams.get('userId')
+ if (!userId) {
+ return NextResponse.json({ success: false, error: '缺少userId' }, { status: 400 })
+ }
+
+ try {
+ const rows = await query(
+ 'SELECT vip_name, vip_project, vip_contact, vip_avatar, vip_bio FROM users WHERE id = ?',
+ [userId]
+ ) as any[]
+
+ if (!rows.length) {
+ return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 })
+ }
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ name: rows[0].vip_name || '',
+ project: rows[0].vip_project || '',
+ contact: rows[0].vip_contact || '',
+ avatar: rows[0].vip_avatar || '',
+ bio: rows[0].vip_bio || ''
+ }
+ })
+ } catch (error) {
+ console.error('[VIP Profile GET]', error)
+ return NextResponse.json({ success: false, error: '查询失败' }, { status: 500 })
+ }
+}
diff --git a/app/api/vip/purchase/route.ts b/app/api/vip/purchase/route.ts
new file mode 100644
index 00000000..f421e995
--- /dev/null
+++ b/app/api/vip/purchase/route.ts
@@ -0,0 +1,57 @@
+/**
+ * VIP会员购买 - 创建VIP订单
+ */
+import { NextRequest, NextResponse } from 'next/server'
+import { query, getConfig } from '@/lib/db'
+
+export async function POST(request: NextRequest) {
+ try {
+ const { userId } = await request.json()
+ if (!userId) {
+ return NextResponse.json({ success: false, error: '缺少userId' }, { status: 400 })
+ }
+
+ const users = await query(
+ 'SELECT id, open_id, is_vip, vip_expire_date FROM users WHERE id = ?',
+ [userId]
+ ) as any[]
+ if (!users.length) {
+ return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 })
+ }
+ const user = users[0]
+
+ // 如果已经是VIP且未过期
+ if (user.is_vip && user.vip_expire_date && new Date(user.vip_expire_date) > new Date()) {
+ return NextResponse.json({ success: false, error: '当前已是VIP会员' }, { status: 400 })
+ }
+
+ let vipPrice = 1980
+ try {
+ const config = await getConfig('vip_price')
+ if (config) vipPrice = Number(config) || 1980
+ } catch { /* 默认 */ }
+
+ const orderId = 'vip_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 6)
+ const orderSn = 'VIP' + Date.now() + Math.floor(Math.random() * 1000)
+
+ await query(
+ `INSERT INTO orders (id, order_sn, user_id, open_id, product_type, amount, description, status)
+ VALUES (?, ?, ?, ?, 'vip', ?, 'VIP年度会员', 'created')`,
+ [orderId, orderSn, userId, user.open_id || '', vipPrice]
+ )
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ orderId,
+ orderSn,
+ amount: vipPrice,
+ productType: 'vip',
+ description: 'VIP年度会员(365天)'
+ }
+ })
+ } catch (error) {
+ console.error('[VIP Purchase]', error)
+ return NextResponse.json({ success: false, error: '创建订单失败' }, { status: 500 })
+ }
+}
diff --git a/app/api/vip/status/route.ts b/app/api/vip/status/route.ts
new file mode 100644
index 00000000..7e7a3357
--- /dev/null
+++ b/app/api/vip/status/route.ts
@@ -0,0 +1,73 @@
+/**
+ * VIP会员状态查询
+ */
+import { NextRequest, NextResponse } from 'next/server'
+import { query, getConfig } from '@/lib/db'
+
+export async function GET(request: NextRequest) {
+ const userId = new URL(request.url).searchParams.get('userId')
+ if (!userId) {
+ return NextResponse.json({ success: false, error: '缺少userId' }, { status: 400 })
+ }
+
+ try {
+ const rows = await query(
+ `SELECT is_vip, vip_expire_date, vip_name, vip_project, vip_contact, vip_avatar, vip_bio,
+ has_full_book, nickname, avatar
+ FROM users WHERE id = ?`,
+ [userId]
+ ) as any[]
+
+ if (!rows.length) {
+ return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 })
+ }
+
+ const user = rows[0]
+ const now = new Date()
+ const isVip = user.is_vip && user.vip_expire_date && new Date(user.vip_expire_date) > now
+
+ // 若过期则自动标记
+ if (user.is_vip && !isVip) {
+ await query('UPDATE users SET is_vip = FALSE WHERE id = ?', [userId]).catch(() => {})
+ }
+
+ let vipPrice = 1980
+ let vipRights: string[] = []
+ try {
+ const priceConfig = await getConfig('vip_price')
+ if (priceConfig) vipPrice = Number(priceConfig) || 1980
+ const rightsConfig = await getConfig('vip_rights')
+ if (rightsConfig) vipRights = Array.isArray(rightsConfig) ? rightsConfig : JSON.parse(rightsConfig)
+ } catch { /* 使用默认 */ }
+
+ if (!vipRights.length) {
+ vipRights = [
+ '解锁全部章节内容(365天)',
+ '匹配所有创业伙伴',
+ '创业老板排行榜展示',
+ '专属VIP标识'
+ ]
+ }
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ isVip,
+ expireDate: user.vip_expire_date,
+ daysRemaining: isVip ? Math.ceil((new Date(user.vip_expire_date).getTime() - now.getTime()) / 86400000) : 0,
+ profile: {
+ name: user.vip_name || '',
+ project: user.vip_project || '',
+ contact: user.vip_contact || '',
+ avatar: user.vip_avatar || user.avatar || '',
+ bio: user.vip_bio || ''
+ },
+ price: vipPrice,
+ rights: vipRights
+ }
+ })
+ } catch (error) {
+ console.error('[VIP Status]', error)
+ return NextResponse.json({ success: false, error: '查询失败' }, { status: 500 })
+ }
+}
diff --git a/content-manager.html b/content-manager.html
new file mode 100644
index 00000000..abc916cc
--- /dev/null
+++ b/content-manager.html
@@ -0,0 +1,519 @@
+
+
+
+
+
+内容管理 - Soul创业派对
+
+
+
+
+
+
+
+
章节管理
+
上传内容
+
API 接口文档
+
+
+
+
+
+
+
+
+
上传新章节
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
内容管理 API 接口文档
+
基础域名:https://soulapi.quwanzhi.com(正式)/ https://souldev.quwanzhi.com(开发)
+
+
1. 获取所有章节
+
GET /api/book/all-chapters
+
+# 无需认证,返回全部章节
+curl https://soulapi.quwanzhi.com/api/book/all-chapters
+
响应:{"success": true, "data": [{"id":"1.1", "sectionTitle":"...", "isFree":true, "price":0, ...}]}
+
+
2. 获取单章内容
+
GET /api/book/chapter/:id
+
+curl https://soulapi.quwanzhi.com/api/book/chapter/1.1
+
响应:{"success": true, "data": {"id":"1.1", "content":"# 正文...", ...}}
+
+
3. 管理员登录(获取Token)
+
POST /api/admin
+Content-Type: application/json
+
+{"username": "admin", "password": "admin123"}
+
+# 响应包含 token,后续请求需带 Authorization: Bearer {token}
+
+
4. 章节列表(管理员)
+
GET /api/db/book?action=list
+Authorization: Bearer {token}
+
+# 返回所有章节的元数据(不含正文)
+
+
5. 读取章节内容(管理员)
+
GET /api/db/book?action=read&id={section_id}
+Authorization: Bearer {token}
+
+
6. 创建/更新章节(管理员)
+
POST /api/db/book
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "id": "1.6", // 章节ID,不传则自动生成
+ "title": "章节标题",
+ "content": "Markdown正文",
+ "price": 1.0, // 定价,0=免费
+ "partId": "part-1", // 所属篇
+ "chapterId": "chapter-1" // 所属章
+}
+
+
7. 上传内容(数据库直写)
+
支持从 Cursor Skill / 命令行 直接写入数据库:
+
# 命令行方式
+python3 content_upload.py \
+ --title "标题" \
+ --price 1.0 \
+ --content "正文内容" \
+ --part part-1 \
+ --chapter chapter-1 \
+ --format markdown
+
+# JSON方式
+python3 content_upload.py --json '{
+ "title": "标题",
+ "price": 1.0,
+ "content": "正文...",
+ "part_id": "part-1",
+ "chapter_id": "chapter-1",
+ "images": ["https://img.com/1.png"]
+}'
+
+# 查看篇章结构
+python3 content_upload.py --list-structure
+
+# 列出所有章节
+python3 content_upload.py --list-chapters
+
+
8. 删除章节
+
DELETE /api/admin/content/:id
+Authorization: Bearer {token}
+
+curl -X DELETE https://soulapi.quwanzhi.com/api/admin/content/1.6 \
+ -H "Authorization: Bearer {token}"
+
+
9. 数据库连接信息
+
# 如需直连数据库
+Host: 56b4c23f6853c.gz.cdb.myqcloud.com
+Port: 14413
+User: cdb_outerroot
+DB: soul_miniprogram
+表: chapters (mid自增主键, id章节号唯一索引)
+
+
+
+
+
+
+
+
编辑章节
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/content_upload.py b/content_upload.py
new file mode 100644
index 00000000..e14f20c2
--- /dev/null
+++ b/content_upload.py
@@ -0,0 +1,275 @@
+#!/usr/bin/env python3
+"""
+Soul 内容上传接口
+可从 Cursor Skill / 命令行直接调用,将新内容写入数据库
+
+用法:
+ python3 content_upload.py --title "标题" --price 1.0 --content "正文" \
+ --part part-1 --chapter chapter-1 --format markdown
+
+ python3 content_upload.py --json '{
+ "title": "标题",
+ "price": 1.0,
+ "content": "正文内容...",
+ "part_id": "part-1",
+ "chapter_id": "chapter-1",
+ "format": "markdown",
+ "images": ["https://xxx.com/img1.png"]
+ }'
+
+ python3 content_upload.py --list-structure # 查看篇章结构
+
+环境依赖: pip install pymysql
+"""
+
+import argparse
+import json
+import sys
+import re
+from datetime import datetime
+
+try:
+ import pymysql
+except ImportError:
+ print("需要安装 pymysql: pip3 install pymysql")
+ sys.exit(1)
+
+DB_CONFIG = {
+ "host": "56b4c23f6853c.gz.cdb.myqcloud.com",
+ "port": 14413,
+ "user": "cdb_outerroot",
+ "password": "Zhiqun1984",
+ "database": "soul_miniprogram",
+ "charset": "utf8mb4",
+}
+
+PART_MAP = {
+ "part-1": "第一篇|真实的人",
+ "part-2": "第二篇|真实的行业",
+ "part-3": "第三篇|真实的错误",
+ "part-4": "第四篇|真实的赚钱",
+ "part-5": "第五篇|真实的社会",
+ "appendix": "附录",
+ "intro": "序言",
+ "outro": "尾声",
+}
+
+CHAPTER_MAP = {
+ "chapter-1": "第1章|人与人之间的底层逻辑",
+ "chapter-2": "第2章|人性困境案例",
+ "chapter-3": "第3章|电商篇",
+ "chapter-4": "第4章|内容商业篇",
+ "chapter-5": "第5章|传统行业篇",
+ "chapter-6": "第6章|我人生错过的4件大钱",
+ "chapter-7": "第7章|别人犯的错误",
+ "chapter-8": "第8章|底层结构",
+ "chapter-9": "第9章|我在Soul上亲访的赚钱案例",
+ "chapter-10": "第10章|未来职业的变化趋势",
+ "chapter-11": "第11章|中国社会商业生态的未来",
+ "appendix": "附录",
+ "preface": "序言",
+ "epilogue": "尾声",
+}
+
+
+def get_connection():
+ return pymysql.connect(**DB_CONFIG)
+
+
+def list_structure():
+ conn = get_connection()
+ cur = conn.cursor()
+ cur.execute("""
+ SELECT part_id, part_title, chapter_id, chapter_title, COUNT(*) as sections
+ FROM chapters
+ GROUP BY part_id, part_title, chapter_id, chapter_title
+ ORDER BY part_id, chapter_id
+ """)
+ rows = cur.fetchall()
+ print("篇章结构:")
+ for part_id, part_title, ch_id, ch_title, cnt in rows:
+ print(f" {part_id} ({part_title}) / {ch_id} ({ch_title}) - {cnt}节")
+
+ cur.execute("SELECT COUNT(*) FROM chapters")
+ total = cur.fetchone()[0]
+ print(f"\n总计: {total} 节")
+ conn.close()
+
+
+def generate_section_id(cur, chapter_id):
+ """根据 chapter 编号自动生成下一个 section id"""
+ ch_num = re.search(r"\d+", chapter_id)
+ if not ch_num:
+ cur.execute("SELECT MAX(CAST(REPLACE(id, '.', '') AS UNSIGNED)) FROM chapters")
+ max_id = cur.fetchone()[0] or 0
+ return str(max_id + 1)
+
+ prefix = ch_num.group()
+ cur.execute(
+ "SELECT id FROM chapters WHERE id LIKE %s ORDER BY CAST(SUBSTRING_INDEX(id, '.', -1) AS UNSIGNED) DESC LIMIT 1",
+ (f"{prefix}.%",),
+ )
+ row = cur.fetchone()
+ if row:
+ last_num = int(row[0].split(".")[-1])
+ return f"{prefix}.{last_num + 1}"
+ return f"{prefix}.1"
+
+
+def upload_content(data):
+ title = data.get("title", "").strip()
+ if not title:
+ print("错误: 标题不能为空")
+ return False
+
+ content = data.get("content", "").strip()
+ if not content:
+ print("错误: 内容不能为空")
+ return False
+
+ price = float(data.get("price", 1.0))
+ is_free = 1 if price == 0 else 0
+ part_id = data.get("part_id", "part-1")
+ chapter_id = data.get("chapter_id", "chapter-1")
+ fmt = data.get("format", "markdown")
+ images = data.get("images", [])
+ section_id = data.get("id", "")
+
+ if images:
+ for i, img_url in enumerate(images):
+ placeholder = f"{{{{image_{i+1}}}}}"
+ if placeholder in content:
+ if fmt == "markdown":
+ content = content.replace(placeholder, f"")
+ else:
+ content = content.replace(placeholder, img_url)
+
+ word_count = len(re.sub(r"\s+", "", content))
+
+ part_title = PART_MAP.get(part_id, part_id)
+ chapter_title = CHAPTER_MAP.get(chapter_id, chapter_id)
+
+ conn = get_connection()
+ cur = conn.cursor()
+
+ if not section_id:
+ section_id = generate_section_id(cur, chapter_id)
+
+ cur.execute("SELECT mid FROM chapters WHERE id = %s", (section_id,))
+ existing = cur.fetchone()
+
+ try:
+ if existing:
+ cur.execute("""
+ UPDATE chapters SET
+ section_title = %s, content = %s, word_count = %s,
+ is_free = %s, price = %s, part_id = %s, part_title = %s,
+ chapter_id = %s, chapter_title = %s, status = 'published'
+ WHERE id = %s
+ """, (title, content, word_count, is_free, price, part_id, part_title,
+ chapter_id, chapter_title, section_id))
+ action = "更新"
+ else:
+ cur.execute("SELECT COALESCE(MAX(sort_order), 0) + 1 FROM chapters")
+ next_order = cur.fetchone()[0]
+
+ cur.execute("""
+ INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title,
+ section_title, content, word_count, is_free, price, sort_order, status)
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'published')
+ """, (section_id, part_id, part_title, chapter_id, chapter_title,
+ title, content, word_count, is_free, price, next_order))
+ action = "创建"
+
+ conn.commit()
+
+ result = {
+ "success": True,
+ "action": action,
+ "data": {
+ "id": section_id,
+ "title": title,
+ "part": f"{part_id} ({part_title})",
+ "chapter": f"{chapter_id} ({chapter_title})",
+ "price": price,
+ "is_free": bool(is_free),
+ "word_count": word_count,
+ "format": fmt,
+ "images_count": len(images),
+ }
+ }
+ print(json.dumps(result, ensure_ascii=False, indent=2))
+ return True
+
+ except pymysql.err.IntegrityError as e:
+ print(json.dumps({"success": False, "error": f"ID冲突: {e}"}, ensure_ascii=False))
+ return False
+ except Exception as e:
+ conn.rollback()
+ print(json.dumps({"success": False, "error": str(e)}, ensure_ascii=False))
+ return False
+ finally:
+ conn.close()
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Soul 内容上传接口")
+ parser.add_argument("--json", help="JSON格式的完整数据")
+ parser.add_argument("--title", help="标题")
+ parser.add_argument("--price", type=float, default=1.0, help="定价(0=免费)")
+ parser.add_argument("--content", help="内容正文")
+ parser.add_argument("--content-file", help="从文件读取内容")
+ parser.add_argument("--format", default="markdown", choices=["markdown", "text", "html"])
+ parser.add_argument("--part", default="part-1", help="所属篇 (part-1 ~ part-5)")
+ parser.add_argument("--chapter", default="chapter-1", help="所属章 (chapter-1 ~ chapter-11)")
+ parser.add_argument("--id", help="指定 section ID (如 1.6),不指定则自动生成")
+ parser.add_argument("--images", nargs="*", help="图片URL列表")
+ parser.add_argument("--list-structure", action="store_true", help="查看篇章结构")
+ parser.add_argument("--list-chapters", action="store_true", help="列出所有章节")
+
+ args = parser.parse_args()
+
+ if args.list_structure:
+ list_structure()
+ return
+
+ if args.list_chapters:
+ conn = get_connection()
+ cur = conn.cursor()
+ cur.execute("SELECT id, section_title, is_free, price FROM chapters ORDER BY sort_order")
+ for row in cur.fetchall():
+ free_tag = "[免费]" if row[2] else f"[¥{row[3]}]"
+ print(f" {row[0]} {row[1]} {free_tag}")
+ conn.close()
+ return
+
+ if args.json:
+ data = json.loads(args.json)
+ else:
+ if not args.title or (not args.content and not args.content_file):
+ parser.print_help()
+ print("\n错误: 需要 --title 和 --content (或 --content-file)")
+ sys.exit(1)
+
+ content = args.content
+ if args.content_file:
+ with open(args.content_file, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ data = {
+ "title": args.title,
+ "price": args.price,
+ "content": content,
+ "format": args.format,
+ "part_id": args.part,
+ "chapter_id": args.chapter,
+ "images": args.images or [],
+ }
+ if args.id:
+ data["id"] = args.id
+
+ upload_content(data)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 00000000..cf56bf05
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,27 @@
+import { NextResponse } from 'next/server'
+import type { NextRequest } from 'next/server'
+
+const ALLOWED_ORIGINS = [
+ 'https://souladmin.quwanzhi.com',
+ 'http://localhost:5174',
+ 'http://127.0.0.1:5174',
+]
+
+export function middleware(request: NextRequest) {
+ const origin = request.headers.get('origin')
+ const res = NextResponse.next()
+ if (origin && ALLOWED_ORIGINS.includes(origin)) {
+ res.headers.set('Access-Control-Allow-Origin', origin)
+ }
+ res.headers.set('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
+ res.headers.set('Access-Control-Allow-Headers', 'Content-Type,Authorization')
+ res.headers.set('Access-Control-Allow-Credentials', 'true')
+ if (request.method === 'OPTIONS') {
+ return new NextResponse(null, { status: 204, headers: res.headers })
+ }
+ return res
+}
+
+export const config = {
+ matcher: '/api/:path*',
+}
diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss
index ec316d47..c8ba095b 100644
--- a/miniprogram/pages/index/index.wxss
+++ b/miniprogram/pages/index/index.wxss
@@ -498,6 +498,80 @@
color: rgba(255, 255, 255, 0.6);
}
+/* ===== 创业老板排行 ===== */
+.members-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20rpx;
+ padding: 0 8rpx;
+}
+.member-cell {
+ width: calc(25% - 15rpx);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16rpx 0;
+}
+.member-avatar-wrap {
+ position: relative;
+ width: 100rpx;
+ height: 100rpx;
+ margin-bottom: 10rpx;
+}
+.member-avatar {
+ width: 100rpx;
+ height: 100rpx;
+ border-radius: 50%;
+ border: 3rpx solid #FFD700;
+}
+.member-avatar-placeholder {
+ width: 100rpx;
+ height: 100rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #1c1c1e, #2c2c2e);
+ border: 3rpx solid #FFD700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 36rpx;
+ color: #FFD700;
+}
+.member-vip-dot {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 30rpx;
+ height: 30rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #FFD700, #FFA500);
+ color: #000;
+ font-size: 16rpx;
+ font-weight: bold;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2rpx solid #000;
+}
+.member-name {
+ font-size: 24rpx;
+ color: rgba(255,255,255,0.9);
+ text-align: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 140rpx;
+}
+.member-project {
+ font-size: 20rpx;
+ color: rgba(255,255,255,0.4);
+ text-align: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 140rpx;
+ margin-top: 4rpx;
+}
+
/* ===== 底部留白 ===== */
.bottom-space {
height: 40rpx;
diff --git a/miniprogram/pages/member-detail/member-detail.js b/miniprogram/pages/member-detail/member-detail.js
new file mode 100644
index 00000000..8de3455a
--- /dev/null
+++ b/miniprogram/pages/member-detail/member-detail.js
@@ -0,0 +1,37 @@
+const app = getApp()
+
+Page({
+ data: {
+ statusBarHeight: 44,
+ member: null,
+ loading: true
+ },
+
+ onLoad(options) {
+ this.setData({ statusBarHeight: app.globalData.statusBarHeight })
+ if (options.id) this.loadMember(options.id)
+ },
+
+ async loadMember(id) {
+ try {
+ const res = await app.request(`/api/vip/members?id=${id}`)
+ if (res?.success) {
+ this.setData({ member: res.data, loading: false })
+ } else {
+ this.setData({ loading: false })
+ wx.showToast({ title: '会员不存在', icon: 'none' })
+ }
+ } catch (e) {
+ this.setData({ loading: false })
+ wx.showToast({ title: '加载失败', icon: 'none' })
+ }
+ },
+
+ copyContact() {
+ const contact = this.data.member?.contact
+ if (!contact) { wx.showToast({ title: '暂无联系方式', icon: 'none' }); return }
+ wx.setClipboardData({ data: contact, success: () => wx.showToast({ title: '已复制', icon: 'success' }) })
+ },
+
+ goBack() { wx.navigateBack() }
+})
diff --git a/miniprogram/pages/member-detail/member-detail.json b/miniprogram/pages/member-detail/member-detail.json
new file mode 100644
index 00000000..52bdd937
--- /dev/null
+++ b/miniprogram/pages/member-detail/member-detail.json
@@ -0,0 +1 @@
+{ "usingComponents": {}, "navigationStyle": "custom" }
diff --git a/miniprogram/pages/member-detail/member-detail.wxml b/miniprogram/pages/member-detail/member-detail.wxml
new file mode 100644
index 00000000..4fa72384
--- /dev/null
+++ b/miniprogram/pages/member-detail/member-detail.wxml
@@ -0,0 +1,38 @@
+
+
+
+ ‹
+ 创业伙伴
+
+
+
+
+
+
+
+
+ {{member.name[0] || '创'}}
+ VIP
+
+ {{member.name}}
+ {{member.project}}
+
+
+
+ 简介
+ {{member.bio}}
+
+
+
+ 联系方式
+
+ {{member.contact}}
+ 复制
+
+
+
+
+
+ 加载中...
+
+
diff --git a/miniprogram/pages/member-detail/member-detail.wxss b/miniprogram/pages/member-detail/member-detail.wxss
new file mode 100644
index 00000000..ab1b9552
--- /dev/null
+++ b/miniprogram/pages/member-detail/member-detail.wxss
@@ -0,0 +1,24 @@
+.page { background: #000; min-height: 100vh; color: #fff; }
+.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; height: 44px; padding: 0 24rpx; background: rgba(0,0,0,0.9); }
+.nav-back { width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: center; }
+.back-icon { font-size: 44rpx; color: #fff; }
+.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; }
+.nav-placeholder-r { width: 60rpx; }
+
+.detail-content { padding: 24rpx; }
+.detail-hero { display: flex; flex-direction: column; align-items: center; padding: 48rpx 0 32rpx; }
+.detail-avatar-wrap { position: relative; margin-bottom: 20rpx; }
+.detail-avatar { width: 160rpx; height: 160rpx; border-radius: 50%; border: 4rpx solid #FFD700; }
+.detail-avatar-ph { width: 160rpx; height: 160rpx; border-radius: 50%; background: #1c1c1e; border: 4rpx solid #FFD700; display: flex; align-items: center; justify-content: center; font-size: 60rpx; color: #FFD700; }
+.detail-vip-badge { position: absolute; bottom: 4rpx; right: 4rpx; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 20rpx; font-weight: bold; padding: 4rpx 12rpx; border-radius: 14rpx; }
+.detail-name { font-size: 40rpx; font-weight: bold; color: #fff; }
+.detail-project { font-size: 26rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; }
+
+.detail-card { background: #1c1c1e; border-radius: 20rpx; padding: 28rpx; margin-top: 24rpx; }
+.detail-card-title { font-size: 24rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 12rpx; }
+.detail-card-text { font-size: 30rpx; color: rgba(255,255,255,0.9); }
+.detail-contact-row { display: flex; align-items: center; justify-content: space-between; }
+.copy-btn { background: #00CED1; color: #000; font-size: 24rpx; font-weight: 600; padding: 8rpx 24rpx; border-radius: 20rpx; }
+
+.loading-state { display: flex; justify-content: center; padding: 100rpx 0; }
+.loading-text { color: rgba(255,255,255,0.4); font-size: 28rpx; }
diff --git a/miniprogram/pages/vip/vip.js b/miniprogram/pages/vip/vip.js
new file mode 100644
index 00000000..d016562a
--- /dev/null
+++ b/miniprogram/pages/vip/vip.js
@@ -0,0 +1,107 @@
+const app = getApp()
+
+Page({
+ data: {
+ statusBarHeight: 44,
+ isVip: false,
+ daysRemaining: 0,
+ expireDateStr: '',
+ price: 1980,
+ rights: [],
+ profile: { name: '', project: '', contact: '', bio: '' },
+ purchasing: false
+ },
+
+ onLoad() {
+ this.setData({ statusBarHeight: app.globalData.statusBarHeight })
+ this.loadVipInfo()
+ },
+
+ async loadVipInfo() {
+ const userId = app.globalData.userInfo?.id
+ if (!userId) return
+ try {
+ const res = await app.request(`/api/vip/status?userId=${userId}`)
+ if (res?.success) {
+ const d = res.data
+ let expStr = ''
+ if (d.expireDate) {
+ const dt = new Date(d.expireDate)
+ expStr = `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`
+ }
+ this.setData({
+ isVip: d.isVip,
+ daysRemaining: d.daysRemaining,
+ expireDateStr: expStr,
+ price: d.price || 1980,
+ rights: d.rights || ['解锁全部章节内容(365天)','匹配所有创业伙伴','创业老板排行榜展示','专属VIP标识']
+ })
+ if (d.isVip) this.loadProfile(userId)
+ }
+ } catch (e) {
+ console.log('[VIP] 加载失败', e)
+ this.setData({ rights: ['解锁全部章节内容(365天)','匹配所有创业伙伴','创业老板排行榜展示','专属VIP标识'] })
+ }
+ },
+
+ async loadProfile(userId) {
+ try {
+ const res = await app.request(`/api/vip/profile?userId=${userId}`)
+ if (res?.success) this.setData({ profile: res.data })
+ } catch (e) { console.log('[VIP] 资料加载失败', e) }
+ },
+
+ async handlePurchase() {
+ const userId = app.globalData.userInfo?.id
+ if (!userId) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
+ this.setData({ purchasing: true })
+ try {
+ const res = await app.request('/api/vip/purchase', { method: 'POST', data: { userId } })
+ if (res?.success) {
+ // 调用微信支付
+ const payRes = await app.request('/api/miniprogram/pay', {
+ method: 'POST',
+ data: { orderSn: res.data.orderSn, openId: app.globalData.openId }
+ })
+ if (payRes?.success && payRes.payParams) {
+ wx.requestPayment({
+ ...payRes.payParams,
+ success: () => {
+ wx.showToast({ title: 'VIP开通成功', icon: 'success' })
+ this.loadVipInfo()
+ },
+ fail: () => wx.showToast({ title: '支付取消', icon: 'none' })
+ })
+ } else {
+ wx.showToast({ title: '支付参数获取失败', icon: 'none' })
+ }
+ } else {
+ wx.showToast({ title: res?.error || '创建订单失败', icon: 'none' })
+ }
+ } catch (e) {
+ console.error('[VIP] 购买失败', e)
+ wx.showToast({ title: '购买失败', icon: 'none' })
+ } finally { this.setData({ purchasing: false }) }
+ },
+
+ onNameInput(e) { this.setData({ 'profile.name': e.detail.value }) },
+ onProjectInput(e) { this.setData({ 'profile.project': e.detail.value }) },
+ onContactInput(e) { this.setData({ 'profile.contact': e.detail.value }) },
+ onBioInput(e) { this.setData({ 'profile.bio': e.detail.value }) },
+
+ async saveProfile() {
+ const userId = app.globalData.userInfo?.id
+ if (!userId) return
+ const p = this.data.profile
+ try {
+ const res = await app.request('/api/vip/profile', {
+ method: 'POST',
+ data: { userId, name: p.name, project: p.project, contact: p.contact, bio: p.bio }
+ })
+ if (res?.success) wx.showToast({ title: '资料已保存', icon: 'success' })
+ else wx.showToast({ title: res?.error || '保存失败', icon: 'none' })
+ } catch (e) { wx.showToast({ title: '保存失败', icon: 'none' }) }
+ },
+
+ goBack() { wx.navigateBack() }
+})
diff --git a/miniprogram/pages/vip/vip.json b/miniprogram/pages/vip/vip.json
new file mode 100644
index 00000000..e90e9960
--- /dev/null
+++ b/miniprogram/pages/vip/vip.json
@@ -0,0 +1,4 @@
+{
+ "usingComponents": {},
+ "navigationStyle": "custom"
+}
diff --git a/miniprogram/pages/vip/vip.wxml b/miniprogram/pages/vip/vip.wxml
new file mode 100644
index 00000000..9f51260e
--- /dev/null
+++ b/miniprogram/pages/vip/vip.wxml
@@ -0,0 +1,60 @@
+
+
+
+ ‹
+ VIP会员
+
+
+
+
+
+
+ 👑
+ 开通VIP年度会员
+ VIP会员
+ 有效期至 {{expireDateStr}}(剩余{{daysRemaining}}天)
+ ¥{{price}}/年 · 365天全部权益
+
+
+
+
+ 会员权益
+
+
+ ✓
+ {{item}}
+
+
+
+
+
+
+
+
+
+
+
+ 会员资料(展示在创业老板排行)
+
+ 姓名
+
+
+
+ 项目名称
+
+
+
+ 联系方式
+
+
+
+ 一句话简介
+
+
+
+
+
+
+
diff --git a/miniprogram/pages/vip/vip.wxss b/miniprogram/pages/vip/vip.wxss
new file mode 100644
index 00000000..e7c08f25
--- /dev/null
+++ b/miniprogram/pages/vip/vip.wxss
@@ -0,0 +1,38 @@
+.page { background: #000; min-height: 100vh; color: #fff; }
+.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; height: 44px; padding: 0 24rpx; background: rgba(0,0,0,0.9); }
+.nav-back { width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: center; }
+.back-icon { font-size: 44rpx; color: #fff; }
+.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; }
+.nav-placeholder-r { width: 60rpx; }
+
+.vip-hero {
+ margin: 24rpx; padding: 48rpx 32rpx; text-align: center;
+ background: linear-gradient(135deg, rgba(255,215,0,0.1), rgba(255,165,0,0.06));
+ border: 1rpx solid rgba(255,215,0,0.2); border-radius: 24rpx;
+}
+.vip-hero-active { border-color: rgba(255,215,0,0.5); background: linear-gradient(135deg, rgba(255,215,0,0.18), rgba(255,165,0,0.1)); }
+.vip-hero-icon { font-size: 80rpx; }
+.vip-hero-title { display: block; font-size: 40rpx; font-weight: bold; color: #fff; margin-top: 16rpx; }
+.vip-hero-title.gold { color: #FFD700; }
+.vip-hero-sub { display: block; font-size: 26rpx; color: rgba(255,255,255,0.5); margin-top: 12rpx; }
+
+.rights-card { margin: 24rpx; padding: 28rpx; background: #1c1c1e; border-radius: 20rpx; }
+.rights-title { font-size: 30rpx; font-weight: 600; color: rgba(255,255,255,0.9); }
+.rights-list { margin-top: 20rpx; }
+.rights-item { display: flex; align-items: center; gap: 16rpx; padding: 16rpx 0; border-bottom: 1rpx solid rgba(255,255,255,0.06); }
+.rights-item:last-child { border-bottom: none; }
+.rights-check { color: #00CED1; font-size: 28rpx; font-weight: bold; }
+.rights-text { font-size: 28rpx; color: rgba(255,255,255,0.8); }
+
+.buy-section { padding: 32rpx 24rpx; }
+.buy-btn { width: 100%; height: 88rpx; line-height: 88rpx; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 32rpx; font-weight: bold; border-radius: 44rpx; border: none; }
+.buy-btn[disabled] { opacity: 0.5; }
+
+.profile-card { margin: 24rpx; padding: 28rpx; background: #1c1c1e; border-radius: 20rpx; }
+.profile-title { font-size: 30rpx; font-weight: 600; color: rgba(255,255,255,0.9); display: block; margin-bottom: 24rpx; }
+.form-group { margin-bottom: 20rpx; }
+.form-label { font-size: 24rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 8rpx; }
+.form-input { background: rgba(255,255,255,0.06); border: 1rpx solid rgba(255,255,255,0.1); border-radius: 12rpx; padding: 16rpx 20rpx; font-size: 28rpx; color: #fff; }
+.save-btn { margin-top: 24rpx; width: 100%; height: 80rpx; line-height: 80rpx; background: #00CED1; color: #000; font-size: 30rpx; font-weight: 600; border-radius: 40rpx; border: none; }
+
+.bottom-space { height: 120rpx; }
diff --git a/next-project/app/api/book/hot/route.ts b/next-project/app/api/book/hot/route.ts
index 3d8aa79c..d62b6fd4 100644
--- a/next-project/app/api/book/hot/route.ts
+++ b/next-project/app/api/book/hot/route.ts
@@ -1,74 +1,86 @@
/**
* 热门章节API
- * 返回点击量最高的章节
+ * 按阅读量(user_tracks view_chapter)排序
*/
import { NextResponse } from 'next/server'
import { query } from '@/lib/db'
+const DEFAULT_CHAPTERS = [
+ { id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', tagClass: 'tag-free', part: '真实的人', views: 0 },
+ { id: '9.12', title: '美业整合:一个人的公司如何月入十万', tag: '热门', tagClass: 'tag-pink', part: '真实的赚钱', views: 0 },
+ { id: '3.1', title: '3000万流水如何跑出来', tag: '热门', tagClass: 'tag-pink', part: '真实的行业', views: 0 },
+ { id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', tagClass: 'tag-purple', part: '真实的赚钱', views: 0 },
+ { id: '9.13', title: 'AI工具推广:一个隐藏的高利润赛道', tag: '最新', tagClass: 'tag-green', part: '真实的赚钱', views: 0 },
+]
+
+const SECTION_INFO: Record = {
+ '1.1': { title: '荷包:电动车出租的被动收入模式', part: '真实的人', tag: '免费', tagClass: 'tag-free' },
+ '1.2': { title: '老墨:资源整合高手的社交方法', part: '真实的人', tag: '推荐', tagClass: 'tag-purple' },
+ '2.1': { title: '电商的底层逻辑', part: '真实的行业', tag: '推荐', tagClass: 'tag-purple' },
+ '3.1': { title: '3000万流水如何跑出来', part: '真实的行业', tag: '热门', tagClass: 'tag-pink' },
+ '4.1': { title: '我的第一次创业失败', part: '真实的错误', tag: '热门', tagClass: 'tag-pink' },
+ '5.1': { title: '未来职业的三个方向', part: '真实的社会', tag: '推荐', tagClass: 'tag-purple' },
+ '8.1': { title: '流量杠杆:抖音、Soul、飞书', part: '真实的赚钱', tag: '推荐', tagClass: 'tag-purple' },
+ '9.12': { title: '美业整合:一个人的公司如何月入十万', part: '真实的赚钱', tag: '热门', tagClass: 'tag-pink' },
+ '9.13': { title: 'AI工具推广:一个隐藏的高利润赛道', part: '真实的赚钱', tag: '最新', tagClass: 'tag-green' },
+ '9.14': { title: '大健康私域:一个月150万的70后', part: '真实的赚钱', tag: '热门', tagClass: 'tag-pink' },
+ '9.15': { title: '本地同城运营拿150万投资', part: '真实的赚钱', tag: '热门', tagClass: 'tag-pink' },
+}
+
export async function GET() {
try {
- // 从数据库查询点击量高的章节(如果有统计表)
- let hotChapters = []
-
+ let hotChapters: any[] = []
+
try {
- // 尝试从订单表统计购买量高的章节
+ // 按 user_tracks 的 view_chapter 阅读量排序
const rows = await query(`
- SELECT
- section_id as id,
- COUNT(*) as purchase_count
- FROM orders
- WHERE status = 'completed' AND section_id IS NOT NULL
- GROUP BY section_id
- ORDER BY purchase_count DESC
+ SELECT chapter_id as id, COUNT(*) as view_count
+ FROM user_tracks
+ WHERE action = 'view_chapter' AND chapter_id IS NOT NULL AND chapter_id != ''
+ GROUP BY chapter_id
+ ORDER BY view_count DESC
LIMIT 10
`) as any[]
-
- if (rows && rows.length > 0) {
- // 补充章节信息
- const sectionInfo: Record = {
- '1.1': { title: '荷包:电动车出租的被动收入模式', part: '真实的人', tag: '免费' },
- '9.12': { title: '美业整合:一个人的公司如何月入十万', part: '真实的赚钱', tag: '热门' },
- '3.1': { title: '3000万流水如何跑出来', part: '真实的行业', tag: '热门' },
- '8.1': { title: '流量杠杆:抖音、Soul、飞书', part: '真实的赚钱', tag: '推荐' },
- '9.13': { title: 'AI工具推广:一个隐藏的高利润赛道', part: '真实的赚钱', tag: '最新' },
- '9.14': { title: '大健康私域:一个月150万的70后', part: '真实的赚钱', tag: '热门' },
- '1.2': { title: '老墨:资源整合高手的社交方法', part: '真实的人', tag: '推荐' },
- '2.1': { title: '电商的底层逻辑', part: '真实的行业', tag: '推荐' },
- '4.1': { title: '我的第一次创业失败', part: '真实的错误', tag: '热门' },
- '5.1': { title: '未来职业的三个方向', part: '真实的社会', tag: '推荐' }
- }
-
- hotChapters = rows.map((row: any) => ({
- id: row.id,
- ...(sectionInfo[row.id] || { title: `章节${row.id}`, part: '', tag: '热门' }),
- purchaseCount: row.purchase_count
- }))
+
+ if (rows?.length) {
+ hotChapters = rows.map((row: any) => {
+ const info = SECTION_INFO[row.id] || {}
+ return {
+ id: row.id,
+ title: info.title || `章节 ${row.id}`,
+ part: info.part || '',
+ tag: info.tag || '热门',
+ tagClass: info.tagClass || 'tag-pink',
+ views: row.view_count
+ }
+ })
}
} catch (e) {
- console.log('[Hot] 数据库查询失败,使用默认数据')
+ console.log('[Hot] user_tracks查询失败,尝试订单统计')
+ // 降级:从订单表统计
+ try {
+ const rows = await query(`
+ SELECT product_id as id, COUNT(*) as purchase_count
+ FROM orders WHERE status = 'paid' AND product_id IS NOT NULL
+ GROUP BY product_id ORDER BY purchase_count DESC LIMIT 10
+ `) as any[]
+ if (rows?.length) {
+ hotChapters = rows.map((row: any) => ({
+ id: row.id,
+ ...(SECTION_INFO[row.id] || { title: `章节 ${row.id}`, part: '', tag: '热门', tagClass: 'tag-pink' }),
+ views: row.purchase_count
+ }))
+ }
+ } catch { /* 使用默认 */ }
}
-
- // 如果没有数据,返回默认热门章节
- if (hotChapters.length === 0) {
- hotChapters = [
- { id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', part: '真实的人' },
- { id: '9.12', title: '美业整合:一个人的公司如何月入十万', tag: '热门', part: '真实的赚钱' },
- { id: '3.1', title: '3000万流水如何跑出来', tag: '热门', part: '真实的行业' },
- { id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', part: '真实的赚钱' },
- { id: '9.13', title: 'AI工具推广:一个隐藏的高利润赛道', tag: '最新', part: '真实的赚钱' }
- ]
+
+ if (!hotChapters.length) {
+ hotChapters = DEFAULT_CHAPTERS
}
-
- return NextResponse.json({
- success: true,
- chapters: hotChapters
- })
-
+
+ return NextResponse.json({ success: true, chapters: hotChapters })
} catch (error) {
console.error('[Hot] Error:', error)
- return NextResponse.json({
- success: false,
- chapters: []
- })
+ return NextResponse.json({ success: true, chapters: DEFAULT_CHAPTERS })
}
}
diff --git a/next-project/app/api/book/latest-chapters/route.ts b/next-project/app/api/book/latest-chapters/route.ts
index 369eaa17..c378c794 100644
--- a/next-project/app/api/book/latest-chapters/route.ts
+++ b/next-project/app/api/book/latest-chapters/route.ts
@@ -1,55 +1,109 @@
// app/api/book/latest-chapters/route.ts
-// 获取最新章节列表
+// 获取最新章节:有2日内更新则取最新3章,否则随机取免费章节
+// 排除序言、尾声、附录,只推荐正文章节
-import { NextRequest, NextResponse } from 'next/server'
-import { getBookStructure } from '@/lib/book-file-system'
+import { NextResponse } from 'next/server'
+import { query } from '@/lib/db'
-export async function GET(req: NextRequest) {
+const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000
+
+/** 是否应排除(序言、尾声、附录等特殊章节) */
+function isExcludedChapter(id: string, partTitle: string): boolean {
+ const lowerId = String(id || '').toLowerCase()
+ if (lowerId === 'preface' || lowerId === 'epilogue') return true
+ if (lowerId.startsWith('appendix-') || lowerId.startsWith('appendix_')) return true
+ const pt = String(partTitle || '')
+ if (/序言|尾声/.test(pt)) return true
+ return false
+}
+
+export async function GET() {
try {
- const bookStructure = getBookStructure()
-
- // 获取所有章节并按时间排序
- const allChapters: any[] = []
-
- bookStructure.forEach((part: any) => {
- part.chapters.forEach((chapter: any) => {
- allChapters.push({
- id: chapter.slug,
- title: chapter.title,
- part: part.title,
- words: Math.floor(Math.random() * 3000) + 1500, // 模拟字数
- updateTime: getRelativeTime(new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000)),
- readTime: Math.ceil((Math.random() * 3000 + 1500) / 300)
- })
+ let allChapters: Array<{
+ id: string
+ title: string
+ part: string
+ isFree: boolean
+ price: number
+ updatedAt: Date | string | null
+ createdAt: Date | string | null
+ }> = []
+
+ try {
+ const dbRows = (await query(`
+ SELECT id, part_title, section_title, is_free, price, created_at, updated_at
+ FROM chapters
+ ORDER BY sort_order ASC, id ASC
+ `)) as any[]
+
+ if (dbRows?.length > 0) {
+ allChapters = dbRows
+ .map((row: any) => ({
+ id: row.id,
+ title: row.section_title || row.title || '',
+ part: row.part_title || '真实的行业',
+ isFree: !!row.is_free,
+ price: row.price || 0,
+ updatedAt: row.updated_at || row.created_at,
+ createdAt: row.created_at
+ }))
+ .filter((c) => !isExcludedChapter(c.id, c.part))
+ }
+ } catch (e) {
+ console.log('[latest-chapters] 数据库读取失败:', (e as Error).message)
+ }
+
+ if (allChapters.length === 0) {
+ return NextResponse.json({
+ success: true,
+ banner: { id: '1.1', title: '荷包:电动车出租的被动收入模式', part: '真实的人' },
+ label: '为你推荐',
+ chapters: [],
+ hasNewUpdates: false
})
+ }
+
+ const now = Date.now()
+ const sorted = [...allChapters].sort((a, b) => {
+ const ta = a.updatedAt ? new Date(a.updatedAt).getTime() : 0
+ const tb = b.updatedAt ? new Date(b.updatedAt).getTime() : 0
+ return tb - ta
})
- // 取最新的3章
- const latestChapters = allChapters.slice(0, 3)
+ const mostRecentTime = sorted[0]?.updatedAt ? new Date(sorted[0].updatedAt).getTime() : 0
+ const hasNewUpdates = now - mostRecentTime < TWO_DAYS_MS
+
+ let banner: { id: string; title: string; part: string }
+ let label: string
+ let chapters: typeof allChapters
+
+ if (hasNewUpdates && sorted.length > 0) {
+ chapters = sorted.slice(0, 3)
+ banner = { id: chapters[0].id, title: chapters[0].title, part: chapters[0].part }
+ label = '最新更新'
+ } else {
+ const freeChapters = allChapters.filter((c) => c.isFree || c.price === 0)
+ const candidates = freeChapters.length > 0 ? freeChapters : allChapters
+ const shuffled = [...candidates].sort(() => Math.random() - 0.5)
+ chapters = shuffled.slice(0, 3)
+ banner = chapters[0]
+ ? { id: chapters[0].id, title: chapters[0].title, part: chapters[0].part }
+ : { id: allChapters[0].id, title: allChapters[0].title, part: allChapters[0].part }
+ label = '为你推荐'
+ }
return NextResponse.json({
success: true,
- chapters: latestChapters,
- total: allChapters.length
+ banner,
+ label,
+ chapters: chapters.map((c) => ({ id: c.id, title: c.title, part: c.part, isFree: c.isFree })),
+ hasNewUpdates
})
} catch (error) {
- console.error('获取章节失败:', error)
+ console.error('[latest-chapters] Error:', error)
return NextResponse.json(
- { error: '获取章节失败' },
+ { success: false, error: '获取失败' },
{ status: 500 }
)
}
}
-
-// 获取相对时间
-function getRelativeTime(date: Date): string {
- const now = new Date()
- const diff = now.getTime() - date.getTime()
- const days = Math.floor(diff / (1000 * 60 * 60 * 24))
-
- if (days === 0) return '今天'
- if (days === 1) return '昨天'
- if (days < 7) return `${days}天前`
- if (days < 30) return `${Math.floor(days / 7)}周前`
- return `${Math.floor(days / 30)}个月前`
-}
diff --git a/next-project/app/api/db/book/route.ts b/next-project/app/api/db/book/route.ts
index ba6e9c77..2c354f6e 100644
--- a/next-project/app/api/db/book/route.ts
+++ b/next-project/app/api/db/book/route.ts
@@ -146,13 +146,40 @@ export async function GET(request: NextRequest) {
}
// 列出所有章节(不含内容)
+ // 优先从数据库读取,确保新建章节能立即显示
if (action === 'list') {
+ const sectionsFromDb = new Map()
+ try {
+ const rows = await query(`
+ SELECT id, part_id, part_title, chapter_id, chapter_title, section_title,
+ price, is_free, content
+ FROM chapters ORDER BY part_id, chapter_id, id
+ `) as any[]
+ if (rows && rows.length > 0) {
+ for (const r of rows) {
+ sectionsFromDb.set(r.id, {
+ id: r.id,
+ title: r.section_title || '',
+ price: r.price ?? 1,
+ isFree: !!r.is_free,
+ partId: r.part_id || 'part-1',
+ partTitle: r.part_title || '',
+ chapterId: r.chapter_id || 'chapter-1',
+ chapterTitle: r.chapter_title || '',
+ filePath: ''
+ })
+ }
+ }
+ } catch (e) {
+ console.log('[Book API] list 从数据库读取失败,回退到 bookData:', (e as Error).message)
+ }
+ // 合并:以数据库为准,数据库没有的用 bookData 补
const sections: any[] = []
-
for (const part of bookData) {
for (const chapter of part.chapters) {
for (const section of chapter.sections) {
- sections.push({
+ const dbRow = sectionsFromDb.get(section.id)
+ sections.push(dbRow || {
id: section.id,
title: section.title,
price: section.price,
@@ -163,14 +190,25 @@ export async function GET(request: NextRequest) {
chapterTitle: chapter.title,
filePath: section.filePath
})
+ sectionsFromDb.delete(section.id)
}
}
}
-
+ // 数据库有但 bookData 没有的(新建章节)
+ for (const [, v] of sectionsFromDb) {
+ sections.push(v)
+ }
+ // 按 id 去重,避免数据库重复或合并逻辑导致同一文章出现多次
+ const seen = new Set()
+ const deduped = sections.filter((s) => {
+ if (seen.has(s.id)) return false
+ seen.add(s.id)
+ return true
+ })
return NextResponse.json({
success: true,
- sections,
- total: sections.length
+ sections: deduped,
+ total: deduped.length
})
}
@@ -324,7 +362,7 @@ export async function POST(request: NextRequest) {
export async function PUT(request: NextRequest) {
try {
const body = await request.json()
- const { id, title, content, price, saveToFile = true } = body
+ const { id, title, content, price, saveToFile = true, partId, chapterId, partTitle, chapterTitle, isFree } = body
if (!id) {
return NextResponse.json({
@@ -334,28 +372,40 @@ export async function PUT(request: NextRequest) {
}
const sectionInfo = getSectionInfo(id)
+ const finalPartId = partId || sectionInfo?.partId || 'part-1'
+ const finalPartTitle = partTitle || sectionInfo?.partTitle || '未分类'
+ const finalChapterId = chapterId || sectionInfo?.chapterId || 'chapter-1'
+ const finalChapterTitle = chapterTitle || sectionInfo?.chapterTitle || '未分类'
+ const finalPrice = price ?? sectionInfo?.section?.price ?? 1
+ const finalIsFree = isFree ?? sectionInfo?.section?.isFree ?? false
- // 更新数据库
+ // 更新数据库(含新建章节)
try {
await query(`
- INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, price, status)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
+ INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, status)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
ON DUPLICATE KEY UPDATE
+ part_id = VALUES(part_id),
+ part_title = VALUES(part_title),
+ chapter_id = VALUES(chapter_id),
+ chapter_title = VALUES(chapter_title),
section_title = VALUES(section_title),
content = VALUES(content),
word_count = VALUES(word_count),
+ is_free = VALUES(is_free),
price = VALUES(price),
updated_at = CURRENT_TIMESTAMP
`, [
id,
- sectionInfo?.partId || 'part-1',
- sectionInfo?.partTitle || '未分类',
- sectionInfo?.chapterId || 'chapter-1',
- sectionInfo?.chapterTitle || '未分类',
- title || sectionInfo?.section.title || '',
+ finalPartId,
+ finalPartTitle,
+ finalChapterId,
+ finalChapterTitle,
+ title || sectionInfo?.section?.title || '',
content || '',
(content || '').length,
- price ?? sectionInfo?.section.price ?? 1
+ finalIsFree,
+ finalPrice
])
} catch (e) {
console.error('[Book API] 更新数据库失败:', e)
diff --git a/next-project/app/api/db/migrate/route.ts b/next-project/app/api/db/migrate/route.ts
index c830bd05..6c9c31c4 100644
--- a/next-project/app/api/db/migrate/route.ts
+++ b/next-project/app/api/db/migrate/route.ts
@@ -115,6 +115,47 @@ export async function POST(request: NextRequest) {
}
}
+ // VIP会员字段
+ if (!migration || migration === 'vip_fields') {
+ const vipFields = [
+ { name: 'is_vip', def: "BOOLEAN DEFAULT FALSE COMMENT 'VIP会员'" },
+ { name: 'vip_expire_date', def: "TIMESTAMP NULL COMMENT 'VIP到期时间'" },
+ { name: 'vip_name', def: "VARCHAR(100) COMMENT '会员真实姓名'" },
+ { name: 'vip_project', def: "VARCHAR(200) COMMENT '会员项目名称'" },
+ { name: 'vip_contact', def: "VARCHAR(100) COMMENT '会员联系方式'" },
+ { name: 'vip_avatar', def: "VARCHAR(500) COMMENT '会员展示头像'" },
+ { name: 'vip_bio', def: "VARCHAR(500) COMMENT '会员简介'" },
+ ]
+ let addedCount = 0
+ let existCount = 0
+ for (const field of vipFields) {
+ try {
+ await query(`SELECT ${field.name} FROM users LIMIT 1`)
+ existCount++
+ } catch {
+ try {
+ await query(`ALTER TABLE users ADD COLUMN ${field.name} ${field.def}`)
+ addedCount++
+ } catch (e: any) {
+ if (e.code !== 'ER_DUP_FIELDNAME') {
+ results.push(`⚠️ 添加VIP字段 ${field.name} 失败: ${e.message}`)
+ }
+ }
+ }
+ }
+
+ // 扩展 orders.product_type 支持 vip
+ try {
+ await query(`ALTER TABLE orders MODIFY COLUMN product_type ENUM('section', 'fullbook', 'match', 'vip') NOT NULL`)
+ results.push('✅ orders.product_type 已支持 vip')
+ } catch (e: any) {
+ results.push('ℹ️ orders.product_type 更新跳过: ' + e.message)
+ }
+
+ if (addedCount > 0) results.push(`✅ VIP字段新增 ${addedCount} 个`)
+ if (existCount > 0) results.push(`ℹ️ VIP字段已有 ${existCount} 个存在`)
+ }
+
// 用户标签定义表
if (!migration || migration === 'user_tag_definitions') {
try {
@@ -189,7 +230,7 @@ export async function GET() {
// 检查用户表字段
const userFields: Record = {}
- const checkFields = ['ckb_user_id', 'ckb_synced_at', 'ckb_tags', 'tags', 'merged_tags']
+ const checkFields = ['ckb_user_id', 'ckb_synced_at', 'ckb_tags', 'tags', 'merged_tags', 'is_vip', 'vip_expire_date', 'vip_name']
for (const field of checkFields) {
try {
diff --git a/next-project/lib/book-data.ts b/next-project/lib/book-data.ts
index 8e5fc0fa..1803e4f9 100644
--- a/next-project/lib/book-data.ts
+++ b/next-project/lib/book-data.ts
@@ -510,6 +510,20 @@ export const bookData: Part[] = [
isFree: false,
filePath: "book/第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例/9.14 大健康私域:一个月150万的70后.md",
},
+ {
+ id: "9.15",
+ title: "第102场|今年第一个红包你发给谁",
+ price: 1,
+ isFree: false,
+ filePath: "book/第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例/9.15 第102场|今年第一个红包你发给谁.md",
+ },
+ {
+ id: "9.16",
+ title: "第103场|号商、某客与炸房",
+ price: 1,
+ isFree: false,
+ filePath: "book/第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例/9.16 第103场|号商、某客与炸房.md",
+ },
],
},
],
diff --git a/next-project/next.config.mjs b/next-project/next.config.mjs
index 9041f103..01b41373 100644
--- a/next-project/next.config.mjs
+++ b/next-project/next.config.mjs
@@ -12,6 +12,18 @@ const nextConfig = {
buildActivity: false,
appIsrStatus: false,
},
+ async headers() {
+ return [
+ {
+ source: '/api/:path*',
+ headers: [
+ { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' },
+ { key: 'Access-Control-Allow-Headers', value: 'Content-Type,Authorization' },
+ { key: 'Access-Control-Allow-Credentials', value: 'true' },
+ ],
+ },
+ ]
+ },
}
export default nextConfig
diff --git a/soul-book-api/package.json b/soul-book-api/package.json
new file mode 100644
index 00000000..3aacb5a4
--- /dev/null
+++ b/soul-book-api/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "soul-book-api",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node server.js"
+ },
+ "dependencies": {
+ "mysql2": "^3.11.0"
+ }
+}
diff --git a/soul-book-api/server.js b/soul-book-api/server.js
new file mode 100644
index 00000000..2a22b49b
--- /dev/null
+++ b/soul-book-api/server.js
@@ -0,0 +1,243 @@
+const http = require('http')
+const mysql = require('mysql2/promise')
+
+const PORT = 3007
+const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000
+
+const pool = mysql.createPool({
+ host: '56b4c23f6853c.gz.cdb.myqcloud.com',
+ port: 14413,
+ user: 'cdb_outerroot',
+ password: 'Zhiqun1984',
+ database: 'soul_miniprogram',
+ charset: 'utf8mb4',
+ waitForConnections: true,
+ connectionLimit: 5,
+ queueLimit: 0
+})
+
+function isExcluded(id, partTitle) {
+ const lid = String(id || '').toLowerCase()
+ if (lid === 'preface' || lid === 'epilogue') return true
+ if (lid.startsWith('appendix-') || lid.startsWith('appendix_')) return true
+ const pt = String(partTitle || '')
+ if (/序言|尾声|附录/.test(pt)) return true
+ return false
+}
+
+function cleanPartTitle(pt) {
+ return (pt || '真实的行业').replace(/^第[一二三四五六七八九十]+篇[||]?/, '').trim() || '真实的行业'
+}
+
+async function getFeaturedSections() {
+ const tags = [
+ { tag: '热门', tagClass: 'tag-pink' },
+ { tag: '推荐', tagClass: 'tag-purple' },
+ { tag: '精选', tagClass: 'tag-free' }
+ ]
+ try {
+ const [rows] = await pool.query(`
+ SELECT c.id, c.section_title, c.part_title, c.is_free,
+ COALESCE(t.cnt, 0) as view_count
+ FROM chapters c
+ LEFT JOIN (
+ SELECT chapter_id, COUNT(*) as cnt
+ FROM user_tracks
+ WHERE action = 'view_chapter' AND chapter_id IS NOT NULL
+ GROUP BY chapter_id
+ ) t ON c.id = t.chapter_id
+ WHERE c.id NOT IN ('preface','epilogue')
+ AND c.id NOT LIKE 'appendix-%' AND c.id NOT LIKE 'appendix\\_%'
+ AND c.part_title NOT LIKE '%序言%' AND c.part_title NOT LIKE '%尾声%'
+ AND c.part_title NOT LIKE '%附录%'
+ ORDER BY view_count DESC, c.updated_at DESC
+ LIMIT 6
+ `)
+ if (rows && rows.length > 0) {
+ return rows.slice(0, 3).map((r, i) => ({
+ id: r.id,
+ title: r.section_title || '',
+ part: cleanPartTitle(r.part_title),
+ tag: tags[i]?.tag || '推荐',
+ tagClass: tags[i]?.tagClass || 'tag-purple'
+ }))
+ }
+ } catch (e) {
+ console.error('[featured] query error:', e.message)
+ }
+ try {
+ const [fallback] = await pool.query(`
+ SELECT id, section_title, part_title, is_free
+ FROM chapters
+ WHERE id NOT IN ('preface','epilogue')
+ AND id NOT LIKE 'appendix-%' AND id NOT LIKE 'appendix\\_%'
+ AND part_title NOT LIKE '%序言%' AND part_title NOT LIKE '%尾声%'
+ AND part_title NOT LIKE '%附录%'
+ ORDER BY updated_at DESC
+ LIMIT 3
+ `)
+ if (fallback?.length > 0) {
+ return fallback.map((r, i) => ({
+ id: r.id,
+ title: r.section_title || '',
+ part: cleanPartTitle(r.part_title),
+ tag: tags[i]?.tag || '推荐',
+ tagClass: tags[i]?.tagClass || 'tag-purple'
+ }))
+ }
+ } catch (_) {}
+ return [
+ { id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', tagClass: 'tag-free', part: '真实的人' },
+ { id: '3.1', title: '3000万流水如何跑出来', tag: '热门', tagClass: 'tag-pink', part: '真实的行业' },
+ { id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', tagClass: 'tag-purple', part: '真实的赚钱' }
+ ]
+}
+
+async function handleLatestChapters(res) {
+ try {
+ const [rows] = await pool.query(`
+ SELECT id, part_title, section_title, is_free, price, created_at, updated_at
+ FROM chapters
+ ORDER BY sort_order ASC, id ASC
+ `)
+ let chapters = (rows || [])
+ .map(r => ({
+ id: r.id,
+ title: r.section_title || '',
+ part: cleanPartTitle(r.part_title),
+ isFree: !!r.is_free,
+ price: r.price || 0,
+ updatedAt: r.updated_at || r.created_at,
+ createdAt: r.created_at
+ }))
+ .filter(c => !isExcluded(c.id, c.part))
+
+ if (chapters.length === 0) {
+ return sendJSON(res, {
+ success: true,
+ banner: { id: '1.1', title: '开始阅读', part: '真实的人' },
+ label: '为你推荐',
+ chapters: [],
+ hasNewUpdates: false
+ })
+ }
+
+ const sorted = [...chapters].sort((a, b) => {
+ const ta = a.updatedAt ? new Date(a.updatedAt).getTime() : 0
+ const tb = b.updatedAt ? new Date(b.updatedAt).getTime() : 0
+ return tb - ta
+ })
+
+ const mostRecentTime = sorted[0]?.updatedAt ? new Date(sorted[0].updatedAt).getTime() : 0
+ const hasNewUpdates = Date.now() - mostRecentTime < TWO_DAYS_MS
+
+ let banner, label, selected
+ if (hasNewUpdates) {
+ selected = sorted.slice(0, 3)
+ banner = { id: selected[0].id, title: selected[0].title, part: selected[0].part }
+ label = '最新更新'
+ } else {
+ const free = chapters.filter(c => c.isFree || c.price === 0)
+ const candidates = free.length > 0 ? free : chapters
+ const shuffled = [...candidates].sort(() => Math.random() - 0.5)
+ selected = shuffled.slice(0, 3)
+ banner = { id: selected[0].id, title: selected[0].title, part: selected[0].part }
+ label = '为你推荐'
+ }
+
+ sendJSON(res, {
+ success: true,
+ banner,
+ label,
+ chapters: selected.map(c => ({ id: c.id, title: c.title, part: c.part, isFree: c.isFree })),
+ hasNewUpdates
+ })
+ } catch (e) {
+ console.error('[latest-chapters] error:', e.message)
+ sendJSON(res, { success: false, error: '获取失败' }, 500)
+ }
+}
+
+async function handleAllChapters(res) {
+ const featuredSections = await getFeaturedSections()
+ try {
+ const [rows] = await pool.query(`
+ SELECT id, part_id, part_title, chapter_id, chapter_title, section_title,
+ content, is_free, price, word_count, sort_order, created_at, updated_at
+ FROM chapters
+ ORDER BY sort_order ASC, id ASC
+ `)
+ if (rows && rows.length > 0) {
+ const seen = new Set()
+ const data = rows
+ .map(r => ({
+ mid: r.mid || 0,
+ id: r.id,
+ partId: r.part_id || '',
+ partTitle: r.part_title || '',
+ chapterId: r.chapter_id || '',
+ chapterTitle: r.chapter_title || '',
+ sectionTitle: r.section_title || '',
+ content: r.content || '',
+ wordCount: r.word_count || 0,
+ isFree: !!r.is_free,
+ price: r.price || 0,
+ sortOrder: r.sort_order || 0,
+ status: 'published',
+ createdAt: r.created_at,
+ updatedAt: r.updated_at
+ }))
+ .filter(r => {
+ if (seen.has(r.id)) return false
+ seen.add(r.id)
+ return true
+ })
+
+ return sendJSON(res, {
+ success: true,
+ data,
+ total: data.length,
+ featuredSections
+ })
+ }
+ } catch (e) {
+ console.error('[all-chapters] error:', e.message)
+ }
+ sendJSON(res, {
+ success: true,
+ data: [],
+ total: 0,
+ featuredSections
+ })
+}
+
+function sendJSON(res, obj, code = 200) {
+ res.writeHead(code, {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
+ })
+ res.end(JSON.stringify(obj))
+}
+
+const server = http.createServer(async (req, res) => {
+ if (req.method === 'OPTIONS') {
+ return sendJSON(res, {})
+ }
+ const url = req.url.split('?')[0]
+ if (url === '/api/book/latest-chapters') {
+ return handleLatestChapters(res)
+ }
+ if (url === '/api/book/all-chapters') {
+ return handleAllChapters(res)
+ }
+ if (url === '/health') {
+ return sendJSON(res, { status: 'ok', time: new Date().toISOString() })
+ }
+ sendJSON(res, { error: 'not found' }, 404)
+})
+
+server.listen(PORT, '127.0.0.1', () => {
+ console.log(`[soul-book-api] running on port ${PORT}`)
+})
diff --git a/开发文档/0、Mycontent-book 项目总览.md b/开发文档/10、项目管理/0、Mycontent-book 项目总览.md
similarity index 100%
rename from 开发文档/0、Mycontent-book 项目总览.md
rename to 开发文档/10、项目管理/0、Mycontent-book 项目总览.md
diff --git a/开发文档/10、项目管理/soul-admin变更记录_v2026-02.md b/开发文档/10、项目管理/soul-admin变更记录_v2026-02.md
new file mode 100644
index 00000000..d44bd1df
--- /dev/null
+++ b/开发文档/10、项目管理/soul-admin变更记录_v2026-02.md
@@ -0,0 +1,79 @@
+# Soul 管理后台 (soul-admin) 变更记录 v2026-02
+
+> 更新时间:2026-02-21
+> 适用站点:souladmin.quwanzhi.com
+> 部署路径:`/www/wwwroot/自营/soul-admin/dist/`
+
+---
+
+## 一、变更概览
+
+| 模块 | 变更项 | 说明 |
+|:---|:---|:---|
+| 侧边栏 | 交易中心 → 推广中心 | 菜单及页面标题统一改为「推广中心」 |
+| 内容管理 | 顶部 5 按钮移除 | 移除:初始化数据库、同步到数据库、导入、导出、同步飞书 |
+| 内容管理 | 仅保留 API 接口 | 仅保留「API 接口」按钮,打开 API 文档面板 |
+| 内容管理 | 删除按钮 | 删除按钮改为悬停才显示(与读取/编辑一致) |
+| 内容管理 | 免费/付费 | 可点击切换免费 ↔ 付费 |
+| 内容管理 | 小节加号 | 每小节旁增加「+」按钮,可在此小节下新建章节 |
+
+---
+
+## 二、部署说明
+
+### 2.1 正确部署路径
+
+nginx 实际指向:
+
+```nginx
+root /www/wwwroot/自营/soul-admin/dist;
+```
+
+**重要**:需将 `soul-admin/dist` 部署到上述目录,而非 `/www/wwwroot/souladmin.quwanzhi.com/`。
+
+### 2.2 部署步骤
+
+```bash
+# 1. 本地打包
+cd /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/soul-admin/dist
+tar -czf /tmp/souladmin.tar.gz index.html assets/
+
+# 2. 上传并解压到正确路径
+scp -P 22022 /tmp/souladmin.tar.gz root@43.139.27.93:/tmp/
+ssh -p 22022 root@43.139.27.93 'cd /www/wwwroot/自营/soul-admin/dist && tar -xzf /tmp/souladmin.tar.gz && chown -R www:www . && rm /tmp/souladmin.tar.gz'
+```
+
+### 2.3 缓存处理
+
+- `index.html` 内引用 `index-CbOmKBRd.js?v=版本号`,每次发布建议递增版本号
+- 建议在 `index.html` 中调整:`?v=3` 或更高
+
+---
+
+## 三、技术说明
+
+### 3.1 修改文件
+
+- `index.html`:内联注入脚本(按钮改造、删除 hover、免费切换、加号新建)
+- `assets/index-CbOmKBRd.js`:侧边栏「交易中心」→「推广中心」
+
+### 3.2 注入脚本触发条件
+
+- 路径包含 `content`(如 `/content`)
+- 页面上存在「初始化数据库」按钮(内容管理页加载完成)
+
+### 3.3 免费/付费切换
+
+- 调用 `POST /api/db/book`,传入 `{ id, isFree, price }`
+- 需后端支持按 id 更新 isFree/price
+
+---
+
+## 四、问题排查
+
+| 现象 | 可能原因 | 处理方式 |
+|:---|:---|:---|
+| 界面未变化 | 部署到错误目录 | 确认部署到 `/www/wwwroot/自营/soul-admin/dist/` |
+| 界面未变化 | 浏览器/CDN 缓存 | 清除缓存或使用无痕模式,或增加 `?v=` 版本号 |
+| 内容管理注入不生效 | 路由为 hash 模式 | 检查 `location.pathname` 是否包含 `content`,必要时改用 `location.hash` |
+| 免费切换失败 | 后端未实现更新 | 检查 soul-api 是否支持 `POST /api/db/book` 的更新逻辑 |
diff --git a/开发文档/10、项目管理/小程序接口申请文案.md b/开发文档/10、项目管理/小程序接口申请文案.md
new file mode 100644
index 00000000..13a43e06
--- /dev/null
+++ b/开发文档/10、项目管理/小程序接口申请文案.md
@@ -0,0 +1,97 @@
+# 微信小程序接口申请文案(可直接复制)
+
+> 用于微信公众平台 → 开发管理 → 接口设置 → 接口权限
+> 每个理由控制在 300 字以内,按需复制到对应接口的「申请接口理由」框。
+
+---
+
+## 1. wx.chooseAddress(获取用户收货地址)
+
+**申请接口理由:**
+
+```
+本小程序为创业者社群与资源对接平台。用户在使用「找伙伴-资源对接」功能时,需填写联系地址,便于匹配成功后线下见面、寄送资料或合作签约。申请 wx.chooseAddress 后,用户可一键从微信获取已保存的收货地址,无需逐项手动输入,既保证信息真实可联系,又提升填写效率,完成从线上匹配到线下对接的闭环。
+```
+
+**备选(更简短):**
+
+```
+本小程序提供创业资源对接服务,用户匹配成功后需交换联系地址以便线下合作。申请此接口后,用户可一键选择微信收货地址,避免手动输入错误,提升填写效率与用户体验。
+```
+
+---
+
+## 2. wx.getPhoneNumber(获取用户手机号)
+
+**申请接口理由:**
+
+```
+本小程序为创业者匹配与电子书付费平台,需手机号用于:一、用户身份校验,确保真实用户;二、创业伙伴匹配成功后交换联系方式;三、分销推广收益提现时的账户校验与到账通知。申请 wx.getPhoneNumber 后,用户授权即可获取微信绑定手机号,减少手动输入,提高注册与提现流程的完成率。
+```
+
+**备选(更简短):**
+
+```
+本小程序涉及付费阅读与分销提现,需手机号完成身份验证与提现到账。申请此接口可实现一键获取微信绑定手机号,提升用户注册与提现流程的完成率与安全性。
+```
+
+---
+
+## 3. wx.chooseLocation(打开地图选择位置)
+
+**申请接口理由:**
+
+```
+本小程序提供创业者线下见面与资源对接服务。用户发布合作需求或预约见面时,需选择具体见面地点。申请 wx.chooseLocation 后,用户可在地图上选点并获取详细地址与坐标,便于双方导航赴约,完成从线上匹配到线下见面的业务闭环。
+```
+
+**备选(更简短):**
+
+```
+本小程序为创业资源对接平台,用户匹配成功后需约定线下见面地点。申请此接口后,用户可在地图上选择位置并获取地址,方便双方导航见面。
+```
+
+---
+
+## 4. wx.choosePoi(打开 POI 列表选择位置)
+
+**申请接口理由:**
+
+```
+本小程序为创业者线下对接场景服务。用户约定见面地点时,除地图选点外,还需从咖啡馆、会议室等 POI 中选择具体场所。申请 wx.choosePoi 后,用户可从附近 POI 列表中快速选择地点,便于填写规范地址并提升约见效率。
+```
+
+---
+
+## 5. wx.getFuzzyLocation(获取当前模糊地理位置)
+
+**申请接口理由:**
+
+```
+本小程序需根据用户所在城市推荐同城创业伙伴与线下活动,不涉及精确定位。申请 wx.getFuzzyLocation 后,仅获取城市级模糊位置用于同城匹配与活动推荐,在满足业务需求的同时符合隐私最小化原则。
+```
+
+---
+
+## 6. wx.getLocation(获取当前精确地理位置)
+
+**申请接口理由:**
+
+```
+本小程序提供创业者线下见面与活动报名功能。用户参加线下沙龙、路演等活动时,需获取当前位置用于:一、展示与活动地点的距离;二、推荐附近的创业活动与伙伴。申请此接口以便用户查看「离我最近」的活动与匹配结果,提升线下参与率。
+```
+
+**说明:** 若类目为「商业服务-综合」等,审核可能较严,建议优先申请 wx.getFuzzyLocation,再视业务需要申请 wx.getLocation。
+
+---
+
+## 填写与提交建议
+
+1. **申请接口理由**:从上面选一段主文案粘贴,字数不够时用「备选」补充,总长不超过 300 字。
+2. **使用场景截图**:上传小程序内实际使用该能力的页面截图(如设置页地址、匹配页选地点、提现页手机号等),每张图对应一个场景。
+3. **小程序官网链接**:可填 `https://soul.quwanzhi.com`。
+4. **一次只申请一个接口**,通过后再申请下一个,通过率更高。
+
+---
+
+**文档更新日期:** 2026-01-29
diff --git a/开发文档/10、项目管理/永平版优化对比与合并说明.md b/开发文档/10、项目管理/永平版优化对比与合并说明.md
new file mode 100644
index 00000000..45f7e43f
--- /dev/null
+++ b/开发文档/10、项目管理/永平版优化对比与合并说明.md
@@ -0,0 +1,91 @@
+# 永平版 vs 主项目:优化对比与合并说明
+
+> 对比目录:主项目(一场soul的创业实验) vs 永平版(一场soul的创业实验-永平)
+> 更新日期:2026-02-20
+
+---
+
+## 一、两套目录结构概览
+
+| 项目 | 根目录特点 | Next 源码位置 |
+|------|------------|----------------|
+| **主项目** | 单仓:Next + book + miniprogram + 开发文档 | 根下 `app/`、`lib/`、`components/` |
+| **永平版** | 多仓:soul-api(Go)、soul-admin(Vue)、soul(Next) | `soul/dist/`(源码与构建同目录) |
+
+永平版还包含:`本机运行文档.md`、Go API(8080)、Vue 管理后台(静态)、开发 API(8081)。主项目为纯 Next 站 + 宝塔 3006 部署。
+
+---
+
+## 二、已合并到主项目的优化(本次迭代)
+
+| 模块 | 优化内容 | 主项目路径 |
+|------|----------|------------|
+| **数据库** | 环境变量 `MYSQL_*`、`SKIP_DB`、连接超时与单次连接错误日志 | `lib/db.ts` |
+| **数据库** | 订单表 status 增加 `created`/`expired`,字段 `referrer_id`/`referral_code`;用户表 ALTER 兼容 MySQL 5.7 | 同上 |
+| **认证** | 密码哈希/校验(scrypt,兼容旧明文) | `lib/password.ts`(新增) |
+| **认证** | Web 端手机号+密码登录 | `app/api/auth/login/route.ts`(新增) |
+| **认证** | 重置密码 | `app/api/auth/reset-password/route.ts`(新增) |
+| **后台** | 管理员登出(清除 Cookie) | `app/api/admin/logout/route.ts`(新增) |
+| **前端** | 仅生产环境加载 Vercel Analytics | `app/layout.tsx` |
+| **文档** | 本机/服务器运行说明(端口、目录、Nginx) | `开发文档/本机运行文档.md`(新增) |
+
+---
+
+## 三、永平有、主项目未合并的(可选后续)
+
+| 模块 | 说明 | 永平路径 | 合并建议 |
+|------|------|----------|----------|
+| 定时任务 | 订单状态同步、过期解绑 | `app/api/cron/sync-orders`、`cron/unbind-expired` | 若需定时同步/解绑再迁入;需配置 CRON_SECRET |
+| 提现扩展 | 待确认列表、提现记录 API | `withdraw/pending-confirm`、`withdraw/records` | 若后台要做提现工作流与记录查询可迁入 |
+| 用户 API | 购买状态、阅读进度、收货地址 CRUD | `user/check-purchased`、`user/reading-progress`、`user/addresses` | 按产品需要选择性迁入 |
+| 后台 | 分销概览 API、推广设置页 | `admin/distribution/overview`、`admin/referral-settings/page.tsx` | 若有分销看板/推广配置页可迁入 |
+| 前台 | 忘记密码页、我的地址列表/编辑/新增 | `app/view/login/forgot`、`app/view/my/addresses/*` | 主项目路由为 `app/login/`、`app/my/`,可对应新增 |
+| 构建 | standalone 复制 static/public、clean、write-warning | `scripts/prepare-standalone.js` 等 | 若主项目用 standalone 部署可迁入 |
+| 数据层 | Prisma 模型与迁移 | `prisma/schema.prisma`、迁移脚本 | 主项目当前为 mysql2;若统一用 Prisma 再迁 |
+| 路由结构 | 前台统一在 `app/view/` | 整棵 `app/view/` | 主项目保持扁平 `app/`,非必须 |
+
+---
+
+## 四、主项目保留、与永平不同的部分
+
+- **CORS**:主项目在 `middleware.ts` + `next.config.mjs` 的 headers 中配置 API CORS;永平可能用 Nginx/Go,未在 Next 层做。
+- **路由**:主项目前台为 `app/page.tsx`、`app/my/`、`app/read/` 等,无 `view` 前缀。
+- **book**:主项目根下保留 `book/` Markdown 与现有内容体系;永平书内容可能来自 API/DB。
+
+---
+
+## 五、环境变量说明(合并后)
+
+主项目 `.env.local` 建议支持(可选):
+
+```bash
+# 数据库(不设则用代码内默认值)
+MYSQL_HOST=
+MYSQL_PORT=
+MYSQL_USER=
+MYSQL_PASSWORD=
+MYSQL_DATABASE=
+
+# 本地无数据库时跳过连接(接口会报错,适合纯前端联调)
+SKIP_DB=0
+```
+
+---
+
+## 六、合并与实施注意
+
+1. **路径**:永平 Next 源码在 `soul/dist/`,合并到主项目时对应到根下 `app/`、`lib/`、`开发文档/`。
+2. **CORS**:保留主项目现有 `middleware.ts` 与 `next.config.mjs` 的 CORS 配置。
+3. **数据库**:主项目继续使用 mysql2,未引入 Prisma;`lib/db.ts` 已支持环境变量与 `SKIP_DB`。
+4. **admin 登出**:后台可增加「退出登录」按钮,请求 `POST /api/admin/logout` 后跳转登录页。
+
+5. **已有数据库**:若主项目此前已建过 `orders` 表且无 `referrer_id`/`referral_code` 或 status 无 `created`/`expired`,需自行执行迁移,例如:
+ ```sql
+ ALTER TABLE orders MODIFY COLUMN status ENUM('created','pending','paid','cancelled','refunded','expired') DEFAULT 'created';
+ ALTER TABLE orders ADD COLUMN referrer_id VARCHAR(50) NULL COMMENT '推荐人用户ID', ADD COLUMN referral_code VARCHAR(20) NULL COMMENT '下单时使用的邀请码';
+ ```
+ 若表为新建,`initDatabase()` 已包含上述结构。
+
+---
+
+**文档状态**:已合并项已落地;未合并项见第三节,按需迭代。
diff --git a/开发文档/10、项目管理/派对每日数据汇总.md b/开发文档/10、项目管理/派对每日数据汇总.md
new file mode 100644
index 00000000..7e66d1b8
--- /dev/null
+++ b/开发文档/10、项目管理/派对每日数据汇总.md
@@ -0,0 +1,61 @@
+# Soul 派对每日数据汇总
+
+按「派对小助手」表头整理的日维度数据,便于按天相加。
+
+## 表头说明(与第5张图一致)
+
+| 时长 | Soul推流人数 | 进房人数 | 人均时长 | 互动数量 | 礼物 | 灵魂力 | 增加关注 | 最高在线 |
+|------|--------------|----------|----------|----------|------|--------|----------|----------|
+
+- **时长**:派对总时长(分钟)
+- **Soul推流人数**:本场获得额外曝光(次)
+- **进房人数**:派对成员/进房总人数(人)
+- **人均时长**:人均停留时长(分钟)
+- **互动数量**:本场互动次数
+- **礼物**:本场收到礼物(个)
+- **灵魂力**:收获灵魂力
+- **增加关注**:新增粉丝(人)
+- **最高在线**:当日各场中最高同时在线人数(人),取最大值、不相加
+
+---
+
+## 当日汇总(一天相加后的数据)
+
+**日期**:2026-02-19(根据截图当日多场合并)
+
+| 时长 | Soul推流人数 | 进房人数 | 人均时长 | 互动数量 | 礼物 | 灵魂力 | 增加关注 | 最高在线 |
+|------|--------------|----------|----------|----------|------|--------|----------|----------|
+| 155 | 46749 | 545 | 7 | 34 | 1 | 8 | 13 | 47 |
+
+**计算说明:**
+
+- **时长**:54 + 22 + 79 = **155** 分钟(第3张与第4张为同一场,只计一次,取结算 79 分钟)
+- **Soul推流人数**:12588 + 7695 + 26466 = **46749** 次
+- **进房人数**:164 + 92 + 289 = **545** 人(同一场取 289,不重复加 279)
+- **人均时长**:仅一场有数据,取 **7** 分钟
+- **互动数量**:仅一场有数据,取 **34** 次
+- **礼物**:0 + 0 + 1 = **1** 个
+- **灵魂力**:0 + 0 + 8 = **8**
+- **增加关注**:2 + 4 + 7 = **13** 人(同一场只计 7,不重复加 5)
+- **最高在线**:取当日各场最高值 max(34, 30, 47) = **47** 人(不相加)
+
+---
+
+## 当日分场明细(便于核对)
+
+*说明:第3张(派对小助手浮层)与第4张(派对已关闭结算)为同一场派对,只计一场。*
+
+| 场次 | 时长(min) | 曝光/推流 | 进房人数 | 人均时长 | 互动 | 礼物 | 灵魂力 | 新增关注 | 最高在线 |
+|------|-----------|-----------|----------|----------|------|------|--------|----------|----------|
+| 1 | 54 | 12588 | 164 | — | — | 0 | 0 | 2 | 34 |
+| 2 | 22 | 7695 | 92 | — | — | 0 | 0 | 4 | 30 |
+| 3(图3+图4同场) | 79 | 26466 | 289 | 7 | 34 | 1 | 8 | 7 | 47 |
+| **合计** | **155** | **46749** | **545** | 7 | 34 | **1** | **8** | **13** | **47** |
+
+---
+
+**使用方式**:把「当日汇总」那一行加到总表或飞书运营报表。
+
+**导入飞书运营报表时**(脚本 `soul_party_to_feishu_sheet.py`):
+- 只填前 10 项(主题、时长、推流、进房、人均时长、互动、礼物、灵魂力、增加关注、最高在线),**按数字填写**。
+- 推流进房率、1分钟进多少人、加微率 **不填**,由表格公式自动计算。
diff --git a/开发文档/10、项目管理/运营报表与Soul聊天记录全量分析.md b/开发文档/10、项目管理/运营报表与Soul聊天记录全量分析.md
new file mode 100644
index 00000000..afb6ff42
--- /dev/null
+++ b/开发文档/10、项目管理/运营报表与Soul聊天记录全量分析.md
@@ -0,0 +1,111 @@
+# 运营报表 + Soul 聊天记录全量分析(10 月第一场至今)
+
+> 时间范围:2025 年 10 月第一场 → 2026 年 2 月(当前)。
+> 数据来源:飞书运营报表多月度汇总 + `聊天记录/soul` 目录下全部派对/会议 txt 与合并稿。
+
+---
+
+## 一、时间线与数据覆盖
+
+### 1.1 场次与日历对应(据聊天记录文件名整理)
+
+| 时期 | 日期范围 | 场次/内容 | 聊天记录文件情况 |
+|:---|:---|:---|:---|
+| **2025 年 10 月** | 10/25–10/31 | 最早场次(未统一编号) | 10月25日、26、27、30、31 等 txt;soul202510-20260102 含 10/22 起大段合并 |
+| **2025 年 11 月** | 11/4–11/27 | 多场,部分有 26场、27场、32场 等 | 11月多日 + 魔兽私服/留学/美业、电竞陪玩、学校创业等主题文件名 |
+| **2025 年 12 月** | 12/2–12/31 | 41场→50场(12/18)、51–62场 | 12月2日到31日;44场(12/11)、50场(12/18)、51–62场 等 |
+| **2026 年 1 月** | 1/1–1/31 | 62场(1/1)、83–90场 | 62场 1月1日;83–90场(1/26–2/3);团队会议 17场、39场 等 |
+| **2026 年 2 月** | 2/16–2/20 | 101–105场 | 101–105场 2/16–2/20;104场 妙记/纪要 等 |
+
+说明:10 月、11 月部分日期未在文件名中标「第 x 场」,按日期与后续 44–62 场反推,**第一场可视为 2025 年 10 月**;当前至 **105 场**(2026/02/20)。
+
+### 1.2 聊天记录全量统计
+
+| 项目 | 数值 |
+|:---|:---|
+| **txt 文件数(仅 .txt)** | 约 85+ 个 |
+| **时间跨度** | 2025-10-22/25 → 2026-02-20 |
+| **合并长稿** | soul202510-20260102.txt(约 8 万+ 行,10/22–1/2);soul派对会议到12月3日-1月7日.txt |
+| **含「场」编号的文件** | 41场–62场、83–90场、101–105场 等 |
+| **团队会议** | 12月11日(第一场)、17场、39场、产研第20场 等 |
+
+---
+
+## 二、运营报表数据(10 月至今)
+
+以下为飞书运营报表中**可解析的多月度**汇总(含 25年10月、2026年1月、2026年2月 等)。
+
+### 2.1 按时期汇总
+
+| 时期 | 有数据场次 | 总时长(分钟) | Soul推流 | 进房人数 | 互动 | 礼物 | 灵魂力 | 增加关注 | 最高在线 | 人均时长(分钟) |
+|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|
+| **25年10月** | 11 | 1,746 | 0* | 905 | 201 | 110 | 124 | 0* | 0* | — |
+| **2026年1月** | 27 | 3,659 | 772,133 | 9,690 | 4,841 | 177 | 22,644 | 496 | 75 | 10.3 |
+| **2026年2月** | 12 | 1,477 | 310,078 | 3,721 | 660 | 40 | 5,972 | 174 | 56 | 9.5 |
+| **合计(报表内)** | **50** | **6,882** | **1,082,211** | **14,316** | **5,702** | **327** | **28,740** | **670** | **75** | **11.2** |
+
+\* 25年10月 报表中推流/关注/最高在线 多为空或 0,仅部分指标有数。
+
+### 2.2 全量合计(含 10 月)
+
+- **总场次(有数据)**:约 50 场(报表填写);若含 10 月、11 月、12 月未完全入表场次,**实际派对场次从 10 月起累计约 105 场**。
+- **总时长**:报表内 6,882 分钟(约 114.7 小时);加 10 月 1,746 分钟 ≈ **8,628 分钟**(约 143.8 小时)。
+- **Soul 推流**:约 **108.2 万**(10 月表内未录推流)。
+- **进房人数**:约 **14,316**(表内)+ 10 月 905 ≈ **15,221**。
+- **互动 / 礼物 / 灵魂力 / 关注**:见表;10 月贡献 201 互动、110 礼物、124 灵魂力。
+
+---
+
+## 三、聊天记录内容与主题(全量视角)
+
+### 3.1 来源与结构
+
+- **单场/单日**:`soul 2025年10月25日.txt`、`soul 派对 第103场 20260218.txt` 等,多为「关键词 + 文字记录」或飞书妙记导出。
+- **长合并**:`soul202510-20260102.txt` 从 2025-10-22 到 2026-01-02,含大量逐字稿。
+- **主题在文件名中的体现**:电竞陪玩、学校创业、魔兽私服、留学、美业、小程序、书、分层与规则 等。
+
+### 3.2 高频主题与关键词(来自抽样与文件名)
+
+从 10 月、11 月及合并稿抽样可见,**贯穿全程的主题**包括:
+
+| 类别 | 关键词/主题 |
+|:---|:---|
+| **变现与生意** | 直播、电商、抖音、小红书、流量、粉丝、带货、知识付费、私域、微信、老板、生意、底层逻辑 |
+| **行业与赛道** | 主播方向、电竞、陪玩、留学、美业、魔兽私服、学校创业、财税、税筹、资源整合、行业整合 |
+| **组织与协作** | 客户、企业、体量、财务、风险、合作伙伴、中台、赋能、加盟、分账 |
+| **产品与项目** | 小程序、书、派对、分层、规则、落地执行、朋友圈素材、6980 套餐 |
+
+与项目「内容 + 私域 + 分销」和「谁在挣钱、怎么挣」的定位一致;聊天记录构成**书稿与运营动作的素材源**。
+
+### 3.3 与运营报表的对应关系
+
+- **有「场次」的聊天记录**(如 44–62、83–90、101–105)可与报表中「同场次」或「同日期列」的效果数据一一对应,做单场**内容主题 ↔ 进房/互动/关注**分析。
+- **10 月、11 月**部分仅有日期无场次号,可按日期与报表「日期列」或后续整理的场次映射做关联。
+- **团队会议**(12/11 第一场、17场、39场、产研20场)与「内部会议纪要」行、会议图片上传对应,用于复盘与决策追溯。
+
+---
+
+## 四、全量运营结论(10 月至今)
+
+1. **规模**
+ - 从 **2025 年 10 月第一场** 到 **2026 年 2 月 105 场**,报表内约 50 场有完整效果数据;总曝光约 **108 万推流**,进房约 **1.5 万+**,总时长约 **144 小时** 量级。
+2. **流量与沉淀**
+ - 1 月推流与进房最高(约 77 万、9,690);2 月场次与总量下降,人均时长与最高在线仍维持在约 9–10 分钟、56 人,单场质量未明显下滑。
+3. **内容与记录**
+ - **85+ 个 txt** 覆盖 10/22–2/20,含单场逐字稿与 10/22–1/2 长合并稿;主题覆盖电商、主播、电竞、财税、私域、小程序与书,与项目定位一致,可支撑书稿、复盘与运营分析。
+4. **数据完整性**
+ - 10 月、11 月报表字段不全(推流/关注/最高在线多缺),建议在报表或本地表中对 10–11 月做「补录或标注」,便于全周期对比;聊天记录与报表的「场次/日期」对应关系可固化为一张映射表,方便后续全量分析自动化。
+
+---
+
+## 五、建议的后续动作
+
+1. **报表**:对 10 月、11 月能做补录的场次补全推流/关注/最高在线;新场次继续按「日期列 + 场次」填写并发群(竖状)。
+2. **聊天记录**:保持「按场次/日期」命名;大段合并稿保留,并可与单场 txt 做交叉校验。
+3. **分析**:每月或每季度跑一次「10 月至今」全量汇总(报表 + 聊天记录文件清单),更新本文档第二节与第四节,并和项目目标(链接数、会员、付费)做对照。
+
+---
+
+**文档版本**:v1.0
+**数据基准**:飞书运营报表(25年10月、2026年1月、2026年2月);聊天记录目录 `聊天记录/soul` 下 85+ 个 txt 及 soul202510-20260102 等合并稿。
+**更新**:随新场次与补录数据更新上表与结论。
diff --git a/开发文档/10、项目管理/运营报表与项目运营分析.md b/开发文档/10、项目管理/运营报表与项目运营分析.md
new file mode 100644
index 00000000..be73908b
--- /dev/null
+++ b/开发文档/10、项目管理/运营报表与项目运营分析.md
@@ -0,0 +1,132 @@
+# 运营报表数据与项目运营分析
+
+> 基于飞书运营报表全量数据,结合「一场 SOUL 的创业实验」项目目标的运营视角分析。
+> 数据来源:飞书运营报表(Soul 派对效果数据);项目:推进表、派对定位、书/小程序闭环。
+
+---
+
+## 一、项目与派对定位(背景)
+
+| 维度 | 内容 |
+|:---|:---|
+| **项目名称** | 一场 SOUL 的创业实验场 |
+| **核心目标** | 内容阅读 + 私域引流 + 知识变现,验证「内容 + 私域 + 分销」商业闭环 |
+| **派对定位** | 晨间 6–9 点 Soul 语音派对,主题:谁在挣钱、怎么挣;链接创业/副业人群 |
+| **产出** | 《一场soul的创业实验》书籍内容、H5/小程序「卡若的创业派对」、会员群/资源群、线下见面 |
+| **阶段目标(历史)** | 前 50 场目标链接 1000 人(实际约 270,完成度 27%);51–100 场组建 7 管理、40+ 副业、90+ 老板、会员群;线下见面 30+ 人 |
+
+派对是**流量与链接入口**,书与小程序是**内容与变现载体**,运营报表衡量的是**入口侧的规模、参与度与沉淀**。
+
+---
+
+## 二、运营报表数据总览
+
+以下为从飞书运营报表汇总得到的多月度合计(含 1 月、2 月及可解析的其他月份)。
+
+### 2.1 全量汇总(多个月度合计)
+
+| 指标 | 数值 | 说明 |
+|:---|:---|:---|
+| **有数据场次** | 约 50 场 | 报表内已填写的场次 |
+| **总时长** | 约 6,882 分钟 | 约 114.7 小时 |
+| **Soul 推流人数** | 约 108.2 万 | 平台曝光量级 |
+| **进房人数** | 约 14,316 | 去重后约 1.4 万+ 进房 |
+| **互动数量** | 约 5,702 | 总互动次数 |
+| **礼物** | 约 327 | 场均约 6.5 个 |
+| **灵魂力** | 约 28,740 | 平台内成长值/积分 |
+| **增加关注** | 约 670 | 新增粉丝合计 |
+| **最高在线(单场最大)** | 75 人 | 各场峰值取 max |
+| **人均时长(有数据场平均)** | 约 11.2 分钟 | 停留质量参考 |
+
+### 2.2 分月对比(第 1 月 vs 第 2 月)
+
+| 指标 | 第 1 月(约 27 场) | 第 2 月(约 12 场) | 环比变化 |
+|:---|:---|:---|:---|
+| 总时长(分钟) | 3,659 | 1,477 | 场次减,总时长降 |
+| Soul 推流人数 | 772,133 | 310,078 | 约 -60% |
+| 进房人数 | 9,690 | 3,721 | 约 -62% |
+| 互动数量 | 4,841 | 660 | 约 -86% |
+| 礼物 | 177 | 40 | 约 -77% |
+| 灵魂力 | 22,644 | 5,972 | 约 -74% |
+| 增加关注 | 496 | 174 | 约 -65% |
+| 最高在线 | 75 | 56 | 峰值略降 |
+| 人均时长(分钟) | 10.3 | 9.5 | 基本稳定 |
+
+第 2 月有数据场次明显少于第 1 月(约 12 场 vs 27 场),各项总量随场次下降而下降;人均时长、最高在线等「单场质量」指标相对稳定,说明单场运营节奏未明显变差。
+
+### 2.3 单场样本(近期有完整数据的场次)
+
+| 场次 | 主题(核心干货) | 时长 | 推流 | 进房 | 人均时长 | 互动 | 礼物 | 灵魂力 | 关注 | 最高在线 |
+|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|
+| 99 | — | 116 | 16,976 | 208 | — | — | 4 | 166 | 12 | 39 |
+| 103 | 号商几毛卖十几 日销两万 | 155 | 46,749 | 545 | 7 | 34 | 1 | 8 | 13 | 47 |
+| 104 | AI创业最赚钱一月分享 | 140 | 36,221 | 367 | 7 | 49 | 0 | 0 | 11 | 38 |
+| 105 | 创业社群AI培训6980 电竞私域 | 138 | — | 403 | 10 | 170 | 2 | 24 | 31 | 54 |
+
+可看出的规律:单场时长约 2–2.5 小时;进房 300–500 量级与项目内「一场 300–600 人」一致;主题明确、有干货的场次(如 103、104、105)互动与关注更好。
+
+---
+
+## 三、运营视角分析
+
+### 3.1 流量与规模
+
+- **推流规模**:累计约 108 万推流,说明 Soul 侧给了可观曝光,派对作为内容形态被平台认可。
+- **进房转化**:进房约 1.43 万(去重后),相对 108 万推流,**推流→进房** 转化约 1.3% 量级;单场进房 300–500 是常态,与「每天 300–600 人」的定位一致。
+- **结论**:流量规模足够支撑「链接与内容沉淀」;若要进一步放大,可重点优化推流→进房(标题、时段、话题)和进房→停留(人均时长、互动设计)。
+
+### 3.2 参与与粘性
+
+- **人均时长**:有数据场平均约 10–11 分钟,说明多数用户是「短停留、多场次」;与「停留 10–15 分钟能听两三个视角」的设定吻合。
+- **互动**:总互动 5,702,场均约 114;近期场 34–170 不等,主题清晰、干货强的场互动更高。
+- **最高在线**:单场峰值 38–54(近期),历史最高 75;反映同时段「强参与」人数,可作为热力与内容爆点的观测指标。
+- **结论**:当前设计适合「轻参与、多触达」;若要做深链接,可针对「高停留/高互动」用户设计分层(如会员群、资源群入口),与项目「私域引流」目标对齐。
+
+### 3.3 沉淀与转化(关注、私域)
+
+- **增加关注**:累计约 670,约 50 场,场均约 13;近期单场 11–31。
+- **与项目目标的关系**:前 50 场目标链接 1000 人、实际约 270;关注数 670 是「Soul 内关注」口径,与「链接」(加微/进群)不是同一漏斗,但可视为上游指标。
+- **结论**:关注是私域的前置;若项目 KPI 是「加微/进群/会员」,需在报表或线下增加「进群数/加微数」等字段,并与 Soul 关注、进房、互动做交叉分析,才能看清「派对→私域→变现」的转化率。
+
+### 3.4 商业化与内容
+
+- **礼物/灵魂力**:礼物 327、灵魂力 28,740,更多反映平台内参与与打赏,不是项目主变现路径。
+- **主变现路径**:书/小程序(一天一块钱一节)、会员群、线下;派对侧主要贡献「流量 + 内容素材 + 链接机会」。
+- **结论**:运营报表侧重「派对效果」;若要全面评估项目,需把「小程序/书籍付费、会员、线下见面」等与报表做联动分析(例如:某月派对进房/关注上涨时,当月付费或进群是否同步变化)。
+
+### 3.5 稳定性与节奏
+
+- **开播节奏**:每天 6–9 点固定时段,有利于养成用户习惯和平台推流稳定性。
+- **场次与总量**:第 2 月有数据场次减少,总时长、推流、进房等随之下降;若 2 月存在春节等客观因素,可视为短期波动;若为主动收缩,需明确是「提质减量」还是「产能不足」,以便调整目标。
+- **结论**:建议按「周/月」固定复盘:场次、总时长、进房、关注、最高在线;并和「链接数、会员、付费」做简单对照,便于做运营决策。
+
+---
+
+## 四、与项目目标的对照
+
+| 项目目标 | 运营报表可支撑的观测 | 建议 |
+|:---|:---|:---|
+| **内容沉淀** | 总时长、场次、主题(表格内主题列) | 主题与书/小程序章节对应,便于「派对→内容→付费」追溯 |
+| **私域引流** | 进房、关注、互动 | 在报表或线下补充「进群/加微」数,与关注做漏斗分析 |
+| **知识变现** | 报表无直接指标 | 用独立维度记录:小程序付费、会员费、线下活动收入,与派对月度汇总对比 |
+| **链接人数/质量** | 进房、关注、最高在线 | 前 50 场链接 270 人可与「关注 670」对比口径;51–100 场管理/副业/老板数可作质量维度 |
+
+---
+
+## 五、结论与建议(运营视角)
+
+1. **报表价值**:当前运营报表已能清晰反映「派对规模、参与度、平台内沉淀」;与项目结合时,需补「私域与变现」口径,才能闭环评估。
+2. **流量与转化**:108 万推流、1.4 万+ 进房、670 关注,规模足够;下一步可重点看「进房→关注→加微/进群」的转化与节奏。
+3. **单场质量**:人均 10–11 分钟、最高在线 38–75,与「轻参与、多触达」定位一致;若要做深,可对高停留/高互动用户做分层运营。
+4. **节奏与目标**:建议每月固定做「场次、总时长、进房、关注、最高在线」与上月对比,并和链接数/会员/付费做简单对照;遇重大节日或策略调整时单独标注,便于归因。
+5. **数据一致性**:报表按「日期列/场次列」填写、发群用竖状格式,便于前后一致;会议纪要/今日总结用图片入格,有利于保留现场决策与复盘,与运营分析互补。
+
+---
+
+**文档版本**:v1.0
+**数据基准**:飞书运营报表多月度汇总(约 50 场有数据);项目信息来自推进表与派对/书/小程序定位。
+**更新建议**:每月或每季度在本文末追加「当月/当季小结」与目标达成情况,形成可延续的运营分析记录。
+
+---
+
+**延伸**:从**第一场(10 月)至今**的运营报表全量数据与 **Soul 聊天记录**(85+ 个 txt、合并稿)的全量分析见 → [运营报表与Soul聊天记录全量分析.md](./运营报表与Soul聊天记录全量分析.md)。
diff --git a/开发文档/10、项目管理/项目落地推进表.md b/开发文档/10、项目管理/项目落地推进表.md
index 24d2e23a..5f170860 100644
--- a/开发文档/10、项目管理/项目落地推进表.md
+++ b/开发文档/10、项目管理/项目落地推进表.md
@@ -348,8 +348,77 @@ vercel --prod
**项目状态**:✅ **已完成100%,可直接部署到生产环境**
-**建议下一步**:立即部署到Vercel,配置环境变量,测试支付流程
+**建议下一步**:按需接入永平版可选能力(定时任务、提现记录、地址管理、推广设置页等),见 `开发文档/永平版优化对比与合并说明.md`
-**最后更新时间**:2025-12-29 23:59
+**最后更新时间**:2026-02-20
**最后更新人**:卡若 (智能助手)
**项目交付状态**:✅ 完整交付
+
+---
+
+## 九、永平版优化合并迭代(2026-02-20)
+
+### 9.1 对比范围
+
+- **主项目**:`一场soul的创业实验`(单 Next 仓,根目录 app/lib/book/miniprogram)
+- **永平版**:`一场soul的创业实验-永平`(多仓:soul-api Go、soul-admin Vue、soul Next 在 soul/dist)
+
+### 9.2 已合并优化项
+
+| 模块 | 内容 | 路径/说明 |
+|------|------|------------|
+| 数据库 | 环境变量 MYSQL_*、SKIP_DB、连接超时与单次错误日志 | `lib/db.ts` |
+| 数据库 | 订单表 status 含 created/expired,字段 referrer_id/referral_code;用户表 ALTER 兼容 MySQL 5.7 | `lib/db.ts` |
+| 认证 | 密码哈希/校验(scrypt,兼容旧明文) | `lib/password.ts`(新增) |
+| 认证 | Web 手机号+密码登录、重置密码 | `app/api/auth/login`、`app/api/auth/reset-password`(新增) |
+| 后台 | 管理员登出(清除 Cookie) | `app/api/admin/logout`(新增)、`lib/admin-auth.ts`(新增) |
+| 前端 | 仅生产环境加载 Vercel Analytics | `app/layout.tsx` |
+| 文档 | 本机/服务器运行说明 | `开发文档/本机运行文档.md`(新增) |
+| 文档 | 永平 vs 主项目对比与可选合并清单 | `开发文档/永平版优化对比与合并说明.md`(新增) |
+
+### 9.3 可选后续合并(见永平版优化对比与合并说明)
+
+定时任务(订单同步/过期解绑)、提现待确认与记录 API、用户购买状态/阅读进度/地址 API、分销概览与推广设置页、忘记密码页与我的地址页、standalone 构建脚本、Prisma 等;主项目保持现有 CORS 与扁平 app 路由。
+
+---
+
+## 十、链路优化与 yongpxu-soul 对照(2026-02-20)
+
+### 10.1 链路优化(不改文件结构)
+
+- **文档**:已新增 `开发文档/链路优化与运行指南.md`,明确四条链路及落地方式:
+ - **后台鉴权**:admin / key123456(store + admin-auth 一致),登出可调 `POST /api/admin/logout`。
+ - **进群**:支付成功后由前端根据 `groupQrCode` / 活码展示或跳转;配置来自 `/api/config` 与后台「二维码管理」(当前存前端 store,刷新以接口为准)。
+ - **营销策略**:推广、海报、分销比例等以 `api/referral/*`、`api/db/config` 及 store 配置为准;内容以 `book/`、`lib/book-data.ts` 为准。
+ - **支付**:create-order → 微信/支付宝 notify → 校验 → 进群/解锁内容;保持现有 `app/api/payment/*` 与 `lib/payment*` 不变。
+- **协同**:鉴权、进群、营销、支付可多角色并行优化,所有改动限于现有目录与文件,不新增一级目录。
+- **运行**:以第一目录为基准,`pnpm dev` / 生产 build+standalone,端口 3006;详见 `开发文档/本机运行文档.md` 与链路指南内运行检查清单。
+
+### 10.2 yongpxu-soul 分支变更要点(已对照)
+
+- **相对 soul-content**:yongpxu-soul 主要增加部署与文档,业务代码与主项目一致。
+ - 新增:`scripts/deploy_baota.py`、`开发文档/8、部署/宝塔配置检查说明.md`、`开发文档/8、部署/当前项目部署到线上.md`、小程序相关(miniprogram 上传脚本、开发文档/小程序管理、开发文档/服务器管理)、`开发文档/提现功能完整技术文档.md`、`lib/wechat-transfer.ts` 等。
+ - 删除/合并:大量历史部署报告与重复文档(如多份「部署完成」「升级完成」等),功能迭代记录合并精简。
+- **结论**:业务链路(鉴权→进群→营销→支付)以**第一目录现有实现**为准;yongpxu-soul 的修改用于**部署方式、小程序发布、文档与运维**,不改变主项目文件结构与上述四条链路的代码归属。
+- **可运行性**:按《链路优化与运行指南》第七节检查清单自检后,项目可在不修改文件结构的前提下完成落地与运行。
+
+### 10.3 运行检查已执行(2026-02-20)
+
+- 已执行:`pnpm install`、`pnpm run build`、`pnpm dev` 下验证 `GET /`、`GET /api/config` 返回 200。
+- 执行记录详见 `开发文档/链路优化与运行指南.md` 第八节。
+- 结论:构建与开发环境运行正常,链路就绪。
+
+---
+
+## 十一、下一步行动计划(2026-02-20)
+
+| 优先级 | 行动项 | 负责模块 | 说明 |
+|--------|--------|----------|------|
+| P0 | 生产部署与回调配置 | 支付/部署 | 将当前分支部署至宝塔(或现有环境),配置微信/支付宝回调 URL 指向 `/api/payment/wechat/notify`、`/api/payment/alipay/notify`,并验证支付→到账→进群展示。 |
+| P1 | 进群配置持久化(可选) | 进群/配置 | 若需多环境或刷新不丢失:让 `/api/config` 或单独接口读取/写入 `api/db/config` 的 `payment_config.wechatGroupUrl`、活码链接;或后台「二维码管理」保存时调用 db 配置 API。 |
+| P1 | 后台「退出登录」对接 | 鉴权 | 在 `app/admin/layout.tsx` 将「返回前台」旁增加「退出登录」按钮,点击请求 `POST /api/admin/logout` 后跳转 `/admin/login`(若后续改为服务端 Cookie 鉴权即可生效)。 |
+| P2 | Admin 密码环境变量统一(可选) | 鉴权 | 在 `lib/store.ts` 的 `adminLogin` 中从 `process.env.NEXT_PUBLIC_ADMIN_USERNAME` / `NEXT_PUBLIC_ADMIN_PASSWORD` 读取(或通过小 API 校验),与 `lib/admin-auth.ts` 一致。 |
+| P2 | 营销与内容迭代 | 营销/内容 | 在现有结构内更新:`book/` 下 Markdown、`lib/book-data.ts` 章节与免费列表、`api/referral/*` 与 `api/db/config` 分销/推广配置;后台「系统设置」「内容管理」按需调整。 |
+| P2 | 文档与分支同步 | 文档 | 定期将 yongpxu-soul 的部署/小程序/运维文档变更合并到主分支或文档目录,保持《链路优化与运行指南》《本机运行文档》与线上一致。 |
+
+以上按 P0 → P1 → P2 顺序推进;P0 完成即可上线跑通整条链路,P1/P2 为体验与可维护性增强。
diff --git a/开发文档/1、需求/需求日志.md b/开发文档/1、需求/需求日志.md
new file mode 100644
index 00000000..814ddadd
--- /dev/null
+++ b/开发文档/1、需求/需求日志.md
@@ -0,0 +1,31 @@
+# 需求日志
+
+> 每次对话的需求自动追加到此表,含日期和状态。
+
+| 日期 | 需求描述 | 状态 | 版本 |
+|------|---------|------|------|
+| 2026-01-26 | 最近阅读显示章节真实名称 | 已完成 | v1.19 |
+| 2026-01-26 | 推广中心改为「我的收益」,移到昵称下方stats区 | 已完成 | v1.19 |
+| 2026-01-26 | 去掉推广中心入口 | 已完成 | v1.19 |
+| 2026-01-26 | 账号设置整合到「我的」概览区 | 已完成 | v1.19 |
+| 2026-01-26 | 首页精选推荐按后端文章阅读量排序 | 已完成 | v1.19 |
+| 2026-01-26 | 文章点击记录(user_tracks view_chapter) | 已完成 | v1.19 |
+| 2026-01-26 | 首页去掉「内容概览」,改为「创业老板排行」(4列网格头像+名字) | 已完成 | v1.19 |
+| 2026-01-26 | 新增VIP会员系统(1980元/年,365天,全部章节+匹配+排行展示+VIP标识) | 已完成 | v1.19 |
+| 2026-01-26 | VIP头像标识(非会员灰框/VIP金框+角标) | 已完成 | v1.19 |
+| 2026-01-26 | VIP详情页(权益说明+购买+资料填写) | 已完成 | v1.19 |
+| 2026-01-26 | 会员详情页(创业老板排行点击进详情) | 已完成 | v1.19 |
+| 2026-01-26 | 后端VIP API(purchase/status/profile/members) | 已完成 | v1.19 |
+| 2026-01-26 | 后端hot接口改按user_tracks阅读量排序 | 已完成 | v1.19 |
+| 2026-01-26 | 后端users表新增VIP字段+migrate | 已完成 | v1.19 |
+| 2026-01-26 | 开发文档精简为10个标准目录 | 已完成 | v1.19 |
+| 2026-01-26 | 创建项目SKILL.md | 已完成 | v1.19 |
+| 2026-01-26 | 需求日志规范化为结构化表格 | 已完成 | v1.19 |
+
+---
+
+## 历史记录(原始需求文本)
+
+### 2026-01-26
+
+我的足迹里最近阅读要写清楚章节名称;推广中心改成我的收益;待领收益改成我的收益显示金额;推广中心入口去掉;账号设置移到我的资料里面;首页精选推荐按后端文章阅读量排序,做点击记录;首页内容预览去掉,改成创业老板排行(4个一排头像+名字,点击进详情=优秀会员板块);开发文档只保留10个目录整合;SKILL管理项目开发;需求表记录每次对话;新增VIP会员(1980/年,365天权益,全部章节+匹配+排行展示+VIP标识,头像灰色/金色区分);后台新增会员管理;确保小程序与后端匹配正常使用。
diff --git a/开发文档/2、架构/链路优化与运行指南.md b/开发文档/2、架构/链路优化与运行指南.md
new file mode 100644
index 00000000..de283553
--- /dev/null
+++ b/开发文档/2、架构/链路优化与运行指南.md
@@ -0,0 +1,104 @@
+# 链路优化与运行指南
+
+> 以**第一个目录(主项目)**为基准,不修改文件与目录结构,仅明确「后台鉴权 → 进群 → 营销策略 → 支付」整条链路的落地与运行方式。
+> 更新日期:2026-02-20
+
+---
+
+## 一、链路总览
+
+```
+后台鉴权 → 进群(支付后跳转) → 营销策略(推广/活码/配置) → 支付(下单→回调→到账)
+```
+
+- **基准**:主项目现有 `app/`、`lib/`、`components/`、`app/api/` 结构不变。
+- **运行**:本机 `pnpm dev` 或生产 `pnpm build` + `node .next/standalone/server.js`,端口 3006(见 `开发文档/本机运行文档.md`)。
+- **配置**:前端通过 `ConfigLoader` 调用 `fetchSettings()` → `GET /api/config` 拉取配置并写入 store;后台「系统设置」「支付设置」「二维码管理」等仅改前端 store,刷新后由 `/api/config` 再次覆盖(当前 `/api/config` 为静态实现,如需持久化可后续对接 `GET /api/db/config`)。
+
+---
+
+## 二、后台鉴权
+
+| 项目 | 说明 |
+|------|------|
+| **入口** | `app/admin/login/page.tsx`,账号密码提交后调用 `store.adminLogin(username, password)`。 |
+| **校验** | `lib/store.ts` 内 `adminLogin`:`username === 'admin'` 且 `password === 'key123456'` 即通过,与 `.cursorrules`、`lib/admin-auth.ts` 默认一致。 |
+| **登出** | 可调用 `POST /api/admin/logout` 清除管理员 Cookie(当前后台为前端 store 登录,未使用 Cookie 时该接口仅清 Cookie,不影响已登录状态;若后续改为服务端 Cookie 鉴权,再在后台加「退出登录」按钮请求该接口)。 |
+| **环境变量** | `ADMIN_USERNAME` / `ADMIN_PASSWORD` 在 `lib/admin-auth.ts` 中生效;`store.adminLogin` 仍为写死 `key123456`,若需统一可从环境变量读(需改 store 一处)。 |
+
+**落地要点**:保持现有结构即可运行;默认 admin / key123456,与文档一致。
+
+---
+
+## 三、进群(支付后跳转)
+
+| 项目 | 说明 |
+|------|------|
+| **配置来源** | 前端:`settings.paymentMethods.wechat.groupQrCode`、`settings.liveQRCodes`(活码多链接)。来源为 `fetchSettings()` → `GET /api/config` 与 store 默认值合并。 |
+| **后台配置** | 「二维码管理」页(`app/admin/qrcodes/page.tsx`):可改微信群活码多链接、微信群跳转链接;保存后写入 **前端 store**(`updateSettings`),刷新页面会重新从 `/api/config` 拉取,当前接口为静态,故刷新后可能恢复为代码默认;若需持久化,需后续让 `/api/config` 或单独接口读/写 `api/db/config` 的 `payment_config.wechatGroupUrl` 等。 |
+| **支付成功** | 支付成功后的「进群」行为由前端驱动:如展示群二维码、或跳转 `groupQrCode` / 活码 URL(`getLiveQRCodeUrl`)。 |
+| **静态配置** | `app/api/config/route.ts` 中 `paymentMethods.wechat.groupQrCode`、`marketing.partyGroup` 等可改代码内默认,部署后生效。 |
+
+**落地要点**:当前不改文件结构即可跑通;进群链接/活码以后台「二维码管理」或直接改 `app/api/config/route.ts` 默认值均可;若要多环境/持久化,再对接 db 配置。
+
+---
+
+## 四、营销策略
+
+| 项目 | 说明 |
+|------|------|
+| **配置** | 站点名、作者信息、派对房时间、Banner 等:`/api/config` 返回的 `siteConfig`、`authorInfo`、`marketing.banner` 等,经 `fetchSettings` 合并进 store。 |
+| **推广** | 邀请码绑定 `POST /api/referral/bind`,推广数据 `GET /api/referral/data`,访问记录 `POST /api/referral/visit`;分销比例等见 `api/db/config` 的 `referral_config`(后台「系统设置」可调)。 |
+| **海报** | 推广海报由前端组件(如 `components/modules/referral/poster-modal.tsx`)生成,依赖 store 中的用户与配置。 |
+| **内容** | 书籍章节、免费章节列表等:来自 `lib/book-data` + 接口(如 `api/book/*`、`api/content`);内容修改以第一个目录下 `book/` 及 `lib/book-data.ts` 为准,不新增目录。 |
+
+**落地要点**:营销与内容均以主项目现有模块为准;配置优先从 `/api/config`(及可选 db)读取,保证运行一致。
+
+---
+
+## 五、支付
+
+| 项目 | 说明 |
+|------|------|
+| **下单** | `POST /api/payment/create-order` 创建订单;参数与支付方式以 `lib/payment-service`、`lib/payment/*` 及后台「支付设置」相关配置为准。 |
+| **回调** | 微信 `POST /api/payment/wechat/notify`,支付宝 `POST /api/payment/alipay/notify`;支付网关配置回调 URL 至上述接口。 |
+| **前端回调** | 前端轮询或跳转:`/api/payment/verify`、`/api/payment/status/[orderSn]`、`/api/payment/callback`(当前 callback 为简单确认,实际到账以微信/支付宝 notify 为准)。 |
+| **与进群衔接** | 支付成功并校验通过后,前端根据 `settings.paymentMethods.wechat.groupQrCode` 或活码展示/跳转进群。 |
+
+**落地要点**:保持现有支付路由与 lib 不变;确保生产环境配置好微信/支付宝回调地址及密钥,即可跑通整条「支付 → 到账 → 进群」链路。
+
+---
+
+## 六、多端协同与 yongpxu-soul 分支
+
+- **主项目(第一目录)**:单仓 Next,鉴权/进群/营销/支付均按上文链路运行,不新增目录、不改变现有文件结构。
+- **yongpxu-soul 分支**:在现有基础上增加了部署脚本(如 `scripts/deploy_baota.py`)、小程序构建与上传、开发文档(小程序管理、服务器管理、提现功能文档等),以及部分依赖与配置;**业务链路(鉴权→进群→营销→支付)与主项目一致**,仍以 `app/`、`lib/`、`app/api/` 现有实现为准。
+- **协同方式**:多个角色可并行优化——例如:A 负责后台鉴权与登出对接;B 负责进群配置与活码持久化方案;C 负责营销配置与内容更新;D 负责支付回调与对账——所有改动均限制在现有文件与路由内,不增加新的一级目录或拆仓。
+
+---
+
+## 七、运行检查清单(保证可运行)
+
+1. **环境**:`pnpm install`,可选 `.env.local` 配置 `MYSQL_*`、`SKIP_DB`(见 `开发文档/本机运行文档.md`)。
+2. **鉴权**:访问 `/admin/login`,admin / key123456 可进入后台。
+3. **配置**:首页或任意页加载时 `ConfigLoader` 会请求 `/api/config`;若接口失败,前端使用 store 默认值仍可浏览。
+4. **进群**:后台「二维码管理」配置群链接/活码后,支付成功页或相关弹窗可展示/跳转(当前为前端 store,刷新后以 `/api/config` 为准)。
+5. **营销**:推广链接、海报、分销比例依赖 store 与 `api/referral/*`、`api/db/config`,按现有逻辑即可。
+6. **支付**:创建订单 → 支付 → 微信/支付宝回调至 `/api/payment/wechat/notify`、`/api/payment/alipay/notify`;前端校验订单状态后展示进群或解锁内容。
+
+按上述清单自检后,整条链路可在不修改文件结构的前提下完成落地与运行;后续迭代(如活码持久化、admin 密码从环境变量读取)可在对应单文件内扩展。
+
+---
+
+## 八、运行检查执行记录(2026-02-20)
+
+| 检查项 | 结果 | 说明 |
+|--------|------|------|
+| 环境 | ✅ | `pnpm install` 成功;可选 `.env.local` 配置 `MYSQL_*`、`SKIP_DB`。 |
+| 构建 | ✅ | `pnpm run build` 成功,Next.js 16.0.10,output: standalone。 |
+| 首页 | ✅ | 开发环境 `pnpm dev` 启动后 `GET /` 返回 200。 |
+| 配置接口 | ✅ | `GET /api/config` 返回 200,含 paymentMethods、marketing 等。 |
+| 鉴权 | ✅ | 路由 `/admin/login` 存在;store.adminLogin 与 admin/key123456 一致。 |
+| 进群/营销/支付 | ✅ | 路由与 store 配置完整;支付回调路由 `/api/payment/wechat/notify`、`/api/payment/alipay/notify` 已存在。 |
+
+结论:项目在未修改文件结构下可正常构建与运行,链路(鉴权→进群→营销→支付)就绪。
diff --git a/开发文档/提现功能完整技术文档.md b/开发文档/5、接口/提现功能完整技术文档.md
similarity index 100%
rename from 开发文档/提现功能完整技术文档.md
rename to 开发文档/5、接口/提现功能完整技术文档.md
diff --git a/开发文档/API/配置清单.md b/开发文档/5、接口/配置清单.md
similarity index 100%
rename from 开发文档/API/配置清单.md
rename to 开发文档/5、接口/配置清单.md
diff --git a/开发文档/6、后端/内容创建问题修复说明.md b/开发文档/6、后端/内容创建问题修复说明.md
new file mode 100644
index 00000000..a21ec1ea
--- /dev/null
+++ b/开发文档/6、后端/内容创建问题修复说明.md
@@ -0,0 +1,93 @@
+# 内容创建问题修复说明
+
+> 问题:souladmin 添加内容后显示「创建成功」,但目录和数据库未增加,前端也未显示。
+
+## 根因分析
+
+1. **两套后台数据源不一致**
+ - souladmin.quwanzhi.com 调用 soulapi.quwanzhi.com(Go API)
+ - soul.quwanzhi.com/admin 使用 Next.js API,list 此前仅从 bookData(静态)读取
+ - 新建章节写入数据库,但 list 不查库,导致新建内容不显示
+
+2. **PUT 创建未完整支持 partId/chapterId**
+ - 新建章节时 partId、chapterId、partTitle、chapterTitle 未正确写入数据库
+
+## 已做修复
+
+### 1. 修改 `/api/db/book` list 接口
+- **原逻辑**:仅从 bookData 读取
+- **现逻辑**:优先从数据库 chapters 表读取,再与 bookData 合并
+- **效果**:新建章节会立即出现在列表中
+
+### 2. 修改 PUT 接口支持新建章节
+- 支持 body 传入 `partId`、`chapterId`、`partTitle`、`chapterTitle`、`isFree`
+- 新建章节能正确写入数据库
+
+### 3. 在 book-data 中新增 9.15
+- 章节 ID: 9.15
+- 标题: 第102场|今年第一个红包你发给谁
+- 文件: book/第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例/9.15 第102场|今年第一个红包你发给谁.md
+
+### 4. soul-admin 改用 soul.quwanzhi.com 作为 API
+- 修改 soul-admin 的 API 基址:soulapi → soul.quwanzhi.com
+- 在 Next.js 中为 souladmin.quwanzhi.com 配置 CORS
+
+## 部署步骤
+
+### 步骤 1:部署 soul 主站(小型宝塔)
+
+```bash
+cd /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验
+# 按 .cursorrules 中的流程执行
+pnpm build
+# 然后执行部署脚本
+```
+
+### 步骤 2:同步 9.15 到数据库
+
+部署后访问 soul.quwanzhi.com/admin,在内容管理页面点击「同步到数据库」,将包含 9.15 的 bookData 同步进库。
+
+### 步骤 3:部署修改后的 soul-admin(KR 宝塔)
+
+```bash
+# 将 一场soul的创业实验-永平 中的 soul-admin/dist 上传到 KR 宝塔
+cd /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平
+tar -czf soul-admin-dist.tar.gz soul-admin/dist
+sshpass -p 'Zhiqun1984' scp -P 22022 soul-admin-dist.tar.gz root@43.139.27.93:/tmp/
+sshpass -p 'Zhiqun1984' ssh -p 22022 root@43.139.27.93 "
+ cd /www/wwwroot/自营/soul-admin
+ rm -rf dist.bak
+ mv dist dist.bak 2>/dev/null || true
+ tar -xzf /tmp/soul-admin-dist.tar.gz -C .
+ rm /tmp/soul-admin-dist.tar.gz
+"
+```
+
+### 步骤 4:校验
+
+1. 打开 souladmin.quwanzhi.com/content
+2. 新建章节,确认创建后列表中立即出现
+3. 刷新 soul.quwanzhi.com 主站,确认新章节可读
+
+## 注意事项
+
+- souladmin 现改为调用 soul.quwanzhi.com,不再调用 soulapi(Go),需确保 soul 主站可用
+- 若仍需使用 Go API,需在 soul-api 源码中修复 list/create 逻辑
+
+---
+
+## 内容上传 API(供科室/Skill 调用)
+
+- **地址**:`POST /api/content/upload`
+- **Content-Type**:`application/json`
+- **Body 字段**:
+ - `title`(必填):节标题
+ - `price`:定价,默认 1
+ - `content`:正文(Markdown 或 HTML)
+ - `format`:`markdown` | `html`,默认 `markdown`
+ - `images`:图片 URL 数组;正文中可用 `{{image_0}}`、`{{image_1}}` 占位,会替换为对应图片的 Markdown 图链
+ - `partId`、`partTitle`、`chapterId`、`chapterTitle`:归属篇/章,可选
+ - `isFree`:是否免费,默认 false
+ - `sectionId`:指定节 ID,不传则自动生成(如 `upload.标题slug.时间戳`)
+- **返回**:`{ success, id, message, title, price, isFree, wordCount }`
+- 写入数据库 `chapters` 表,list/目录会从库中读取并去重显示。
diff --git a/开发文档/8、部署/小程序上传复盘_2026-02-23.md b/开发文档/8、部署/小程序上传复盘_2026-02-23.md
new file mode 100644
index 00000000..643f2a38
--- /dev/null
+++ b/开发文档/8、部署/小程序上传复盘_2026-02-23.md
@@ -0,0 +1,75 @@
+# 小程序同步与上传复盘(2026-02-23)
+
+## 目标 & 结果
+
+- **目标**:将 GitHub 仓库 `fnvtk/Mycontent` 分支 `yongpxu-soul` 下 **miniprogram** 最新版同步到本地,并上传到微信公众平台(腾讯侧小程序后台)。
+- **结果**:本地已与 GitHub 最新版一致;小程序已成功上传至微信,版本号 **1.17**,描述为「从GitHub(yongpxu-soul)同步最新版」。
+
+---
+
+## 过程
+
+1. **从 GitHub 拉取 miniprogram**
+ - 克隆仓库:`git clone --depth 1 --branch yongpxu-soul https://github.com/fnvtk/Mycontent.git`(临时目录)。
+ - 使用 `rsync -av --delete` 将 `Mycontent/miniprogram/` 覆盖到本地目录:
+ `一场soul的创业实验/miniprogram/`。
+ - 同步后删除临时克隆目录。
+
+2. **本地上传能力整理**
+ - 为 `miniprogram/上传小程序.py` 增加 **Mac 微信开发者工具 CLI** 路径:
+ `/Applications/wechatwebdevtools.app/Contents/MacOS/cli`(及用户目录下的备用路径),便于在 Mac 上自动找到 CLI。
+ - 执行 `python3 上传小程序.py` 时,因 **未配置 private.key**(密钥未入库、本机未放置),脚本在「检查上传密钥」步骤退出,未执行实际上传。
+
+3. **改用微信开发者工具 CLI 直接上传**
+ - 使用本机已安装的微信开发者工具 CLI,不依赖 private.key,执行:
+ `cli upload --project --version 1.17 --desc "从GitHub(yongpxu-soul)同步最新版"`。
+ - CLI 自动完成连接/启动服务、拉取 AppID 权限、打包上传;上传成功,包体积约 259.9 KB。
+
+---
+
+## 本次更新内容(相对你之前本地的版本)
+
+- **来源**:GitHub `fnvtk/Mycontent` 分支 `yongpxu-soul` 的 `miniprogram` 目录(与当前本地已一致)。
+- **主要结构**(与 README 一致):
+ - **入口与配置**:`app.js`、`app.json`、`app.wxss`,`project.config.json`(AppID:wxb8bbb2b10dec74aa)、`sitemap.json`。
+ - **页面**:首页、目录、找伙伴、我的、阅读、关于作者、推广中心、订单、设置、搜索(见 `app.json` pages)。
+ - **能力**:自定义 TabBar、阅读/付费墙、分享海报、推广佣金、支付等;后端基地址 `https://soul.quwanzhi.com`。
+ - **脚本与文档**:`上传小程序.py`、`upload.js`、`小程序快速配置指南.md`、`小程序部署说明.md`、`自动部署.sh`、`编译小程序.bat/.ps1` 等。
+- **脚本层面**:仅在 `上传小程序.py` 中新增 Mac 版微信开发者工具 CLI 路径,便于后续在 Mac 上一键上传(仍可选配 private.key 使用 Node/miniprogram-ci 方式)。
+
+---
+
+## 反思
+
+- **private.key**:正确做法是不把密钥提交到 Git;本机若要用 `上传小程序.py` 或 `upload.js`(miniprogram-ci)上传,需在 [微信公众平台 → 开发管理 → 开发设置 → 小程序代码上传密钥] 下载密钥,重命名为 `private.key` 并放到 `miniprogram/` 目录。
+- **Mac 上传方式**:在未配置 private.key 的情况下,本机通过 **微信开发者工具 CLI** 直接上传可行(CLI 会启动或连接本地 IDE 服务完成上传),适合当前「同步 GitHub 后快速上传」的流程。
+
+---
+
+## 总结
+
+- 本地 **miniprogram** 已与 GitHub `yongpxu-soul` 最新版一致。
+- 小程序已上传至微信公众平台,**版本 1.17**;上传方式为本次使用的微信开发者工具 CLI(未使用 private.key)。
+- 后续如需继续用「脚本/CI」上传,可在 `miniprogram/` 下配置 `private.key` 后使用 `上传小程序.py` 或 `upload.js`;若仅本机上传,可继续使用 CLI 命令。
+
+---
+
+## 执行(后续建议)
+
+1. **微信公众平台**
+ - 登录 [mp.weixin.qq.com](https://mp.weixin.qq.com/) → 版本管理。
+ - 确认开发版 **1.17** 已出现;如需给体验人员使用,可设为「选为体验版」;准备发正式版则「提交审核」。
+
+2. **下次从 GitHub 同步后再上传**
+ - 同步代码(同上 rsync 或你已有的脚本)。
+ - 上传命令示例(在终端执行):
+ ```bash
+ /Applications/wechatwebdevtools.app/Contents/MacOS/cli upload \
+ --project "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram" \
+ --version "1.18" \
+ --desc "本次更新说明"
+ ```
+ 将 `1.18` 和 `本次更新说明` 按实际版本与描述修改即可。
+
+3. **可选**
+ - 若希望用 `上传小程序.py` 在 Mac 上一键上传,可将从公众平台下载的代码上传密钥重命名为 `private.key` 放入 `miniprogram/`,再运行 `python3 上传小程序.py`。
diff --git a/开发文档/8、部署/本机运行文档.md b/开发文档/8、部署/本机运行文档.md
new file mode 100644
index 00000000..91121b0d
--- /dev/null
+++ b/开发文档/8、部署/本机运行文档.md
@@ -0,0 +1,98 @@
+# Soul 主站 · 本机运行文档
+
+> 主项目(一场soul的创业实验)本机与服务器运行说明。永平版多服务架构见「永平版优化对比与合并说明」中的本机运行文档参考。
+
+---
+
+## 一、主项目运行架构(单 Next 站)
+
+### 1.1 进程与端口
+
+| 说明 | 端口 | 命令 |
+|----------|------|------|
+| 开发 | 3000 | `pnpm dev`(Next 默认) |
+| 生产 | 3006 | `pnpm build` 后 `PORT=3006 HOSTNAME=0.0.0.0 node .next/standalone/server.js` |
+
+### 1.2 目录与部署
+
+- **本地开发**:根目录即 Next 源码,`app/`、`lib/`、`components/`、`book/`、`miniprogram/` 同层。
+- **生产部署**:小型宝塔 42.194.232.22,项目路径 `/www/wwwroot/soul`,PM2 进程名 `soul`,端口 3006。
+
+---
+
+## 二、本机运行步骤
+
+### 2.1 安装依赖
+
+```bash
+pnpm install
+```
+
+### 2.2 开发模式
+
+```bash
+pnpm dev
+```
+
+- 默认端口 3000,可在 `package.json` 或环境变量中指定 `PORT=3006`。
+- 访问:http://localhost:3000(或 http://localhost:3006)
+
+### 2.3 生产模式(本地模拟)
+
+```bash
+pnpm build
+PORT=3006 HOSTNAME=0.0.0.0 node .next/standalone/server.js
+```
+
+- 需先完成 `pnpm build`,standalone 输出在 `.next/standalone/`。
+- 环境变量:`.env.local` 中配置 `MYSQL_*`(可选)、`SKIP_DB`(本地无 DB 时可设 `SKIP_DB=1`,部分接口会报错,适合纯前端联调)。
+
+### 2.4 数据库
+
+- 默认使用腾讯云 MySQL(见 `lib/db.ts` 默认值)。
+- 本地无数据库时:设置 `SKIP_DB=1`,接口中依赖 DB 的会抛错,可配合 Mock 或仅跑静态页。
+- 环境变量覆盖:`MYSQL_HOST`、`MYSQL_PORT`、`MYSQL_USER`、`MYSQL_PASSWORD`、`MYSQL_DATABASE`。
+
+---
+
+## 三、关键配置
+
+### 3.1 环境变量(.env.local)
+
+| 配置项 | 说明 |
+|--------|------|
+| MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD / MYSQL_DATABASE | 数据库连接,不设则用代码默认值 |
+| SKIP_DB | 设为 1 或 true 时跳过 DB 连接,适合无 DB 环境 |
+| ADMIN_USERNAME / ADMIN_PASSWORD | 后台管理员账号密码(默认 admin / key123456) |
+| ADMIN_SESSION_SECRET | 管理员 Cookie 签名密钥(生产建议修改) |
+
+### 3.2 管理后台
+
+- 登录:http://localhost:3000/admin/login(开发)或 /admin/login(生产)
+- 默认账号:admin / key123456(与 .cursorrules 一致,可通过环境变量覆盖)
+- 登出 API:`POST /api/admin/logout`(清除管理员 Cookie,可与「退出登录」按钮对接)
+
+---
+
+## 四、与永平版差异
+
+- **永平版**:多服务(Go API 8080、Vue 管理后台 5174、Next 主站 3006),见永平根目录 `本机运行文档.md`。
+- **本主项目**:单 Next 应用,无独立 Go/Vue,管理后台为 Next 内 `/admin`,API 为 Next 内 `/api/*`。
+- CORS:主项目在 `middleware.ts` 与 `next.config.mjs` 中配置;永平可能由 Nginx/Go 处理。
+
+---
+
+## 五、常见问题
+
+1. **端口被占用**
+ 修改启动命令:`PORT=3007 pnpm dev` 或 `PORT=3007 node .next/standalone/server.js`
+
+2. **数据库连接失败**
+ 检查 `.env.local` 中 `MYSQL_*` 及本机网络是否能访问腾讯云 MySQL;或设 `SKIP_DB=1` 做无 DB 联调。
+
+3. **API 跨域**
+ 主项目已通过 `middleware.ts` 为 `/api/:path*` 设置 CORS,允许来源见 `ALLOWED_ORIGINS`。
+
+---
+
+**文档状态**:适用于主项目单站部署与本机开发;多服务架构以永平版文档为准。
diff --git a/开发文档/SKILL.md b/开发文档/SKILL.md
new file mode 100644
index 00000000..5a69fb92
--- /dev/null
+++ b/开发文档/SKILL.md
@@ -0,0 +1,84 @@
+# Soul创业派对 - 项目开发SKILL
+
+## 项目概述
+
+| 项目 | 值 |
+|------|-----|
+| 项目名 | Soul创业派对(一场Soul的创业实验) |
+| 小程序AppID | wxb8bbb2b10dec74aa |
+| 后端地址 | https://soul.quwanzhi.com |
+| 技术栈(前端) | 微信小程序原生 + 自定义TabBar |
+| 技术栈(后端) | Next.js App Router + MySQL |
+| 数据库 | 腾讯云MySQL |
+| 仓库 | github.com/fnvtk/Mycontent (yongpxu-soul分支) |
+
+## 开发文档目录索引
+
+```
+开发文档/
+├── 1、需求/ ← 需求文档、需求日志、TDD方案
+├── 2、架构/ ← 系统架构、技术选型、链路说明
+├── 3、原型/ ← 原型设计
+├── 4、前端/ ← 前端架构、UI截图
+├── 5、接口/ ← API接口文档、接口定义规范
+├── 6、后端/ ← 后端架构、修复说明
+├── 7、数据库/ ← 数据库设计、管理规范
+├── 8、部署/ ← 部署流程、宝塔配置、小程序上传
+├── 9、手册/ ← 使用手册、写作手册
+├── 10、项目管理/ ← 项目总览、运营报表、会议记录
+├── 小程序管理/ ← 小程序生命周期SKILL(独立)
+└── 服务器管理/ ← 服务器运维SKILL(独立)
+```
+
+## 需求日志管理规范
+
+- 每次对话的需求自动追加到 `1、需求/需求日志.md`
+- 格式:`| 日期 | 需求描述 | 状态 | 备注 |`
+- 状态:待开发 / 开发中 / 已完成 / 已取消
+- 每个版本上传后,将该批需求标记为「已完成」
+
+## 常用命令
+
+### 上传小程序
+```bash
+/Applications/wechatwebdevtools.app/Contents/MacOS/cli upload \
+ --project "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram" \
+ --version "版本号" --desc "版本说明"
+```
+
+### 从GitHub同步miniprogram
+```bash
+cd /tmp && rm -rf Mycontent_soul_tmp
+git clone --depth 1 --branch yongpxu-soul https://github.com/fnvtk/Mycontent.git Mycontent_soul_tmp
+rsync -av --delete Mycontent_soul_tmp/miniprogram/ "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram/"
+rm -rf Mycontent_soul_tmp
+```
+
+### 数据库迁移
+```bash
+curl -X POST https://soul.quwanzhi.com/api/db/migrate -H 'Content-Type: application/json' -d '{}'
+```
+
+## 核心页面结构
+
+| 页面 | 路径 | 说明 |
+|------|------|------|
+| 首页 | pages/index/index | 精选推荐(阅读量)、创业老板排行 |
+| 目录 | pages/chapters/chapters | 章节列表 |
+| 找伙伴 | pages/match/match | 匹配动画 |
+| 我的 | pages/my/my | 用户信息、收益、VIP、账号设置 |
+| 阅读 | pages/read/read | 章节内容、付费墙 |
+| VIP | pages/vip/vip | VIP权益、购买、资料填写 |
+| 会员详情 | pages/member-detail/member-detail | 创业老板排行点击详情 |
+
+## 后端API模块
+
+| 模块 | 路径前缀 | 说明 |
+|------|---------|------|
+| VIP会员 | /api/vip/ | purchase、status、profile、members |
+| 书籍 | /api/book/ | chapters、hot、latest-chapters、search |
+| 用户 | /api/user/ | profile、update、track |
+| 支付 | /api/miniprogram/pay | 微信小程序支付 |
+| 推广 | /api/referral/ | bind、data、visit |
+| 提现 | /api/withdraw | 提现到微信零钱 |
+| 管理后台 | /api/admin/ | content、chapters、payment等 |
diff --git a/部署到GitHub与宝塔.sh b/部署到GitHub与宝塔.sh
new file mode 100755
index 00000000..08957aef
--- /dev/null
+++ b/部署到GitHub与宝塔.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# 1) 以本地为准推送到 GitHub yongpxu-soul
+# 2) 打包 → SCP 上传 → SSH 解压并 pnpm install + build
+# 3) 使用宝塔 API 重启 Node 项目(不用 pm2 命令)
+# 在「一场soul的创业实验」目录下执行
+
+set -e
+cd "$(dirname "$0")"
+
+echo "===== 1. 推送到 GitHub(以本地为准)====="
+git push origin yongpxu-soul --force-with-lease
+
+echo "===== 2. 打包 ====="
+tar --exclude='node_modules' --exclude='.next' --exclude='.git' -czf /tmp/soul_update.tar.gz .
+
+echo "===== 3. 上传到宝塔服务器 ====="
+sshpass -p 'Zhiqun1984' scp /tmp/soul_update.tar.gz root@42.194.232.22:/tmp/
+
+echo "===== 4. SSH:解压、安装、构建(不执行 pm2)====="
+sshpass -p 'Zhiqun1984' ssh root@42.194.232.22 "
+ cd /www/wwwroot/soul
+ rm -rf app components lib public styles *.json *.js *.ts *.mjs *.md .next
+ tar -xzf /tmp/soul_update.tar.gz
+ rm /tmp/soul_update.tar.gz
+ export PATH=/www/server/nodejs/v22.14.0/bin:\$PATH
+ pnpm install
+ pnpm run build
+"
+
+echo "===== 5. 宝塔 API 重启 Node 项目 soul ====="
+BT_HOST="42.194.232.22"
+BT_PORT="9988"
+BT_KEY="hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd"
+REQUEST_TIME=$(date +%s)
+# request_token = md5( request_time + md5(api_key) ),兼容 macOS/Linux
+md5hex() { printf '%s' "$1" | openssl md5 2>/dev/null | awk '{print $NF}' || true; }
+MD5_KEY=$(md5hex "$BT_KEY")
+SIGN_STR="${REQUEST_TIME}${MD5_KEY}"
+REQUEST_TOKEN=$(md5hex "$SIGN_STR")
+
+RESP=$(curl -s -k -X POST "https://${BT_HOST}:${BT_PORT}/project/nodejs/restart_project" \
+ -d "request_time=${REQUEST_TIME}" \
+ -d "request_token=${REQUEST_TOKEN}" \
+ -d "project_name=soul" 2>/dev/null || true)
+
+if echo "$RESP" | grep -q '"status":true\|"status": true'; then
+ echo "宝塔 API 重启成功: $RESP"
+else
+ echo "宝塔 API 返回(若失败请到面板手动重启): $RESP"
+fi
+
+echo "===== 部署完成 ====="
diff --git a/部署到Kr宝塔.sh b/部署到Kr宝塔.sh
new file mode 100755
index 00000000..16e88eea
--- /dev/null
+++ b/部署到Kr宝塔.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# 部署到 Kr宝塔 (43.139.27.93):打包 → SCP(端口22022) → SSH 解压构建 → 宝塔 API 重启
+# 不用 pm2 命令,用宝塔 API 操作。在「一场soul的创业实验」目录下执行。
+
+set -e
+cd "$(dirname "$0")"
+
+SSH_PORT="22022"
+BT_HOST="43.139.27.93"
+BT_PORT="9988"
+BT_KEY="qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
+PROJECT_PATH="/www/wwwroot/soul"
+PROJECT_NAME="soul"
+
+echo "===== 1. 打包 ====="
+tar --exclude='node_modules' --exclude='.next' --exclude='.git' -czf /tmp/soul_update.tar.gz .
+
+echo "===== 2. 上传到 Kr宝塔 (${BT_HOST}:${SSH_PORT}) ====="
+sshpass -p 'Zhiqun1984' scp -P "$SSH_PORT" /tmp/soul_update.tar.gz root@${BT_HOST}:/tmp/
+
+echo "===== 3. SSH:解压、安装、构建(不执行 pm2)====="
+sshpass -p 'Zhiqun1984' ssh -p "$SSH_PORT" root@${BT_HOST} "
+ mkdir -p ${PROJECT_PATH}
+ cd ${PROJECT_PATH}
+ rm -rf app components lib public styles *.json *.js *.ts *.mjs *.md .next
+ tar -xzf /tmp/soul_update.tar.gz
+ rm /tmp/soul_update.tar.gz
+ export PATH=/www/server/nodejs/v22.14.0/bin:\$PATH
+ [ -x \"\$(command -v pnpm)\" ] || npm i -g pnpm
+ pnpm install
+ pnpm run build
+"
+
+echo "===== 4. 宝塔 API 重启 Node 项目 ${PROJECT_NAME} ====="
+REQUEST_TIME=$(date +%s)
+md5hex() { printf '%s' "$1" | openssl md5 2>/dev/null | awk '{print $NF}' || true; }
+MD5_KEY=$(md5hex "$BT_KEY")
+SIGN_STR="${REQUEST_TIME}${MD5_KEY}"
+REQUEST_TOKEN=$(md5hex "$SIGN_STR")
+
+RESP=$(curl -s -k -X POST "https://${BT_HOST}:${BT_PORT}/project/nodejs/restart_project" \
+ -d "request_time=${REQUEST_TIME}" \
+ -d "request_token=${REQUEST_TOKEN}" \
+ -d "project_name=${PROJECT_NAME}" 2>/dev/null || true)
+
+if echo "$RESP" | grep -q '"status":true\|"status": true'; then
+ echo "宝塔 API 重启成功: $RESP"
+else
+ echo "宝塔 API 返回(若失败请到面板 网站→Node项目→${PROJECT_NAME}→重启): $RESP"
+fi
+
+echo "===== 部署到 Kr宝塔 完成 ====="