-```
-
-#### JS修改 (`miniprogram/pages/referral/referral.js`)
-
-**删除data中的isLoading:**
-```javascript
-data: {
- isLoading: false, // ❌ 删除
- // ...
-}
-```
-
-**使用微信原生API:**
-```javascript
-// 初始化数据
-async initData() {
- const { isLoggedIn, userInfo } = app.globalData
- if (isLoggedIn && userInfo) {
- // ✅ 显示微信原生loading
- wx.showLoading({
- title: '加载中...',
- mask: true // 防止触摸穿透
- })
-
- try {
- // ... 数据加载逻辑
-
- this.setData({
- // ... 设置数据
- })
-
- // ✅ 隐藏loading
- wx.hideLoading()
-
- } catch (e) {
- console.log('[Referral] ❌ API调用失败:', e)
- // ✅ 失败也要隐藏loading
- wx.hideLoading()
- }
- }
-}
-```
-
-#### WXSS修改 (`miniprogram/pages/referral/referral.wxss`)
-
-**删除:**
-```css
-/* ❌ 删除所有自定义loading样式 */
-.loading-overlay { ... }
-.loading-content { ... }
-.loading-spinner { ... }
-@keyframes spin { ... }
-.loading-text { ... }
-.content-loading { ... }
-```
-
-## 三、微信原生Loading API详解
-
-### wx.showLoading(Object)
-
-**参数说明:**
-```javascript
-wx.showLoading({
- title: '加载中...', // 提示的内容
- mask: true, // 是否显示透明蒙层,防止触摸穿透(建议true)
- success: function() {}, // 接口调用成功的回调函数(可选)
- fail: function() {}, // 接口调用失败的回调函数(可选)
- complete: function() {} // 接口调用结束的回调函数(可选)
-})
-```
-
-**特点:**
-- ✅ 原生组件,性能优异
-- ✅ 自动居中显示
-- ✅ 与微信小程序风格统一
-- ✅ 自带菊花图(loading icon)
-- ✅ 支持透明蒙层,防止用户误触
-
-### wx.hideLoading()
-
-**用法:**
-```javascript
-wx.hideLoading() // 无需参数,直接调用
-```
-
-**注意事项:**
-1. ⚠️ `wx.showLoading` 和 `wx.hideLoading` 必须配对使用
-2. ⚠️ 同一时间只能显示一个loading,多次调用 `wx.showLoading` 会覆盖
-3. ⚠️ 如果忘记调用 `wx.hideLoading`,用户会一直看到loading
-4. ✅ 建议在 `try-catch` 的 `finally` 或 `catch` 中也调用 `wx.hideLoading()`
-
-### 与 wx.showToast 的区别
-
-| 特性 | wx.showLoading | wx.showToast |
-|------|---------------|--------------|
-| 用途 | 数据加载中 | 操作结果反馈 |
-| 图标 | 菊花图(loading icon) | 成功/失败/警告图标 |
-| 自动关闭 | ❌ 需要手动 `wx.hideLoading()` | ✅ 自动关闭(默认1.5秒) |
-| 蒙层 | ✅ 支持mask参数 | ✅ 支持mask参数 |
-| 互斥 | 与 `wx.showToast` 互斥 | 与 `wx.showLoading` 互斥 |
-
-## 四、优化前后对比
-
-### 代码量对比
-
-| 文件 | 优化前 | 优化后 | 减少 |
-|------|--------|--------|------|
-| WXML | +7行 | 0行 | -7行 |
-| JS | `isLoading` state + `this.setData()` | 2行API调用 | -3行 |
-| WXSS | +50行 | 0行 | -50行 |
-| **总计** | **+60行** | **2行** | **-58行** |
-
-### 性能对比
-
-| 指标 | 自定义Loading | 微信原生Loading |
-|------|---------------|----------------|
-| 渲染性能 | 需要WXML渲染 | 原生组件,更快 |
-| 内存占用 | 额外DOM节点 | 无额外DOM |
-| 样式一致性 | 需要手动调整 | 与微信风格统一 |
-| 维护成本 | 高 | 低 |
-
-### 用户体验对比
-
-**自定义Loading:**
-- 需要自己设计样式
-- 可能与微信小程序风格不一致
-- 动画性能依赖CSS实现
-
-**微信原生Loading:**
-- ✅ 与微信小程序风格统一
-- ✅ 用户熟悉的交互体验
-- ✅ 性能更优
-
-## 五、最佳实践
-
-### 1. 基本用法
-
-```javascript
-async loadData() {
- wx.showLoading({ title: '加载中...', mask: true })
-
- try {
- const data = await fetchData()
- this.setData({ data })
- } catch (e) {
- wx.showToast({ title: '加载失败', icon: 'none' })
- } finally {
- wx.hideLoading() // ✅ 确保一定会关闭
- }
-}
-```
-
-### 2. 多个异步操作
-
-```javascript
-async loadMultipleData() {
- wx.showLoading({ title: '加载中...', mask: true })
-
- try {
- // 并行请求
- const [data1, data2, data3] = await Promise.all([
- fetchData1(),
- fetchData2(),
- fetchData3()
- ])
-
- this.setData({ data1, data2, data3 })
- } catch (e) {
- wx.showToast({ title: '加载失败', icon: 'none' })
- } finally {
- wx.hideLoading()
- }
-}
-```
-
-### 3. 动态提示文案
-
-```javascript
-async syncData() {
- wx.showLoading({ title: '同步数据中...', mask: true })
-
- try {
- await syncToServer()
- wx.hideLoading()
- wx.showToast({ title: '同步成功', icon: 'success' })
- } catch (e) {
- wx.hideLoading()
- wx.showToast({ title: '同步失败', icon: 'none' })
- }
-}
-```
-
-### 4. 防止重复调用
-
-```javascript
-data: {
- isRequesting: false // 添加请求锁
-},
-
-async loadData() {
- if (this.data.isRequesting) return // ✅ 防止重复请求
-
- this.setData({ isRequesting: true })
- wx.showLoading({ title: '加载中...', mask: true })
-
- try {
- const data = await fetchData()
- this.setData({ data })
- } finally {
- wx.hideLoading()
- this.setData({ isRequesting: false })
- }
-}
-```
-
-## 六、常见问题
-
-### Q1: Loading不关闭怎么办?
-
-**原因:**
-- 忘记调用 `wx.hideLoading()`
-- 请求出错,没有在catch中关闭
-
-**解决:**
-```javascript
-// ✅ 使用 finally 确保一定关闭
-try {
- // ...
-} finally {
- wx.hideLoading()
-}
-```
-
-### Q2: Loading和Toast冲突?
-
-**原因:**
-`wx.showLoading` 和 `wx.showToast` 互斥,同一时间只能显示一个。
-
-**解决:**
-```javascript
-// ❌ 错误:loading没关闭就显示toast
-wx.showLoading({ title: '加载中...' })
-wx.showToast({ title: '操作成功' }) // 不会显示
-
-// ✅ 正确:先关闭loading再显示toast
-wx.showLoading({ title: '加载中...' })
-await fetchData()
-wx.hideLoading()
-wx.showToast({ title: '操作成功' })
-```
-
-### Q3: 如何自定义Loading样式?
-
-**答:**
-微信原生Loading不支持自定义样式。如果需要完全自定义样式,可以:
-1. 使用自定义组件(本次优化前的方案)
-2. 使用第三方UI库(如Vant Weapp)
-3. 考虑是否真的需要自定义(推荐使用原生)
-
-### Q4: mask参数有什么用?
-
-**答:**
-`mask: true` 会显示一个透明蒙层,防止用户在loading期间点击其他元素。
-
-```javascript
-// ✅ 推荐:防止用户误触
-wx.showLoading({ title: '加载中...', mask: true })
-
-// ⚠️ 不推荐:用户可能在加载时误触其他按钮
-wx.showLoading({ title: '加载中...', mask: false })
-```
-
-## 七、总结
-
-### 优化成果
-
-- ✅ 代码量减少 97%(60行 → 2行)
-- ✅ 无需维护自定义CSS和WXML
-- ✅ 性能更优(原生组件)
-- ✅ 用户体验更好(与微信风格统一)
-- ✅ 维护成本更低
-
-### 建议
-
-1. **优先使用微信原生API**
- - `wx.showLoading()` / `wx.hideLoading()`
- - `wx.showToast()`
- - `wx.showModal()`
-
-2. **仅在必要时自定义**
- - 特殊设计需求
- - 品牌强相关的UI
- - 复杂交互场景
-
-3. **遵循最佳实践**
- - 使用 `try-finally` 确保loading关闭
- - 设置 `mask: true` 防止误触
- - 避免loading和toast冲突
-
----
-
-**优化时间**:2026-02-04
-**版本**:v2(微信原生API)
-**推荐指数**:⭐⭐⭐⭐⭐
diff --git a/开发文档/8、部署/分销中心接口优化实施记录.md b/开发文档/8、部署/分销中心接口优化实施记录.md
deleted file mode 100644
index 05885003..00000000
--- a/开发文档/8、部署/分销中心接口优化实施记录.md
+++ /dev/null
@@ -1,549 +0,0 @@
-# 分销中心接口优化实施记录
-
-## 一、优化概述
-
-**优化目标**:提升分销中心页面加载速度,减少数据库负载
-
-**优化时间**:2026-02-04
-
-**优化范围**:`/api/referral/data` 核心接口
-
-## 二、优化内容
-
-### 1. 核心优化 ⭐⭐⭐
-
-#### 合并统计查询
-
-**优化前:5个独立查询**
-```typescript
-// 查询1:用户基本信息
-SELECT id, nickname, referral_code, earnings, pending_earnings,
- withdrawn_earnings, referral_count
-FROM users WHERE id = ?
-
-// 查询2:绑定关系统计
-SELECT COUNT(*) as total,
- SUM(CASE WHEN status = 'active' AND expiry_date > NOW() THEN 1 ELSE 0 END) as active,
- ...
-FROM referral_bindings WHERE referrer_id = ?
-
-// 查询3:访问统计
-SELECT COUNT(DISTINCT visitor_id) as count
-FROM referral_visits WHERE referrer_id = ?
-
-// 查询4:付款统计
-SELECT COUNT(DISTINCT o.user_id) as paid_count,
- COALESCE(SUM(o.amount), 0) as total_amount
-FROM orders o
-JOIN referral_bindings rb ON o.user_id = rb.referee_id
-WHERE rb.referrer_id = ? AND o.status = 'paid'
-
-// 查询5:待审核提现金额
-SELECT COALESCE(SUM(amount), 0) as pending_amount
-FROM withdrawals WHERE user_id = ? AND status = 'pending'
-```
-
-**优化后:1个聚合查询**
-```typescript
-SELECT
- -- 用户基本信息
- u.id, u.nickname, u.referral_code, u.earnings, u.pending_earnings,
- u.withdrawn_earnings, u.referral_count,
-
- -- 绑定关系统计(子查询)
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id) as total_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND expiry_date > NOW()) as active_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND purchase_count > 0) as converted_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND (status IN ('expired', 'cancelled') OR (status = 'active' AND expiry_date <= NOW()))) as expired_bindings,
-
- -- 访问统计(子查询)
- (SELECT COUNT(DISTINCT visitor_id) FROM referral_visits WHERE referrer_id = u.id) as total_visits,
-
- -- 付款统计(子查询)
- (SELECT COUNT(DISTINCT o.user_id)
- FROM orders o
- JOIN referral_bindings rb ON o.user_id = rb.referee_id
- WHERE rb.referrer_id = u.id AND o.status = 'paid') as paid_count,
- (SELECT COALESCE(SUM(o.amount), 0)
- FROM orders o
- JOIN referral_bindings rb ON o.user_id = rb.referee_id
- WHERE rb.referrer_id = u.id AND o.status = 'paid') as total_amount,
-
- -- 待审核提现金额(子查询)
- (SELECT COALESCE(SUM(amount), 0) FROM withdrawals WHERE user_id = u.id AND status = 'pending') as pending_withdraw_amount
-
-FROM users u
-WHERE u.id = ?
-```
-
-**优化效果:**
-- ✅ 查询次数:5次 → 1次(减少80%)
-- ✅ 数据库往返:5次 → 1次(减少80%)
-- ✅ 预计响应时间:减少60-70%
-
-### 2. 减少数据传输量 ⭐⭐
-
-#### 列表数据量优化
-
-**优化前:**
-```typescript
-// 活跃用户列表
-LIMIT 50 // 50条
-
-// 已转化用户列表
-LIMIT 50 // 50条
-
-// 已过期用户列表
-LIMIT 50 // 50条
-
-// 收益明细
-LIMIT 30 // 30条
-
-// 总计:180条数据
-```
-
-**优化后:**
-```typescript
-// 活跃用户列表
-LIMIT 20 // 20条(↓60%)
-
-// 已转化用户列表
-LIMIT 20 // 20条(↓60%)
-
-// 已过期用户列表
-LIMIT 20 // 20条(↓60%)
-
-// 收益明细
-LIMIT 20 // 20条(↓33%)
-
-// 总计:80条数据(↓55%)
-```
-
-**优化效果:**
-- ✅ 数据量:180条 → 80条(减少55%)
-- ✅ 传输大小:~50KB → ~22KB(减少55%)
-- ✅ 加载速度:更快的数据传输和渲染
-
-### 3. 数据库索引优化 ⭐⭐⭐
-
-#### 新增索引
-
-**referral_bindings 表:**
-```sql
--- 推荐人 + 状态 + 过期日期
-CREATE INDEX idx_referrer_status_expiry
-ON referral_bindings(referrer_id, status, expiry_date);
-
--- 推荐人 + 购买次数
-CREATE INDEX idx_referrer_purchase
-ON referral_bindings(referrer_id, purchase_count);
-
--- 推荐人 + 最后购买时间
-CREATE INDEX idx_referrer_last_purchase
-ON referral_bindings(referrer_id, last_purchase_date);
-
--- 被推荐人 + 推荐人
-CREATE INDEX idx_referee_referrer
-ON referral_bindings(referee_id, referrer_id);
-```
-
-**orders 表:**
-```sql
--- 用户 + 状态 + 支付时间
-CREATE INDEX idx_user_status_paytime
-ON orders(user_id, status, pay_time);
-
--- 推荐人 + 状态
-CREATE INDEX idx_referrer_status
-ON orders(referrer_id, status);
-
--- 状态 + 支付时间
-CREATE INDEX idx_status_paytime
-ON orders(status, pay_time);
-```
-
-**withdrawals 表:**
-```sql
--- 用户 + 状态
-CREATE INDEX idx_user_status
-ON withdrawals(user_id, status);
-
--- 状态 + 创建时间
-CREATE INDEX idx_status_created
-ON withdrawals(status, created_at);
-```
-
-**users 表:**
-```sql
--- 推荐码
-CREATE INDEX idx_referral_code
-ON users(referral_code);
-```
-
-**优化效果:**
-- ✅ 查询效率提升:30-50%
-- ✅ 避免全表扫描
-- ✅ 减少数据库CPU使用率
-
-## 三、优化对比
-
-### 性能指标
-
-| 指标 | 优化前 | 优化后 | 提升 |
-|------|--------|--------|------|
-| **数据库查询次数** | 9个 | 5个 | ↓ 44% |
-| **统计查询次数** | 5个 | 1个 | ↓ 80% |
-| **列表查询次数** | 4个 | 4个 | - |
-| **返回数据量** | ~180条 | ~80条 | ↓ 55% |
-| **数据传输大小** | ~50KB | ~22KB | ↓ 55% |
-| **预计响应时间** | 500-800ms | 200-300ms | ↓ 60-70% |
-| **数据库负载** | 高 | 中 | ↓ 50% |
-
-### 架构优化
-
-**优化前:**
-```
-小程序请求
- ↓
-/api/referral/data
- ↓
-查询1:用户信息
-查询2:绑定统计
-查询3:访问统计
-查询4:付款统计
-查询5:待审核提现
-查询6:活跃用户列表
-查询7:转化用户列表
-查询8:过期用户列表
-查询9:收益明细
- ↓
-返回 ~50KB 数据(180条记录)
-```
-
-**优化后:**
-```
-小程序请求
- ↓
-/api/referral/data
- ↓
-查询1:聚合统计查询(包含用户信息、绑定统计、访问统计、付款统计、待审核提现)
-查询2:活跃用户列表(20条)
-查询3:转化用户列表(20条)
-查询4:过期用户列表(20条)
-查询5:收益明细(20条)
- ↓
-返回 ~22KB 数据(80条记录)
-```
-
-## 四、代码修改
-
-### 修改文件
-
-1. **后端API** (`app/api/referral/data/route.ts`)
- - 合并统计查询(lines 31-90)
- - 减少列表LIMIT(lines 120-169)
- - 更新注释说明(lines 1-18)
-
-2. **数据库索引** (`scripts/optimize-referral-indexes.sql`)
- - 创建所有推荐的索引
- - 包含验证和维护命令
-
-3. **文档** (`开发文档/8、部署/`)
- - 分销中心接口优化方案.md
- - 分销中心接口优化实施记录.md
-
-### 向后兼容
-
-✅ **完全兼容** - 优化后的接口响应格式与原格式完全一致,前端无需任何修改。
-
-```json
-{
- "success": true,
- "data": {
- // 所有字段保持不变,仅内部查询逻辑优化
- "bindingCount": 10,
- "visitCount": 50,
- "paidCount": 5,
- "totalCommission": 450.00,
- "availableEarnings": 300.00,
- "pendingWithdrawAmount": 100.00,
- "activeUsers": [...], // 数量减少,格式不变
- "convertedUsers": [...], // 数量减少,格式不变
- "expiredUsers": [...], // 数量减少,格式不变
- "earningsDetails": [...] // 数量减少,格式不变
- }
-}
-```
-
-## 五、部署步骤
-
-### Step 1: 备份数据库(必须)
-
-```bash
-# 备份整个数据库
-mysqldump -u root -p mycontent_db > backup_before_optimization_20260204.sql
-
-# 或只备份相关表
-mysqldump -u root -p mycontent_db referral_bindings orders withdrawals users > backup_tables_20260204.sql
-```
-
-### Step 2: 添加数据库索引
-
-```bash
-# 登录到数据库
-mysql -u root -p mycontent_db
-
-# 执行索引创建脚本
-source /path/to/scripts/optimize-referral-indexes.sql
-
-# 或者在Baota面板的phpMyAdmin中:
-# 1. 打开 SQL 标签
-# 2. 粘贴 optimize-referral-indexes.sql 内容
-# 3. 点击"执行"
-```
-
-### Step 3: 验证索引
-
-```sql
--- 查看新创建的索引
-SHOW INDEX FROM referral_bindings;
-SHOW INDEX FROM orders;
-SHOW INDEX FROM withdrawals;
-SHOW INDEX FROM users;
-
--- 分析表,更新统计信息
-ANALYZE TABLE referral_bindings;
-ANALYZE TABLE orders;
-ANALYZE TABLE withdrawals;
-ANALYZE TABLE users;
-```
-
-### Step 4: 部署代码
-
-```bash
-# 本地构建
-cd /e/Gongsi/Mycontent
-npm run build
-
-# 或使用 devlop.py 部署
-python devlop.py
-```
-
-### Step 5: 重启服务
-
-```bash
-# 重启 PM2
-pm2 restart mycontent-next
-
-# 查看日志
-pm2 logs mycontent-next --lines 100
-```
-
-### Step 6: 验证优化效果
-
-```bash
-# 1. 测试接口响应时间
-curl -w "@curl-format.txt" -o /dev/null -s "https://your-domain.com/api/referral/data?userId=xxx"
-
-# 2. 查看数据库慢查询日志
-tail -f /var/log/mysql/slow-query.log
-
-# 3. 使用浏览器开发者工具
-# - 打开 Network 标签
-# - 刷新分销中心页面
-# - 查看 /api/referral/data 请求时间
-```
-
-## 六、测试验证
-
-### 功能测试
-
-✅ **核心功能验证**
-- [ ] 分销中心页面正常加载
-- [ ] 统计数据显示正确(绑定数、访问数、付款数)
-- [ ] 收益数据准确(累计佣金、可提现、待审核)
-- [ ] 用户列表显示正常(活跃、转化、过期)
-- [ ] 收益明细显示完整
-
-✅ **边界情况测试**
-- [ ] 新用户(无任何数据)
-- [ ] 只有绑定无付款的用户
-- [ ] 有付款有提现的用户
-- [ ] 大量数据的用户(>100条绑定)
-
-### 性能测试
-
-✅ **响应时间测试**
-```bash
-# 使用 Apache Bench 压力测试
-ab -n 100 -c 10 "https://your-domain.com/api/referral/data?userId=xxx"
-
-# 预期结果:
-# - 平均响应时间 < 300ms ✅
-# - 95%请求 < 400ms ✅
-# - 无失败请求 ✅
-```
-
-✅ **数据库性能测试**
-```sql
--- 查看查询执行计划(确保使用了索引)
-EXPLAIN SELECT ...
-
--- 预期结果:
--- - type: ref 或 range(非 ALL 全表扫描)✅
--- - key: 使用了创建的索引 ✅
--- - rows: 扫描行数 < 1000 ✅
-```
-
-### 数据一致性验证
-
-✅ **统计数据验证**
-```sql
--- 手动验证统计数据准确性
-
--- 1. 绑定用户数
-SELECT COUNT(*) FROM referral_bindings
-WHERE referrer_id = 'xxx' AND status = 'active' AND expiry_date > NOW();
-
--- 2. 付款人数
-SELECT COUNT(DISTINCT o.user_id)
-FROM orders o
-JOIN referral_bindings rb ON o.user_id = rb.referee_id
-WHERE rb.referrer_id = 'xxx' AND o.status = 'paid';
-
--- 3. 待审核提现
-SELECT SUM(amount) FROM withdrawals
-WHERE user_id = 'xxx' AND status = 'pending';
-
--- 与API返回结果对比,确保一致 ✅
-```
-
-## 七、监控和回滚
-
-### 监控指标
-
-**关键指标:**
-1. **响应时间**:/api/referral/data 平均响应时间 < 300ms
-2. **错误率**:接口错误率 < 0.1%
-3. **数据库负载**:CPU使用率、慢查询数量
-
-**监控工具:**
-- PM2 日志监控
-- MySQL 慢查询日志
-- 浏览器开发者工具
-
-### 回滚方案
-
-**如果出现问题,可以快速回滚:**
-
-#### 方案1:代码回滚
-
-```bash
-# 1. 切换到优化前的Git提交
-git checkout
-
-# 2. 重新构建和部署
-npm run build
-pm2 restart mycontent-next
-
-# 3. 验证功能正常
-```
-
-#### 方案2:数据库回滚
-
-```bash
-# 1. 删除新创建的索引(如果索引导致问题)
-DROP INDEX idx_referrer_status_expiry ON referral_bindings;
-DROP INDEX idx_referrer_purchase ON referral_bindings;
-DROP INDEX idx_referrer_last_purchase ON referral_bindings;
-DROP INDEX idx_referee_referrer ON referral_bindings;
-DROP INDEX idx_user_status_paytime ON orders;
-DROP INDEX idx_referrer_status ON orders;
-DROP INDEX idx_status_paytime ON orders;
-DROP INDEX idx_user_status ON withdrawals;
-DROP INDEX idx_status_created ON withdrawals;
-DROP INDEX idx_referral_code ON users;
-
-# 2. 代码也回滚到优化前版本
-```
-
-## 八、预期收益
-
-### 用户体验提升
-
-- ✅ **加载速度更快** - 页面打开时间减少60-70%
-- ✅ **更流畅的交互** - 数据刷新更快,无明显延迟
-- ✅ **更好的移动体验** - 减少数据传输,节省流量
-
-### 服务器性能提升
-
-- ✅ **数据库负载降低** - 查询次数减少44%,负载降低50%
-- ✅ **更高的并发能力** - 可支持更多同时在线用户
-- ✅ **成本优化** - 数据库资源消耗减少,可降低服务器配置需求
-
-### 开发维护提升
-
-- ✅ **更清晰的代码结构** - 聚合查询更易理解和维护
-- ✅ **更好的可扩展性** - 为未来功能扩展打下基础
-- ✅ **完善的文档** - 详细的优化记录和测试验证
-
-## 九、后续优化建议
-
-### Phase 2: 进阶优化(可选)
-
-1. **配置缓存** ⭐⭐
- - 使用内存缓存配置数据(5分钟过期)
- - 减少配置查询次数
-
-2. **Redis缓存** ⭐
- - 缓存用户分销数据(1分钟过期)
- - 适用于高并发场景
- - 需要额外的Redis服务
-
-3. **CDN缓存** ⭐
- - 缓存小程序码图片
- - 减少重复生成
- - 需要CDN服务支持
-
-4. **懒加载优化** ⭐
- - 初始只加载核心统计数据
- - 用户切换Tab时按需加载列表
- - 进一步减少初始加载时间
-
-### Phase 3: 长期规划
-
-1. **分库分表** - 当数据量达到百万级时考虑
-2. **读写分离** - 使用MySQL主从复制,读请求分流
-3. **异步统计** - 使用消息队列异步更新统计数据
-4. **GraphQL API** - 支持客户端按需查询字段
-
-## 十、总结
-
-### 优化成果
-
-✅ **查询优化**:9个查询 → 5个查询(减少44%)
-✅ **响应时间**:500-800ms → 200-300ms(减少60-70%)
-✅ **数据传输**:~50KB → ~22KB(减少55%)
-✅ **数据库负载**:降低50%
-✅ **用户体验**:显著提升
-
-### 关键要点
-
-1. **合并统计查询** - 最重要的优化,减少80%的统计查询次数
-2. **添加数据库索引** - 确保查询效率,避免全表扫描
-3. **减少数据传输** - 列表数据量减少55%,加快传输速度
-4. **向后兼容** - API格式完全兼容,前端无需修改
-5. **完善的测试** - 功能测试、性能测试、数据一致性验证
-
-### 实施建议
-
-- ⭐⭐⭐ **立即实施** Phase 1 优化(本次已完成)
-- ⭐⭐ 根据实际需求考虑 Phase 2 进阶优化
-- ⭐ 长期规划 Phase 3,为未来扩展做准备
-
----
-
-**优化完成时间**:2026-02-04
-**优化负责人**:AI Assistant
-**文档版本**:v1.0
-**下次复查**:2026-03-04(1个月后评估效果)
diff --git a/开发文档/8、部署/分销中心接口优化方案.md b/开发文档/8、部署/分销中心接口优化方案.md
deleted file mode 100644
index 9bef91ee..00000000
--- a/开发文档/8、部署/分销中心接口优化方案.md
+++ /dev/null
@@ -1,361 +0,0 @@
-# 分销中心接口优化方案
-
-## 一、当前接口分析
-
-### 1. 接口调用情况
-
-**分销中心页面 (`miniprogram/pages/referral/referral.js`)**
-
-| 接口 | 调用时机 | 功能 | 优化空间 |
-|------|---------|------|---------|
-| `/api/referral/data` | 页面初始化 | 获取所有分销数据 | ⚠️ 需优化查询数量 |
-| `/api/miniprogram/qrcode` | 用户点击分享 | 生成小程序码 | ✅ 合理 |
-| `/api/withdraw` | 用户申请提现 | 提现接口 | ✅ 合理 |
-
-### 2. 核心接口 `/api/referral/data` 的查询情况
-
-**当前执行的数据库查询:**
-
-| 序号 | 查询目的 | 表 | 数据量 | 优化建议 |
-|------|---------|-----|--------|---------|
-| 1 | 获取用户基本信息 | `users` | 1行 | 🔄 可合并 |
-| 2 | 获取绑定关系统计 | `referral_bindings` | 1行(聚合) | 🔄 可合并 |
-| 3 | 获取访问量统计 | `referral_visits` | 1行(聚合) | 🔄 可合并 |
-| 4 | 获取付款统计 | `orders` + `referral_bindings` | 1行(聚合) | 🔄 可合并 |
-| 5 | 获取待审核提现金额 | `withdrawals` | 1行(聚合) | 🔄 可合并 |
-| 6 | 获取活跃用户列表 | `referral_bindings` + `users` | ≤50行 | ✅ 保持独立 |
-| 7 | 获取已转化用户列表 | `referral_bindings` + `users` | ≤50行 | ✅ 保持独立 |
-| 8 | 获取已过期用户列表 | `referral_bindings` + `users` | ≤50行 | ✅ 保持独立 |
-| 9 | 获取收益明细 | `orders` + `users` + `referral_bindings` | ≤30行 | ✅ 保持独立 |
-
-**总计:9个查询**
-
-### 3. 性能瓶颈分析
-
-#### 问题点:
-1. **查询次数过多** - 9个独立查询,多次往返数据库
-2. **重复JOIN** - 多次JOIN相同的表(`referral_bindings`, `users`)
-3. **无缓存机制** - 配置数据每次都重新查询
-
-#### 影响:
-- **响应时间慢** - 9个查询串行执行,总耗时约 500-800ms
-- **数据库负载高** - 高并发时增加数据库压力
-- **小程序加载慢** - 用户体验差,需要loading提示
-
-## 二、优化方案设计
-
-### 优化策略
-
-#### 1. **查询合并** - 减少数据库往返次数
-
-**原方案:5个独立的单行统计查询**
-```
-1. SELECT users → 用户信息
-2. SELECT referral_bindings → 绑定统计
-3. SELECT referral_visits → 访问统计
-4. SELECT orders → 付款统计
-5. SELECT withdrawals → 提现统计
-```
-
-**优化方案:1个聚合查询**
-```sql
-SELECT
- -- 用户信息
- u.id, u.nickname, u.referral_code, u.earnings,
- u.pending_earnings, u.withdrawn_earnings, u.referral_count,
-
- -- 绑定统计(子查询)
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id) as total_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND expiry_date > NOW()) as active_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND purchase_count > 0) as converted_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND (status = 'expired' OR expiry_date <= NOW())) as expired_bindings,
-
- -- 访问统计(子查询)
- (SELECT COUNT(DISTINCT visitor_id) FROM referral_visits WHERE referrer_id = u.id) as total_visits,
-
- -- 付款统计(子查询)
- (SELECT COUNT(DISTINCT o.user_id) FROM orders o JOIN referral_bindings rb ON o.user_id = rb.referee_id WHERE rb.referrer_id = u.id AND o.status = 'paid') as paid_count,
- (SELECT COALESCE(SUM(o.amount), 0) FROM orders o JOIN referral_bindings rb ON o.user_id = rb.referee_id WHERE rb.referrer_id = u.id AND o.status = 'paid') as total_amount,
-
- -- 待审核提现(子查询)
- (SELECT COALESCE(SUM(amount), 0) FROM withdrawals WHERE user_id = u.id AND status = 'pending') as pending_withdraw_amount
-
-FROM users u
-WHERE u.id = ?
-```
-
-**优势:**
-- ✅ 从5个查询减少到1个查询
-- ✅ 减少数据库往返次数(从5次减少到1次)
-- ✅ 响应时间减少约 60-70%
-
-#### 2. **列表查询优化** - 合理使用索引
-
-**当前查询:4个独立列表查询**
-- 活跃用户列表(50条)
-- 已转化用户列表(50条)
-- 已过期用户列表(50条)
-- 收益明细(30条)
-
-**优化方案:**
-- ✅ 保持独立查询(列表数据无法合并)
-- ✅ 确保所有JOIN字段有索引
-- ✅ 限制返回字段,减少数据传输量
-- ✅ 添加查询超时控制
-
-**需要的索引:**
-```sql
--- referral_bindings 表
-CREATE INDEX idx_referrer_status_expiry ON referral_bindings(referrer_id, status, expiry_date);
-CREATE INDEX idx_referrer_purchase ON referral_bindings(referrer_id, purchase_count);
-
--- orders 表
-CREATE INDEX idx_user_status_paytime ON orders(user_id, status, pay_time);
-CREATE INDEX idx_referrer_status ON orders(referrer_id, status);
-
--- withdrawals 表
-CREATE INDEX idx_user_status ON withdrawals(user_id, status);
-```
-
-#### 3. **配置缓存** - 减少重复查询
-
-**原方案:**
-```javascript
-// 每次请求都查询配置
-const config = await getConfig('referral_config')
-```
-
-**优化方案:**
-```javascript
-// 使用内存缓存(5分钟过期)
-const configCache = new Map()
-
-async function getCachedConfig(key) {
- const cached = configCache.get(key)
- if (cached && Date.now() - cached.time < 5 * 60 * 1000) {
- return cached.data
- }
- const data = await getConfig(key)
- configCache.set(key, { data, time: Date.now() })
- return data
-}
-```
-
-#### 4. **分页加载** - 减少初始数据量
-
-**原方案:**
-- 一次性加载所有列表数据(活跃50条 + 转化50条 + 过期50条 + 明细30条 = 180条)
-
-**优化方案:**
-- 初始只加载核心统计数据 + 活跃用户列表(20条)
-- 其他列表数据按需加载(用户切换Tab时加载)
-
-```javascript
-// 方案1:单接口 + type参数
-GET /api/referral/data?userId=xxx&type=overview // 只返回统计数据
-GET /api/referral/data?userId=xxx&type=active // 活跃用户列表
-GET /api/referral/data?userId=xxx&type=converted // 已转化列表
-GET /api/referral/data?userId=xxx&type=expired // 已过期列表
-GET /api/referral/data?userId=xxx&type=earnings // 收益明细
-
-// 方案2:保持当前一次性加载(推荐)
-// 理由:分销中心是高频页面,用户通常会查看所有数据
-// 优化查询性能比拆分接口更有效
-```
-
-### 优化后的架构
-
-```
-┌─────────────────────────────────────────┐
-│ 小程序 - 分销中心页面 │
-└─────────────────┬───────────────────────┘
- │
- │ 1次请求
- ↓
-┌─────────────────────────────────────────┐
-│ /api/referral/data (优化后) │
-├─────────────────────────────────────────┤
-│ 1. 聚合统计查询(1个查询) │
-│ - 用户信息 + 绑定统计 + 付款统计 │
-│ - 访问统计 + 提现统计 │
-│ 2. 活跃用户列表(1个查询,20条) │
-│ 3. 已转化用户列表(1个查询,20条) │
-│ 4. 已过期用户列表(1个查询,20条) │
-│ 5. 收益明细(1个查询,20条) │
-├─────────────────────────────────────────┤
-│ 总计:5个查询(vs 原9个查询) │
-│ 预计响应时间:200-300ms(vs 原500-800ms)│
-└─────────────────────────────────────────┘
-```
-
-## 三、实施计划
-
-### Phase 1: 核心优化(立即实施)
-
-1. **合并统计查询** ⭐⭐⭐
- - 将5个独立统计查询合并为1个聚合查询
- - 预计减少响应时间 60-70%
- - 影响范围:`app/api/referral/data/route.ts`
-
-2. **添加数据库索引** ⭐⭐⭐
- - 为高频查询字段添加复合索引
- - 提升查询效率 30-50%
- - 影响范围:数据库 schema
-
-3. **减少列表数据量** ⭐⭐
- - 将列表限制从50条减少到20条
- - 减少数据传输量 40%
- - 影响范围:查询 LIMIT 参数
-
-### Phase 2: 进阶优化(可选)
-
-4. **配置缓存** ⭐⭐
- - 使用内存缓存配置数据
- - 减少配置查询次数
- - 影响范围:`lib/db.ts`
-
-5. **懒加载列表** ⭐
- - 按需加载不同Tab的数据
- - 进一步减少初始加载时间
- - 影响范围:前端 + 后端接口
-
-### Phase 3: 长期优化(未来规划)
-
-6. **Redis缓存** ⭐
- - 缓存用户分销数据(1分钟过期)
- - 适用于高并发场景
- - 需要额外的Redis服务
-
-7. **CDN缓存** ⭐
- - 缓存小程序码图片
- - 减少生成次数
- - 需要CDN服务支持
-
-## 四、性能对比
-
-### 优化前 vs 优化后
-
-| 指标 | 优化前 | 优化后 | 提升 |
-|------|--------|--------|------|
-| **数据库查询次数** | 9个 | 5个 | ↓ 44% |
-| **聚合查询次数** | 5个 | 1个 | ↓ 80% |
-| **列表查询次数** | 4个 | 4个 | - |
-| **返回数据量** | ~180条 | ~80条 | ↓ 55% |
-| **预计响应时间** | 500-800ms | 200-300ms | ↓ 60-70% |
-| **数据库负载** | 高 | 中 | ↓ 50% |
-
-### 优化效果预估
-
-**用户体验提升:**
-- ✅ 页面加载速度提升 60-70%
-- ✅ Loading时间更短,体验更流畅
-- ✅ 数据刷新更快
-
-**服务器性能提升:**
-- ✅ 数据库查询减少 44%
-- ✅ 数据库连接时间减少 60%
-- ✅ 可支持更高并发
-
-**成本优化:**
-- ✅ 数据库资源消耗减少 50%
-- ✅ 带宽消耗减少 55%
-
-## 五、向后兼容
-
-### API响应格式
-
-优化后的 `/api/referral/data` 接口响应格式**完全兼容**原有格式,前端无需修改。
-
-```json
-{
- "success": true,
- "data": {
- // 核心统计(不变)
- "bindingCount": 10,
- "visitCount": 50,
- "paidCount": 5,
- "expiredCount": 2,
-
- // 收益数据(不变)
- "totalCommission": 450.00,
- "availableEarnings": 300.00,
- "pendingWithdrawAmount": 100.00,
- "withdrawnEarnings": 50.00,
-
- // 列表数据(数量减少,格式不变)
- "activeUsers": [...], // 50条 → 20条
- "convertedUsers": [...], // 50条 → 20条
- "expiredUsers": [...], // 50条 → 20条
- "earningsDetails": [...] // 30条 → 20条
- }
-}
-```
-
-## 六、风险评估
-
-| 风险 | 影响 | 概率 | 缓解措施 |
-|------|------|------|---------|
-| 聚合查询性能不如预期 | 中 | 低 | 回滚到原查询,添加更多索引 |
-| 复杂子查询导致慢查询 | 高 | 低 | 使用 EXPLAIN 分析,优化查询计划 |
-| 缓存数据不一致 | 中 | 中 | 设置短期过期时间(5分钟) |
-| 列表数据不够用 | 低 | 中 | 支持分页参数,按需加载更多 |
-
-## 七、测试验证
-
-### 性能测试
-
-```bash
-# 测试工具:Apache Bench
-ab -n 100 -c 10 "http://localhost:3000/api/referral/data?userId=xxx"
-
-# 预期结果:
-# - 平均响应时间 < 300ms
-# - 95%请求 < 400ms
-# - 无失败请求
-```
-
-### 数据正确性测试
-
-```sql
--- 验证聚合查询结果与独立查询一致
--- 1. 绑定统计
-SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = 'xxx';
-
--- 2. 付款统计
-SELECT COUNT(DISTINCT o.user_id)
-FROM orders o
-JOIN referral_bindings rb ON o.user_id = rb.referee_id
-WHERE rb.referrer_id = 'xxx' AND o.status = 'paid';
-
--- 3. 提现统计
-SELECT SUM(amount) FROM withdrawals WHERE user_id = 'xxx' AND status = 'pending';
-```
-
-## 八、总结
-
-### 核心优化点
-
-1. ⭐⭐⭐ **合并统计查询** - 从5个查询减少到1个,性能提升最大
-2. ⭐⭐⭐ **添加数据库索引** - 确保查询效率,避免全表扫描
-3. ⭐⭐ **减少返回数据量** - 减少网络传输,加快响应速度
-4. ⭐ **配置缓存** - 减少重复查询,降低数据库负载
-
-### 实施建议
-
-**推荐方案:立即实施 Phase 1**
-- 合并统计查询
-- 添加数据库索引
-- 减少列表数据量
-
-**预计收益:**
-- 响应时间减少 60-70%
-- 数据库负载减少 50%
-- 用户体验显著提升
-
-**实施时间:**
-- 开发时间:2-3小时
-- 测试时间:1小时
-- 部署时间:30分钟
-- **总计:约半天**
-
-优化后,分销中心将成为一个高性能、低延迟的功能模块,为用户提供流畅的体验!🚀
diff --git a/开发文档/8、部署/分销中心数据库连接错误修复.md b/开发文档/8、部署/分销中心数据库连接错误修复.md
deleted file mode 100644
index d86a85e4..00000000
--- a/开发文档/8、部署/分销中心数据库连接错误修复.md
+++ /dev/null
@@ -1,324 +0,0 @@
-# 分销中心数据库连接错误修复
-
-## 问题描述
-
-### 错误现象
-调用分销数据API时出现数据库连接错误:
-
-```
-GET /api/referral/data?userId=ogpTW5fmXRGNpoUbXB3UEqnVe5Tg
-Response: {
- success: false,
- error: "获取分销数据失败: Connection lost: The server closed the connection."
-}
-```
-
-### 问题原因
-
-1. **子查询过多**:初始优化时将5个查询合并为1个,但包含了10+个子查询,导致:
- - 查询执行时间过长
- - 数据库连接超时
- - 服务器主动关闭连接
-
-2. **可能不存在的表**:
- - `referral_visits` 表可能不存在(访问统计功能未启用)
- - 在主查询中直接查询会导致整个SQL失败
-
-3. **缺少错误处理**:
- - 查询失败时没有捕获错误
- - 无法定位具体失败的子查询
-
-## 解决方案
-
-### 1. 简化主查询
-将主查询中的子查询数量从10+个减少到6个:
-
-**保留的子查询(核心统计)**:
-```sql
--- 绑定关系统计(4个子查询)
-(SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id)
-(SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND expiry_date > NOW())
-(SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND purchase_count > 0)
-(SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND (status IN ('expired', 'cancelled') OR (status = 'active' AND expiry_date <= NOW())))
-
--- 付款统计(2个子查询,直接从orders表查询)
-(SELECT COUNT(DISTINCT user_id) FROM orders WHERE referrer_id = u.id AND status = 'paid')
-(SELECT COALESCE(SUM(amount), 0) FROM orders WHERE referrer_id = u.id AND status = 'paid')
-```
-
-**移除的复杂子查询**:
-```sql
--- ❌ 移除:复杂的JOIN子查询(付款统计)
-(SELECT COUNT(DISTINCT o.user_id)
- FROM orders o
- JOIN referral_bindings rb ON o.user_id = rb.referee_id
- WHERE rb.referrer_id = u.id AND o.status = 'paid')
-
--- ❌ 移除:访问统计(改为独立查询)
-(SELECT COUNT(DISTINCT visitor_id) FROM referral_visits WHERE referrer_id = u.id)
-
--- ❌ 移除:待审核提现金额(改为独立查询)
-(SELECT COALESCE(SUM(amount), 0) FROM withdrawals WHERE user_id = u.id AND status = 'pending')
-```
-
-### 2. 独立查询 + 错误处理
-将可能失败的查询改为独立查询,并添加错误处理:
-
-```typescript
-// 访问统计(可能表不存在)
-let totalVisits = bindingStats.total
-try {
- const visits = await query(`
- SELECT COUNT(DISTINCT visitor_id) as count
- FROM referral_visits
- WHERE referrer_id = ?
- `, [userId]) as any[]
- totalVisits = parseInt(visits[0]?.count) || bindingStats.total
-} catch (e) {
- // referral_visits 表可能不存在,使用绑定数作为访问数
- console.log('[ReferralData] 访问统计表不存在,使用绑定数')
-}
-
-// 待审核提现金额
-let pendingWithdrawAmount = 0
-try {
- const withdraws = await query(`
- SELECT COALESCE(SUM(amount), 0) as pending_amount
- FROM withdrawals
- WHERE user_id = ? AND status = 'pending'
- `, [userId]) as any[]
- pendingWithdrawAmount = parseFloat(withdraws[0]?.pending_amount) || 0
-} catch (e) {
- console.log('[ReferralData] 提现表查询失败:', e)
-}
-```
-
-### 3. 添加主查询错误处理
-对主查询添加 try-catch 并返回详细错误信息:
-
-```typescript
-let statsResult: any[]
-try {
- statsResult = await query(`
- SELECT
- -- 用户基本信息
- u.id, u.nickname, u.referral_code, u.earnings, u.pending_earnings,
- u.withdrawn_earnings, u.referral_count,
-
- -- 绑定关系统计
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id) as total_bindings,
- -- ... 其他子查询
-
- FROM users u
- WHERE u.id = ?
- `, [userId]) as any[]
-} catch (err) {
- console.error('[ReferralData] 统计查询失败:', err)
- return NextResponse.json({
- success: false,
- error: '查询统计数据失败: ' + (err as Error).message
- }, { status: 500 })
-}
-```
-
-## 实施步骤
-
-### 1. 修改后端代码
-文件:`app/api/referral/data/route.ts`
-
-```diff
-- // ⚡ 优化:合并统计查询 - 将5个查询合并为1个(减少数据库往返)
-- const statsResult = await query(`
-+ // ⚡ 优化:合并统计查询 - 添加错误处理
-+ let statsResult: any[]
-+ try {
-+ statsResult = await query(`
- SELECT
- -- 用户基本信息
- u.id, u.nickname, u.referral_code, u.earnings, u.pending_earnings,
- u.withdrawn_earnings, u.referral_count,
-
- -- 绑定关系统计
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id) as total_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND expiry_date > NOW()) as active_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND status = 'active' AND purchase_count > 0) as converted_bindings,
- (SELECT COUNT(*) FROM referral_bindings WHERE referrer_id = u.id AND (status IN ('expired', 'cancelled') OR (status = 'active' AND expiry_date <= NOW()))) as expired_bindings,
-
-- -- 访问统计(如果表不存在会返回NULL)
-- (SELECT COUNT(DISTINCT visitor_id) FROM referral_visits WHERE referrer_id = u.id) as total_visits,
--
-- -- 付款统计
-- (SELECT COUNT(DISTINCT o.user_id)
-- FROM orders o
-- JOIN referral_bindings rb ON o.user_id = rb.referee_id
-- WHERE rb.referrer_id = u.id AND o.status = 'paid') as paid_count,
-- (SELECT COALESCE(SUM(o.amount), 0)
-- FROM orders o
-- JOIN referral_bindings rb ON o.user_id = rb.referee_id
-- WHERE rb.referrer_id = u.id AND o.status = 'paid') as total_amount,
--
-- -- 待审核提现金额
-- (SELECT COALESCE(SUM(amount), 0) FROM withdrawals WHERE user_id = u.id AND status = 'pending') as pending_withdraw_amount,
--
-- -- 累计佣金总额(直接从订单表计算)
-- (SELECT COALESCE(SUM(amount), 0) FROM orders WHERE referrer_id = u.id AND status = 'paid') as total_referral_amount
-+ -- 付款统计(直接从orders表查询)
-+ (SELECT COUNT(DISTINCT user_id) FROM orders WHERE referrer_id = u.id AND status = 'paid') as paid_count,
-+ (SELECT COALESCE(SUM(amount), 0) FROM orders WHERE referrer_id = u.id AND status = 'paid') as total_referral_amount
-
- FROM users u
- WHERE u.id = ?
-- `, [userId]) as any[]
-+ `, [userId]) as any[]
-+ } catch (err) {
-+ console.error('[ReferralData] 统计查询失败:', err)
-+ return NextResponse.json({
-+ success: false,
-+ error: '查询统计数据失败: ' + (err as Error).message
-+ }, { status: 500 })
-+ }
-```
-
-### 2. 添加独立查询
-
-```typescript
-const paymentStats = {
- paidCount: parseInt(stats.paid_count) || 0,
- totalAmount: parseFloat(stats.total_referral_amount) || 0
-}
-
-// 获取访问统计(独立查询,带错误处理)
-let totalVisits = bindingStats.total
-try {
- const visits = await query(`
- SELECT COUNT(DISTINCT visitor_id) as count
- FROM referral_visits
- WHERE referrer_id = ?
- `, [userId]) as any[]
- totalVisits = parseInt(visits[0]?.count) || bindingStats.total
-} catch (e) {
- console.log('[ReferralData] 访问统计表不存在,使用绑定数')
-}
-
-// 获取待审核提现金额(独立查询,带错误处理)
-let pendingWithdrawAmount = 0
-try {
- const withdraws = await query(`
- SELECT COALESCE(SUM(amount), 0) as pending_amount
- FROM withdrawals
- WHERE user_id = ? AND status = 'pending'
- `, [userId]) as any[]
- pendingWithdrawAmount = parseFloat(withdraws[0]?.pending_amount) || 0
-} catch (e) {
- console.log('[ReferralData] 提现表查询失败:', e)
-}
-```
-
-### 3. 测试验证
-
-```bash
-# 1. 测试API
-curl "http://localhost:3006/api/referral/data?userId=ogpTW5fmXRGNpoUbXB3UEqnVe5Tg"
-
-# 2. 检查返回数据
-{
- "success": true,
- "data": {
- "stats": {
- "totalVisits": 10, // 访问数
- "totalBindings": 10, // 绑定数
- "activeBindings": 8, // 活跃绑定
- "convertedBindings": 5, // 已转化
- "expiredBindings": 2, // 已过期
- "paidUsers": 5, // 付款人数
- "totalCommission": 450.0, // 累计佣金
- "availableEarnings": 400.0,
- "pendingWithdrawAmount": 50.0
- }
- }
-}
-```
-
-## 性能对比
-
-| 指标 | 修复前 | 修复后 | 改进 |
-|------|--------|--------|------|
-| 主查询子查询数 | 10+ | 6 | ↓40% |
-| 数据库往返次数 | 1 | 3 | ↑2次(但避免超时) |
-| 错误处理 | ❌ 无 | ✅ 完整 | 新增 |
-| 查询成功率 | ❌ 失败 | ✅ 成功 | 从0%到100% |
-
-## 最佳实践总结
-
-### 1. 子查询数量控制
-- ✅ 单个SQL中子查询数量控制在10个以内
-- ✅ 复杂JOIN子查询应拆分为独立查询
-- ✅ 优先使用简单的COUNT/SUM子查询
-
-### 2. 错误处理策略
-- ✅ 核心统计查询必须添加 try-catch
-- ✅ 可选功能(如访问统计)独立查询 + 容错
-- ✅ 返回详细错误信息用于调试
-
-### 3. 查询优化原则
-- ✅ 直接查询优于复杂JOIN(如从orders表直接查询付款统计)
-- ✅ 将可能失败的查询隔离
-- ✅ 为可选功能提供降级方案(如访问数降级为绑定数)
-
-### 4. 数据库连接管理
-- ✅ 避免长时间占用连接
-- ✅ 查询超时时正确释放资源
-- ✅ 考虑连接池配置
-
-## 后续优化建议
-
-### 1. 短期优化
-- [ ] 为常用查询添加数据库索引
-- [ ] 考虑使用缓存减少数据库压力
-- [ ] 监控慢查询并优化
-
-### 2. 长期优化
-- [ ] 实现数据预聚合(定时任务)
-- [ ] 考虑使用Redis缓存统计数据
-- [ ] 实现增量更新机制
-
-## 部署说明
-
-### 1. 本地测试
-```bash
-# 启动开发服务器
-npm run dev
-
-# 测试API
-curl "http://localhost:3006/api/referral/data?userId=YOUR_USER_ID"
-```
-
-### 2. 生产部署
-```bash
-# 构建项目
-npm run build
-
-# 重启PM2
-python devlop.py restart mycontent
-```
-
-### 3. 监控
-```bash
-# 查看PM2日志
-pm2 logs mycontent
-
-# 关注以下日志:
-# [ReferralData] 统计查询失败: ...
-# [ReferralData] 访问统计表不存在,使用绑定数
-# [ReferralData] 提现表查询失败: ...
-```
-
-## 总结
-
-这次修复通过简化主查询、隔离可选功能、添加错误处理,成功解决了数据库连接超时问题。同时保持了API性能优化的核心目标(减少数据库往返次数),并为未来扩展提供了更好的容错机制。
-
-**关键收获**:
-1. 性能优化不能一味追求"合并所有查询"
-2. 需要在性能和可靠性之间找到平衡
-3. 完善的错误处理是生产环境的必备条件
-4. 为可选功能提供降级方案非常重要
diff --git a/开发文档/8、部署/分销中心用户列表数据对接说明.md b/开发文档/8、部署/分销中心用户列表数据对接说明.md
deleted file mode 100644
index a6292535..00000000
--- a/开发文档/8、部署/分销中心用户列表数据对接说明.md
+++ /dev/null
@@ -1,441 +0,0 @@
-# 分销中心用户列表数据对接说明
-
-## 📋 功能说明
-
-小程序分销中心的"绑定用户"列表,包含三个Tab:
-1. **绑定中** - 当前活跃的绑定关系
-2. **已付款** - 购买过商品的用户
-3. **已过期** - 过期或取消的绑定
-
----
-
-## 🔧 本次修改
-
-### 1. 后端API优化(/api/referral/data)
-
-**文件**: `app/api/referral/data/route.ts`
-
-**修改内容**:
-- 在 `convertedUsers` 中添加 `purchaseCount`(购买次数)字段
-
-```typescript
-convertedUsers: convertedBindings.map((b: any) => ({
- id: b.referee_id,
- nickname: b.nickname,
- avatar: b.avatar,
- commission: parseFloat(b.commission_amount) || 0,
- orderAmount: parseFloat(b.order_amount) || 0,
- purchaseCount: parseInt(b.purchase_count) || 0, // 新增
- conversionDate: b.conversion_date,
- status: 'converted'
-}))
-```
-
----
-
-### 2. 小程序前端优化
-
-#### 2.1 数据格式化(referral.js)
-
-**文件**: `miniprogram/pages/referral/referral.js`
-
-**修改内容**:
-```javascript
-// formatUser 函数增强
-const formatUser = (user, type) => {
- return {
- id: user.id,
- nickname: user.nickname,
- avatar: user.avatar,
- status: type,
- daysRemaining: user.daysRemaining || 0,
- bindingDate: this.formatDate(user.bindingDate),
- expiryDate: this.formatDate(user.expiryDate), // 新增:过期时间
- commission: (user.commission || 0).toFixed(2),
- orderAmount: (user.orderAmount || 0).toFixed(2),
- purchaseCount: user.purchaseCount || 0, // 新增:购买次数
- conversionDate: this.formatDate(user.conversionDate) // 新增:转化时间
- }
-}
-```
-
----
-
-#### 2.2 UI显示优化(referral.wxml)
-
-**文件**: `miniprogram/pages/referral/referral.wxml`
-
-**旧代码**:
-```xml
-
-
- +¥{{item.commission}}
- 订单 ¥{{item.orderAmount}}
-
-
-
- {{item.status === 'expired' ? '已过期' : item.daysRemaining + '天'}}
-
-
-
-```
-
-**新代码**:
-```xml
-
-
-
- +¥{{item.commission}}
- 已购{{item.purchaseCount || 1}}次
-
-
-
-
- 已过期
- {{item.expiryDate}}
-
-
-
-
-
- {{item.daysRemaining}}天
-
-
-
-```
-
----
-
-## 📊 数据流向
-
-```
-后端 /api/referral/data
- ↓
-返回三类用户数据
- ├─ activeUsers(绑定中)
- │ - daysRemaining(剩余天数)
- │ - bindingDate(绑定时间)
- │
- ├─ convertedUsers(已付款)
- │ - commission(佣金)
- │ - purchaseCount(购买次数)✨ 新增
- │ - conversionDate(转化时间)
- │
- └─ expiredUsers(已过期)
- - expiryDate(过期时间)
- - bindingDate(绑定时间)
- ↓
-小程序接收并格式化
- ↓
-分Tab显示
-```
-
----
-
-## 🎨 显示效果
-
-### Tab 1: 绑定中
-```
-┌─────────────────────────────┐
-│ [头像] 张三 │
-│ 绑定于 02-01 │
-│ [15天]│
-└─────────────────────────────┘
-```
-
-**显示内容**:
-- 用户昵称
-- 绑定时间
-- 剩余天数(颜色标识:绿色>7天,橙色3-7天,红色≤3天)
-
----
-
-### Tab 2: 已付款
-```
-┌─────────────────────────────┐
-│ [✓] 李四 │
-│ 绑定于 01-20 │
-│ +¥0.90 已购1次│
-└─────────────────────────────┘
-
-┌─────────────────────────────┐
-│ [✓] 王五 │
-│ 绑定于 01-15 │
-│ +¥2.70 已购3次│
-└─────────────────────────────┘
-```
-
-**显示内容**:
-- 用户昵称(头像显示✓)
-- 绑定时间
-- 累计佣金
-- **购买次数**(✨ 新增)
-
-**数据来源**:
-```javascript
-{
- commission: 0.90, // 累计佣金
- purchaseCount: 1, // 购买次数 ✨
- conversionDate: "2026-01-20"
-}
-```
-
----
-
-### Tab 3: 已过期
-```
-┌─────────────────────────────┐
-│ [⏰] 赵六 │
-│ 绑定于 01-05 │
-│ [已过期] 02-04│
-└─────────────────────────────┘
-```
-
-**显示内容**:
-- 用户昵称(头像显示⏰)
-- 绑定时间
-- 已过期标签
-- **过期时间**(✨ 优化显示)
-
-**数据来源**:
-```javascript
-{
- bindingDate: "2026-01-05",
- expiryDate: "2026-02-04" // 显示具体过期日期
-}
-```
-
----
-
-## 🔍 数据验证
-
-### 测试场景1: 已付款用户(单次购买)
-```json
-{
- "nickname": "张三",
- "commission": 0.90,
- "purchaseCount": 1,
- "conversionDate": "2026-02-01"
-}
-```
-
-**显示**: `+¥0.90 已购1次`
-
----
-
-### 测试场景2: 已付款用户(多次购买)
-```json
-{
- "nickname": "李四",
- "commission": 2.70,
- "purchaseCount": 3,
- "conversionDate": "2026-01-20"
-}
-```
-
-**显示**: `+¥2.70 已购3次`
-
----
-
-### 测试场景3: 已过期用户
-```json
-{
- "nickname": "王五",
- "bindingDate": "2026-01-05",
- "expiryDate": "2026-02-04"
-}
-```
-
-**显示**:
-- 标签:`已过期`
-- 时间:`02-04`
-
----
-
-## 🎯 优化亮点
-
-### 1. 已付款用户
-**旧显示**:
-```
-+¥0.90
-订单 ¥1.00
-```
-- ❌ 显示订单金额(用户可能多次购买,只显示一个金额不准确)
-
-**新显示**:
-```
-+¥0.90
-已购1次
-```
-- ✅ 显示购买次数(更直观)
-- ✅ 支持多次购买(已购3次)
-
----
-
-### 2. 已过期用户
-**旧显示**:
-```
-[已过期]
-```
-- ❌ 只有标签,不知道什么时候过期
-
-**新显示**:
-```
-[已过期] 02-04
-```
-- ✅ 显示具体过期时间
-- ✅ 用户可以看到过期日期
-
----
-
-## 📊 后端数据说明
-
-### convertedBindings 查询
-```sql
-SELECT
- rb.referee_id,
- rb.purchase_count, -- 购买次数
- rb.total_commission, -- 累计佣金
- rb.last_purchase_date, -- 最后购买时间
- u.nickname, u.avatar
-FROM referral_bindings rb
-JOIN users u ON rb.referee_id = u.id
-WHERE rb.referrer_id = ?
- AND rb.status = 'active'
- AND rb.purchase_count > 0
-ORDER BY rb.last_purchase_date DESC
-```
-
-**关键字段**:
-- `purchase_count` - 购买次数(每次购买 +1)
-- `total_commission` - 累计佣金(每次购买累加)
-- `last_purchase_date` - 最后购买时间(用于排序)
-
----
-
-### expiredBindings 查询
-```sql
-SELECT
- rb.referee_id,
- rb.binding_date, -- 绑定时间
- rb.expiry_date, -- 过期时间
- u.nickname, u.avatar
-FROM referral_bindings rb
-JOIN users u ON rb.referee_id = u.id
-WHERE rb.referrer_id = ?
- AND (rb.status = 'expired' OR rb.status = 'cancelled')
-ORDER BY rb.expiry_date DESC
-```
-
-**关键字段**:
-- `binding_date` - 绑定时间
-- `expiry_date` - 过期时间(显示在前端)
-- `status` - expired(自然过期)或 cancelled(被切换)
-
----
-
-## 🚀 部署步骤
-
-### 1. 后端部署
-```bash
-pnpm build
-python devlop.py
-pm2 restart soul
-```
-
-### 2. 小程序上传
-- 在微信开发者工具上传代码
-- 提交审核
-- 发布新版本
-
----
-
-## ✅ 测试清单
-
-### 已付款用户
-- [ ] 显示累计佣金
-- [ ] 显示购买次数
-- [ ] 单次购买显示"已购1次"
-- [ ] 多次购买显示"已购N次"
-- [ ] 头像显示✓标记
-
-### 已过期用户
-- [ ] 显示"已过期"标签
-- [ ] 显示过期时间
-- [ ] 头像显示⏰标记
-- [ ] 时间格式正确(MM-DD)
-
-### 绑定中用户
-- [ ] 显示剩余天数
-- [ ] 颜色标识正确(绿/橙/红)
-- [ ] 头像显示首字母
-
----
-
-## 🎨 样式优化(可选)
-
-如果需要调整样式,在 `referral.wxss` 中添加:
-
-```css
-/* 已过期时间显示 */
-.status-time {
- font-size: 22rpx;
- color: #999;
- margin-top: 4rpx;
-}
-
-/* 灰色标签(已过期) */
-.tag-gray {
- background: #f5f5f5;
- color: #999;
-}
-```
-
----
-
-## 📝 API 返回数据示例
-
-### 完整响应
-```json
-{
- "success": true,
- "data": {
- "activeUsers": [
- {
- "id": "user_123",
- "nickname": "张三",
- "avatar": "https://...",
- "daysRemaining": 15,
- "bindingDate": "2026-01-20T10:00:00.000Z",
- "status": "active"
- }
- ],
- "convertedUsers": [
- {
- "id": "user_456",
- "nickname": "李四",
- "avatar": "https://...",
- "commission": 0.90,
- "orderAmount": 1.00,
- "purchaseCount": 1,
- "conversionDate": "2026-02-01T14:30:00.000Z",
- "status": "converted"
- }
- ],
- "expiredUsers": [
- {
- "id": "user_789",
- "nickname": "王五",
- "avatar": "https://...",
- "bindingDate": "2026-01-05T08:00:00.000Z",
- "expiryDate": "2026-02-04T08:00:00.000Z",
- "status": "expired"
- }
- ]
- }
-}
-```
-
----
-
-**✅ 分销中心用户列表数据已完全对接!支持显示购买次数和过期时间。**
diff --git a/开发文档/8、部署/分销中心设置功能说明.md b/开发文档/8、部署/分销中心设置功能说明.md
deleted file mode 100644
index f91bfc46..00000000
--- a/开发文档/8、部署/分销中心设置功能说明.md
+++ /dev/null
@@ -1,343 +0,0 @@
-# 分销中心设置功能说明
-
-## 一、功能概述
-
-分销中心右上角设置按钮提供两个功能:
-1. **自动提现设置** - 配置自动提现功能
-2. **收益通知设置** - 配置收益通知提醒
-
-## 二、自动提现设置
-
-### 功能说明
-
-**目的**:方便用户自动提现,无需手动操作
-
-**工作流程**:
-1. 用户设置自动提现阈值(例如:¥50)
-2. 开启自动提现
-3. 当可提现金额 ≥ 阈值时,自动发起提现申请
-
-### 使用方法
-
-#### 1. 开启自动提现
-
-```
-点击设置 → 自动提现设置 → 开启
-```
-
-**弹窗内容**:
-- 当前状态:已关闭/已开启
-- 自动提现阈值:¥XX
-- 说明文字
-- 按钮:开启/关闭、修改阈值
-
-#### 2. 设置阈值
-
-```
-点击设置 → 自动提现设置 → 修改阈值
-```
-
-**输入要求**:
-- 最低金额:等于系统配置的最低提现金额(默认¥10)
-- 必须是数字
-- 小数点后最多2位
-
-**示例**:
-- ✅ 10
-- ✅ 50.5
-- ✅ 100.00
-- ❌ 5(低于最低金额)
-- ❌ abc(不是数字)
-
-#### 3. 自动提现触发
-
-**触发时机**:
-- 用户开启自动提现后,立即检查当前金额
-- 每次收到新佣金时检查
-- 用户进入分销中心时检查
-
-**触发流程**:
-```
-检测到 可提现金额 ≥ 阈值
- ↓
-弹窗确认:是否立即提现?
- ↓
-用户确认 → 调用提现接口
-```
-
-### 数据存储
-
-**存储位置**:微信本地存储(`wx.setStorageSync`)
-
-**存储格式**:
-```javascript
-// 自动提现开关
-`autoWithdraw_${userId}`: boolean
-
-// 自动提现阈值
-`autoWithdrawThreshold_${userId}`: number
-```
-
-**示例**:
-```javascript
-// 用户ID为 user_123
-autoWithdraw_user_123: true
-autoWithdrawThreshold_user_123: 50
-```
-
-### 代码实现
-
-#### 主要函数
-
-```javascript
-// 显示自动提现设置
-showAutoWithdrawSettings()
-
-// 切换自动提现开关
-toggleAutoWithdraw(enabled, threshold)
-
-// 设置自动提现阈值
-setAutoWithdrawThreshold(currentEnabled, currentThreshold)
-```
-
-#### 调用流程
-
-```javascript
-点击设置按钮
- ↓
-showSettings() - 显示菜单
- ↓
-选择"自动提现设置"
- ↓
-showAutoWithdrawSettings() - 显示设置弹窗
- ↓
-用户选择:
- ├─ 开启/关闭 → toggleAutoWithdraw()
- └─ 修改阈值 → setAutoWithdrawThreshold()
-```
-
-## 三、收益通知设置
-
-### 功能说明
-
-**目的**:及时通知用户有新的收益入账
-
-**通知时机**:
-- 有新用户付款,获得佣金时
-- 有用户续费,获得佣金时
-- 提现成功时
-
-### 使用方法
-
-#### 1. 开启通知
-
-```
-点击设置 → 收益通知设置 → 开启通知
-```
-
-**弹窗内容**:
-- 当前状态:已开启/已关闭
-- 说明文字
-- 按钮:开启通知/关闭通知
-
-#### 2. 授权通知权限
-
-开启通知后会自动请求微信订阅消息权限
-
-**订阅消息模板**:
-```
-模板ID:需在微信公众平台配置
-模板内容:
-- 收益金额:¥XX
-- 收益来源:用户XXX购买XXX
-- 收益时间:XXXX-XX-XX XX:XX
-```
-
-### 数据存储
-
-**存储位置**:微信本地存储
-
-**存储格式**:
-```javascript
-// 收益通知开关(默认true)
-`earningsNotify_${userId}`: boolean
-```
-
-### 代码实现
-
-```javascript
-// 显示收益通知设置
-showNotificationSettings()
-```
-
-## 四、完整代码
-
-### JS部分 (`miniprogram/pages/referral/referral.js`)
-
-```javascript
-// 显示设置
-showSettings() {
- wx.showActionSheet({
- itemList: ['自动提现设置', '收益通知设置'],
- success: (res) => {
- if (res.tapIndex === 0) {
- this.showAutoWithdrawSettings()
- } else {
- this.showNotificationSettings()
- }
- }
- })
-},
-
-// 自动提现设置
-async showAutoWithdrawSettings() {
- const app = getApp()
- const { userInfo } = app.globalData
-
- if (!userInfo) {
- wx.showToast({ title: '请先登录', icon: 'none' })
- return
- }
-
- // 获取当前设置
- let autoWithdrawEnabled = wx.getStorageSync(`autoWithdraw_${userInfo.id}`) || false
- let autoWithdrawThreshold = wx.getStorageSync(`autoWithdrawThreshold_${userInfo.id}`) || this.data.minWithdrawAmount || 10
-
- wx.showModal({
- title: '自动提现设置',
- content: `当前状态:${autoWithdrawEnabled ? '已开启' : '已关闭'}\n自动提现阈值:¥${autoWithdrawThreshold}\n\n开启后,当可提现金额达到阈值时将自动发起提现申请。`,
- confirmText: autoWithdrawEnabled ? '关闭' : '开启',
- cancelText: '修改阈值',
- success: (res) => {
- if (res.confirm) {
- this.toggleAutoWithdraw(!autoWithdrawEnabled, autoWithdrawThreshold)
- } else if (res.cancel) {
- this.setAutoWithdrawThreshold(autoWithdrawEnabled, autoWithdrawThreshold)
- }
- }
- })
-},
-
-// ... 其他函数 ...
-```
-
-## 五、扩展功能(未来)
-
-### 1. 服务器端自动提现
-
-**当前**:本地存储,小程序内检测
-
-**优化**:
-- 将设置保存到服务器
-- 服务器定时任务检测
-- 自动发起提现,无需用户确认
-
-**实现方案**:
-```sql
--- 添加用户设置表
-CREATE TABLE user_settings (
- user_id VARCHAR(64) PRIMARY KEY,
- auto_withdraw_enabled BOOLEAN DEFAULT FALSE,
- auto_withdraw_threshold DECIMAL(10,2) DEFAULT 10.00,
- earnings_notify_enabled BOOLEAN DEFAULT TRUE,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-);
-```
-
-```javascript
-// 后端API
-POST /api/user/settings
-GET /api/user/settings
-
-// 定时任务(每小时执行)
-async function autoWithdrawTask() {
- // 查询所有开启自动提现的用户
- const users = await query(`
- SELECT u.id, u.pending_earnings, s.auto_withdraw_threshold
- FROM users u
- JOIN user_settings s ON u.id = s.user_id
- WHERE s.auto_withdraw_enabled = TRUE
- AND u.pending_earnings >= s.auto_withdraw_threshold
- `)
-
- // 为每个用户发起提现
- for (const user of users) {
- await processWithdraw(user.id, user.pending_earnings)
- }
-}
-```
-
-### 2. 多种通知方式
-
-**当前**:小程序订阅消息
-
-**扩展**:
-- 公众号模板消息
-- 短信通知
-- 邮件通知
-
-### 3. 更多设置项
-
-- 提现到账通知
-- 绑定用户提醒
-- 即将过期提醒
-- 每日收益汇总
-
-## 六、注意事项
-
-### 1. 自动提现限制
-
-- ⚠️ 需要用户手动确认,不是完全自动
-- ⚠️ 仍需满足最低提现金额要求
-- ⚠️ 受提现次数限制(如有)
-
-### 2. 通知权限
-
-- 需要用户授权订阅消息
-- 模板消息需要在微信公众平台配置
-- 用户可以随时关闭通知
-
-### 3. 数据安全
-
-- 本地存储数据可能丢失(卸载小程序、清除缓存)
-- 建议未来同步到服务器
-
-## 七、测试用例
-
-### 自动提现测试
-
-| 场景 | 操作 | 预期结果 |
-|------|------|---------|
-| 开启自动提现 | 可提现金额 < 阈值 | 正常开启,不弹窗 |
-| 开启自动提现 | 可提现金额 ≥ 阈值 | 弹窗询问是否提现 |
-| 修改阈值 | 输入小于最低金额 | 提示错误 |
-| 修改阈值 | 输入非数字 | 提示错误 |
-| 修改阈值 | 输入合法金额 | 保存成功 |
-
-### 通知设置测试
-
-| 场景 | 操作 | 预期结果 |
-|------|------|---------|
-| 开启通知 | 首次开启 | 请求订阅消息权限 |
-| 开启通知 | 已授权 | 直接开启 |
-| 关闭通知 | 点击关闭 | 立即关闭,不再提醒 |
-
-## 八、用户体验优化建议
-
-1. **引导用户使用**
- - 首次进入分销中心时,引导设置自动提现
- - 可提现金额达到阈值时,提示开启自动提现
-
-2. **智能推荐阈值**
- - 根据用户历史提现金额推荐合适的阈值
- - 例如:平均提现金额为¥80,推荐阈值为¥50-¥100
-
-3. **状态反馈**
- - 在分销中心显示自动提现状态(已开启/已关闭)
- - 显示下次自动提现预计金额
-
----
-
-**创建时间**:2026-02-04
-**功能状态**:✅ 已实现
-**适用版本**:小程序 v1.0+
diff --git a/开发文档/8、部署/删除referred_by字段说明.md b/开发文档/8、部署/删除referred_by字段说明.md
deleted file mode 100644
index 81706678..00000000
--- a/开发文档/8、部署/删除referred_by字段说明.md
+++ /dev/null
@@ -1,417 +0,0 @@
-# 删除 users.referred_by 字段说明
-
-## 📋 背景
-
-根据《绑定关系存储方案分析.md》的建议,停用 `users.referred_by` 冗余字段,统一使用 `referral_bindings` 表管理推荐关系。
-
----
-
-## ✅ 已完成的代码修改
-
-### 1. 停止更新 users.referred_by
-
-**修改文件**: `app/api/referral/bind/route.ts`
-
-**修改内容**:
-- 第149-152行:注释掉 `UPDATE users SET referred_by = ?`
-- 不再向该字段写入数据
-
----
-
-### 2. 修改所有旧查询
-
-#### 2.1 `/api/referral/bind` (GET 方法)
-
-**修改前**:
-```typescript
-// 查询用户
-SELECT id, referred_by FROM users WHERE id = ?
-
-// 查询推荐人
-if (user.referred_by) {
- SELECT * FROM users WHERE id = user.referred_by
-}
-
-// 查询被推荐人列表
-SELECT * FROM users WHERE referred_by = ?
-```
-
-**修改后**:
-```typescript
-// 查询用户
-SELECT id FROM users WHERE id = ?
-
-// 查询推荐人(从 referral_bindings)
-SELECT rb.referrer_id, u.nickname, u.avatar
-FROM referral_bindings rb
-JOIN users u ON rb.referrer_id = u.id
-WHERE rb.referee_id = ?
- AND rb.status = 'active'
- AND rb.expiry_date > NOW()
-
-// 查询被推荐人列表(从 referral_bindings)
-SELECT u.*, rb.binding_date, rb.purchase_count
-FROM referral_bindings rb
-JOIN users u ON rb.referee_id = u.id
-WHERE rb.referrer_id = ?
- AND rb.status = 'active'
- AND rb.expiry_date > NOW()
-```
-
----
-
-#### 2.2 `/api/db/users/referrals`
-
-**修改前**:
-```typescript
-// 兜底查询(从 users 表)
-if (referrals.length === 0) {
- SELECT * FROM users WHERE referred_by = ?
-}
-```
-
-**修改后**:
-```typescript
-// 已删除兜底查询,只使用 referral_bindings
-```
-
----
-
-#### 2.3 `/api/auth/login`
-
-**修改前**:
-```typescript
-SELECT id, phone, ..., referred_by, ... FROM users WHERE phone = ?
-
-return {
- referredBy: r.referred_by
-}
-```
-
-**修改后**:
-```typescript
-SELECT id, phone, ..., ... FROM users WHERE phone = ?
-// 移除 referred_by 字段
-
-return {
- // 移除 referredBy
-}
-```
-
----
-
-#### 2.4 `/api/wechat/login`
-
-**修改前**:
-```typescript
-INSERT INTO users (..., referred_by, ...) VALUES (..., ?, ...)
-
-return {
- referredBy: user.referred_by
-}
-```
-
-**修改后**:
-```typescript
-INSERT INTO users (..., ...) VALUES (..., ...)
-// 移除 referred_by 字段
-
-return {
- // 移除 referredBy
-}
-```
-
----
-
-#### 2.5 `/api/db/users`
-
-**修改前**:
-```typescript
-INSERT INTO users (..., referred_by, ...) VALUES (..., ?, ...)
-```
-
-**修改后**:
-```typescript
-INSERT INTO users (..., ...) VALUES (..., ...)
-// 移除 referred_by 字段
-```
-
----
-
-#### 2.6 `/api/payment/wechat/notify` 和 `/api/payment/alipay/notify`
-
-**修改前**:
-```typescript
-SELECT u.id, u.referred_by, rb.referrer_id, rb.status
-FROM users u
-LEFT JOIN referral_bindings rb ...
-```
-
-**修改后**:
-```typescript
-SELECT u.id, rb.referrer_id, rb.status
-FROM users u
-LEFT JOIN referral_bindings rb ...
-// 移除 u.referred_by(不再使用)
-```
-
----
-
-#### 2.7 `/app/admin/users/page.tsx`
-
-**修改前**:
-```typescript
-interface User {
- referred_by?: string | null
-}
-
-{user.referred_by && (
- 来自: {user.referred_by.slice(0, 8)}
-)}
-```
-
-**修改后**:
-```typescript
-interface User {
- // 移除 referred_by
-}
-
-// 移除显示逻辑
-```
-
----
-
-### 3. 小程序海报硬编码修复
-
-**修改文件**: `miniprogram/pages/referral/referral.wxml`
-
-**修改内容**:
-```xml
-
-90%
-
-
-{{shareRate}}%
-```
-
----
-
-## 🗄️ 数据库操作
-
-### 方式1: 在宝塔面板执行(推荐)
-
-1. 登录宝塔面板
-2. 进入「数据库」→「phpMyAdmin」
-3. 选择数据库 `soul_miniprogram`
-4. 点击「SQL」标签
-5. 粘贴 `scripts/remove-referred-by-field.sql` 的内容
-6. 点击「执行」
-
----
-
-### 方式2: 使用 Python 脚本
-
-**文件**: `scripts/remove-referred-by-field-auto.py`
-
-**执行**:
-```bash
-python scripts/remove-referred-by-field-auto.py
-```
-
-**注意**: 需要本地能连接到数据库
-
----
-
-### 方式3: 手动执行SQL
-
-如果上述方式都不行,可以手动执行以下SQL:
-
-```sql
--- 1. 备份
-CREATE TABLE users_referred_by_backup AS
-SELECT id, referred_by, created_at
-FROM users
-WHERE referred_by IS NOT NULL;
-
--- 2. 删除索引
-ALTER TABLE users DROP INDEX IF EXISTS idx_referred_by;
-
--- 3. 删除字段
-ALTER TABLE users DROP COLUMN referred_by;
-
--- 4. 验证
-SELECT COUNT(*) FROM information_schema.columns
-WHERE table_schema = 'soul_miniprogram'
- AND table_name = 'users'
- AND column_name = 'referred_by';
--- 应该返回 0
-```
-
----
-
-## 🧪 测试验证
-
-### 1. 测试新用户注册
-
-```
-1. 小程序注册新用户(带推荐码)
-2. 检查 referral_bindings 表是否有记录
-3. 验证绑定关系正确
-```
-
----
-
-### 2. 测试推荐人切换
-
-```
-1. 用户B已绑定推荐人A
-2. 点击推荐人C的链接
-3. 检查 referral_bindings 表,B的推荐人应切换为C
-```
-
----
-
-### 3. 测试佣金计算
-
-```
-1. 用户B通过推荐人A的链接购买1元商品
-2. 检查 referral_bindings 表:
- - purchase_count 增加1
- - total_commission 增加约0.9元(90%)
-3. 检查 users 表:
- - 推荐人A的 pending_earnings 增加约0.9元
-```
-
----
-
-### 4. 测试分销中心显示
-
-```
-1. 打开小程序分销中心
-2. 验证显示:
- - "你获得 90% 收益"(shareRate动态读取)
- - 绑定用户列表正确
- - 已付款用户显示购买次数
-```
-
----
-
-## 📊 性能影响
-
-### 查询性能对比
-
-| 操作 | 使用 referred_by | 使用 referral_bindings | 差异 |
-|------|------------------|------------------------|------|
-| 获取推荐人 | ~0.01ms | ~0.1ms | +0.09ms |
-| 获取推荐列表 | ~1ms | ~1.2ms | +0.2ms |
-| 绑定切换 | 需要更新2处 | 只更新1处 | 更简单 |
-
-**结论**: 性能差异可忽略,数据一致性大幅提升 ✅
-
----
-
-## 🚨 注意事项
-
-### 1. 备份重要性
-
-- `users_referred_by_backup` 表保留了所有旧数据
-- 建议保留1-2周,确认无误后再删除
-
----
-
-### 2. 代码部署顺序
-
-**正确顺序**:
-```
-1. 修改代码(已完成)
-2. 删除数据库字段(待执行)
-3. 部署新代码到服务器
-4. 测试功能
-```
-
-**错误顺序**(会报错):
-```
-1. 先删除数据库字段 ❌
-2. 旧代码还在查询 referred_by → 报错!
-```
-
----
-
-### 3. 回滚方案
-
-如果需要回滚:
-
-```sql
--- 1. 从备份恢复字段
-ALTER TABLE users ADD COLUMN referred_by VARCHAR(50);
-
--- 2. 恢复数据
-UPDATE users u
-JOIN users_referred_by_backup b ON u.id = b.id
-SET u.referred_by = b.referred_by;
-
--- 3. 重建索引
-CREATE INDEX idx_referred_by ON users(referred_by);
-```
-
----
-
-## 📝 检查清单
-
-执行前检查:
-- [x] 所有代码已修改完成
-- [ ] 数据库已备份
-- [ ] SQL文件已准备
-- [ ] 在测试环境验证过
-
-执行后检查:
-- [ ] referred_by 字段已删除
-- [ ] 备份表已创建
-- [ ] 新代码已部署
-- [ ] 绑定功能测试通过
-- [ ] 佣金计算测试通过
-- [ ] 分销中心显示正常
-
----
-
-## 🚀 快速执行
-
-### 宝塔面板操作步骤
-
-1. **登录宝塔** → `数据库` → `phpMyAdmin`
-2. **选择数据库** `soul_miniprogram`
-3. **点击 SQL 标签**
-4. **复制粘贴** `scripts/remove-referred-by-field.sql` 的内容
-5. **点击执行**
-6. **查看结果**:应该看到备份表创建成功、字段删除成功
-
----
-
-## ✨ 优化效果
-
-### 修改前:
-```
-绑定关系存储在2个地方:
-- users.referred_by(可能过期、不准确)
-- referral_bindings(完整、准确)
-
-问题:
-- 数据不一致
-- 维护成本高
-- 容易出bug
-```
-
-### 修改后:
-```
-绑定关系只存储在1个地方:
-- referral_bindings(唯一数据源)
-
-优势:
-- 数据一致性强 ✅
-- 维护成本低 ✅
-- 不会出现过期数据 ✅
-```
-
----
-
-**执行完SQL后,请告诉我结果,我会继续协助你部署和测试!**
diff --git a/开发文档/8、部署/可提现金额计算修复.md b/开发文档/8、部署/可提现金额计算修复.md
deleted file mode 100644
index f2dd5a5d..00000000
--- a/开发文档/8、部署/可提现金额计算修复.md
+++ /dev/null
@@ -1,359 +0,0 @@
-# 可提现金额计算修复
-
-## 问题总结
-
-### 发现的问题
-
-**当前逻辑(错误)**:
-```javascript
-可提现金额 = 累计佣金 - 待审核金额
-```
-
-**问题场景**:
-1. 用户累计佣金 ¥100,申请提现 ¥50
-2. 此时:可提现 = 100 - 50 = ¥50 ✅ 正确
-3. 审核通过后,提现记录状态改为 completed
-4. 此时:可提现 = 100 - 0 = ¥100 ❌ 错误!
-
-**根本原因**:审核通过后,`pendingWithdrawAmount` 归零,但没有减去"已提现金额",导致可提现金额"回血"。
-
-## 正确方案
-
-### 计算公式
-
-```javascript
-可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
-```
-
-### 字段说明
-
-| 字段 | 说明 | 来源 | 变化规律 |
-|------|------|------|----------|
-| `totalCommission` | 累计佣金总额 | `SUM(orders.amount) × distributorShare` | 有新订单就增加,只增不减 |
-| `withdrawnEarnings` | 已提现金额 | `users.withdrawn_earnings` | 审核通过时累加 |
-| `pendingWithdrawAmount` | 待审核金额 | `SUM(withdrawals.amount WHERE status='pending')` | 申请时增加,审核后归零 |
-| `availableEarnings` | 可提现金额 | 前端计算 | 动态变化 |
-
-## 修复内容
-
-### 1. 前端计算逻辑修正
-
-**文件**:`miniprogram/pages/referral/referral.js`
-
-**修改前**:
-```javascript
-// ❌ 错误:只减去待审核,没减去已提现
-const totalCommissionNum = realData?.totalCommission || 0
-const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
-const availableEarningsNum = totalCommissionNum - pendingWithdrawNum
-```
-
-**修改后**:
-```javascript
-// ✅ 正确:三元素完整计算
-const totalCommissionNum = realData?.totalCommission || 0
-const withdrawnNum = realData?.withdrawnEarnings || 0
-const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
-const availableEarningsNum = totalCommissionNum - withdrawnNum - pendingWithdrawNum
-```
-
-### 2. 详细日志输出
-
-```javascript
-console.log('=== [Referral] 收益计算(完整版)===')
-console.log('累计佣金 (totalCommission):', totalCommissionNum)
-console.log('已提现金额 (withdrawnEarnings):', withdrawnNum)
-console.log('待审核金额 (pendingWithdrawAmount):', pendingWithdrawNum)
-console.log('可提现金额 = 累计 - 已提现 - 待审核 =', totalCommissionNum, '-', withdrawnNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
-console.log('最低提现金额 (minWithdrawAmount):', minWithdrawAmount)
-console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
-console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
-```
-
-## 验证流程
-
-### 完整场景测试
-
-#### 场景1:初始状态
-```
-数据:
-- 累计佣金: ¥100
-- 已提现: ¥0
-- 待审核: ¥0
-
-计算:
-availableEarnings = 100 - 0 - 0 = ¥100
-
-验证:✅ 可提现 ¥100
-```
-
-#### 场景2:申请提现
-```
-操作:申请提现 ¥50
-
-数据:
-- 累计佣金: ¥100 (不变)
-- 已提现: ¥0 (不变)
-- 待审核: ¥50 (新增)
-
-计算:
-availableEarnings = 100 - 0 - 50 = ¥50
-
-验证:✅ 可提现 ¥50
-```
-
-#### 场景3:审核通过(关键)
-```
-操作:管理员审核通过
-
-数据:
-- 累计佣金: ¥100 (不变)
-- 已提现: ¥50 (users.withdrawn_earnings += 50)
-- 待审核: ¥0 (状态改为 completed)
-
-计算:
-availableEarnings = 100 - 50 - 0 = ¥50
-
-验证:✅ 可提现 ¥50(不会回血!)
-```
-
-#### 场景4:新订单产生
-```
-操作:用户购买新商品,产生佣金 ¥20
-
-数据:
-- 累计佣金: ¥120 (100 + 20)
-- 已提现: ¥50 (不变)
-- 待审核: ¥0 (不变)
-
-计算:
-availableEarnings = 120 - 50 - 0 = ¥70
-
-验证:✅ 可提现 ¥70
-```
-
-#### 场景5:二次提现
-```
-操作:申请提现 ¥30
-
-数据:
-- 累计佣金: ¥120 (不变)
-- 已提现: ¥50 (不变)
-- 待审核: ¥30 (新增)
-
-计算:
-availableEarnings = 120 - 50 - 30 = ¥40
-
-验证:✅ 可提现 ¥40
-```
-
-### 数据库验证
-
-#### 1. 检查 users 表
-```sql
-SELECT
- id,
- nickname,
- withdrawn_earnings
-FROM users
-WHERE id = 'YOUR_USER_ID';
-```
-
-#### 2. 检查 withdrawals 表
-```sql
-SELECT
- id,
- amount,
- status,
- created_at
-FROM withdrawals
-WHERE user_id = 'YOUR_USER_ID'
-ORDER BY created_at DESC;
-```
-
-#### 3. 检查 orders 表
-```sql
-SELECT
- SUM(amount) as total_amount
-FROM orders
-WHERE referrer_id = 'YOUR_USER_ID'
- AND status = 'paid';
-```
-
-#### 4. 手动验证计算
-
-```sql
--- 1. 累计佣金
-SET @total_orders = (SELECT SUM(amount) FROM orders WHERE referrer_id = 'YOUR_USER_ID' AND status = 'paid');
-SET @distributor_share = 0.9;
-SET @total_commission = @total_orders * @distributor_share;
-
--- 2. 已提现
-SET @withdrawn = (SELECT withdrawn_earnings FROM users WHERE id = 'YOUR_USER_ID');
-
--- 3. 待审核
-SET @pending = (SELECT SUM(amount) FROM withdrawals WHERE user_id = 'YOUR_USER_ID' AND status = 'pending');
-
--- 4. 可提现
-SET @available = @total_commission - @withdrawn - IFNULL(@pending, 0);
-
--- 显示结果
-SELECT
- @total_commission as '累计佣金',
- @withdrawn as '已提现',
- @pending as '待审核',
- @available as '可提现';
-```
-
-## 测试步骤
-
-### 1. 清除缓存重新编译
-
-微信开发者工具:
-```
-工具 → 清除缓存 → 清除全部缓存数据
-点击 编译 按钮
-```
-
-### 2. 查看控制台日志
-
-进入分销中心页面,查看日志输出:
-
-```
-=== [Referral] 收益计算(完整版)===
-累计佣金 (totalCommission): 100
-已提现金额 (withdrawnEarnings): 0
-待审核金额 (pendingWithdrawAmount): 0
-可提现金额 = 累计 - 已提现 - 待审核 = 100 - 0 - 0 = 100
-最低提现金额 (minWithdrawAmount): 5
-按钮判断: 100 >= 5 = true
-✅ 按钮应该: 🟢 启用(绿色)
-```
-
-### 3. 测试提现流程
-
-1. **申请提现**:点击提现按钮,申请提现
-2. **查看变化**:
- - 累计佣金不变
- - 待审核金额增加
- - 可提现金额减少
-3. **审核通过**:在管理后台审核通过
-4. **再次查看**:
- - 累计佣金不变
- - 已提现金额增加
- - 待审核金额归零
- - **可提现金额不应该回血**
-
-### 4. 验证按钮状态
-
-不同情况下按钮的状态:
-
-| 可提现金额 | 最低提现 | 按钮状态 | 按钮文本 |
-|-----------|---------|---------|---------|
-| ¥10 | ¥5 | 启用 | 申请提现 ¥10 |
-| ¥3 | ¥5 | 禁用 | 满5元可提现 |
-| ¥0 | ¥5 | 禁用 | 满5元可提现 |
-
-## 常见问题
-
-### Q1: 为什么要分"累计佣金"和"可提现金额"?
-
-**A**:
-- **累计佣金**:用户的成就感,"我总共赚了多少"
-- **可提现金额**:用户的可操作余额,"我现在能提多少"
-
-### Q2: 已提现的钱还算在累计佣金里吗?
-
-**A**: 是的。累计佣金是历史总和,只增不减。已提现只是资金流向,不影响累计数字。
-
-示例:
-- 累计赚了 ¥100(成就)
-- 已提现 ¥50(已到手)
-- 还能提现 ¥50(余额)
-
-### Q3: 如果审核拒绝会怎样?
-
-**A**:
-1. 待审核金额归零(提现申请被取消)
-2. 已提现金额不变
-3. 可提现金额恢复(用户可以重新申请)
-
-### Q4: users.withdrawn_earnings 会自动更新吗?
-
-**A**: 是的,在两个地方会更新:
-- `app/api/withdraw/route.ts` - 自动审核通过时
-- `app/api/admin/withdrawals/route.ts` - 管理员审核通过时
-
-### Q5: 为什么不直接从数据库读取可提现金额?
-
-**A**:
-- 可提现金额是**动态计算值**,不是固定字段
-- 实时计算确保数据准确
-- 便于调试和验证逻辑
-
-## 相关文件
-
-- `miniprogram/pages/referral/referral.js` - 前端计算(已修改)✅
-- `app/api/referral/data/route.ts` - 数据查询(无需修改)
-- `app/api/withdraw/route.ts` - 提现接口(无需修改)
-- `app/api/admin/withdrawals/route.ts` - 审核接口(无需修改)
-
-## 数据流图
-
-```
-┌────────────────────────────────────────────┐
-│ 订单产生佣金 │
-│ ↓ │
-│ 累计佣金 (totalCommission) │
-│ 持续累加,只增不减 │
-│ ↓ │
-│ ┌─────────────┴─────────────┐ │
-│ ↓ ↓ │
-│ 申请提现 继续积累 │
-│ ↓ │
-│ 待审核 (pendingWithdrawAmount) │
-│ 暂时冻结 │
-│ ↓ │
-│ 审核通过 │
-│ ↓ │
-│ 已提现 (withdrawnEarnings) │
-│ 累加记录 │
-│ │
-│ ┌─────────────────────────┐ │
-│ │ 可提现金额(实时计算) │ │
-│ │ = 累计佣金 │ │
-│ │ - 已提现金额 │ │
-│ │ - 待审核金额 │ │
-│ └─────────────────────────┘ │
-└────────────────────────────────────────────┘
-```
-
-## 总结
-
-### 修改前的问题
-
-```javascript
-❌ 可提现 = 累计佣金 - 待审核金额
-
-问题:审核通过后,待审核归零,可提现"回血"
-```
-
-### 修改后的正确逻辑
-
-```javascript
-✅ 可提现 = 累计佣金 - 已提现金额 - 待审核金额
-
-优点:
-1. 审核通过后,可提现金额不会增加
-2. 只有新订单产生佣金时,可提现金额才会增加
-3. 逻辑清晰,符合资金流向
-```
-
-### 业务含义
-
-- **累计佣金**:成就感 - "我赚了多少"
-- **已提现**:安全感 - "我拿到了多少"
-- **待审核**:期待感 - "我正在提多少"
-- **可提现**:行动力 - "我能提多少"
-
-这样的设计既满足了用户查看历史成就的需求,又确保了资金安全和准确性。
diff --git a/开发文档/8、部署/后台提现审核-快速测试指南.md b/开发文档/8、部署/后台提现审核-快速测试指南.md
deleted file mode 100644
index f7501547..00000000
--- a/开发文档/8、部署/后台提现审核-快速测试指南.md
+++ /dev/null
@@ -1,278 +0,0 @@
-# 后台提现审核 - 快速测试指南
-
-## 🚀 快速启动
-
-### 1. 重启服务
-
-```bash
-cd E:\Gongsi\Mycontent
-python devlop.py restart mycontent
-```
-
-### 2. 访问页面
-
-```
-http://localhost:3006/admin/withdrawals
-```
-
-## 📊 界面功能验证
-
-### 顶部统计卡片
-
-应该显示:
-- [ ] 总申请数量
-- [ ] 待处理数量和金额
-- [ ] 已完成数量和金额
-- [ ] 已拒绝数量
-
-### 筛选按钮
-
-- [ ] 全部
-- [ ] 待处理(橙色高亮)
-- [ ] 已完成
-- [ ] 已拒绝
-
-### 提现记录表格
-
-| 列名 | 内容验证 |
-|------|---------|
-| 申请时间 | 显示为本地时间格式 |
-| 用户 | 头像 + 昵称 + 电话/推荐码 |
-| 提现金额 | 橙色加粗显示 |
-| **用户佣金信息** | **新增列** |
-| 状态 | 显示对应颜色的徽章 |
-| 处理时间 | 已处理显示时间,未处理显示"-" |
-| 操作 | 待处理显示批准/拒绝按钮 |
-
-## 🔍 关键功能:用户佣金信息
-
-### 正常显示(绿色)
-
-```
-累计佣金: ¥100.00 [青色]
-已提现: ¥30.00 [灰色]
-待审核: ¥50.00 [橙色]
-────────────────────────
-审核后余额: ¥20.00 [绿色] ✅
-```
-
-**验证要点**:
-- [ ] 所有金额都正确显示两位小数
-- [ ] 累计佣金为青色(#38bdac)
-- [ ] 审核后余额为绿色(正数)
-- [ ] 有分隔线
-
-### 风险警告(红色)
-
-```
-累计佣金: ¥100.00 [青色]
-已提现: ¥30.00 [灰色]
-待审核: ¥80.00 [橙色]
-────────────────────────
-审核后余额: -¥10.00 [红色] ❌
-```
-
-**验证要点**:
-- [ ] 审核后余额为红色(负数)
-- [ ] 点击"批准"时弹出风险警告
-- [ ] 警告文字包含负数金额
-
-## 🧪 测试用例
-
-### 测试1:正常用户提现
-
-**准备数据**:
-```sql
--- 用户A:累计佣金 ¥90,已提现 ¥0,待审核 ¥0
--- 申请提现 ¥50
-
--- 插入订单(产生佣金)
-INSERT INTO orders (id, user_id, amount, referrer_id, status, pay_time)
-VALUES ('test_order_1', 'buyer_1', 100, 'YOUR_USER_ID', 'paid', NOW());
-
--- 申请提现
-INSERT INTO withdrawals (id, user_id, amount, status, wechat_openid, created_at)
-VALUES ('W_TEST_1', 'YOUR_USER_ID', 50, 'pending', NULL, NOW());
-```
-
-**预期结果**:
-- 累计佣金: ¥90.00(100 × 90%)
-- 已提现: ¥0.00
-- 待审核: ¥50.00
-- 审核后余额: ¥40.00 ✅ 绿色
-
-**操作**:点击"批准"
-- 弹出确认框(无风险警告)
-- 确认后状态改为 success
-
-### 测试2:超额提现(风险)
-
-**准备数据**:
-```sql
--- 同一用户再申请 ¥60(总待审核 ¥110 > 可提现 ¥90)
-INSERT INTO withdrawals (id, user_id, amount, status, wechat_openid, created_at)
-VALUES ('W_TEST_2', 'YOUR_USER_ID', 60, 'pending', NULL, NOW());
-```
-
-**预期结果**:
-- W_TEST_1: 审核后余额 ¥40.00 ✅ 绿色
-- W_TEST_2: 审核后余额 **-¥20.00** ❌ 红色
-
-**操作**:点击"批准" W_TEST_2
-- ⚠️ 弹出风险警告
-- 显示负数余额
-- 需要二次确认
-
-### 测试3:拒绝提现
-
-**操作**:点击"拒绝" W_TEST_2
-- 弹出输入框:"请输入拒绝原因"
-- 输入:"余额不足"
-- 确认
-
-**预期结果**:
-- 提现记录状态改为 failed
-- error_message 保存为"余额不足"
-- 刷新页面后,W_TEST_1 的"待审核"金额恢复为 ¥50
-
-## 🎯 快速验证步骤
-
-### Step 1: 查看界面
-
-```bash
-# 1. 启动服务
-python devlop.py restart mycontent
-
-# 2. 浏览器访问
-http://localhost:3006/admin/withdrawals
-```
-
-### Step 2: 检查数据显示
-
-- [ ] 表格列是否完整(7列)
-- [ ] 用户佣金信息列是否显示
-- [ ] 头像是否正确显示
-- [ ] 金额是否有两位小数
-
-### Step 3: 测试筛选
-
-- [ ] 点击"待处理",只显示 pending 记录
-- [ ] 点击"已完成",只显示 success 记录
-- [ ] 点击"全部",显示所有记录
-
-### Step 4: 测试审核
-
-- [ ] 找一条审核后余额为绿色的记录
-- [ ] 点击"批准"
-- [ ] 确认是否正常弹框
-- [ ] 确认后检查状态是否更新
-
-### Step 5: 验证计算
-
-使用验证脚本:
-```sql
--- 在 phpMyAdmin 中执行
--- 文件: scripts/verify-withdrawal-data.sql
-
-SET @user_id = 'YOUR_USER_ID';
--- 然后执行脚本中的所有查询
-```
-
-对比:
-- SQL 计算的可提现金额
-- 页面显示的审核后余额
-- 应该一致 ✅
-
-## 🐛 常见问题排查
-
-### 问题1:用户佣金信息显示"暂无数据"
-
-**原因**:后端查询失败或数据为空
-
-**排查**:
-```bash
-# 查看后端日志
-pm2 logs mycontent --lines 50
-
-# 查找错误信息
-[Withdrawals] 查询失败: ...
-```
-
-### 问题2:审核后余额计算错误
-
-**原因**:数据库数据不一致
-
-**排查**:
-```sql
--- 检查用户的 withdrawn_earnings 是否准确
-SELECT
- u.withdrawn_earnings as DB中的已提现,
- COALESCE(SUM(w.amount), 0) as 实际已完成提现
-FROM users u
-LEFT JOIN withdrawals w ON w.user_id = u.id AND w.status = 'success'
-WHERE u.id = 'YOUR_USER_ID'
-GROUP BY u.id, u.withdrawn_earnings;
-```
-
-### 问题3:批准后状态未更新
-
-**原因**:审核接口可能失败
-
-**排查**:
-```bash
-# 查看浏览器 Network 面板
-# 检查 PUT /api/admin/withdrawals 的响应
-
-# 查看后端日志
-pm2 logs mycontent
-```
-
-### 问题4:头像显示不出来
-
-**原因**:头像URL路径问题
-
-**排查**:
-```sql
--- 检查用户头像字段
-SELECT id, nickname, avatar FROM users WHERE avatar IS NOT NULL LIMIT 5;
-```
-
-## 📝 验证清单
-
-### 数据准确性
-- [ ] 累计佣金 = 订单总金额 × 90%
-- [ ] 已提现 = users.withdrawn_earnings
-- [ ] 待审核 = SUM(withdrawals.amount WHERE status='pending')
-- [ ] 审核后余额 = 累计 - 已提现 - 待审核
-
-### 界面显示
-- [ ] 用户头像/首字母
-- [ ] 用户昵称
-- [ ] 用户电话/推荐码
-- [ ] 提现金额(橙色)
-- [ ] 佣金信息(完整4项)
-- [ ] 状态徽章
-- [ ] 批准/拒绝按钮
-
-### 功能测试
-- [ ] 筛选功能
-- [ ] 刷新功能
-- [ ] 批准操作
-- [ ] 拒绝操作
-- [ ] 风险警告
-
-### 安全验证
-- [ ] 超额提现显示红色
-- [ ] 批准时弹出风险警告
-- [ ] 需要二次确认
-- [ ] 批准后数据正确更新
-
-## 🎉 完成标志
-
-当所有验证项都打勾 ✅,说明提现审核功能已完全对接并正常工作!
-
-**关键指标**:
-- 数据显示完整准确
-- 计算逻辑正确
-- 风险警告有效
-- 审核操作成功
diff --git a/开发文档/8、部署/后台提现审核功能完善说明.md b/开发文档/8、部署/后台提现审核功能完善说明.md
deleted file mode 100644
index 1449c3e3..00000000
--- a/开发文档/8、部署/后台提现审核功能完善说明.md
+++ /dev/null
@@ -1,546 +0,0 @@
-# 后台提现审核功能完善说明
-
-## 概述
-
-完善后台管理的"提现审核"页面,增加用户佣金信息展示,帮助管理员做出准确的审核决策。
-
-## 功能增强
-
-### 1. 新增用户佣金信息展示
-
-在提现记录表格中,新增"用户佣金信息"列,显示:
-
-| 字段 | 说明 | 计算方式 | 颜色 |
-|------|------|----------|------|
-| 累计佣金 | 用户的历史总佣金 | `SUM(orders.amount) × 90%` | 青色 |
-| 已提现 | 已到账的金额 | `users.withdrawn_earnings` | 灰色 |
-| 待审核 | 所有待审核的提现申请 | `SUM(withdrawals.amount WHERE status='pending')` | 橙色 |
-| 审核后余额 | 如果通过该申请,用户剩余的可提现金额 | `累计 - 已提现 - 待审核` | 绿色(≥0) / 红色(<0) |
-
-### 2. 超额提现风险警告
-
-当"审核后余额"为负数时:
-- 以红色显示
-- 批准时弹出二次确认警告
-
-```
-⚠️ 风险警告:该用户审核后余额为负数(¥-20.00),可能存在超额提现。
-
-确认已核实用户账户并完成打款?
-```
-
-### 3. 用户头像显示
-
-优化用户信息展示:
-- ✅ 如果有头像,显示真实头像
-- ✅ 如果无头像,显示昵称首字母
-- ✅ 显示推荐码(如果有)
-
-## 技术实现
-
-### 后端API (`app/api/admin/withdrawals/route.ts`)
-
-#### 查询优化
-
-```typescript
-SELECT
- w.*,
- u.nickname as user_nickname,
- u.phone as user_phone,
- u.avatar as user_avatar,
- u.referral_code,
- u.withdrawn_earnings,
- u.earnings,
- u.pending_earnings,
-
- -- 计算累计佣金(从 orders 表)
- (SELECT COALESCE(SUM(o.amount), 0)
- FROM orders o
- WHERE o.referrer_id = w.user_id AND o.status = 'paid') as total_order_amount,
-
- -- 计算待审核提现金额(不包括当前这条)
- (SELECT COALESCE(SUM(w2.amount), 0)
- FROM withdrawals w2
- WHERE w2.user_id = w.user_id AND w2.status = 'pending' AND w2.id != w.id) as other_pending_amount
-
-FROM withdrawals w
-LEFT JOIN users u ON w.user_id = u.id
-```
-
-#### 返回数据结构
-
-```typescript
-{
- id: string,
- userId: string,
- userNickname: string,
- userPhone: string,
- userAvatar: string,
- referralCode: string,
- amount: number,
- status: string,
- createdAt: string,
- processedAt: string,
-
- // ✅ 新增:用户佣金信息
- userCommissionInfo: {
- totalCommission: number, // 累计佣金
- withdrawnEarnings: number, // 已提现
- pendingWithdrawals: number, // 待审核(包括当前)
- availableAfterThis: number // 审核后余额
- }
-}
-```
-
-#### 计算逻辑
-
-```typescript
-// 累计佣金(90%分成)
-const totalCommission = parseFloat(w.total_order_amount) * 0.9
-
-// 已提现金额
-const withdrawnEarnings = parseFloat(w.withdrawn_earnings) || 0
-
-// 其他待审核金额(不包括当前这笔)
-const otherPendingAmount = parseFloat(w.other_pending_amount) || 0
-
-// 当前提现金额
-const currentWithdrawAmount = parseFloat(w.amount)
-
-// ✅ 审核后余额 = 累计佣金 - 已提现 - 其他待审核 - 当前提现
-const availableAfterThis = totalCommission - withdrawnEarnings - otherPendingAmount - currentWithdrawAmount
-```
-
-### 前端页面 (`app/admin/withdrawals/page.tsx`)
-
-#### 界面布局
-
-```tsx
-
- {w.userCommissionInfo ? (
-
- {/* 累计佣金 */}
-
- 累计佣金:
-
- ¥{w.userCommissionInfo.totalCommission.toFixed(2)}
-
-
-
- {/* 已提现 */}
-
- 已提现:
-
- ¥{w.userCommissionInfo.withdrawnEarnings.toFixed(2)}
-
-
-
- {/* 待审核 */}
-
- 待审核:
-
- ¥{w.userCommissionInfo.pendingWithdrawals.toFixed(2)}
-
-
-
- {/* 审核后余额(带边框分隔) */}
-
- 审核后余额:
- = 0
- ? "text-green-400 font-medium"
- : "text-red-400 font-medium"
- }>
- ¥{w.userCommissionInfo.availableAfterThis.toFixed(2)}
-
-
-
- ) : (
- 暂无数据
- )}
- |
-```
-
-#### 风险警告
-
-```typescript
-const handleApprove = async (id: string) => {
- const withdrawal = withdrawals.find(w => w.id === id)
-
- // 检查超额提现风险
- if (withdrawal?.userCommissionInfo?.availableAfterThis < 0) {
- if (!confirm(`⚠️ 风险警告:该用户审核后余额为负数...`)) {
- return
- }
- } else {
- if (!confirm("确认已完成打款?批准后将更新用户提现记录。")) {
- return
- }
- }
-
- // 执行批准操作
- // ...
-}
-```
-
-## 使用场景
-
-### 场景1:正常提现
-
-**用户A的提现申请**:
-- 累计佣金: ¥100.00
-- 已提现: ¥30.00
-- 待审核: ¥40.00(包括当前 ¥40)
-- 审核后余额: ¥30.00 ✅ 绿色
-
-**管理员判断**:
-- ✅ 数据正常
-- ✅ 审核通过后还有 ¥30 余额
-- ✅ 可以批准
-
-### 场景2:超额提现(风险)
-
-**用户B的提现申请**:
-- 累计佣金: ¥100.00
-- 已提现: ¥30.00
-- 待审核: ¥80.00(当前 ¥60 + 其他 ¥20)
-- 审核后余额: **-¥10.00** ❌ 红色
-
-**管理员判断**:
-- ⚠️ 超额提现风险!
-- ⚠️ 待审核总额 ¥80 > 可提现 ¥70
-- ⚠️ 可能是并发提现或恶意提现
-- 🛑 建议拒绝,或核实后只批准部分
-
-### 场景3:多笔待审核
-
-**用户C的多笔提现**:
-
-**第一笔申请 ¥40**:
-- 累计佣金: ¥100.00
-- 已提现: ¥0.00
-- 待审核: ¥40.00
-- 审核后余额: ¥60.00 ✅
-
-**第二笔申请 ¥30**:
-- 累计佣金: ¥100.00
-- 已提现: ¥0.00
-- 待审核: ¥70.00(¥40 + ¥30)
-- 审核后余额: ¥30.00 ✅
-
-**第三笔申请 ¥40**:
-- 累计佣金: ¥100.00
-- 已提现: ¥0.00
-- 待审核: ¥110.00(¥40 + ¥30 + ¥40)
-- 审核后余额: **-¥10.00** ❌ 红色
-
-**管理员策略**:
-- ✅ 批准第一笔(¥40)
-- ✅ 批准第二笔(¥30)
-- ❌ 拒绝第三笔(¥40)- 余额不足
-
-## 测试验证
-
-### 1. 准备测试数据
-
-```sql
--- 用户A:正常用户
-INSERT INTO users (id, nickname, phone, withdrawn_earnings, referral_code)
-VALUES ('user_a', '测试用户A', '13800138000', 0, 'SOULA001');
-
-INSERT INTO orders (id, user_id, amount, referrer_id, status, pay_time)
-VALUES ('order_a1', 'buyer_1', 100, 'user_a', 'paid', NOW());
-
-INSERT INTO withdrawals (id, user_id, amount, status)
-VALUES ('W_A1', 'user_a', 50, 'pending');
-
--- 用户B:超额提现用户
-INSERT INTO users (id, nickname, phone, withdrawn_earnings, referral_code)
-VALUES ('user_b', '测试用户B', '13900139000', 30, 'SOULB001');
-
-INSERT INTO orders (id, user_id, amount, referrer_id, status, pay_time)
-VALUES ('order_b1', 'buyer_2', 100, 'user_b', 'paid', NOW());
-
-INSERT INTO withdrawals (id, user_id, amount, status)
-VALUES
- ('W_B1', 'user_b', 20, 'pending'),
- ('W_B2', 'user_b', 60, 'pending');
-```
-
-### 2. 访问页面
-
-```
-http://localhost:3006/admin/withdrawals
-```
-
-### 3. 验证显示
-
-**用户A的记录**:
-- 累计佣金: ¥90.00(100 × 90%)
-- 已提现: ¥0.00
-- 待审核: ¥50.00
-- 审核后余额: ¥40.00 ✅ 绿色
-
-**用户B的记录(W_B1)**:
-- 累计佣金: ¥90.00
-- 已提现: ¥30.00
-- 待审核: ¥80.00(20 + 60)
-- 审核后余额: **-¥20.00** ❌ 红色
-
-**用户B的记录(W_B2)**:
-- 累计佣金: ¥90.00
-- 已提现: ¥30.00
-- 待审核: ¥80.00(20 + 60)
-- 审核后余额: **-¥20.00** ❌ 红色
-
-### 4. 测试批准操作
-
-**批准用户A的提现**:
-- 正常弹出确认框
-- 批准成功
-
-**批准用户B的提现**:
-- ⚠️ 弹出风险警告
-- 显示负数余额
-- 需要二次确认
-
-## 管理员决策指南
-
-### 何时批准?
-
-- ✅ 审核后余额 ≥ 0
-- ✅ 用户信息真实完整
-- ✅ 累计佣金合理(有订单支持)
-- ✅ 已完成线下打款(或准备自动转账)
-
-### 何时拒绝?
-
-- ❌ 审核后余额 < 0
-- ❌ 累计佣金异常(没有订单但有佣金)
-- ❌ 同一用户有多笔待审核且总额超额
-- ❌ 用户信息不全或异常
-
-### 如何处理并发提现?
-
-**发现场景**:同一用户有多笔待审核,总额超出可提现金额
-
-**处理策略**:
-1. 按时间顺序查看
-2. 计算每笔的"审核后余额"
-3. 批准最早的几笔(余额充足的)
-4. 拒绝超额的后续申请
-5. 联系用户说明情况
-
-## 数据完整性说明
-
-### 数据来源
-
-| 数据 | 来源 | 可靠性 |
-|------|------|--------|
-| 累计佣金 | `orders` 表实时计算 | ⭐⭐⭐⭐⭐ 最准确 |
-| 已提现 | `users.withdrawn_earnings` | ⭐⭐⭐⭐ 需要提现接口正确维护 |
-| 待审核 | `withdrawals` 表实时查询 | ⭐⭐⭐⭐⭐ 实时准确 |
-
-### 计算公式
-
-```
-累计佣金 = Σ(已付款订单金额) × 分成比例(90%)
-已提现金额 = users.withdrawn_earnings
-待审核金额 = Σ(status='pending'的提现申请)
-审核后余额 = 累计佣金 - 已提现金额 - 待审核金额
-```
-
-## 界面效果
-
-### 提现记录表格
-
-```
-┌──────────────┬───────────┬──────────┬─────────────────────┬────────┬──────────┬────────┐
-│ 申请时间 │ 用户 │ 提现金额 │ 用户佣金信息 │ 状态 │ 处理时间 │ 操作 │
-├──────────────┼───────────┼──────────┼─────────────────────┼────────┼──────────┼────────┤
-│ 2026-02-04 │ [头像] │ ¥50.00 │ 累计佣金: ¥100.00 │ 待处理 │ - │ 批准 │
-│ 15:30 │ 张三 │ │ 已提现: ¥30.00 │ │ │ 拒绝 │
-│ │ 138***00 │ │ 待审核: ¥50.00 │ │ │ │
-│ │ │ │ ───────────────── │ │ │ │
-│ │ │ │ 审核后余额: ¥20.00 │ │ │ │
-└──────────────┴───────────┴──────────┴─────────────────────┴────────┴──────────┴────────┘
-```
-
-### 风险提示(审核后余额<0)
-
-```
-┌──────────────┬───────────┬──────────┬─────────────────────┬────────┬──────────┬────────┐
-│ 申请时间 │ 用户 │ 提现金额 │ 用户佣金信息 │ 状态 │ 处理时间 │ 操作 │
-├──────────────┼───────────┼──────────┼─────────────────────┼────────┼──────────┼────────┤
-│ 2026-02-04 │ [头像] │ ¥80.00 │ 累计佣金: ¥100.00 │ 待处理 │ - │ 批准 │
-│ 15:35 │ 李四 │ │ 已提现: ¥30.00 │ │ │ 拒绝 │
-│ │ 139***00 │ │ 待审核: ¥80.00 │ │ │ │
-│ │ │ │ ───────────────── │ │ │ │
-│ │ │ │ 审核后余额: -¥10.00 │ │ │ │
-│ │ │ │ ↑ [红色] │ │ │ │
-└──────────────┴───────────┴──────────┴─────────────────────┴────────┴──────────┴────────┘
-```
-
-**点击批准时**:
-```
-┌─────────────────────────────────────────┐
-│ ⚠️ 风险警告 │
-├─────────────────────────────────────────┤
-│ 该用户审核后余额为负数(¥-10.00), │
-│ 可能存在超额提现。 │
-│ │
-│ 确认已核实用户账户并完成打款? │
-├─────────────────────────────────────────┤
-│ [取消] [确定] │
-└─────────────────────────────────────────┘
-```
-
-## 审核流程示例
-
-### 正常流程
-
-1. **管理员查看**:
- - 用户:张三
- - 提现金额:¥50
- - 累计佣金:¥100,已提现:¥30,待审核:¥50
- - 审核后余额:¥20 ✅
-
-2. **判断**:数据正常,余额充足
-
-3. **操作**:点击"批准"
-
-4. **结果**:
- - 提现记录状态改为 success
- - 用户的 withdrawn_earnings += 50
- - 用户收到微信零钱(自动转账)
-
-### 风险流程
-
-1. **管理员查看**:
- - 用户:李四
- - 提现金额:¥80
- - 累计佣金:¥100,已提现:¥30,待审核:¥80
- - 审核后余额:**-¥10** ❌
-
-2. **判断**:超额提现,存在风险
-
-3. **操作**:
- - 方案A:直接拒绝,备注"余额不足"
- - 方案B:核实数据后,只批准部分金额
-
-4. **结果**:
- - 拒绝:用户的待审核金额归零,可重新申请
- - 部分批准:需要先拒绝原申请,等用户重新申请合理金额
-
-## 相关文件
-
-### 后端文件
-- `app/api/admin/withdrawals/route.ts` - 提现审核API ✅
-- `app/api/withdraw/route.ts` - 用户提现接口
-- `app/api/referral/data/route.ts` - 分销数据接口
-
-### 前端文件
-- `app/admin/withdrawals/page.tsx` - 提现审核页面 ✅
-- `miniprogram/pages/referral/referral.js` - 小程序提现页面
-
-### 脚本文件
-- `scripts/verify-withdrawal-data.sql` - 数据验证SQL脚本 ✅
-
-## 部署步骤
-
-### 1. 重启后端服务
-
-```bash
-python devlop.py restart mycontent
-```
-
-### 2. 访问管理后台
-
-```
-http://localhost:3006/admin/withdrawals
-```
-
-### 3. 验证数据显示
-
-- [ ] 用户信息完整(昵称、头像、电话)
-- [ ] 提现金额正确
-- [ ] 佣金信息完整(累计、已提现、待审核、审核后余额)
-- [ ] 审核后余额颜色正确(绿色/红色)
-- [ ] 批准/拒绝按钮可用
-
-### 4. 测试审核操作
-
-- [ ] 批准正常提现(审核后余额≥0)
-- [ ] 批准风险提现(会弹出警告)
-- [ ] 拒绝提现(余额返还)
-
-### 5. 验证数据更新
-
-批准后:
-```sql
--- 检查提现记录状态
-SELECT status FROM withdrawals WHERE id = 'W_XXX';
--- 应该是 'success'
-
--- 检查用户已提现金额
-SELECT withdrawn_earnings FROM users WHERE id = 'user_xxx';
--- 应该增加了提现金额
-```
-
-## 后续优化建议
-
-### 1. 批量审核
-
-选中多条记录,一键批准所有:
-```tsx
-
-```
-
-### 2. 自动审核
-
-小额提现(如 ≤ ¥50)自动审核通过:
-```typescript
-if (amount <= 50 && availableAmount >= amount) {
- // 自动批准
-}
-```
-
-### 3. 审核备注
-
-添加备注字段,记录审核原因:
-```sql
-ALTER TABLE withdrawals ADD COLUMN admin_note VARCHAR(500);
-```
-
-### 4. 审核历史
-
-记录审核人和审核时间:
-```sql
-ALTER TABLE withdrawals
-ADD COLUMN approved_by VARCHAR(64),
-ADD COLUMN approved_at TIMESTAMP;
-```
-
-## 总结
-
-这次数据对接实现了:
-
-### 信息完整性
-- ✅ 显示用户的累计佣金
-- ✅ 显示用户的已提现金额
-- ✅ 显示用户的待审核金额
-- ✅ 计算审核后余额
-
-### 风险防控
-- ✅ 超额提现自动标红
-- ✅ 批准时弹出风险警告
-- ✅ 帮助管理员做出正确决策
-
-### 用户体验
-- ✅ 界面清晰,数据一目了然
-- ✅ 颜色区分(绿色安全,红色风险)
-- ✅ 详细的计算明细
-
-**核心价值**:让管理员能够**快速准确地审核提现申请**,同时**有效防范超额提现风险**。
diff --git a/开发文档/8、部署/后台提现审核数据对接.md b/开发文档/8、部署/后台提现审核数据对接.md
deleted file mode 100644
index d9b7434b..00000000
--- a/开发文档/8、部署/后台提现审核数据对接.md
+++ /dev/null
@@ -1,377 +0,0 @@
-# 后台提现审核数据对接
-
-## 需求
-
-在后台管理的"交易中心-提现审核"页面,完善数据显示,让管理员能够:
-1. 查看用户的累计佣金信息
-2. 查看用户的已提现金额
-3. 查看用户的待审核提现金额
-4. 预判审核通过后用户的剩余余额
-5. 识别超额提现风险
-
-## 实现内容
-
-### 1. 后端API增强
-
-**文件**:`app/api/admin/withdrawals/route.ts`
-
-#### 数据库查询优化
-
-添加了用户佣金相关信息的查询:
-
-```typescript
-SELECT
- w.*,
- u.nickname as user_nickname,
- u.phone as user_phone,
- u.avatar as user_avatar,
- u.referral_code,
- u.withdrawn_earnings,
- u.earnings,
- u.pending_earnings,
- -- 计算累计佣金(从 orders 表)
- (SELECT COALESCE(SUM(o.amount), 0)
- FROM orders o
- WHERE o.referrer_id = w.user_id AND o.status = 'paid') as total_order_amount,
- -- 计算待审核提现金额(不包括当前这条)
- (SELECT COALESCE(SUM(w2.amount), 0)
- FROM withdrawals w2
- WHERE w2.user_id = w.user_id AND w2.status = 'pending' AND w2.id != w.id) as other_pending_amount
-FROM withdrawals w
-LEFT JOIN users u ON w.user_id = u.id
-```
-
-#### 返回数据结构
-
-新增 `userCommissionInfo` 字段:
-
-```typescript
-{
- id: string,
- userId: string,
- userNickname: string,
- amount: number,
- status: string,
- // ... 其他字段
-
- // ✅ 新增:用户佣金信息
- userCommissionInfo: {
- totalCommission: number, // 累计佣金(订单金额 × 90%)
- withdrawnEarnings: number, // 已提现金额
- pendingWithdrawals: number, // 待审核金额(包括当前这笔)
- availableAfterThis: number // 审核通过后的剩余余额
- }
-}
-```
-
-#### 计算逻辑
-
-```typescript
-// 计算累计佣金(90%分成)
-const totalCommission = parseFloat(w.total_order_amount) * 0.9
-
-// 已提现金额
-const withdrawnEarnings = parseFloat(w.withdrawn_earnings) || 0
-
-// 其他待审核金额(不包括当前这笔)
-const otherPendingAmount = parseFloat(w.other_pending_amount) || 0
-
-// 当前提现金额
-const currentWithdrawAmount = parseFloat(w.amount)
-
-// ✅ 审核后余额 = 累计佣金 - 已提现 - 其他待审核 - 当前提现
-const availableAfterThis = totalCommission - withdrawnEarnings - otherPendingAmount - currentWithdrawAmount
-```
-
-### 2. 前端页面增强
-
-**文件**:`app/admin/withdrawals/page.tsx`
-
-#### 新增列:用户佣金信息
-
-在表格中添加了一列显示用户的完整佣金情况:
-
-| 字段 | 说明 | 颜色 |
-|------|------|------|
-| 累计佣金 | 用户的历史总佣金 | 青色(#38bdac) |
-| 已提现 | 已到账的金额 | 灰色 |
-| 待审核 | 所有待审核的提现申请总和 | 橙色 |
-| 审核后余额 | 如果通过当前申请,用户剩余的可提现金额 | 绿色(≥0)/ 红色(<0) |
-
-#### 界面效果
-
-```
-┌─────────────────────────────────────────┐
-│ 用户佣金信息 │
-├─────────────────────────────────────────┤
-│ 累计佣金: ¥100.00 [青色] │
-│ 已提现: ¥30.00 [灰色] │
-│ 待审核: ¥50.00 [橙色] │
-│ ───────────────────────────── │
-│ 审核后余额: ¥20.00 [绿色] │
-└─────────────────────────────────────────┘
-```
-
-#### 风险警告
-
-当 `审核后余额 < 0` 时:
-- 显示为红色
-- 批准时弹出警告提示
-
-```typescript
-if (withdrawal.userCommissionInfo.availableAfterThis < 0) {
- confirm(`⚠️ 风险警告:该用户审核后余额为负数(¥${availableAfterThis}),可能存在超额提现。\n\n确认已核实用户账户并完成打款?`)
-}
-```
-
-## 数据示例
-
-### 示例1:正常提现
-
-```
-用户A:
-- 累计佣金: ¥100
-- 已提现: ¥30
-- 其他待审核: ¥20
-- 当前申请: ¥40
-
-审核后余额 = 100 - 30 - 20 - 40 = ¥10 ✅ 正常
-```
-
-### 示例2:超额提现(风险)
-
-```
-用户B:
-- 累计佣金: ¥100
-- 已提现: ¥30
-- 其他待审核: ¥20
-- 当前申请: ¥60
-
-审核后余额 = 100 - 30 - 20 - 60 = -¥10 ❌ 超额!
-```
-
-**警告**:用户B可能存在以下问题:
-1. 重复提现(并发提交)
-2. 恶意超额提现
-3. 数据异常
-
-### 示例3:多笔待审核
-
-```
-用户C:
-- 累计佣金: ¥200
-- 已提现: ¥50
-- 其他待审核: ¥80(两笔:¥50 + ¥30)
-- 当前申请: ¥50
-
-审核后余额 = 200 - 50 - 80 - 50 = ¥20 ✅ 正常
-但注意:如果三笔都通过,还能再提 ¥20
-```
-
-## 审核流程
-
-### 管理员视角
-
-```
-1. 打开提现审核页面
- ↓
-2. 查看待审核列表
- ↓
-3. 查看用户佣金信息
- - 累计佣金: 判断用户赚了多少
- - 已提现: 判断之前提现过多少
- - 待审核: 判断还有多少在审核中
- - 审核后余额: 判断是否超额
- ↓
-4. 点击"批准"或"拒绝"
- ↓
-5. 如果审核后余额 < 0:
- - ⚠️ 弹出风险警告
- - 需要二次确认
- ↓
-6. 确认后执行操作
-```
-
-### 决策依据
-
-**批准条件**:
-- ✅ 审核后余额 ≥ 0
-- ✅ 用户信息真实
-- ✅ 已完成线下打款(或准备自动转账)
-
-**拒绝条件**:
-- ❌ 审核后余额 < 0(超额提现)
-- ❌ 用户信息异常
-- ❌ 疑似恶意提现
-- ❌ 未完成打款且不打算打款
-
-## 统计信息
-
-### 顶部统计卡片
-
-显示全局提现统计:
-
-| 统计项 | 说明 |
-|--------|------|
-| 总申请 | 所有提现申请的数量 |
-| 待处理 | 状态为 pending 的数量和金额 |
-| 已完成 | 状态为 success 的数量和金额 |
-| 已拒绝 | 状态为 failed 的数量 |
-
-### 筛选功能
-
-可以按状态筛选:
-- 全部
-- 待处理(pending)
-- 已完成(success)
-- 已拒绝(failed)
-
-## 状态说明
-
-| 状态 | 英文 | 说明 | 可操作 |
-|------|------|------|--------|
-| 待处理 | pending | 用户刚提交,等待审核 | 批准/拒绝 |
-| 处理中 | processing | 已发起微信转账,等待到账 | - |
-| 已完成 | success | 已到账,提现完成 | - |
-| 已拒绝 | failed | 管理员拒绝,余额已返还 | - |
-
-## 测试步骤
-
-### 1. 准备测试数据
-
-```sql
--- 插入测试用户
-INSERT INTO users (id, nickname, phone, withdrawn_earnings, referral_code)
-VALUES ('test_user_1', '测试用户A', '13800138000', 0, 'SOULA001');
-
--- 插入测试订单(产生佣金)
-INSERT INTO orders (id, user_id, amount, referrer_id, status, pay_time)
-VALUES ('order_1', 'buyer_1', 100, 'test_user_1', 'paid', NOW());
-
--- 插入测试提现申请
-INSERT INTO withdrawals (id, user_id, amount, status, created_at)
-VALUES ('W001', 'test_user_1', 50, 'pending', NOW());
-```
-
-### 2. 访问页面
-
-```
-http://localhost:3006/admin/withdrawals
-```
-
-### 3. 验证数据
-
-**查看提现记录**:
-- 用户昵称:测试用户A
-- 电话:138****8000
-- 提现金额:¥50.00
-
-**查看佣金信息**:
-- 累计佣金:¥90.00(100 × 90%)
-- 已提现:¥0.00
-- 待审核:¥50.00
-- 审核后余额:¥40.00 ✅
-
-### 4. 测试超额提现
-
-```sql
--- 再插入一笔超额提现
-INSERT INTO withdrawals (id, user_id, amount, status, created_at)
-VALUES ('W002', 'test_user_1', 60, 'pending', NOW());
-```
-
-**刷新页面**,查看 W002:
-- 累计佣金:¥90.00
-- 已提现:¥0.00
-- 待审核:¥110.00(50 + 60)
-- 审核后余额:**-¥20.00** ❌ 红色显示
-
-**点击批准**:
-- 应该弹出风险警告
-- 需要二次确认
-
-## 安全检查
-
-### 防止超额提现
-
-1. **前端校验**:小程序端计算可提现金额
-2. **后端校验**:提现接口验证金额
-3. **管理端提示**:显示审核后余额,红色警告
-
-### 并发提现防护
-
-如果用户快速提交多笔提现:
-- 后端查询会累加所有待审核金额
-- 管理员能看到"待审核"总额
-- "审核后余额"会提前减去所有待审核的金额
-
-### 数据一致性
-
-- 累计佣金:实时从 orders 表计算
-- 已提现:从 users.withdrawn_earnings 读取
-- 待审核:实时从 withdrawals 表查询
-
-## 常见问题
-
-### Q1: 为什么审核后余额是负数?
-
-**A**: 可能原因:
-1. 用户并发提交多笔提现
-2. 前端校验被绕过
-3. 数据库数据异常
-
-**处理**:
-- 拒绝超额提现申请
-- 核查用户账户
-- 只批准合理范围内的提现
-
-### Q2: 如果有多笔待审核,应该如何处理?
-
-**A**:
-- 查看"待审核"总额
-- 确保所有待审核的总和 ≤ 可提现金额
-- 按时间顺序逐笔审核
-- 或者拒绝超额的申请
-
-### Q3: 审核后余额为0,还能再提现吗?
-
-**A**:
-- 如果有新订单产生佣金,可以
-- 审核后余额0表示当前所有佣金都将被提走
-- 新订单会增加累计佣金,从而增加可提现金额
-
-### Q4: 如何处理已拒绝的提现?
-
-**A**:
-- 拒绝时余额自动返还
-- 用户可以在小程序重新申请
-- "审核后余额"会恢复
-
-## 相关文件
-
-- `app/admin/withdrawals/page.tsx` - 前端页面 ✅
-- `app/api/admin/withdrawals/route.ts` - 后端API ✅
-- `app/api/withdraw/route.ts` - 用户提现接口
-- `miniprogram/pages/referral/referral.js` - 小程序提现页面
-
-## 总结
-
-这次数据对接实现了:
-
-### 功能增强
-- ✅ 显示用户的完整佣金信息
-- ✅ 计算审核后余额
-- ✅ 风险警告(超额提现)
-- ✅ 数据实时计算,确保准确
-
-### 管理便利
-- ✅ 一眼看清用户的资金状况
-- ✅ 提前识别超额提现风险
-- ✅ 批准/拒绝决策有据可依
-
-### 安全保障
-- ✅ 三层校验(前端、后端、管理端)
-- ✅ 并发提现检测
-- ✅ 超额提现警告
-
-**核心价值**:让管理员能够**看到完整的资金流水**,做出**明智的审核决策**,防止**超额提现风险**。
diff --git a/开发文档/8、部署/后台订单显示优化说明.md b/开发文档/8、部署/后台订单显示优化说明.md
deleted file mode 100644
index 0201c983..00000000
--- a/开发文档/8、部署/后台订单显示优化说明.md
+++ /dev/null
@@ -1,360 +0,0 @@
-# 后台订单显示优化说明
-
-## 📋 优化内容
-
-### 新增显示字段
-- ✅ 购买者昵称
-- ✅ 购买的书名(《一场Soul的创业实验》)
-- ✅ 购买的章节(第X章 第X节)
-- ✅ 商品类型标签
-
----
-
-## 🔧 修改的文件
-
-### 1. `/app/api/orders/route.ts` - 订单API
-
-**修改内容**:
-- JOIN `users` 表获取购买者信息
-- 返回 `userNickname` 和 `userAvatar` 字段
-
-**新增字段**:
-```typescript
-{
- userNickname: string | null, // 购买者昵称
- userAvatar: string | null // 购买者头像
-}
-```
-
-**SQL 查询**:
-```sql
-SELECT o.*, u.nickname as user_nickname, u.avatar as user_avatar
-FROM orders o
-LEFT JOIN users u ON o.user_id = u.id
-ORDER BY o.created_at DESC
-```
-
----
-
-### 2. `/app/admin/page.tsx` - 管理后台首页
-
-**优化内容**:
-- 最近订单卡片显示购买者昵称
-- 显示完整的书名和章节信息
-- 优化UI布局,增加头像展示
-- 改进时间显示格式
-
-**显示效果**:
-
-```
-┌─────────────────────────────────────────────┐
-│ [头像] 张三 · 《一场Soul的创业实验》 │
-│ 章节购买 | 02-04 14:30 +¥0.95 │
-│ 推荐: ABC123 │
-└─────────────────────────────────────────────┘
-```
-
-**核心函数**:
-```typescript
-// 格式化商品信息
-const formatOrderProduct = (p: any) => {
- // 解析 description 字段,返回:
- // { title: "第1章 第2节", subtitle: "《一场Soul的创业实验》" }
-}
-```
-
----
-
-### 3. `/app/admin/orders/page.tsx` - 订单管理页面
-
-**优化内容**:
-- 从API读取订单(包含购买者昵称)
-- 显示完整的书名和章节
-- 改进搜索功能(支持昵称、手机号、商品名搜索)
-- 支持订单号搜索
-- 优化状态筛选(兼容 'paid' 和 'completed' 状态)
-
-**表格列**:
-| 订单号 | 用户 | 商品 | 金额 | 支付方式 | 状态 | 分销佣金 | 下单时间 |
-|--------|------|------|------|----------|------|----------|----------|
-| MP20260204... | 张三
138xxxx | 第1章 第2节
《一场Soul...》 | ¥0.95 | 微信支付 | 已完成 | ¥0.86 | 2026-02-04 14:30 |
-
-**核心函数**:
-```typescript
-// 格式化商品信息
-const formatProduct = (order: any) => {
- return {
- name: "第1章 第2节",
- type: "《一场Soul的创业实验》"
- }
-}
-```
-
----
-
-## 📊 商品信息解析逻辑
-
-### 输入数据(orders 表)
-```javascript
-{
- productType: "section", // 商品类型
- productId: "1-2", // 章节ID
- description: "章节购买-1-2", // 商品描述
- amount: 0.95 // 金额
-}
-```
-
-### 解析规则
-
-#### 1. 章节购买
-**输入**:
-```javascript
-{
- productType: "section",
- productId: "1-2",
- description: "章节购买-1-2"
-}
-```
-
-**输出**:
-```javascript
-{
- title: "第1章 第2节",
- subtitle: "《一场Soul的创业实验》"
-}
-```
-
-#### 2. 整本购买
-**输入**:
-```javascript
-{
- productType: "fullbook",
- description: "《一场Soul的创业实验》全书"
-}
-```
-
-**输出**:
-```javascript
-{
- title: "《一场Soul的创业实验》",
- subtitle: "全书购买"
-}
-```
-
-#### 3. 找伙伴
-**输入**:
-```javascript
-{
- productType: "match",
- description: "找伙伴匹配"
-}
-```
-
-**输出**:
-```javascript
-{
- title: "找伙伴匹配",
- subtitle: "功能服务"
-}
-```
-
----
-
-## 🎨 UI 改进
-
-### 主仪表盘 - 最近订单
-
-**旧版**:
-```
-单章 1-2
-2026-02-04 14:30:15
-邀请码: ABC123
-
-+¥0.95
-微信支付
-```
-
-**新版**:
-```
-[头像] 张三 · 《一场Soul的创业实验》
- 章节购买 | 02-04 14:30 +¥0.95
- 推荐: ABC123 微信
-```
-
-**优势**:
-- ✅ 一目了然看到购买者
-- ✅ 清晰显示书名和章节
-- ✅ 更紧凑的布局
-- ✅ 支持hover高亮
-
----
-
-### 订单管理页面
-
-**改进点**:
-1. **用户列** - 显示昵称和手机号
-2. **商品列** - 显示书名和章节,带类型标签
-3. **搜索** - 支持昵称、手机号、商品名、订单号搜索
-4. **筛选** - 支持多种订单状态筛选
-5. **兼容性** - 兼容 'paid' 和 'completed' 两种状态
-
----
-
-## 🔍 搜索功能增强
-
-### 支持的搜索维度
-1. **用户维度**
- - 购买者昵称
- - 购买者手机号
-
-2. **订单维度**
- - 订单号(orderSn)
- - 订单ID
-
-3. **商品维度**
- - 商品名称(书名、章节)
- - 商品描述
-
-### 示例
-```javascript
-// 搜索 "张三" → 匹配用户昵称
-// 搜索 "138" → 匹配手机号
-// 搜索 "第1章" → 匹配商品名称
-// 搜索 "MP20260204" → 匹配订单号
-```
-
----
-
-## ✅ 测试验证
-
-### 测试场景
-
-#### 1. 主仪表盘 - 最近订单
-- [ ] 显示购买者昵称
-- [ ] 显示完整书名
-- [ ] 显示章节信息
-- [ ] 显示推荐人
-- [ ] 头像正常显示
-
-#### 2. 订单管理页面
-- [ ] 用户列显示昵称和手机号
-- [ ] 商品列显示书名和章节
-- [ ] 搜索功能正常
-- [ ] 筛选功能正常
-- [ ] 订单状态正确
-
-#### 3. API 测试
-```bash
-# 测试订单API
-curl http://localhost:3000/api/orders | jq '.orders[0] | {userNickname, description, productType}'
-
-# 预期输出:
-{
- "userNickname": "张三",
- "description": "章节购买-1-2",
- "productType": "section"
-}
-```
-
----
-
-## 📝 数据库查询说明
-
-### 原查询(无购买者信息)
-```sql
-SELECT * FROM orders ORDER BY created_at DESC
-```
-
-### 新查询(JOIN 用户信息)
-```sql
-SELECT
- o.*,
- u.nickname as user_nickname,
- u.avatar as user_avatar
-FROM orders o
-LEFT JOIN users u ON o.user_id = u.id
-ORDER BY o.created_at DESC
-```
-
-**优势**:
-- 一次查询获取所有必要信息
-- 避免前端多次查询
-- 提升页面加载速度
-
----
-
-## 🚀 部署说明
-
-### 无需数据库迁移
-- ✅ 只是修改查询逻辑,不改表结构
-- ✅ 使用 LEFT JOIN,兼容旧数据
-
-### 部署步骤
-```bash
-# 1. 构建
-pnpm build
-
-# 2. 部署
-python devlop.py
-
-# 3. 重启服务
-# 在宝塔面板重启 PM2
-```
-
----
-
-## 📌 注意事项
-
-### 1. 数据兼容性
-- 如果 `user_id` 对应的用户不存在,显示"匿名用户"
-- 如果 `description` 为空,使用 fallback 显示
-
-### 2. 性能考虑
-- LEFT JOIN 不会影响性能(users 表很小)
-- 前端只展示最近 5 条订单,查询很快
-
-### 3. 未来扩展
-- 可以添加更多商品类型
-- 可以添加订单详情弹窗
-- 可以支持导出带购买者信息的Excel
-
----
-
-## ✅ 完成清单
-
-- ✅ 修改 `/api/orders` API(JOIN users)
-- ✅ 优化主仪表盘"最近订单"卡片
-- ✅ 优化订单管理页面表格
-- ✅ 增强搜索功能
-- ✅ 改进UI布局
-- ✅ 创建文档
-
----
-
-## 📸 效果预览
-
-### 主仪表盘
-```
-┌─ 最近订单 ──────────────────────────────┐
-│ │
-│ [Z] 张三 · 《一场Soul的创业实验》 │
-│ 章节购买 | 02-04 14:30 +¥0.95 │
-│ 推荐: ABC123 │
-│ │
-│ [L] 李四 · 找伙伴匹配 +¥1.00 │
-│ 功能服务 | 02-04 13:15 │
-│ │
-└──────────────────────────────────────────┘
-```
-
-### 订单管理页面
-```
-订单号 | 用户 | 商品 | 金额 | 状态
-----------------|---------------|---------------------|--------|------
-MP20260204... | 张三 | 第1章 第2节 | ¥0.95 | 已完成
- | 138xxxx | 《一场Soul...》 | |
-```
-
----
-
-**优化完成!后台管理端现在可以清晰显示购买者、书名和章节信息了。**
diff --git a/开发文档/8、部署/小程序头像上传优化说明.md b/开发文档/8、部署/小程序头像上传优化说明.md
deleted file mode 100644
index dc2a461a..00000000
--- a/开发文档/8、部署/小程序头像上传优化说明.md
+++ /dev/null
@@ -1,345 +0,0 @@
-# 小程序头像上传优化说明
-
-## 🔧 问题描述
-
-**旧逻辑**:
-- 小程序换头像时直接保存微信临时图片路径
-- 临时路径会过期,导致头像无法显示
-- 数据库存储的是微信的临时URL
-
-**问题**:
-- ❌ 微信临时图片有效期有限(通常几天后失效)
-- ❌ 头像无法长期显示
-- ❌ 用户体验差
-
----
-
-## ✅ 解决方案
-
-**新逻辑**:
-1. 用户选择头像后,先上传图片到自己的服务器
-2. 服务器保存图片到 `public/assets/avatars/` 目录
-3. 返回永久可访问的URL
-4. 将永久URL保存到数据库
-
-**优势**:
-- ✅ 图片永久保存在自己服务器
-- ✅ 头像不会失效
-- ✅ 完全可控
-
----
-
-## 🔧 修改的文件
-
-### 1. `miniprogram/pages/my/my.js`
-
-**修改函数**: `onChooseAvatar()`
-
-**旧逻辑**:
-```javascript
-// 直接使用临时路径
-const avatarUrl = e.detail.avatarUrl
-userInfo.avatar = avatarUrl
-
-// 保存临时路径到数据库
-await app.request('/api/user/update', {
- data: { userId: userInfo.id, avatar: avatarUrl }
-})
-```
-
-**新逻辑**:
-```javascript
-// 1. 上传到服务器
-const uploadRes = await wx.uploadFile({
- url: app.globalData.baseUrl + '/api/upload',
- filePath: tempAvatarUrl,
- name: 'file',
- formData: { folder: 'avatars' }
-})
-
-// 2. 获取永久URL
-const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
-
-// 3. 保存永久URL到数据库
-await app.request('/api/user/update', {
- data: { userId: userInfo.id, avatar: avatarUrl }
-})
-```
-
----
-
-### 2. `miniprogram/pages/settings/settings.js`
-
-**修改函数**: `getWechatAvatar()`
-
-**功能**: 使用 `wx.getUserProfile` 获取微信头像
-
-**修改内容**: 与 `my.js` 相同,先上传图片再保存URL
-
----
-
-## 📁 服务器存储路径
-
-### 图片保存位置
-```
-public/assets/avatars/
- ├── 1738756123456_abc123.jpg
- ├── 1738756234567_def456.png
- └── ...
-```
-
-### 访问URL格式
-```
-https://soul.quwanzhi.com/assets/avatars/1738756123456_abc123.jpg
-```
-
-### 数据库存储
-```javascript
-// users 表
-{
- id: "user_123",
- avatar: "https://soul.quwanzhi.com/assets/avatars/1738756123456_abc123.jpg"
-}
-```
-
----
-
-## 🔄 上传流程
-
-### 完整流程图
-
-```
-用户点击更换头像
- ↓
-微信弹出头像选择器(chooseAvatar / getUserProfile)
- ↓
-获取临时图片路径(tempAvatarUrl)
- ↓
-调用 wx.uploadFile 上传到服务器
- ↓
-服务器保存图片到 public/assets/avatars/
- ↓
-服务器返回永久URL
- ↓
-更新本地 userInfo(app.globalData / storage)
- ↓
-调用 /api/user/update 保存到数据库
- ↓
-完成!
-```
-
-### 代码实现
-
-```javascript
-// 1. 获取临时头像
-const tempAvatarUrl = e.detail.avatarUrl
-
-// 2. 上传到服务器
-const uploadRes = await new Promise((resolve, reject) => {
- wx.uploadFile({
- url: app.globalData.baseUrl + '/api/upload',
- filePath: tempAvatarUrl,
- name: 'file',
- formData: {
- folder: 'avatars' // 保存到 avatars 文件夹
- },
- success: (res) => {
- const data = JSON.parse(res.data)
- if (data.success) {
- resolve(data) // { success: true, data: { url: '/assets/avatars/xxx.jpg' } }
- } else {
- reject(new Error(data.error))
- }
- },
- fail: reject
- })
-})
-
-// 3. 拼接完整URL
-const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
-// 结果: https://soul.quwanzhi.com/assets/avatars/xxx.jpg
-
-// 4. 保存到数据库
-await app.request('/api/user/update', {
- method: 'POST',
- data: { userId: userInfo.id, avatar: avatarUrl }
-})
-```
-
----
-
-## 🔍 错误处理
-
-### 1. 上传失败
-```javascript
-try {
- // ... 上传逻辑
-} catch (e) {
- wx.showToast({
- title: e.message || '上传失败,请重试',
- icon: 'none'
- })
-}
-```
-
-### 2. 网络错误
-- 自动重试机制(可选)
-- 清晰的错误提示
-
-### 3. 文件格式错误
-- 服务器会验证文件类型
-- 只允许 JPG、PNG、GIF、WebP、SVG
-- 文件大小限制 5MB
-
----
-
-## 📊 服务器API
-
-### `/api/upload` - 图片上传接口
-
-**请求方式**: POST (multipart/form-data)
-
-**参数**:
-- `file`: 图片文件
-- `folder`: 保存文件夹(如 'avatars')
-
-**返回**:
-```json
-{
- "success": true,
- "data": {
- "url": "/assets/avatars/1738756123456_abc123.jpg",
- "fileName": "1738756123456_abc123.jpg",
- "size": 45678,
- "type": "image/jpeg"
- }
-}
-```
-
-**文件命名规则**:
-```javascript
-const timestamp = Date.now()
-const randomStr = Math.random().toString(36).substring(2, 8)
-const fileName = `${timestamp}_${randomStr}.${ext}`
-// 例如: 1738756123456_abc123.jpg
-```
-
----
-
-## ✅ 测试清单
-
-### 功能测试
-- [ ] 在"我的"页面点击头像,选择图片后成功上传
-- [ ] 上传后头像立即显示
-- [ ] 刷新页面后头像依然正常显示
-- [ ] 在设置页面使用"获取微信头像"功能正常
-- [ ] 后台管理页面能正确显示用户头像
-
-### 数据验证
-- [ ] 数据库 `users.avatar` 字段保存的是完整URL
-- [ ] URL格式: `https://soul.quwanzhi.com/assets/avatars/xxx.jpg`
-- [ ] 不是微信临时路径(不包含 `weixin` 或 `tmp`)
-
-### 文件验证
-- [ ] 服务器 `public/assets/avatars/` 目录存在
-- [ ] 上传的图片文件正常保存
-- [ ] 文件可通过浏览器直接访问
-
----
-
-## 🚀 部署步骤
-
-### 1. 确保目录存在
-```bash
-# 在服务器上创建目录
-mkdir -p /www/wwwroot/soul.quwanzhi.com/public/assets/avatars
-chmod 755 /www/wwwroot/soul.quwanzhi.com/public/assets/avatars
-```
-
-### 2. 部署代码
-```bash
-# 本地构建
-pnpm build
-
-# 部署到服务器
-python devlop.py
-
-# 重启PM2
-pm2 restart soul
-```
-
-### 3. 小程序代码上传
-- 在微信开发者工具中上传代码
-- 提交审核
-- 发布新版本
-
----
-
-## 📝 注意事项
-
-### 1. 兼容性
-- 旧版本用户的头像可能还是微信临时路径
-- 建议提示用户重新上传头像
-
-### 2. 存储空间
-- 每个头像约 50-200KB
-- 10000个用户约 0.5-2GB
-- 定期清理无用头像(可选)
-
-### 3. CDN优化(可选)
-- 如果用户量大,考虑使用CDN加速
-- 将 `public/assets/avatars/` 目录同步到CDN
-
-### 4. 图片压缩(可选)
-- 可以在上传时自动压缩图片
-- 减少存储空间和加载时间
-
----
-
-## 🔄 数据迁移(可选)
-
-如果需要迁移旧数据(微信临时路径 → 永久URL):
-
-### 方案1: 提示用户重新上传
-```javascript
-// 在小程序中检查头像URL
-if (userInfo.avatar && userInfo.avatar.includes('weixin')) {
- // 提示用户重新上传头像
- wx.showModal({
- title: '头像过期',
- content: '请重新上传您的头像',
- confirmText: '立即上传'
- })
-}
-```
-
-### 方案2: 自动下载并上传(服务器端)
-```javascript
-// 在服务器端批量处理
-// 1. 查询所有微信临时路径的头像
-// 2. 下载图片
-// 3. 上传到自己服务器
-// 4. 更新数据库
-// (需要开发专门的迁移脚本)
-```
-
----
-
-## ✅ 完成状态
-
-- ✅ 修改 `my.js` 的 `onChooseAvatar()` 函数
-- ✅ 修改 `settings.js` 的 `getWechatAvatar()` 函数
-- ✅ 使用现有的 `/api/upload` 接口
-- ✅ 添加错误处理和日志
-- ✅ 创建说明文档
-
----
-
-## 📚 相关文档
-
-- `后台订单显示优化说明.md` - 后台显示头像相关
-- `/api/upload` 接口文档
-
----
-
-**优化完成!小程序头像将永久保存在自己的服务器上,不会再失效!**
diff --git a/开发文档/8、部署/小程序提现金额对接说明.md b/开发文档/8、部署/小程序提现金额对接说明.md
deleted file mode 100644
index 4dc5b90b..00000000
--- a/开发文档/8、部署/小程序提现金额对接说明.md
+++ /dev/null
@@ -1,269 +0,0 @@
-# 小程序最低提现金额对接说明
-
-## 📋 需求
-
-小程序分销中心的最低提现金额,需要从管理后台的「推广设置」→「提现规则」→「最低提现金额」动态获取。
-
----
-
-## ✅ 已完成的对接
-
-### 1. 后端 API 返回最低提现金额
-
-**文件**: `app/api/referral/data/route.ts`
-
-**代码**(第34-42行,第200行):
-```typescript
-// 获取分销配置
-let distributorShare = DISTRIBUTOR_SHARE
-let minWithdrawAmount = 10 // 默认最低提现金额
-try {
- const config = await getConfig('referral_config')
- if (config?.distributorShare) {
- distributorShare = config.distributorShare / 100
- }
- if (config?.minWithdrawAmount) {
- minWithdrawAmount = Number(config.minWithdrawAmount)
- }
-} catch (e) { /* 使用默认配置 */ }
-
-// 返回数据
-return NextResponse.json({
- data: {
- // ... 其他数据 ...
- shareRate: Math.round(distributorShare * 100),
- minWithdrawAmount, // ← 返回给小程序
- }
-})
-```
-
-**逻辑**:
-1. 从 `system_config` 表读取 `referral_config.minWithdrawAmount`
-2. 如果读取失败,使用默认值 10
-3. 在 API 响应中返回给前端
-
----
-
-### 2. 小程序接收并保存配置
-
-**文件**: `miniprogram/pages/referral/referral.js`
-
-**初始化**(第30行):
-```javascript
-data: {
- minWithdrawAmount: 10, // 最低提现金额(从后端获取)
-}
-```
-
-**动态更新**(第161行):
-```javascript
-this.setData({
- shareRate: realData?.shareRate || 90,
- minWithdrawAmount: realData?.minWithdrawAmount || 10, // ← 从API获取
-})
-```
-
-**逻辑**:
-1. 页面加载时初始值为 10
-2. 调用 `/api/referral/data` 获取真实配置
-3. 使用 `setData` 更新 `minWithdrawAmount`
-
----
-
-### 3. 小程序 UI 动态显示
-
-**文件**: `miniprogram/pages/referral/referral.wxml`
-
-**提现按钮**(第52-54行):
-```xml
-
- {{pendingEarnings < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现'}}
-
-```
-
-**显示效果**:
-- **未达到金额**: 按钮显示 "满10元可提现"(灰色禁用)
-- **达到金额**: 按钮显示 "申请提现"(绿色可点击)
-
-**动态性**:
-- 如果管理后台改为 20 元,按钮会显示 "满20元可提现"
-- 如果管理后台改为 5 元,按钮会显示 "满5元可提现"
-
----
-
-### 4. 提现逻辑验证(新增)
-
-**文件**: `miniprogram/pages/referral/referral.js`
-
-**修改前**(第559-565行):
-```javascript
-async handleWithdraw() {
- const pendingEarnings = parseFloat(this.data.pendingEarnings) || 0
-
- if (pendingEarnings <= 0) {
- wx.showToast({ title: '暂无可提现收益', icon: 'none' })
- return
- }
- // 直接提现(没有检查最低金额)❌
-}
-```
-
-**修改后**(已完成):
-```javascript
-async handleWithdraw() {
- const pendingEarnings = parseFloat(this.data.pendingEarnings) || 0
- const minWithdrawAmount = this.data.minWithdrawAmount || 10
-
- if (pendingEarnings <= 0) {
- wx.showToast({ title: '暂无可提现收益', icon: 'none' })
- return
- }
-
- // 检查是否达到最低提现金额 ✅
- if (pendingEarnings < minWithdrawAmount) {
- wx.showToast({
- title: `满${minWithdrawAmount}元可提现`,
- icon: 'none'
- })
- return
- }
-
- // 确认提现...
-}
-```
-
-**优势**:
-- ✅ 双重验证(UI禁用 + 逻辑验证)
-- ✅ 防止用户绕过UI直接调用
-- ✅ 提示信息也是动态的
-
----
-
-## 📊 数据流转图
-
-```
-管理后台输入
- ↓
-保存到 system_config.referral_config.minWithdrawAmount
- ↓
-后端 API (/api/referral/data) 读取并返回
- ↓
-小程序 loadData() 接收并保存到 this.data.minWithdrawAmount
- ↓
-┌─────────────────────┬─────────────────────┐
-│ │ │
-WXML 动态显示 JS 逻辑验证
-│ │
-"满X元可提现" handleWithdraw()
-```
-
----
-
-## 🧪 测试验证
-
-### 测试1: 修改最低提现金额为 20 元
-
-**步骤**:
-1. 登录管理后台 `/admin/referral-settings`
-2. 将「最低提现金额」改为 **20**
-3. 点击「保存配置」
-4. 打开小程序分销中心
-5. 刷新页面(下拉刷新)
-
-**预期结果**:
-- 如果待结算收益 < 20 元,按钮显示 "满20元可提现"(灰色)
-- 如果待结算收益 ≥ 20 元,按钮显示 "申请提现"(绿色)
-- 点击按钮时,也会验证是否 ≥ 20 元
-
----
-
-### 测试2: 修改最低提现金额为 5 元
-
-**步骤**:
-1. 管理后台改为 **5** 元
-2. 小程序刷新
-
-**预期结果**:
-- 按钮显示 "满5元可提现" 或 "申请提现"
-
----
-
-### 测试3: 尝试绕过验证
-
-**步骤**:
-1. 设置最低提现金额为 20 元
-2. 用户只有 10 元待结算
-3. 尝试点击提现按钮(虽然已禁用)
-
-**预期结果**:
-- UI 层面:按钮已禁用,无法点击 ✅
-- 逻辑层面:即使绕过UI,也会提示 "满20元可提现" ✅
-
----
-
-## 📝 对接清单
-
-| 位置 | 功能 | 状态 |
-|------|------|------|
-| 管理后台 | 配置最低提现金额 | ✅ 已完成 |
-| 后端 API | 读取配置并返回 | ✅ 已完成 |
-| 小程序 JS | 接收并保存到 data | ✅ 已完成 |
-| 小程序 WXML | 动态显示按钮文案 | ✅ 已完成 |
-| 小程序 JS | 提现时验证金额 | ✅ 新增完成 |
-
----
-
-## 🎯 核心代码位置
-
-### 后端配置读取
-- **文件**: `app/api/referral/data/route.ts`
-- **行数**: 第34-42行(读取配置),第200行(返回数据)
-
-### 小程序数据接收
-- **文件**: `miniprogram/pages/referral/referral.js`
-- **行数**: 第30行(初始化),第161行(动态更新)
-
-### 小程序 UI 显示
-- **文件**: `miniprogram/pages/referral/referral.wxml`
-- **行数**: 第52-54行(提现按钮)
-
-### 小程序逻辑验证
-- **文件**: `miniprogram/pages/referral/referral.js`
-- **行数**: 第558-578行(handleWithdraw 函数)
-
----
-
-## ✨ 完成效果
-
-### 管理后台操作
-```
-1. 进入「推广设置」
-2. 修改「最低提现金额」为任意值(如 15)
-3. 保存配置
-```
-
-### 小程序自动响应
-```
-1. 用户打开分销中心
-2. API 自动返回最新的 minWithdrawAmount = 15
-3. 按钮显示:
- - 待结算 < 15 元 → "满15元可提现"
- - 待结算 ≥ 15 元 → "申请提现"
-4. 点击提现时,再次验证 ≥ 15 元
-```
-
----
-
-## 🚀 无需额外操作
-
-**好消息**:
-- ✅ 后端已经在返回 `minWithdrawAmount`
-- ✅ 小程序已经在使用这个值
-- ✅ UI 已经动态显示
-- ✅ 现在又加上了逻辑验证
-
-**只需要部署新代码即可!**
-
----
-
-**现在最低提现金额已经完全对接,管理后台修改后小程序会自动生效!**
diff --git a/开发文档/8、部署/小程序昵称自动填充说明.md b/开发文档/8、部署/小程序昵称自动填充说明.md
deleted file mode 100644
index 412cffc2..00000000
--- a/开发文档/8、部署/小程序昵称自动填充说明.md
+++ /dev/null
@@ -1,584 +0,0 @@
-# 小程序昵称自动填充功能说明
-
-## 📋 需求
-
-在"我的"页面点击修改昵称时,唤醒微信的自动填充功能,用户可以一键使用微信昵称。
-
----
-
-## ✅ 实现方案
-
-使用微信官方推荐的 `` 组件,支持自动填充微信昵称。
-
----
-
-## 🔧 实现细节
-
-### 1. 添加昵称输入弹窗
-
-**文件**: `miniprogram/pages/my/my.wxml`
-
-**新增代码**:
-```xml
-
-
-
- ✕
-
-
-
-
- 微信用户可点击自动填充昵称
-
-
-
- 取消
- 确定
-
-
-
-```
-
-**关键点**:
-- `type="nickname"` - 启用微信昵称自动填充 ✅
-- `bindchange="onNicknameChange"` - 监听自动填充事件 ✅
-- `bindinput="onNicknameInput"` - 监听手动输入事件 ✅
-- `maxlength="20"` - 限制昵称长度 ✅
-
----
-
-### 2. 修改 JS 逻辑
-
-**文件**: `miniprogram/pages/my/my.js`
-
-#### 2.1 添加数据字段
-
-```javascript
-data: {
- showNicknameModal: false, // 控制弹窗显示
- editingNickname: '' // 正在编辑的昵称
-}
-```
-
-#### 2.2 修改 editNickname 函数
-
-**修改前**(使用系统弹窗):
-```javascript
-editNickname() {
- wx.showModal({
- title: '修改昵称',
- editable: true,
- placeholderText: '请输入昵称',
- success: async (res) => {
- // ... 处理逻辑
- }
- })
-}
-```
-
-**修改后**(使用自定义弹窗):
-```javascript
-// 打开昵称修改弹窗
-editNickname() {
- this.setData({
- showNicknameModal: true,
- editingNickname: this.data.userInfo?.nickname || ''
- })
-}
-
-// 关闭昵称弹窗
-closeNicknameModal() {
- this.setData({
- showNicknameModal: false,
- editingNickname: ''
- })
-}
-
-// 昵称输入实时更新
-onNicknameInput(e) {
- this.setData({
- editingNickname: e.detail.value
- })
-}
-
-// 昵称变化(微信自动填充时触发)
-onNicknameChange(e) {
- console.log('[My] 昵称已自动填充:', e.detail.value)
- this.setData({
- editingNickname: e.detail.value
- })
-}
-
-// 确认修改昵称
-async confirmNickname() {
- const newNickname = this.data.editingNickname.trim()
-
- if (!newNickname) {
- wx.showToast({ title: '昵称不能为空', icon: 'none' })
- return
- }
-
- if (newNickname.length < 1 || newNickname.length > 20) {
- wx.showToast({ title: '昵称1-20个字符', icon: 'none' })
- return
- }
-
- // 关闭弹窗
- this.closeNicknameModal()
-
- // 显示加载
- wx.showLoading({ title: '更新中...' })
-
- try {
- // 更新本地
- const userInfo = this.data.userInfo
- userInfo.nickname = newNickname
- this.setData({ userInfo })
- app.globalData.userInfo = userInfo
- wx.setStorageSync('userInfo', userInfo)
-
- // 同步到服务器
- await app.request('/api/user/update', {
- method: 'POST',
- data: { userId: userInfo.id, nickname: newNickname }
- })
-
- wx.hideLoading()
- wx.showToast({ title: '昵称已更新', icon: 'success' })
- } catch (e) {
- wx.hideLoading()
- console.error('[My] 更新昵称失败:', e)
- wx.showToast({ title: '更新失败', icon: 'none' })
- }
-}
-```
-
----
-
-### 3. 添加样式
-
-**文件**: `miniprogram/pages/my/my.wxss`
-
-**新增样式**:
-```css
-/* 修改昵称弹窗 */
-.nickname-modal {
- width: 600rpx;
- max-width: 90%;
-}
-
-.modal-header {
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-bottom: 40rpx;
-}
-
-.modal-icon {
- font-size: 60rpx;
- margin-bottom: 16rpx;
-}
-
-.modal-title {
- font-size: 32rpx;
- color: #ffffff;
- font-weight: 600;
-}
-
-.nickname-input-wrap {
- margin-bottom: 40rpx;
-}
-
-.nickname-input {
- width: 100%;
- height: 88rpx;
- padding: 0 24rpx;
- background: rgba(255, 255, 255, 0.05);
- border: 2rpx solid rgba(56, 189, 172, 0.3);
- border-radius: 12rpx;
- font-size: 28rpx;
- color: #ffffff;
- box-sizing: border-box;
-}
-
-.input-tip {
- display: block;
- margin-top: 12rpx;
- font-size: 22rpx;
- color: rgba(56, 189, 172, 0.6);
- text-align: center;
-}
-
-.modal-actions {
- display: flex;
- gap: 20rpx;
-}
-
-.modal-btn {
- flex: 1;
- height: 80rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 12rpx;
- font-size: 28rpx;
- font-weight: 500;
-}
-
-.modal-btn-cancel {
- background: rgba(255, 255, 255, 0.05);
- color: rgba(255, 255, 255, 0.5);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.modal-btn-confirm {
- background: linear-gradient(135deg, #38bdac 0%, #2da396 100%);
- color: #ffffff;
- box-shadow: 0 8rpx 24rpx rgba(56, 189, 172, 0.3);
-}
-```
-
----
-
-## 🎯 使用流程
-
-### 用户操作步骤
-
-1. **打开"我的"页面**
-2. **点击昵称**(或点击"点击设置昵称")
-3. **弹出昵称修改弹窗**
-4. **点击输入框**
- - 微信用户:会自动弹出"使用微信昵称"选项
- - 非微信用户:手动输入昵称
-5. **选择"使用微信昵称"** 或 **手动输入**
-6. **点击"确定"**
-7. **昵称更新成功** ✅
-
----
-
-## 📱 效果展示
-
-### 自动填充流程
-
-```
-点击昵称
- ↓
-显示弹窗(输入框为空或显示当前昵称)
- ↓
-点击输入框
- ↓
-微信弹出选择:
- ┌─────────────────────┐
- │ 使用微信昵称 │
- │ [张三] │ ← 点击即可自动填充
- ├─────────────────────┤
- │ 手动输入 │ ← 或者自己输入
- └─────────────────────┘
- ↓
-自动填充到输入框(触发 onNicknameChange)
- ↓
-点击"确定"
- ↓
-保存到本地 + 同步到服务器
-```
-
----
-
-## 🆚 对比旧版
-
-### 旧版(系统弹窗)❌
-
-```javascript
-wx.showModal({
- editable: true,
- placeholderText: '请输入昵称'
-})
-```
-
-**问题**:
-- ❌ 样式单调,无法自定义
-- ❌ 不支持微信昵称自动填充
-- ❌ 用户体验较差
-
----
-
-### 新版(自定义弹窗)✅
-
-```xml
-
-```
-
-**优势**:
-- ✅ 支持微信昵称自动填充
-- ✅ 样式可自定义(符合APP风格)
-- ✅ 用户体验更好
-- ✅ 微信官方推荐方式
-
----
-
-## 🧪 测试验证
-
-### 测试1: 微信用户自动填充
-
-**步骤**:
-1. 使用微信登录小程序
-2. 进入"我的"页面
-3. 点击昵称
-4. 弹出昵称修改弹窗
-5. 点击输入框
-6. 应该看到"使用微信昵称"选项
-7. 点击"使用微信昵称"
-8. 昵称自动填充到输入框
-9. 点击"确定"
-10. 昵称更新成功
-
-**预期结果**: ✅ 一键使用微信昵称
-
----
-
-### 测试2: 手动输入昵称
-
-**步骤**:
-1. 点击昵称
-2. 弹出弹窗
-3. 点击输入框
-4. 手动输入"Soul创业者"
-5. 点击"确定"
-
-**预期结果**: ✅ 手动输入的昵称保存成功
-
----
-
-### 测试3: 昵称验证
-
-**步骤**:
-1. 输入空昵称 → 提示"昵称不能为空"
-2. 输入超长昵称(>20字符) → 提示"昵称1-20个字符"
-3. 输入正常昵称 → 保存成功
-
-**预期结果**: ✅ 验证逻辑正常
-
----
-
-## 📦 修改文件清单
-
-| 文件 | 修改内容 | 状态 |
-|------|----------|------|
-| `miniprogram/pages/my/my.wxml` | 添加昵称输入弹窗 | ✅ |
-| `miniprogram/pages/my/my.js` | 修改 editNickname 逻辑 | ✅ |
-| `miniprogram/pages/my/my.wxss` | 添加弹窗样式 | ✅ |
-
----
-
-## 🎨 UI 设计
-
-### 弹窗外观
-```
-┌────────────────────────────┐
-│ ✕ │ ← 关闭按钮
-│ │
-│ ✏️ │ ← 图标
-│ 修改昵称 │ ← 标题
-│ │
-│ ┌──────────────────────┐ │
-│ │ 点击输入昵称 │ │ ← 输入框(支持自动填充)
-│ └──────────────────────┘ │
-│ 微信用户可点击自动填充昵称 │ ← 提示文字
-│ │
-│ ┌────────┐ ┌──────────┐ │
-│ │ 取消 │ │ 确定 │ │ ← 操作按钮
-│ └────────┘ └──────────┘ │
-└────────────────────────────┘
-```
-
-### 颜色方案
-- 背景:深色半透明遮罩
-- 弹窗:渐变背景(与APP整体风格一致)
-- 输入框:品牌色边框 `rgba(56, 189, 172, 0.3)`
-- 确定按钮:品牌渐变 `#38bdac → #2da396`
-- 取消按钮:灰色透明
-
----
-
-## 🔍 核心技术点
-
-### 1. `type="nickname"` 属性
-
-**作用**: 启用微信昵称自动填充功能
-
-**触发时机**: 用户点击输入框时
-
-**用户体验**:
-- iOS: 弹出键盘上方显示"使用微信昵称"选项
-- Android: 显示快捷选择弹窗
-
----
-
-### 2. `bindchange` vs `bindinput`
-
-**bindchange**:
-- 当用户点击"使用微信昵称"时触发
-- 自动填充完成时触发
-- `e.detail.value` 包含完整的微信昵称
-
-**bindinput**:
-- 用户手动输入时实时触发
-- 每输入一个字符都会触发
-- `e.detail.value` 包含当前输入值
-
-**两者配合**: 完美支持自动填充和手动输入 ✅
-
----
-
-### 3. 数据流转
-
-```
-用户点击昵称
- ↓
-this.editNickname()
- ↓
-显示弹窗 (showNicknameModal = true)
- ↓
-用户点击输入框
- ↓
-微信弹出选择
- ↓
-选择"使用微信昵称"
- ↓
-onNicknameChange() 触发
- ↓
-editingNickname 更新为微信昵称
- ↓
-用户点击"确定"
- ↓
-confirmNickname() 执行
- ↓
-保存到本地 + 同步服务器
- ↓
-显示成功提示
-```
-
----
-
-## 🔐 安全性
-
-### 1. 输入验证
-
-```javascript
-if (!newNickname) {
- wx.showToast({ title: '昵称不能为空', icon: 'none' })
- return
-}
-
-if (newNickname.length < 1 || newNickname.length > 20) {
- wx.showToast({ title: '昵称1-20个字符', icon: 'none' })
- return
-}
-```
-
----
-
-### 2. 数据同步
-
-```javascript
-// 1. 先更新本地(立即响应)
-userInfo.nickname = newNickname
-this.setData({ userInfo })
-app.globalData.userInfo = userInfo
-wx.setStorageSync('userInfo', userInfo)
-
-// 2. 再同步到服务器(异步)
-await app.request('/api/user/update', {
- method: 'POST',
- data: { userId: userInfo.id, nickname: newNickname }
-})
-```
-
-**优势**:
-- ✅ 用户体验流畅(先更新UI)
-- ✅ 数据持久化(同步到服务器)
-- ✅ 离线友好(失败不影响本地显示)
-
----
-
-## 🎁 额外优化
-
-### 1. 弹窗动画
-
-复用现有的 `.modal-overlay` 和 `.modal-content` 样式,自带淡入淡出效果。
-
----
-
-### 2. 友好提示
-
-```xml
-微信用户可点击自动填充昵称
-```
-
-让用户知道可以使用自动填充功能。
-
----
-
-### 3. 错误处理
-
-```javascript
-try {
- // 同步到服务器
- await app.request(...)
-} catch (e) {
- console.error('[My] 更新昵称失败:', e)
- wx.showToast({ title: '更新失败', icon: 'none' })
-}
-```
-
-即使服务器同步失败,本地仍然更新成功,不影响用户体验。
-
----
-
-## 📱 兼容性
-
-### 微信版本要求
-
-`` 支持的最低版本:
-- **基础库**: 2.21.2
-- **微信版本**: 8.0.16
-
-**兼容处理**:
-- 新版微信:显示"使用微信昵称"选项 ✅
-- 旧版微信:降级为普通输入框(仍可手动输入)✅
-
----
-
-## ✨ 完成效果
-
-### 修改前
-```
-点击昵称 → 系统弹窗 → 手动输入 → 保存
-```
-
-### 修改后
-```
-点击昵称 → 自定义弹窗 →
- ├─ 点击"使用微信昵称" → 一键填充 ✅
- └─ 手动输入 → 保存 ✅
-```
-
----
-
-**现在用户可以一键使用微信昵称了!** 🎉
-
-**相关文件**:
-- ✅ `miniprogram/pages/my/my.wxml`
-- ✅ `miniprogram/pages/my/my.js`
-- ✅ `miniprogram/pages/my/my.wxss`
diff --git a/开发文档/8、部署/小程序调整说明.md b/开发文档/8、部署/小程序调整说明.md
deleted file mode 100644
index 410ff60f..00000000
--- a/开发文档/8、部署/小程序调整说明.md
+++ /dev/null
@@ -1,231 +0,0 @@
-# 小程序调整说明 - 新分销逻辑
-
-## ✅ 已完成的调整
-
-### 1. UI修改
-**文件**: `miniprogram/pages/referral/referral.wxml`
-**修改**: 删除"我的邀请码"卡片
-
-```xml
-
-
-```
-
----
-
-## ✅ 无需调整的部分
-
-### 1. 绑定逻辑(app.js)
-**文件**: `miniprogram/app.js`
-**当前逻辑**: ✅ 完全兼容新逻辑
-
-```javascript
-// 点击推荐链接时
-handleReferralCode(options) {
- // 1. 记录访问
- this.recordReferralVisit(refCode)
-
- // 2. 保存推荐码
- wx.setStorageSync('referral_code', refCode)
-
- // 3. 如果已登录,立即绑定
- if (this.globalData.isLoggedIn) {
- this.bindReferralCode(refCode) // 调用 /api/referral/bind
- }
-}
-```
-
-**为什么无需调整**:
-- 小程序只负责调用 `/api/referral/bind`
-- 后端API已实现"立即切换"逻辑
-- 无论是新绑定、续期还是切换,小程序无需感知
-
----
-
-### 2. 支付逻辑(pages/read/read.js)
-**文件**: `miniprogram/pages/read/read.js`
-**当前逻辑**: ✅ 完全兼容新逻辑
-
-```javascript
-// 支付时
-const referralCode = wx.getStorageSync('referral_code') || ''
-await app.request('/api/miniprogram/pay', {
- data: {
- amount, // 原价(如 1.00)
- referralCode: referralCode || undefined
- }
-})
-```
-
-**为什么无需调整**:
-- 小程序传递原价和推荐码
-- 后端自动计算折扣(如 5% off)
-- 微信支付弹窗会显示折后价(无需小程序干预)
-
----
-
-### 3. 分销中心数据展示(pages/referral/referral.js)
-**文件**: `miniprogram/pages/referral/referral.js`
-**当前逻辑**: ✅ 完全兼容新逻辑
-
-```javascript
-// 数据来源
-const res = await app.request('/api/referral/data?userId=' + userInfo.id)
-
-// 展示数据
-setData({
- bindingCount, // 绑定中的人数
- paidCount, // 已付款的人数
- activeBindings, // 绑定中的用户列表
- convertedBindings, // 已付款的用户列表
- expiredBindings // 已过期的用户列表
-})
-```
-
-**为什么无需调整**:
-- 后端API `/api/referral/data` 已适配新逻辑
-- `convertedBindings` 现在返回 `status = 'active' AND purchase_count > 0`
-- 小程序只是展示后端数据,无需改代码
-
----
-
-## 🆕 可选增强功能
-
-### 建议1: 显示购买次数
-
-**当前显示**:
-```
-用户A +¥0.90
-已付款
-```
-
-**增强后显示**:
-```
-用户A +¥1.80
-已购2次
-```
-
-**实现方式**(可选):
-
-#### 修改 WXML
-```xml
-
-
-
- +¥{{item.commission}}
-
- 已购{{item.purchaseCount || 1}}次
-
-
-```
-
-#### 修改 JS
-```javascript
-// 在 pages/referral/referral.js 的 formatUser 函数中
-const formatUser = (user, type) => {
- return {
- id: user.id,
- nickname: user.nickname,
- commission: (user.commission || 0).toFixed(2),
- purchaseCount: user.purchaseCount || 0, // 新增
- // ...
- }
-}
-```
-
-**是否需要**:根据产品需求决定
-
----
-
-### 建议2: 显示"切换提示"
-
-当用户点击新的推荐链接时,弹窗提示:
-
-```javascript
-// 在 app.js 的 bindReferralCode 函数中
-async bindReferralCode(refCode) {
- const res = await this.request('/api/referral/bind', {
- method: 'POST',
- data: { userId, referralCode: refCode }
- })
-
- // 新增:切换提示
- if (res.success && res.action === 'switch') {
- wx.showToast({
- title: '已切换推荐人',
- icon: 'success'
- })
- }
-}
-```
-
-**是否需要**:可以让用户明确知道绑定关系已切换
-
----
-
-### 建议3: 价格显示优化
-
-**当前**:章节价格固定显示 1.00 元
-
-**优化**:根据是否有推荐码,显示折后价
-
-```javascript
-// 在 pages/read/read.js 的 onLoad 或 onShow 中
-async loadPriceWithDiscount() {
- const referralCode = wx.getStorageSync('referral_code')
- let displayPrice = this.data.section.price // 原价 1.00
-
- if (referralCode) {
- // 从配置获取折扣
- const res = await app.request('/api/db/config?key=referral_config')
- if (res.success && res.config?.userDiscount) {
- const discount = res.config.userDiscount / 100
- displayPrice = this.data.section.price * (1 - discount)
- }
- }
-
- this.setData({
- displayPrice: displayPrice.toFixed(2),
- hasDiscount: referralCode ? true : false
- })
-}
-```
-
-**WXML显示**:
-```xml
-
- ¥1.00
- ¥{{displayPrice}}
- 推荐优惠
-
-```
-
-**是否需要**:可以让用户看到优惠,提升转化率
-
----
-
-## 📋 小程序调整总结
-
-### 必须调整(已完成)
-- ✅ 删除"我的邀请码"卡片
-
-### 无需调整(后端已兼容)
-- ✅ 绑定逻辑(app.js)
-- ✅ 支付逻辑(pages/read/read.js)
-- ✅ 分销中心展示(pages/referral/referral.js)
-
-### 可选增强(看产品需求)
-- ⏸️ 显示购买次数
-- ⏸️ 显示切换提示
-- ⏸️ 显示折后价格
-
----
-
-## ✅ 结论
-
-**小程序端只需要已完成的1处修改(删除邀请码卡片),其他功能都通过后端API自动适配新逻辑,无需额外调整!**
-
-如果你想要可选增强功能,告诉我具体要加哪个,我来帮你实现。
diff --git a/开发文档/8、部署/推广设置功能-完整修复清单.md b/开发文档/8、部署/推广设置功能-完整修复清单.md
deleted file mode 100644
index 32289f24..00000000
--- a/开发文档/8、部署/推广设置功能-完整修复清单.md
+++ /dev/null
@@ -1,369 +0,0 @@
-# 推广设置功能 - 完整修复清单
-
-## 修复概述
-为了确保后台「推广设置」页面的配置能正确应用到整个分销流程,我们修复了 **3 个关键 bug**,并创建了新的管理页面。
-
-## ✅ 已完成的修改
-
-### 1. 创建管理页面入口
-**文件**: `app/admin/layout.tsx`
-**修改内容**: 在侧边栏菜单中增加「推广设置」入口
-
-```typescript
-{ icon: CreditCard, label: "推广设置", href: "/admin/referral-settings" },
-```
-
-**位置**: 「交易中心」和「系统设置」之间
-
----
-
-### 2. 创建推广设置页面
-**文件**: `app/admin/referral-settings/page.tsx` (新建)
-**功能**:
-- 配置「好友优惠」(userDiscount) - 百分比
-- 配置「推广者分成」(distributorShare) - 百分比,带滑块
-- 配置「绑定有效期」(bindingDays) - 天数
-- 配置「最低提现金额」(minWithdrawAmount) - 元
-- 配置「自动提现开关」(enableAutoWithdraw) - 布尔值 (预留)
-
-**关键特性**:
-- 保存时强制类型转换(确保所有数字字段是 `Number` 类型)
-- 加载时有默认值保护
-- 保存成功后有详细提示
-
-```typescript
-const safeConfig = {
- distributorShare: Number(config.distributorShare) || 0,
- minWithdrawAmount: Number(config.minWithdrawAmount) || 0,
- bindingDays: Number(config.bindingDays) || 0,
- userDiscount: Number(config.userDiscount) || 0,
- enableAutoWithdraw: Boolean(config.enableAutoWithdraw),
-}
-```
-
----
-
-### 3. 修复绑定 API 硬编码问题 ⚠️
-**文件**: `app/api/referral/bind/route.ts`
-**Bug**: 使用硬编码 `BINDING_DAYS = 30`,不读取配置
-
-**修复**:
-```typescript
-// 修复前
-const BINDING_DAYS = 30
-const expiryDate = new Date()
-expiryDate.setDate(expiryDate.getDate() + BINDING_DAYS)
-
-// 修复后
-const DEFAULT_BINDING_DAYS = 30
-let bindingDays = DEFAULT_BINDING_DAYS
-try {
- const config = await getConfig('referral_config')
- if (config?.bindingDays) {
- bindingDays = Number(config.bindingDays)
- }
-} catch (e) {
- console.warn('[Referral Bind] 读取配置失败,使用默认值', DEFAULT_BINDING_DAYS)
-}
-
-const expiryDate = new Date()
-expiryDate.setDate(expiryDate.getDate() + bindingDays)
-```
-
-**影响**:
-- ✅ 新用户绑定关系的过期时间会使用后台配置的天数
-- ✅ 支持动态调整绑定期(如改为 60 天)
-
----
-
-### 4. 修复提现 API 缺少门槛检查 ⚠️
-**文件**: `app/api/withdraw/route.ts`
-**Bug**: 没有检查最低提现门槛,只检查了 `amount > 0`
-
-**修复**:
-```typescript
-// 导入 getConfig
-import { query, getConfig } from '@/lib/db'
-
-// 在 POST 函数中添加检查
-let minWithdrawAmount = 10 // 默认值
-try {
- const config = await getConfig('referral_config')
- if (config?.minWithdrawAmount) {
- minWithdrawAmount = Number(config.minWithdrawAmount)
- }
-} catch (e) {
- console.warn('[Withdraw] 读取配置失败,使用默认值 10 元')
-}
-
-// 检查最低提现门槛
-if (amount < minWithdrawAmount) {
- return NextResponse.json({
- success: false,
- message: `最低提现金额为 ¥${minWithdrawAmount},当前 ¥${amount}`
- }, { status: 400 })
-}
-```
-
-**影响**:
-- ✅ 用户提现时会校验后台配置的最低门槛
-- ✅ 防止低于门槛的提现请求
-
----
-
-### 5. 后端 API 验证
-已确认以下 API **正确读取** `referral_config`:
-
-#### 5.1 支付回调 - 佣金计算
-**文件**: `app/api/miniprogram/pay/notify/route.ts`
-```typescript
-let distributorShare = DEFAULT_DISTRIBUTOR_SHARE
-const config = await getConfig('referral_config')
-if (config?.distributorShare) {
- distributorShare = config.distributorShare / 100 // 90 → 0.9
-}
-const commission = Math.round(amount * distributorShare * 100) / 100
-```
-✅ **已验证正确**
-
-#### 5.2 推广数据 API
-**文件**: `app/api/referral/data/route.ts`
-```typescript
-let distributorShare = DISTRIBUTOR_SHARE
-const config = await getConfig('referral_config')
-if (config?.distributorShare) {
- distributorShare = config.distributorShare / 100 // 用于展示
-}
-```
-✅ **已验证正确**
-
----
-
-## 配置字段说明
-
-### 数据库表: `system_config`
-- **config_key**: `referral_config`
-- **config_value**: JSON 字符串
-
-### JSON 结构:
-```json
-{
- "distributorShare": 90, // 推广者分成百分比(存 90,计算时除以 100)
- "minWithdrawAmount": 10, // 最低提现金额(元)
- "bindingDays": 30, // 绑定有效期(天)
- "userDiscount": 5, // 好友优惠百分比(预留字段)
- "enableAutoWithdraw": false // 自动提现开关(预留字段)
-}
-```
-
-**注意**:
-- `distributorShare` 在数据库存的是百分比数字(如 90),使用时需除以 100(0.9)
-- 所有字段必须是 **数字类型**,不能是字符串 `"90"`
-
----
-
-## 完整业务流程
-
-### 1. 用户通过推广链接注册
-**API**: `/api/referral/bind`
-**读取配置**: `bindingDays`
-**行为**: 创建绑定关系,过期时间 = 当前时间 + bindingDays 天
-
-### 2. 绑定用户下单支付
-**API**: `/api/miniprogram/pay/notify`
-**读取配置**: `distributorShare`
-**行为**: 计算佣金 = 订单金额 × (distributorShare / 100),写入 `referral_bindings` 表
-
-### 3. 推广者查看收益
-**API**: `/api/referral/data`
-**读取配置**: `distributorShare`
-**行为**: 展示推广规则卡片,显示当前分成比例
-
-### 4. 推广者申请提现
-**API**: `/api/withdraw`
-**读取配置**: `minWithdrawAmount`
-**行为**:
-- 检查提现金额 >= minWithdrawAmount
-- 创建提现记录
-
-### 5. 管理员审核提现
-**API**: `/api/admin/withdrawals`
-**读取配置**: 不需要
-**行为**: 更新提现状态为 `completed` 或 `rejected`
-
----
-
-## 测试验证步骤
-
-### 验证 1: 绑定天数动态生效
-1. 后台设置「绑定有效期」为 **60 天**,保存
-2. 小程序新用户通过推广链接注册
-3. 数据库查询:
-```sql
-SELECT expiry_date FROM referral_bindings WHERE referee_id = '新用户ID' ORDER BY created_at DESC LIMIT 1;
-```
-4. **预期**: `expiry_date` = 当前时间 + **60 天**
-
-### 验证 2: 佣金比例动态生效
-1. 后台设置「推广者分成」为 **85%**,保存
-2. 已绑定用户购买 100 元订单
-3. 数据库查询:
-```sql
-SELECT commission FROM referral_bindings WHERE status = 'converted' ORDER BY created_at DESC LIMIT 1;
-```
-4. **预期**: `commission` = **85.00**
-
-### 验证 3: 提现门槛动态生效
-1. 后台设置「最低提现金额」为 **50 元**,保存
-2. 用户尝试提现 **30 元**
-3. **预期**: 返回错误「最低提现金额为 ¥50,当前 ¥30」
-
----
-
-## 部署清单
-
-### 1. 代码部署
-```bash
-# 本地构建
-pnpm build
-
-# 上传到服务器
-python devlop.py
-
-# 重启 PM2
-pm2 restart soul
-```
-
-### 2. 数据库检查
-确保 `system_config` 表存在 `referral_config` 配置:
-```sql
-SELECT * FROM system_config WHERE config_key = 'referral_config';
-```
-
-如果不存在,插入默认配置:
-```sql
-INSERT INTO system_config (config_key, config_value, description) VALUES (
- 'referral_config',
- '{"distributorShare":90,"minWithdrawAmount":10,"bindingDays":30,"userDiscount":5,"enableAutoWithdraw":false}',
- '分销 / 推广规则配置'
-);
-```
-
-### 3. 清理缓存
-- 重启 Node.js 服务
-- 清除前端缓存(刷新浏览器 Ctrl+Shift+R)
-- 删除微信小程序缓存(开发者工具 -> 清除缓存)
-
----
-
-## 潜在风险
-
-### 风险 1: 配置读取失败
-**场景**: 数据库连接异常或配置格式错误
-**保护措施**: 所有读取配置的地方都有默认值 fallback
-```typescript
-try {
- const config = await getConfig('referral_config')
- if (config?.distributorShare) {
- distributorShare = config.distributorShare / 100
- }
-} catch (e) {
- // 使用默认配置 DEFAULT_DISTRIBUTOR_SHARE
-}
-```
-
-### 风险 2: 历史订单佣金
-**场景**: 修改配置后,历史订单的佣金会变吗?
-**回答**: **不会**。已结算的佣金存在 `referral_bindings` 表的 `commission` 字段,不会因配置修改而变化。只影响 **新订单**。
-
-### 风险 3: 类型错误
-**场景**: 前端输入框可能返回字符串 `"90"` 而不是数字 `90`
-**保护措施**: 管理页面保存时强制类型转换
-```typescript
-const safeConfig = {
- distributorShare: Number(config.distributorShare) || 0,
- // ...
-}
-```
-
----
-
-## 性能优化建议
-
-### 当前实现
-每次绑定/支付/提现都会查询一次 `system_config` 表
-
-### 优化方案 (可选)
-增加 Redis 缓存:
-```typescript
-// 伪代码
-const cachedConfig = await redis.get('referral_config')
-if (cachedConfig) {
- return JSON.parse(cachedConfig)
-}
-
-const config = await getConfig('referral_config')
-await redis.set('referral_config', JSON.stringify(config), 'EX', 60) // TTL 60s
-return config
-```
-
-**收益**: 减少数据库查询,QPS 可提升 10-20 倍
-**成本**: 需要部署 Redis,配置变更有最多 60 秒延迟
-
----
-
-## 遗留问题
-
-### userDiscount 字段未应用
-**状态**: ✅ 已定义,❌ 未应用
-**说明**: `userDiscount` (好友优惠) 目前只存在配置中,但订单价格计算逻辑中没有实际使用。
-**影响**: 修改这个值 **不会** 影响实际订单价格
-**建议**: 如需启用,需在订单创建 API 中读取此配置并应用折扣
-
-### enableAutoWithdraw 字段未应用
-**状态**: ✅ 已定义,❌ 未实现
-**说明**: 自动提现功能需结合定时任务(cron job)和微信商家转账 API
-**影响**: 修改这个开关 **不会** 触发任何行为
-**建议**: 后续实现定时任务模块时读取此配置
-
----
-
-## FAQ
-
-### Q1: 修改配置后需要重启服务吗?
-**A**: **不需要**。每次请求都会动态读取数据库配置。
-
-### Q2: 小程序展示的规则和后台设置不一致?
-**A**: 可能原因:
-1. 小程序缓存未清除 - 重新编译上传小程序
-2. API 未正确读取配置 - 检查 PM2 日志
-3. 前端硬编码了文案 - 检查小程序代码
-
-### Q3: 测试环境如何验证?
-**A**: 使用测试数据库,修改配置后用测试账号走完整流程(绑定→下单→提现)
-
-### Q4: 如何回滚配置?
-**A**: 执行 SQL:
-```sql
-UPDATE system_config
-SET config_value = '{"distributorShare":90,"minWithdrawAmount":10,"bindingDays":30,"userDiscount":5,"enableAutoWithdraw":false}'
-WHERE config_key = 'referral_config';
-```
-
----
-
-## 总结
-
-✅ **3 个 bug 已修复**:
-1. 绑定 API 读取配置的 `bindingDays`
-2. 提现 API 检查 `minWithdrawAmount`
-3. 管理页面强制类型转换
-
-✅ **5 个 API 已验证正确**:
-1. `/api/referral/bind` - 绑定关系
-2. `/api/miniprogram/pay/notify` - 佣金计算
-3. `/api/referral/data` - 推广数据
-4. `/api/withdraw` - 提现申请
-5. `/api/admin/withdrawals` - 提现审核
-
-✅ **整个分销流程已打通**,后台配置会实时生效!
diff --git a/开发文档/8、部署/推广设置页面测试清单.md b/开发文档/8、部署/推广设置页面测试清单.md
deleted file mode 100644
index 92c19606..00000000
--- a/开发文档/8、部署/推广设置页面测试清单.md
+++ /dev/null
@@ -1,148 +0,0 @@
-# 推广设置页面 - 完整性测试清单
-
-## 前置条件
-- ✅ 已创建 `app/admin/referral-settings/page.tsx`
-- ✅ 已修改 `app/admin/layout.tsx` 添加菜单入口
-- ✅ 已修复 `app/api/referral/bind/route.ts` 读取 `bindingDays`
-
-## 功能验证(按顺序测试)
-
-### 1. 页面访问测试
-- [ ] 访问 `https://soul.quwanzhi.com/admin/referral-settings` 页面正常加载
-- [ ] 左侧菜单显示「推广设置」入口
-- [ ] 点击菜单可正常跳转,高亮状态正确
-
-### 2. 配置加载测试
-- [ ] 页面打开时自动从 `system_config` 表加载现有配置
-- [ ] 若无配置,显示默认值:
- - 好友优惠:5%
- - 推广者分成:90%
- - 绑定有效期:30天
- - 最低提现金额:10元
- - 自动提现:关闭
-
-### 3. 表单输入验证
-- [ ] 修改「好友优惠」为 10,输入框显示正确
-- [ ] 拖动「推广者分成」滑块,数值同步更新
-- [ ] 输入「绑定有效期」为 60,输入框显示正确
-- [ ] 修改「最低提现金额」为 50,输入框显示正确
-- [ ] 切换「自动提现」开关,状态正确
-
-### 4. 配置保存测试
-- [ ] 点击「保存配置」按钮
-- [ ] 弹出成功提示:「✅ 分销配置已保存成功!」
-- [ ] 刷新页面后,配置仍然是刚才保存的值
-
-### 5. 数据库验证
-在数据库执行以下查询:
-```sql
-SELECT config_key, config_value, description
-FROM system_config
-WHERE config_key = 'referral_config';
-```
-
-**预期结果**:
-```json
-{
- "distributorShare": 90,
- "minWithdrawAmount": 10,
- "bindingDays": 30,
- "userDiscount": 5,
- "enableAutoWithdraw": false
-}
-```
-
-所有字段都应该是 **数字类型**(不是字符串 "90")
-
-### 6. 业务流程验证
-
-#### 6.1 绑定关系测试
-1. 修改「绑定有效期」为 **60 天**,保存
-2. 在小程序中让一个新用户通过推广链接进入并登录
-3. 查询数据库:
-```sql
-SELECT referee_id, referrer_id, expiry_date, status
-FROM referral_bindings
-WHERE referee_id = '新用户ID'
-ORDER BY created_at DESC LIMIT 1;
-```
-4. **验证**:`expiry_date` 应该是当前时间 + **60 天**(不是硬编码的 30 天)
-
-#### 6.2 佣金计算测试
-1. 修改「推广者分成」为 **85%**,保存
-2. 让已绑定的用户在小程序购买 100 元的订单
-3. 等待支付成功回调
-4. 查询数据库:
-```sql
-SELECT user_id, earnings, pending_earnings
-FROM users
-WHERE user_id = '推广者ID';
-```
-5. **验证**:`pending_earnings` 应该增加 **85 元**(不是 90 元)
-
-#### 6.3 提现门槛测试
-1. 修改「最低提现金额」为 **50 元**,保存
-2. 刷新小程序「推广中心」页面
-3. **验证**:推广规则卡片显示「满 **50 元** 可提现」
-4. 用 pending_earnings < 50 的账号点击提现,应提示「可提现金额不足」
-
-#### 6.4 小程序展示验证
-刷新小程序「推广中心」页面,检查「推广规则」卡片:
-- [ ] 「好友优惠」显示与后台设置一致(如 5%)
-- [ ] 「你得收益」显示与后台设置一致(如 90%)
-- [ ] 「绑定期」显示与后台设置一致(如 30 天)
-
-## 关键代码验证点
-
-### 读取 referral_config 的 API:
-1. ✅ `app/api/referral/bind/route.ts` - 读取 `bindingDays`
-2. ✅ `app/api/miniprogram/pay/notify/route.ts` - 读取 `distributorShare` 并除以 100
-3. ✅ `app/api/referral/data/route.ts` - 读取 `distributorShare` 并除以 100
-
-### 数据类型保护:
-- ✅ 前端保存时强制转换为 `Number` 类型
-- ✅ 后端 `setConfig` 使用 `JSON.stringify` 正确序列化
-- ✅ 后端 `getConfig` 使用 `JSON.parse` 正确反序列化
-
-## 回滚方案
-如果测试发现问题,可以执行以下 SQL 恢复默认配置:
-```sql
-UPDATE system_config
-SET config_value = '{"distributorShare":90,"minWithdrawAmount":10,"bindingDays":30,"userDiscount":5,"enableAutoWithdraw":false}'
-WHERE config_key = 'referral_config';
-```
-
-## 常见问题排查
-
-### Q1: 保存后刷新,配置变成了默认值?
-**排查**:检查数据库 `system_config` 表是否正确写入
-
-### Q2: 小程序显示的比例与后台不一致?
-**排查**:
-1. 小程序端是否有缓存,清除缓存重试
-2. 检查 API `/api/referral/data` 是否正确读取配置
-3. 检查小程序代码是否硬编码了规则文案
-
-### Q3: 新绑定关系的过期时间还是 30 天?
-**排查**:
-1. 确认 `app/api/referral/bind/route.ts` 已正确修改
-2. 重启 Node.js 服务(`pm2 restart soul`)
-3. 检查服务器日志是否有 "读取配置失败" 的警告
-
-### Q4: 佣金计算还是用的旧比例?
-**排查**:
-1. 确认订单是在 **修改配置之后** 创建的
-2. 历史订单不会重算,只影响新订单
-3. 检查 `app/api/miniprogram/pay/notify/route.ts` 的日志
-
-## 上线建议
-
-1. **先在测试环境验证**:完成上述所有测试用例
-2. **备份数据库**:上线前导出 `system_config` 表
-3. **灰度发布**:先让内部测试账号测试,确认无误后全量放开
-4. **监控日志**:上线后密切关注 PM2 日志,搜索 "referral_config" 关键词
-
-## 性能影响
-- 每次绑定/支付回调会额外查询 1 次 `system_config` 表
-- 由于 `config_key` 有索引,性能影响可忽略
-- 建议后续可增加 Redis 缓存(TTL 60秒)优化性能
diff --git a/开发文档/8、部署/提现卡片数据优化说明.md b/开发文档/8、部署/提现卡片数据优化说明.md
deleted file mode 100644
index 89b50f8d..00000000
--- a/开发文档/8、部署/提现卡片数据优化说明.md
+++ /dev/null
@@ -1,367 +0,0 @@
-# 提现卡片数据优化说明
-
-## 一、修改需求
-
-**用户需求**:
-1. **累计佣金**:显示用户获得的所有佣金总额(包括可提现、待审核、已提现的所有佣金)
-2. **待审核金额**:显示当前已发起提现申请但还未审核的金额累计总和(`withdrawals` 表中 `status = 'pending'` 的金额)
-3. **可提现金额**:显示可以发起提现的金额(即 `users.pending_earnings`)
-
-## 二、数据定义
-
-### 1. 原数据结构
-
-| 字段 | 原定义 | 问题 |
-|------|--------|------|
-| `users.earnings` | 已结算收益 | 不够直观 |
-| `users.pending_earnings` | 待结算收益 | 命名容易误解,实际是可提现金额 |
-| `users.withdrawn_earnings` | 已提现金额 | ✅ 正确 |
-
-### 2. 新数据结构
-
-| 字段 | 新定义 | 说明 |
-|------|--------|------|
-| **累计佣金** (`totalCommission`) | `earnings` + `pending_earnings` + `withdrawn_earnings` | 所有获得的佣金总额 |
-| **可提现金额** (`availableEarnings`) | `pending_earnings` | 未申请提现的佣金,可以发起提现 |
-| **待审核金额** (`pendingWithdrawAmount`) | `SUM(withdrawals.amount) WHERE status='pending'` | 已发起提现但未审核的金额 |
-| **已提现金额** (`withdrawnEarnings`) | `withdrawn_earnings` | 已成功提现的金额 |
-
-### 3. 业务流程
-
-```
-用户获得佣金
- ↓
-累计佣金 +X
-可提现金额 +X (pending_earnings)
- ↓
-用户发起提现申请
- ↓
-可提现金额 -X (pending_earnings)
-待审核金额 +X (withdrawals.status='pending')
- ↓
-管理员审核通过
- ↓
-待审核金额 -X (withdrawals.status='success')
-已提现金额 +X (withdrawn_earnings)
-累计佣金不变
-```
-
-## 三、代码修改
-
-### 1. 后端 API 修改 (`app/api/referral/data/route.ts`)
-
-#### 添加待审核金额查询
-
-```typescript
-// 7. 获取待审核提现金额
-let pendingWithdrawAmount = 0
-try {
- const pendingResult = await query(`
- SELECT COALESCE(SUM(amount), 0) as pending_amount
- FROM withdrawals
- WHERE user_id = ? AND status = 'pending'
- `, [userId]) as any[]
- pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
-} catch (e) {
- console.log('[ReferralData] 获取待审核提现金额失败:', e)
-}
-```
-
-#### 修改返回数据
-
-```typescript
-// === 收益数据 ===
-// 累计佣金总额(所有获得的佣金)
-totalCommission: Math.round((
- (parseFloat(user.earnings) || 0) +
- (parseFloat(user.pending_earnings) || 0) +
- (parseFloat(user.withdrawn_earnings) || 0)
-) * 100) / 100,
-// 可提现金额(pending_earnings)
-availableEarnings: parseFloat(user.pending_earnings) || 0,
-// 待审核金额(提现申请中的金额)
-pendingWithdrawAmount: Math.round(pendingWithdrawAmount * 100) / 100,
-// 已提现金额
-withdrawnEarnings: parseFloat(user.withdrawn_earnings) || 0,
-// 已结算收益(保留兼容)
-earnings: parseFloat(user.earnings) || 0,
-// 待结算收益(保留兼容)
-pendingEarnings: parseFloat(user.pending_earnings) || 0,
-```
-
-### 2. 小程序前端修改
-
-#### 数据字段 (`miniprogram/pages/referral/referral.js`)
-
-```javascript
-data: {
- // === 收益数据 ===
- totalCommission: 0, // 累计佣金总额(所有获得的佣金)
- availableEarnings: 0, // 可提现金额(未申请提现的佣金)
- pendingWithdrawAmount: 0, // 待审核金额(已申请提现但未审核)
- withdrawnEarnings: 0, // 已提现金额
- earnings: 0, // 已结算收益(保留兼容)
- pendingEarnings: 0, // 待结算收益(保留兼容)
- shareRate: 90, // 分成比例(90%)
- minWithdrawAmount: 10, // 最低提现金额(从后端获取)
-}
-```
-
-#### 数据更新逻辑
-
-```javascript
-this.setData({
- // 收益数据 - 格式化为两位小数
- totalCommission: formatMoney(realData?.totalCommission || 0),
- availableEarnings: formatMoney(realData?.availableEarnings || 0),
- pendingWithdrawAmount: formatMoney(realData?.pendingWithdrawAmount || 0),
- withdrawnEarnings: formatMoney(realData?.withdrawnEarnings || 0),
- earnings: formatMoney(realData?.earnings || 0),
- pendingEarnings: formatMoney(realData?.pendingEarnings || 0),
- shareRate: realData?.shareRate || 90,
- minWithdrawAmount: realData?.minWithdrawAmount || 10,
-})
-```
-
-#### 提现逻辑修改
-
-```javascript
-async handleWithdraw() {
- const availableEarnings = parseFloat(this.data.availableEarnings) || 0
- const minWithdrawAmount = this.data.minWithdrawAmount || 10
-
- if (availableEarnings <= 0) {
- wx.showToast({ title: '暂无可提现收益', icon: 'none' })
- return
- }
-
- // 检查是否达到最低提现金额
- if (availableEarnings < minWithdrawAmount) {
- wx.showToast({
- title: `满${minWithdrawAmount}元可提现`,
- icon: 'none'
- })
- return
- }
-
- // 确认提现
- wx.showModal({
- title: '确认提现',
- content: `将提现 ¥${availableEarnings.toFixed(2)} 到您的微信零钱`,
- confirmText: '立即提现',
- success: async (res) => {
- if (res.confirm) {
- await this.doWithdraw(availableEarnings)
- }
- }
- })
-}
-```
-
-### 3. UI 界面修改 (`miniprogram/pages/referral/referral.wxml`)
-
-```xml
-
-
-
-
-
-
- {{availableEarnings < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}}
-
-
-
-```
-
-### 4. 界面变化对比
-
-| 位置 | 原显示 | 新显示 | 说明 |
-|------|--------|--------|------|
-| 卡片标题 | 累计收益 | 累计佣金 | 更准确的描述 |
-| 主金额 | `earnings` | `totalCommission` | 显示所有佣金总和 |
-| 副金额标签 | "待结算" | "待审核" | 更明确的状态描述 |
-| 副金额 | `pendingEarnings` | `pendingWithdrawAmount` | 显示提现申请中的金额 |
-| 按钮文案 | "申请提现" | "申请提现 ¥XX" | 显示可提现金额 |
-| 按钮禁用 | `pendingEarnings < minWithdrawAmount` | `availableEarnings < minWithdrawAmount` | 使用可提现金额判断 |
-
-## 四、验证方法
-
-### 1. 数据库检查
-
-```sql
--- 查看用户收益数据
-SELECT
- id,
- nickname,
- earnings,
- pending_earnings,
- withdrawn_earnings,
- (earnings + pending_earnings + withdrawn_earnings) as total_commission
-FROM users
-WHERE id = 'user_xxx';
-
--- 查看待审核提现金额
-SELECT
- user_id,
- SUM(amount) as pending_amount
-FROM withdrawals
-WHERE status = 'pending'
-GROUP BY user_id;
-```
-
-### 2. API 测试
-
-```bash
-# 测试接口
-curl -X GET "http://localhost:3000/api/referral/data" \
- -H "Cookie: auth_token=xxx"
-```
-
-**期望返回数据**:
-```json
-{
- "success": true,
- "data": {
- "totalCommission": 100.00, // 累计佣金 = 30 + 50 + 20
- "availableEarnings": 50.00, // 可提现 = pending_earnings
- "pendingWithdrawAmount": 20.00, // 待审核 = SUM(withdrawals WHERE status='pending')
- "withdrawnEarnings": 30.00, // 已提现
- "earnings": 30.00, // 已结算(保留兼容)
- "pendingEarnings": 50.00, // 待结算(保留兼容)
- "shareRate": 90,
- "minWithdrawAmount": 10
- }
-}
-```
-
-### 3. 小程序测试
-
-1. **查看提现卡片**
- - ✅ 累计佣金显示正确(所有佣金总和)
- - ✅ 待审核金额显示正确(提现申请中的金额)
- - ✅ 提现按钮显示可提现金额
-
-2. **发起提现**
- - ✅ 提现按钮使用 `availableEarnings` 判断是否可用
- - ✅ 提现金额为 `availableEarnings`
- - ✅ 提现后,`availableEarnings` 减少,`pendingWithdrawAmount` 增加
-
-3. **管理员审核后**
- - ✅ `pendingWithdrawAmount` 减少
- - ✅ `withdrawnEarnings` 增加
- - ✅ `totalCommission` 保持不变
-
-## 五、注意事项
-
-### 1. 向后兼容
-
-为了保证系统稳定,保留了原有的 `earnings` 和 `pendingEarnings` 字段,仅在小程序中使用新字段。
-
-### 2. 提现流程
-
-用户发起提现时,系统会:
-1. 扣减 `users.pending_earnings`
-2. 创建 `withdrawals` 记录(`status = 'pending'`)
-3. 管理员审核通过后,`withdrawals.status` 改为 `'success'`
-4. 同时增加 `users.withdrawn_earnings`
-
-### 3. 数据一致性
-
-确保以下等式始终成立:
-```
-totalCommission = availableEarnings + pendingWithdrawAmount + withdrawnEarnings
-```
-
-### 4. 前端显示
-
-所有金额都使用 `formatMoney()` 函数格式化为两位小数。
-
-## 六、影响范围
-
-### 修改文件
-
-1. **后端**
- - `app/api/referral/data/route.ts` - 添加 `pendingWithdrawAmount` 查询和返回字段
-
-2. **小程序**
- - `miniprogram/pages/referral/referral.js` - 数据字段和提现逻辑
- - `miniprogram/pages/referral/referral.wxml` - UI 显示
-
-### 不影响
-
-- ❌ 提现流程逻辑(`/api/withdraw`)
-- ❌ 管理后台(仍使用原字段)
-- ❌ 佣金计算逻辑(`/api/payment/*/notify`)
-- ❌ 数据库表结构(无需修改)
-
-## 七、测试场景
-
-### 场景 1:新用户获得佣金
-
-```
-初始状态:
-- totalCommission = 0
-- availableEarnings = 0
-- pendingWithdrawAmount = 0
-- withdrawnEarnings = 0
-
-用户 A 购买了 100 元的书籍,推荐人 B 获得 90 元佣金:
-- totalCommission = 90
-- availableEarnings = 90
-- pendingWithdrawAmount = 0
-- withdrawnEarnings = 0
-```
-
-### 场景 2:用户发起提现
-
-```
-用户 B 发起提现 90 元:
-- totalCommission = 90(不变)
-- availableEarnings = 0(减少 90)
-- pendingWithdrawAmount = 90(增加 90)
-- withdrawnEarnings = 0
-```
-
-### 场景 3:管理员审核通过
-
-```
-管理员审核通过,打款成功:
-- totalCommission = 90(不变)
-- availableEarnings = 0
-- pendingWithdrawAmount = 0(减少 90)
-- withdrawnEarnings = 90(增加 90)
-```
-
-### 场景 4:管理员拒绝提现
-
-```
-管理员拒绝提现:
-- totalCommission = 90(不变)
-- availableEarnings = 90(恢复 90)
-- pendingWithdrawAmount = 0(减少 90)
-- withdrawnEarnings = 0
-```
-
-## 八、总结
-
-此次优化主要解决了提现卡片数据定义不清晰的问题:
-
-1. **累计佣金**:直观展示用户获得的所有佣金
-2. **可提现金额**:明确告知用户可以发起提现的金额
-3. **待审核金额**:让用户清楚知道有多少提现申请正在处理中
-
-优化后的界面更加清晰、易懂,用户体验更佳!
diff --git a/开发文档/8、部署/提现双向校验实现.md b/开发文档/8、部署/提现双向校验实现.md
deleted file mode 100644
index 0556f0b5..00000000
--- a/开发文档/8、部署/提现双向校验实现.md
+++ /dev/null
@@ -1,423 +0,0 @@
-# 提现双向校验实现
-
-## 需求
-
-前端和后端都必须使用**相同的逻辑**校验可提现金额,确保安全性和一致性。
-
-## 校验逻辑(统一)
-
-```javascript
-可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
-```
-
-## 前后端对比
-
-### 前端校验(miniprogram)
-
-**文件**:`miniprogram/pages/referral/referral.js`
-
-**作用**:按钮启用/禁用判断
-
-```javascript
-// 计算可提现金额
-const totalCommissionNum = realData?.totalCommission || 0
-const withdrawnNum = realData?.withdrawnEarnings || 0
-const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
-const availableEarningsNum = totalCommissionNum - withdrawnNum - pendingWithdrawNum
-
-// 判断按钮状态
-if (availableEarningsNum >= minWithdrawAmount) {
- // 启用按钮
-} else {
- // 禁用按钮
-}
-```
-
-### 后端校验(API)
-
-**文件**:`app/api/withdraw/route.ts`
-
-**作用**:提现申请最终验证
-
-```typescript
-// 1. 查询累计佣金
-const ordersResult = await query(`
- SELECT COALESCE(SUM(amount), 0) as total_amount
- FROM orders
- WHERE referrer_id = ? AND status = 'paid'
-`, [userId])
-const totalAmount = parseFloat(ordersResult[0]?.total_amount || 0)
-const totalCommission = totalAmount * distributorShare
-
-// 2. 读取已提现金额
-const withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
-
-// 3. 查询待审核金额
-const pendingResult = await query(`
- SELECT COALESCE(SUM(amount), 0) as pending_amount
- FROM withdrawals
- WHERE user_id = ? AND status = 'pending'
-`, [userId])
-const pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
-
-// 4. 计算可提现金额
-const availableAmount = totalCommission - withdrawnEarnings - pendingWithdrawAmount
-
-// 5. 验证
-if (amount > availableAmount) {
- return NextResponse.json({
- success: false,
- message: `可提现金额不足。当前可提现 ¥${availableAmount.toFixed(2)}(累计 ¥${totalCommission.toFixed(2)} - 已提现 ¥${withdrawnEarnings.toFixed(2)} - 待审核 ¥${pendingWithdrawAmount.toFixed(2)})`
- })
-}
-```
-
-## 修改内容
-
-### 1. 后端添加已提现金额
-
-**修改前(错误)**:
-```typescript
-// ❌ 只减去待审核,没减去已提现
-const availableAmount = totalCommission - pendingWithdrawAmount
-```
-
-**修改后(正确)**:
-```typescript
-// ✅ 三元素完整校验
-const withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
-const availableAmount = totalCommission - withdrawnEarnings - pendingWithdrawAmount
-```
-
-### 2. 详细日志输出
-
-```typescript
-console.log('[Withdraw] 提现验证(完整版):')
-console.log('- 累计佣金 (totalCommission):', totalCommission)
-console.log('- 已提现金额 (withdrawnEarnings):', withdrawnEarnings)
-console.log('- 待审核金额 (pendingWithdrawAmount):', pendingWithdrawAmount)
-console.log('- 可提现金额 = 累计 - 已提现 - 待审核 =', totalCommission, '-', withdrawnEarnings, '-', pendingWithdrawAmount, '=', availableAmount)
-console.log('- 申请提现金额 (amount):', amount)
-console.log('- 判断:', amount, '>', availableAmount, '=', amount > availableAmount)
-```
-
-### 3. 优化错误提示
-
-**修改前**:
-```typescript
-message: `可提现金额不足,当前可提现 ¥${availableAmount.toFixed(2)},待审核 ¥${pendingWithdrawAmount.toFixed(2)}`
-```
-
-**修改后**:
-```typescript
-message: `可提现金额不足。当前可提现 ¥${availableAmount.toFixed(2)}(累计 ¥${totalCommission.toFixed(2)} - 已提现 ¥${withdrawnEarnings.toFixed(2)} - 待审核 ¥${pendingWithdrawAmount.toFixed(2)})`
-```
-
-## 双向校验流程
-
-```
-用户点击提现按钮
- ↓
-┌──────────────────────────┐
-│ 前端校验(第一层) │
-│ 按钮启用/禁用判断 │
-├──────────────────────────┤
-│ 可提现 >= 最低金额? │
-│ YES → 允许点击 │
-│ NO → 按钮禁用 │
-└──────────────────────────┘
- ↓
-用户确认提现
- ↓
-┌──────────────────────────┐
-│ 后端校验(第二层) │
-│ API 最终验证 │
-├──────────────────────────┤
-│ 1. 查询累计佣金 │
-│ 2. 读取已提现金额 │
-│ 3. 查询待审核金额 │
-│ 4. 计算可提现金额 │
-│ 5. amount > available? │
-│ YES → 拒绝提现 │
-│ NO → 允许提现 │
-└──────────────────────────┘
- ↓
-创建提现记录
-```
-
-## 为什么需要双向校验?
-
-### 前端校验的作用
-
-**优点**:
-- ✅ 快速响应,提升用户体验
-- ✅ 减少无效请求,降低服务器压力
-- ✅ 按钮禁用,防止误操作
-
-**局限**:
-- ❌ 数据可能过期(API 数据缓存)
-- ❌ 可被绕过(客户端不可信)
-- ❌ 无法阻止恶意请求
-
-### 后端校验的作用
-
-**优点**:
-- ✅ 最终防线,确保资金安全
-- ✅ 实时数据,准确无误
-- ✅ 不可绕过,强制验证
-
-**必要性**:
-- ✅ 防止前端被篡改
-- ✅ 防止并发提现
-- ✅ 防止逻辑漏洞
-
-## 攻击场景防御
-
-### 场景1:前端被篡改
-
-**攻击**:
-- 黑客修改前端代码,移除按钮禁用逻辑
-- 强制发送提现请求
-
-**防御**:
-- ✅ 后端独立校验,拒绝超额提现
-
-### 场景2:并发提现
-
-**攻击**:
-- 用户快速点击两次提现按钮
-- 或同时在多个设备上提现
-
-**防御**:
-- ✅ 后端查询最新的 `pendingWithdrawAmount`
-- ✅ 数据库事务保证原子性
-
-### 场景3:API 重放攻击
-
-**攻击**:
-- 捕获提现请求,重复发送
-
-**防御**:
-- ✅ 后端实时校验可提现金额
-- ✅ 创建提现记录后,`pendingWithdrawAmount` 增加
-- ✅ 第二次请求时,可提现金额不足,拒绝
-
-## 测试用例
-
-### 测试1:正常提现
-
-**数据**:
-- 累计佣金: ¥100
-- 已提现: ¥0
-- 待审核: ¥0
-- 申请提现: ¥50
-
-**前端**:
-```
-可提现 = 100 - 0 - 0 = 100
-50 < 100 → 按钮启用 ✅
-```
-
-**后端**:
-```
-可提现 = 100 - 0 - 0 = 100
-50 ≤ 100 → 允许提现 ✅
-```
-
-**结果**:✅ 提现成功
-
-### 测试2:超额提现
-
-**数据**:
-- 累计佣金: ¥100
-- 已提现: ¥0
-- 待审核: ¥0
-- 申请提现: ¥150
-
-**前端**:
-```
-可提现 = 100 - 0 - 0 = 100
-150 > 100 → 按钮禁用 ✅
-(正常情况下用户无法点击)
-```
-
-**后端**(假设黑客绕过前端):
-```
-可提现 = 100 - 0 - 0 = 100
-150 > 100 → 拒绝提现 ✅
-返回错误:可提现金额不足
-```
-
-**结果**:✅ 后端拦截成功
-
-### 测试3:重复提现
-
-**数据**:
-- 累计佣金: ¥100
-- 已提现: ¥0
-- 待审核: ¥0
-
-**第一次提现 ¥50**:
-```
-前端:100 - 0 - 0 = 100,允许 ✅
-后端:100 - 0 - 0 = 100,允许 ✅
-创建提现记录,pending += 50
-```
-
-**第二次提现 ¥60**(并发或快速点击):
-```
-前端:可能还是显示 100(数据未刷新)
-后端:100 - 0 - 50 = 50
-60 > 50 → 拒绝提现 ✅
-```
-
-**结果**:✅ 后端防御成功
-
-### 测试4:审核通过后再次提现
-
-**初始**:
-- 累计佣金: ¥100
-- 已提现: ¥50(之前提现已到账)
-- 待审核: ¥0
-
-**申请提现 ¥60**:
-```
-前端:100 - 50 - 0 = 50
-60 > 50 → 按钮禁用 ✅
-```
-
-**后端**(假设绕过前端):
-```
-100 - 50 - 0 = 50
-60 > 50 → 拒绝提现 ✅
-```
-
-**结果**:✅ 双重防护
-
-## 日志示例
-
-### 前端日志
-
-```
-=== [Referral] 收益计算(完整版)===
-累计佣金 (totalCommission): 100
-已提现金额 (withdrawnEarnings): 50
-待审核金额 (pendingWithdrawAmount): 0
-可提现金额 = 累计 - 已提现 - 待审核 = 100 - 50 - 0 = 50
-最低提现金额 (minWithdrawAmount): 5
-按钮判断: 50 >= 5 = true
-✅ 按钮应该: 🟢 启用(绿色)
-```
-
-### 后端日志
-
-```
-[Withdraw] 佣金计算:
-- 订单总金额: 111.11
-- 分成比例: 90%
-- 累计佣金: 100
-
-[Withdraw] 提现验证(完整版):
-- 累计佣金 (totalCommission): 100
-- 已提现金额 (withdrawnEarnings): 50
-- 待审核金额 (pendingWithdrawAmount): 0
-- 可提现金额 = 累计 - 已提现 - 待审核 = 100 - 50 - 0 = 50
-- 申请提现金额 (amount): 50
-- 判断: 50 > 50 = false
-✅ 提现申请通过
-```
-
-## 数据一致性保证
-
-### 数据来源
-
-| 字段 | 前端数据来源 | 后端数据来源 | 一致性 |
-|------|-------------|-------------|--------|
-| 累计佣金 | `/api/referral/data` | 实时查询 `orders` | ✅ 相同算法 |
-| 已提现 | `/api/referral/data` | 读取 `users.withdrawn_earnings` | ✅ 相同字段 |
-| 待审核 | `/api/referral/data` | 实时查询 `withdrawals` | ✅ 相同查询 |
-
-### 同步机制
-
-1. **前端数据**:来自 `/api/referral/data`
-2. **后端校验**:独立查询,实时数据
-3. **一致性保证**:
- - 相同的数据库表
- - 相同的计算公式
- - 后端数据更准确(实时查询)
-
-## 相关文件
-
-- `miniprogram/pages/referral/referral.js` - 前端校验 ✅
-- `app/api/withdraw/route.ts` - 后端校验 ✅
-- `app/api/referral/data/route.ts` - 数据查询
-
-## 部署注意事项
-
-### 1. 重启后端服务
-
-```bash
-python devlop.py restart mycontent
-```
-
-### 2. 清除前端缓存
-
-```
-微信开发者工具:工具 → 清除缓存 → 清除全部缓存数据
-```
-
-### 3. 监控日志
-
-```bash
-pm2 logs mycontent --lines 100
-```
-
-关注:
-- `[Withdraw] 提现验证(完整版):` - 后端校验日志
-- `[Referral] 收益计算(完整版)` - 前端计算日志
-
-### 4. 测试验证
-
-- [ ] 正常提现(可提现 > 最低金额)
-- [ ] 超额提现(申请金额 > 可提现)
-- [ ] 并发提现(快速点击两次)
-- [ ] 审核后再提现
-
-## 安全检查清单
-
-- [x] 前端使用三元素计算
-- [x] 后端使用三元素校验
-- [x] 前后端公式完全一致
-- [x] 后端独立查询数据(不信任前端)
-- [x] 详细日志记录
-- [x] 清晰的错误提示
-
-## 总结
-
-### 双向校验的意义
-
-**前端**:
-- 用户体验优化
-- 快速反馈
-- 减少无效请求
-
-**后端**:
-- 安全防线
-- 资金保护
-- 最终决策
-
-### 公式统一
-
-```javascript
-// 前端和后端完全一致
-可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
-```
-
-### 防御能力
-
-- ✅ 防篡改
-- ✅ 防并发
-- ✅ 防重放
-- ✅ 防超额
-
-**核心原则**:前端便利,后端安全,双重保障。
diff --git a/开发文档/8、部署/提现审核流程优化.md b/开发文档/8、部署/提现审核流程优化.md
deleted file mode 100644
index ff9acd1c..00000000
--- a/开发文档/8、部署/提现审核流程优化.md
+++ /dev/null
@@ -1,312 +0,0 @@
-# 提现审核流程优化
-
-## 需求
-
-提现申请提交后,应该明确告知用户:
-- ✅ 提现申请已提交
-- ⏳ 正在审核中
-- 💰 审核通过后会自动到账微信零钱
-
-而不是误导用户以为已经立即到账。
-
-## 修改内容
-
-### 1. 后端提示优化
-
-**文件**:`app/api/withdraw/route.ts`
-
-```typescript
-return NextResponse.json({
- success: true,
- message: '提现申请已提交,正在审核中,通过后会自动到账您的微信零钱',
- data: {
- withdrawId,
- amount,
- account,
- accountType: accountType === 'alipay' ? '支付宝' : '微信',
- status: 'pending' // ✅ 新增:返回状态
- }
-})
-```
-
-**变更**:
-- ❌ 旧:`message: '提现成功'` (误导性)
-- ✅ 新:`message: '提现申请已提交,正在审核中,通过后会自动到账您的微信零钱'` (准确)
-- ✅ 新增 `status: 'pending'` 字段
-
-### 2. 前端提示优化
-
-**文件**:`miniprogram/pages/referral/referral.js`
-
-```javascript
-if (res.success) {
- wx.showModal({
- title: '提现申请已提交 ✅',
- content: res.message || '正在审核中,通过后会自动到账您的微信零钱',
- showCancel: false,
- confirmText: '知道了'
- })
-
- // 刷新数据(此时待审核金额会增加,可提现金额会减少)
- this.initData()
-}
-```
-
-**变更**:
-- ❌ 旧标题:`'提现成功 🎉'` (误导性)
-- ✅ 新标题:`'提现申请已提交 ✅'` (准确)
-- ❌ 旧内容:`'¥X.XX 已到账您的微信零钱'` (虚假)
-- ✅ 新内容:`'正在审核中,通过后会自动到账您的微信零钱'` (真实)
-- ❌ 旧按钮:`'好的'`
-- ✅ 新按钮:`'知道了'`
-
-### 3. 错误提示优化
-
-```javascript
-} else {
- wx.showToast({
- title: res.message || res.error || '提现失败',
- icon: 'none',
- duration: 3000 // ✅ 增加显示时间到3秒
- })
-}
-```
-
-## 提现流程说明
-
-### 完整流程
-
-```
-1. 用户点击"申请提现"
- ↓
-2. 前端验证:可提现金额 >= 最低提现金额
- ↓
-3. 后端创建提现记录:status = 'pending'
- ↓
-4. 提示用户:"提现申请已提交,正在审核中"
- ↓
-5. 刷新分销中心数据:
- - 待审核金额 += 提现金额
- - 可提现金额 -= 提现金额
- ↓
-6. 管理员在后台审核
- ↓
-7. 审核通过:
- - 更新 withdrawals.status = 'completed'
- - 自动转账到微信零钱
- - 发送微信通知给用户
-```
-
-### 状态说明
-
-| 状态 | 英文 | 说明 | 用户提示 |
-|------|------|------|----------|
-| 待审核 | `pending` | 提现申请已提交,等待管理员审核 | 正在审核中 |
-| 已完成 | `completed` | 审核通过,已转账到微信零钱 | 已到账 |
-| 已拒绝 | `failed` | 审核未通过 | 提现失败 |
-
-### 数据变化示例
-
-**提现前**:
-```
-累计佣金: 100元
-待审核金额: 0元
-可提现金额: 100 - 0 = 100元
-```
-
-**申请提现50元后**:
-```
-累计佣金: 100元
-待审核金额: 0 + 50 = 50元
-可提现金额: 100 - 50 = 50元
-```
-
-**审核通过后**:
-```
-累计佣金: 100元
-待审核金额: 50 - 50 = 0元
-已提现金额: 0 + 50 = 50元
-可提现金额: 100 - 0 = 100元 (如果有新订单)
-```
-
-## 用户体验对比
-
-### 修改前(误导性)
-
-```
-┌─────────────────────┐
-│ 提现成功 🎉 │
-├─────────────────────┤
-│ ¥50.00 已到账您的 │
-│ 微信零钱 │
-├─────────────────────┤
-│ [好的] │
-└─────────────────────┘
-```
-
-**问题**:
-- ❌ 用户以为钱已经到账
-- ❌ 用户去微信查看,发现没钱
-- ❌ 产生疑惑和不满
-
-### 修改后(准确清晰)
-
-```
-┌─────────────────────┐
-│ 提现申请已提交 ✅ │
-├─────────────────────┤
-│ 正在审核中,通过后 │
-│ 会自动到账您的微信 │
-│ 零钱 │
-├─────────────────────┤
-│ [知道了] │
-└─────────────────────┘
-```
-
-**优点**:
-- ✅ 用户知道需要等待审核
-- ✅ 用户知道审核通过后会自动到账
-- ✅ 符合实际情况
-
-## 后续优化建议
-
-### 1. 审核通知
-
-管理员审核通过后,发送微信模板消息通知用户:
-
-```javascript
-// 伪代码
-wx.sendTemplateMessage({
- touser: userOpenId,
- template_id: 'OPENTM415934726',
- data: {
- thing1: { value: '提现申请' },
- amount2: { value: '50.00元' },
- phrase3: { value: '审核通过' },
- time4: { value: '2026-02-04 15:30' }
- }
-})
-```
-
-### 2. 提现记录页面
-
-在"我的"页面增加"提现记录"入口,让用户查看:
-- 待审核的提现申请
-- 已完成的提现记录
-- 失败的提现记录及原因
-
-### 3. 审核预计时间
-
-在提示中增加审核时长预期:
-
-```
-正在审核中,预计1-3个工作日内完成审核,
-通过后会自动到账您的微信零钱
-```
-
-### 4. 自动审核
-
-对于小额提现(如50元以下),可以实现自动审核:
-
-```javascript
-if (amount <= 50) {
- // 自动审核通过
- await query(`UPDATE withdrawals SET status = 'completed' WHERE id = ?`, [withdrawId])
- // 立即转账
- await wechatPay.transferToWallet(...)
- return { message: '提现成功,已到账您的微信零钱' }
-} else {
- // 需要人工审核
- return { message: '提现申请已提交,正在审核中' }
-}
-```
-
-## 管理后台提现审核功能
-
-### 审核页面功能
-
-1. **提现列表**:
- - 显示所有待审核的提现申请
- - 显示用户信息、提现金额、申请时间
- - 显示用户的累计佣金、历史提现次数
-
-2. **审核操作**:
- - 通过:调用微信商家转账接口
- - 拒绝:填写拒绝原因
-
-3. **记录查询**:
- - 已完成的提现记录
- - 失败的提现记录
-
-### 审核接口(待实现)
-
-```typescript
-// POST /api/admin/withdraw/approve
-{
- "withdrawId": "W1738694028123",
- "action": "approve" | "reject",
- "reason": "拒绝原因(可选)"
-}
-```
-
-## 测试步骤
-
-### 1. 测试提现申请
-
-1. 在小程序中进入分销中心
-2. 确保可提现金额 >= 5元
-3. 点击"申请提现"按钮
-4. 确认提现金额
-5. 查看提示是否为"提现申请已提交,正在审核中"
-
-### 2. 验证数据变化
-
-提现后立即刷新页面,检查:
-- ✅ 累计佣金不变
-- ✅ 待审核金额增加
-- ✅ 可提现金额减少
-- ✅ 提现按钮重新判断是否可用
-
-### 3. 查看数据库
-
-```sql
--- 查看提现记录
-SELECT * FROM withdrawals
-WHERE user_id = 'ogpTW5fmXRGNpoUbXB3UEqnVe5Tg'
-ORDER BY created_at DESC
-LIMIT 5;
-
--- 应该看到最新的记录,status = 'pending'
-```
-
-### 4. 模拟审核通过
-
-```sql
--- 手动更新状态为已完成
-UPDATE withdrawals
-SET status = 'completed',
- completed_at = NOW()
-WHERE id = 'W1738694028123';
-```
-
-再次刷新小程序,检查:
-- ✅ 待审核金额减少
-- ✅ 可提现金额恢复
-
-## 相关文件
-
-- `app/api/withdraw/route.ts` - 提现接口(修改返回消息)
-- `miniprogram/pages/referral/referral.js` - 前端提现逻辑(修改提示)
-
-## 总结
-
-这次优化的核心是**准确传达信息**:
-
-- ❌ 不要给用户虚假期望("已到账"实际未到账)
-- ✅ 明确告知用户当前状态("正在审核")
-- ✅ 告知用户后续流程("通过后会自动到账")
-
-这样可以:
-1. 提升用户体验(不会产生困惑)
-2. 减少客服咨询(用户知道要等待)
-3. 建立信任(说到做到)
diff --git a/开发文档/8、部署/提现按钮状态修复说明.md b/开发文档/8、部署/提现按钮状态修复说明.md
deleted file mode 100644
index 35a7ef77..00000000
--- a/开发文档/8、部署/提现按钮状态修复说明.md
+++ /dev/null
@@ -1,223 +0,0 @@
-# 提现按钮状态修复说明
-
-## 问题描述
-
-**现象**:
-- 累计佣金:¥8.10
-- 待审核:¥0.00
-- 可提现金额:8.10元
-- 最低提现金额:5元
-- **预期**:按钮应显示"申请提现 ¥8.10"(可用状态)
-- **实际**:按钮显示"满5元可提现"(灰色禁用状态)
-
-## 问题原因
-
-`availableEarnings` 被 `formatMoney()` 格式化为字符串 `"8.10"`,在 WXML 模板中进行数值比较时可能出现类型转换问题。
-
-## 解决方案
-
-### 修改1:增加数字类型字段
-
-在 `referral.js` 的 `data` 中增加 `availableEarningsNum` 字段:
-
-```javascript
-data: {
- availableEarnings: 0, // 字符串格式用于显示
- availableEarningsNum: 0, // 数字格式用于判断
- minWithdrawAmount: 10,
- // ...
-}
-```
-
-### 修改2:初始化时同时设置两个字段
-
-在 `initData()` 方法中:
-
-```javascript
-// 获取原始数字值(用于判断)
-const availableEarningsNum = realData?.availableEarnings || 0
-
-console.log('[Referral] 收益数据:')
-console.log('[Referral] - totalCommission:', realData?.totalCommission)
-console.log('[Referral] - availableEarnings:', availableEarningsNum)
-console.log('[Referral] - minWithdrawAmount:', realData?.minWithdrawAmount)
-console.log('[Referral] - 按钮应该', availableEarningsNum >= (realData?.minWithdrawAmount || 10) ? '可用' : '禁用')
-
-this.setData({
- // ...
- availableEarnings: formatMoney(availableEarningsNum), // 字符串,用于显示
- availableEarningsNum: availableEarningsNum, // 数字,用于判断
- minWithdrawAmount: realData?.minWithdrawAmount || 10,
- // ...
-})
-```
-
-### 修改3:WXML 使用数字字段判断
-
-```xml
-
- {{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}}
-
-```
-
-**注意**:
-- 类名判断使用 `availableEarningsNum`(数字)
-- 文本显示使用 `availableEarnings`(字符串,格式化后的)
-
-### 修改4:提现逻辑也使用数字字段
-
-```javascript
-async handleWithdraw() {
- // 使用数字版本直接进行判断,避免重复转换
- const availableEarnings = this.data.availableEarningsNum || 0
- const minWithdrawAmount = this.data.minWithdrawAmount || 10
-
- console.log('[Withdraw] 提现检查:', {
- availableEarnings,
- minWithdrawAmount,
- shouldEnable: availableEarnings >= minWithdrawAmount
- })
-
- if (availableEarnings <= 0) {
- wx.showToast({ title: '暂无可提现收益', icon: 'none' })
- return
- }
-
- if (availableEarnings < minWithdrawAmount) {
- wx.showToast({
- title: `满${minWithdrawAmount}元可提现`,
- icon: 'none'
- })
- return
- }
- // ...
-}
-```
-
-## 测试步骤
-
-### 1. 清理小程序缓存(重要)
-
-在微信开发者工具中:
-
-1. **清除缓存数据**:
- - 点击顶部菜单 `工具` → `清除缓存` → `清除全部缓存数据`
-
-2. **重新编译**:
- - 点击 `编译` 按钮
- - 或使用快捷键 `Ctrl + B` (Windows) / `Cmd + B` (Mac)
-
-3. **如果还不行,尝试完全重启**:
- - 关闭微信开发者工具
- - 重新打开项目
- - 再次编译
-
-### 2. 查看调试日志
-
-打开控制台(Console),查找以下日志:
-
-```
-[Referral] 收益数据:
-[Referral] - totalCommission: 8.1
-[Referral] - availableEarnings: 8.1
-[Referral] - minWithdrawAmount: 5
-[Referral] - 按钮应该 可用
-```
-
-如果看到"按钮应该 可用",说明逻辑判断正确。
-
-### 3. 点击提现按钮
-
-如果按钮仍然是灰色,点击它,查看是否有日志输出:
-
-```
-[Withdraw] 提现检查: {
- availableEarnings: 8.1,
- minWithdrawAmount: 5,
- shouldEnable: true
-}
-```
-
-### 4. 检查数据值
-
-在小程序调试器的 `AppData` 标签页中查找 `referral` 页面的数据:
-
-```json
-{
- "availableEarnings": "8.10", // 字符串
- "availableEarningsNum": 8.1, // 数字
- "minWithdrawAmount": 5
-}
-```
-
-确认 `availableEarningsNum` 是数字类型,不是字符串。
-
-## 预期结果
-
-修复后,当 `availableEarningsNum` (8.1) >= `minWithdrawAmount` (5) 时:
-
-- ✅ 按钮显示:`申请提现 ¥8.10`
-- ✅ 按钮样式:渐变青色背景(可点击)
-- ✅ 点击后弹出提现确认对话框
-
-## 常见问题
-
-### Q1: 修改后按钮还是灰色?
-**A**: 清除小程序缓存并重新编译:
-```
-工具 → 清除缓存 → 清除全部缓存数据
-然后点击 编译 按钮
-```
-
-### Q2: 控制台没有看到调试日志?
-**A**:
-1. 确保控制台的日志级别包含 `log`
-2. 检查是否过滤了某些日志
-3. 尝试刷新页面(下拉刷新或重新进入)
-
-### Q3: `availableEarningsNum` 是 undefined?
-**A**: 检查后端 API 返回的数据格式,确保 `realData.availableEarnings` 有值:
-```javascript
-console.log('API返回:', realData)
-```
-
-### Q4: 数据正确但按钮还是不可点击?
-**A**: 检查 WXSS 中是否有其他样式覆盖:
-```css
-.withdraw-btn.btn-disabled {
- pointer-events: none; /* 可能导致无法点击 */
-}
-```
-
-## 相关文件
-
-- `miniprogram/pages/referral/referral.js` - 主要逻辑
-- `miniprogram/pages/referral/referral.wxml` - 模板
-- `miniprogram/pages/referral/referral.wxss` - 样式
-- `app/api/referral/data/route.ts` - 后端API
-
-## 验证清单
-
-- [ ] 后端API返回正确的 `availableEarnings` 数值
-- [ ] `initData()` 中正确设置 `availableEarningsNum`
-- [ ] WXML 使用 `availableEarningsNum` 进行条件判断
-- [ ] 清除小程序缓存并重新编译
-- [ ] 控制台显示正确的调试日志
-- [ ] 按钮显示正确文本和样式
-- [ ] 点击按钮可以正常提现
-
-## 技术总结
-
-**核心问题**:字符串类型的数字在某些场景下的比较可能不符合预期。
-
-**解决思路**:
-1. 保存两份数据:字符串用于显示,数字用于判断
-2. 在数据初始化时就区分好类型
-3. 在需要比较的地方使用数字类型
-4. 添加详细的调试日志便于排查问题
-
-**最佳实践**:
-- ✅ 数值计算和比较始终使用 Number 类型
-- ✅ 格式化显示使用 String 类型
-- ✅ 在 setData 时明确类型转换
-- ✅ 避免在模板中进行复杂的类型转换
diff --git a/开发文档/8、部署/提现按钮逻辑修正.md b/开发文档/8、部署/提现按钮逻辑修正.md
deleted file mode 100644
index f3fe2743..00000000
--- a/开发文档/8、部署/提现按钮逻辑修正.md
+++ /dev/null
@@ -1,263 +0,0 @@
-# 提现按钮逻辑修正
-
-## 问题描述
-
-**错误理解**:
-- 错误地直接使用后端返回的 `availableEarnings` 进行判断
-
-**正确逻辑**:
-- ✅ **可提现金额 = 累计佣金 - 待审核金额**
-- ✅ **按钮启用条件:可提现金额 >= 最低提现金额**
-
-## 具体案例
-
-当前数据:
-- 累计佣金:¥8.10
-- 待审核金额:¥0.00
-- **计算:可提现金额 = 8.10 - 0 = 8.10元**
-- 最低提现金额:¥5.00
-- **判断:8.10 >= 5.00 = true** → 按钮应该启用(绿色)
-
-## 修复方案
-
-### 1. 前端计算可提现金额
-
-在 `miniprogram/pages/referral/referral.js` 的 `initData()` 方法中:
-
-```javascript
-// ✅ 修正:可提现金额 = 累计佣金 - 待审核金额
-const totalCommissionNum = realData?.totalCommission || 0
-const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
-const availableEarningsNum = totalCommissionNum - pendingWithdrawNum
-const minWithdrawAmount = realData?.minWithdrawAmount || 10
-
-console.log('=== [Referral] 收益计算(修正后)===')
-console.log('累计佣金:', totalCommissionNum)
-console.log('待审核金额:', pendingWithdrawNum)
-console.log('可提现金额 = 累计佣金 - 待审核金额 =', totalCommissionNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
-console.log('最低提现金额:', minWithdrawAmount)
-console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
-console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
-```
-
-### 2. 设置数据
-
-```javascript
-this.setData({
- // 收益数据 - 格式化为两位小数
- totalCommission: formatMoney(totalCommissionNum),
- availableEarnings: formatMoney(availableEarningsNum), // ✅ 使用计算后的可提现金额
- availableEarningsNum: availableEarningsNum, // ✅ 数字格式用于按钮判断
- pendingWithdrawAmount: formatMoney(pendingWithdrawNum),
- minWithdrawAmount: minWithdrawAmount,
- // ...
-})
-```
-
-### 3. 按钮判断逻辑(WXML)
-
-```xml
-
- {{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}}
-
-```
-
-**注意**:
-- 类名判断:`availableEarningsNum < minWithdrawAmount`(数字比较)
-- 文本显示:使用格式化后的 `availableEarnings` 字符串
-
-### 4. 提现函数中的验证
-
-```javascript
-async handleWithdraw() {
- // 使用数字版本直接进行判断
- const availableEarnings = this.data.availableEarningsNum || 0
- const minWithdrawAmount = this.data.minWithdrawAmount || 10
-
- console.log('[Withdraw] 提现检查:', {
- availableEarnings,
- minWithdrawAmount,
- shouldEnable: availableEarnings >= minWithdrawAmount
- })
-
- if (availableEarnings <= 0) {
- wx.showToast({ title: '暂无可提现收益', icon: 'none' })
- return
- }
-
- if (availableEarnings < minWithdrawAmount) {
- wx.showToast({
- title: `满${minWithdrawAmount}元可提现`,
- icon: 'none'
- })
- return
- }
-
- // ... 继续提现逻辑
-}
-```
-
-## 测试步骤
-
-### 1. 完全清除缓存
-
-在微信开发者工具中:
-```
-1. 关闭微信开发者工具
-2. 重新打开项目
-3. 工具 → 清除缓存 → 清除全部缓存数据
-4. 点击"编译"按钮
-```
-
-### 2. 查看控制台日志
-
-应该看到类似这样的输出:
-
-```
-=== [Referral] 收益计算(修正后)===
-累计佣金: 8.1
-待审核金额: 0
-可提现金额 = 累计佣金 - 待审核金额 = 8.1 - 0 = 8.1
-最低提现金额: 5
-按钮判断: 8.1 >= 5 = true
-✅ 按钮应该: 🟢 启用(绿色)
-
-=== [Referral] 按钮状态验证 ===
-累计佣金 (totalCommission): 8.10
-待审核金额 (pendingWithdrawAmount): 0.00
-可提现金额 (availableEarnings 显示): 8.10
-可提现金额 (availableEarningsNum 判断): 8.1 number
-最低提现金额 (minWithdrawAmount): 5 number
-按钮启用条件: 8.1 >= 5 = true
-✅ 最终结果: 按钮应该 🟢 启用
-```
-
-### 3. 验证界面
-
-- ✅ 按钮文本:`申请提现 ¥8.10`
-- ✅ 按钮样式:渐变青色背景(不是灰色)
-- ✅ 可以点击
-
-## 逻辑公式总结
-
-### 核心计算
-
-```
-可提现金额 = 累计佣金 - 待审核金额
-```
-
-### 按钮状态
-
-```
-if (可提现金额 >= 最低提现金额) {
- // ✅ 启用按钮(绿色)
- 显示文本: "申请提现 ¥{可提现金额}"
-} else {
- // ❌ 禁用按钮(灰色)
- 显示文本: "满{最低提现金额}元可提现"
-}
-```
-
-### 数据关系
-
-```
-totalCommission (累计佣金)
- ├─ 所有已完成订单的佣金总和
- └─ 显示在顶部"累计佣金"位置
-
-pendingWithdrawAmount (待审核金额)
- ├─ 已申请提现但未审核通过的金额总和
- └─ 显示在"待审核"位置
-
-availableEarnings (可提现金额)
- ├─ = totalCommission - pendingWithdrawAmount
- ├─ 用户实际可以申请提现的金额
- └─ 显示在提现按钮上
-
-minWithdrawAmount (最低提现金额)
- ├─ 从管理后台配置获取
- ├─ 默认值:10元
- └─ 用于判断是否允许提现
-```
-
-## 为什么要在前端计算?
-
-### 优势
-
-1. **实时准确**:
- - 每次进入页面都基于最新的累计佣金和待审核金额计算
- - 避免后端缓存导致的数据延迟
-
-2. **逻辑清晰**:
- - 公式简单明了:`累计佣金 - 待审核金额`
- - 便于调试和验证
-
-3. **减轻后端负担**:
- - 简单的减法运算在前端完成
- - 后端只需返回原始数据
-
-### 数据流
-
-```
-后端API返回:
-{
- totalCommission: 8.1, // 累计佣金
- pendingWithdrawAmount: 0, // 待审核金额
- minWithdrawAmount: 5 // 最低提现金额
-}
-
-前端计算:
-availableEarningsNum = 8.1 - 0 = 8.1 // 可提现金额
-
-前端判断:
-8.1 >= 5 ? 启用按钮 : 禁用按钮
-```
-
-## 相关文件
-
-- `miniprogram/pages/referral/referral.js` - 计算逻辑
-- `miniprogram/pages/referral/referral.wxml` - 按钮显示
-- `miniprogram/pages/referral/referral.wxss` - 按钮样式
-
-## 常见问题
-
-### Q1: 为什么要保存两个字段?
-
-**A**:
-- `availableEarnings` (字符串):用于界面显示,格式化为 "8.10"
-- `availableEarningsNum` (数字):用于条件判断,精确比较 `8.1 >= 5`
-
-### Q2: 后端的 availableEarnings 还有用吗?
-
-**A**: 如果后端返回了 `availableEarnings`,现在会被前端计算的值覆盖。建议:
-- 方案1:后端不再返回 `availableEarnings`,只返回 `totalCommission` 和 `pendingWithdrawAmount`
-- 方案2:保留后端计算,但前端不使用(当前方案)
-
-### Q3: 如果待审核金额大于累计佣金怎么办?
-
-**A**: 理论上不应该出现这种情况,但可以添加保护:
-```javascript
-const availableEarningsNum = Math.max(0, totalCommissionNum - pendingWithdrawNum)
-```
-
-## 验证清单
-
-- [x] 修改前端计算逻辑:`可提现金额 = 累计佣金 - 待审核金额`
-- [x] 添加详细调试日志
-- [x] 确保使用数字类型进行比较
-- [x] 清除小程序缓存
-- [x] 重新编译
-- [ ] 查看控制台日志验证计算
-- [ ] 确认按钮显示正确文本和样式
-- [ ] 测试点击提现功能
-
-## 总结
-
-这次修正的核心是**理解业务逻辑**:
-
-1. **累计佣金** = 所有获得的佣金(历史总和)
-2. **待审核金额** = 已申请但未到账的金额
-3. **可提现金额** = 累计佣金 - 待审核金额 = 当前可以申请提现的金额
-4. **按钮启用** = 可提现金额 >= 最低提现金额
-
-之前的错误是直接使用后端返回的值,没有理解这个减法关系。现在在前端明确计算,确保逻辑正确。
diff --git a/开发文档/8、部署/提现接口数据查询错误修复.md b/开发文档/8、部署/提现接口数据查询错误修复.md
deleted file mode 100644
index 83140a31..00000000
--- a/开发文档/8、部署/提现接口数据查询错误修复.md
+++ /dev/null
@@ -1,352 +0,0 @@
-# 提现接口数据查询错误修复
-
-## 问题描述
-
-访问 `/api/db/withdrawals` 接口时报错:
-
-```json
-{
- "success": false,
- "error": "获取提现数据失败: Cannot read properties of undefined (reading 'length')",
- "withdrawals": []
-}
-```
-
-**错误原因**:尝试读取 `undefined.length`,说明 `query()` 返回了 `undefined`。
-
-## 问题根源
-
-### 1. 缺少 Null Check
-
-**问题代码**:
-```typescript
-const withdrawals = await query(`...`) as any[]
-console.log('[DB Withdrawals] 查询成功,记录数:', withdrawals.length)
-// ❌ 如果 query 返回 undefined,这里会报错
-```
-
-### 2. 表可能不存在
-
-如果 `withdrawals` 表还未创建,`query()` 会抛出异常并被 catch 捕获,但错误信息不够明确。
-
-### 3. 错误处理不完善
-
-缺少对特定数据库错误(如表不存在)的处理。
-
-## 解决方案
-
-### 1. 增加表初始化逻辑
-
-**新增函数**:
-```typescript
-// 确保提现表存在
-async function ensureWithdrawalsTable() {
- try {
- await query(`
- CREATE TABLE IF NOT EXISTS withdrawals (
- id VARCHAR(50) PRIMARY KEY,
- user_id VARCHAR(50) NOT NULL,
- amount DECIMAL(10,2) NOT NULL,
- status ENUM('pending', 'processing', 'success', 'failed') DEFAULT 'pending',
- wechat_openid VARCHAR(100),
- transaction_id VARCHAR(100),
- error_message VARCHAR(500),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- processed_at TIMESTAMP,
- INDEX idx_user_id (user_id),
- INDEX idx_status (status),
- INDEX idx_created_at (created_at)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
- `)
- console.log('[DB Withdrawals] 提现表检查/创建成功')
- } catch (error: any) {
- console.error('[DB Withdrawals] 提现表创建失败:', error.message)
- }
-}
-```
-
-**调用时机**:在 `GET` 方法开始时调用
-```typescript
-export async function GET(request: Request) {
- const authErr = requireAdminResponse(request)
- if (authErr) return authErr
-
- // 确保表存在
- await ensureWithdrawalsTable()
-
- try {
- // ... 查询逻辑
- }
-}
-```
-
-### 2. 增强查询错误处理
-
-**修改前**:
-```typescript
-const withdrawals = await query(`...`) as any[]
-console.log('[DB Withdrawals] 查询成功,记录数:', withdrawals.length)
-```
-
-**修改后**:
-```typescript
-let withdrawals: any[] = []
-
-try {
- const result = await query(`...`)
- withdrawals = (result as any[]) || []
-} catch (queryError: any) {
- console.error('[DB Withdrawals] SQL查询失败:', queryError.message)
-
- // 如果表不存在,返回空数组
- if (queryError.message?.includes("doesn't exist") ||
- queryError.message?.includes('ER_NO_SUCH_TABLE')) {
- console.warn('[DB Withdrawals] withdrawals 表不存在,返回空数据')
- return NextResponse.json({
- success: true,
- withdrawals: [],
- total: 0,
- message: 'withdrawals 表尚未初始化'
- })
- }
- throw queryError
-}
-
-console.log('[DB Withdrawals] 查询成功,记录数:', withdrawals.length)
-```
-
-**改进点**:
-- ✅ 初始化为空数组,避免 undefined
-- ✅ 单独 try-catch 包裹查询逻辑
-- ✅ 识别"表不存在"错误,返回友好提示
-- ✅ 确保 `withdrawals` 始终是数组
-
-### 3. 增强数据转换安全性
-
-**修改前**:
-```typescript
-const formattedWithdrawals = withdrawals.map(w => {
- // ...
-})
-```
-
-**修改后**:
-```typescript
-const formattedWithdrawals = (withdrawals || []).map(w => {
- // ... 双重保险
-})
-```
-
-## 修改文件
-
-**文件路径**:`app/api/db/withdrawals/route.ts`
-
-**修改内容**:
-1. 新增 `ensureWithdrawalsTable()` 函数(第 6-30 行)
-2. 在 `GET` 方法中调用表初始化(第 36 行)
-3. 增强查询错误处理(第 38-62 行)
-4. 增强数据转换安全性(第 66 行)
-
-## 验证步骤
-
-### 1. 重启服务
-
-```powershell
-pm2 restart mycontent
-# 或
-npm run dev
-```
-
-### 2. 访问 API 测试
-
-**在浏览器中访问**:
-```
-http://localhost:3006/api/db/withdrawals
-```
-
-**期望结果**(如果表为空):
-```json
-{
- "success": true,
- "withdrawals": [],
- "total": 0
-}
-```
-
-**期望结果**(如果有数据):
-```json
-{
- "success": true,
- "withdrawals": [
- {
- "id": "W_xxx",
- "user_id": "user123",
- "user_name": "张三",
- "amount": 50.00,
- "status": "pending",
- "created_at": "2026-02-04 10:00:00"
- }
- ],
- "total": 1
-}
-```
-
-### 3. 查看服务器日志
-
-应该看到:
-```
-[DB Withdrawals] 提现表检查/创建成功
-[DB Withdrawals] 查询提现记录...
-[DB Withdrawals] 查询成功,记录数: 0
-```
-
-### 4. 访问前端页面
-
-```
-http://localhost:3006/admin/distribution
-```
-
-点击"提现审核" tab,应该能正常显示(即使数据为空)。
-
-### 5. 数据库验证
-
-```sql
--- 检查表是否存在
-SHOW TABLES LIKE 'withdrawals';
-
--- 查看表结构
-DESC withdrawals;
-
--- 查看数据
-SELECT * FROM withdrawals;
-```
-
-## 错误处理流程图
-
-```
-访问 /api/db/withdrawals
- ↓
-验证管理员权限
- ↓
-ensureWithdrawalsTable()
- ├─ 成功:表已存在或已创建
- └─ 失败:记录错误,继续执行
- ↓
-执行查询 SQL
- ├─ 成功:返回数据数组
- │ └─ 数据转换 → 返回 JSON
- │
- └─ 失败:捕获错误
- ├─ 表不存在错误?
- │ └─ 是:返回空数组 + 提示信息
- │
- └─ 否:抛出异常
- └─ 外层 catch:返回 500 错误
-```
-
-## 常见问题
-
-### Q1: 为什么需要 `ensureWithdrawalsTable()`?
-
-**答**:
-- 多种方式访问系统时,表可能未创建
-- 数据库可能被重置或迁移
-- 提供自修复能力,提高系统健壮性
-
-### Q2: 如果表已经存在会怎样?
-
-**答**:
-`CREATE TABLE IF NOT EXISTS` 不会报错,也不会重复创建:
-- 如果表存在:SQL 执行成功,什么都不做
-- 如果表不存在:创建新表
-
-### Q3: 查询返回空数组和 undefined 有什么区别?
-
-**答**:
-- 空数组 `[]`:表示查询成功,但没有数据(`.length` 为 0)
-- `undefined`:表示查询失败或返回值异常(无法读取 `.length`)
-
-我们的修复确保始终返回数组,即使查询失败也返回 `[]`。
-
-### Q4: 为什么用两层 try-catch?
-
-**答**:
-```typescript
-try {
- // 外层 try-catch:捕获所有错误
-
- try {
- // 内层 try-catch:专门处理查询错误
- const result = await query(...)
- } catch (queryError) {
- // 识别特定错误类型(如表不存在)
- // 可以返回友好提示而不是 500 错误
- }
-
-} catch (error) {
- // 兜底:处理未预料到的错误
- return 500 错误
-}
-```
-
-这样可以对不同类型的错误做差异化处理。
-
-### Q5: 如果数据库连接失败会怎样?
-
-**答**:
-- `ensureWithdrawalsTable()` 会失败,但被 catch 捕获,记录错误
-- 继续执行查询,查询也会失败
-- 最终返回 500 错误和详细错误信息
-
-建议检查:
-1. 数据库服务是否运行
-2. 连接配置是否正确(`lib/db.ts`)
-3. 用户权限是否足够
-
-## 性能优化
-
-### 缓存表存在状态
-
-如果频繁调用该 API,可以缓存表的存在状态:
-
-```typescript
-let tableChecked = false
-
-async function ensureWithdrawalsTable() {
- if (tableChecked) return // 已检查过,跳过
-
- try {
- await query(`CREATE TABLE IF NOT EXISTS ...`)
- tableChecked = true // 标记为已检查
- console.log('[DB Withdrawals] 提现表检查/创建成功')
- } catch (error: any) {
- console.error('[DB Withdrawals] 提现表创建失败:', error.message)
- }
-}
-```
-
-**注意**:如果使用 pm2 cluster 模式(多进程),每个进程都需要检查一次。
-
-## 相关文件
-
-- **API 文件**:`app/api/db/withdrawals/route.ts`
-- **数据库初始化**:`app/api/db/init/route.ts`
-- **用户提现 API**:`app/api/withdraw/route.ts`
-- **交易中心页面**:`app/admin/distribution/page.tsx`
-
-## 后续改进
-
-1. **统一表初始化**:将所有表的初始化逻辑集中到 `app/api/db/init/route.ts`
-2. **健康检查 API**:创建 `/api/health` 检查数据库连接和关键表状态
-3. **自动迁移**:使用数据库迁移工具(如 Prisma Migrate)管理表结构变更
-4. **监控告警**:记录数据库查询失败次数,超过阈值时告警
-
-## 版本信息
-
-- **修复时间**:2026-02-04
-- **修复内容**:
- 1. 新增 `ensureWithdrawalsTable()` 表初始化函数
- 2. 增强查询错误处理,识别"表不存在"错误
- 3. 确保 `withdrawals` 变量始终是数组
- 4. 增强数据转换安全性
- 5. 添加详细的错误日志
diff --git a/开发文档/8、部署/提现接口统一修复完成.md b/开发文档/8、部署/提现接口统一修复完成.md
deleted file mode 100644
index a8fa3b19..00000000
--- a/开发文档/8、部署/提现接口统一修复完成.md
+++ /dev/null
@@ -1,343 +0,0 @@
-# 提现接口统一修复 - 完成报告
-
-## 问题回顾
-
-**用户反馈**:"/api/db/withdrawals 接口还是访问失败,不是就访问提现表么?怎么问题这么多,是不是实现方式错了"
-
-## 根本原因
-
-✅ **确认:实现方式确实有问题!**
-
-系统中存在**两个功能重复的提现接口**:
-
-1. `/api/admin/withdrawals` - 原有的完整接口(260行,功能完善)
-2. `/api/db/withdrawals` - 新创建的简化接口(217行,重复实现)
-
-这导致:
-- 维护成本翻倍
-- 逻辑不一致风险
-- 调试困难
-- 接口失败时难以定位问题
-
-## 修复方案
-
-**统一使用原有的完整接口 `/api/admin/withdrawals`**
-
-### 修复内容
-
-#### 1. 删除重复接口
-
-```bash
-✅ 已删除:app/api/db/withdrawals/route.ts (6927 bytes)
-```
-
-#### 2. 前端调用统一(3处修改)
-
-**文件**:`app/admin/distribution/page.tsx`
-
-##### 修改 1: 查询提现数据 (Line 237)
-```typescript
-// 修改前
-const withdrawalsRes = await fetch('/api/db/withdrawals')
-
-// 修改后
-const withdrawalsRes = await fetch('/api/admin/withdrawals')
-```
-
-##### 修改 2: 批准提现 (Line 299)
-```typescript
-// 修改前
-await fetch('/api/db/withdrawals', {
- method: 'PUT',
- body: JSON.stringify({ id, status: 'completed' })
-})
-
-// 修改后
-await fetch('/api/admin/withdrawals', {
- method: 'PUT',
- body: JSON.stringify({ id, action: 'approve' })
-})
-```
-
-##### 修改 3: 拒绝提现 (Line 316)
-```typescript
-// 修改前
-await fetch('/api/db/withdrawals', {
- method: 'PUT',
- body: JSON.stringify({ id, status: 'rejected', reason })
-})
-
-// 修改后
-await fetch('/api/admin/withdrawals', {
- method: 'PUT',
- body: JSON.stringify({ id, action: 'reject', errorMessage: reason })
-})
-```
-
-#### 3. 更新接口定义
-
-**增强 `Withdrawal` 接口**,支持完整的用户佣金信息:
-
-```typescript
-interface Withdrawal {
- // 基础字段
- id: string
- userId?: string
- user_id?: string
- userNickname?: string
- user_name?: string
- userPhone?: string
- userAvatar?: string
- referralCode?: string
- amount: number
-
- // 状态字段
- status: 'pending' | 'success' | 'failed' | 'completed' | 'rejected'
- wechatOpenid?: string
- transactionId?: string
- errorMessage?: string
-
- // 时间字段
- createdAt?: string
- created_at?: string
- processedAt?: string
- completed_at?: string
-
- // 新增:用户佣金详情(用于风险提示)
- userCommissionInfo?: {
- totalCommission: number // 累计佣金
- withdrawnEarnings: number // 已提现
- pendingWithdrawals: number // 待审核
- availableAfterThis: number // 审核后可提现(可能为负,触发风险提示)
- }
-}
-```
-
-#### 4. 添加数据映射
-
-统一字段命名,兼容不同格式:
-
-```typescript
-const formattedWithdrawals = (withdrawalsData.withdrawals || []).map((w: any) => ({
- ...w,
- // 字段名统一
- user_id: w.userId || w.user_id,
- user_name: w.userNickname || w.user_name,
- created_at: w.createdAt || w.created_at,
- completed_at: w.processedAt || w.completed_at,
-
- // 状态映射(数据库 success/failed → 前端 completed/rejected)
- status: w.status === 'success' ? 'completed'
- : (w.status === 'failed' ? 'rejected' : w.status)
-}))
-```
-
-## 修复效果
-
-### ✅ 解决的问题
-
-1. **接口失败** → 现在使用稳定的原有接口
-2. **维护困难** → 只有一个接口,逻辑清晰
-3. **数据不一致** → 统一数据源和格式
-4. **功能不完整** → 保留所有用户佣金计算和风险提示功能
-
-### ✅ 新增功能
-
-通过使用 `/api/admin/withdrawals`,自动获得:
-
-1. **用户佣金详情展示**:
- - 累计佣金(从 orders 表计算)
- - 已提现金额
- - 待审核金额
- - 审核后可提现金额
-
-2. **风险提示**:
- - 当 `availableAfterThis < 0` 时,显示红色警告
- - 批准前弹出确认对话框
-
-3. **完整的用户信息**:
- - 头像显示
- - 手机号
- - 推广码
-
-## 测试清单
-
-### 必须测试的功能
-
-#### 1. 提现列表加载
-```bash
-✅ 登录后台管理
-✅ 进入"交易中心" → "提现审核" tab
-✅ 检查列表正常加载
-✅ 检查用户信息显示完整(昵称、手机、头像)
-✅ 检查佣金详情显示(累计、已提现、待审核、可提现)
-```
-
-#### 2. 批准提现
-```bash
-✅ 选择一条待审核的提现记录
-✅ 点击"批准"按钮
-✅ 确认对话框正常弹出
-✅ 批准成功后,状态更新为"已完成"
-✅ 用户的 withdrawn_earnings 字段正确更新
-```
-
-#### 3. 拒绝提现
-```bash
-✅ 选择一条待审核的提现记录
-✅ 点击"拒绝"按钮
-✅ 输入拒绝原因
-✅ 拒绝成功后,状态更新为"已拒绝"
-✅ 拒绝原因正确保存
-```
-
-#### 4. 风险提示
-```bash
-✅ 找一条会导致用户余额为负的提现记录
-✅ 检查 availableAfterThis 显示为红色负数
-✅ 点击批准时,弹出风险确认对话框
-```
-
-#### 5. 刷新功能
-```bash
-✅ 点击"刷新数据"按钮
-✅ 数据正确重新加载
-✅ 不会重复请求其他 tab 的数据
-```
-
-### 测试命令
-
-```bash
-# 1. 重启开发服务器
-npm run dev
-
-# 2. 检查接口是否可访问(需先登录)
-curl http://localhost:3006/api/admin/withdrawals
-
-# 3. 检查数据库表结构
-mysql -h56b4c23f6853c.gz.cdb.myqcloud.com -P14413 -ucdb_outerroot -p
-USE soul_miniprogram;
-DESC withdrawals;
-SELECT * FROM withdrawals LIMIT 5;
-```
-
-## 数据库状态映射
-
-| 数据库状态 | 前端显示 | 说明 |
-|----------|---------|------|
-| `pending` | 待审核 | 初始状态 |
-| `processing` | 处理中 | 微信转账中 |
-| `success` | 已完成 (completed) | 审批通过,打款成功 |
-| `failed` | 已拒绝 (rejected) | 审批拒绝或打款失败 |
-
-## API 接口说明
-
-### GET /api/admin/withdrawals
-
-**功能**:查询提现记录列表
-
-**权限**:需要管理员登录(Cookie: admin_session)
-
-**请求参数**:
-- `status`(可选):筛选状态 `pending|success|failed|all`
-
-**响应格式**:
-```json
-{
- "success": true,
- "withdrawals": [
- {
- "id": "withdraw_xxx",
- "userId": "user_xxx",
- "userNickname": "用户昵称",
- "userPhone": "13800138000",
- "userAvatar": "https://...",
- "referralCode": "ABC123",
- "amount": 50.00,
- "status": "pending",
- "createdAt": "2026-02-04T10:00:00.000Z",
- "processedAt": null,
- "userCommissionInfo": {
- "totalCommission": 100.00,
- "withdrawnEarnings": 30.00,
- "pendingWithdrawals": 50.00,
- "availableAfterThis": 20.00
- }
- }
- ],
- "stats": {
- "total": 10,
- "pendingCount": 3,
- "pendingAmount": 150.00,
- "successCount": 7,
- "successAmount": 350.00,
- "failedCount": 0
- }
-}
-```
-
-### PUT /api/admin/withdrawals
-
-**功能**:审批提现(批准或拒绝)
-
-**权限**:需要管理员登录
-
-**请求参数**:
-```json
-// 批准
-{
- "id": "withdraw_xxx",
- "action": "approve"
-}
-
-// 拒绝
-{
- "id": "withdraw_xxx",
- "action": "reject",
- "errorMessage": "拒绝原因"
-}
-```
-
-**响应格式**:
-```json
-{
- "success": true,
- "message": "提现已批准" | "提现已拒绝"
-}
-```
-
-## 相关文件
-
-### 已修改
-- ✅ `app/admin/distribution/page.tsx` - 前端调用统一
-- ✅ `app/api/admin/withdrawals/route.ts` - 原有接口(保持不变)
-
-### 已删除
-- ❌ `app/api/db/withdrawals/route.ts` - 重复接口已删除
-
-### 新增文档
-- 📄 `开发文档/8、部署/提现接口重复问题修复.md` - 问题分析
-- 📄 `开发文档/8、部署/提现接口统一修复完成.md` - 本文档
-
-## 后续优化建议
-
-### 短期优化(可选)
-
-1. **统一状态枚举**:前后端都使用 `pending|processing|completed|rejected`,避免映射
-2. **统一字段命名**:全部使用驼峰命名(`userId`, `userNickname`),避免下划线
-3. **添加接口文档**:为 `/api/admin/withdrawals` 添加 OpenAPI 文档
-
-### 长期优化(建议)
-
-1. **单元测试**:为提现接口添加自动化测试
-2. **日志优化**:使用结构化日志(如 winston),便于排查问题
-3. **监控告警**:对提现操作添加监控和异常告警
-4. **审计日志**:记录所有提现审批操作,便于追溯
-
-## 总结
-
-✅ **问题根源**:接口重复导致维护混乱
-✅ **解决方案**:统一使用原有的完整接口
-✅ **修复效果**:代码更清晰,功能更完整,维护更简单
-
-**现在请重启服务并测试,提现功能应该可以正常工作了!** 🎉
diff --git a/开发文档/8、部署/提现接口逻辑修正.md b/开发文档/8、部署/提现接口逻辑修正.md
deleted file mode 100644
index bf850617..00000000
--- a/开发文档/8、部署/提现接口逻辑修正.md
+++ /dev/null
@@ -1,348 +0,0 @@
-# 提现接口逻辑修正
-
-## 问题描述
-
-**错误提示**:
-```
-POST /api/withdraw
-Response: {
- success: false,
- message: "可提现金额不足,当前可提现 ¥0.00"
-}
-```
-
-**问题原因**:
-后端提现接口的佣金计算逻辑与前端不一致,导致计算出的可提现金额为0。
-
-## 问题分析
-
-### 旧逻辑(错误)
-
-```typescript
-// ❌ 从 referral_bindings 表查询
-const earningsResult = await query(`
- SELECT COALESCE(SUM(commission), 0) as total_commission
- FROM referral_bindings
- WHERE referrer_id = ? AND status = 'converted'
-`, [userId])
-
-totalEarnings = parseFloat(earningsResult[0]?.total_commission || 0)
-
-// 计算可提现金额
-const availableAmount = totalEarnings - withdrawnAmount
-```
-
-**问题**:
-1. 从 `referral_bindings.commission` 字段查询,但该字段可能未维护或不准确
-2. 与前端/分销中心API的计算逻辑不一致
-3. 导致后端计算的可提现金额为0
-
-### 正确逻辑
-
-应该与前端和 `/api/referral/data` 保持一致:
-
-```
-累计佣金 = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid') × distributorShare
-可提现金额 = 累计佣金 - 待审核提现金额
-```
-
-## 修复方案
-
-### 1. 修改累计佣金计算
-
-**文件**:`app/api/withdraw/route.ts`
-
-```typescript
-// ✅ 修正:从 orders 表查询累计佣金(与前端逻辑一致)
-let totalCommission = 0
-try {
- // 读取分成比例
- let distributorShare = 0.9 // 默认90%
- try {
- const config = await getConfig('referral_config')
- if (config?.distributorShare) {
- distributorShare = Number(config.distributorShare)
- }
- } catch (e) {
- console.warn('[Withdraw] 读取分成比例失败,使用默认值 90%')
- }
-
- // 查询订单总金额
- const ordersResult = await query(`
- SELECT COALESCE(SUM(amount), 0) as total_amount
- FROM orders
- WHERE referrer_id = ? AND status = 'paid'
- `, [userId]) as any[]
-
- const totalAmount = parseFloat(ordersResult[0]?.total_amount || 0)
- totalCommission = totalAmount * distributorShare
-
- console.log('[Withdraw] 佣金计算:')
- console.log('- 订单总金额:', totalAmount)
- console.log('- 分成比例:', distributorShare * 100 + '%')
- console.log('- 累计佣金:', totalCommission)
-} catch (e) {
- console.log('[Withdraw] 查询收益失败:', e)
-}
-```
-
-### 2. 修改可提现金额计算
-
-```typescript
-// 查询待审核提现金额
-let pendingWithdrawAmount = 0
-try {
- const pendingResult = await query(`
- SELECT COALESCE(SUM(amount), 0) as pending_amount
- FROM withdrawals
- WHERE user_id = ? AND status = 'pending'
- `, [userId]) as any[]
- pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
-} catch (e) {
- console.log('[Withdraw] 查询待审核金额失败:', e)
-}
-
-// ✅ 修正:可提现金额 = 累计佣金 - 待审核金额(与前端逻辑一致)
-const availableAmount = totalCommission - pendingWithdrawAmount
-
-console.log('[Withdraw] 提现验证:')
-console.log('- 累计佣金 (totalCommission):', totalCommission)
-console.log('- 待审核金额 (pendingWithdrawAmount):', pendingWithdrawAmount)
-console.log('- 可提现金额 (availableAmount):', availableAmount)
-console.log('- 申请提现金额 (amount):', amount)
-console.log('- 判断:', amount, '>', availableAmount, '=', amount > availableAmount)
-
-if (amount > availableAmount) {
- return NextResponse.json({
- success: false,
- message: `可提现金额不足,当前可提现 ¥${availableAmount.toFixed(2)},待审核 ¥${pendingWithdrawAmount.toFixed(2)}`
- })
-}
-```
-
-## 修改对比
-
-### 数据来源
-
-| 项目 | 旧逻辑 | 新逻辑 |
-|------|--------|--------|
-| 累计佣金 | `referral_bindings.commission` | `orders.amount × distributorShare` |
-| 已提现 | `withdrawals.status = 'completed'` | **改为待审核** |
-| 待审核 | ❌ 未查询 | `withdrawals.status = 'pending'` |
-| 可提现 | `累计佣金 - 已提现` | `累计佣金 - 待审核` |
-
-### 计算公式
-
-**旧逻辑**:
-```
-累计佣金 = SUM(referral_bindings.commission WHERE status = 'converted')
-已提现金额 = SUM(withdrawals.amount WHERE status = 'completed')
-可提现金额 = 累计佣金 - 已提现金额 ❌ 错误
-```
-
-**新逻辑**:
-```
-订单总金额 = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid')
-累计佣金 = 订单总金额 × distributorShare (90%)
-待审核金额 = SUM(withdrawals.amount WHERE status = 'pending')
-可提现金额 = 累计佣金 - 待审核金额 ✅ 正确
-```
-
-## 一致性验证
-
-现在三个地方的逻辑完全一致:
-
-### 1. 前端小程序 (`referral.js`)
-
-```javascript
-const totalCommissionNum = realData?.totalCommission || 0
-const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
-const availableEarningsNum = totalCommissionNum - pendingWithdrawNum
-```
-
-### 2. 分销数据API (`/api/referral/data`)
-
-```typescript
-// 计算累计佣金
-const totalAmount = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid')
-const totalCommission = totalAmount * distributorShare
-
-// 查询待审核金额
-const pendingWithdrawAmount = SUM(withdrawals.amount WHERE user_id = userId AND status = 'pending')
-
-// 返回给前端
-return {
- totalCommission,
- pendingWithdrawAmount,
- availableEarnings: totalCommission - pendingWithdrawAmount
-}
-```
-
-### 3. 提现API (`/api/withdraw`)
-
-```typescript
-// 计算累计佣金
-const totalAmount = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid')
-const totalCommission = totalAmount * distributorShare
-
-// 查询待审核金额
-const pendingWithdrawAmount = SUM(withdrawals.amount WHERE user_id = userId AND status = 'pending')
-
-// 验证可提现金额
-const availableAmount = totalCommission - pendingWithdrawAmount
-if (amount > availableAmount) {
- return error("可提现金额不足")
-}
-```
-
-## 测试步骤
-
-### 1. 查看后端日志
-
-提现时应该看到详细日志:
-
-```
-[Withdraw] 佣金计算:
-- 订单总金额: 9
-- 分成比例: 90%
-- 累计佣金: 8.1
-
-[Withdraw] 提现验证:
-- 累计佣金 (totalCommission): 8.1
-- 待审核金额 (pendingWithdrawAmount): 0
-- 可提现金额 (availableAmount): 8.1
-- 申请提现金额 (amount): 8.1
-- 判断: 8.1 > 8.1 = false
-```
-
-### 2. 测试提现
-
-```bash
-# 测试提现API
-curl -X POST http://localhost:3006/api/withdraw \
- -H "Content-Type: application/json" \
- -d '{
- "userId": "ogpTW5fmXRGNpoUbXB3UEqnVe5Tg",
- "amount": 8.1
- }'
-```
-
-**预期结果**:
-```json
-{
- "success": true,
- "message": "提现成功",
- "data": {
- "withdrawId": "W1738694028123",
- "amount": 8.1,
- "account": "...",
- "accountType": "微信"
- }
-}
-```
-
-### 3. 验证数据库
-
-```sql
--- 查看待审核提现记录
-SELECT * FROM withdrawals WHERE user_id = 'ogpTW5fmXRGNpoUbXB3UEqnVe5Tg' AND status = 'pending';
-
--- 查看订单总金额
-SELECT SUM(amount) as total_amount
-FROM orders
-WHERE referrer_id = 'ogpTW5fmXRGNpoUbXB3UEqnVe5Tg' AND status = 'paid';
-```
-
-## 常见问题
-
-### Q1: 为什么改成"待审核"而不是"已提现"?
-
-**A**: 因为提现流程是:
-1. 用户申请提现 → 状态 `pending`(待审核)
-2. 管理员审核通过 → 状态改为 `completed`(已完成)
-
-在用户申请提现后,这笔金额应该从"可提现"中扣除,所以要减去 `status = 'pending'` 的金额。
-
-### Q2: 如果有多笔待审核提现会怎样?
-
-**A**:
-```
-累计佣金: 100元
-待审核: 30元 + 20元 = 50元
-可提现: 100 - 50 = 50元
-```
-
-用户只能再申请最多50元的提现。
-
-### Q3: 审核通过后会发生什么?
-
-**A**:
-```sql
--- 管理员审核通过
-UPDATE withdrawals SET status = 'completed' WHERE id = 'W123';
-```
-
-这笔金额从"待审核"变为"已完成",下次计算时:
-```
-待审核金额减少 → 可提现金额增加(如果有新订单)
-```
-
-### Q4: referral_bindings 表还有用吗?
-
-**A**: 有用,但不再用于佣金计算:
-- 记录绑定关系
-- 记录绑定状态(active/expired)
-- 记录购买次数
-- 但佣金数据以 `orders` 表为准
-
-## 部署说明
-
-### 1. 重启服务
-
-修改后需要重启 Next.js 服务:
-
-```bash
-# 使用部署脚本重启
-python devlop.py restart mycontent
-
-# 或手动重启
-pm2 restart mycontent
-```
-
-### 2. 查看日志
-
-```bash
-# 查看实时日志
-pm2 logs mycontent
-
-# 查看最近的日志
-pm2 logs mycontent --lines 100
-```
-
-### 3. 监控错误
-
-关注以下日志:
-- `[Withdraw] 佣金计算:` - 佣金计算是否正确
-- `[Withdraw] 提现验证:` - 可提现金额是否准确
-- `[Withdraw] 查询收益失败:` - 是否有表不存在等错误
-
-## 相关文件
-
-- `app/api/withdraw/route.ts` - 提现接口(本次修改)
-- `app/api/referral/data/route.ts` - 分销数据接口(已统一)
-- `miniprogram/pages/referral/referral.js` - 前端逻辑(已统一)
-
-## 总结
-
-这次修复确保了**三端逻辑完全一致**:
-
-1. **前端小程序**:显示的可提现金额
-2. **分销数据API**:返回的数据
-3. **提现接口**:验证的金额
-
-都使用相同的计算方式:
-```
-可提现金额 = (订单总金额 × 分成比例) - 待审核提现金额
-```
-
-这样可以避免前端显示可以提现,但后端验证失败的问题。
diff --git a/开发文档/8、部署/提现接口重复问题修复.md b/开发文档/8、部署/提现接口重复问题修复.md
deleted file mode 100644
index 5e12293d..00000000
--- a/开发文档/8、部署/提现接口重复问题修复.md
+++ /dev/null
@@ -1,200 +0,0 @@
-# 提现接口重复问题修复方案
-
-## 问题描述
-
-系统中存在两个功能重复的提现接口,导致维护困难和潜在的数据不一致问题:
-
-1. **`/api/admin/withdrawals`** - 原有的完整接口(260行)
- - 位置:`app/api/admin/withdrawals/route.ts`
- - 功能:完整的提现管理,包括用户佣金计算、风险提示等
- - 使用场景:后台管理 - 提现审核页面
-
-2. **`/api/db/withdrawals`** - 新创建的简化接口(217行)
- - 位置:`app/api/db/withdrawals/route.ts`
- - 功能:简化版提现数据查询
- - 使用场景:交易中心页面的提现审核 tab
-
-## 问题分析
-
-### 为什么会失败?
-
-1. **重复逻辑导致混乱**:两个接口都在查询 `withdrawals` 表,但实现细节不同
-2. **数据格式不一致**:返回的字段名可能有差异
-3. **维护困难**:bug 修复需要在两处同步
-4. **权限验证重复**:都使用 `requireAdminResponse`,但可能有细微差异
-
-### 根本原因
-
-在实现"交易中心 Tab 按需加载"功能时,误认为需要创建新接口,实际上应该直接使用现有的 `/api/admin/withdrawals`。
-
-## 解决方案
-
-### 方案一:统一使用 `/api/admin/withdrawals`(推荐)
-
-**优点**:
-- ✅ 功能最完整(包含用户佣金详情、风险提示)
-- ✅ 代码已经过充分测试
-- ✅ 减少维护成本
-- ✅ 避免数据不一致
-
-**修改步骤**:
-
-#### 1. 修改前端调用(3处)
-
-**文件**:`app/admin/distribution/page.tsx`
-
-```typescript
-// 修改前
-const withdrawalsRes = await fetch('/api/db/withdrawals')
-
-// 修改后
-const withdrawalsRes = await fetch('/api/admin/withdrawals')
-```
-
-同样的修改需要在以下3个位置:
-- Line 237: 查询提现数据
-- Line 299: 批准提现
-- Line 316: 拒绝提现
-
-#### 2. 删除重复接口
-
-删除文件:`app/api/db/withdrawals/route.ts`
-
-#### 3. 检查数据格式兼容性
-
-`/api/admin/withdrawals` 的返回格式:
-
-```json
-{
- "success": true,
- "withdrawals": [
- {
- "id": "string",
- "user_id": "string",
- "user_nickname": "string",
- "user_phone": "string",
- "user_avatar": "string",
- "referral_code": "string",
- "amount": "number",
- "status": "pending|success|failed",
- "created_at": "timestamp",
- "processed_at": "timestamp",
- "userCommissionInfo": {
- "totalCommission": "number",
- "withdrawnEarnings": "number",
- "pendingWithdrawals": "number",
- "availableAfterThis": "number"
- }
- }
- ],
- "total": "number"
-}
-```
-
-前端期望的格式(需要检查):
-
-```typescript
-interface Withdrawal {
- id: string
- user_id: string
- user_name: string // ⚠️ 对应 user_nickname
- user_phone: string
- amount: number
- status: 'pending' | 'completed' | 'rejected' // ⚠️ 需要状态映射
- created_at: string
- completed_at: string // ⚠️ 对应 processed_at
- userCommissionInfo?: {
- totalCommission: number
- withdrawnEarnings: number
- pendingWithdrawals: number
- availableAfterThis: number
- }
-}
-```
-
-#### 4. 前端数据映射(如需要)
-
-如果字段名不完全匹配,在前端做映射:
-
-```typescript
-const formattedWithdrawals = withdrawalsData.withdrawals.map((w: any) => ({
- ...w,
- user_name: w.user_nickname, // 字段名映射
- completed_at: w.processed_at,
- // 状态映射在后端已完成,无需前端处理
-}))
-```
-
-### 方案二:保留两个接口,明确分工(不推荐)
-
-如果确实需要两个接口,应该:
-
-1. **重命名并明确用途**:
- - `/api/admin/withdrawals` → 完整管理接口(包含风险计算)
- - `/api/db/withdrawals` → 精简列表接口(仅基础字段)
-
-2. **文档化差异**:在每个文件顶部注释说明用途和差异
-
-3. **同步关键逻辑**:状态映射、权限验证必须保持一致
-
-**但这仍然不推荐**,因为会增加维护负担。
-
-## 实施步骤
-
-### 第1步:修改前端调用
-
-```bash
-# 在 app/admin/distribution/page.tsx 中全局替换
-/api/db/withdrawals → /api/admin/withdrawals
-```
-
-### 第2步:删除重复接口
-
-```bash
-rm app/api/db/withdrawals/route.ts
-```
-
-### 第3步:测试验证
-
-1. 启动开发服务器:`npm run dev`
-2. 登录后台管理
-3. 进入"交易中心" - "提现审核" tab
-4. 检查:
- - ✅ 提现列表能正常加载
- - ✅ 用户信息显示正确
- - ✅ 佣金详情显示正确(累计、已提现、待审核、可提现)
- - ✅ 批准提现功能正常
- - ✅ 拒绝提现功能正常
- - ✅ 风险提示(负余额)正常显示
-
-### 第4步:更新文档
-
-删除或更新所有提到 `/api/db/withdrawals` 的文档。
-
-## 预期效果
-
-✅ **接口统一**:只有一个提现数据接口,逻辑清晰
-✅ **功能完整**:保留所有用户佣金计算和风险提示功能
-✅ **易于维护**:bug 修复和功能升级只需修改一处
-✅ **数据一致**:避免两个接口返回不同数据导致的问题
-
-## 风险提示
-
-⚠️ **修改前请备份**:虽然修改范围小,但涉及核心财务功能
-
-⚠️ **充分测试**:修改后务必测试所有提现相关功能
-
-⚠️ **状态映射**:确认前端期望的状态值(`completed`/`rejected` vs `success`/`failed`)
-
-## 后续优化建议
-
-1. **统一状态枚举**:前后端使用相同的状态值,避免映射
-2. **统一字段命名**:`user_name` vs `user_nickname` 应统一
-3. **接口文档化**:为 `/api/admin/withdrawals` 编写完整的 API 文档
-4. **单元测试**:为提现接口添加自动化测试
-
-## 参考
-
-- 原接口:`app/api/admin/withdrawals/route.ts`
-- 前端调用:`app/admin/distribution/page.tsx` (Line 237, 299, 316)
-- 相关文档:`开发文档/8、部署/后台提现审核数据对接.md`
diff --git a/开发文档/8、部署/提现记录获取失败诊断指南.md b/开发文档/8、部署/提现记录获取失败诊断指南.md
deleted file mode 100644
index 7b55de27..00000000
--- a/开发文档/8、部署/提现记录获取失败诊断指南.md
+++ /dev/null
@@ -1,454 +0,0 @@
-# 提现记录获取失败诊断指南
-
-## 问题描述
-
-在后台管理的"交易中心-提现审核" tab 中,提示"获取提现记录失败"。
-
-## 诊断步骤
-
-### 1. 查看浏览器控制台
-
-**操作**:
-1. 打开 `http://localhost:3006/admin/distribution`
-2. 按 F12 打开开发者工具
-3. 切换到 Console 标签
-4. 点击"提现审核" tab
-5. 查看控制台输出
-
-**期望看到的日志**:
-```
-[Admin] 加载初始数据...
-[Admin] 概览数据加载成功
-[Admin] 用户数据加载成功
-[Admin] 加载 withdrawals 数据...
-[Admin] 请求提现数据...
-[Admin] 提现接口响应状态: 200 OK
-[Admin] 提现接口返回数据: { success: true, withdrawals: [...] }
-[Admin] 提现数据加载成功: X 条
-```
-
-**可能的错误情况**:
-
-#### 情况 1:权限错误(401)
-```
-[Admin] 提现接口响应状态: 401 Unauthorized
-[Admin] 提现接口HTTP错误: 401 {"error":"未授权访问,请先登录"}
-```
-
-**原因**:未登录或 session 过期
-
-**解决**:
-1. 访问 `/admin/login` 重新登录
-2. 确认登录后的 Cookie `admin_session` 存在
-
-#### 情况 2:表不存在(500)
-```
-[Admin] 提现接口响应状态: 500 Internal Server Error
-[Admin] 提现接口返回失败: Table 'mycontent.withdrawals' doesn't exist
-```
-
-**原因**:数据库中 `withdrawals` 表未创建
-
-**解决**:
-```sql
-CREATE TABLE IF NOT EXISTS withdrawals (
- id VARCHAR(50) PRIMARY KEY,
- user_id VARCHAR(50) NOT NULL,
- amount DECIMAL(10,2) NOT NULL,
- status ENUM('pending', 'processing', 'success', 'failed') DEFAULT 'pending',
- wechat_openid VARCHAR(100),
- transaction_id VARCHAR(100),
- error_message VARCHAR(500),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- processed_at TIMESTAMP,
- INDEX idx_user_id (user_id),
- INDEX idx_status (status),
- INDEX idx_created_at (created_at)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-```
-
-或访问 `/api/db/init` 初始化所有表。
-
-#### 情况 3:数据库连接失败(500)
-```
-[Admin] 提现接口响应状态: 500 Internal Server Error
-[Admin] 提现接口返回失败: Connection lost: The server closed the connection
-```
-
-**原因**:数据库连接问题
-
-**解决**:
-1. 检查数据库是否运行
-2. 检查 `.env` 中的数据库配置:
- ```
- DB_HOST=localhost
- DB_USER=root
- DB_PASSWORD=123456
- DB_NAME=mycontent
- ```
-3. 重启数据库服务
-4. 重启 Next.js 服务
-
-#### 情况 4:CORS 错误
-```
-Access to fetch at 'http://localhost:3006/api/db/withdrawals' from origin 'http://localhost:3000' has been blocked by CORS policy
-```
-
-**原因**:跨域请求被阻止
-
-**解决**:
-- 确保访问的是同一端口(都是 3006)
-- 检查 Next.js 配置
-
-#### 情况 5:网络错误
-```
-[Admin] 加载提现数据异常: TypeError: Failed to fetch
-```
-
-**原因**:网络请求失败或服务未启动
-
-**解决**:
-1. 确认服务正在运行:`pm2 list` 或查看终端
-2. 检查端口 3006 是否被占用
-3. 尝试重启服务
-
-### 2. 查看 Network 标签
-
-**操作**:
-1. 按 F12 打开开发者工具
-2. 切换到 Network 标签
-3. 点击"提现审核" tab
-4. 找到 `/api/db/withdrawals` 请求
-5. 查看详细信息
-
-**检查项**:
-
-| 检查项 | 期望值 | 问题 |
-|-------|--------|------|
-| Status | 200 OK | 401:权限问题
500:服务器错误
404:接口不存在 |
-| Response Headers | `Content-Type: application/json` | 如果是 `text/html`,说明返回了错误页面 |
-| Response Body | `{"success": true, "withdrawals": [...]}` | 查看具体错误信息 |
-| Request Headers | 包含 `Cookie: admin_session=...` | 缺少则是权限问题 |
-
-### 3. 查看服务器日志
-
-**操作**:
-```powershell
-# 使用 pm2
-pm2 logs mycontent --lines 50
-
-# 或查看终端输出
-# 直接查看 npm run dev 的终端
-```
-
-**期望看到的日志**:
-```
-[DB Withdrawals] 提现表检查/创建成功
-[DB Withdrawals] 查询提现记录...
-[DB Withdrawals] 查询成功,记录数: 0
-```
-
-**可能的错误日志**:
-
-```
-[DB Withdrawals] 提现表创建失败: ER_ACCESS_DENIED_ERROR
-```
-**解决**:数据库用户权限不足,需要 CREATE TABLE 权限
-
-```
-[DB Withdrawals] SQL查询失败: ER_NO_SUCH_TABLE
-```
-**解决**:表不存在,需要创建表
-
-```
-[DB Withdrawals] 查询失败: Connection lost
-```
-**解决**:数据库连接问题,检查配置
-
-### 4. 手动测试 API
-
-**使用浏览器**:
-```
-http://localhost:3006/api/db/withdrawals
-```
-
-**使用 curl**(需要先获取 admin_session cookie):
-```powershell
-curl http://localhost:3006/api/db/withdrawals `
- -H "Cookie: admin_session=your-token-here" `
- -v
-```
-
-**期望响应**:
-```json
-{
- "success": true,
- "withdrawals": [],
- "total": 0
-}
-```
-
-或(如果有数据):
-```json
-{
- "success": true,
- "withdrawals": [
- {
- "id": "W_xxx",
- "user_id": "user123",
- "user_name": "张三",
- "amount": 50.00,
- "status": "pending",
- "created_at": "2026-02-04 10:00:00"
- }
- ],
- "total": 1
-}
-```
-
-### 5. 检查数据库
-
-**连接数据库**:
-```sql
-USE mycontent;
-
--- 检查表是否存在
-SHOW TABLES LIKE 'withdrawals';
-
--- 查看表结构
-DESC withdrawals;
-
--- 查看数据
-SELECT * FROM withdrawals LIMIT 10;
-
--- 检查用户表(关联查询需要)
-DESC users;
-```
-
-**预期结果**:
-- `withdrawals` 表存在
-- 表结构正确
-- 可以正常查询
-
-## 常见问题修复
-
-### 问题 1:权限验证失败
-
-**症状**:返回 401 Unauthorized
-
-**修复**:
-1. 确认已登录管理后台
-2. 检查 Cookie 中是否有 `admin_session`
-3. 清除浏览器缓存后重新登录
-4. 检查 `lib/admin-auth.ts` 中的验证逻辑
-
-### 问题 2:表不存在
-
-**症状**:返回 500,错误信息 "Table doesn't exist"
-
-**修复方式 1**(推荐):
-```
-访问 http://localhost:3006/api/db/init
-```
-这会自动创建所有缺失的表。
-
-**修复方式 2**(手动):
-执行 `scripts/check-withdrawals-data.sql` 中的建表语句。
-
-**修复方式 3**(代码触发):
-访问 `/api/db/withdrawals` 会自动调用 `ensureWithdrawalsTable()` 创建表。
-
-### 问题 3:数据库连接失败
-
-**症状**:返回 500,错误信息 "Connection lost" 或 "ECONNREFUSED"
-
-**修复**:
-
-1. **检查数据库服务**:
- ```powershell
- # MySQL
- mysqld --version
- ```
-
-2. **检查环境变量**(`.env` 文件):
- ```
- DB_HOST=localhost
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=123456
- DB_NAME=mycontent
- ```
-
-3. **测试数据库连接**:
- ```powershell
- mysql -h localhost -u root -p123456 mycontent -e "SELECT 1"
- ```
-
-4. **重启服务**:
- ```powershell
- pm2 restart mycontent
- ```
-
-### 问题 4:查询返回空数组
-
-**症状**:接口成功但 `withdrawals: []`
-
-**原因**:数据库中确实没有提现记录
-
-**验证**:
-```sql
-SELECT COUNT(*) FROM withdrawals;
-```
-
-**解决**:
-- 如果是正常情况(没有用户提现),这是预期行为
-- 如果需要测试数据,可以插入测试记录:
- ```sql
- INSERT INTO withdrawals (id, user_id, amount, status, created_at)
- VALUES ('W_TEST_001', 'test_user', 50.00, 'pending', NOW());
- ```
-
-### 问题 5:前端没有显示错误
-
-**症状**:页面加载但没有数据,也没有错误提示
-
-**原因**:错误被静默处理
-
-**修复**:
-1. 打开浏览器 Console 查看日志
-2. 检查是否有 `try-catch` 吞掉了错误
-3. 查看我们刚添加的详细错误日志
-
-## 增强的错误处理
-
-我已经增强了前端的错误处理,现在会:
-
-1. ✅ 在 Console 输出详细的请求和响应信息
-2. ✅ 显示明确的错误提示弹窗
-3. ✅ 记录 HTTP 状态码和错误消息
-4. ✅ 区分不同类型的错误(权限、服务器、网络)
-
-**新增的日志**:
-```javascript
-[Admin] 请求提现数据...
-[Admin] 提现接口响应状态: 200 OK
-[Admin] 提现接口返回数据: {...}
-[Admin] 提现数据加载成功: 5 条
-```
-
-**错误提示**:
-- 权限错误:`获取提现记录失败 (401): 未授权访问,请先登录`
-- 服务器错误:`获取提现记录失败 (500): Table doesn't exist`
-- 网络错误:`加载提现数据失败: Failed to fetch`
-
-## 测试修复
-
-### 1. 重启服务
-
-```powershell
-pm2 restart mycontent
-# 或
-npm run dev
-```
-
-### 2. 清除缓存
-
-```
-Ctrl + F5 强制刷新页面
-```
-
-### 3. 测试流程
-
-1. 访问 `http://localhost:3006/admin/distribution`
-2. 点击"提现审核" tab
-3. 查看 Console 输出
-4. 如果有错误,会弹窗显示具体信息
-5. 根据错误信息按照上述"常见问题修复"处理
-
-### 4. 成功标志
-
-- ✅ Console 显示 `[Admin] 提现数据加载成功: X 条`
-- ✅ 页面显示提现列表(或"暂无数据")
-- ✅ 没有错误弹窗
-- ✅ Network 标签显示 200 OK
-
-## 预防措施
-
-### 1. 定期检查数据库连接
-
-在 `lib/db.ts` 中添加心跳检测:
-```typescript
-setInterval(async () => {
- try {
- await query('SELECT 1')
- } catch (error) {
- console.error('[DB] 数据库连接检查失败:', error)
- }
-}, 60000) // 每分钟检查一次
-```
-
-### 2. 添加健康检查接口
-
-```typescript
-// app/api/health/route.ts
-export async function GET() {
- try {
- await query('SELECT 1')
- return NextResponse.json({ status: 'ok', db: 'connected' })
- } catch (error) {
- return NextResponse.json(
- { status: 'error', db: 'disconnected', error: error.message },
- { status: 500 }
- )
- }
-}
-```
-
-访问 `/api/health` 检查系统状态。
-
-### 3. 使用数据库连接池
-
-确保 `lib/db.ts` 使用连接池:
-```typescript
-const pool = mysql.createPool({
- host: process.env.DB_HOST,
- user: process.env.DB_USER,
- password: process.env.DB_PASSWORD,
- database: process.env.DB_NAME,
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0
-})
-```
-
-## 相关文件
-
-- **前端页面**:`app/admin/distribution/page.tsx`
-- **后端 API**:`app/api/db/withdrawals/route.ts`
-- **权限验证**:`lib/admin-auth.ts`
-- **数据库配置**:`lib/db.ts`
-- **测试脚本**:`scripts/test-withdrawals-api.js`
-- **数据检查 SQL**:`scripts/check-withdrawals-data.sql`
-
-## 获取帮助
-
-如果按照上述步骤仍无法解决,请提供以下信息:
-
-1. **浏览器 Console 完整日志**(包括 `[Admin]` 开头的所有日志)
-2. **Network 标签中 `/api/db/withdrawals` 的完整请求和响应**
-3. **服务器日志**(`pm2 logs` 或终端输出)
-4. **数据库查询结果**:
- ```sql
- SHOW TABLES LIKE 'withdrawals';
- DESC withdrawals;
- SELECT COUNT(*) FROM withdrawals;
- ```
-
-## 版本信息
-
-- **更新时间**:2026-02-04
-- **修改内容**:
- 1. 增强前端错误处理和日志输出
- 2. 添加详细的错误提示弹窗
- 3. 创建诊断和修复指南
- 4. 提供测试脚本和 SQL 检查工具
diff --git a/开发文档/8、部署/收益明细优化说明.md b/开发文档/8、部署/收益明细优化说明.md
deleted file mode 100644
index 7df60cc7..00000000
--- a/开发文档/8、部署/收益明细优化说明.md
+++ /dev/null
@@ -1,733 +0,0 @@
-# 分销中心收益明细优化说明
-
-## 📋 需求
-
-在分销中心的"收益明细"部分,显示更详细的购买信息:
-1. 购买用户的头像
-2. 购买用户的昵称
-3. 购买的书籍和章节
-4. 下单时间
-
----
-
-## ✅ 实现方案
-
-### 修改前
-
-```
-┌─────────────────────────────┐
-│ 🎁 整本书购买 │
-│ 12-25 │
-│ +¥0.90 │
-└─────────────────────────────┘
-```
-
-**问题**:
-- ❌ 不知道是谁购买的
-- ❌ 不知道买的哪本书、哪一章
-- ❌ 信息太简略
-
----
-
-### 修改后
-
-```
-┌─────────────────────────────┐
-│ 👤 张三 +¥0.90 │ ← 头像 + 昵称 + 佣金
-│ 《Soul创业派对》- 1.1 │ ← 书名 - 章节
-│ 12-25 │ ← 购买时间
-└─────────────────────────────┘
-```
-
-**优势**:
-- ✅ 显示买家头像和昵称
-- ✅ 显示具体书籍和章节
-- ✅ 信息完整、清晰
-
----
-
-## 🔧 实现细节
-
-### 1. 后端 API 增强
-
-**文件**: `app/api/referral/data/route.ts`
-
-**修改前**(第159-170行):
-```typescript
-earningsDetails = await query(`
- SELECT o.id, o.order_sn, o.amount, o.product_type, o.pay_time,
- u.nickname as buyer_nickname,
- rb.commission_amount
- FROM orders o
- JOIN users u ON o.user_id = u.id
- JOIN referral_bindings rb ON o.user_id = rb.referee_id AND rb.referrer_id = ?
- WHERE o.status = 'paid'
- ORDER BY o.pay_time DESC
- LIMIT 30
-`, [userId])
-```
-
-**修改后**:
-```typescript
-earningsDetails = await query(`
- SELECT
- o.id,
- o.order_sn,
- o.amount,
- o.product_type,
- o.product_id,
- o.description, -- ✅ 新增:商品描述(书名-章节)
- o.pay_time,
- u.nickname as buyer_nickname,
- u.avatar as buyer_avatar, -- ✅ 新增:买家头像
- rb.total_commission / rb.purchase_count as commission_per_order
- FROM orders o
- JOIN users u ON o.user_id = u.id
- JOIN referral_bindings rb ON o.user_id = rb.referee_id AND rb.referrer_id = ?
- WHERE o.status = 'paid' AND o.referrer_id = ?
- ORDER BY o.pay_time DESC
- LIMIT 30
-`, [userId, userId])
-```
-
-**新增字段**:
-- ✅ `description` - 商品描述(如"《Soul创业派对》- 1.1 派对房的秘密")
-- ✅ `buyer_avatar` - 买家头像URL
-- ✅ `product_id` - 商品ID(如章节ID)
-
----
-
-### 2. 后端返回数据格式
-
-**文件**: `app/api/referral/data/route.ts` (第261-272行)
-
-**修改前**:
-```typescript
-earningsDetails: earningsDetails.map((e: any) => ({
- id: e.id,
- productType: e.product_type,
- commission: parseFloat(e.commission_amount),
- buyerNickname: e.buyer_nickname,
- payTime: e.pay_time
-}))
-```
-
-**修改后**:
-```typescript
-earningsDetails: earningsDetails.map((e: any) => ({
- id: e.id,
- orderSn: e.order_sn,
- amount: parseFloat(e.amount),
- commission: parseFloat(e.commission_per_order) || parseFloat(e.amount) * distributorShare,
- productType: e.product_type,
- productId: e.product_id,
- description: e.description, // ✅ 新增
- buyerNickname: e.buyer_nickname || '用户' + e.id?.toString().slice(-4),
- buyerAvatar: e.buyer_avatar, // ✅ 新增
- payTime: e.pay_time
-}))
-```
-
----
-
-### 3. 小程序解析商品描述
-
-**文件**: `miniprogram/pages/referral/referral.js`
-
-**新增函数**:
-```javascript
-// 解析商品描述,获取书名和章节
-parseProductDescription(description, productType) {
- if (!description) {
- return {
- bookTitle: '未知商品',
- chapterTitle: ''
- }
- }
-
- // 匹配格式:《书名》- 章节名
- const match = description.match(/《(.+?)》(?:\s*-\s*(.+))?/)
-
- if (match) {
- return {
- bookTitle: match[1] || '未知书籍',
- chapterTitle: match[2] || (productType === 'fullbook' ? '全书购买' : '')
- }
- }
-
- // 如果匹配失败,直接返回原始描述
- return {
- bookTitle: description.split('-')[0] || description,
- chapterTitle: description.split('-')[1] || ''
- }
-}
-```
-
-**解析示例**:
-
-| 原始 description | bookTitle | chapterTitle |
-|------------------|-----------|--------------|
-| 《Soul创业派对》- 1.1 派对房的秘密 | Soul创业派对 | 1.1 派对房的秘密 |
-| 《Soul创业派对》- 全书购买 | Soul创业派对 | 全书购买 |
-| 《Soul创业派对》 | Soul创业派对 | (空)|
-
----
-
-### 4. 小程序数据格式化
-
-**文件**: `miniprogram/pages/referral/referral.js` (第179-193行)
-
-**修改前**:
-```javascript
-earningsDetails: (realData?.earningsDetails || []).map(item => ({
- id: item.id,
- productType: item.productType,
- commission: (item.commission || 0).toFixed(2),
- payTime: item.payTime ? this.formatDate(item.payTime) : '--',
- buyerNickname: item.buyerNickname
-}))
-```
-
-**修改后**:
-```javascript
-earningsDetails: (realData?.earningsDetails || []).map(item => {
- // 解析商品描述,获取书名和章节
- const productInfo = this.parseProductDescription(item.description, item.productType)
-
- return {
- id: item.id,
- productType: item.productType,
- bookTitle: productInfo.bookTitle, // ✅ 新增:书名
- chapterTitle: productInfo.chapterTitle, // ✅ 新增:章节
- commission: (item.commission || 0).toFixed(2),
- payTime: item.payTime ? this.formatDate(item.payTime) : '--',
- buyerNickname: item.buyerNickname || '用户',
- buyerAvatar: item.buyerAvatar // ✅ 新增:头像
- }
-})
-```
-
----
-
-### 5. 小程序 UI 重构
-
-**文件**: `miniprogram/pages/referral/referral.wxml` (第213-231行)
-
-**修改前**:
-```xml
-
-
-
-
-
-
- {{item.productType === 'fullbook' ? '整本书购买' : '单节购买'}}
- {{item.payTime}}
-
-
- +¥{{item.commission}}
-
-```
-
-**修改后**:
-```xml
-
-
-
-
-
- {{item.buyerNickname.charAt(0)}}
-
-
-
-
-
-
- {{item.buyerNickname}}
- +¥{{item.commission}}
-
-
- {{item.bookTitle}}
- - {{item.chapterTitle}}
-
- {{item.payTime}}
-
-
-```
-
----
-
-### 6. 样式优化
-
-**文件**: `miniprogram/pages/referral/referral.wxss`
-
-**新增样式**:
-```css
-/* 收益明细增强样式 */
-.detail-item {
- display: flex;
- align-items: center;
- gap: 24rpx;
- padding: 24rpx;
- background: rgba(255, 255, 255, 0.02);
- border-radius: 16rpx;
- margin-bottom: 16rpx;
-}
-
-.detail-avatar-wrap {
- flex-shrink: 0;
-}
-
-.detail-avatar {
- width: 88rpx;
- height: 88rpx;
- border-radius: 50%;
- border: 2rpx solid rgba(56, 189, 172, 0.2);
-}
-
-.detail-avatar-text {
- width: 88rpx;
- height: 88rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #38bdac 0%, #2da396 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
-}
-
-.detail-content {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 8rpx;
-}
-
-.detail-top {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.detail-buyer {
- font-size: 28rpx;
- font-weight: 500;
- color: #ffffff;
-}
-
-.detail-amount {
- font-size: 32rpx;
- font-weight: 700;
- color: #38bdac;
-}
-
-.detail-product {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.detail-book {
- color: rgba(255, 255, 255, 0.7);
- font-weight: 500;
-}
-
-.detail-chapter {
- color: rgba(255, 255, 255, 0.5);
-}
-
-.detail-time {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-```
-
----
-
-## 🎨 UI 效果对比
-
-### 修改前 ❌
-
-```
-┌──────────────────────────────┐
-│ 🎁 整本书购买 +¥0.90 │
-│ 12-25 │
-└──────────────────────────────┘
-```
-
-**信息量**: 只有类型、时间、金额
-
----
-
-### 修改后 ✅
-
-```
-┌──────────────────────────────┐
-│ 👤 │
-│ 张三 +¥0.90 │ ← 头像 + 昵称 + 佣金
-│ 《Soul创业派对》- 1.1 派对房的秘密
-│ 12-25 │ ← 时间
-└──────────────────────────────┘
-```
-
-**信息量**: 头像、昵称、书名、章节、金额、时间 ✅
-
----
-
-## 📊 数据流转
-
-```
-订单创建
- ↓
-orders 表记录:
- - user_id (买家ID)
- - description (商品描述)
- - amount (金额)
- - pay_time (支付时间)
- ↓
-后端 API 查询:
- - JOIN users 获取买家信息(昵称、头像)
- - 返回 description、buyerAvatar 等
- ↓
-小程序解析:
- - parseProductDescription() 解析书名和章节
- - formatDate() 格式化时间
- ↓
-UI 显示:
- - 头像(有则显示,无则显示首字母)
- - 昵称、书名、章节、时间、佣金
-```
-
----
-
-## 🎯 显示逻辑
-
-### 1. 头像显示
-
-```xml
-
-
-
-
-
- {{item.buyerNickname.charAt(0)}}
-
-```
-
-**效果**:
-- 有头像:显示圆形头像(带品牌色边框)
-- 无头像:显示品牌渐变背景 + 昵称首字母
-
----
-
-### 2. 商品信息解析
-
-**输入**: `《Soul创业派对》- 1.1 派对房的秘密`
-
-**解析函数**:
-```javascript
-parseProductDescription(description, productType) {
- const match = description.match(/《(.+?)》(?:\s*-\s*(.+))?/)
- if (match) {
- return {
- bookTitle: match[1], // "Soul创业派对"
- chapterTitle: match[2] // "1.1 派对房的秘密"
- }
- }
-}
-```
-
-**显示**:
-```xml
-
- {{item.bookTitle}}
- - {{item.chapterTitle}}
-
-```
-
-**效果**: `Soul创业派对 - 1.1 派对房的秘密`
-
----
-
-### 3. 全书购买特殊处理
-
-**输入**: `《Soul创业派对》- 全书购买`
-
-**解析**:
-- `bookTitle`: "Soul创业派对"
-- `chapterTitle`: "全书购买"
-
-**显示**: `Soul创业派对 - 全书购买`
-
----
-
-### 4. 时间格式化
-
-**输入**: `2026-02-04 15:30:00`
-
-**格式化**:
-```javascript
-formatDate(dateStr) {
- const d = new Date(dateStr)
- const month = (d.getMonth() + 1).toString().padStart(2, '0')
- const day = d.getDate().toString().padStart(2, '0')
- return `${month}-${day}`
-}
-```
-
-**输出**: `02-04`
-
----
-
-## 🎨 视觉设计
-
-### 布局结构
-
-```
-┌─────────────────────────────────────┐
-│ ┌──────┐ ┌──────────────────────┐ │
-│ │ │ │ 昵称 +¥金额 │ │
-│ │ 头像 │ │ 书名 - 章节 │ │
-│ │ │ │ 时间 │ │
-│ └──────┘ └──────────────────────┘ │
-└─────────────────────────────────────┘
-```
-
-### 配色方案
-
-| 元素 | 颜色 | 说明 |
-|------|------|------|
-| 头像边框 | `rgba(56, 189, 172, 0.2)` | 品牌色半透明 |
-| 头像背景(无图)| `#38bdac → #2da396` | 品牌渐变 |
-| 昵称 | `#ffffff` | 白色 |
-| 佣金 | `#38bdac` | 品牌色(醒目)|
-| 书名 | `rgba(255, 255, 255, 0.7)` | 白色70% |
-| 章节 | `rgba(255, 255, 255, 0.5)` | 白色50% |
-| 时间 | `rgba(255, 255, 255, 0.4)` | 白色40% |
-
----
-
-## 📦 修改文件清单
-
-| 文件 | 修改内容 | 状态 |
-|------|----------|------|
-| `app/api/referral/data/route.ts` | SQL查询增加 description、buyer_avatar | ✅ |
-| `app/api/referral/data/route.ts` | 返回数据添加新字段 | ✅ |
-| `miniprogram/pages/referral/referral.js` | 添加 parseProductDescription 函数 | ✅ |
-| `miniprogram/pages/referral/referral.js` | earningsDetails 数据处理逻辑 | ✅ |
-| `miniprogram/pages/referral/referral.wxml` | 重构收益明细 UI | ✅ |
-| `miniprogram/pages/referral/referral.wxss` | 添加新样式 | ✅ |
-
----
-
-## 🧪 测试用例
-
-### 测试1: 完整信息显示
-
-**数据**:
-```json
-{
- "buyerNickname": "张三",
- "buyerAvatar": "https://...",
- "description": "《Soul创业派对》- 1.1 派对房的秘密",
- "commission": 0.90,
- "payTime": "2026-02-04 15:30:00"
-}
-```
-
-**预期显示**:
-```
-[头像] 张三 +¥0.90
- Soul创业派对 - 1.1 派对房的秘密
- 02-04
-```
-
----
-
-### 测试2: 无头像用户
-
-**数据**:
-```json
-{
- "buyerNickname": "李四",
- "buyerAvatar": null,
- "description": "《Soul创业派对》- 全书购买",
- "commission": 8.91,
- "payTime": "2026-02-03 10:20:00"
-}
-```
-
-**预期显示**:
-```
-[李] 李四 +¥8.91 ← 显示"李"(品牌色圆圈)
- Soul创业派对 - 全书购买
- 02-03
-```
-
----
-
-### 测试3: 全书购买
-
-**数据**:
-```json
-{
- "buyerNickname": "王五",
- "description": "《Soul创业派对》- 全书购买",
- "productType": "fullbook"
-}
-```
-
-**预期显示**:
-```
-[王] 王五 +¥8.91
- Soul创业派对 - 全书购买
- 02-03
-```
-
----
-
-## 🔍 技术细节
-
-### 1. 正则表达式解析
-
-```javascript
-const match = description.match(/《(.+?)》(?:\s*-\s*(.+))?/)
-```
-
-**匹配规则**:
-- `《(.+?)》` - 匹配书名(在《》内)
-- `(?:\s*-\s*(.+))?` - 可选匹配章节(` - ` 后的内容)
-
-**示例**:
-- `《Soul创业派对》- 1.1 派对房的秘密` → `["Soul创业派对", "1.1 派对房的秘密"]`
-- `《Soul创业派对》` → `["Soul创业派对", undefined]`
-
----
-
-### 2. 头像兜底方案
-
-```xml
-
-
-
-
-{{item.buyerNickname.charAt(0)}}
-```
-
-**charAt(0)**: 获取昵称第一个字符
-- "张三" → "张"
-- "Soul用户" → "S"
-- "用户1234" → "用"
-
----
-
-### 3. 文字溢出处理
-
-```css
-.detail-product {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-```
-
-**作用**: 如果章节名太长,自动省略显示 `...`
-
-**示例**:
-- 正常:`Soul创业派对 - 1.1 派对房的秘密`
-- 超长:`Soul创业派对 - 1.1 派对房的秘密以及后续的...`
-
----
-
-## 📱 响应式适配
-
-### 小屏手机
-
-```
-┌────────────────────────┐
-│ 👤 张三 +¥0.90 │ ← 紧凑布局
-│ Soul创业派对 - 1.1 │
-│ 02-04 │
-└────────────────────────┘
-```
-
-### 大屏手机
-
-```
-┌──────────────────────────────┐
-│ 👤 张三 +¥0.90 │ ← 舒适间距
-│ Soul创业派对 - 1.1 派对房的秘密
-│ 02-04 │
-└──────────────────────────────┘
-```
-
-**自适应**: 使用 `rpx` 单位,自动适配不同屏幕
-
----
-
-## ✨ 完成效果
-
-### 收益明细卡片
-
-```
-┌─────────────────────────────────┐
-│ 收益明细 │
-├─────────────────────────────────┤
-│ 👤 张三 +¥0.90 │
-│ Soul创业派对 - 1.1 派对房的秘密
-│ 02-04 │
-├─────────────────────────────────┤
-│ 👤 李四 +¥8.91 │
-│ Soul创业派对 - 全书购买 │
-│ 02-03 │
-├─────────────────────────────────┤
-│ [王] 王五 +¥0.90 │ ← 无头像显示首字母
-│ Soul创业派对 - 2.3 资源整合 │
-│ 02-02 │
-└─────────────────────────────────┘
-```
-
----
-
-## 🚀 部署说明
-
-### 无需数据库修改
-
-所有需要的字段(`description`、`avatar`)都已存在,只需部署代码即可。
-
----
-
-### 验证步骤
-
-1. 部署新代码
-2. 打开分销中心
-3. 查看"收益明细"
-4. 验证显示:
- - ✅ 买家头像或首字母
- - ✅ 买家昵称
- - ✅ 书名和章节
- - ✅ 购买时间
- - ✅ 佣金金额
-
----
-
-## 📊 信息完整度提升
-
-| 维度 | 修改前 | 修改后 |
-|------|--------|--------|
-| 买家信息 | ❌ 无 | ✅ 头像 + 昵称 |
-| 商品信息 | ❌ 只有类型 | ✅ 书名 + 章节 |
-| 金额信息 | ✅ 佣金 | ✅ 佣金 |
-| 时间信息 | ✅ 日期 | ✅ 日期 |
-
-**信息完整度**: 30% → **100%** ✅
-
----
-
-**现在收益明细显示完整,推广者可以清楚看到每笔收益的详细来源!** 🎉
diff --git a/开发文档/8、部署/新分销逻辑-代码修改总结.md b/开发文档/8、部署/新分销逻辑-代码修改总结.md
deleted file mode 100644
index 31ed4506..00000000
--- a/开发文档/8、部署/新分销逻辑-代码修改总结.md
+++ /dev/null
@@ -1,381 +0,0 @@
-# 新分销逻辑 - 代码修改总结
-
-## ✅ 已完成的代码修改
-
-### 1. 数据库层(Database Layer)
-
-#### 迁移脚本
-- ✅ `scripts/migration-add-binding-fields.sql`(SQL版本)
-- ✅ `scripts/migrate_binding_fields.py`(Python完整版)
-- ✅ `scripts/migrate_db_simple.py`(Python简化版)- **已执行成功**
-
-#### 新增字段
-```sql
-referral_bindings 表:
-✅ last_purchase_date DATETIME - 最后购买时间
-✅ purchase_count INT - 购买次数
-✅ total_commission DECIMAL(10,2) - 累计佣金
-✅ status 新增枚举值 'cancelled' - 被切换状态
-```
-
-#### 新增索引
-```sql
-✅ idx_referee_status (referee_id, status)
-✅ idx_expiry_purchase (expiry_date, purchase_count, status)
-```
-
----
-
-### 2. 核心业务逻辑(Business Logic)
-
-#### 2.1 绑定API:`app/api/referral/bind/route.ts`
-
-**修改前**:
-```typescript
-// ❌ 有效期内不能切换
-if (expiryDate < now) {
- // 已过期才能抢夺
-} else {
- return { error: '绑定有效期内无法更换' }
-}
-```
-
-**修改后**:
-```typescript
-// ✅ 立即切换(无条件)
-if (existing.referrer_id === referrer.id) {
- action = 'renew' // 同一推荐人:续期
-} else {
- action = 'switch' // 不同推荐人:立即切换
-
- // 旧绑定标记为 cancelled
- await query("UPDATE referral_bindings SET status = 'cancelled' WHERE id = ?", [existing.id])
-}
-```
-
-**核心变化**:
-- ✅ 删除"有效期内不能切换"限制
-- ✅ 点击新链接立即切换推荐人
-- ✅ 旧绑定标记为 `cancelled`(不是 `expired`)
-- ✅ 新绑定重新计算30天
-
----
-
-#### 2.2 支付回调:`app/api/miniprogram/pay/notify/route.ts`
-
-**修改前**:
-```typescript
-// ❌ 购买后标记为 converted,不再累加
-await query(`
- UPDATE referral_bindings
- SET status = 'converted',
- commission_amount = ?
- WHERE id = ?
-`, [commission, binding.id])
-```
-
-**修改后**:
-```typescript
-// ✅ 保持 active,累加购买次数和佣金
-await query(`
- UPDATE referral_bindings
- SET last_purchase_date = CURRENT_TIMESTAMP,
- purchase_count = purchase_count + 1,
- total_commission = total_commission + ?
- WHERE id = ?
-`, [commission, binding.id])
-```
-
-**核心变化**:
-- ✅ 不再改变 `status`(保持 `active`)
-- ✅ 累加 `purchase_count`
-- ✅ 累加 `total_commission`
-- ✅ 记录 `last_purchase_date`
-- ✅ 支持同一绑定多次购买分佣
-
----
-
-#### 2.3 支付订单:`app/api/miniprogram/pay/route.ts`
-
-**新增功能**:好友优惠折扣
-
-```typescript
-// ✅ 读取好友优惠配置
-const referralConfig = await getConfig('referral_config')
-const userDiscount = referralConfig?.userDiscount || 0
-
-// ✅ 如果有推荐码,应用折扣
-if (userDiscount > 0 && body.referralCode) {
- const discountRate = userDiscount / 100
- finalAmount = amount * (1 - discountRate)
- // 原价 1.00 → 优惠 5% → 实付 0.95
-}
-```
-
-**核心变化**:
-- ✅ 通过推荐链接购买会自动打折
-- ✅ 折扣比例从后台配置读取
-- ✅ 佣金基于实付金额计算
-
----
-
-#### 2.4 提现API:`app/api/withdraw/route.ts`
-
-**新增功能**:读取最低提现门槛
-
-```typescript
-// ✅ 从配置读取最低提现门槛
-const config = await getConfig('referral_config')
-const minWithdrawAmount = config?.minWithdrawAmount || 10
-
-// ✅ 检查最低门槛
-if (amount < minWithdrawAmount) {
- return { error: `最低提现金额为 ¥${minWithdrawAmount}` }
-}
-```
-
-**核心变化**:
-- ✅ 提现门槛可通过后台配置
-- ✅ 替代了硬编码的 10 元
-
----
-
-### 3. 管理后台(Admin Panel)
-
-#### 3.1 推广设置页面:`app/admin/referral-settings/page.tsx`
-
-**新增功能**:
-- ✅ 配置好友优惠(userDiscount)
-- ✅ 配置推广者分成(distributorShare)
-- ✅ 配置绑定有效期(bindingDays)
-- ✅ 配置最低提现金额(minWithdrawAmount)
-- ✅ 配置自动提现开关(enableAutoWithdraw)
-
-**数据安全**:
-```typescript
-// ✅ 保存时强制类型转换
-const safeConfig = {
- distributorShare: Number(config.distributorShare) || 0,
- minWithdrawAmount: Number(config.minWithdrawAmount) || 0,
- bindingDays: Number(config.bindingDays) || 0,
- userDiscount: Number(config.userDiscount) || 0,
- enableAutoWithdraw: Boolean(config.enableAutoWithdraw),
-}
-```
-
----
-
-#### 3.2 菜单入口:`app/admin/layout.tsx`
-
-```typescript
-// ✅ 新增菜单项
-{ icon: CreditCard, label: "推广设置", href: "/admin/referral-settings" }
-```
-
----
-
-### 4. 小程序(MiniProgram)
-
-#### 4.1 分销中心UI:`miniprogram/pages/referral/referral.wxml`
-
-**修改**:
-```xml
-
-
-```
-
----
-
-### 5. 定时任务(Scheduled Task)
-
-#### 自动解绑脚本
-
-- ✅ `scripts/auto-unbind-expired.js`(标准版)
-- ✅ `scripts/auto-unbind-expired-simple.js`(简化版,直接连MySQL)
-
-**解绑条件**:
-```javascript
-WHERE status = 'active'
- AND expiry_date < NOW()
- AND purchase_count = 0
-```
-
-**执行逻辑**:
-1. 查询符合条件的绑定
-2. 标记为 `expired`
-3. 更新推荐人的 `referral_count`
-4. 输出日志
-
----
-
-## 📋 完整的业务流程
-
-### 场景1:新用户绑定
-
-```
-用户操作:B 点击 A 的分享链接
-触发API:/api/referral/bind
-数据变化:
- - referral_bindings 新增记录
- - referee_id: B
- - referrer_id: A
- - status: active
- - expiry_date: NOW + 30天
- - purchase_count: 0
- - users.referred_by: A
- - users.referral_count (A): +1
-```
-
----
-
-### 场景2:切换推荐人
-
-```
-用户操作:B 点击 C 的分享链接
-触发API:/api/referral/bind
-数据变化:
- - 旧绑定 (A -> B):
- - status: active → cancelled
- - 新绑定 (C -> B):
- - 新增记录
- - status: active
- - expiry_date: NOW + 30天
- - users.referred_by: A → C
- - users.referral_count (A): -1
- - users.referral_count (C): +1
-```
-
----
-
-### 场景3:购买分佣
-
-```
-用户操作:B 购买文章(1元,假设无优惠)
-触发API:/api/miniprogram/pay/notify
-数据变化:
- - referral_bindings (C -> B):
- - purchase_count: 0 → 1
- - total_commission: 0 → 0.90
- - last_purchase_date: NOW
- - status: 保持 active
- - users.pending_earnings (C): +0.90
-```
-
----
-
-### 场景4:好友优惠购买
-
-```
-用户操作:B 通过推荐链接购买(原价1元,优惠5%)
-触发API:/api/miniprogram/pay
-计算逻辑:
- - 原价: 1.00元
- - 优惠: 1.00 × 5% = 0.05元
- - 实付: 0.95元
-
-后续分佣:
- - 佣金 = 0.95 × 90% = 0.855元
- - C 获得 0.86元(四舍五入)
-```
-
----
-
-### 场景5:自动解绑
-
-```
-触发:定时任务(每天02:00)
-执行脚本:scripts/auto-unbind-expired-simple.js
-筛选条件:
- - status = 'active'
- - expiry_date < NOW
- - purchase_count = 0
-
-数据变化:
- - referral_bindings: status → expired
- - users.referral_count: -1(对应的推荐人)
-```
-
----
-
-## 🎯 核心逻辑总结
-
-| 功能 | 实现状态 | 说明 |
-|------|---------|------|
-| **立即切换绑定** | ✅ 完成 | 点击新链接立即切换推荐人 |
-| **佣金归属** | ✅ 完成 | 给购买时的当前推荐人 |
-| **购买累加** | ✅ 完成 | 同一绑定可多次购买分佣 |
-| **好友优惠** | ✅ 完成 | 通过推荐链接自动打折 |
-| **提现门槛** | ✅ 完成 | 后台可配置最低金额 |
-| **自动解绑** | ✅ 完成 | 30天无购买自动解绑 |
-| **推广设置页** | ✅ 完成 | 管理后台统一配置入口 |
-
----
-
-## 📦 已部署文件清单
-
-### 后端API(7个文件)
-1. ✅ `app/api/referral/bind/route.ts` - 立即切换绑定
-2. ✅ `app/api/miniprogram/pay/notify/route.ts` - 累加分佣
-3. ✅ `app/api/miniprogram/pay/route.ts` - 好友优惠
-4. ✅ `app/api/withdraw/route.ts` - 提现门槛
-5. ✅ `app/admin/referral-settings/page.tsx` - 推广设置页
-6. ✅ `app/admin/layout.tsx` - 菜单入口
-
-### 小程序(1个文件)
-7. ✅ `miniprogram/pages/referral/referral.wxml` - 去掉邀请码卡片
-
-### 脚本(5个文件)
-8. ✅ `scripts/migrate_db_simple.py` - 数据库迁移
-9. ✅ `scripts/auto-unbind-expired-simple.js` - 定时任务
-10. ✅ `scripts/test-referral-flow.js` - 功能测试
-
-### 文档(3个文件)
-11. ✅ `开发文档/8、部署/新分销逻辑设计方案.md`
-12. ✅ `开发文档/8、部署/新分销逻辑-部署步骤.md`
-13. ✅ `开发文档/8、部署/新分销逻辑-宝塔操作清单.md`
-
----
-
-## 🔄 部署状态
-
-- ✅ 数据库字段已添加
-- ✅ 代码已构建(pnpm build)
-- ✅ 代码已上传服务器(python devlop.py)
-- ⏳ **待操作:宝塔面板重启服务**
-- ⏳ **待操作:宝塔面板配置定时任务**
-
----
-
-## 🚦 下一步操作
-
-### 必须完成(服务才能生效)
-
-1. **重启 Node.js 服务**
- - 宝塔面板 → 网站 → soul.quwanzhi.com → Node项目 → 重启
- - 或SSH执行:`/www/server/nodejs/v16.20.2/bin/pm2 restart soul`
-
-2. **配置定时任务**
- - 宝塔面板 → 计划任务 → 添加Shell脚本
- - 执行周期:每天 02:00
- - 脚本内容:
- ```bash
- cd /www/wwwroot/soul/dist && /www/server/nodejs/v16.20.2/bin/node scripts/auto-unbind-expired-simple.js >> /www/wwwroot/soul/logs/auto-unbind.log 2>&1
- ```
-
-### 建议测试
-
-3. **验证功能**
- - 访问推广设置页面:`https://soul.quwanzhi.com/admin/referral-settings`
- - 小程序测试绑定切换
- - 测试购买分佣
-
----
-
-## ✅ 代码逻辑完成度:100%
-
-**所有核心逻辑已全部实现并部署!**
-
-剩余工作仅为:
-1. 宝塔面板重启服务(1分钟)
-2. 宝塔面板配置定时任务(2分钟)
-3. 功能测试验证(可选)
diff --git a/开发文档/8、部署/章节阅读页集成示例.md b/开发文档/8、部署/章节阅读页集成示例.md
deleted file mode 100644
index dbfdea81..00000000
--- a/开发文档/8、部署/章节阅读页集成示例.md
+++ /dev/null
@@ -1,436 +0,0 @@
-# 章节阅读页集成示例
-
-> 展示如何在 `miniprogram/pages/read/read.js` 中集成权限管理器和阅读追踪器
-
----
-
-## 一、引入工具类
-
-```javascript
-// pages/read/read.js
-import accessManager from '../../utils/chapterAccessManager'
-import readingTracker from '../../utils/readingTracker'
-
-const app = getApp()
-
-Page({
- data: {
- // 系统信息
- statusBarHeight: 44,
- navBarHeight: 88,
-
- // 章节信息
- sectionId: '',
- section: null,
-
- // 【新增】权限状态(状态机)
- accessState: 'unknown', // unknown | free | locked_not_login | locked_not_purchased | unlocked_purchased | error
-
- // 内容
- content: '',
- contentParagraphs: [],
- previewParagraphs: [],
- loading: true,
-
- // 用户状态
- isLoggedIn: false,
-
- // 配置
- freeIds: [],
- sectionPrice: 1,
- fullBookPrice: 9.9
- },
-
- // 页面加载(标准流程)
- async onLoad(options) {
- const { id, ref } = options
-
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight,
- navBarHeight: app.globalData.navBarHeight,
- sectionId: id,
- loading: true,
- accessState: 'unknown'
- })
-
- // 处理推荐码(异步不阻塞)
- if (ref) {
- wx.setStorageSync('referral_code', ref)
- app.handleReferralCode({ query: { ref } })
- }
-
- try {
- // 1. 拉取最新配置(免费列表、价格)
- const config = await accessManager.fetchLatestConfig()
- this.setData({
- freeIds: config.freeChapters,
- sectionPrice: config.prices.section,
- fullBookPrice: config.prices.fullbook
- })
-
- // 2. 确定权限状态
- const accessState = await accessManager.determineAccessState(id, config.freeChapters)
- this.setData({
- accessState,
- isLoggedIn: !!app.globalData.userInfo?.id
- })
-
- // 3. 加载内容
- await this.loadContent(id, accessState)
-
- // 4. 如果有权限,初始化阅读追踪
- if (accessManager.canAccessFullContent(accessState)) {
- readingTracker.init(id)
- }
-
- // 5. 加载导航
- this.loadNavigation(id)
-
- } catch (e) {
- console.error('[Read] 初始化失败:', e)
- wx.showToast({ title: '加载失败,请重试', icon: 'none' })
- this.setData({ accessState: 'error' })
- } finally {
- this.setData({ loading: false })
- }
- },
-
- // 加载内容(根据权限决定全文或预览)
- async loadContent(id, accessState) {
- try {
- const res = await app.request(`/api/book/chapter/${id}`)
-
- if (res && res.content) {
- const lines = res.content.split('\n').filter(line => line.trim())
- const previewCount = Math.ceil(lines.length * 0.2)
-
- this.setData({
- content: res.content,
- contentParagraphs: lines,
- previewParagraphs: lines.slice(0, previewCount),
- section: {
- id: res.id,
- title: res.title || res.sectionTitle,
- price: res.price || this.data.sectionPrice,
- isFree: res.isFree
- }
- })
- }
- } catch (e) {
- console.error('[Read] 加载内容失败:', e)
- throw e
- }
- },
-
- // 滚动事件(追踪阅读进度)
- onPageScroll(e) {
- // 只在有权限时追踪
- if (!accessManager.canAccessFullContent(this.data.accessState)) {
- return
- }
-
- // 获取滚动信息
- const query = wx.createSelectorQuery()
- query.select('.page').boundingClientRect()
- query.selectViewport().scrollOffset()
- query.exec((res) => {
- if (res[0] && res[1]) {
- const scrollInfo = {
- scrollTop: res[1].scrollTop,
- scrollHeight: res[0].height,
- clientHeight: res[1].height
- }
-
- // 更新追踪器
- readingTracker.updateProgress(scrollInfo)
- }
- })
- },
-
- // 登录成功(标准流程)
- async handleWechatLogin() {
- try {
- const result = await app.login()
- if (!result) return
-
- this.setData({ showLoginModal: false })
- wx.showLoading({ title: '更新状态中...' })
-
- try {
- // 1. 刷新用户购买状态
- await accessManager.refreshUserPurchaseStatus()
-
- // 2. 重新拉取免费列表(可能刚改免费)
- const config = await accessManager.fetchLatestConfig()
- this.setData({ freeIds: config.freeChapters })
-
- // 3. 重新判断当前章节权限
- const newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- config.freeChapters
- )
-
- this.setData({
- accessState: newAccessState,
- isLoggedIn: true
- })
-
- // 4. 如果已解锁,重新加载内容并初始化追踪
- if (accessManager.canAccessFullContent(newAccessState)) {
- await this.loadContent(this.data.sectionId, newAccessState)
- readingTracker.init(this.data.sectionId)
- }
-
- wx.hideLoading()
- wx.showToast({ title: '登录成功', icon: 'success' })
-
- } catch (e) {
- wx.hideLoading()
- console.error('[Read] 登录后更新状态失败:', e)
- wx.showToast({ title: '状态更新失败,请重试', icon: 'none' })
- }
-
- } catch (e) {
- wx.showToast({ title: '登录失败', icon: 'none' })
- }
- },
-
- // 支付成功(标准流程)
- async onPaymentSuccess() {
- wx.showLoading({ title: '确认购买中...' })
-
- try {
- // 1. 等待服务端处理支付回调
- await this.sleep(2000)
-
- // 2. 刷新购买状态
- await accessManager.refreshUserPurchaseStatus()
-
- // 3. 重新判断权限(应为 unlocked_purchased)
- let newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- this.data.freeIds
- )
-
- // 如果权限未生效,再重试一次
- if (newAccessState !== 'unlocked_purchased') {
- await this.sleep(1000)
- newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- this.data.freeIds
- )
- }
-
- this.setData({ accessState: newAccessState })
-
- // 4. 重新加载全文
- await this.loadContent(this.data.sectionId, newAccessState)
-
- // 5. 初始化阅读追踪
- readingTracker.init(this.data.sectionId)
-
- wx.hideLoading()
- wx.showToast({ title: '购买成功', icon: 'success' })
-
- } catch (e) {
- wx.hideLoading()
- console.error('[Read] 支付后更新失败:', e)
- wx.showModal({
- title: '提示',
- content: '购买成功,但内容加载失败,请返回重新进入',
- showCancel: false
- })
- }
- },
-
- // 重试按钮(当 accessState 为 error 时显示)
- async handleRetry() {
- wx.showLoading({ title: '重试中...' })
-
- try {
- const config = await accessManager.fetchLatestConfig()
- this.setData({ freeIds: config.freeChapters })
-
- const newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- config.freeChapters
- )
-
- this.setData({ accessState: newAccessState })
-
- if (accessManager.canAccessFullContent(newAccessState)) {
- await this.loadContent(this.data.sectionId, newAccessState)
- readingTracker.init(this.data.sectionId)
- }
-
- wx.hideLoading()
- wx.showToast({ title: '加载成功', icon: 'success' })
-
- } catch (e) {
- wx.hideLoading()
- wx.showToast({ title: '重试失败,请检查网络', icon: 'none' })
- }
- },
-
- // 页面隐藏/卸载时上报进度
- onHide() {
- readingTracker.onPageHide()
- },
-
- onUnload() {
- readingTracker.cleanup()
- },
-
- // 工具方法
- sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms))
- },
-
- // ... 其他方法(分享、导航等)
-})
-```
-
----
-
-## 二、WXML 模板适配
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
- 🔒
- 登录后继续阅读
-
- 立即登录
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
- 🔒
- 购买后继续阅读
-
-
-
- 购买本章 ¥{{sectionPrice}}
-
-
- 解锁全书 ¥{{fullBookPrice}}
-
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
- ⚠️
- 网络异常
- 无法确认权限,请检查网络后重试
-
- 重新加载
-
-
-
-
-```
-
----
-
-## 三、关键改动点对比
-
-### 改造前(存在的问题)
-```javascript
-// ❌ 问题:多处权限判断逻辑不统一
-if (isFree) { canAccess = true }
-else if (!isLoggedIn) { canAccess = false }
-else if (hasFullBook || purchasedSections.includes(id)) { canAccess = true }
-else { /* 请求接口 */ }
-
-// ❌ 问题:异常时用本地缓存可能误解锁
-catch (e) {
- canAccess = hasFullBook || purchasedSections.includes(id)
-}
-
-// ❌ 问题:无阅读进度追踪
-```
-
-### 改造后(标准流程)
-```javascript
-// ✅ 统一通过 accessManager 判断权限
-const accessState = await accessManager.determineAccessState(id, freeList)
-
-// ✅ 异常时保守策略:返回 'error' 状态,展示重试按钮
-catch (e) {
- return 'error' // 不信任本地缓存
-}
-
-// ✅ 有权限时自动追踪阅读进度
-if (accessManager.canAccessFullContent(accessState)) {
- readingTracker.init(id)
-}
-```
-
----
-
-## 四、迁移步骤
-
-1. **引入工具类**:将 `chapterAccessManager.js` 和 `readingTracker.js` 放入 `miniprogram/utils/`
-2. **创建数据表**:在数据库执行 `create_reading_progress_table.sql`
-3. **部署接口**:将 `reading-progress/route.ts` 部署到服务端
-4. **改造阅读页**:参考上面示例,将 `onLoad`、`handleWechatLogin`、`onPaymentSuccess` 按标准流程重构
-5. **测试验证**:各种场景(免费、未登录、已登录未购买、已购买、网络异常)逐一测试
-6. **数据验证**:检查 `reading_progress` 表是否正常记录进度
-
----
-
-## 五、预期效果
-
-- ✅ **权限判断统一**:所有权限判断走 `determineAccessState`,以服务端为准
-- ✅ **状态流转清晰**:通过 `accessState` 枚举,UI 展示与状态一一对应
-- ✅ **异常降级标准**:网络异常时展示 `error` 状态 + 重试按钮,不误解锁
-- ✅ **阅读进度可追踪**:记录进度、时长、是否读完,支持断点续读
-- ✅ **数据驱动分析**:`reading_progress` 表为后续数据分析提供基础
-
-以上为完整的集成示例,建议先在测试环境验证,再部署到生产。
diff --git a/开发文档/8、部署/管理端推广配置与小程序对接说明.md b/开发文档/8、部署/管理端推广配置与小程序对接说明.md
deleted file mode 100644
index 8e078df2..00000000
--- a/开发文档/8、部署/管理端推广配置与小程序对接说明.md
+++ /dev/null
@@ -1,384 +0,0 @@
-# 管理端推广配置与小程序对接说明
-
-## 📋 配置项说明
-
-### 管理端配置
-**位置**: `/admin/referral-settings`
-
-**配置项**:
-1. **distributorShare** - 分销比例(例如:90 表示 90%)
-2. **minWithdrawAmount** - 最低提现金额(例如:10 表示 10元)
-3. **bindingDays** - 绑定天数(例如:30 表示 30天)
-4. **userDiscount** - 好友优惠(例如:5 表示 5% 折扣)
-5. **enableAutoWithdraw** - 是否启用自动提现
-
-**存储位置**: `system_config` 表,键名 `referral_config`
-
----
-
-## ✅ 已对接的配置
-
-### 1. distributorShare(分销比例)
-
-#### 后端使用
-**文件**: `app/api/miniprogram/pay/notify/route.ts`
-
-**用途**: 计算推荐人佣金
-```typescript
-// 获取配置
-const config = await getConfig('referral_config')
-const distributorShare = config.distributorShare / 100 // 90 → 0.9
-
-// 计算佣金
-const commission = amount * distributorShare // 1元 * 0.9 = 0.9元
-```
-
-#### 前端显示
-**文件**: `miniprogram/pages/referral/referral.wxml`
-
-**位置**: 分销中心页面
-```xml
-{{shareRate}}% 返利
-```
-
-**数据来源**: `/api/referral/data` 接口返回 `shareRate`
-
-**对接状态**: ✅ 已完成
-- 后端从配置读取
-- API返回给前端
-- 前端动态显示
-
----
-
-### 2. minWithdrawAmount(最低提现金额)
-
-#### 后端使用
-**文件**: `app/api/withdraw/route.ts`
-
-**用途**: 验证提现金额
-```typescript
-// 获取配置
-const config = await getConfig('referral_config')
-const minWithdrawAmount = config.minWithdrawAmount || 10
-
-// 验证金额
-if (amount < minWithdrawAmount) {
- return error('提现金额不能低于' + minWithdrawAmount + '元')
-}
-```
-
-#### 前端显示
-**文件**: `miniprogram/pages/referral/referral.wxml`
-
-**位置**: 提现按钮
-```xml
-
-
- {{earnings < 10 ? '满10元可提现' : '申请提现'}}
-
-
-
-
- {{pendingEarnings < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现'}}
-
-```
-
-**数据来源**: `/api/referral/data` 接口返回 `minWithdrawAmount`
-
-**对接状态**: ✅ 刚完成
-- 后端从配置读取
-- API新增返回字段
-- 前端动态显示
-
----
-
-### 3. bindingDays(绑定天数)
-
-#### 后端使用
-**文件**: `app/api/referral/bind/route.ts`
-
-**用途**: 计算绑定过期时间
-```typescript
-// 获取配置
-const config = await getConfig('referral_config')
-const bindingDays = config.bindingDays || 30
-
-// 计算过期时间
-const expiryDate = new Date()
-expiryDate.setDate(expiryDate.getDate() + bindingDays)
-```
-
-**对接状态**: ✅ 已完成
-- 后端从配置读取
-- 自动应用于绑定逻辑
-- 前端无需显示(内部逻辑)
-
----
-
-### 4. userDiscount(好友优惠)
-
-#### 后端使用
-**文件**: `app/api/miniprogram/pay/route.ts`
-
-**用途**: 计算好友购买折扣
-```typescript
-// 获取配置
-const config = await getConfig('referral_config')
-const userDiscount = config.userDiscount || 0
-
-// 计算折后价
-if (userDiscount > 0 && referralCode) {
- finalAmount = amount * (1 - userDiscount / 100) // 1元 * (1 - 0.05) = 0.95元
-}
-```
-
-**对接状态**: ✅ 已完成
-- 后端从配置读取
-- 自动应用于支付流程
-- 前端无需显示(微信支付弹窗自动显示折后价)
-
----
-
-### 5. enableAutoWithdraw(自动提现)
-
-**对接状态**: ⏸️ 功能待开发
-- 配置已存在
-- 后端逻辑待实现
-- 前端UI待实现
-
----
-
-## 🔄 数据流向图
-
-```
-管理端修改配置
- ↓
-保存到 system_config 表
- ↓
-后端API读取配置(getConfig)
- ↓
- ├─→ /api/referral/bind → 使用 bindingDays
- ├─→ /api/miniprogram/pay → 使用 userDiscount
- ├─→ /api/miniprogram/pay/notify → 使用 distributorShare
- ├─→ /api/withdraw → 使用 minWithdrawAmount
- └─→ /api/referral/data → 返回 shareRate + minWithdrawAmount
- ↓
- 小程序获取数据
- ↓
- 动态显示配置
-```
-
----
-
-## 📝 本次修改内容
-
-### 1. 后端API修改
-**文件**: `app/api/referral/data/route.ts`
-
-**修改内容**:
-```typescript
-// 新增读取 minWithdrawAmount
-let minWithdrawAmount = 10
-try {
- const config = await getConfig('referral_config')
- if (config?.minWithdrawAmount) {
- minWithdrawAmount = Number(config.minWithdrawAmount)
- }
-} catch (e) { /* 使用默认 */ }
-
-// 返回数据中新增字段
-return {
- shareRate: Math.round(distributorShare * 100),
- minWithdrawAmount, // 新增
- // ... 其他字段
-}
-```
-
----
-
-### 2. 小程序JS修改
-**文件**: `miniprogram/pages/referral/referral.js`
-
-**修改内容**:
-```javascript
-// data 中新增字段
-data: {
- minWithdrawAmount: 10, // 新增
- shareRate: 90,
- // ...
-}
-
-// 从API获取配置
-setData({
- shareRate: realData?.shareRate || 90,
- minWithdrawAmount: realData?.minWithdrawAmount || 10, // 新增
- // ...
-})
-```
-
----
-
-### 3. 小程序WXML修改
-**文件**: `miniprogram/pages/referral/referral.wxml`
-
-**旧代码**:
-```xml
-
- {{earnings < 10 ? '满10元可提现' : '申请提现'}}
-
-```
-
-**新代码**:
-```xml
-
- {{pendingEarnings < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现'}}
-
-```
-
----
-
-## ✅ 对接完成度
-
-| 配置项 | 后端使用 | API返回 | 小程序显示 | 状态 |
-|--------|---------|---------|------------|------|
-| distributorShare | ✅ | ✅ | ✅ | 已完成 |
-| minWithdrawAmount | ✅ | ✅ | ✅ | 刚完成 |
-| bindingDays | ✅ | - | - | 已完成(内部逻辑)|
-| userDiscount | ✅ | - | - | 已完成(自动应用)|
-| enableAutoWithdraw | ⏸️ | - | - | 待开发 |
-
----
-
-## 🧪 测试验证
-
-### 1. 修改配置
-1. 登录管理后台
-2. 进入「推广设置」页面
-3. 修改配置:
- - 分销比例:改为 85%
- - 最低提现金额:改为 20元
-4. 保存配置
-
-### 2. 验证后端
-```bash
-# 测试分销中心API
-curl "https://soul.quwanzhi.com/api/referral/data?userId=xxx"
-
-# 预期返回
-{
- "shareRate": 85,
- "minWithdrawAmount": 20,
- ...
-}
-```
-
-### 3. 验证小程序
-1. 打开小程序「分销中心」页面
-2. 检查显示:
- - 「85% 返利」(而不是 90%)
- - 「满20元可提现」(而不是满10元)
-3. 尝试提现时,应该验证是否满20元
-
----
-
-## 🚀 部署步骤
-
-### 1. 部署后端
-```bash
-pnpm build
-python devlop.py
-pm2 restart soul
-```
-
-### 2. 测试API
-```bash
-curl "https://soul.quwanzhi.com/api/referral/data?userId=xxx"
-```
-
-### 3. 上传小程序
-- 在微信开发者工具上传代码
-- 提交审核
-- 发布新版本
-
----
-
-## 📊 配置示例
-
-### 默认配置
-```json
-{
- "distributorShare": 90,
- "minWithdrawAmount": 10,
- "bindingDays": 30,
- "userDiscount": 5,
- "enableAutoWithdraw": false
-}
-```
-
-### 修改后效果
-
-#### 场景1: 提高最低提现门槛
-```json
-{ "minWithdrawAmount": 50 }
-```
-**效果**:
-- 后端验证:必须满50元才能提现
-- 小程序显示:「满50元可提现」
-
-#### 场景2: 降低分成比例
-```json
-{ "distributorShare": 70 }
-```
-**效果**:
-- 后端计算:推荐人获得 70% 佣金
-- 小程序显示:「70% 返利」
-
-#### 场景3: 增加好友优惠
-```json
-{ "userDiscount": 10 }
-```
-**效果**:
-- 后端计算:好友购买打 9折
-- 微信支付:显示折后价(例如 1元 → 0.9元)
-
----
-
-## 🔍 问题排查
-
-### 问题1: 小程序显示的分成比例不对
-**原因**: 前端没有重新加载数据
-**解决**: 下拉刷新页面,重新调用 `/api/referral/data`
-
-### 问题2: 提现验证还是用的旧金额
-**原因**: 后端缓存或配置未更新
-**解决**:
-1. 检查数据库 `system_config` 表
-2. 重启PM2服务
-
-### 问题3: 修改配置后小程序不生效
-**原因**: 小程序使用了旧版本
-**解决**:
-1. 确保上传了新版本小程序
-2. 用户需要重启小程序
-
----
-
-## ✅ 总结
-
-**已完成对接**:
-- ✅ distributorShare(分销比例)- 后端计算 + 小程序显示
-- ✅ minWithdrawAmount(最低提现金额)- 后端验证 + 小程序显示
-- ✅ bindingDays(绑定天数)- 后端逻辑
-- ✅ userDiscount(好友优惠)- 后端计算
-
-**待开发功能**:
-- ⏸️ enableAutoWithdraw(自动提现)
-
-**优势**:
-- 管理员可以在后台随时调整配置
-- 无需修改代码即可生效
-- 用户看到的是实时配置
-
----
-
-**现在管理端的推广配置已完全对接到小程序逻辑!**
diff --git a/开发文档/8、部署/累计佣金计算修复说明.md b/开发文档/8、部署/累计佣金计算修复说明.md
deleted file mode 100644
index c40e2a5f..00000000
--- a/开发文档/8、部署/累计佣金计算修复说明.md
+++ /dev/null
@@ -1,265 +0,0 @@
-# 累计佣金计算修复说明
-
-## 一、问题描述
-
-**问题**:小程序分销中心显示的"累计佣金"金额不正确
-
-**表现**:显示 ¥0.90,但实际应该更高
-
-## 二、问题根源
-
-### 错误的计算逻辑
-
-**原逻辑(错误)**:
-```typescript
-totalCommission: Math.round((
- (parseFloat(user.earnings) || 0) + // 已结算收益
- (parseFloat(user.pending_earnings) || 0) + // 待结算收益
- (parseFloat(user.withdrawn_earnings) || 0) // 已提现金额
-) * 100) / 100
-```
-
-**问题分析**:
-1. 使用 `users` 表的字段计算
-2. `users.earnings`、`users.pending_earnings`、`users.withdrawn_earnings` 这三个字段可能未正确维护
-3. 没有从实际的佣金记录(`referral_bindings.total_commission`)中统计
-
-### 正确的数据来源
-
-累计佣金应该从 `referral_bindings` 表统计:
-
-```sql
-SELECT COALESCE(SUM(total_commission), 0)
-FROM referral_bindings
-WHERE referrer_id = 'user_id'
-```
-
-**为什么?**
-- `referral_bindings.total_commission` 记录了每个推荐关系产生的佣金
-- 每次用户付款时,这个字段都会累加(在 `/api/payment/*/notify` 中更新)
-- 这是最准确的佣金来源
-
-## 三、修复方案
-
-### 1. 修改聚合查询SQL
-
-**文件**:`app/api/referral/data/route.ts`
-
-**修改内容**:在统计查询中添加累计佣金字段
-
-```typescript
-const statsResult = await query(`
- SELECT
- -- ... 其他字段 ...
-
- -- 累计佣金总额(从所有推荐关系中累加)
- (SELECT COALESCE(SUM(total_commission), 0)
- FROM referral_bindings
- WHERE referrer_id = u.id) as total_commission_from_bindings
-
- FROM users u
- WHERE u.id = ?
-`, [userId]) as any[]
-```
-
-### 2. 修改返回数据
-
-```typescript
-// === 收益数据 ===
-// 累计佣金总额(从referral_bindings表累加所有佣金)
-totalCommission: Math.round(parseFloat(stats.total_commission_from_bindings || 0) * 100) / 100,
-```
-
-## 四、数据流程说明
-
-### 佣金累加流程
-
-```
-用户A购买商品(¥10)
- ↓
-支付回调 /api/payment/*/notify
- ↓
-计算佣金:¥10 × 90% = ¥9
- ↓
-更新数据:
-- users.pending_earnings += 9 // 可提现金额
-- referral_bindings.total_commission += 9 // 累计佣金 ✅
-- referral_bindings.purchase_count += 1
- ↓
-显示:
-- 累计佣金:从 referral_bindings.total_commission 累加
-- 可提现金额:users.pending_earnings
-```
-
-### 关键字段说明
-
-| 表 | 字段 | 说明 | 用途 |
-|------|------|------|------|
-| `users` | `pending_earnings` | 可提现金额 | 提现时使用 |
-| `users` | `earnings` | 已结算收益 | 历史兼容字段 |
-| `users` | `withdrawn_earnings` | 已提现金额 | 提现记录 |
-| `referral_bindings` | `total_commission` | **累计佣金** | **显示累计佣金** ✅ |
-| `referral_bindings` | `purchase_count` | 购买次数 | 统计转化 |
-
-## 五、验证方法
-
-### 1. 数据库验证
-
-```sql
--- 查看某个用户的累计佣金
-SELECT
- referrer_id,
- SUM(total_commission) as total_commission,
- COUNT(*) as binding_count,
- SUM(purchase_count) as total_purchases
-FROM referral_bindings
-WHERE referrer_id = 'user_xxx'
-GROUP BY referrer_id;
-
--- 查看每个推荐关系的佣金明细
-SELECT
- rb.id,
- rb.referee_id,
- u.nickname as referee_nickname,
- rb.total_commission,
- rb.purchase_count,
- rb.status
-FROM referral_bindings rb
-JOIN users u ON rb.referee_id = u.id
-WHERE rb.referrer_id = 'user_xxx'
-ORDER BY rb.total_commission DESC;
-```
-
-### 2. API验证
-
-```bash
-# 调用接口
-curl -X GET "https://your-domain.com/api/referral/data?userId=xxx" \
- -H "Cookie: auth_token=xxx"
-```
-
-**检查返回数据**:
-```json
-{
- "success": true,
- "data": {
- "totalCommission": 45.00, // 应该等于数据库中的累加值
- "availableEarnings": 30.00,
- "pendingWithdrawAmount": 0.00,
- "withdrawnEarnings": 15.00
- }
-}
-```
-
-### 3. 小程序验证
-
-刷新分销中心页面,检查:
-- ✅ 累计佣金金额正确
-- ✅ 与数据库中的 `SUM(total_commission)` 一致
-
-## 六、数据一致性检查
-
-### 检查是否有数据不一致
-
-```sql
--- 检查 users 表的金额 vs referral_bindings 表的金额
-SELECT
- u.id,
- u.nickname,
- u.earnings + u.pending_earnings + u.withdrawn_earnings as users_total,
- COALESCE((SELECT SUM(total_commission) FROM referral_bindings WHERE referrer_id = u.id), 0) as bindings_total,
- u.earnings + u.pending_earnings + u.withdrawn_earnings -
- COALESCE((SELECT SUM(total_commission) FROM referral_bindings WHERE referrer_id = u.id), 0) as difference
-FROM users u
-WHERE u.referral_count > 0
- OR EXISTS (SELECT 1 FROM referral_bindings WHERE referrer_id = u.id)
-ORDER BY ABS(difference) DESC
-LIMIT 20;
-```
-
-**如果有差异**:说明数据不一致,需要检查支付回调逻辑是否正确更新了两边的数据。
-
-## 七、未来优化建议
-
-### 1. 数据冗余优化
-
-**问题**:
-- `users.earnings`、`users.pending_earnings`、`users.withdrawn_earnings`
-- `referral_bindings.total_commission`
-- 两处存储相同的数据,容易不一致
-
-**建议**:
-- 保留 `referral_bindings.total_commission` 作为主要数据源
-- `users.pending_earnings` 用于提现判断
-- `users.withdrawn_earnings` 用于提现历史
-- 移除 `users.earnings` 字段(已不使用)
-
-### 2. 实时统计
-
-**当前**:每次查询时从 `referral_bindings` 累加
-
-**优化**:
-- 使用物化视图或定时任务
-- 将累计佣金缓存到 `users` 表的一个字段
-- 每次佣金变化时更新
-
-### 3. 数据校验
-
-**建议添加定时任务**:
-```sql
--- 校验并修复数据不一致
-UPDATE users u
-SET u.total_commission_cache = (
- SELECT COALESCE(SUM(total_commission), 0)
- FROM referral_bindings
- WHERE referrer_id = u.id
-)
-WHERE u.referral_count > 0;
-```
-
-## 八、相关文件
-
-### 修改文件
-1. `app/api/referral/data/route.ts` - 修改累计佣金计算逻辑
-
-### 相关文件
-1. `app/api/payment/wechat/notify/route.ts` - 支付回调,更新佣金
-2. `app/api/payment/alipay/notify/route.ts` - 支付回调,更新佣金
-3. `miniprogram/pages/referral/referral.wxml` - 显示累计佣金
-
-### 数据库表
-1. `users` - 用户表
-2. `referral_bindings` - 推荐关系表
-3. `orders` - 订单表
-
-## 九、总结
-
-### 修复前
-- ❌ 从 `users` 表的三个字段相加
-- ❌ 数据可能不准确
-- ❌ 没有反映实际的佣金记录
-
-### 修复后
-- ✅ 从 `referral_bindings.total_commission` 累加
-- ✅ 数据准确,反映实际佣金
-- ✅ 与支付回调逻辑一致
-
-### 关键改动
-**一行SQL**:
-```sql
-(SELECT COALESCE(SUM(total_commission), 0)
- FROM referral_bindings
- WHERE referrer_id = u.id) as total_commission_from_bindings
-```
-
-**一行代码**:
-```typescript
-totalCommission: Math.round(parseFloat(stats.total_commission_from_bindings || 0) * 100) / 100,
-```
-
----
-
-**修复时间**:2026-02-04
-**影响范围**:累计佣金显示
-**向后兼容**:✅ 完全兼容,仅修改计算来源
-**需要重启**:是(重启Next.js服务)
diff --git a/开发文档/8、部署/绑定关系存储方案分析.md b/开发文档/8、部署/绑定关系存储方案分析.md
deleted file mode 100644
index e5aefa88..00000000
--- a/开发文档/8、部署/绑定关系存储方案分析.md
+++ /dev/null
@@ -1,554 +0,0 @@
-# 绑定关系存储方案分析
-
-## 📊 当前实现
-
-### 表结构
-
-#### 1. referral_bindings 表(主表)
-```sql
-CREATE TABLE referral_bindings (
- id VARCHAR(50) PRIMARY KEY,
- referrer_id VARCHAR(50), -- 推荐人ID
- referee_id VARCHAR(50), -- 被推荐人ID
- referral_code VARCHAR(50), -- 推荐码
- status ENUM('active', 'expired', 'cancelled'), -- 状态
- binding_date DATETIME, -- 绑定时间
- expiry_date DATETIME, -- 过期时间
- last_purchase_date DATETIME, -- 最后购买时间
- purchase_count INT DEFAULT 0, -- 购买次数
- total_commission DECIMAL(10,2) DEFAULT 0.00, -- 累计佣金
-
- INDEX idx_referee_status (referee_id, status),
- INDEX idx_referrer_status (referrer_id, status)
-)
-```
-
-#### 2. users 表(冗余字段)
-```sql
-CREATE TABLE users (
- id VARCHAR(50) PRIMARY KEY,
- referred_by VARCHAR(50), -- 冗余:当前推荐人ID
- referral_count INT DEFAULT 0, -- 冗余:推荐人的推广数量
- referral_code VARCHAR(50), -- 自己的推荐码
- pending_earnings DECIMAL(10,2), -- 待结算收益
- earnings DECIMAL(10,2), -- 已结算收益
- withdrawn_earnings DECIMAL(10,2) -- 已提现金额
-)
-```
-
----
-
-## 🔍 当前使用情况分析
-
-### 1. 绑定关系的创建/更新(/api/referral/bind)
-
-**操作**:
-```typescript
-// 1. 查询当前绑定(使用 referral_bindings)
-SELECT * FROM referral_bindings
-WHERE referee_id = ? AND status = 'active'
-
-// 2. 创建/更新绑定记录
-INSERT INTO referral_bindings (...)
-
-// 3. 同步更新 users.referred_by(冗余)
-UPDATE users SET referred_by = ? WHERE id = ?
-
-// 4. 更新 users.referral_count(冗余计数)
-UPDATE users SET referral_count = referral_count + 1 WHERE id = ?
-```
-
-**问题**:
-- ✅ `referral_bindings` 是真实来源
-- ⚠️ `users.referred_by` 是冗余,可能不一致
-
----
-
-### 2. 支付回调计算佣金(/api/miniprogram/pay/notify)
-
-**操作**:
-```typescript
-// 查询绑定关系(使用 referral_bindings)
-SELECT * FROM referral_bindings
-WHERE referee_id = ? AND status = 'active'
-ORDER BY binding_date DESC LIMIT 1
-
-// 如果找到 → 给推荐人佣金
-UPDATE users SET pending_earnings = pending_earnings + ? WHERE id = ?
-```
-
-**结论**:
-- ✅ 只使用 `referral_bindings`
-- ✅ 不依赖 `users.referred_by`
-
----
-
-### 3. 分销中心数据(/api/referral/data)
-
-**操作**:
-```typescript
-// 查询活跃绑定
-SELECT * FROM referral_bindings
-WHERE referrer_id = ? AND status = 'active' AND expiry_date > NOW()
-
-// 查询已转化用户
-SELECT * FROM referral_bindings
-WHERE referrer_id = ? AND status = 'active' AND purchase_count > 0
-
-// 查询过期绑定
-SELECT * FROM referral_bindings
-WHERE referrer_id = ? AND status IN ('expired', 'cancelled')
-```
-
-**结论**:
-- ✅ 只使用 `referral_bindings`
-- ✅ 不依赖 `users.referred_by`
-
----
-
-### 4. 自动解绑(/api/cron/unbind-expired)
-
-**操作**:
-```typescript
-// 查询需要解绑的记录
-SELECT * FROM referral_bindings
-WHERE status = 'active'
- AND expiry_date < NOW()
- AND purchase_count = 0
-
-// 批量更新为 expired
-UPDATE referral_bindings SET status = 'expired' WHERE id IN (...)
-
-// 更新 referral_count
-UPDATE users SET referral_count = GREATEST(referral_count - ?, 0) WHERE id = ?
-```
-
-**结论**:
-- ✅ 只使用 `referral_bindings`
-- ⚠️ 但没有更新 `users.referred_by`(可能导致不一致)
-
----
-
-### 5. 旧代码兼容(/api/referral/bind - 旧接口)
-
-**操作**:
-```typescript
-// 查询推荐的用户(使用 users.referred_by)
-SELECT * FROM users WHERE referred_by = ?
-```
-
-**问题**:
-- ⚠️ 使用了 `users.referred_by`
-- ⚠️ 可能查到已过期的绑定
-- ⚠️ 应该改用 `referral_bindings`
-
----
-
-## 📊 数据一致性分析
-
-### 场景1: 用户 A 推荐 B,30天后过期
-
-#### referral_bindings 表
-```sql
-referrer_id: A
-referee_id: B
-status: expired ✅ 正确
-expiry_date: 2026-01-01
-```
-
-#### users 表
-```sql
-B.referred_by: A ⚠️ 仍然是 A(未清空)
-A.referral_count: 1 ⚠️ 未减少(自动解绑任务有更新)
-```
-
-**问题**:
-- `users.referred_by` 没有在过期时清空
-- 如果查询 `users.referred_by`,会得到错误结果
-
----
-
-### 场景2: B 从 A 切换到 C
-
-#### referral_bindings 表
-```sql
--- 旧绑定
-referrer_id: A
-referee_id: B
-status: cancelled ✅ 正确
-
--- 新绑定
-referrer_id: C
-referee_id: B
-status: active ✅ 正确
-```
-
-#### users 表
-```sql
-B.referred_by: C ✅ 正确(已更新)
-A.referral_count: 0 ✅ 正确(已减少)
-C.referral_count: 1 ✅ 正确(已增加)
-```
-
-**结论**:切换时同步正确
-
----
-
-## 🎯 性能分析
-
-### 方案1: 只用 referral_bindings(推荐)
-
-**优势**:
-- ✅ 数据一致性强(单一数据源)
-- ✅ 状态清晰(active / expired / cancelled)
-- ✅ 信息完整(过期时间、购买次数等)
-- ✅ 易于维护
-
-**劣势**:
-- ❌ 查询需要 JOIN 或多次查询
-- ❌ 复杂查询性能稍低
-
-**查询示例**:
-```typescript
-// 查询用户的当前推荐人
-SELECT referrer_id FROM referral_bindings
-WHERE referee_id = ? AND status = 'active' AND expiry_date > NOW()
-ORDER BY binding_date DESC LIMIT 1
-```
-
-**性能**:
-- 有索引 `idx_referee_status`
-- 查询速度:~0.1ms
-- 适合:几乎所有场景
-
----
-
-### 方案2: 冗余到 users 表
-
-**优势**:
-- ✅ 查询快(直接读 users.referred_by)
-- ✅ 简单场景方便
-
-**劣势**:
-- ❌ 数据一致性差(需要同步)
-- ❌ 过期后不准确
-- ❌ 切换时需要多表更新
-- ❌ 维护成本高
-
-**需要同步的场景**:
-1. 新绑定时
-2. 切换推荐人时
-3. 绑定过期时 ⚠️(当前未同步)
-4. 绑定取消时 ⚠️(当前未同步)
-
----
-
-### 方案3: 视图或计算字段(推荐)
-
-**实现**:
-```sql
--- 创建视图
-CREATE VIEW user_current_referrer AS
-SELECT
- rb.referee_id as user_id,
- rb.referrer_id,
- u.nickname as referrer_nickname,
- rb.expiry_date,
- rb.purchase_count
-FROM referral_bindings rb
-JOIN users u ON rb.referrer_id = u.id
-WHERE rb.status = 'active'
- AND rb.expiry_date > NOW()
-```
-
-**使用**:
-```typescript
-// 查询用户的当前推荐人
-SELECT * FROM user_current_referrer WHERE user_id = ?
-```
-
-**优势**:
-- ✅ 数据一致性强
-- ✅ 查询方便
-- ✅ 自动更新
-- ✅ 无需维护冗余
-
----
-
-## 🔧 当前问题
-
-### 问题1: users.referred_by 不准确
-
-**场景**:绑定过期后,`users.referred_by` 仍然有值
-
-**影响**:
-```typescript
-// 错误的查询
-SELECT * FROM users WHERE referred_by = ?
-// 会查到已过期的用户
-```
-
-**解决方案**:
-1. 停用 `users.referred_by`,只用 `referral_bindings`
-2. 或者在过期时清空 `users.referred_by`
-
----
-
-### 问题2: 旧代码依赖 users.referred_by
-
-**位置**:`/api/referral/bind` 的 GET 接口
-
-```typescript
-// 旧代码
-SELECT * FROM users WHERE referred_by = ?
-```
-
-**应该改为**:
-```typescript
-// 新代码
-SELECT u.* FROM users u
-JOIN referral_bindings rb ON u.id = rb.referee_id
-WHERE rb.referrer_id = ?
- AND rb.status = 'active'
- AND rb.expiry_date > NOW()
-```
-
----
-
-## 🎯 推荐方案
-
-### 方案A: 渐进式优化(推荐)
-
-**步骤1: 停用 users.referred_by**
-- 不再更新 `users.referred_by`
-- 所有查询改用 `referral_bindings`
-
-**步骤2: 优化索引**
-- 确保 `referral_bindings` 有合适的索引
-- `idx_referee_status` ✅ 已有
-- `idx_referrer_status` ✅ 已有
-
-**步骤3: 创建辅助函数**
-```typescript
-// 获取用户的当前推荐人
-async function getCurrentReferrer(userId: string) {
- const bindings = await query(`
- SELECT referrer_id, expiry_date, purchase_count
- FROM referral_bindings
- WHERE referee_id = ?
- AND status = 'active'
- AND expiry_date > NOW()
- ORDER BY binding_date DESC
- LIMIT 1
- `, [userId])
-
- return bindings[0]?.referrer_id || null
-}
-```
-
-**优势**:
-- ✅ 数据一致性强
-- ✅ 无需维护冗余
-- ✅ 性能优秀(有索引)
-- ✅ 维护成本低
-
----
-
-### 方案B: 保留 users.referred_by(不推荐)
-
-如果一定要保留,需要确保同步:
-
-**同步点**:
-1. ✅ 新绑定时(已实现)
-2. ✅ 切换推荐人时(已实现)
-3. ❌ 绑定过期时(需要添加)
-4. ❌ 绑定取消时(需要添加)
-
-**实现**:
-```typescript
-// 在自动解绑时
-UPDATE users SET referred_by = NULL
-WHERE id IN (
- SELECT referee_id FROM referral_bindings
- WHERE status = 'expired'
-)
-```
-
-**劣势**:
-- ❌ 维护成本高
-- ❌ 容易出错
-- ❌ 收益不大
-
----
-
-## 📊 性能对比
-
-### 查询1: 获取用户的推荐人
-
-#### 使用 users.referred_by
-```sql
-SELECT referred_by FROM users WHERE id = ?
-```
-- 耗时:~0.01ms
-- 准确性:❌ 可能过期
-
-#### 使用 referral_bindings
-```sql
-SELECT referrer_id FROM referral_bindings
-WHERE referee_id = ? AND status = 'active' AND expiry_date > NOW()
-LIMIT 1
-```
-- 耗时:~0.1ms(有索引)
-- 准确性:✅ 完全准确
-
-**差异**:0.09ms(几乎可以忽略)
-
----
-
-### 查询2: 获取推荐人的下级列表
-
-#### 使用 users.referred_by
-```sql
-SELECT * FROM users WHERE referred_by = ?
-```
-- 耗时:~1ms
-- 准确性:❌ 包含过期用户
-
-#### 使用 referral_bindings
-```sql
-SELECT u.* FROM users u
-JOIN referral_bindings rb ON u.id = rb.referee_id
-WHERE rb.referrer_id = ?
- AND rb.status = 'active'
- AND rb.expiry_date > NOW()
-```
-- 耗时:~1.5ms(有索引)
-- 准确性:✅ 完全准确
-
-**差异**:0.5ms(可接受)
-
----
-
-## ✅ 结论与建议
-
-### 推荐:方案A(只用 referral_bindings)
-
-**理由**:
-1. ✅ **数据一致性**:单一数据源,避免不一致
-2. ✅ **逻辑清晰**:状态明确(active / expired / cancelled)
-3. ✅ **维护简单**:无需同步冗余字段
-4. ✅ **性能优秀**:有合适的索引,差异可忽略
-5. ✅ **功能完整**:支持过期、切换、购买次数等
-
-### 不推荐:保留 users.referred_by
-
-**理由**:
-1. ❌ 数据一致性差(容易出错)
-2. ❌ 维护成本高(多处同步)
-3. ❌ 性能提升微乎其微(0.09ms)
-4. ❌ 功能受限(无法判断是否过期)
-
----
-
-## 🔧 优化建议
-
-### 短期优化(立即执行)
-
-1. **停用 users.referred_by 的写入**
- - 不再更新这个字段
- - 保留字段(避免破坏性变更)
-
-2. **修改旧查询**
- - 找到所有使用 `users.referred_by` 的查询
- - 改用 `referral_bindings`
-
-3. **添加辅助函数**
- - 封装常用查询
- - 简化代码
-
-### 中期优化(1-2周内)
-
-1. **性能监控**
- - 监控查询性能
- - 确保没有性能问题
-
-2. **数据清理**
- - 可选:清空 `users.referred_by`
- - 避免误用
-
-### 长期优化(可选)
-
-1. **删除冗余字段**
- - 如果确认不再使用
- - 彻底删除 `users.referred_by`
-
-2. **创建视图或缓存**
- - 如果有特殊性能需求
- - 考虑 Redis 缓存
-
----
-
-## 📝 具体修改建议
-
-### 1. 停止更新 users.referred_by
-
-```typescript
-// app/api/referral/bind/route.ts
-
-// 删除或注释掉这行
-// await query('UPDATE users SET referred_by = ? WHERE id = ?', [referrer.id, user.id])
-```
-
-### 2. 修改旧查询
-
-```typescript
-// 旧代码
-const users = await query('SELECT * FROM users WHERE referred_by = ?', [userId])
-
-// 新代码
-const users = await query(`
- SELECT u.* FROM users u
- JOIN referral_bindings rb ON u.id = rb.referee_id
- WHERE rb.referrer_id = ?
- AND rb.status = 'active'
- AND rb.expiry_date > NOW()
-`, [userId])
-```
-
-### 3. 添加辅助函数
-
-```typescript
-// lib/referral-helpers.ts
-
-export async function getCurrentReferrer(userId: string) {
- const bindings = await query(`
- SELECT referrer_id, expiry_date, purchase_count, total_commission
- FROM referral_bindings
- WHERE referee_id = ?
- AND status = 'active'
- AND expiry_date > NOW()
- ORDER BY binding_date DESC
- LIMIT 1
- `, [userId])
-
- return bindings[0] || null
-}
-
-export async function getActiveReferrals(referrerId: string) {
- return await query(`
- SELECT
- u.id, u.nickname, u.avatar,
- rb.binding_date, rb.expiry_date, rb.purchase_count, rb.total_commission
- FROM referral_bindings rb
- JOIN users u ON rb.referee_id = u.id
- WHERE rb.referrer_id = ?
- AND rb.status = 'active'
- AND rb.expiry_date > NOW()
- ORDER BY rb.binding_date DESC
- `, [referrerId])
-}
-```
-
----
-
-**总结:建议停用 users.referred_by,只使用 referral_bindings 表,性能差异微乎其微,但数据一致性大幅提升!**
diff --git a/开发文档/8、部署/自动化与Webhook.md b/开发文档/8、部署/自动化与Webhook.md
deleted file mode 100644
index 17a4f4ab..00000000
--- a/开发文档/8、部署/自动化与Webhook.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# 自动化与 Webhook(合并自 Next.js自动化、WEBHOOK、GitHub Webhook 与宝塔、自动同步)
-
-Next.js 自动化部署流程、Vercel/宝塔 Webhook 配置、自动同步与分支策略。详见原各文档。
diff --git a/开发文档/8、部署/自动解绑API配置说明.md b/开发文档/8、部署/自动解绑API配置说明.md
deleted file mode 100644
index 2874e552..00000000
--- a/开发文档/8、部署/自动解绑API配置说明.md
+++ /dev/null
@@ -1,280 +0,0 @@
-# 自动解绑API配置说明
-
-## 📋 接口信息
-
-### API 地址
-```
-GET https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026
-```
-
-### 功能说明
-自动解绑过期的推荐关系,条件:
-- ✅ `status = 'active'`(活跃状态)
-- ✅ `expiry_date < NOW()`(已过期)
-- ✅ `purchase_count = 0`(从未购买)
-
-**规则说明**:
-- 只解绑「活跃 + 过期 + 未购买」的绑定
-- 如果用户购买过(`purchase_count > 0`),即使过期也**不解绑**
-- 保留有价值的推荐关系记录
-
----
-
-## 🔧 宝塔面板配置
-
-### 步骤1: 创建计划任务
-
-1. 登录宝塔面板
-2. 点击左侧菜单「计划任务」
-3. 点击「添加计划任务」
-
-### 步骤2: 配置任务参数
-
-**任务类型**: 访问URL
-
-**任务名称**: 自动解绑过期推荐关系
-
-**执行周期**: N分钟
-
-**分钟选择**: 30(每30分钟执行一次)
-
-**URL地址**:
-```
-https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026
-```
-
-**备注**: 自动解绑过期且未购买的推荐关系
-
-### 步骤3: 保存并测试
-
-1. 点击「保存」
-2. 点击「执行」按钮手动测试一次
-3. 查看执行日志,确认任务正常运行
-
----
-
-## 📊 返回数据格式
-
-### 成功响应(有数据)
-```json
-{
- "success": true,
- "message": "自动解绑完成",
- "unbound": 5,
- "updatedReferrers": 3,
- "details": [
- {
- "refereeId": "user_123",
- "referrerId": "user_456",
- "bindingDate": "2026-01-05T10:30:00.000Z",
- "expiryDate": "2026-02-04T10:30:00.000Z",
- "daysExpired": 1
- }
- ],
- "duration": 245
-}
-```
-
-### 成功响应(无数据)
-```json
-{
- "success": true,
- "message": "无需解绑的记录",
- "unbound": 0,
- "duration": 12
-}
-```
-
-### 失败响应(密钥错误)
-```json
-{
- "success": false,
- "error": "未授权访问"
-}
-```
-
----
-
-## 🔍 日志示例
-
-### 控制台输出
-```
-[UnbindExpired] ========== 自动解绑任务开始 ==========
-[UnbindExpired] 找到 5 条需要解绑的记录
-[UnbindExpired] 1. 用户 user_123
- 推荐人: user_456
- 绑定时间: 2026/1/5
- 过期时间: 2026/2/4 (已过期 1 天)
- 购买次数: 0
- 累计佣金: ¥0.00
-[UnbindExpired] 2. 用户 user_789
- 推荐人: user_456
- 绑定时间: 2026/1/10
- 过期时间: 2026/2/3 (已过期 2 天)
- 购买次数: 0
- 累计佣金: ¥0.00
-...
-[UnbindExpired] 已成功解绑 5 条记录
-[UnbindExpired] 更新推荐人 user_456 的 referral_count (-3)
-[UnbindExpired] 更新推荐人 user_999 的 referral_count (-2)
-[UnbindExpired] 解绑完成: 5 条记录,更新 2 个推荐人
-[UnbindExpired] ========== 任务结束 (耗时 245ms) ==========
-```
-
----
-
-## 🔐 安全说明
-
-### 密钥保护
-- 密钥硬编码在代码中:`soul_cron_unbind_2026`
-- 只能通过正确的密钥访问接口
-- 如果需要修改密钥,编辑 `app/api/cron/unbind-expired/route.ts` 第 24 行
-
-### 访问权限
-- ✅ 只支持 GET 请求
-- ✅ 需要提供正确的 secret 参数
-- ✅ 错误的密钥返回 401 未授权
-
----
-
-## ⏰ 推荐执行频率
-
-### 每30分钟(推荐)
-- ✅ 及时处理过期绑定
-- ✅ 不会给服务器造成压力
-- ✅ 符合业务需求
-
-### 其他选项
-- 每15分钟:如果需要更实时的解绑
-- 每1小时:如果对实时性要求不高
-- 每天凌晨3点:如果只需要每日清理
-
----
-
-## 🧪 手动测试
-
-### 方法1: 浏览器测试
-直接在浏览器访问:
-```
-https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026
-```
-
-### 方法2: curl 命令
-```bash
-curl "https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026"
-```
-
-### 方法3: 宝塔面板手动执行
-1. 进入「计划任务」
-2. 找到"自动解绑过期推荐关系"任务
-3. 点击「执行」按钮
-4. 查看「日志」了解执行结果
-
----
-
-## 📝 数据库操作
-
-### 查询将被解绑的记录(测试用)
-```sql
-SELECT
- id,
- referrer_id,
- referee_id,
- binding_date,
- expiry_date,
- purchase_count,
- total_commission,
- DATEDIFF(NOW(), expiry_date) as days_expired
-FROM referral_bindings
-WHERE status = 'active'
- AND expiry_date < NOW()
- AND purchase_count = 0
-ORDER BY expiry_date ASC;
-```
-
-### 查看解绑历史
-```sql
-SELECT
- id,
- referrer_id,
- referee_id,
- binding_date,
- expiry_date,
- status,
- purchase_count,
- total_commission
-FROM referral_bindings
-WHERE status = 'expired'
-ORDER BY expiry_date DESC
-LIMIT 20;
-```
-
----
-
-## 🔄 对比:API vs 脚本
-
-### 旧方案(Node.js 脚本)
-```bash
-# 缺点:
-- ❌ 需要配置服务器环境变量
-- ❌ 需要手动配置数据库连接
-- ❌ 需要确保 Node.js 路径正确
-- ❌ 依赖外部脚本文件
-```
-
-### 新方案(API接口)
-```bash
-# 优点:
-- ✅ 无需配置环境变量
-- ✅ 无需手动配置数据库(使用现有连接)
-- ✅ 宝塔面板直接调用URL
-- ✅ 集成在应用代码中
-- ✅ 更易于维护和监控
-```
-
----
-
-## 📊 监控与告警
-
-### 监控指标
-- 解绑数量(`unbound`)
-- 执行时长(`duration`)
-- 成功率
-
-### 查看执行历史
-在宝塔面板的「计划任务」→「日志」中查看每次执行的结果。
-
-### 建议
-- 如果单次解绑数量 > 100,检查是否有异常
-- 如果连续失败,检查数据库连接或接口状态
-
----
-
-## ✅ 部署检查清单
-
-部署前确认:
-- ✅ API 文件已创建:`app/api/cron/unbind-expired/route.ts`
-- ✅ 代码已部署到服务器
-- ✅ PM2 服务已重启
-
-部署后确认:
-- ✅ 手动访问接口测试成功
-- ✅ 宝塔计划任务已创建
-- ✅ 执行周期设置为 30 分钟
-- ✅ 手动执行一次测试成功
-- ✅ 查看日志确认正常运行
-
----
-
-## 🚀 快速配置命令
-
-### 宝塔面板 - 计划任务配置
-
-**任务类型**: 访问URL
-**URL地址**: `https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026`
-**执行周期**: N分钟 → 30
-**任务名称**: 自动解绑过期推荐关系
-
----
-
-**配置完成后,系统将每30分钟自动解绑过期且未购买的推荐关系!**
diff --git a/开发文档/8、部署/自定义导航组件方案.md b/开发文档/8、部署/自定义导航组件方案.md
deleted file mode 100644
index 64a2e4f3..00000000
--- a/开发文档/8、部署/自定义导航组件方案.md
+++ /dev/null
@@ -1,595 +0,0 @@
-# 自定义导航组件方案
-
-## 📋 背景
-
-### 为什么不使用原生 tabBar?
-
-**需求**:
-- "找伙伴"功能需要根据 API 配置动态显示/隐藏
-- 不同环境(开发/测试/生产)可能有不同的功能开关
-- 需要灵活控制导航栏的显示逻辑
-
-**原生 tabBar 的限制**:
-1. ❌ **静态配置**:`app.json` 中的 `tabBar.list` 是固定的,无法动态增删
-2. ❌ **无法隐藏单个 tab**:只能显示或隐藏整个 tabBar
-3. ❌ **样式受限**:虽然支持自定义 tabBar,但配置复杂
-4. ❌ **功能限制**:自定义 tabBar 需要在每个页面手动管理状态
-
-**自定义组件的优势**:
-1. ✅ **完全动态**:可以根据任何条件显示/隐藏任意 tab
-2. ✅ **样式自由**:完全控制样式,可以实现中间凸起按钮等特殊效果
-3. ✅ **状态统一**:通过 props 传递当前页面,组件内部管理激活态
-4. ✅ **跨平台一致**:Web 和小程序使用相同的组件逻辑
-
----
-
-## 🔧 技术方案
-
-### 方案对比
-
-| 特性 | 原生 tabBar | 自定义 tabBar | 自定义组件(当前方案) |
-|------|------------|--------------|---------------------|
-| 动态显示/隐藏 | ❌ | ⚠️ 复杂 | ✅ 简单 |
-| 样式自由度 | ❌ 受限 | ✅ 自由 | ✅ 完全自由 |
-| 中间凸起按钮 | ❌ 不支持 | ⚠️ 复杂 | ✅ 简单 |
-| 跨平台一致性 | ❌ 不同 | ⚠️ 需额外处理 | ✅ 一致 |
-| 页面跳转 | `wx.switchTab` | `wx.switchTab` | `wx.reLaunch` |
-| 配置复杂度 | 简单 | 复杂 | 中等 |
-
-**结论**:使用**自定义组件**方案
-
----
-
-## 📝 实现细节
-
-### 1. 移除原生 tabBar 配置
-
-**文件**:`newpp/build/miniprogram.config.js`
-
-```javascript
-appExtraConfig: {
- sitemapLocation: 'sitemap.json',
- // ✅ 不配置 tabBar,使用完全自定义的导航组件
- // 原因:需要根据 API 配置动态显示/隐藏"找伙伴"功能
-},
-```
-
-**说明**:
-- 不在 `app.json` 中配置 `tabBar`
-- 完全依赖自定义组件 `BottomNav.jsx`
-
----
-
-### 2. 修改路由跳转方式
-
-**文件**:`newpp/src/adapters/router.js`
-
-#### Before(使用 wx.switchTab)
-
-```javascript
-export function switchTab(path) {
- if (isMiniProgram()) {
- wx.switchTab({ url: toMpPath(path) }) // ❌ 需要原生 tabBar 配置
- } else {
- window.location.href = path === '/' ? 'index.html' : path.replace(/^\//, '') + '.html'
- }
-}
-```
-
-#### After(使用 wx.reLaunch)
-
-```javascript
-export function switchTab(path) {
- if (isMiniProgram()) {
- // ✅ 使用 wx.reLaunch 代替 wx.switchTab
- // 原因:没有配置原生 tabBar,使用自定义组件
- wx.reLaunch({ url: toMpPath(path) })
- } else {
- window.location.href = path === '/' ? 'index.html' : path.replace(/^\//, '') + '.html'
- }
-}
-```
-
-**wx.reLaunch vs wx.switchTab:**
-
-| API | 说明 | 页面栈 | 限制 | 适用场景 |
-|-----|------|--------|------|---------|
-| `wx.switchTab` | 跳转到 tabBar 页面 | 清空 | 只能跳转到原生 tabBar 页面 | 原生 tabBar |
-| `wx.reLaunch` | 关闭所有页面,打开某页面 | 清空 | 无 | 自定义导航 |
-| `wx.redirectTo` | 关闭当前页面,跳转 | 替换栈顶 | 无 | 页面跳转 |
-| `wx.navigateTo` | 保留当前页面,跳转 | 新增栈 | 最多10层 | 详情页 |
-
-**为什么选择 `wx.reLaunch`?**
-1. ✅ 清空页面栈,避免页面堆积
-2. ✅ 无限制,可以跳转到任何页面
-3. ✅ 模拟 tabBar 的行为(清空栈)
-4. ⚠️ 缺点:每次跳转会重新加载页面(但对于导航栏切换这是正常的)
-
----
-
-### 3. 自定义组件实现
-
-**文件**:`newpp/src/components/BottomNav.jsx`
-
-#### 核心逻辑
-
-```javascript
-export default function BottomNav({ current }) {
- const [matchEnabled, setMatchEnabled] = useState(true)
- const [configLoaded, setConfigLoaded] = useState(false)
-
- useEffect(() => {
- if (isMiniProgram()) {
- // ✅ 小程序:从 app.globalData 读取配置
- try {
- const app = getApp()
- if (app && app.globalData) {
- setMatchEnabled(app.globalData.matchEnabled !== false)
- }
- } catch (e) {
- // 默认显示
- } finally {
- setConfigLoaded(true)
- }
- } else {
- // ✅ Web:从 API 加载配置
- fetch('/api/db/config')
- .then((res) => res.json())
- .then((data) => {
- if (data.features) {
- setMatchEnabled(data.features.matchEnabled === true)
- }
- })
- .catch(() => {
- setMatchEnabled(false)
- })
- .finally(() => {
- setConfigLoaded(true)
- })
- }
- }, [])
-
- // ✅ 根据配置动态生成可见的 tabs
- const visibleTabs = matchEnabled ? tabs : tabs.filter((t) => t.id !== 'match')
-
- const handleTabClick = (path) => {
- if (path === current) return
-
- // ✅ 使用 switchTab(内部调用 wx.reLaunch)
- try {
- switchTab(path)
- } catch (e) {
- console.error('switchTab error:', e)
- }
- }
-
- return (
-
-
- {visibleTabs.map((tab) => {
- // ✅ 根据 isCenter 渲染不同样式
- if (tab.isCenter) {
- return (/* 中间凸起按钮 */)
- }
- return (/* 普通按钮 */)
- })}
-
-
- )
-}
-```
-
-#### 配置加载流程
-
-```
-┌─────────────────┐
-│ 组件挂载 │
-└────────┬────────┘
- │
- ▼
-┌─────────────────┐
-│ 判断环境 │
-│ isMiniProgram? │
-└────┬──────┬─────┘
- │ Yes │ No
- ▼ ▼
-┌─────────┐ ┌──────────────┐
-│小程序环境│ │ Web 环境 │
-│getApp() │ │fetch('/api') │
-│.globalData│ │ .then() │
-└────┬────┘ └──────┬───────┘
- │ │
- └──────┬──────┘
- ▼
- ┌───────────────┐
- │setMatchEnabled│
- │setConfigLoaded│
- └───────┬───────┘
- ▼
- ┌───────────────┐
- │ 动态渲染 tabs │
- │ visibleTabs │
- └───────────────┘
-```
-
----
-
-## 🎯 配置管理
-
-### Web 环境
-
-**API 端点**:`/api/db/config`
-
-**返回格式**:
-```json
-{
- "features": {
- "matchEnabled": true // ✅ 控制"找伙伴"功能
- }
-}
-```
-
-**配置位置**:数据库或配置文件
-
-```javascript
-// app/api/db/config/route.ts
-export async function GET() {
- const config = await db.collection('config').findOne({ key: 'features' })
- return NextResponse.json({
- features: {
- matchEnabled: config?.matchEnabled ?? true, // 默认开启
- },
- })
-}
-```
-
-### 小程序环境
-
-**配置位置**:`miniprogram/app.js`
-
-```javascript
-App({
- globalData: {
- matchEnabled: true, // ✅ 控制"找伙伴"功能
- // 其他配置...
- },
-
- onLaunch() {
- // ✅ 可以从服务器加载配置
- this.loadFeatureConfig()
- },
-
- async loadFeatureConfig() {
- try {
- const res = await wx.request({
- url: 'https://your-api.com/config',
- method: 'GET',
- })
-
- if (res.data && res.data.features) {
- this.globalData.matchEnabled = res.data.features.matchEnabled
-
- // ✅ 触发页面更新(如果需要)
- this.notifyConfigUpdate()
- }
- } catch (e) {
- console.error('Load config error:', e)
- }
- },
-
- notifyConfigUpdate() {
- // 通知所有页面配置已更新
- // 可以使用事件总线或全局状态管理
- },
-})
-```
-
----
-
-## 📱 页面集成
-
-### 在每个导航页面中使用
-
-**示例**:`newpp/src/pages/HomePage.jsx`
-
-```javascript
-import BottomNav from '../components/BottomNav'
-
-export default function HomePage() {
- return (
-
-
- {/* 页面内容 */}
-
-
- {/* ✅ 传入当前路径 */}
-
-
- )
-}
-```
-
-**其他页面**:
-- `ChaptersPage.jsx`: ``
-- `MatchPage.jsx`: ``
-- `MyPage.jsx`: ``
-
----
-
-## 🐛 已知问题与解决方案
-
-### 问题 1:页面切换时闪烁
-
-**症状**:使用 `wx.reLaunch` 时,页面会重新加载,可能出现白屏
-
-**原因**:`wx.reLaunch` 会关闭所有页面,然后打开新页面
-
-**解决方案**:
-
-1. **方案 A:优化页面加载速度**
- - 使用骨架屏
- - 预加载数据
- - 缓存页面状态
-
-2. **方案 B:使用页面栈管理(推荐)**
- ```javascript
- export function switchTab(path) {
- if (isMiniProgram()) {
- const pages = getCurrentPages()
- const currentPage = pages[pages.length - 1]
- const currentPath = '/' + currentPage.route.replace(/^pages\//, '').replace(/\/index$/, '')
-
- // ✅ 如果是相同页面,不跳转
- if (currentPath === path) {
- return
- }
-
- // ✅ 检查页面栈中是否已有目标页面
- const targetPageIndex = pages.findIndex((p) => {
- const pPath = '/' + p.route.replace(/^pages\//, '').replace(/\/index$/, '')
- return pPath === path
- })
-
- if (targetPageIndex >= 0) {
- // ✅ 如果已有,返回到该页面
- const delta = pages.length - 1 - targetPageIndex
- wx.navigateBack({ delta })
- } else {
- // ✅ 如果没有,使用 reLaunch
- wx.reLaunch({ url: toMpPath(path) })
- }
- } else {
- window.location.href = path === '/' ? 'index.html' : path.replace(/^\//, '') + '.html'
- }
- }
- ```
-
-3. **方案 C:保留部分页面栈**
- ```javascript
- export function switchTab(path) {
- if (isMiniProgram()) {
- const pages = getCurrentPages()
-
- // ✅ 如果栈中只有1个页面,使用 redirectTo
- if (pages.length === 1) {
- wx.redirectTo({ url: toMpPath(path) })
- } else {
- // ✅ 否则,返回到第一个页面,然后 redirectTo
- wx.navigateBack({ delta: pages.length - 1 })
- setTimeout(() => {
- wx.redirectTo({ url: toMpPath(path) })
- }, 100)
- }
- } else {
- window.location.href = path === '/' ? 'index.html' : path.replace(/^\//, '') + '.html'
- }
- }
- ```
-
-**推荐**:方案 B(检查页面栈)
-
----
-
-### 问题 2:配置更新不及时
-
-**症状**:修改 `app.globalData.matchEnabled` 后,导航栏不更新
-
-**原因**:组件已挂载,`useEffect` 只执行一次
-
-**解决方案**:
-
-1. **方案 A:监听配置变化**
- ```javascript
- useEffect(() => {
- if (isMiniProgram()) {
- const app = getApp()
-
- // ✅ 监听配置变化
- const checkConfig = () => {
- if (app && app.globalData) {
- setMatchEnabled(app.globalData.matchEnabled !== false)
- }
- }
-
- // ✅ 初始加载
- checkConfig()
-
- // ✅ 定时检查(或使用事件监听)
- const interval = setInterval(checkConfig, 1000)
-
- return () => clearInterval(interval)
- }
- }, [])
- ```
-
-2. **方案 B:使用全局状态管理**
- ```javascript
- // store/index.js
- const useStore = create((set) => ({
- matchEnabled: true,
- setMatchEnabled: (enabled) => set({ matchEnabled: enabled }),
- }))
-
- // BottomNav.jsx
- const { matchEnabled } = useStore()
- ```
-
-3. **方案 C:页面激活时刷新**
- ```javascript
- useEffect(() => {
- const handleShow = () => {
- // ✅ 页面显示时重新读取配置
- if (isMiniProgram()) {
- const app = getApp()
- if (app && app.globalData) {
- setMatchEnabled(app.globalData.matchEnabled !== false)
- }
- }
- }
-
- // ✅ 监听页面显示事件
- if (isMiniProgram() && typeof wx !== 'undefined') {
- wx.onAppShow?.(handleShow)
- }
-
- return () => {
- wx.offAppShow?.(handleShow)
- }
- }, [])
- ```
-
-**推荐**:方案 C(页面激活时刷新)
-
----
-
-### 问题 3:Web 和小程序跳转体验不一致
-
-**症状**:Web 使用 `location.href` 会刷新整个页面,小程序使用 `wx.reLaunch` 有过渡动画
-
-**原因**:Web 和小程序的路由机制不同
-
-**解决方案**:
-
-1. **Web 端使用 SPA 路由**(如果是 Next.js)
- ```javascript
- import { useRouter } from 'next/router'
-
- export function switchTab(path) {
- if (isMiniProgram()) {
- wx.reLaunch({ url: toMpPath(path) })
- } else {
- // ✅ 使用 Next.js 路由
- const router = useRouter()
- router.push(path)
- }
- }
- ```
-
-2. **小程序端优化过渡**
- ```javascript
- export function switchTab(path) {
- if (isMiniProgram()) {
- wx.reLaunch({
- url: toMpPath(path),
- success: () => {
- // ✅ 跳转成功
- },
- fail: (err) => {
- console.error('reLaunch fail:', err)
- // ✅ 降级到 navigateTo
- wx.navigateTo({ url: toMpPath(path) })
- },
- })
- } else {
- window.location.href = path === '/' ? 'index.html' : path.replace(/^\//, '') + '.html'
- }
- }
- ```
-
----
-
-## ✅ 测试清单
-
-### 功能测试
-
-- [ ] **配置加载**:小程序启动时正确读取 `matchEnabled`
-- [ ] **动态显示/隐藏**:
- - [ ] `matchEnabled: true` 时,显示"找伙伴" tab
- - [ ] `matchEnabled: false` 时,隐藏"找伙伴" tab
-- [ ] **页面跳转**:
- - [ ] 点击"首页" → 跳转到首页
- - [ ] 点击"目录" → 跳转到目录页
- - [ ] 点击"找伙伴" → 跳转到找伙伴页
- - [ ] 点击"我的" → 跳转到我的页
-- [ ] **激活态**:当前页 tab 高亮显示
-
-### 样式测试
-
-- [ ] **中间凸起按钮**:渐变色 + 阴影
-- [ ] **普通按钮**:灰色/高亮切换
-- [ ] **安全区适配**:底部留出安全区高度
-
-### 性能测试
-
-- [ ] **配置加载速度**:< 100ms
-- [ ] **页面跳转速度**:< 500ms
-- [ ] **内存占用**:无内存泄漏
-
----
-
-## 📊 对比总结
-
-| 方案 | 原生 tabBar | 自定义 tabBar | 自定义组件(当前) |
-|------|------------|--------------|------------------|
-| 动态控制 | ❌ | ⚠️ | ✅ |
-| 样式自由 | ❌ | ✅ | ✅ |
-| 配置复杂度 | 低 | 高 | 中 |
-| 跳转方式 | `wx.switchTab` | `wx.switchTab` | `wx.reLaunch` |
-| 页面栈 | 清空 | 清空 | 清空 |
-| 跨平台一致性 | ❌ | ⚠️ | ✅ |
-| 维护成本 | 低 | 高 | 中 |
-
-**结论**:自定义组件方案最适合当前需求
-
----
-
-## 🚀 后续优化
-
-### Priority P1(推荐)
-
-1. **优化页面跳转体验**
- - 实现方案 B(页面栈检查)
- - 避免不必要的页面重新加载
-
-2. **配置热更新**
- - 实现配置变化监听
- - 自动更新导航栏显示
-
-3. **添加骨架屏**
- - 减少页面切换时的白屏
- - 提升用户体验
-
-### Priority P2(可选)
-
-1. **图标优化**
- - 使用图片替换 emoji
- - 支持激活态和非激活态
-
-2. **动效优化**
- - 添加页面切换动画
- - 优化按钮点击反馈
-
-3. **埋点统计**
- - 记录 tab 点击次数
- - 分析用户行为
-
----
-
-## 📚 相关文档
-
-1. [小程序样式修复说明](./小程序样式修复说明.md)
-2. [小程序底部导航修复说明](./小程序底部导航修复说明.md)
-
----
-
-**总结**:使用自定义组件方案,完全控制导航栏的显示逻辑,满足根据 API 配置动态显示/隐藏"找伙伴"功能的需求。
diff --git a/开发文档/8、部署/订单管理商品显示优化.md b/开发文档/8、部署/订单管理商品显示优化.md
deleted file mode 100644
index 65b65506..00000000
--- a/开发文档/8、部署/订单管理商品显示优化.md
+++ /dev/null
@@ -1,508 +0,0 @@
-# 订单管理商品显示优化
-
-## 问题描述
-
-在后台管理的"交易中心-订单管理" tab 中,商品列显示不正确:
-
-**问题截图**:
-- 显示为"章节undefined"
-- 无法区分单章购买、全本购买、匹配次数购买
-- 缺少书名和章节详细信息
-
-**期望效果**:
-- 单章购买:显示"《底层逻辑》- 节标题",副标题显示章标题
-- 全本购买:显示"《底层逻辑》- 全本",副标题显示"全书解锁"
-- 匹配次数:显示"匹配次数购买",副标题显示"功能权益"
-
-## 数据结构分析
-
-### Orders 表结构
-
-```sql
-CREATE TABLE orders (
- id VARCHAR(50) PRIMARY KEY,
- order_sn VARCHAR(50) UNIQUE NOT NULL,
- user_id VARCHAR(50) NOT NULL,
- product_type ENUM('section', 'fullbook', 'match') NOT NULL,
- product_id VARCHAR(50), -- 章节ID(如 '1.1', '2.3')或书籍ID
- amount DECIMAL(10,2) NOT NULL,
- description VARCHAR(200),
- status ENUM('created', 'pending', 'paid', 'cancelled', 'refunded', 'expired'),
- referrer_id VARCHAR(50) NULL,
- referral_code VARCHAR(20) NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- -- ... 其他字段
-)
-```
-
-### Chapters 表结构
-
-```sql
-CREATE TABLE chapters (
- id VARCHAR(20) PRIMARY KEY, -- 章节ID,如 '1.1', 'preface'
- part_id VARCHAR(20) NOT NULL,
- part_title VARCHAR(100) NOT NULL, -- 篇标题
- chapter_id VARCHAR(20) NOT NULL,
- chapter_title VARCHAR(200) NOT NULL, -- 章标题
- section_title VARCHAR(200) NOT NULL, -- 节标题
- content LONGTEXT NOT NULL,
- word_count INT DEFAULT 0,
- is_free BOOLEAN DEFAULT FALSE,
- price DECIMAL(10,2) DEFAULT 1.00,
- -- ... 其他字段
-)
-```
-
-### 购买类型说明
-
-| product_type | product_id | 说明 | 显示格式 |
-|-------------|-----------|------|---------|
-| `section` | `'1.1'`, `'2.3'` | 单章购买 | 《底层逻辑》- 节标题
章标题 |
-| `fullbook` | `'book-1'` 或 NULL | 全本购买 | 《底层逻辑》- 全本
全书解锁 |
-| `match` | NULL | 匹配次数 | 匹配次数购买
功能权益 |
-
-## 解决方案
-
-### 1. 修改订单 API(`app/api/orders/route.ts`)
-
-#### 1.1 更新数据转换函数
-
-**修改前**:
-```typescript
-function rowToOrder(row: Record) {
- return {
- id: row.id,
- productType: row.product_type,
- productId: row.product_id,
- amount: parseFloat(row.amount as string) || 0,
- // ... 其他字段
- userNickname: row.user_nickname ?? null,
- userAvatar: row.user_avatar ?? null,
- }
-}
-```
-
-**修改后**:
-```typescript
-function rowToOrder(row: Record) {
- return {
- id: row.id,
- productType: row.product_type,
- productId: row.product_id,
- amount: parseFloat(row.amount as string) || 0,
- // ... 其他字段
- userNickname: row.user_nickname ?? null,
- userAvatar: row.user_avatar ?? null,
- // 新增:章节信息
- bookName: '《底层逻辑》', // 书名(固定)
- chapterTitle: row.chapter_title ?? null, // 章标题
- sectionTitle: row.section_title ?? null, // 节标题
- }
-}
-```
-
-#### 1.2 更新 SQL 查询
-
-**修改前**(仅 JOIN users 表):
-```sql
-SELECT o.*, u.nickname as user_nickname, u.avatar as user_avatar
-FROM orders o
-LEFT JOIN users u ON o.user_id = u.id
-ORDER BY o.created_at DESC
-```
-
-**修改后**(JOIN users + chapters 表):
-```sql
-SELECT
- o.*,
- u.nickname as user_nickname,
- u.avatar as user_avatar,
- c.chapter_title,
- c.section_title
-FROM orders o
-LEFT JOIN users u ON o.user_id = u.id
-LEFT JOIN chapters c ON o.product_id = c.id AND o.product_type = 'section'
-ORDER BY o.created_at DESC
-```
-
-**关键点**:
-- `LEFT JOIN chapters c` 仅在 `product_type = 'section'` 时关联
-- 这样全本购买和匹配次数购买的 `chapter_title` 和 `section_title` 为 NULL
-- 不影响查询性能
-
-### 2. 修改前端类型定义(`app/admin/distribution/page.tsx`)
-
-**修改前**:
-```typescript
-interface Order {
- id: string
- userId: string
- type: 'section' | 'fullbook' | 'match'
- sectionId?: string
- sectionTitle?: string
- amount: number
- status: 'pending' | 'completed' | 'failed'
- // ... 其他字段
-}
-```
-
-**修改后**:
-```typescript
-interface Order {
- id: string
- userId: string
- productType: 'section' | 'fullbook' | 'match' // API 返回的字段名
- type?: 'section' | 'fullbook' | 'match' // 兼容旧字段名
- productId?: string
- sectionId?: string // 兼容旧字段名
- bookName?: string // 书名
- chapterTitle?: string // 章标题
- sectionTitle?: string // 节标题
- amount: number
- status: 'pending' | 'completed' | 'failed' | 'paid' | 'created'
- // ... 其他字段
-}
-```
-
-### 3. 修改前端显示逻辑
-
-#### 3.1 商品信息显示
-
-**修改前**(显示"章节undefined"):
-```tsx
-
- {order.type === 'fullbook' ? '整本购买' :
- order.type === 'match' ? '匹配次数' :
- order.sectionTitle || `章节${order.sectionId}`}
-
-
- {order.type === 'fullbook' ? '全书' :
- order.type === 'match' ? '功能' : '单章'}
-
-```
-
-**修改后**(显示完整商品信息):
-```tsx
-
- {(() => {
- const type = order.productType || order.type
- if (type === 'fullbook') {
- return `${order.bookName || '《底层逻辑》'} - 全本`
- } else if (type === 'match') {
- return '匹配次数购买'
- } else {
- // section - 单章购买
- return `${order.bookName || '《底层逻辑》'} - ${order.sectionTitle || order.chapterTitle || `章节${order.productId || order.sectionId || ''}`}`
- }
- })()}
-
-
- {(() => {
- const type = order.productType || order.type
- if (type === 'fullbook') {
- return '全书解锁'
- } else if (type === 'match') {
- return '功能权益'
- } else {
- return order.chapterTitle || '单章购买'
- }
- })()}
-
-```
-
-**显示效果**:
-
-| 购买类型 | 主标题 | 副标题 |
-|---------|--------|--------|
-| 单章购买 | 《底层逻辑》- 荷包:电动车出租的被动收入模式 | 第1章|人与人之间的底层逻辑 |
-| 全本购买 | 《底层逻辑》- 全本 | 全书解锁 |
-| 匹配次数 | 匹配次数购买 | 功能权益 |
-
-#### 3.2 搜索功能增强
-
-**修改前**:
-```typescript
-if (searchTerm) {
- const term = searchTerm.toLowerCase()
- return (
- order.id?.toLowerCase().includes(term) ||
- order.userNickname?.toLowerCase().includes(term) ||
- order.userPhone?.includes(term) ||
- order.sectionTitle?.toLowerCase().includes(term) ||
- (order.referrerCode && order.referrerCode.toLowerCase().includes(term))
- )
-}
-```
-
-**修改后**(增加书名和章标题搜索):
-```typescript
-if (searchTerm) {
- const term = searchTerm.toLowerCase()
- return (
- order.id?.toLowerCase().includes(term) ||
- order.userNickname?.toLowerCase().includes(term) ||
- order.userPhone?.includes(term) ||
- order.sectionTitle?.toLowerCase().includes(term) ||
- order.chapterTitle?.toLowerCase().includes(term) ||
- order.bookName?.toLowerCase().includes(term) ||
- (order.referrerCode && order.referrerCode.toLowerCase().includes(term)) ||
- (order.referrerNickname && order.referrerNickname.toLowerCase().includes(term))
- )
-}
-```
-
-**搜索能力**:
-- 可搜索订单 ID
-- 可搜索用户昵称和手机号
-- 可搜索书名、章标题、节标题
-- 可搜索推荐人信息
-
-## 扩展性设计
-
-### 多本书支持
-
-当前书名是硬编码的 `'《底层逻辑》'`。如果未来需要支持多本书,可以:
-
-#### 方案1:在订单表增加 book_id 字段
-
-```sql
-ALTER TABLE orders ADD COLUMN book_id VARCHAR(20);
-```
-
-#### 方案2:创建 books 表
-
-```sql
-CREATE TABLE books (
- id VARCHAR(20) PRIMARY KEY,
- title VARCHAR(100) NOT NULL, -- 如《底层逻辑》
- author VARCHAR(50),
- price DECIMAL(10,2),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- 修改 chapters 表增加 book_id
-ALTER TABLE chapters ADD COLUMN book_id VARCHAR(20);
-ALTER TABLE chapters ADD INDEX idx_book_id (book_id);
-```
-
-然后修改 API 查询:
-```sql
-SELECT
- o.*,
- u.nickname as user_nickname,
- u.avatar as user_avatar,
- c.chapter_title,
- c.section_title,
- b.title as book_name
-FROM orders o
-LEFT JOIN users u ON o.user_id = u.id
-LEFT JOIN chapters c ON o.product_id = c.id AND o.product_type = 'section'
-LEFT JOIN books b ON c.book_id = b.id
-ORDER BY o.created_at DESC
-```
-
-### 章节ID规范
-
-建议统一章节 ID 格式:
-
-| 类型 | ID 格式 | 示例 |
-|-----|---------|------|
-| 序言 | `preface` | `preface` |
-| 正文 | `{章}.{节}` | `1.1`, `2.3`, `9.14` |
-| 尾声 | `epilogue` | `epilogue` |
-| 附录 | `appendix-{序号}` | `appendix-1`, `appendix-2` |
-
-## 修改文件清单
-
-### 后端 API
-
-1. **`app/api/orders/route.ts`**
- - 修改 `rowToOrder()` 函数,增加 `bookName`, `chapterTitle`, `sectionTitle` 字段
- - 修改 SQL 查询,JOIN `chapters` 表获取章节信息
- - 行号:12-32, 44-59
-
-### 前端页面
-
-2. **`app/admin/distribution/page.tsx`**
- - 更新 `Order` 接口定义,增加新字段
- - 修改商品信息显示逻辑,根据 `productType` 显示不同格式
- - 增强搜索功能,支持搜索书名、章标题、节标题
- - 行号:78-96, 636-647, 659-685
-
-## 验证步骤
-
-### 1. 重启服务
-
-```powershell
-pm2 restart mycontent
-# 或
-npm run dev
-```
-
-### 2. 访问页面
-
-打开浏览器:`http://localhost:3006/admin/distribution`
-
-### 3. 测试订单管理 Tab
-
-1. **查看商品显示**:
- - 点击"订单管理" tab
- - 检查商品列是否正确显示:
- - 单章:《底层逻辑》- 节标题(副标题:章标题)
- - 全本:《底层逻辑》- 全本(副标题:全书解锁)
- - 匹配:匹配次数购买(副标题:功能权益)
-
-2. **测试搜索**:
- - 搜索"底层逻辑",应该能找到所有订单
- - 搜索章节名(如"荷包"),应该能找到对应订单
- - 搜索用户昵称,应该能找到对应订单
-
-3. **检查不同订单类型**:
- - 如果有单章购买订单,确认显示完整的"书名 - 节标题"
- - 如果有全本购买订单,确认显示"《底层逻辑》- 全本"
- - 如果有匹配次数订单,确认显示"匹配次数购买"
-
-### 4. 查看 API 响应
-
-打开 DevTools → Network 标签 → 找到 `/api/orders` 请求:
-
-```json
-{
- "success": true,
- "orders": [
- {
- "id": "ORDER_123",
- "productType": "section",
- "productId": "1.1",
- "bookName": "《底层逻辑》",
- "chapterTitle": "第1章|人与人之间的底层逻辑",
- "sectionTitle": "1.1 荷包:电动车出租的被动收入模式",
- "amount": 1.00,
- "userNickname": "张三",
- "status": "paid"
- },
- {
- "id": "ORDER_456",
- "productType": "fullbook",
- "productId": null,
- "bookName": "《底层逻辑》",
- "chapterTitle": null,
- "sectionTitle": null,
- "amount": 99.00,
- "userNickname": "李四",
- "status": "paid"
- }
- ]
-}
-```
-
-### 5. 数据库验证
-
-```sql
--- 查看订单和章节信息
-SELECT
- o.id,
- o.product_type,
- o.product_id,
- o.amount,
- c.chapter_title,
- c.section_title,
- u.nickname
-FROM orders o
-LEFT JOIN users u ON o.user_id = u.id
-LEFT JOIN chapters c ON o.product_id = c.id AND o.product_type = 'section'
-WHERE o.status = 'paid'
-ORDER BY o.created_at DESC
-LIMIT 10;
-
--- 验证章节数据
-SELECT
- id,
- chapter_title,
- section_title,
- is_free,
- price
-FROM chapters
-WHERE id IN ('1.1', '1.2', '2.1')
-LIMIT 5;
-```
-
-## 常见问题
-
-### Q1: 为什么只对 product_type='section' 进行 JOIN?
-
-**答**:
-- 全本购买和匹配次数购买不需要章节信息
-- `LEFT JOIN` 的条件 `o.product_type = 'section'` 确保只有单章购买才查询 chapters 表
-- 这样可以提高查询性能,避免不必要的 JOIN
-
-### Q2: 如果 chapters 表没有某个章节数据怎么办?
-
-**答**:
-显示逻辑有兜底机制:
-```typescript
-order.sectionTitle || order.chapterTitle || `章节${order.productId}`
-```
-- 优先显示节标题
-- 其次显示章标题
-- 最后显示"章节 + ID"
-
-### Q3: 全本购买的 product_id 是什么?
-
-**答**:
-全本购买的 `product_id` 可能是:
-- `NULL`(早期订单)
-- `'book-1'`(如果有书籍ID)
-- 固定值如 `'fullbook'`
-
-建议在创建订单时统一为 `NULL` 或 `'fullbook'`。
-
-### Q4: 如何添加新的书籍?
-
-**答**:
-1. 在 `books` 表中新增书籍记录(如果使用了多本书支持)
-2. 在 `chapters` 表中新增章节,关联 `book_id`
-3. 修改 API 的 `rowToOrder()` 函数,从 JOIN 的 books 表读取书名
-4. 前端显示逻辑无需修改(自动读取 `order.bookName`)
-
-## 性能优化
-
-### 索引优化
-
-为 JOIN 查询添加索引:
-
-```sql
--- orders 表索引
-CREATE INDEX idx_orders_product ON orders(product_type, product_id);
-CREATE INDEX idx_orders_user ON orders(user_id);
-
--- chapters 表索引
-CREATE INDEX idx_chapters_id ON chapters(id);
-
--- users 表索引(如果没有)
-CREATE INDEX idx_users_id ON users(id);
-```
-
-### 查询优化
-
-如果订单量很大(>10万),考虑:
-1. 分页查询:`LIMIT 100 OFFSET 0`
-2. 时间范围过滤:`WHERE o.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)`
-3. 缓存热门查询结果
-
-## 相关文件
-
-- **订单 API**:`app/api/orders/route.ts`
-- **交易中心页面**:`app/admin/distribution/page.tsx`
-- **数据库初始化**:`lib/db.ts`
-- **书籍数据**:`lib/book-data.ts`
-
-## 版本信息
-
-- **修改时间**:2026-02-04
-- **修改内容**:
- 1. 修改订单 API,JOIN chapters 表获取章节信息
- 2. 返回 bookName, chapterTitle, sectionTitle 字段
- 3. 优化前端显示逻辑,区分单章/全本/匹配次数
- 4. 增强搜索功能,支持搜索书名和章节标题
- 5. 设计多本书扩展方案
diff --git a/开发文档/8、部署/订单管理数据类型错误修复.md b/开发文档/8、部署/订单管理数据类型错误修复.md
deleted file mode 100644
index 2dd3e2a3..00000000
--- a/开发文档/8、部署/订单管理数据类型错误修复.md
+++ /dev/null
@@ -1,411 +0,0 @@
-# 订单管理数据类型错误修复
-
-## 问题描述
-
-在后台管理的"交易中心-订单管理" tab 中,点击时出现以下错误:
-
-### 错误 1:JSON 解析错误
-```
-SyntaxError: Unexpected token '<', ") {
- return {
- id: row.id,
- amount: row.amount, // 可能是字符串 "99.00"
- // ... 其他字段
- }
-}
-```
-
-**修改后**:
-```typescript
-function rowToOrder(row: Record) {
- return {
- id: row.id,
- amount: parseFloat(row.amount as string) || 0, // 转换为数字
- // ... 其他字段
- }
-}
-```
-
-### 2. 增强前端错误处理
-
-**文件**:`app/admin/distribution/page.tsx`
-
-#### 2.1 概览数据加载
-
-**修改前**:
-```typescript
-const overviewRes = await fetch('/api/admin/distribution/overview')
-const overviewData = await overviewRes.json() // 可能抛出异常
-if (overviewData.success) {
- setOverview(overviewData.overview)
-}
-```
-
-**修改后**:
-```typescript
-try {
- const overviewRes = await fetch('/api/admin/distribution/overview')
- if (overviewRes.ok) { // 检查 HTTP 状态
- const overviewData = await overviewRes.json()
- if (overviewData.success) {
- setOverview(overviewData.overview)
- }
- }
-} catch (overviewError) {
- console.error('[Admin] 概览接口异常:', overviewError)
-}
-```
-
-#### 2.2 订单数据加载
-
-**修改前**:
-```typescript
-const ordersRes = await fetch('/api/orders')
-const ordersData = await ordersRes.json() // 可能抛出异常
-if (ordersData.success) {
- const enrichedOrders = ordersData.orders.map((order: Order) => ({
- ...order,
- // 假设 amount 是数字
- }))
- setOrders(enrichedOrders)
-}
-```
-
-**修改后**:
-```typescript
-try {
- const ordersRes = await fetch('/api/orders')
- if (!ordersRes.ok) {
- console.error('[Admin] 订单接口错误:', ordersRes.status)
- setOrders([])
- } else {
- const ordersData = await ordersRes.json()
- if (ordersData.success && ordersData.orders) {
- const enrichedOrders = ordersData.orders.map((order: Order) => ({
- ...order,
- amount: parseFloat(order.amount as any) || 0, // 双重保险
- // ... 其他字段
- }))
- setOrders(enrichedOrders)
- } else {
- setOrders([])
- }
- }
-} catch (orderError) {
- console.error('[Admin] 加载订单数据失败:', orderError)
- setOrders([])
-}
-```
-
-#### 2.3 其他数据加载
-
-对用户数据、绑定数据、提现数据也采用同样的错误处理模式:
-
-```typescript
-try {
- const res = await fetch('/api/...')
- if (res.ok) {
- const data = await res.json()
- // 处理数据
- } else {
- // 设置为空数组
- }
-} catch (error) {
- console.error('加载失败:', error)
- // 设置为空数组
-}
-```
-
-### 3. 增强前端渲染安全性
-
-**文件**:`app/admin/distribution/page.tsx`
-
-#### 3.1 订单金额显示
-
-**修改前**:
-```typescript
-¥{(order.amount || 0).toFixed(2)}
-// 如果 amount 是字符串,会报错
-```
-
-**修改后**:
-```typescript
-¥{typeof order.amount === 'number'
- ? order.amount.toFixed(2)
- : parseFloat(order.amount || '0').toFixed(2)}
-// 检查类型,确保安全
-```
-
-#### 3.2 推荐人收益显示
-
-**修改前**:
-```typescript
-{order.referrerEarnings ? `¥${order.referrerEarnings.toFixed(2)}` : '-'}
-// 如果是字符串,会报错
-```
-
-**修改后**:
-```typescript
-{order.referrerEarnings
- ? `¥${(typeof order.referrerEarnings === 'number'
- ? order.referrerEarnings
- : parseFloat(order.referrerEarnings)).toFixed(2)}`
- : '-'}
-```
-
-## 修改文件清单
-
-### 后端 API
-
-1. **`app/api/orders/route.ts`**
- - 修改 `rowToOrder()` 函数
- - 将 `amount` 字段从字符串转换为数字
- - 行号:20
-
-### 前端页面
-
-2. **`app/admin/distribution/page.tsx`**
- - 增强概览数据加载的错误处理(行号:117-129)
- - 增强用户数据加载的错误处理(行号:131-140)
- - 增强订单数据加载的错误处理(行号:142-167)
- - 增强绑定数据加载的错误处理(行号:169-179)
- - 增强提现数据加载的错误处理(行号:181-191)
- - 修复订单金额显示的类型安全(行号:667)
- - 修复推荐人收益显示的类型安全(行号:692-696)
-
-## 防御性编程原则
-
-本次修复遵循以下防御性编程原则:
-
-### 1. 多层防御
-
-- **数据库层**:查询结果可能是字符串
-- **API 层**:转换为数字类型
-- **前端加载层**:再次确保数字类型
-- **前端渲染层**:类型检查 + 类型转换
-
-### 2. 优雅降级
-
-- API 调用失败时,设置为空数组 `[]`,而不是中断整个加载流程
-- 单个 API 失败不影响其他 API 的加载
-- 页面始终可用,只是某些 tab 可能没有数据
-
-### 3. 详细日志
-
-每个错误都有清晰的日志输出:
-```typescript
-console.error('[Admin] 订单接口错误:', ordersRes.status)
-console.error('[Admin] 加载订单数据失败:', orderError)
-```
-
-方便定位问题源头。
-
-### 4. 类型安全
-
-渲染时不假设数据类型:
-```typescript
-typeof value === 'number' ? value.toFixed(2) : parseFloat(value).toFixed(2)
-```
-
-## 验证步骤
-
-### 1. 重启服务
-
-```powershell
-pm2 restart mycontent
-# 或
-npm run dev
-```
-
-### 2. 访问页面
-
-打开浏览器:`http://localhost:3006/admin/distribution`
-
-### 3. 测试订单管理 Tab
-
-1. **查看订单列表**:
- - 点击"订单管理" tab
- - 应该能看到订单列表
- - 金额显示正常(如 `¥99.00`)
- - 不应该有 `toFixed is not a function` 错误
-
-2. **测试刷新**:
- - 点击页面顶部的"刷新数据"按钮
- - 查看浏览器 DevTools Console
- - 不应该有 JSON 解析错误
- - 即使某个 API 失败,其他 tab 仍然可用
-
-3. **查看其他 Tab**:
- - 数据概览 - 应该正常显示统计数据
- - 绑定管理 - 应该正常显示绑定关系
- - 提现审核 - 应该正常显示提现记录
-
-### 4. 查看网络请求
-
-打开 DevTools → Network 标签:
-- `/api/orders` 应该返回 200 OK
-- 响应体应该是正确的 JSON
-- `amount` 字段应该是数字类型(在 JSON 中显示为 `99.00` 而不是 `"99.00"`)
-
-### 5. 查看控制台
-
-服务器控制台应该输出:
-```
-[Admin] 概览数据加载成功: { todayClicks: 0, ... }
-```
-
-如果某个 API 失败:
-```
-[Admin] 订单接口错误: 500 Internal Server Error
-[Admin] 加载订单数据失败: SyntaxError: ...
-```
-
-浏览器控制台也会有对应的错误日志,但页面不会崩溃。
-
-## 数据库验证
-
-检查订单表的数据类型:
-
-```sql
--- 查看订单表结构
-DESC orders;
-
--- 确认 amount 字段类型应该是 DECIMAL
--- 输出示例:
--- amount | decimal(10,2) | YES | | NULL | |
-
--- 查看订单数据
-SELECT id, user_id, amount, status, created_at
-FROM orders
-ORDER BY created_at DESC
-LIMIT 10;
-
--- 测试类型转换
-SELECT
- amount,
- CAST(amount AS DECIMAL(10,2)) as decimal_amount,
- amount + 0 as numeric_amount
-FROM orders
-LIMIT 5;
-```
-
-## 常见问题
-
-### Q1: 为什么要多层转换?
-
-**答**:
-- 不同环境、不同 MySQL 驱动可能返回不同的类型
-- API 层转换保证了大部分情况的正确性
-- 前端再次转换作为"安全网",防止极端情况
-- 渲染层的类型检查防止运行时错误
-
-### Q2: 其他数字字段是否也有这个问题?
-
-**答**:
-可能有!建议对所有来自数据库的 `DECIMAL`、`FLOAT`、`DOUBLE` 类型字段都做转换:
-- `referrerEarnings`
-- `earnings`
-- `withdrawn_earnings`
-- `pending_earnings`
-
-可以在相应的 API 的 `rowToXxx()` 函数中统一处理。
-
-### Q3: 为什么不修改数据库字段类型?
-
-**答**:
-- `DECIMAL(10,2)` 是存储货币金额的标准类型
-- 保证精度,避免浮点数误差(如 0.1 + 0.2 ≠ 0.3)
-- 问题在于查询结果的类型转换,而不是存储类型
-
-### Q4: 如何避免类似问题?
-
-**答**:
-1. **API 层统一转换**:在所有查询结果转换函数中明确类型转换
-2. **TypeScript 类型定义**:使用严格的类型定义
-3. **单元测试**:测试 API 返回值的类型
-4. **前端防御**:渲染前做类型检查
-
-## 相关文件
-
-- **订单 API**:`app/api/orders/route.ts`
-- **交易中心页面**:`app/admin/distribution/page.tsx`
-- **类型定义**:在 `page.tsx` 文件顶部(`interface Order`)
-
-## 后续优化建议
-
-1. **统一类型转换工具**:
- ```typescript
- // lib/utils/db-converter.ts
- export function toNumber(value: unknown, defaultValue = 0): number {
- if (typeof value === 'number') return value
- if (typeof value === 'string') {
- const num = parseFloat(value)
- return isNaN(num) ? defaultValue : num
- }
- return defaultValue
- }
- ```
-
-2. **API 响应类型验证**:
- 使用 Zod 或类似库验证 API 响应的类型
-
-3. **全局错误边界增强**:
- 提供更友好的错误提示,而不是显示技术错误
-
-4. **监控告警**:
- 记录 API 调用失败次数,超过阈值时告警
-
-## 版本信息
-
-- **修复时间**:2026-02-04
-- **修复内容**:
- 1. 修复 `/api/orders` 中 `amount` 字段的类型转换
- 2. 增强交易中心页面的错误处理(5 个 API 调用)
- 3. 增强前端渲染的类型安全(2 处 `.toFixed()` 调用)
- 4. 采用"多层防御 + 优雅降级"策略
diff --git a/开发文档/8、部署/部署总览.md b/开发文档/8、部署/部署总览.md
index 2ae1e6f2..47a4f137 100644
--- a/开发文档/8、部署/部署总览.md
+++ b/开发文档/8、部署/部署总览.md
@@ -1,8 +1,6 @@
# 部署总览
-**我是卡若。**
-
-本文档是 8、部署 的入口,只列核心文档;历史修复与优化说明见 [修复与优化记录](修复与优化记录/) 子目录。
+> **当前生产架构**:soul-api(Go + Gin)+ soul-admin(React)+ miniprogram。next-project 仅预览。
---
@@ -10,13 +8,9 @@
| 文档 | 说明 |
|------|------|
-| [本地运行](本地运行.md) | 只写书不跑站 / 跑 Next 站点的本地步骤 |
-| [Next.js宝塔部署方案](Next.js宝塔部署方案.md) | 宝塔 + Node/PM2 + Nginx,deploy/devlop 模式 |
-| [Next.js自动化部署流程](Next.js自动化部署流程.md) | 自动化构建与发布 |
| [新分销逻辑-部署步骤](新分销逻辑-部署步骤.md) | 新分销上线:备份、迁移、部署、验证 |
| [新分销逻辑-宝塔操作清单](新分销逻辑-宝塔操作清单.md) | 宝塔侧具体操作 |
-| [自动同步与分支策略](自动同步与分支策略.md) | 代码同步与分支策略 |
-| [Standalone模式说明](Standalone模式说明.md) | Next.js standalone 输出说明 |
+| [运行与部署](运行与部署.md) | 运行与部署说明 |
---
@@ -27,7 +21,6 @@
| [MCP-MySQL配置说明](MCP-MySQL配置说明.md) | MCP 连接 MySQL |
| [Soul-MySQL-MCP配置说明](Soul-MySQL-MCP配置说明.md) | 本项目 MySQL MCP 配置 |
| [API接入说明](API接入说明.md) | 外部/小程序接入 API |
-| [宝塔配置检查说明](宝塔配置检查说明.md) | 宝塔环境检查 |
| [宝塔面板配置订单同步定时任务](宝塔面板配置订单同步定时任务.md) | 订单同步定时任务 |
---
@@ -42,7 +35,7 @@
| [章节阅读付费标准流程设计](章节阅读付费标准流程设计.md) | 阅读页付费流程 |
| [阅读页标准流程改造说明](阅读页标准流程改造说明.md) | 阅读页改造要点 |
| [支付接口清单](支付接口清单.md) | 支付相关接口列表 |
-| [提现功能完整技术文档](../提现功能完整技术文档.md) | 微信支付商家转账到零钱 API 集成(签名、加解密、代码) |
+| [提现功能完整技术文档](../../soul-api/提现功能完整技术文档.md) | 微信支付商家转账到零钱 API 集成(soul-api 目录) |
---
@@ -53,14 +46,3 @@
| [代码逻辑和数据库最终检查清单](代码逻辑和数据库最终检查清单.md) | 上线前检查项 |
| [佣金计算逻辑检查](佣金计算逻辑检查.md) | 佣金逻辑校验 |
| [佣金问题-快速诊断和修复](佣金问题-快速诊断和修复.md) | 佣金问题排查 |
-
----
-
-## 五、修复与优化记录(历史说明,按需查阅)
-
-- **分销中心**:[loading 优化 v2](分销中心loading优化说明-v2.md)、[接口优化方案](分销中心接口优化方案.md)、[接口优化实施记录](分销中心接口优化实施记录.md)、[数据库连接错误修复](分销中心数据库连接错误修复.md)、[用户列表数据对接](分销中心用户列表数据对接说明.md)、[设置功能说明](分销中心设置功能说明.md)
-- **提现与佣金**:[可提现金额计算修复](可提现金额计算修复.md)、[累计佣金计算修复](累计佣金计算修复说明.md)、[提现卡片数据优化](提现卡片数据优化说明.md)、[提现双向校验](提现双向校验实现.md)、[提现审核流程优化](提现审核流程优化.md)、[提现按钮状态修复](提现按钮状态修复说明.md)、[提现按钮逻辑修正](提现按钮逻辑修正.md)、[提现接口逻辑修正](提现接口逻辑修正.md)、[提现接口数据查询错误修复](提现接口数据查询错误修复.md)、[提现记录获取失败诊断指南](提现记录获取失败诊断指南.md)、[后台提现审核](后台提现审核功能完善说明.md)、[后台提现审核数据对接](后台提现审核数据对接.md)、[后台提现审核测试指南](后台提现审核-快速测试指南.md)
-- **交易中心与订单**:[交易中心 Tab 按需加载优化](交易中心Tab按需加载优化.md)、[订单管理商品显示优化](订单管理商品显示优化.md)、[订单管理数据类型错误修复](订单管理数据类型错误修复.md)
-- **小程序**:[头像上传优化](小程序头像上传优化说明.md)、[提现金额对接](小程序提现金额对接说明.md)、[昵称自动填充](小程序昵称自动填充说明.md)、[调整说明](小程序调整说明.md)
-- **推广与后台**:[推广设置完整修复清单](推广设置功能-完整修复清单.md)、[推广设置测试清单](推广设置页面测试清单.md)、[管理端推广配置与小程序对接](管理端推广配置与小程序对接说明.md)、[后台订单显示优化](后台订单显示优化说明.md)
-- **其他**:[删除 referred_by](删除referred_by字段说明.md)、[绑定关系存储方案分析](绑定关系存储方案分析.md)、[自动解绑 API](自动解绑API配置说明.md)、[自定义导航组件](自定义导航组件方案.md)、[收益明细优化](收益明细优化说明.md)、[新分销代码修改总结](新分销逻辑-代码修改总结.md)、[章节阅读页集成示例](章节阅读页集成示例.md)
diff --git a/开发文档/README.md b/开发文档/README.md
new file mode 100644
index 00000000..1f59b173
--- /dev/null
+++ b/开发文档/README.md
@@ -0,0 +1,57 @@
+# Soul 创业派对 - 开发文档索引
+
+> 小橙整理 · 2026-02-26
+
+## 当前项目架构
+
+| 子项目 | 目录 | 用途 |
+|--------|------|------|
+| soul-api | soul-api/ | Go + Gin + GORM 接口服务(生产后端) |
+| soul-admin | soul-admin/ | React 管理后台 |
+| miniprogram | miniprogram/ | 微信原生小程序 |
+| next-project | next-project/ | 仅预览,非生产 |
+
+---
+
+## 文档导航
+
+### 需求与项目管理
+
+| 文档 | 说明 |
+|------|------|
+| [1、需求/需求汇总](1、需求/需求汇总.md) | 需求清单、业务需求 |
+| [10、项目管理/项目落地推进表](10、项目管理/项目落地推进表.md) | 里程碑、永平落地 |
+| [10、项目管理/运营与变更](10、项目管理/运营与变更.md) | 近期讨论、变更记录 |
+
+### 架构与规范
+
+| 文档 | 说明 |
+|------|------|
+| [2、架构/系统与技术](2、架构/系统与技术.md) | 系统与技术栈 |
+| [2、架构/链路与变现](2、架构/链路与变现.md) | 业务链路 |
+| [4、前端/前端开发规范](4、前端/前端开发规范.md) | 前端规范 |
+| [6、后端/后端开发规范](6、后端/后端开发规范.md) | 后端规范 |
+
+### 接口与部署
+
+| 文档 | 说明 |
+|------|------|
+| [5、接口/API接口完整文档](5、接口/API接口完整文档.md) | API 完整文档 |
+| [8、部署/部署总览](8、部署/部署总览.md) | 部署入口、分销与流程 |
+
+### 临时需求池
+
+| 文档 | 说明 |
+|------|------|
+| 临时需求池/需求分析-产品经理视角 | 需求分析 |
+| 临时需求池/分润需求-技术分析 | 会员分润差异化技术方案 |
+
+---
+
+## 已移除文档(2026-02-26)
+
+- Prisma ORM 迁移相关(3 份)— 项目已迁至 Go/GORM
+- Next.js 宝塔部署方案、Standalone 模式 — next-project 仅预览
+- 拆解计划 — 已完成,soul-admin 与 soul-api 已落地
+- 近3天更新文档(5 份)— 已合并至 运营与变更 第六部分
+- 8、部署 历史修复说明(约 35 份)— 一次性修复记录,已无参考价值
diff --git a/开发文档/小程序管理/SKILL.md b/开发文档/小程序管理/SKILL.md
new file mode 100644
index 00000000..4645ec85
--- /dev/null
+++ b/开发文档/小程序管理/SKILL.md
@@ -0,0 +1,1797 @@
+# 小程序管理技能 v3.0
+
+> 📅 创建日期:2026-01-25
+> 📅 更新日期:2026-01-25(v3.0 全能版)
+> 📋 通过微信开放平台API完整管理小程序的全生命周期:申请、认证、开发、审核、发布、运营
+> 🚀 支持多小程序管理、一键部署、自动认证检查、汇总报告
+
+---
+
+## 🎯 能力全景图
+
+```
+╔══════════════════════════════════════════════════════════════════════════════╗
+║ 小程序管理技能 v3.0 能力全景图 ║
+╠══════════════════════════════════════════════════════════════════════════════╣
+║ ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ 🔧 工具整合层 │ ║
+║ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ ║
+║ │ │ 微信开发者 │ │ miniprogram │ │ 开放平台 │ │ GitHub │ │ ║
+║ │ │ 工具 CLI │ │ -ci (npm) │ │ API │ │ Actions │ │ ║
+║ │ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ │ ║
+║ ▼ ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ 📦 核心功能层 │ ║
+║ │ │ ║
+║ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ ║
+║ │ │ 多小程序 │ │ 项目 │ │ 一键 │ │ 汇总 │ │ ║
+║ │ │ 管理 │ │ 检查 │ │ 部署 │ │ 报告 │ │ ║
+║ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ ║
+║ │ │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ │ ║
+║ ▼ ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ 🚀 部署流程 │ ║
+║ │ │ ║
+║ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ ║
+║ │ │ 检查 │ → │ 编译 │ → │ 上传 │ → │ 提审 │ → │ 审核 │ → │ 发布 │ │ ║
+║ │ │check │ │build │ │upload│ │audit │ │wait │ │release│ │ ║
+║ │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │ ║
+║ │ │ │ │ │ │ │ │ ║
+║ │ ▼ ▼ ▼ ▼ ▼ ▼ │ ║
+║ │ 自动化 自动化 自动化 自动化 等待 手动/自动 │ ║
+║ │ 1-7天 │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ ║
+╚══════════════════════════════════════════════════════════════════════════════╝
+```
+
+---
+
+## 📊 命令速查表
+
+| 命令 | 说明 | 示例 |
+|------|------|------|
+| `mp_full.py report` | 生成所有小程序汇总报告 | `python3 mp_full.py report` |
+| `mp_full.py check` | 检查项目问题 | `python3 mp_full.py check soul-party` |
+| `mp_full.py auto` | 全自动部署(上传+提审) | `python3 mp_full.py auto soul-party` |
+| `mp_deploy.py list` | 列出所有小程序 | `python3 mp_deploy.py list` |
+| `mp_deploy.py add` | 添加新小程序 | `python3 mp_deploy.py add` |
+| `mp_deploy.py cert-status` | 查询认证状态 | `python3 mp_deploy.py cert-status soul-party` |
+| `mp_deploy.py cert-done` | 标记认证完成 | `python3 mp_deploy.py cert-done soul-party` |
+| `mp_deploy.py release` | 发布上线 | `python3 mp_deploy.py release soul-party` |
+
+---
+
+## 🎯 技能概述
+
+本技能用于通过API实现微信小程序的完整管理,包括:
+
+### 核心能力
+
+| 阶段 | 能力 | 说明 |
+|------|------|------|
+| **申请** | 快速注册小程序 | 复用公众号主体资质,无需300元认证费 |
+| **认证** | 企业认证管理 | 自动检查认证状态、提醒过期、材料管理 |
+| **配置** | 基础信息设置 | 名称、头像、介绍、类目管理 |
+| **开发** | 代码管理 | 上传代码、生成体验版 |
+| **审核** | 提审发布 | 提交审核、查询状态、撤回、发布 |
+| **运营** | 接口管理 | 域名配置、隐私协议、接口权限 |
+| **推广** | 小程序码 | 生成无限量小程序码 |
+| **数据** | 数据分析 | 访问数据、用户画像 |
+
+### v2.0 新增能力
+
+| 能力 | 命令 | 说明 |
+|------|------|------|
+| **多小程序管理** | `mp_deploy.py list` | 统一管理多个小程序 |
+| **一键部署** | `mp_deploy.py deploy ` | 编译→上传→提审一步完成 |
+| **认证管理** | `mp_deploy.py cert ` | 认证状态检查、材料管理 |
+| **快速上传** | `mp_deploy.py upload ` | 快速上传代码到开发版 |
+
+### 开源工具集成
+
+| 工具 | 用途 | 安装方式 |
+|------|------|----------|
+| **miniprogram-ci** | 微信官方CI工具 | `npm install miniprogram-ci -g` |
+| **微信开发者工具CLI** | 本地编译上传 | 安装开发者工具自带 |
+| **multi-mini-ci** | 多平台小程序上传 | GitHub开源 |
+
+---
+
+## 🚀 触发词
+
+- 管理小程序
+- 小程序申请
+- 小程序审核
+- 小程序发布
+- 上传小程序代码
+- 配置小程序域名
+- 生成小程序码
+- 查看小程序状态
+
+---
+
+## 🏗️ 技术架构
+
+### 管理方式对比
+
+| 方式 | 优点 | 缺点 | 适用场景 |
+|------|------|------|----------|
+| **微信后台手动操作** | 无需开发 | 效率低,无法批量 | 单个小程序 |
+| **微信开发者工具CLI** | 简单易用 | 功能有限 | 开发测试 |
+| **开放平台API(本方案)** | 功能完整、可自动化 | 需要开发 | 批量管理、自动化 |
+
+### 整体架构
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ 小程序管理引擎 v1.0 │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 第一层:认证层 │ │
+│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
+│ │ │ 第三方平台 │ │ component_ │ │ authorizer_ │ │ │
+│ │ │ 认证信息 │ → │ access_token │ → │ access_token │ │ │
+│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 第二层:管理API层 │ │
+│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
+│ │ │ 注册 │ │ 配置 │ │ 代码 │ │ 审核 │ │ 运营 │ │ │
+│ │ │ 管理 │ │ 管理 │ │ 管理 │ │ 管理 │ │ 管理 │ │ │
+│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 第三层:本地CLI层 │ │
+│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
+│ │ │ mp_manager.py │ │ 微信开发者 │ │ 自动化脚本 │ │ │
+│ │ │ Python CLI │ │ 工具 CLI │ │ Shell │ │ │
+│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 📋 前置条件
+
+### 1. 微信开放平台账号
+
+- 注册地址:https://open.weixin.qq.com/
+- 完成开发者资质认证(需企业资质)
+
+### 2. 创建第三方平台
+
+登录开放平台后台 → 管理中心 → 第三方平台 → 创建
+
+**必填信息**:
+- 平台名称
+- 服务类型(选择"平台型"可管理多个小程序)
+- 授权发起页域名
+- 消息与事件接收URL
+
+### 3. 获取关键凭证
+
+| 凭证 | 说明 | 获取方式 |
+|------|------|----------|
+| `component_appid` | 第三方平台AppID | 开放平台后台 |
+| `component_appsecret` | 第三方平台密钥 | 开放平台后台 |
+| `component_verify_ticket` | 验证票据 | 微信每10分钟推送 |
+| `component_access_token` | 平台调用凭证 | API获取,2小时有效 |
+| `authorizer_access_token` | 授权方调用凭证 | API获取,2小时有效 |
+
+---
+
+## 🔑 认证流程
+
+### 获取 component_access_token
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/component/api_component_token
+{
+ "component_appid": "你的第三方平台AppID",
+ "component_appsecret": "你的第三方平台密钥",
+ "component_verify_ticket": "微信推送的验证票据"
+}
+
+# 返回
+{
+ "component_access_token": "xxxx",
+ "expires_in": 7200
+}
+```
+
+### 获取预授权码
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode
+{
+ "component_appid": "你的第三方平台AppID"
+}
+
+# 返回
+{
+ "pre_auth_code": "xxxx",
+ "expires_in": 600
+}
+```
+
+### 引导用户授权
+
+```
+https://mp.weixin.qq.com/cgi-bin/componentloginpage?
+component_appid=第三方平台AppID&
+pre_auth_code=预授权码&
+redirect_uri=授权回调地址&
+auth_type=1 # 1=仅小程序,2=仅公众号,3=两者都可
+```
+
+### 获取 authorizer_access_token
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/component/api_query_auth
+{
+ "component_appid": "第三方平台AppID",
+ "authorization_code": "授权回调返回的code"
+}
+
+# 返回
+{
+ "authorization_info": {
+ "authorizer_appid": "授权方AppID",
+ "authorizer_access_token": "授权方调用凭证",
+ "expires_in": 7200,
+ "authorizer_refresh_token": "刷新令牌"
+ }
+}
+```
+
+---
+
+## 📱 核心API接口
+
+### 一、小程序注册
+
+#### 1.1 复用公众号资质快速注册
+
+**前提**:已有认证的公众号
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/account/fastregister?access_token=ACCESS_TOKEN
+{
+ "ticket": "公众号扫码授权的ticket"
+}
+
+# 返回
+{
+ "errcode": 0,
+ "errmsg": "ok",
+ "appid": "新注册的小程序AppID",
+ "authorization_code": "授权码"
+}
+```
+
+**优势**:
+- 无需重新提交主体材料
+- 无需对公打款
+- 无需支付300元认证费
+
+**限制**:
+- 非个体户:每月可注册5个
+- 个体户:每月可注册1个
+
+#### 1.2 快速注册企业小程序
+
+**前提**:有企业营业执照
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/component/fastregisterminiprogram
+{
+ "name": "小程序名称",
+ "code": "统一社会信用代码",
+ "code_type": 1, # 1=营业执照
+ "legal_persona_wechat": "法人微信号",
+ "legal_persona_name": "法人姓名",
+ "component_phone": "联系电话"
+}
+```
+
+---
+
+### 二、基础信息配置
+
+#### 2.1 获取基础信息
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/account/getaccountbasicinfo
+# 无请求参数
+
+# 返回
+{
+ "appid": "小程序AppID",
+ "account_type": 2, # 2=小程序
+ "principal_type": 1, # 1=企业
+ "principal_name": "主体名称",
+ "realname_status": 1, # 1=已认证
+ "nickname": "小程序名称",
+ "head_image_url": "头像URL",
+ "signature": "简介"
+}
+```
+
+#### 2.2 修改名称
+
+```python
+# POST https://api.weixin.qq.com/wxa/setnickname?access_token=ACCESS_TOKEN
+{
+ "nick_name": "新名称",
+ "id_card": "管理员身份证号",
+ "license": "营业执照media_id", # 需要先上传
+ "naming_other_stuff_1": "其他证明material_id" # 可选
+}
+```
+
+#### 2.3 修改头像
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/account/modifyheadimage?access_token=ACCESS_TOKEN
+{
+ "head_img_media_id": "头像图片media_id", # 需要先上传
+ "x1": 0, "y1": 0, "x2": 1, "y2": 1 # 裁剪区域
+}
+```
+
+#### 2.4 修改简介
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/account/modifysignature?access_token=ACCESS_TOKEN
+{
+ "signature": "新的简介内容" # 4-120字
+}
+```
+
+---
+
+### 三、类目管理
+
+#### 3.1 获取可选类目
+
+```python
+# GET https://api.weixin.qq.com/cgi-bin/wxopen/getallcategories?access_token=ACCESS_TOKEN
+```
+
+#### 3.2 获取已设置类目
+
+```python
+# GET https://api.weixin.qq.com/cgi-bin/wxopen/getcategory?access_token=ACCESS_TOKEN
+```
+
+#### 3.3 添加类目
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/wxopen/addcategory?access_token=ACCESS_TOKEN
+{
+ "categories": [
+ {
+ "first": 1, # 一级类目ID
+ "second": 2, # 二级类目ID
+ "certicates": [ # 资质证明
+ {"key": "资质名称", "value": "media_id"}
+ ]
+ }
+ ]
+}
+```
+
+#### 3.4 删除类目
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/wxopen/deletecategory?access_token=ACCESS_TOKEN
+{
+ "first": 1,
+ "second": 2
+}
+```
+
+---
+
+### 四、域名配置
+
+#### 4.1 设置服务器域名
+
+```python
+# POST https://api.weixin.qq.com/wxa/modify_domain?access_token=ACCESS_TOKEN
+{
+ "action": "set", # add/delete/set/get
+ "requestdomain": ["https://api.example.com"],
+ "wsrequestdomain": ["wss://ws.example.com"],
+ "uploaddomain": ["https://upload.example.com"],
+ "downloaddomain": ["https://download.example.com"],
+ "udpdomain": ["udp://udp.example.com"],
+ "tcpdomain": ["tcp://tcp.example.com"]
+}
+```
+
+#### 4.2 设置业务域名
+
+```python
+# POST https://api.weixin.qq.com/wxa/setwebviewdomain?access_token=ACCESS_TOKEN
+{
+ "action": "set", # add/delete/set/get
+ "webviewdomain": ["https://web.example.com"]
+}
+```
+
+---
+
+### 五、隐私协议配置
+
+#### 5.1 获取隐私协议设置
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/component/getprivacysetting?access_token=ACCESS_TOKEN
+{
+ "privacy_ver": 2 # 1=当前版本,2=开发版本
+}
+```
+
+#### 5.2 设置隐私协议
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/component/setprivacysetting?access_token=ACCESS_TOKEN
+{
+ "privacy_ver": 2,
+ "setting_list": [
+ {
+ "privacy_key": "UserInfo",
+ "privacy_text": "用于展示用户头像和昵称"
+ },
+ {
+ "privacy_key": "Location",
+ "privacy_text": "用于获取您的位置信息以推荐附近服务"
+ },
+ {
+ "privacy_key": "PhoneNumber",
+ "privacy_text": "用于登录验证和订单通知"
+ }
+ ],
+ "owner_setting": {
+ "contact_email": "contact@example.com",
+ "contact_phone": "15880802661",
+ "notice_method": "弹窗提示"
+ }
+}
+```
+
+**常用隐私字段**:
+
+| privacy_key | 说明 |
+|-------------|------|
+| `UserInfo` | 用户信息(头像、昵称) |
+| `Location` | 地理位置 |
+| `PhoneNumber` | 手机号 |
+| `Album` | 相册 |
+| `Camera` | 相机 |
+| `Record` | 麦克风 |
+| `Clipboard` | 剪切板 |
+| `MessageFile` | 微信消息中的文件 |
+| `ChooseAddress` | 收货地址 |
+| `BluetoothInfo` | 蓝牙 |
+
+---
+
+### 六、代码管理
+
+#### 6.1 上传代码
+
+```python
+# POST https://api.weixin.qq.com/wxa/commit?access_token=ACCESS_TOKEN
+{
+ "template_id": 1, # 代码模板ID
+ "ext_json": "{\"extAppid\":\"授权方AppID\",\"ext\":{}}",
+ "user_version": "1.0.0",
+ "user_desc": "版本描述"
+}
+```
+
+**注意**:需要先在第三方平台创建代码模板
+
+#### 6.2 获取体验版二维码
+
+```python
+# GET https://api.weixin.qq.com/wxa/get_qrcode?access_token=ACCESS_TOKEN&path=pages/index/index
+# 返回二维码图片
+```
+
+#### 6.3 获取已上传的代码页面列表
+
+```python
+# GET https://api.weixin.qq.com/wxa/get_page?access_token=ACCESS_TOKEN
+
+# 返回
+{
+ "errcode": 0,
+ "page_list": ["pages/index/index", "pages/my/my"]
+}
+```
+
+---
+
+### 七、审核管理
+
+#### 7.1 提交审核
+
+```python
+# POST https://api.weixin.qq.com/wxa/submit_audit?access_token=ACCESS_TOKEN
+{
+ "item_list": [
+ {
+ "address": "pages/index/index",
+ "tag": "电子书 阅读 创业",
+ "first_class": "教育",
+ "second_class": "在线教育",
+ "first_id": 1,
+ "second_id": 2,
+ "title": "首页"
+ }
+ ],
+ "preview_info": {
+ "video_id_list": [],
+ "pic_id_list": []
+ },
+ "version_desc": "版本说明",
+ "feedback_info": "反馈内容",
+ "feedback_stuff": "media_id" # 反馈附件
+}
+```
+
+#### 7.2 查询审核状态
+
+```python
+# POST https://api.weixin.qq.com/wxa/get_auditstatus?access_token=ACCESS_TOKEN
+{
+ "auditid": 1234567890
+}
+
+# 返回
+{
+ "errcode": 0,
+ "status": 0, # 0=审核成功,1=审核被拒,2=审核中,3=已撤回,4=审核延后
+ "reason": "拒绝原因", # status=1时返回
+ "screenshot": "截图"
+}
+```
+
+#### 7.3 查询最新审核状态
+
+```python
+# GET https://api.weixin.qq.com/wxa/get_latest_auditstatus?access_token=ACCESS_TOKEN
+```
+
+#### 7.4 撤回审核
+
+```python
+# GET https://api.weixin.qq.com/wxa/undocodeaudit?access_token=ACCESS_TOKEN
+```
+
+**限制**:单个小程序每天只能撤回1次
+
+---
+
+### 八、发布管理
+
+#### 8.1 发布已审核通过的版本
+
+```python
+# POST https://api.weixin.qq.com/wxa/release?access_token=ACCESS_TOKEN
+{} # 无请求参数
+
+# 返回
+{
+ "errcode": 0,
+ "errmsg": "ok"
+}
+```
+
+#### 8.2 版本回退
+
+```python
+# GET https://api.weixin.qq.com/wxa/revertcoderelease?access_token=ACCESS_TOKEN
+```
+
+**限制**:只能回退到上一个版本,且只能回退一次
+
+#### 8.3 获取可回退版本历史
+
+```python
+# GET https://api.weixin.qq.com/wxa/revertcoderelease?access_token=ACCESS_TOKEN&action=get_history_version
+```
+
+#### 8.4 分阶段发布
+
+```python
+# POST https://api.weixin.qq.com/wxa/grayrelease?access_token=ACCESS_TOKEN
+{
+ "gray_percentage": 10 # 灰度比例 1-100
+}
+```
+
+---
+
+### 九、小程序码生成
+
+#### 9.1 获取小程序码(有限制)
+
+```python
+# POST https://api.weixin.qq.com/wxa/getwxacode?access_token=ACCESS_TOKEN
+{
+ "path": "pages/index/index?scene=123",
+ "width": 430,
+ "auto_color": false,
+ "line_color": {"r": 0, "g": 0, "b": 0},
+ "is_hyaline": false # 是否透明背景
+}
+# 返回图片二进制
+```
+
+**限制**:每个path只能生成10万个
+
+#### 9.2 获取无限小程序码(推荐)
+
+```python
+# POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
+{
+ "scene": "user_id=123&from=share", # 最长32字符
+ "page": "pages/index/index", # 必须是已发布的页面
+ "width": 430,
+ "auto_color": false,
+ "line_color": {"r": 0, "g": 0, "b": 0},
+ "is_hyaline": false
+}
+# 返回图片二进制
+```
+
+**注意**:scene参数需要在小程序中用`onLoad(options)`解析
+
+#### 9.3 生成小程序短链接
+
+```python
+# POST https://api.weixin.qq.com/wxa/genwxashortlink?access_token=ACCESS_TOKEN
+{
+ "page_url": "pages/index/index?id=123",
+ "page_title": "页面标题",
+ "is_permanent": false # 是否永久有效
+}
+
+# 返回
+{
+ "errcode": 0,
+ "link": "https://wxaurl.cn/xxxx"
+}
+```
+
+---
+
+### 十、接口权限管理
+
+#### 10.1 查询接口调用额度
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/openapi/quota/get?access_token=ACCESS_TOKEN
+{
+ "cgi_path": "/wxa/getwxacode"
+}
+
+# 返回
+{
+ "quota": {
+ "daily_limit": 100000,
+ "used": 500,
+ "remain": 99500
+ }
+}
+```
+
+#### 10.2 重置接口调用次数
+
+```python
+# POST https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=ACCESS_TOKEN
+{
+ "appid": "小程序AppID"
+}
+```
+
+**限制**:每月只能重置10次
+
+---
+
+### 十一、数据分析
+
+#### 11.1 获取访问趋势
+
+```python
+# POST https://api.weixin.qq.com/datacube/getweanalysisappiddailyvisittrend?access_token=ACCESS_TOKEN
+{
+ "begin_date": "20260101",
+ "end_date": "20260125"
+}
+
+# 返回
+{
+ "list": [
+ {
+ "ref_date": "20260125",
+ "session_cnt": 1000, # 打开次数
+ "visit_pv": 5000, # 访问次数
+ "visit_uv": 800, # 访问人数
+ "visit_uv_new": 100, # 新用户数
+ "stay_time_uv": 120.5, # 人均停留时长(秒)
+ "stay_time_session": 60.2 # 次均停留时长(秒)
+ }
+ ]
+}
+```
+
+#### 11.2 获取用户画像
+
+```python
+# POST https://api.weixin.qq.com/datacube/getweanalysisappiduserportrait?access_token=ACCESS_TOKEN
+{
+ "begin_date": "20260101",
+ "end_date": "20260125"
+}
+```
+
+---
+
+## 🛠️ 快速使用
+
+### 方式一:使用Python管理脚本(推荐)
+
+```bash
+# 进入脚本目录
+cd /Users/karuo/Documents/个人/卡若AI/02_卡人(水)/小程序管理/scripts
+
+# 安装依赖
+pip install httpx python-dotenv
+
+# 配置凭证
+cp .env.example .env
+# 编辑 .env 填入你的凭证
+
+# 使用命令行工具
+python mp_manager.py status # 查看小程序状态
+python mp_manager.py audit # 查看审核状态
+python mp_manager.py release # 发布上线
+python mp_manager.py qrcode # 生成小程序码
+```
+
+### 方式二:使用微信开发者工具CLI
+
+```bash
+# CLI路径
+CLI="/Applications/wechatwebdevtools.app/Contents/MacOS/cli"
+
+# 打开项目
+$CLI -o "/path/to/miniprogram"
+
+# 编译
+$CLI build-npm --project "/path/to/miniprogram"
+
+# 预览(生成二维码)
+$CLI preview --project "/path/to/miniprogram" --qr-format image --qr-output preview.png
+
+# 上传代码
+$CLI upload --project "/path/to/miniprogram" --version "1.0.0" --desc "版本说明"
+
+# 提交审核
+$CLI submit-audit --project "/path/to/miniprogram"
+```
+
+### 方式三:直接调用API
+
+```python
+import httpx
+
+# 配置
+ACCESS_TOKEN = "你的access_token"
+BASE_URL = "https://api.weixin.qq.com"
+
+async def check_audit_status(auditid: int):
+ """查询审核状态"""
+ async with httpx.AsyncClient() as client:
+ resp = await client.post(
+ f"{BASE_URL}/wxa/get_auditstatus?access_token={ACCESS_TOKEN}",
+ json={"auditid": auditid}
+ )
+ return resp.json()
+
+# 使用
+result = await check_audit_status(1234567890)
+print(result)
+```
+
+---
+
+## 📁 文件结构
+
+```
+小程序管理/
+├── SKILL.md # 技能说明文档(本文件)
+├── scripts/
+│ ├── mp_manager.py # 小程序管理CLI工具
+│ ├── mp_api.py # API封装类
+│ ├── requirements.txt # Python依赖
+│ └── .env.example # 环境变量模板
+└── references/
+ ├── API接口速查表.md # 常用API速查
+ ├── 隐私协议填写指南.md # 隐私协议配置指南
+ └── 审核规范.md # 审核常见问题
+```
+
+---
+
+## ⚠️ 常见问题
+
+### Q1: 如何获取 component_verify_ticket?
+
+微信会每10分钟向你配置的"消息与事件接收URL"推送。你需要部署一个服务来接收并存储它。
+
+```python
+# 接收推送示例
+@app.post("/callback")
+async def receive_ticket(request: Request):
+ xml_data = await request.body()
+ # 解密并解析XML
+ # 提取 ComponentVerifyTicket
+ # 存储到Redis或数据库
+ return "success"
+```
+
+### Q2: 审核被拒常见原因?
+
+| 原因 | 解决方案 |
+|------|----------|
+| 类目不符 | 确保小程序功能与所选类目一致 |
+| 隐私协议缺失 | 配置完整的隐私保护指引 |
+| 诱导分享 | 移除"分享到群获取xx"等诱导文案 |
+| 虚拟支付 | iOS不能使用虚拟支付,需走IAP |
+| 内容违规 | 检查文案、图片是否合规 |
+| 功能不完整 | 确保所有页面功能正常 |
+
+### Q3: 审核需要多长时间?
+
+- 首次提审:1-7个工作日
+- 非首次:1-3个工作日
+- 加急审核:付费服务,24小时内
+
+### Q4: 如何提高审核通过率?
+
+1. **提交完整测试账号**:如需登录才能体验功能
+2. **录制操作视频**:复杂功能建议附上操作视频
+3. **详细的版本描述**:说明本次更新内容
+4. **完善隐私协议**:所有收集数据的接口都要说明用途
+
+### Q5: 代码模板是什么?
+
+第三方平台需要先将小程序代码上传到"草稿箱",再添加到"代码模板库"。之后可以用这个模板批量部署到多个小程序。
+
+流程:开发完成 → 上传到草稿箱 → 添加到模板库 → 使用模板部署
+
+---
+
+## 🔗 官方文档
+
+- [微信开放平台文档](https://developers.weixin.qq.com/doc/oplatform/)
+- [第三方平台开发指南](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/getting_started/how_to_read.html)
+- [小程序管理接口](https://developers.weixin.qq.com/doc/oplatform/openApi/OpenApiDoc/)
+- [代码管理接口](https://developers.weixin.qq.com/doc/oplatform/openApi/OpenApiDoc/miniprogram-management/code-management/commit.html)
+- [隐私协议开发指南](https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/)
+
+---
+
+## 📊 当前项目配置
+
+### Soul派对小程序
+
+| 项目 | 配置值 |
+|------|--------|
+| **AppID** | `wxb8bbb2b10dec74aa` |
+| **项目路径** | `/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram` |
+| **API域名** | `https://soul.quwanzhi.com` |
+| **当前版本** | `1.0.13` |
+| **认证状态** | ⏳ 审核中(等待1-5工作日) |
+| **检查结果** | ✅ 8项通过 / ⚠️ 2项警告 / ❌ 0项错误 |
+| **可上传** | ✅ 是 |
+| **可发布** | ❌ 需等待认证通过 |
+
+### 快速命令
+
+```bash
+# 打开项目
+/Applications/wechatwebdevtools.app/Contents/MacOS/cli -o "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram"
+
+# 预览
+/Applications/wechatwebdevtools.app/Contents/MacOS/cli preview --project "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram" --qr-format image --qr-output preview.png
+
+# 上传
+/Applications/wechatwebdevtools.app/Contents/MacOS/cli upload --project "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram" --version "1.0.0" --desc "版本说明"
+```
+
+---
+
+## 🚀 全自动部署流程(v3.0)
+
+### 完整生命周期流程图
+
+```
+╔══════════════════════════════════════════════════════════════════════════════╗
+║ 小程序完整生命周期管理 ║
+╠══════════════════════════════════════════════════════════════════════════════╣
+║ ║
+║ 阶段一:准备阶段 ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ │ ║
+║ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ║
+║ │ │ 注册小程序│ → │ 企业认证 │ → │ 配置项目 │ │ ║
+║ │ │ (微信后台)│ │ (300元/年)│ │ (添加到管理)│ │ ║
+║ │ └──────────┘ └──────────┘ └──────────┘ │ ║
+║ │ │ │ │ │ ║
+║ │ │ │ ▼ │ ║
+║ │ │ │ mp_deploy.py add │ ║
+║ │ │ ▼ │ ║
+║ │ │ 等待审核 1-5 天 │ ║
+║ │ │ mp_deploy.py cert-done │ ║
+║ │ │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ │ ║
+║ ▼ ║
+║ 阶段二:开发阶段 ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ │ ║
+║ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ║
+║ │ │ 编写代码 │ → │ 本地测试 │ → │ 项目检查 │ │ ║
+║ │ │ (开发者) │ │ (模拟器) │ │ mp_full │ │ ║
+║ │ └──────────┘ └──────────┘ └──────────┘ │ ║
+║ │ │ │ ║
+║ │ ▼ │ ║
+║ │ mp_full.py check │ ║
+║ │ 检查: 配置/域名/隐私/认证 │ ║
+║ │ │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ │ ║
+║ ▼ ║
+║ 阶段三:部署阶段(全自动) ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ │ ║
+║ │ mp_full.py auto -v "1.0.0" -d "版本描述" │ ║
+║ │ │ │ ║
+║ │ ▼ │ ║
+║ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ║
+║ │ │ ① 检查 │ → │ ② 上传 │ → │ ③ 提审 │ │ ║
+║ │ │ 项目问题 │ │ 代码到微信│ │ 提交审核 │ │ ║
+║ │ └──────────┘ └──────────┘ └──────────┘ │ ║
+║ │ │ │ │ │ ║
+║ │ ▼ ▼ ▼ │ ║
+║ │ 自动检测 使用CLI/npm 已认证→API提审 │ ║
+║ │ 配置/认证/域名 优先级选择 未认证→手动提审 │ ║
+║ │ │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ │ ║
+║ ▼ ║
+║ 阶段四:发布阶段 ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ │ ║
+║ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ║
+║ │ │ 等待审核 │ → │ 审核通过 │ → │ 发布上线 │ │ ║
+║ │ │ 1-7工作日 │ │ 通知 │ │ release │ │ ║
+║ │ └──────────┘ └──────────┘ └──────────┘ │ ║
+║ │ │ │ ║
+║ │ ▼ │ ║
+║ │ mp_deploy.py release │ ║
+║ │ 或 微信后台点击发布 │ ║
+║ │ │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ │ ║
+║ ▼ ║
+║ 阶段五:运营阶段 ║
+║ ┌─────────────────────────────────────────────────────────────────────┐ ║
+║ │ │ ║
+║ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ║
+║ │ │ 数据分析 │ │ 汇总报告 │ │ 版本迭代 │ │ ║
+║ │ │ mp_manager│ │ mp_full │ │ 循环部署 │ │ ║
+║ │ └──────────┘ └──────────┘ └──────────┘ │ ║
+║ │ │ │ │ │ ║
+║ │ ▼ ▼ ▼ │ ║
+║ │ data命令 report命令 返回部署阶段 │ ║
+║ │ │ ║
+║ └─────────────────────────────────────────────────────────────────────┘ ║
+║ ║
+╚══════════════════════════════════════════════════════════════════════════════╝
+```
+
+### 工具整合架构图
+
+```
+┌────────────────────────────────────────────────────────────────────────────┐
+│ 小程序管理工具整合架构 │
+├────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 用户命令层 │ │
+│ │ mp_full.py report │ mp_full.py check │ mp_full.py auto │ │
+│ │ mp_deploy.py list │ mp_deploy.py cert │ mp_manager.py status │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Python 管理层 │ │
+│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
+│ │ │ mp_full.py │ │ mp_deploy.py │ │ mp_api.py │ │ │
+│ │ │ 全能管理器 │ │ 部署工具 │ │ API封装 │ │ │
+│ │ │ • 检查 │ │ • 多小程序 │ │ • 认证 │ │ │
+│ │ │ • 报告 │ │ • 认证管理 │ │ • 审核 │ │ │
+│ │ │ • 自动部署 │ │ • 发布 │ │ • 发布 │ │ │
+│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 外部工具层 │ │
+│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
+│ │ │ 微信开发者 │ │ miniprogram │ │ 微信开放平台 │ │ │
+│ │ │ 工具 CLI │ │ -ci (npm) │ │ API │ │ │
+│ │ │ │ │ │ │ │ │ │
+│ │ │ • 打开项目 │ │ • 上传代码 │ │ • 提交审核 │ │ │
+│ │ │ • 预览 │ │ • 预览 │ │ • 发布 │ │ │
+│ │ │ • 上传 │ │ • 构建npm │ │ • 认证管理 │ │ │
+│ │ │ • 编译 │ │ • sourceMap │ │ • 数据分析 │ │ │
+│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
+│ │ │ │ │ │ │
+│ │ ▼ ▼ ▼ │ │
+│ │ 优先级: 1 优先级: 2 优先级: 3 │ │
+│ │ (无需密钥) (需要密钥) (需要token) │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 快速开始
+
+```bash
+# 进入脚本目录
+cd /Users/karuo/Documents/个人/卡若AI/02_卡人(水)/小程序管理/scripts
+
+# 1. 查看已配置的小程序
+python3 mp_deploy.py list
+
+# 2. 添加新小程序(交互式)
+python3 mp_deploy.py add
+
+# 3. 检查认证状态
+python3 mp_deploy.py cert-status soul-party
+
+# 4. 一键部署
+python3 mp_deploy.py deploy soul-party
+
+# 5. 审核通过后发布
+python3 mp_deploy.py release soul-party
+```
+
+### 命令速查
+
+| 命令 | 说明 | 示例 |
+|------|------|------|
+| `list` | 列出所有小程序 | `python3 mp_deploy.py list` |
+| `add` | 添加新小程序 | `python3 mp_deploy.py add` |
+| `deploy` | 一键部署 | `python3 mp_deploy.py deploy soul-party` |
+| `upload` | 仅上传代码 | `python3 mp_deploy.py upload soul-party` |
+| `cert` | 提交认证 | `python3 mp_deploy.py cert soul-party` |
+| `cert-status` | 查询认证状态 | `python3 mp_deploy.py cert-status soul-party` |
+| `cert-done` | 标记认证完成 | `python3 mp_deploy.py cert-done soul-party` |
+| `release` | 发布上线 | `python3 mp_deploy.py release soul-party` |
+
+---
+
+## 📋 企业认证详解
+
+### 认证流程图
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ 企业认证流程 │
+├──────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
+│ │ 准备 │ → │ 提交 │ → │ 审核 │ │
+│ │ 材料 │ │ 认证 │ │ 等待 │ │
+│ └─────────┘ └─────────┘ └─────────┘ │
+│ │ │ │ │
+│ ↓ ↓ ↓ │
+│ ┌─────────────────────────────────────────┐ │
+│ │ • 营业执照 • 微信后台上传 • 1-5工作日 │ │
+│ │ • 法人身份证 • 法人扫码验证 • 审核结果 │ │
+│ │ • 法人微信号 • 支付300元 • 通知 │ │
+│ └─────────────────────────────────────────┘ │
+│ │
+│ ⚠️ 认证有效期:1年,到期需年审 │
+│ │
+└──────────────────────────────────────────────────────────────┘
+```
+
+### 认证材料清单
+
+| 材料 | 必需 | 说明 |
+|------|------|------|
+| 企业营业执照 | ✅ | 扫描件或照片,信息清晰 |
+| 法人身份证 | ✅ | 正反面照片 |
+| 法人微信号 | ✅ | 需绑定银行卡,用于扫码验证 |
+| 联系人手机号 | ✅ | 接收审核通知 |
+| 认证费用 | ✅ | 300元/年 |
+| 其他资质 | 可选 | 特殊行业需要(如医疗、金融) |
+
+### 认证状态说明
+
+| 状态 | 说明 | 下一步操作 |
+|------|------|------------|
+| `unknown` | 未知/未检查 | 运行 `cert-status` 检查 |
+| `pending` | 审核中 | 等待1-5个工作日 |
+| `verified` | 已认证 | 可以正常发布 |
+| `rejected` | 被拒绝 | 查看原因,修改后重新提交 |
+| `expired` | 已过期 | 需要重新认证(年审) |
+
+### 认证API(第三方平台)
+
+如果你有第三方平台资质,可以通过API代商家提交认证:
+
+```python
+# POST https://api.weixin.qq.com/wxa/sec/wxaauth?access_token=ACCESS_TOKEN
+{
+ "auth_type": 1, # 1=企业
+ "auth_data": {
+ "name": "企业名称",
+ "code": "统一社会信用代码",
+ "legal_persona_wechat": "法人微信号",
+ "legal_persona_name": "法人姓名",
+ "legal_persona_idcard": "法人身份证号",
+ "component_phone": "联系电话"
+ }
+}
+```
+
+---
+
+## 🔧 GitHub开源工具推荐
+
+### 1. miniprogram-ci(官方)
+
+微信官方提供的CI工具,支持Node.js环境使用。
+
+**安装**:
+```bash
+npm install miniprogram-ci -g
+```
+
+**使用**:
+```javascript
+const ci = require('miniprogram-ci');
+
+const project = new ci.Project({
+ appid: 'wxb8bbb2b10dec74aa',
+ type: 'miniProgram',
+ projectPath: '/path/to/miniprogram',
+ privateKeyPath: '/path/to/private.key',
+ ignores: ['node_modules/**/*']
+});
+
+// 上传
+await ci.upload({
+ project,
+ version: '1.0.0',
+ desc: '版本描述',
+ setting: {
+ es6: true,
+ minify: true
+ }
+});
+```
+
+**获取私钥**:
+1. 登录小程序后台
+2. 开发管理 → 开发设置
+3. 小程序代码上传密钥 → 下载
+
+### 2. multi-mini-ci
+
+多平台小程序自动化上传工具。
+
+**GitHub**: https://github.com/Ethan-zjc/multi-mini-ci
+
+**支持平台**:
+- 微信小程序
+- 支付宝小程序
+- 百度小程序
+- 字节跳动小程序
+
+### 3. GitHub Actions集成
+
+在项目中创建 `.github/workflows/deploy.yml`:
+
+```yaml
+name: Deploy MiniProgram
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '18'
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Build
+ run: npm run build
+
+ - name: Upload to WeChat
+ env:
+ PRIVATE_KEY: ${{ secrets.MINIPROGRAM_PRIVATE_KEY }}
+ run: |
+ echo "$PRIVATE_KEY" > private.key
+ npx miniprogram-ci upload \
+ --pp ./dist \
+ --pkp ./private.key \
+ --appid wxb8bbb2b10dec74aa \
+ --uv "1.0.${{ github.run_number }}" \
+ -r 1 \
+ --desc "CI auto deploy"
+```
+
+---
+
+## 📁 完整文件结构(v3.0)
+
+```
+小程序管理/
+├── SKILL.md # 技能说明文档(本文件)
+├── scripts/
+│ ├── mp_full.py # 全能管理工具(推荐)⭐
+│ ├── mp_deploy.py # 部署工具(多小程序管理)
+│ ├── mp_manager.py # API管理工具(基础版)
+│ ├── mp_api.py # API封装类(Python SDK)
+│ ├── apps_config.json # 多小程序配置文件
+│ ├── requirements.txt # Python依赖
+│ ├── env_template.txt # 环境变量模板
+│ └── reports/ # 检查报告存放目录
+│ ├── summary_*.json # 汇总报告
+│ └── report_*.json # 项目报告
+└── references/
+ ├── API接口速查表.md # 常用API速查
+ ├── 企业认证完整指南.md # 认证操作指南
+ ├── 隐私协议填写指南.md # 隐私协议配置指南
+ └── 审核规范.md # 审核常见问题
+```
+
+### 脚本功能对比
+
+| 脚本 | 功能 | 推荐场景 |
+|------|------|----------|
+| **mp_full.py** | 全能:检查+报告+自动部署 | 日常管理(推荐) |
+| **mp_deploy.py** | 多小程序管理+认证 | 多项目管理 |
+| **mp_manager.py** | API调用工具 | 高级操作 |
+| **mp_api.py** | Python SDK | 二次开发 |
+
+---
+
+## 📊 多小程序配置
+
+配置文件位于 `scripts/apps_config.json`:
+
+```json
+{
+ "apps": [
+ {
+ "id": "soul-party",
+ "name": "Soul派对",
+ "appid": "wxb8bbb2b10dec74aa",
+ "project_path": "/path/to/miniprogram",
+ "certification": {
+ "status": "verified",
+ "enterprise_name": "厦门智群网络科技有限公司"
+ }
+ },
+ {
+ "id": "another-app",
+ "name": "另一个小程序",
+ "appid": "wx1234567890",
+ "project_path": "/path/to/another",
+ "certification": {
+ "status": "pending"
+ }
+ }
+ ],
+ "certification_materials": {
+ "enterprise_name": "厦门智群网络科技有限公司",
+ "license_number": "统一社会信用代码",
+ "legal_persona_name": "法人姓名",
+ "component_phone": "15880802661"
+ }
+}
+```
+
+---
+
+## ⚡ 常见场景速查
+
+### 场景1:发布时提示"未完成微信认证"
+
+```bash
+# 1. 检查认证状态
+python3 mp_deploy.py cert-status soul-party
+
+# 2. 如果未认证,查看认证指引
+python3 mp_deploy.py cert soul-party
+
+# 3. 在微信后台完成认证后,标记完成
+python3 mp_deploy.py cert-done soul-party
+
+# 4. 重新部署
+python3 mp_deploy.py deploy soul-party
+```
+
+### 场景2:新建小程序并快速上线
+
+```bash
+# 1. 添加小程序配置
+python3 mp_deploy.py add
+
+# 2. 提交认证
+python3 mp_deploy.py cert my-new-app
+
+# 3. 在微信后台完成认证(1-5天)
+
+# 4. 认证通过后标记
+python3 mp_deploy.py cert-done my-new-app
+
+# 5. 一键部署
+python3 mp_deploy.py deploy my-new-app
+
+# 6. 审核通过后发布
+python3 mp_deploy.py release my-new-app
+```
+
+### 场景3:仅更新代码(不提审)
+
+```bash
+# 快速上传到开发版
+python3 mp_deploy.py upload soul-party -v "1.0.5" -d "修复xxx问题"
+```
+
+### 场景4:批量管理多个小程序
+
+```bash
+# 查看所有小程序
+python3 mp_deploy.py list
+
+# 检查所有认证状态
+for app in soul-party another-app third-app; do
+ echo "=== $app ==="
+ python3 mp_deploy.py cert-status $app
+done
+```
+
+---
+
+## 🔗 相关资源
+
+### 官方文档
+- [微信开放平台](https://developers.weixin.qq.com/doc/oplatform/)
+- [小程序CI工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html)
+- [第三方平台代认证](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/product/weapp_wxverify.html)
+
+### 开源项目
+- [miniprogram-ci](https://www.npmjs.com/package/miniprogram-ci) - 官方CI工具
+- [multi-mini-ci](https://github.com/Ethan-zjc/multi-mini-ci) - 多平台上传
+- [uz-miniprogram-ci](https://github.com/uzhan/uz-miniprogram-ci) - 一键上传发布
+
+### 相关文章
+- [GitHub Actions集成小程序CI](https://idayer.com/use-github-actions-and-mp-ci-for-wechat-miniprogram-ci/)
+
+---
+
+## 🔥 实战经验库(持续更新)
+
+> 基于 Soul创业派对 项目开发过程中的真实问题和解决方案
+
+### 一、数据库与后端问题
+
+#### 1.1 后台初始化失败:Unknown column 'password' in 'field list'
+
+**问题现象**:后台用户管理显示"初始化失败"
+
+**根本原因**:数据库表结构缺少字段
+
+**解决方案**:创建数据库初始化API自动修复
+
+```typescript
+// app/api/db/init/route.ts
+// 自动检查并添加缺失字段
+const columnsToAdd = [
+ { name: 'password', type: 'VARCHAR(100)' },
+ { name: 'session_key', type: 'VARCHAR(100)' },
+ { name: 'referred_by', type: 'VARCHAR(50)' },
+ { name: 'is_admin', type: 'BOOLEAN DEFAULT FALSE' },
+]
+
+for (const col of columnsToAdd) {
+ // 检查列是否存在,不存在则添加
+ await query(`ALTER TABLE users ADD COLUMN ${col.name} ${col.type}`)
+}
+```
+
+**访问修复**:`curl https://your-domain.com/api/db/init`
+
+---
+
+#### 1.2 Column 'open_id' cannot be null
+
+**问题现象**:后台添加用户失败
+
+**根本原因**:数据库 `open_id` 字段设置为 NOT NULL,但后台添加用户时没有openId
+
+**解决方案**:
+```sql
+ALTER TABLE users MODIFY COLUMN open_id VARCHAR(100) NULL
+```
+
+**最佳实践**:openId允许为NULL,因为:
+- 后台手动添加的用户没有openId
+- 微信登录用户有openId
+- 两种用户需要共存
+
+---
+
+#### 1.3 AppID配置不一致
+
+**问题现象**:微信登录返回错误,或获取openId失败
+
+**根本原因**:项目中多个文件使用了不同的AppID
+
+**检查清单**:
+
+| 文件 | 配置项 | 正确值 |
+|:---|:---|:---|
+| `miniprogram/project.config.json` | appid | wxb8bbb2b10dec74aa |
+| `app/api/miniprogram/login/route.ts` | MINIPROGRAM_CONFIG.appId | wxb8bbb2b10dec74aa |
+| `app/api/wechat/login/route.ts` | APPID | wxb8bbb2b10dec74aa |
+| `app/api/withdraw/route.ts` | WECHAT_PAY_CONFIG.appId | wxb8bbb2b10dec74aa |
+
+**搜索命令**:
+```bash
+# 查找所有AppID配置
+rg "wx[a-f0-9]{16}" --type ts --type json
+```
+
+---
+
+#### 1.4 用户ID设计最佳实践
+
+**推荐方案**:使用 `openId` 作为用户主键
+
+```typescript
+// 微信登录创建用户
+const userId = openId // 直接使用openId作为用户ID
+await query(`
+ INSERT INTO users (id, open_id, ...) VALUES (?, ?, ...)
+`, [userId, openId, ...])
+```
+
+**优势**:
+- 与微信官方标识一致
+- 便于追踪和管理
+- 后台显示更直观
+
+**兼容方案**:后台添加的用户使用 `user_` 前缀
+
+---
+
+### 二、前端与UI问题
+
+#### 2.1 Next.js Hydration错误
+
+**问题现象**:页面显示"哎呀,出错了",控制台报 hydration 错误
+
+**根本原因**:服务端和客户端渲染结果不一致(如使用localStorage、zustand持久化)
+
+**解决方案**:添加mounted状态检查
+
+```tsx
+export default function AdminLayout({ children }) {
+ const [mounted, setMounted] = useState(false)
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ // 等待客户端mount后再渲染
+ if (!mounted) {
+ return 加载中...
+ }
+
+ return {children}
+}
+```
+
+---
+
+#### 2.2 数据类型不匹配:toFixed() 报错
+
+**问题现象**:显示金额时报错 `toFixed is not a function`
+
+**根本原因**:数据库返回的 `DECIMAL` 字段是字符串类型
+
+**解决方案**:
+```tsx
+// ❌ 错误
+{user.earnings.toFixed(2)}
+
+// ✅ 正确
+{parseFloat(String(user.earnings || 0)).toFixed(2)}
+```
+
+**通用处理函数**:
+```typescript
+const formatMoney = (value: any) => {
+ return parseFloat(String(value || 0)).toFixed(2)
+}
+```
+
+---
+
+### 三、小程序开发问题
+
+#### 3.1 搜索功能:章节ID格式不一致
+
+**问题现象**:搜索结果跳转到阅读页404
+
+**根本原因**:
+- `book-chapters.json` 使用 `chapter-2`, `chapter-3` 格式
+- 阅读页使用 `1.1`, `1.2` 格式
+
+**解决方案**:从标题提取章节号
+
+```typescript
+// 从标题提取章节号(如 "1.1 荷包:..." → "1.1")
+const sectionIdMatch = chapter.title?.match(/^(\d+\.\d+)\s/)
+const sectionId = sectionIdMatch ? sectionIdMatch[1] : chapter.id
+```
+
+---
+
+#### 3.2 搜索功能:敏感信息过滤
+
+**需求**:搜索结果不能显示用户手机号、微信号等
+
+**实现**:
+```typescript
+const cleanContent = content
+ .replace(/1[3-9]\d{9}/g, '***') // 手机号
+ .replace(/微信[::]\s*\S+/g, '微信:***') // 微信号
+ .replace(/QQ[::]\s*\d+/g, 'QQ:***') // QQ号
+ .replace(/邮箱[::]\s*\S+@\S+/g, '邮箱:***') // 邮箱
+```
+
+---
+
+#### 3.3 上下章导航:付费内容也需要显示
+
+**需求**:即使用户没有购买,也要显示上一篇/下一篇导航
+
+**实现**:将导航组件移到付费墙之外
+
+```html
+
+
+
+
+
+
+
+ 上一篇
+ 下一篇
+
+```
+
+---
+
+#### 3.4 分销绑定:推广码捕获
+
+**需求**:用户通过分享链接进入时,自动绑定推广者
+
+**实现流程**:
+
+```javascript
+// app.js - onLaunch/onShow
+onLaunch(options) {
+ if (options.query && options.query.ref) {
+ wx.setStorageSync('referral_code', options.query.ref)
+ this.bindReferral(options.query.ref)
+ }
+}
+
+// 小程序码scene参数解析
+const scene = decodeURIComponent(options.scene)
+const params = new URLSearchParams(scene)
+const ref = params.get('ref')
+```
+
+**后端绑定**:
+```sql
+UPDATE users SET referred_by = ? WHERE id = ? AND referred_by IS NULL
+```
+
+---
+
+### 四、后台管理优化
+
+#### 4.1 菜单精简原则
+
+**优化前(9项)**:
+- 数据概览、网站配置、内容管理、用户管理、匹配配置、分销管理、支付配置、分账提现、二维码、系统设置
+
+**优化后(6项)**:
+- 数据概览、内容管理、用户管理、分账管理、支付设置、系统设置
+
+**精简原则**:
+1. 合并相似功能(分销管理 + 分账提现 → 分账管理)
+2. 移除低频功能(二维码、匹配配置 → 可在系统设置中配置)
+3. 核心功能优先
+
+---
+
+#### 4.2 用户绑定关系展示
+
+**需求**:查看用户的推广下线详情
+
+**实现API**:
+```typescript
+// GET /api/db/users/referrals?userId=xxx
+const referrals = await query(`
+ SELECT * FROM users WHERE referred_by = ?
+ ORDER BY created_at DESC
+`, [referralCode])
+```
+
+**展示信息**:
+- 绑定总数、已付费人数、免费用户
+- 累计收益、待提现金额
+- 每个绑定用户的状态(VIP/已付费/未付费)
+
+---
+
+### 五、分销与提现
+
+#### 5.1 自动分账规则
+
+| 配置项 | 值 | 说明 |
+|:---|:---|:---|
+| 分销比例 | 90% | 推广者获得订单金额的90% |
+| 结算方式 | 自动 | 用户付款后立即计入推广者账户 |
+| 提现方式 | 微信零钱 | 企业付款到零钱 |
+| 提现门槛 | 1元 | 累计收益≥1元可提现 |
+
+#### 5.2 提现流程
+
+```
+用户申请提现
+ ↓
+扣除账户余额,增加待提现金额
+ ↓
+管理员后台审核
+ ↓
+批准 → 调用微信企业付款API → 到账
+拒绝 → 返还用户余额
+```
+
+---
+
+### 六、开发规范
+
+#### 6.1 配置统一管理
+
+```typescript
+// lib/config.ts
+export const WECHAT_CONFIG = {
+ appId: process.env.WECHAT_APPID || 'wxb8bbb2b10dec74aa',
+ appSecret: process.env.WECHAT_APPSECRET || '...',
+ mchId: '1318592501',
+ apiKey: '...'
+}
+```
+
+**所有API文件统一引用此配置,避免硬编码**
+
+#### 6.2 数据库字段命名
+
+| 前端字段 | 数据库字段 | 说明 |
+|:---|:---|:---|
+| openId | open_id | 微信openId |
+| hasFullBook | has_full_book | 是否购买全书 |
+| referralCode | referral_code | 推广码 |
+| pendingEarnings | pending_earnings | 待提现收益 |
+
+**规则**:数据库使用snake_case,前端使用camelCase
+
+#### 6.3 错误处理模板
+
+```typescript
+export async function POST(request: Request) {
+ try {
+ const body = await request.json()
+ // 业务逻辑
+ return NextResponse.json({ success: true, data: ... })
+ } catch (error) {
+ console.error('[API名称] 错误:', error)
+ return NextResponse.json({
+ success: false,
+ error: '用户友好的错误信息'
+ }, { status: 500 })
+ }
+}
+```
+
+---
+
+## 📌 问题排查清单
+
+### 小程序无法登录
+
+- [ ] 检查AppID是否正确(project.config.json vs 后端)
+- [ ] 检查AppSecret是否正确
+- [ ] 检查API域名是否已配置
+- [ ] 检查后端服务是否正常运行
+- [ ] 查看后端日志 `[MiniLogin]`
+
+### 后台显示异常
+
+- [ ] 运行 `/api/db/init` 初始化数据库
+- [ ] 检查数据库连接是否正常
+- [ ] 清除浏览器缓存(Cmd+Shift+R)
+- [ ] 查看浏览器控制台错误
+
+### 搜索功能无结果
+
+- [ ] 检查 `public/book-chapters.json` 是否存在
+- [ ] 检查章节文件路径是否正确(filePath字段)
+- [ ] 检查关键词编码(中文需URL编码)
+
+### 提现失败
+
+- [ ] 检查用户余额是否充足
+- [ ] 检查用户是否有openId
+- [ ] 检查微信商户API证书配置
+- [ ] 查看后端日志 `[Withdraw]`
+
+---
+
+**创建时间**:2026-01-25
+**更新时间**:2026-01-25
+**版本**:v3.1(新增实战经验库)
+**维护者**:卡若
diff --git a/开发文档/小程序管理/scripts/__pycache__/mp_api.cpython-314.pyc b/开发文档/小程序管理/scripts/__pycache__/mp_api.cpython-314.pyc
new file mode 100644
index 00000000..17805a5a
Binary files /dev/null and b/开发文档/小程序管理/scripts/__pycache__/mp_api.cpython-314.pyc differ
diff --git a/开发文档/小程序管理/scripts/apps_config.json b/开发文档/小程序管理/scripts/apps_config.json
new file mode 100644
index 00000000..eeb0a7d5
--- /dev/null
+++ b/开发文档/小程序管理/scripts/apps_config.json
@@ -0,0 +1,40 @@
+{
+ "_comment": "小程序配置文件 - 支持管理多个小程序",
+ "apps": [
+ {
+ "id": "soul-party",
+ "name": "Soul派对",
+ "appid": "wxb8bbb2b10dec74aa",
+ "project_path": "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram",
+ "private_key_path": "",
+ "api_domain": "https://soul.quwanzhi.com",
+ "description": "一场SOUL的创业实验场",
+ "certification": {
+ "status": "pending",
+ "enterprise_name": "泉州市卡若网络技术有限公司",
+ "license_number": "",
+ "legal_persona_name": "",
+ "legal_persona_wechat": "",
+ "component_phone": "15880802661"
+ }
+ }
+ ],
+ "certification_materials": {
+ "_comment": "企业认证通用材料(所有小程序共用)",
+ "enterprise_name": "泉州市卡若网络技术有限公司",
+ "license_number": "",
+ "license_media_id": "",
+ "legal_persona_name": "",
+ "legal_persona_wechat": "",
+ "legal_persona_idcard": "",
+ "component_phone": "15880802661",
+ "contact_email": "zhiqun@qq.com"
+ },
+ "third_party_platform": {
+ "_comment": "第三方平台配置(用于代认证)",
+ "component_appid": "",
+ "component_appsecret": "",
+ "component_verify_ticket": "",
+ "authorized": false
+ }
+}
diff --git a/开发文档/小程序管理/scripts/env_template.txt b/开发文档/小程序管理/scripts/env_template.txt
new file mode 100644
index 00000000..9f7686d6
--- /dev/null
+++ b/开发文档/小程序管理/scripts/env_template.txt
@@ -0,0 +1,30 @@
+# 微信小程序管理 - 环境变量配置
+# 复制此文件为 .env 并填入实际值
+
+# ==================== 方式一:直接使用 access_token ====================
+# 如果你已经有 access_token,直接填入即可(适合快速测试)
+ACCESS_TOKEN=你的access_token
+
+# ==================== 方式二:使用第三方平台凭证 ====================
+# 如果你有第三方平台资质,填入以下信息可自动刷新token
+
+# 第三方平台 AppID
+COMPONENT_APPID=你的第三方平台AppID
+
+# 第三方平台密钥
+COMPONENT_APPSECRET=你的第三方平台密钥
+
+# 授权小程序 AppID(要管理的小程序)
+AUTHORIZER_APPID=wxb8bbb2b10dec74aa
+
+# 授权刷新令牌(从授权回调中获取)
+AUTHORIZER_REFRESH_TOKEN=授权时获取的refresh_token
+
+# ==================== 小程序项目路径 ====================
+# 用于 CLI 工具操作
+MINIPROGRAM_PATH=/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram
+
+# ==================== 可选配置 ====================
+# 隐私协议联系方式(用于快速配置)
+CONTACT_EMAIL=zhiqun@qq.com
+CONTACT_PHONE=15880802661
diff --git a/开发文档/小程序管理/scripts/mp_api.py b/开发文档/小程序管理/scripts/mp_api.py
new file mode 100644
index 00000000..61fae3ae
--- /dev/null
+++ b/开发文档/小程序管理/scripts/mp_api.py
@@ -0,0 +1,635 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+微信小程序管理API封装
+支持:注册、配置、代码管理、审核、发布、数据分析
+"""
+
+import os
+import json
+import time
+import httpx
+from typing import Optional, Dict, Any, List
+from dataclasses import dataclass
+from pathlib import Path
+
+# 尝试加载dotenv(可选依赖)
+try:
+ from dotenv import load_dotenv
+ load_dotenv()
+except ImportError:
+ pass # dotenv不是必需的
+
+
+@dataclass
+class MiniProgramInfo:
+ """小程序基础信息"""
+ appid: str
+ nickname: str
+ head_image_url: str
+ signature: str
+ principal_name: str
+ realname_status: int # 1=已认证
+
+
+@dataclass
+class AuditStatus:
+ """审核状态"""
+ auditid: int
+ status: int # 0=成功,1=被拒,2=审核中,3=已撤回,4=延后
+ reason: Optional[str] = None
+ screenshot: Optional[str] = None
+
+ @property
+ def status_text(self) -> str:
+ status_map = {
+ 0: "✅ 审核成功",
+ 1: "❌ 审核被拒",
+ 2: "⏳ 审核中",
+ 3: "↩️ 已撤回",
+ 4: "⏸️ 审核延后"
+ }
+ return status_map.get(self.status, "未知状态")
+
+
+class MiniProgramAPI:
+ """微信小程序管理API"""
+
+ BASE_URL = "https://api.weixin.qq.com"
+
+ def __init__(
+ self,
+ component_appid: Optional[str] = None,
+ component_appsecret: Optional[str] = None,
+ authorizer_appid: Optional[str] = None,
+ access_token: Optional[str] = None
+ ):
+ """
+ 初始化API
+
+ Args:
+ component_appid: 第三方平台AppID
+ component_appsecret: 第三方平台密钥
+ authorizer_appid: 授权小程序AppID
+ access_token: 直接使用的access_token(如已获取)
+ """
+ self.component_appid = component_appid or os.getenv("COMPONENT_APPID")
+ self.component_appsecret = component_appsecret or os.getenv("COMPONENT_APPSECRET")
+ self.authorizer_appid = authorizer_appid or os.getenv("AUTHORIZER_APPID")
+ self._access_token = access_token or os.getenv("ACCESS_TOKEN")
+ self._token_expires_at = 0
+
+ self.client = httpx.Client(timeout=30.0)
+
+ @property
+ def access_token(self) -> str:
+ """获取access_token,如果过期则刷新"""
+ if self._access_token and time.time() < self._token_expires_at:
+ return self._access_token
+
+ # 如果没有配置刷新token的信息,直接返回现有token
+ if not self.component_appid:
+ return self._access_token or ""
+
+ # TODO: 实现token刷新逻辑
+ return self._access_token or ""
+
+ def set_access_token(self, token: str, expires_in: int = 7200):
+ """手动设置access_token"""
+ self._access_token = token
+ self._token_expires_at = time.time() + expires_in - 300 # 提前5分钟过期
+
+ def _request(
+ self,
+ method: str,
+ path: str,
+ params: Optional[Dict] = None,
+ json_data: Optional[Dict] = None,
+ **kwargs
+ ) -> Dict[str, Any]:
+ """发起API请求"""
+ url = f"{self.BASE_URL}{path}"
+
+ # 添加access_token
+ if params is None:
+ params = {}
+ if "access_token" not in params:
+ params["access_token"] = self.access_token
+
+ if method.upper() == "GET":
+ resp = self.client.get(url, params=params, **kwargs)
+ else:
+ resp = self.client.post(url, params=params, json=json_data, **kwargs)
+
+ # 解析响应
+ try:
+ result = resp.json()
+ except json.JSONDecodeError:
+ # 可能是二进制数据(如图片)
+ return {"_binary": resp.content}
+
+ # 检查错误
+ if result.get("errcode", 0) != 0:
+ raise APIError(result.get("errcode"), result.get("errmsg", "Unknown error"))
+
+ return result
+
+ # ==================== 基础信息 ====================
+
+ def get_basic_info(self) -> MiniProgramInfo:
+ """获取小程序基础信息"""
+ result = self._request("POST", "/cgi-bin/account/getaccountbasicinfo")
+ return MiniProgramInfo(
+ appid=result.get("appid", ""),
+ nickname=result.get("nickname", ""),
+ head_image_url=result.get("head_image_url", ""),
+ signature=result.get("signature", ""),
+ principal_name=result.get("principal_name", ""),
+ realname_status=result.get("realname_status", 0)
+ )
+
+ def modify_signature(self, signature: str) -> bool:
+ """修改简介(4-120字)"""
+ self._request("POST", "/cgi-bin/account/modifysignature", json_data={
+ "signature": signature
+ })
+ return True
+
+ # ==================== 域名配置 ====================
+
+ def get_domain(self) -> Dict[str, List[str]]:
+ """获取服务器域名配置"""
+ result = self._request("POST", "/wxa/modify_domain", json_data={
+ "action": "get"
+ })
+ return {
+ "requestdomain": result.get("requestdomain", []),
+ "wsrequestdomain": result.get("wsrequestdomain", []),
+ "uploaddomain": result.get("uploaddomain", []),
+ "downloaddomain": result.get("downloaddomain", [])
+ }
+
+ def set_domain(
+ self,
+ requestdomain: Optional[List[str]] = None,
+ wsrequestdomain: Optional[List[str]] = None,
+ uploaddomain: Optional[List[str]] = None,
+ downloaddomain: Optional[List[str]] = None
+ ) -> bool:
+ """设置服务器域名"""
+ data = {"action": "set"}
+ if requestdomain:
+ data["requestdomain"] = requestdomain
+ if wsrequestdomain:
+ data["wsrequestdomain"] = wsrequestdomain
+ if uploaddomain:
+ data["uploaddomain"] = uploaddomain
+ if downloaddomain:
+ data["downloaddomain"] = downloaddomain
+
+ self._request("POST", "/wxa/modify_domain", json_data=data)
+ return True
+
+ def get_webview_domain(self) -> List[str]:
+ """获取业务域名"""
+ result = self._request("POST", "/wxa/setwebviewdomain", json_data={
+ "action": "get"
+ })
+ return result.get("webviewdomain", [])
+
+ def set_webview_domain(self, webviewdomain: List[str]) -> bool:
+ """设置业务域名"""
+ self._request("POST", "/wxa/setwebviewdomain", json_data={
+ "action": "set",
+ "webviewdomain": webviewdomain
+ })
+ return True
+
+ # ==================== 隐私协议 ====================
+
+ def get_privacy_setting(self, privacy_ver: int = 2) -> Dict[str, Any]:
+ """获取隐私协议设置"""
+ result = self._request("POST", "/cgi-bin/component/getprivacysetting", json_data={
+ "privacy_ver": privacy_ver
+ })
+ return result
+
+ def set_privacy_setting(
+ self,
+ setting_list: List[Dict[str, str]],
+ contact_email: Optional[str] = None,
+ contact_phone: Optional[str] = None,
+ notice_method: str = "弹窗提示"
+ ) -> bool:
+ """
+ 设置隐私协议
+
+ Args:
+ setting_list: 隐私配置列表,如 [{"privacy_key": "UserInfo", "privacy_text": "用于展示头像"}]
+ contact_email: 联系邮箱
+ contact_phone: 联系电话
+ notice_method: 告知方式
+ """
+ data = {
+ "privacy_ver": 2,
+ "setting_list": setting_list
+ }
+
+ owner_setting = {"notice_method": notice_method}
+ if contact_email:
+ owner_setting["contact_email"] = contact_email
+ if contact_phone:
+ owner_setting["contact_phone"] = contact_phone
+ data["owner_setting"] = owner_setting
+
+ self._request("POST", "/cgi-bin/component/setprivacysetting", json_data=data)
+ return True
+
+ # ==================== 类目管理 ====================
+
+ def get_all_categories(self) -> List[Dict]:
+ """获取可选类目列表"""
+ result = self._request("GET", "/cgi-bin/wxopen/getallcategories")
+ return result.get("categories_list", {}).get("categories", [])
+
+ def get_category(self) -> List[Dict]:
+ """获取已设置的类目"""
+ result = self._request("GET", "/cgi-bin/wxopen/getcategory")
+ return result.get("categories", [])
+
+ def add_category(self, categories: List[Dict]) -> bool:
+ """
+ 添加类目
+
+ Args:
+ categories: 类目列表,如 [{"first": 1, "second": 2}]
+ """
+ self._request("POST", "/cgi-bin/wxopen/addcategory", json_data={
+ "categories": categories
+ })
+ return True
+
+ def delete_category(self, first: int, second: int) -> bool:
+ """删除类目"""
+ self._request("POST", "/cgi-bin/wxopen/deletecategory", json_data={
+ "first": first,
+ "second": second
+ })
+ return True
+
+ # ==================== 代码管理 ====================
+
+ def commit_code(
+ self,
+ template_id: int,
+ user_version: str,
+ user_desc: str,
+ ext_json: Optional[str] = None
+ ) -> bool:
+ """
+ 上传代码
+
+ Args:
+ template_id: 代码模板ID
+ user_version: 版本号
+ user_desc: 版本描述
+ ext_json: 扩展配置JSON字符串
+ """
+ data = {
+ "template_id": template_id,
+ "user_version": user_version,
+ "user_desc": user_desc
+ }
+ if ext_json:
+ data["ext_json"] = ext_json
+
+ self._request("POST", "/wxa/commit", json_data=data)
+ return True
+
+ def get_page(self) -> List[str]:
+ """获取已上传代码的页面列表"""
+ result = self._request("GET", "/wxa/get_page")
+ return result.get("page_list", [])
+
+ def get_qrcode(self, path: Optional[str] = None) -> bytes:
+ """
+ 获取体验版二维码
+
+ Args:
+ path: 页面路径,如 "pages/index/index"
+
+ Returns:
+ 二维码图片二进制数据
+ """
+ params = {"access_token": self.access_token}
+ if path:
+ params["path"] = path
+
+ resp = self.client.get(f"{self.BASE_URL}/wxa/get_qrcode", params=params)
+ return resp.content
+
+ # ==================== 审核管理 ====================
+
+ def submit_audit(
+ self,
+ item_list: Optional[List[Dict]] = None,
+ version_desc: Optional[str] = None,
+ feedback_info: Optional[str] = None
+ ) -> int:
+ """
+ 提交审核
+
+ Args:
+ item_list: 页面审核信息列表
+ version_desc: 版本说明
+ feedback_info: 反馈内容
+
+ Returns:
+ 审核单ID
+ """
+ data = {}
+ if item_list:
+ data["item_list"] = item_list
+ if version_desc:
+ data["version_desc"] = version_desc
+ if feedback_info:
+ data["feedback_info"] = feedback_info
+
+ result = self._request("POST", "/wxa/submit_audit", json_data=data)
+ return result.get("auditid", 0)
+
+ def get_audit_status(self, auditid: int) -> AuditStatus:
+ """查询审核状态"""
+ result = self._request("POST", "/wxa/get_auditstatus", json_data={
+ "auditid": auditid
+ })
+ return AuditStatus(
+ auditid=auditid,
+ status=result.get("status", -1),
+ reason=result.get("reason"),
+ screenshot=result.get("screenshot")
+ )
+
+ def get_latest_audit_status(self) -> AuditStatus:
+ """查询最新审核状态"""
+ result = self._request("GET", "/wxa/get_latest_auditstatus")
+ return AuditStatus(
+ auditid=result.get("auditid", 0),
+ status=result.get("status", -1),
+ reason=result.get("reason"),
+ screenshot=result.get("screenshot")
+ )
+
+ def undo_code_audit(self) -> bool:
+ """撤回审核(每天限1次)"""
+ self._request("GET", "/wxa/undocodeaudit")
+ return True
+
+ # ==================== 发布管理 ====================
+
+ def release(self) -> bool:
+ """发布已审核通过的版本"""
+ self._request("POST", "/wxa/release", json_data={})
+ return True
+
+ def revert_code_release(self) -> bool:
+ """版本回退(只能回退到上一版本)"""
+ self._request("GET", "/wxa/revertcoderelease")
+ return True
+
+ def get_revert_history(self) -> List[Dict]:
+ """获取可回退版本历史"""
+ result = self._request("GET", "/wxa/revertcoderelease", params={
+ "action": "get_history_version"
+ })
+ return result.get("version_list", [])
+
+ def gray_release(self, gray_percentage: int) -> bool:
+ """
+ 分阶段发布
+
+ Args:
+ gray_percentage: 灰度比例 1-100
+ """
+ self._request("POST", "/wxa/grayrelease", json_data={
+ "gray_percentage": gray_percentage
+ })
+ return True
+
+ # ==================== 小程序码 ====================
+
+ def get_wxacode(
+ self,
+ path: str,
+ width: int = 430,
+ auto_color: bool = False,
+ line_color: Optional[Dict[str, int]] = None,
+ is_hyaline: bool = False
+ ) -> bytes:
+ """
+ 获取小程序码(有限制,每个path最多10万个)
+
+ Args:
+ path: 页面路径,如 "pages/index/index?id=123"
+ width: 宽度 280-1280
+ auto_color: 自动配置线条颜色
+ line_color: 线条颜色 {"r": 0, "g": 0, "b": 0}
+ is_hyaline: 是否透明背景
+
+ Returns:
+ 二维码图片二进制数据
+ """
+ data = {
+ "path": path,
+ "width": width,
+ "auto_color": auto_color,
+ "is_hyaline": is_hyaline
+ }
+ if line_color:
+ data["line_color"] = line_color
+
+ resp = self.client.post(
+ f"{self.BASE_URL}/wxa/getwxacode",
+ params={"access_token": self.access_token},
+ json=data
+ )
+ return resp.content
+
+ def get_wxacode_unlimit(
+ self,
+ scene: str,
+ page: Optional[str] = None,
+ width: int = 430,
+ auto_color: bool = False,
+ line_color: Optional[Dict[str, int]] = None,
+ is_hyaline: bool = False
+ ) -> bytes:
+ """
+ 获取无限小程序码(推荐)
+
+ Args:
+ scene: 场景值,最长32字符,如 "user_id=123&from=share"
+ page: 页面路径,必须是已发布的页面
+ width: 宽度 280-1280
+ auto_color: 自动配置线条颜色
+ line_color: 线条颜色 {"r": 0, "g": 0, "b": 0}
+ is_hyaline: 是否透明背景
+
+ Returns:
+ 二维码图片二进制数据
+ """
+ data = {
+ "scene": scene,
+ "width": width,
+ "auto_color": auto_color,
+ "is_hyaline": is_hyaline
+ }
+ if page:
+ data["page"] = page
+ if line_color:
+ data["line_color"] = line_color
+
+ resp = self.client.post(
+ f"{self.BASE_URL}/wxa/getwxacodeunlimit",
+ params={"access_token": self.access_token},
+ json=data
+ )
+ return resp.content
+
+ def gen_short_link(
+ self,
+ page_url: str,
+ page_title: str,
+ is_permanent: bool = False
+ ) -> str:
+ """
+ 生成小程序短链接
+
+ Args:
+ page_url: 页面路径,如 "pages/index/index?id=123"
+ page_title: 页面标题
+ is_permanent: 是否永久有效
+
+ Returns:
+ 短链接
+ """
+ result = self._request("POST", "/wxa/genwxashortlink", json_data={
+ "page_url": page_url,
+ "page_title": page_title,
+ "is_permanent": is_permanent
+ })
+ return result.get("link", "")
+
+ # ==================== 数据分析 ====================
+
+ def get_daily_visit_trend(self, begin_date: str, end_date: str) -> List[Dict]:
+ """
+ 获取每日访问趋势
+
+ Args:
+ begin_date: 开始日期 YYYYMMDD
+ end_date: 结束日期 YYYYMMDD
+ """
+ result = self._request(
+ "POST",
+ "/datacube/getweanalysisappiddailyvisittrend",
+ json_data={"begin_date": begin_date, "end_date": end_date}
+ )
+ return result.get("list", [])
+
+ def get_user_portrait(self, begin_date: str, end_date: str) -> Dict:
+ """
+ 获取用户画像
+
+ Args:
+ begin_date: 开始日期 YYYYMMDD
+ end_date: 结束日期 YYYYMMDD
+ """
+ result = self._request(
+ "POST",
+ "/datacube/getweanalysisappiduserportrait",
+ json_data={"begin_date": begin_date, "end_date": end_date}
+ )
+ return result
+
+ # ==================== API配额 ====================
+
+ def get_api_quota(self, cgi_path: str) -> Dict:
+ """
+ 查询接口调用额度
+
+ Args:
+ cgi_path: 接口路径,如 "/wxa/getwxacode"
+ """
+ result = self._request("POST", "/cgi-bin/openapi/quota/get", json_data={
+ "cgi_path": cgi_path
+ })
+ return result.get("quota", {})
+
+ def clear_quota(self, appid: Optional[str] = None) -> bool:
+ """重置接口调用次数(每月限10次)"""
+ self._request("POST", "/cgi-bin/clear_quota", json_data={
+ "appid": appid or self.authorizer_appid
+ })
+ return True
+
+ def close(self):
+ """关闭连接"""
+ self.client.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
+
+
+class APIError(Exception):
+ """API错误"""
+
+ ERROR_CODES = {
+ -1: "系统繁忙",
+ 40001: "access_token无效",
+ 40002: "grant_type不正确",
+ 40013: "appid不正确",
+ 40029: "code无效",
+ 40125: "appsecret不正确",
+ 41002: "缺少appid参数",
+ 41004: "缺少appsecret参数",
+ 42001: "access_token过期",
+ 42007: "refresh_token过期",
+ 45009: "调用超过限制",
+ 61039: "代码检测任务未完成,请稍后再试",
+ 85006: "标签格式错误",
+ 85007: "页面路径错误",
+ 85009: "已有审核版本,请先撤回",
+ 85010: "版本输入错误",
+ 85011: "当前版本不能回退",
+ 85012: "无效的版本",
+ 85015: "该账号已有发布中的版本",
+ 85019: "没有审核版本",
+ 85020: "审核状态异常",
+ 85064: "找不到模板",
+ 85085: "该小程序不能被操作",
+ 85086: "小程序没有绑定任何类目",
+ 87013: "每天只能撤回1次审核",
+ 89020: "该小程序尚未认证",
+ 89248: "隐私协议内容不完整",
+ }
+
+ def __init__(self, code: int, message: str):
+ self.code = code
+ self.message = message
+ super().__init__(f"[{code}] {self.ERROR_CODES.get(code, message)}")
+
+
+# 便捷函数
+def create_api_from_env() -> MiniProgramAPI:
+ """从环境变量创建API实例"""
+ return MiniProgramAPI()
+
+
+if __name__ == "__main__":
+ # 测试
+ api = create_api_from_env()
+ print("API初始化成功")
diff --git a/开发文档/小程序管理/scripts/mp_deploy.py b/开发文档/小程序管理/scripts/mp_deploy.py
new file mode 100644
index 00000000..22f859f4
--- /dev/null
+++ b/开发文档/小程序管理/scripts/mp_deploy.py
@@ -0,0 +1,725 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+小程序一键部署工具 v2.0
+
+功能:
+- 多小程序管理
+- 一键部署上线
+- 自动认证提交
+- 认证状态检查
+- 材料有效性验证
+
+使用方法:
+ python mp_deploy.py list # 列出所有小程序
+ python mp_deploy.py add # 添加新小程序
+ python mp_deploy.py deploy # 一键部署
+ python mp_deploy.py cert # 提交认证
+ python mp_deploy.py cert-status # 查询认证状态
+ python mp_deploy.py upload # 上传代码
+ python mp_deploy.py release # 发布上线
+"""
+
+import os
+import sys
+import json
+import subprocess
+import argparse
+from datetime import datetime
+from pathlib import Path
+from typing import Optional, Dict, List, Any
+from dataclasses import dataclass, asdict
+
+# 配置文件路径
+CONFIG_FILE = Path(__file__).parent / "apps_config.json"
+
+
+@dataclass
+class AppConfig:
+ """小程序配置"""
+ id: str
+ name: str
+ appid: str
+ project_path: str
+ private_key_path: str = ""
+ api_domain: str = ""
+ description: str = ""
+ certification: Dict = None
+
+ def __post_init__(self):
+ if self.certification is None:
+ self.certification = {
+ "status": "unknown",
+ "enterprise_name": "",
+ "license_number": "",
+ "legal_persona_name": "",
+ "legal_persona_wechat": "",
+ "component_phone": ""
+ }
+
+
+class ConfigManager:
+ """配置管理器"""
+
+ def __init__(self, config_file: Path = CONFIG_FILE):
+ self.config_file = config_file
+ self.config = self._load_config()
+
+ def _load_config(self) -> Dict:
+ """加载配置"""
+ if self.config_file.exists():
+ with open(self.config_file, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ return {"apps": [], "certification_materials": {}, "third_party_platform": {}}
+
+ def _save_config(self):
+ """保存配置"""
+ with open(self.config_file, 'w', encoding='utf-8') as f:
+ json.dump(self.config, f, ensure_ascii=False, indent=2)
+
+ def get_apps(self) -> List[AppConfig]:
+ """获取所有小程序"""
+ return [AppConfig(**app) for app in self.config.get("apps", [])]
+
+ def get_app(self, app_id: str) -> Optional[AppConfig]:
+ """获取指定小程序"""
+ for app in self.config.get("apps", []):
+ if app["id"] == app_id or app["appid"] == app_id:
+ return AppConfig(**app)
+ return None
+
+ def add_app(self, app: AppConfig):
+ """添加小程序"""
+ apps = self.config.get("apps", [])
+ # 检查是否已存在
+ for i, existing in enumerate(apps):
+ if existing["id"] == app.id:
+ apps[i] = asdict(app)
+ self.config["apps"] = apps
+ self._save_config()
+ return
+ apps.append(asdict(app))
+ self.config["apps"] = apps
+ self._save_config()
+
+ def update_app(self, app_id: str, updates: Dict):
+ """更新小程序配置"""
+ apps = self.config.get("apps", [])
+ for i, app in enumerate(apps):
+ if app["id"] == app_id:
+ apps[i].update(updates)
+ self.config["apps"] = apps
+ self._save_config()
+ return True
+ return False
+
+ def get_cert_materials(self) -> Dict:
+ """获取通用认证材料"""
+ return self.config.get("certification_materials", {})
+
+ def update_cert_materials(self, materials: Dict):
+ """更新认证材料"""
+ self.config["certification_materials"] = materials
+ self._save_config()
+
+
+class MiniProgramDeployer:
+ """小程序部署器"""
+
+ # 微信开发者工具CLI路径
+ WX_CLI = "/Applications/wechatwebdevtools.app/Contents/MacOS/cli"
+
+ def __init__(self):
+ self.config = ConfigManager()
+
+ def _check_wx_cli(self) -> bool:
+ """检查微信开发者工具是否安装"""
+ return os.path.exists(self.WX_CLI)
+
+ def _run_cli(self, *args, project_path: str = None) -> tuple:
+ """运行CLI命令"""
+ cmd = [self.WX_CLI] + list(args)
+ if project_path:
+ cmd.extend(["--project", project_path])
+
+ try:
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
+ return result.returncode == 0, result.stdout + result.stderr
+ except subprocess.TimeoutExpired:
+ return False, "命令执行超时"
+ except Exception as e:
+ return False, str(e)
+
+ def list_apps(self):
+ """列出所有小程序"""
+ apps = self.config.get_apps()
+
+ if not apps:
+ print("\n📭 暂无配置的小程序")
+ print(" 运行 'python mp_deploy.py add' 添加小程序")
+ return
+
+ print("\n" + "=" * 60)
+ print(" 📱 小程序列表")
+ print("=" * 60)
+
+ for i, app in enumerate(apps, 1):
+ cert_status = app.certification.get("status", "unknown")
+ status_icon = {
+ "verified": "✅",
+ "pending": "⏳",
+ "rejected": "❌",
+ "expired": "⚠️",
+ "unknown": "❓"
+ }.get(cert_status, "❓")
+
+ print(f"\n [{i}] {app.name}")
+ print(f" ID: {app.id}")
+ print(f" AppID: {app.appid}")
+ print(f" 认证: {status_icon} {cert_status}")
+ print(f" 路径: {app.project_path}")
+
+ print("\n" + "-" * 60)
+ print(" 使用方法:")
+ print(" python mp_deploy.py deploy 一键部署")
+ print(" python mp_deploy.py cert 提交认证")
+ print("=" * 60 + "\n")
+
+ def add_app(self):
+ """交互式添加小程序"""
+ print("\n" + "=" * 50)
+ print(" ➕ 添加新小程序")
+ print("=" * 50 + "\n")
+
+ # 收集信息
+ app_id = input("小程序ID(用于标识,如 my-app): ").strip()
+ if not app_id:
+ print("❌ ID不能为空")
+ return
+
+ name = input("小程序名称: ").strip()
+ appid = input("AppID(如 wx1234567890): ").strip()
+ project_path = input("项目路径: ").strip()
+
+ if not os.path.exists(project_path):
+ print(f"⚠️ 警告:路径不存在 {project_path}")
+
+ api_domain = input("API域名(可选): ").strip()
+ description = input("描述(可选): ").strip()
+
+ # 认证信息
+ print("\n📋 认证信息(可稍后配置):")
+ enterprise_name = input("企业名称: ").strip()
+
+ app = AppConfig(
+ id=app_id,
+ name=name,
+ appid=appid,
+ project_path=project_path,
+ api_domain=api_domain,
+ description=description,
+ certification={
+ "status": "unknown",
+ "enterprise_name": enterprise_name,
+ "license_number": "",
+ "legal_persona_name": "",
+ "legal_persona_wechat": "",
+ "component_phone": "15880802661"
+ }
+ )
+
+ self.config.add_app(app)
+ print(f"\n✅ 小程序 [{name}] 添加成功!")
+
+ def deploy(self, app_id: str, skip_cert_check: bool = False):
+ """一键部署流程"""
+ app = self.config.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ print(" 运行 'python mp_deploy.py list' 查看所有小程序")
+ return False
+
+ print("\n" + "=" * 60)
+ print(f" 🚀 一键部署: {app.name}")
+ print("=" * 60)
+
+ steps = [
+ ("检查环境", self._step_check_env),
+ ("检查认证状态", lambda a: self._step_check_cert(a, skip_cert_check)),
+ ("编译项目", self._step_build),
+ ("上传代码", self._step_upload),
+ ("提交审核", self._step_submit_audit),
+ ]
+
+ for step_name, step_func in steps:
+ print(f"\n📍 步骤: {step_name}")
+ print("-" * 40)
+
+ success = step_func(app)
+ if not success:
+ print(f"\n❌ 部署中断于: {step_name}")
+ return False
+
+ print("\n" + "=" * 60)
+ print(" 🎉 部署完成!")
+ print("=" * 60)
+ print(f"\n 下一步操作:")
+ print(f" 1. 等待审核(通常1-3个工作日)")
+ print(f" 2. 审核通过后运行: python mp_deploy.py release {app_id}")
+ print(f" 3. 查看状态: python mp_deploy.py status {app_id}")
+
+ return True
+
+ def _step_check_env(self, app: AppConfig) -> bool:
+ """检查环境"""
+ # 检查微信开发者工具
+ if not self._check_wx_cli():
+ print("❌ 未找到微信开发者工具")
+ print(" 请安装: https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html")
+ return False
+ print("✅ 微信开发者工具已安装")
+
+ # 检查项目路径
+ if not os.path.exists(app.project_path):
+ print(f"❌ 项目路径不存在: {app.project_path}")
+ return False
+ print(f"✅ 项目路径存在")
+
+ # 检查project.config.json
+ config_file = os.path.join(app.project_path, "project.config.json")
+ if os.path.exists(config_file):
+ with open(config_file, 'r') as f:
+ config = json.load(f)
+ if config.get("appid") != app.appid:
+ print(f"⚠️ 警告: project.config.json中的AppID与配置不一致")
+ print(f" 配置: {app.appid}")
+ print(f" 文件: {config.get('appid')}")
+
+ print("✅ 环境检查通过")
+ return True
+
+ def _step_check_cert(self, app: AppConfig, skip: bool = False) -> bool:
+ """检查认证状态"""
+ if skip:
+ print("⏭️ 跳过认证检查")
+ return True
+
+ cert_status = app.certification.get("status", "unknown")
+
+ if cert_status == "verified":
+ print("✅ 已完成微信认证")
+ return True
+
+ if cert_status == "pending":
+ print("⏳ 认证审核中")
+ print(" 可选择:")
+ print(" 1. 继续部署(未认证可上传,但无法发布)")
+ print(" 2. 等待认证完成")
+
+ choice = input("\n是否继续? (y/n): ").strip().lower()
+ return choice == 'y'
+
+ if cert_status == "expired":
+ print("⚠️ 认证已过期,需要重新认证")
+ print(" 运行: python mp_deploy.py cert " + app.id)
+
+ choice = input("\n是否继续部署? (y/n): ").strip().lower()
+ return choice == 'y'
+
+ # 未认证或未知状态
+ print("⚠️ 未完成微信认证")
+ print(" 未认证的小程序可以上传代码,但无法发布上线")
+ print(" 运行: python mp_deploy.py cert " + app.id + " 提交认证")
+
+ choice = input("\n是否继续? (y/n): ").strip().lower()
+ return choice == 'y'
+
+ def _step_build(self, app: AppConfig) -> bool:
+ """编译项目"""
+ print("📦 编译项目...")
+
+ # 使用CLI编译
+ success, output = self._run_cli("build-npm", project_path=app.project_path)
+
+ if not success:
+ # build-npm可能失败(如果没有npm依赖),不算错误
+ print("ℹ️ 编译完成(无npm依赖或编译失败,继续...)")
+ else:
+ print("✅ 编译成功")
+
+ return True
+
+ def _step_upload(self, app: AppConfig) -> bool:
+ """上传代码"""
+ # 获取版本号
+ version = datetime.now().strftime("%Y.%m.%d.%H%M")
+ desc = f"自动部署 - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
+
+ print(f"📤 上传代码...")
+ print(f" 版本: {version}")
+ print(f" 描述: {desc}")
+
+ success, output = self._run_cli(
+ "upload",
+ "--version", version,
+ "--desc", desc,
+ project_path=app.project_path
+ )
+
+ if not success:
+ print(f"❌ 上传失败")
+ print(f" {output}")
+
+ # 常见错误处理
+ if "login" in output.lower():
+ print("\n💡 提示: 请在微信开发者工具中登录后重试")
+ return False
+
+ print("✅ 上传成功")
+ return True
+
+ def _step_submit_audit(self, app: AppConfig) -> bool:
+ """提交审核"""
+ print("📝 提交审核...")
+
+ # 使用CLI提交审核
+ success, output = self._run_cli(
+ "submit-audit",
+ project_path=app.project_path
+ )
+
+ if not success:
+ if "未认证" in output or "认证" in output:
+ print("⚠️ 提交审核失败:未完成微信认证")
+ print(" 代码已上传,但需要完成认证后才能提交审核")
+ print(f" 运行: python mp_deploy.py cert {app.id}")
+ return True # 不算失败,只是需要认证
+
+ print(f"❌ 提交审核失败")
+ print(f" {output}")
+ return False
+
+ print("✅ 审核已提交")
+ return True
+
+ def submit_certification(self, app_id: str):
+ """提交企业认证"""
+ app = self.config.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ return
+
+ print("\n" + "=" * 60)
+ print(f" 📋 提交认证: {app.name}")
+ print("=" * 60)
+
+ # 获取通用认证材料
+ materials = self.config.get_cert_materials()
+ cert = app.certification
+
+ # 合并材料(小程序配置优先)
+ enterprise_name = cert.get("enterprise_name") or materials.get("enterprise_name", "")
+
+ print(f"\n📌 认证信息:")
+ print(f" 小程序: {app.name} ({app.appid})")
+ print(f" 企业名称: {enterprise_name}")
+
+ # 检查必要材料
+ missing = []
+ if not enterprise_name:
+ missing.append("企业名称")
+ if not materials.get("license_number"):
+ missing.append("营业执照号")
+ if not materials.get("legal_persona_name"):
+ missing.append("法人姓名")
+
+ if missing:
+ print(f"\n⚠️ 缺少认证材料:")
+ for m in missing:
+ print(f" - {m}")
+
+ print(f"\n请先完善认证材料:")
+ print(f" 编辑: {self.config.config_file}")
+ print(f" 或运行: python mp_deploy.py cert-config")
+ return
+
+ print("\n" + "-" * 40)
+ print("📋 认证方式说明:")
+ print("-" * 40)
+ print("""
+ 【方式一】微信后台手动认证(推荐)
+
+ 1. 登录小程序后台: https://mp.weixin.qq.com/
+ 2. 设置 → 基本设置 → 微信认证
+ 3. 选择"企业"类型
+ 4. 填写企业信息、上传营业执照
+ 5. 法人微信扫码验证
+ 6. 支付认证费用(300元/年)
+ 7. 等待审核(1-5个工作日)
+
+ 【方式二】通过第三方平台代认证(需开发)
+
+ 如果你有第三方平台资质,可以通过API代认证:
+ 1. 配置第三方平台凭证
+ 2. 获取授权
+ 3. 调用认证API
+
+ API接口: POST /wxa/sec/wxaauth
+ """)
+
+ print("\n" + "-" * 40)
+ print("📝 认证材料清单:")
+ print("-" * 40)
+ print("""
+ 必需材料:
+ ☐ 企业营业执照(扫描件或照片)
+ ☐ 法人身份证(正反面)
+ ☐ 法人微信号(用于扫码验证)
+ ☐ 联系人手机号
+ ☐ 认证费用 300元
+
+ 认证有效期: 1年
+ 到期后需重新认证(年审)
+ """)
+
+ # 更新状态为待认证
+ self.config.update_app(app_id, {
+ "certification": {
+ **cert,
+ "status": "pending",
+ "submit_time": datetime.now().isoformat()
+ }
+ })
+
+ print("\n✅ 已标记为待认证状态")
+ print(" 完成认证后运行: python mp_deploy.py cert-done " + app_id)
+
+ def check_cert_status(self, app_id: str):
+ """检查认证状态"""
+ app = self.config.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ return
+
+ print("\n" + "=" * 60)
+ print(f" 🔍 认证状态: {app.name}")
+ print("=" * 60)
+
+ cert = app.certification
+ status = cert.get("status", "unknown")
+
+ status_info = {
+ "verified": ("✅ 已认证", "认证有效"),
+ "pending": ("⏳ 审核中", "请等待审核结果"),
+ "rejected": ("❌ 被拒绝", "请查看拒绝原因并重新提交"),
+ "expired": ("⚠️ 已过期", "需要重新认证(年审)"),
+ "unknown": ("❓ 未知", "请在微信后台确认状态")
+ }
+
+ icon, desc = status_info.get(status, ("❓", "未知状态"))
+
+ print(f"\n📌 当前状态: {icon}")
+ print(f" 说明: {desc}")
+ print(f" 企业: {cert.get('enterprise_name', '未填写')}")
+
+ if cert.get("submit_time"):
+ print(f" 提交时间: {cert.get('submit_time')}")
+
+ if cert.get("verify_time"):
+ print(f" 认证时间: {cert.get('verify_time')}")
+
+ if cert.get("expire_time"):
+ print(f" 到期时间: {cert.get('expire_time')}")
+
+ # 提示下一步操作
+ print("\n" + "-" * 40)
+ if status == "unknown" or status == "rejected":
+ print("👉 下一步: python mp_deploy.py cert " + app_id)
+ elif status == "pending":
+ print("👉 等待审核,通常1-5个工作日")
+ print(" 审核通过后运行: python mp_deploy.py cert-done " + app_id)
+ elif status == "verified":
+ print("👉 可以发布小程序: python mp_deploy.py deploy " + app_id)
+ elif status == "expired":
+ print("👉 需要重新认证: python mp_deploy.py cert " + app_id)
+
+ def mark_cert_done(self, app_id: str):
+ """标记认证完成"""
+ app = self.config.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ return
+
+ cert = app.certification
+ self.config.update_app(app_id, {
+ "certification": {
+ **cert,
+ "status": "verified",
+ "verify_time": datetime.now().isoformat(),
+ "expire_time": datetime.now().replace(year=datetime.now().year + 1).isoformat()
+ }
+ })
+
+ print(f"✅ 已标记 [{app.name}] 认证完成")
+ print(f" 有效期至: {datetime.now().year + 1}年")
+
+ def release(self, app_id: str):
+ """发布上线"""
+ app = self.config.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ return
+
+ print("\n" + "=" * 60)
+ print(f" 🎉 发布上线: {app.name}")
+ print("=" * 60)
+
+ # 检查认证状态
+ if app.certification.get("status") != "verified":
+ print("\n⚠️ 警告: 小程序未完成认证")
+ print(" 未认证的小程序无法发布上线")
+
+ choice = input("\n是否继续尝试? (y/n): ").strip().lower()
+ if choice != 'y':
+ return
+
+ print("\n📦 正在发布...")
+
+ # 尝试使用CLI发布
+ success, output = self._run_cli("release", project_path=app.project_path)
+
+ if success:
+ print("\n🎉 发布成功!小程序已上线")
+ else:
+ print(f"\n发布结果: {output}")
+
+ if "认证" in output:
+ print("\n💡 提示: 请先完成微信认证")
+ print(f" 运行: python mp_deploy.py cert {app_id}")
+ else:
+ print("\n💡 提示: 请在微信后台手动发布")
+ print(" 1. 登录 https://mp.weixin.qq.com/")
+ print(" 2. 版本管理 → 审核版本 → 发布")
+
+ def quick_upload(self, app_id: str, version: str = None, desc: str = None):
+ """快速上传代码"""
+ app = self.config.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ return
+
+ if not version:
+ version = datetime.now().strftime("%Y.%m.%d.%H%M")
+ if not desc:
+ desc = f"快速上传 - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
+
+ print(f"\n📤 上传代码: {app.name}")
+ print(f" 版本: {version}")
+ print(f" 描述: {desc}")
+
+ success, output = self._run_cli(
+ "upload",
+ "--version", version,
+ "--desc", desc,
+ project_path=app.project_path
+ )
+
+ if success:
+ print("✅ 上传成功")
+ else:
+ print(f"❌ 上传失败: {output}")
+
+
+def print_header(title: str):
+ print("\n" + "=" * 50)
+ print(f" {title}")
+ print("=" * 50)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="小程序一键部署工具 v2.0",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+示例:
+ python mp_deploy.py list 列出所有小程序
+ python mp_deploy.py add 添加新小程序
+ python mp_deploy.py deploy soul-party 一键部署
+ python mp_deploy.py cert soul-party 提交认证
+ python mp_deploy.py cert-status soul-party 查询认证状态
+ python mp_deploy.py cert-done soul-party 标记认证完成
+ python mp_deploy.py upload soul-party 仅上传代码
+ python mp_deploy.py release soul-party 发布上线
+
+部署流程:
+ 1. add 添加小程序配置
+ 2. cert 提交企业认证(首次)
+ 3. cert-done 认证通过后标记
+ 4. deploy 一键部署(编译+上传+提审)
+ 5. release 审核通过后发布
+"""
+ )
+
+ subparsers = parser.add_subparsers(dest="command", help="子命令")
+
+ # list
+ subparsers.add_parser("list", help="列出所有小程序")
+
+ # add
+ subparsers.add_parser("add", help="添加新小程序")
+
+ # deploy
+ deploy_parser = subparsers.add_parser("deploy", help="一键部署")
+ deploy_parser.add_argument("app_id", help="小程序ID")
+ deploy_parser.add_argument("--skip-cert", action="store_true", help="跳过认证检查")
+
+ # cert
+ cert_parser = subparsers.add_parser("cert", help="提交认证")
+ cert_parser.add_argument("app_id", help="小程序ID")
+
+ # cert-status
+ cert_status_parser = subparsers.add_parser("cert-status", help="查询认证状态")
+ cert_status_parser.add_argument("app_id", help="小程序ID")
+
+ # cert-done
+ cert_done_parser = subparsers.add_parser("cert-done", help="标记认证完成")
+ cert_done_parser.add_argument("app_id", help="小程序ID")
+
+ # upload
+ upload_parser = subparsers.add_parser("upload", help="上传代码")
+ upload_parser.add_argument("app_id", help="小程序ID")
+ upload_parser.add_argument("-v", "--version", help="版本号")
+ upload_parser.add_argument("-d", "--desc", help="版本描述")
+
+ # release
+ release_parser = subparsers.add_parser("release", help="发布上线")
+ release_parser.add_argument("app_id", help="小程序ID")
+
+ args = parser.parse_args()
+
+ if not args.command:
+ parser.print_help()
+ return
+
+ deployer = MiniProgramDeployer()
+
+ commands = {
+ "list": lambda: deployer.list_apps(),
+ "add": lambda: deployer.add_app(),
+ "deploy": lambda: deployer.deploy(args.app_id, args.skip_cert if hasattr(args, 'skip_cert') else False),
+ "cert": lambda: deployer.submit_certification(args.app_id),
+ "cert-status": lambda: deployer.check_cert_status(args.app_id),
+ "cert-done": lambda: deployer.mark_cert_done(args.app_id),
+ "upload": lambda: deployer.quick_upload(args.app_id, getattr(args, 'version', None), getattr(args, 'desc', None)),
+ "release": lambda: deployer.release(args.app_id),
+ }
+
+ cmd_func = commands.get(args.command)
+ if cmd_func:
+ cmd_func()
+ else:
+ parser.print_help()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/开发文档/小程序管理/scripts/mp_full.py b/开发文档/小程序管理/scripts/mp_full.py
new file mode 100644
index 00000000..2a754a2a
--- /dev/null
+++ b/开发文档/小程序管理/scripts/mp_full.py
@@ -0,0 +1,555 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+小程序全能管理工具 v3.0
+
+整合能力:
+- 微信开发者工具CLI
+- miniprogram-ci (npm官方工具)
+- 微信开放平台API
+- 多小程序管理
+- 自动化部署+提审
+- 汇总报告生成
+
+使用方法:
+ python mp_full.py report # 生成汇总报告
+ python mp_full.py check # 检查项目问题
+ python mp_full.py auto # 全自动部署(上传+提审)
+ python mp_full.py batch-report # 批量生成所有小程序报告
+"""
+
+import os
+import sys
+import json
+import subprocess
+import argparse
+from datetime import datetime
+from pathlib import Path
+from typing import Optional, Dict, List, Any
+from dataclasses import dataclass, asdict, field
+
+# 配置文件路径
+SCRIPT_DIR = Path(__file__).parent
+CONFIG_FILE = SCRIPT_DIR / "apps_config.json"
+REPORT_DIR = SCRIPT_DIR / "reports"
+
+
+@dataclass
+class CheckResult:
+ """检查结果"""
+ name: str
+ status: str # ok, warning, error
+ message: str
+ fix_hint: str = ""
+
+
+@dataclass
+class AppReport:
+ """小程序报告"""
+ app_id: str
+ app_name: str
+ appid: str
+ check_time: str
+ checks: List[CheckResult] = field(default_factory=list)
+ summary: Dict = field(default_factory=dict)
+
+ @property
+ def has_errors(self) -> bool:
+ return any(c.status == "error" for c in self.checks)
+
+ @property
+ def has_warnings(self) -> bool:
+ return any(c.status == "warning" for c in self.checks)
+
+
+class MiniProgramManager:
+ """小程序全能管理器"""
+
+ # 工具路径
+ WX_CLI = "/Applications/wechatwebdevtools.app/Contents/MacOS/cli"
+
+ def __init__(self):
+ self.config = self._load_config()
+ REPORT_DIR.mkdir(exist_ok=True)
+
+ def _load_config(self) -> Dict:
+ if CONFIG_FILE.exists():
+ with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ return {"apps": []}
+
+ def _save_config(self):
+ with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
+ json.dump(self.config, f, ensure_ascii=False, indent=2)
+
+ def get_app(self, app_id: str) -> Optional[Dict]:
+ for app in self.config.get("apps", []):
+ if app["id"] == app_id or app["appid"] == app_id:
+ return app
+ return None
+
+ def _run_cmd(self, cmd: List[str], timeout: int = 120) -> tuple:
+ """运行命令"""
+ try:
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
+ return result.returncode == 0, result.stdout + result.stderr
+ except subprocess.TimeoutExpired:
+ return False, "命令执行超时"
+ except Exception as e:
+ return False, str(e)
+
+ def _check_tool(self, tool: str) -> bool:
+ """检查工具是否可用"""
+ success, _ = self._run_cmd(["which", tool], timeout=5)
+ return success
+
+ # ==================== 检查功能 ====================
+
+ def check_project(self, app_id: str) -> AppReport:
+ """检查项目问题"""
+ app = self.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ return None
+
+ report = AppReport(
+ app_id=app["id"],
+ app_name=app["name"],
+ appid=app["appid"],
+ check_time=datetime.now().isoformat()
+ )
+
+ project_path = app["project_path"]
+
+ # 1. 检查项目路径
+ if os.path.exists(project_path):
+ report.checks.append(CheckResult("项目路径", "ok", f"路径存在: {project_path}"))
+ else:
+ report.checks.append(CheckResult("项目路径", "error", f"路径不存在: {project_path}", "请检查项目路径配置"))
+ return report # 路径不存在,无法继续检查
+
+ # 2. 检查project.config.json
+ config_file = os.path.join(project_path, "project.config.json")
+ if os.path.exists(config_file):
+ with open(config_file, 'r') as f:
+ config = json.load(f)
+
+ if config.get("appid") == app["appid"]:
+ report.checks.append(CheckResult("AppID配置", "ok", f"AppID正确: {app['appid']}"))
+ else:
+ report.checks.append(CheckResult("AppID配置", "error",
+ f"AppID不匹配: 配置={app['appid']}, 文件={config.get('appid')}",
+ "请修改project.config.json中的appid"))
+ else:
+ report.checks.append(CheckResult("项目配置", "error", "project.config.json不存在", "请确认这是有效的小程序项目"))
+
+ # 3. 检查app.js
+ app_js = os.path.join(project_path, "app.js")
+ if os.path.exists(app_js):
+ with open(app_js, 'r') as f:
+ content = f.read()
+
+ # 检查API域名
+ if "baseUrl" in content or "apiBase" in content:
+ if "https://" in content:
+ report.checks.append(CheckResult("API域名", "ok", "已配置HTTPS域名"))
+ elif "http://localhost" in content:
+ report.checks.append(CheckResult("API域名", "warning", "使用本地开发地址", "发布前请更换为HTTPS域名"))
+ else:
+ report.checks.append(CheckResult("API域名", "warning", "未检测到HTTPS域名"))
+
+ report.checks.append(CheckResult("入口文件", "ok", "app.js存在"))
+ else:
+ report.checks.append(CheckResult("入口文件", "error", "app.js不存在"))
+
+ # 4. 检查app.json
+ app_json = os.path.join(project_path, "app.json")
+ if os.path.exists(app_json):
+ with open(app_json, 'r') as f:
+ app_config = json.load(f)
+
+ pages = app_config.get("pages", [])
+ if pages:
+ report.checks.append(CheckResult("页面配置", "ok", f"共{len(pages)}个页面"))
+ else:
+ report.checks.append(CheckResult("页面配置", "error", "没有配置页面"))
+
+ # 检查隐私配置
+ if app_config.get("__usePrivacyCheck__"):
+ report.checks.append(CheckResult("隐私配置", "ok", "已启用隐私检查"))
+ else:
+ report.checks.append(CheckResult("隐私配置", "warning", "未启用隐私检查", "建议添加 __usePrivacyCheck__: true"))
+ else:
+ report.checks.append(CheckResult("应用配置", "error", "app.json不存在"))
+
+ # 5. 检查认证状态
+ cert_status = app.get("certification", {}).get("status", "unknown")
+ if cert_status == "verified":
+ report.checks.append(CheckResult("企业认证", "ok", "已完成认证"))
+ elif cert_status == "pending":
+ report.checks.append(CheckResult("企业认证", "warning", "认证审核中", "等待审核结果"))
+ elif cert_status == "expired":
+ report.checks.append(CheckResult("企业认证", "error", "认证已过期", "请尽快完成年审"))
+ else:
+ report.checks.append(CheckResult("企业认证", "warning", "未认证", "无法发布上线,请先完成认证"))
+
+ # 6. 检查开发工具
+ if os.path.exists(self.WX_CLI):
+ report.checks.append(CheckResult("开发者工具", "ok", "微信开发者工具已安装"))
+ else:
+ report.checks.append(CheckResult("开发者工具", "error", "微信开发者工具未安装"))
+
+ # 7. 检查miniprogram-ci
+ if self._check_tool("miniprogram-ci"):
+ report.checks.append(CheckResult("miniprogram-ci", "ok", "npm工具已安装"))
+ else:
+ report.checks.append(CheckResult("miniprogram-ci", "warning", "miniprogram-ci未安装", "运行: npm install -g miniprogram-ci"))
+
+ # 8. 检查私钥
+ if app.get("private_key_path") and os.path.exists(app["private_key_path"]):
+ report.checks.append(CheckResult("上传密钥", "ok", "私钥文件存在"))
+ else:
+ report.checks.append(CheckResult("上传密钥", "warning", "未配置私钥", "在小程序后台下载代码上传密钥"))
+
+ # 生成汇总
+ ok_count = sum(1 for c in report.checks if c.status == "ok")
+ warn_count = sum(1 for c in report.checks if c.status == "warning")
+ error_count = sum(1 for c in report.checks if c.status == "error")
+
+ report.summary = {
+ "total": len(report.checks),
+ "ok": ok_count,
+ "warning": warn_count,
+ "error": error_count,
+ "can_deploy": error_count == 0,
+ "can_release": cert_status == "verified" and error_count == 0
+ }
+
+ return report
+
+ def print_report(self, report: AppReport):
+ """打印报告"""
+ print("\n" + "=" * 70)
+ print(f" 📊 项目检查报告: {report.app_name}")
+ print("=" * 70)
+ print(f" AppID: {report.appid}")
+ print(f" 检查时间: {report.check_time}")
+ print("-" * 70)
+
+ status_icons = {"ok": "✅", "warning": "⚠️", "error": "❌"}
+
+ for check in report.checks:
+ icon = status_icons.get(check.status, "❓")
+ print(f" {icon} {check.name}: {check.message}")
+ if check.fix_hint:
+ print(f" 💡 {check.fix_hint}")
+
+ print("-" * 70)
+ s = report.summary
+ print(f" 📈 汇总: 通过 {s['ok']} / 警告 {s['warning']} / 错误 {s['error']}")
+
+ if s['can_release']:
+ print(" 🎉 状态: 可以发布上线")
+ elif s['can_deploy']:
+ print(" 📦 状态: 可以上传代码,但无法发布(需完成认证)")
+ else:
+ print(" 🚫 状态: 存在错误,请先修复")
+
+ print("=" * 70 + "\n")
+
+ def save_report(self, report: AppReport):
+ """保存报告到文件"""
+ filename = f"report_{report.app_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+ filepath = REPORT_DIR / filename
+
+ with open(filepath, 'w', encoding='utf-8') as f:
+ json.dump(asdict(report), f, ensure_ascii=False, indent=2)
+
+ return filepath
+
+ # ==================== 自动化部署 ====================
+
+ def auto_deploy(self, app_id: str, version: str = None, desc: str = None, submit_audit: bool = True) -> bool:
+ """全自动部署:编译 → 上传 → 提审"""
+ app = self.get_app(app_id)
+ if not app:
+ print(f"❌ 未找到小程序: {app_id}")
+ return False
+
+ print("\n" + "=" * 70)
+ print(f" 🚀 全自动部署: {app['name']}")
+ print("=" * 70)
+
+ # 1. 先检查项目
+ print("\n📋 步骤1: 检查项目...")
+ report = self.check_project(app_id)
+ if report.has_errors:
+ print("❌ 项目存在错误,无法部署")
+ self.print_report(report)
+ return False
+ print("✅ 项目检查通过")
+
+ # 2. 准备版本信息
+ if not version:
+ version = datetime.now().strftime("%Y.%m.%d.%H%M")
+ if not desc:
+ desc = f"自动部署 - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
+
+ print(f"\n📦 步骤2: 上传代码...")
+ print(f" 版本: {version}")
+ print(f" 描述: {desc}")
+
+ # 3. 上传代码
+ success = self._upload_code(app, version, desc)
+ if not success:
+ print("❌ 代码上传失败")
+ return False
+ print("✅ 代码上传成功")
+
+ # 4. 提交审核
+ if submit_audit:
+ print(f"\n📝 步骤3: 提交审核...")
+ cert_status = app.get("certification", {}).get("status", "unknown")
+
+ if cert_status != "verified":
+ print(f"⚠️ 认证状态: {cert_status}")
+ print(" 未认证的小程序无法提交审核")
+ print(" 代码已上传到开发版,请在微信后台手动提交")
+ print("\n" + "-" * 40)
+ print("👉 下一步操作:")
+ print(" 1. 完成企业认证")
+ print(" 2. 在微信后台提交审核")
+ print(" 3. 审核通过后发布上线")
+ else:
+ # 尝试通过API提交审核
+ audit_success = self._submit_audit_via_api(app)
+ if audit_success:
+ print("✅ 审核已提交")
+ else:
+ print("⚠️ 自动提审失败,请在微信后台手动提交")
+ print(" 登录: https://mp.weixin.qq.com/")
+ print(" 版本管理 → 开发版本 → 提交审核")
+
+ # 5. 生成报告
+ print(f"\n📊 步骤4: 生成报告...")
+ report_file = self.save_report(report)
+ print(f"✅ 报告已保存: {report_file}")
+
+ print("\n" + "=" * 70)
+ print(" 🎉 部署完成!")
+ print("=" * 70)
+
+ return True
+
+ def _upload_code(self, app: Dict, version: str, desc: str) -> bool:
+ """上传代码(优先使用CLI)"""
+ project_path = app["project_path"]
+
+ # 方法1:使用微信开发者工具CLI
+ if os.path.exists(self.WX_CLI):
+ cmd = [
+ self.WX_CLI, "upload",
+ "--project", project_path,
+ "--version", version,
+ "--desc", desc
+ ]
+ success, output = self._run_cmd(cmd, timeout=120)
+ if success:
+ return True
+ print(f" CLI上传失败: {output[:200]}")
+
+ # 方法2:使用miniprogram-ci
+ if self._check_tool("miniprogram-ci") and app.get("private_key_path"):
+ cmd = [
+ "miniprogram-ci", "upload",
+ "--pp", project_path,
+ "--pkp", app["private_key_path"],
+ "--appid", app["appid"],
+ "--uv", version,
+ "-r", "1",
+ "--desc", desc
+ ]
+ success, output = self._run_cmd(cmd, timeout=120)
+ if success:
+ return True
+ print(f" miniprogram-ci上传失败: {output[:200]}")
+
+ return False
+
+ def _submit_audit_via_api(self, app: Dict) -> bool:
+ """通过API提交审核(需要access_token)"""
+ # 这里需要access_token才能调用API
+ # 目前返回False,提示用户手动提交
+ return False
+
+ # ==================== 汇总报告 ====================
+
+ def generate_summary_report(self):
+ """生成所有小程序的汇总报告"""
+ apps = self.config.get("apps", [])
+
+ if not apps:
+ print("📭 暂无配置的小程序")
+ return
+
+ print("\n" + "=" * 80)
+ print(" 📊 小程序管理汇总报告")
+ print(f" 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+ print("=" * 80)
+
+ all_reports = []
+
+ for app in apps:
+ report = self.check_project(app["id"])
+ if report:
+ all_reports.append(report)
+
+ # 打印汇总表格
+ print("\n┌" + "─" * 78 + "┐")
+ print(f"│ {'小程序名称':<20} │ {'AppID':<25} │ {'状态':<10} │ {'可发布':<8} │")
+ print("├" + "─" * 78 + "┤")
+
+ for report in all_reports:
+ status = "✅ 正常" if not report.has_errors else "❌ 错误"
+ can_release = "✅" if report.summary.get("can_release") else "❌"
+ print(f"│ {report.app_name:<20} │ {report.appid:<25} │ {status:<10} │ {can_release:<8} │")
+
+ print("└" + "─" * 78 + "┘")
+
+ # 统计
+ total = len(all_reports)
+ ok_count = sum(1 for r in all_reports if not r.has_errors and not r.has_warnings)
+ warn_count = sum(1 for r in all_reports if r.has_warnings and not r.has_errors)
+ error_count = sum(1 for r in all_reports if r.has_errors)
+ can_release = sum(1 for r in all_reports if r.summary.get("can_release"))
+
+ print(f"\n📈 统计:")
+ print(f" 总计: {total} 个小程序")
+ print(f" 正常: {ok_count} | 警告: {warn_count} | 错误: {error_count}")
+ print(f" 可发布: {can_release} 个")
+
+ # 问题清单
+ issues = []
+ for report in all_reports:
+ for check in report.checks:
+ if check.status == "error":
+ issues.append((report.app_name, check.name, check.message, check.fix_hint))
+
+ if issues:
+ print(f"\n⚠️ 问题清单 ({len(issues)} 个):")
+ print("-" * 60)
+ for app_name, check_name, message, hint in issues:
+ print(f" [{app_name}] {check_name}: {message}")
+ if hint:
+ print(f" 💡 {hint}")
+ else:
+ print(f"\n✅ 所有小程序状态正常")
+
+ # 待办事项
+ print(f"\n📋 待办事项:")
+ for report in all_reports:
+ cert_status = "unknown"
+ for check in report.checks:
+ if check.name == "企业认证":
+ if "审核中" in check.message:
+ cert_status = "pending"
+ elif "已完成" in check.message:
+ cert_status = "verified"
+ elif "未认证" in check.message:
+ cert_status = "unknown"
+ break
+
+ if cert_status == "pending":
+ print(f" ⏳ {report.app_name}: 等待认证审核结果")
+ elif cert_status == "unknown":
+ print(f" 📝 {report.app_name}: 需要完成企业认证")
+
+ print("\n" + "=" * 80)
+
+ # 保存汇总报告
+ summary_file = REPORT_DIR / f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+ summary_data = {
+ "generated_at": datetime.now().isoformat(),
+ "total_apps": total,
+ "summary": {
+ "ok": ok_count,
+ "warning": warn_count,
+ "error": error_count,
+ "can_release": can_release
+ },
+ "apps": [asdict(r) for r in all_reports]
+ }
+ with open(summary_file, 'w', encoding='utf-8') as f:
+ json.dump(summary_data, f, ensure_ascii=False, indent=2)
+
+ print(f"📁 报告已保存: {summary_file}\n")
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="小程序全能管理工具 v3.0",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+示例:
+ python mp_full.py report 生成汇总报告
+ python mp_full.py check soul-party 检查项目问题
+ python mp_full.py auto soul-party 全自动部署(上传+提审)
+ python mp_full.py auto soul-party -v 1.0.13 -d "修复问题"
+
+流程说明:
+ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
+ │ 检查 │ → │ 编译 │ → │ 上传 │ → │ 提审 │ → │ 发布 │
+ │ check │ │ build │ │ upload │ │ audit │ │ release │
+ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
+
+工具整合:
+ • 微信开发者工具CLI - 本地编译上传
+ • miniprogram-ci - npm官方CI工具
+ • 开放平台API - 审核/发布/认证
+"""
+ )
+
+ subparsers = parser.add_subparsers(dest="command", help="子命令")
+
+ # report
+ subparsers.add_parser("report", help="生成汇总报告")
+
+ # check
+ check_parser = subparsers.add_parser("check", help="检查项目问题")
+ check_parser.add_argument("app_id", help="小程序ID")
+
+ # auto
+ auto_parser = subparsers.add_parser("auto", help="全自动部署")
+ auto_parser.add_argument("app_id", help="小程序ID")
+ auto_parser.add_argument("-v", "--version", help="版本号")
+ auto_parser.add_argument("-d", "--desc", help="版本描述")
+ auto_parser.add_argument("--no-audit", action="store_true", help="不提交审核")
+
+ args = parser.parse_args()
+
+ if not args.command:
+ parser.print_help()
+ return
+
+ manager = MiniProgramManager()
+
+ if args.command == "report":
+ manager.generate_summary_report()
+
+ elif args.command == "check":
+ report = manager.check_project(args.app_id)
+ if report:
+ manager.print_report(report)
+ manager.save_report(report)
+
+ elif args.command == "auto":
+ manager.auto_deploy(
+ args.app_id,
+ version=args.version,
+ desc=args.desc,
+ submit_audit=not args.no_audit
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/开发文档/小程序管理/scripts/mp_manager.py b/开发文档/小程序管理/scripts/mp_manager.py
new file mode 100644
index 00000000..197f780d
--- /dev/null
+++ b/开发文档/小程序管理/scripts/mp_manager.py
@@ -0,0 +1,558 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+微信小程序管理命令行工具
+
+使用方法:
+ python mp_manager.py status # 查看小程序状态
+ python mp_manager.py audit # 查看审核状态
+ python mp_manager.py release # 发布上线
+ python mp_manager.py qrcode # 生成小程序码
+ python mp_manager.py domain # 查看/配置域名
+ python mp_manager.py privacy # 配置隐私协议
+ python mp_manager.py data # 查看数据分析
+"""
+
+import os
+import sys
+import argparse
+from datetime import datetime, timedelta
+from pathlib import Path
+
+# 添加当前目录到路径
+sys.path.insert(0, str(Path(__file__).parent))
+
+from mp_api import MiniProgramAPI, APIError, create_api_from_env
+
+
+def print_header(title: str):
+ """打印标题"""
+ print("\n" + "=" * 50)
+ print(f" {title}")
+ print("=" * 50)
+
+
+def print_success(message: str):
+ """打印成功信息"""
+ print(f"✅ {message}")
+
+
+def print_error(message: str):
+ """打印错误信息"""
+ print(f"❌ {message}")
+
+
+def print_info(message: str):
+ """打印信息"""
+ print(f"ℹ️ {message}")
+
+
+def cmd_status(api: MiniProgramAPI, args):
+ """查看小程序状态"""
+ print_header("小程序基础信息")
+
+ try:
+ info = api.get_basic_info()
+ print(f"\n📱 AppID: {info.appid}")
+ print(f"📝 名称: {info.nickname}")
+ print(f"📄 简介: {info.signature}")
+ print(f"🏢 主体: {info.principal_name}")
+ print(f"✓ 认证状态: {'已认证' if info.realname_status == 1 else '未认证'}")
+
+ if info.head_image_url:
+ print(f"🖼️ 头像: {info.head_image_url}")
+
+ # 获取类目
+ print("\n📂 已设置类目:")
+ categories = api.get_category()
+ if categories:
+ for cat in categories:
+ print(f" - {cat.get('first_class', '')} > {cat.get('second_class', '')}")
+ else:
+ print(" (未设置类目)")
+
+ except APIError as e:
+ print_error(f"获取信息失败: {e}")
+
+
+def cmd_audit(api: MiniProgramAPI, args):
+ """查看审核状态"""
+ print_header("审核状态")
+
+ try:
+ status = api.get_latest_audit_status()
+ print(f"\n🔢 审核单ID: {status.auditid}")
+ print(f"📊 状态: {status.status_text}")
+
+ if status.reason:
+ print(f"\n❗ 拒绝原因:")
+ print(f" {status.reason}")
+
+ if status.screenshot:
+ print(f"\n📸 问题截图: {status.screenshot}")
+
+ if status.status == 0:
+ print("\n👉 下一步: 运行 'python mp_manager.py release' 发布上线")
+ elif status.status == 1:
+ print("\n👉 请根据拒绝原因修改后重新提交审核")
+ elif status.status == 2:
+ print("\n👉 审核中,请耐心等待(通常1-3个工作日)")
+ print(" 可运行 'python mp_manager.py audit' 再次查询")
+
+ except APIError as e:
+ print_error(f"获取审核状态失败: {e}")
+
+
+def cmd_submit(api: MiniProgramAPI, args):
+ """提交审核"""
+ print_header("提交审核")
+
+ version_desc = args.desc or input("请输入版本说明: ").strip()
+ if not version_desc:
+ print_error("版本说明不能为空")
+ return
+
+ try:
+ # 获取页面列表
+ pages = api.get_page()
+ if not pages:
+ print_error("未找到页面,请先上传代码")
+ return
+
+ print(f"\n📄 检测到 {len(pages)} 个页面:")
+ for p in pages[:5]:
+ print(f" - {p}")
+ if len(pages) > 5:
+ print(f" ... 还有 {len(pages) - 5} 个")
+
+ # 确认提交
+ confirm = input("\n确认提交审核? (y/n): ").strip().lower()
+ if confirm != 'y':
+ print_info("已取消")
+ return
+
+ auditid = api.submit_audit(version_desc=version_desc)
+ print_success(f"审核已提交,审核单ID: {auditid}")
+ print("\n👉 运行 'python mp_manager.py audit' 查询审核状态")
+
+ except APIError as e:
+ print_error(f"提交审核失败: {e}")
+
+
+def cmd_release(api: MiniProgramAPI, args):
+ """发布上线"""
+ print_header("发布上线")
+
+ try:
+ # 先检查审核状态
+ status = api.get_latest_audit_status()
+ if status.status != 0:
+ print_error(f"当前审核状态: {status.status_text}")
+ print_info("只有审核通过的版本才能发布")
+ return
+
+ print(f"📊 审核状态: {status.status_text}")
+
+ # 确认发布
+ confirm = input("\n确认发布上线? (y/n): ").strip().lower()
+ if confirm != 'y':
+ print_info("已取消")
+ return
+
+ api.release()
+ print_success("🎉 发布成功!小程序已上线")
+
+ except APIError as e:
+ print_error(f"发布失败: {e}")
+
+
+def cmd_revert(api: MiniProgramAPI, args):
+ """版本回退"""
+ print_header("版本回退")
+
+ try:
+ # 获取可回退版本
+ history = api.get_revert_history()
+ if not history:
+ print_info("没有可回退的版本")
+ return
+
+ print("\n📜 可回退版本:")
+ for v in history:
+ print(f" - {v.get('user_version', '?')}: {v.get('user_desc', '')}")
+
+ # 确认回退
+ confirm = input("\n确认回退到上一版本? (y/n): ").strip().lower()
+ if confirm != 'y':
+ print_info("已取消")
+ return
+
+ api.revert_code_release()
+ print_success("版本回退成功")
+
+ except APIError as e:
+ print_error(f"版本回退失败: {e}")
+
+
+def cmd_qrcode(api: MiniProgramAPI, args):
+ """生成小程序码"""
+ print_header("生成小程序码")
+
+ # 场景选择
+ print("\n选择类型:")
+ print(" 1. 体验版二维码")
+ print(" 2. 小程序码(有限制,每个path最多10万个)")
+ print(" 3. 无限小程序码(推荐)")
+
+ choice = args.type or input("\n请选择 (1/2/3): ").strip()
+
+ output_file = args.output or f"qrcode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
+
+ try:
+ if choice == "1":
+ # 体验版二维码
+ path = args.path or input("页面路径 (默认首页): ").strip() or None
+ data = api.get_qrcode(path)
+
+ elif choice == "2":
+ # 小程序码
+ path = args.path or input("页面路径: ").strip()
+ if not path:
+ print_error("页面路径不能为空")
+ return
+ data = api.get_wxacode(path)
+
+ elif choice == "3":
+ # 无限小程序码
+ scene = args.scene or input("场景值 (最长32字符): ").strip()
+ if not scene:
+ print_error("场景值不能为空")
+ return
+ page = args.path or input("页面路径 (需已发布): ").strip() or None
+ data = api.get_wxacode_unlimit(scene, page)
+
+ else:
+ print_error("无效选择")
+ return
+
+ # 保存文件
+ with open(output_file, "wb") as f:
+ f.write(data)
+
+ print_success(f"小程序码已保存: {output_file}")
+
+ # 尝试打开
+ if sys.platform == "darwin":
+ os.system(f'open "{output_file}"')
+
+ except APIError as e:
+ print_error(f"生成小程序码失败: {e}")
+
+
+def cmd_domain(api: MiniProgramAPI, args):
+ """查看/配置域名"""
+ print_header("域名配置")
+
+ try:
+ # 获取当前配置
+ domains = api.get_domain()
+ webview_domains = api.get_webview_domain()
+
+ print("\n🌐 服务器域名:")
+ print(f" request: {', '.join(domains.get('requestdomain', [])) or '(无)'}")
+ print(f" wsrequest: {', '.join(domains.get('wsrequestdomain', [])) or '(无)'}")
+ print(f" upload: {', '.join(domains.get('uploaddomain', [])) or '(无)'}")
+ print(f" download: {', '.join(domains.get('downloaddomain', [])) or '(无)'}")
+
+ print(f"\n🔗 业务域名:")
+ print(f" webview: {', '.join(webview_domains) or '(无)'}")
+
+ # 是否要配置
+ if args.set_request:
+ print(f"\n配置 request 域名: {args.set_request}")
+ api.set_domain(requestdomain=[args.set_request])
+ print_success("域名配置成功")
+
+ except APIError as e:
+ print_error(f"域名配置失败: {e}")
+
+
+def cmd_privacy(api: MiniProgramAPI, args):
+ """配置隐私协议"""
+ print_header("隐私协议配置")
+
+ try:
+ # 获取当前配置
+ settings = api.get_privacy_setting()
+
+ print("\n📋 当前隐私设置:")
+ setting_list = settings.get("setting_list", [])
+ if setting_list:
+ for s in setting_list:
+ print(f" - {s.get('privacy_key', '?')}: {s.get('privacy_text', '')}")
+ else:
+ print(" (未配置)")
+
+ owner = settings.get("owner_setting", {})
+ if owner:
+ print(f"\n📧 联系方式:")
+ if owner.get("contact_email"):
+ print(f" 邮箱: {owner['contact_email']}")
+ if owner.get("contact_phone"):
+ print(f" 电话: {owner['contact_phone']}")
+
+ # 快速配置
+ if args.quick:
+ print("\n⚡ 快速配置常用隐私项...")
+
+ default_settings = [
+ {"privacy_key": "UserInfo", "privacy_text": "用于展示您的头像和昵称"},
+ {"privacy_key": "Location", "privacy_text": "用于获取您的位置信息以推荐附近服务"},
+ {"privacy_key": "PhoneNumber", "privacy_text": "用于登录验证和订单通知"},
+ ]
+
+ api.set_privacy_setting(
+ setting_list=default_settings,
+ contact_email=args.email or "contact@example.com",
+ contact_phone=args.phone or "15880802661"
+ )
+ print_success("隐私协议配置成功")
+
+ except APIError as e:
+ print_error(f"隐私协议配置失败: {e}")
+
+
+def cmd_data(api: MiniProgramAPI, args):
+ """查看数据分析"""
+ print_header("数据分析")
+
+ # 默认查询最近7天
+ end_date = datetime.now().strftime("%Y%m%d")
+ begin_date = (datetime.now() - timedelta(days=7)).strftime("%Y%m%d")
+
+ if args.begin:
+ begin_date = args.begin
+ if args.end:
+ end_date = args.end
+
+ try:
+ print(f"\n📊 访问趋势 ({begin_date} ~ {end_date}):")
+
+ data = api.get_daily_visit_trend(begin_date, end_date)
+ if not data:
+ print(" (暂无数据)")
+ return
+
+ # 统计汇总
+ total_pv = sum(d.get("visit_pv", 0) for d in data)
+ total_uv = sum(d.get("visit_uv", 0) for d in data)
+ total_new = sum(d.get("visit_uv_new", 0) for d in data)
+
+ print(f"\n📈 汇总数据:")
+ print(f" 总访问次数: {total_pv:,}")
+ print(f" 总访问人数: {total_uv:,}")
+ print(f" 新用户数: {total_new:,}")
+
+ print(f"\n📅 每日明细:")
+ for d in data[-7:]: # 只显示最近7天
+ date = d.get("ref_date", "?")
+ pv = d.get("visit_pv", 0)
+ uv = d.get("visit_uv", 0)
+ stay = d.get("stay_time_uv", 0)
+ print(f" {date}: PV={pv}, UV={uv}, 人均停留={stay:.1f}秒")
+
+ except APIError as e:
+ print_error(f"获取数据失败: {e}")
+
+
+def cmd_quota(api: MiniProgramAPI, args):
+ """查看API配额"""
+ print_header("API配额")
+
+ common_apis = [
+ "/wxa/getwxacode",
+ "/wxa/getwxacodeunlimit",
+ "/wxa/genwxashortlink",
+ "/wxa/submit_audit",
+ "/cgi-bin/message/subscribe/send"
+ ]
+
+ try:
+ for cgi_path in common_apis:
+ try:
+ quota = api.get_api_quota(cgi_path)
+ daily_limit = quota.get("daily_limit", 0)
+ used = quota.get("used", 0)
+ remain = quota.get("remain", 0)
+
+ print(f"\n📌 {cgi_path}")
+ print(f" 每日限额: {daily_limit:,}")
+ print(f" 已使用: {used:,}")
+ print(f" 剩余: {remain:,}")
+ except APIError:
+ pass
+
+ except APIError as e:
+ print_error(f"获取配额失败: {e}")
+
+
+def cmd_cli(api: MiniProgramAPI, args):
+ """使用微信开发者工具CLI"""
+ print_header("微信开发者工具CLI")
+
+ cli_path = "/Applications/wechatwebdevtools.app/Contents/MacOS/cli"
+ project_path = args.project or os.getenv("MINIPROGRAM_PATH", "")
+
+ if not project_path:
+ project_path = input("请输入小程序项目路径: ").strip()
+
+ if not os.path.exists(project_path):
+ print_error(f"项目路径不存在: {project_path}")
+ return
+
+ if not os.path.exists(cli_path):
+ print_error("未找到微信开发者工具,请先安装")
+ return
+
+ print(f"\n📂 项目路径: {project_path}")
+ print("\n选择操作:")
+ print(" 1. 打开项目")
+ print(" 2. 预览(生成二维码)")
+ print(" 3. 上传代码")
+ print(" 4. 编译")
+
+ choice = input("\n请选择: ").strip()
+
+ if choice == "1":
+ os.system(f'"{cli_path}" -o "{project_path}"')
+ print_success("项目已打开")
+
+ elif choice == "2":
+ output = f"{project_path}/preview.png"
+ os.system(f'"{cli_path}" preview --project "{project_path}" --qr-format image --qr-output "{output}"')
+ if os.path.exists(output):
+ print_success(f"预览二维码已生成: {output}")
+ os.system(f'open "{output}"')
+ else:
+ print_error("生成失败,请检查开发者工具是否已登录")
+
+ elif choice == "3":
+ version = input("版本号 (如 1.0.0): ").strip()
+ desc = input("版本说明: ").strip()
+ os.system(f'"{cli_path}" upload --project "{project_path}" --version "{version}" --desc "{desc}"')
+ print_success("代码上传完成")
+
+ elif choice == "4":
+ os.system(f'"{cli_path}" build-npm --project "{project_path}"')
+ print_success("编译完成")
+
+ else:
+ print_error("无效选择")
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="微信小程序管理工具",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+示例:
+ python mp_manager.py status 查看小程序状态
+ python mp_manager.py audit 查看审核状态
+ python mp_manager.py submit -d "修复xxx问题" 提交审核
+ python mp_manager.py release 发布上线
+ python mp_manager.py qrcode -t 3 -s "id=123" 生成无限小程序码
+ python mp_manager.py domain 查看域名配置
+ python mp_manager.py privacy --quick 快速配置隐私协议
+ python mp_manager.py data 查看数据分析
+ python mp_manager.py cli 使用开发者工具CLI
+"""
+ )
+
+ subparsers = parser.add_subparsers(dest="command", help="子命令")
+
+ # status
+ subparsers.add_parser("status", help="查看小程序状态")
+
+ # audit
+ subparsers.add_parser("audit", help="查看审核状态")
+
+ # submit
+ submit_parser = subparsers.add_parser("submit", help="提交审核")
+ submit_parser.add_argument("-d", "--desc", help="版本说明")
+
+ # release
+ subparsers.add_parser("release", help="发布上线")
+
+ # revert
+ subparsers.add_parser("revert", help="版本回退")
+
+ # qrcode
+ qr_parser = subparsers.add_parser("qrcode", help="生成小程序码")
+ qr_parser.add_argument("-t", "--type", choices=["1", "2", "3"], help="类型:1=体验版,2=小程序码,3=无限小程序码")
+ qr_parser.add_argument("-p", "--path", help="页面路径")
+ qr_parser.add_argument("-s", "--scene", help="场景值(类型3时使用)")
+ qr_parser.add_argument("-o", "--output", help="输出文件名")
+
+ # domain
+ domain_parser = subparsers.add_parser("domain", help="查看/配置域名")
+ domain_parser.add_argument("--set-request", help="设置request域名")
+
+ # privacy
+ privacy_parser = subparsers.add_parser("privacy", help="配置隐私协议")
+ privacy_parser.add_argument("--quick", action="store_true", help="快速配置常用隐私项")
+ privacy_parser.add_argument("--email", help="联系邮箱")
+ privacy_parser.add_argument("--phone", help="联系电话")
+
+ # data
+ data_parser = subparsers.add_parser("data", help="查看数据分析")
+ data_parser.add_argument("--begin", help="开始日期 YYYYMMDD")
+ data_parser.add_argument("--end", help="结束日期 YYYYMMDD")
+
+ # quota
+ subparsers.add_parser("quota", help="查看API配额")
+
+ # cli
+ cli_parser = subparsers.add_parser("cli", help="使用微信开发者工具CLI")
+ cli_parser.add_argument("-p", "--project", help="小程序项目路径")
+
+ args = parser.parse_args()
+
+ if not args.command:
+ parser.print_help()
+ return
+
+ # 创建API实例
+ try:
+ api = create_api_from_env()
+ except Exception as e:
+ print_error(f"初始化API失败: {e}")
+ print_info("请检查 .env 文件中的配置")
+ return
+
+ # 执行命令
+ commands = {
+ "status": cmd_status,
+ "audit": cmd_audit,
+ "submit": cmd_submit,
+ "release": cmd_release,
+ "revert": cmd_revert,
+ "qrcode": cmd_qrcode,
+ "domain": cmd_domain,
+ "privacy": cmd_privacy,
+ "data": cmd_data,
+ "quota": cmd_quota,
+ "cli": cmd_cli,
+ }
+
+ cmd_func = commands.get(args.command)
+ if cmd_func:
+ try:
+ cmd_func(api, args)
+ finally:
+ api.close()
+ else:
+ parser.print_help()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113301.json b/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113301.json
new file mode 100644
index 00000000..628d5a38
--- /dev/null
+++ b/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113301.json
@@ -0,0 +1,76 @@
+{
+ "app_id": "soul-party",
+ "app_name": "Soul派对",
+ "appid": "wxb8bbb2b10dec74aa",
+ "check_time": "2026-01-25T11:33:01.054516",
+ "checks": [
+ {
+ "name": "项目路径",
+ "status": "ok",
+ "message": "路径存在: /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram",
+ "fix_hint": ""
+ },
+ {
+ "name": "AppID配置",
+ "status": "ok",
+ "message": "AppID正确: wxb8bbb2b10dec74aa",
+ "fix_hint": ""
+ },
+ {
+ "name": "API域名",
+ "status": "ok",
+ "message": "已配置HTTPS域名",
+ "fix_hint": ""
+ },
+ {
+ "name": "入口文件",
+ "status": "ok",
+ "message": "app.js存在",
+ "fix_hint": ""
+ },
+ {
+ "name": "页面配置",
+ "status": "ok",
+ "message": "共9个页面",
+ "fix_hint": ""
+ },
+ {
+ "name": "隐私配置",
+ "status": "warning",
+ "message": "未启用隐私检查",
+ "fix_hint": "建议添加 __usePrivacyCheck__: true"
+ },
+ {
+ "name": "企业认证",
+ "status": "warning",
+ "message": "认证审核中",
+ "fix_hint": "等待审核结果"
+ },
+ {
+ "name": "开发者工具",
+ "status": "ok",
+ "message": "微信开发者工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "miniprogram-ci",
+ "status": "ok",
+ "message": "npm工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "上传密钥",
+ "status": "warning",
+ "message": "未配置私钥",
+ "fix_hint": "在小程序后台下载代码上传密钥"
+ }
+ ],
+ "summary": {
+ "total": 10,
+ "ok": 7,
+ "warning": 3,
+ "error": 0,
+ "can_deploy": true,
+ "can_release": false
+ }
+}
\ No newline at end of file
diff --git a/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113423.json b/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113423.json
new file mode 100644
index 00000000..60ca8c23
--- /dev/null
+++ b/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113423.json
@@ -0,0 +1,76 @@
+{
+ "app_id": "soul-party",
+ "app_name": "Soul派对",
+ "appid": "wxb8bbb2b10dec74aa",
+ "check_time": "2026-01-25T11:34:23.760802",
+ "checks": [
+ {
+ "name": "项目路径",
+ "status": "ok",
+ "message": "路径存在: /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram",
+ "fix_hint": ""
+ },
+ {
+ "name": "AppID配置",
+ "status": "ok",
+ "message": "AppID正确: wxb8bbb2b10dec74aa",
+ "fix_hint": ""
+ },
+ {
+ "name": "API域名",
+ "status": "ok",
+ "message": "已配置HTTPS域名",
+ "fix_hint": ""
+ },
+ {
+ "name": "入口文件",
+ "status": "ok",
+ "message": "app.js存在",
+ "fix_hint": ""
+ },
+ {
+ "name": "页面配置",
+ "status": "ok",
+ "message": "共9个页面",
+ "fix_hint": ""
+ },
+ {
+ "name": "隐私配置",
+ "status": "ok",
+ "message": "已启用隐私检查",
+ "fix_hint": ""
+ },
+ {
+ "name": "企业认证",
+ "status": "warning",
+ "message": "认证审核中",
+ "fix_hint": "等待审核结果"
+ },
+ {
+ "name": "开发者工具",
+ "status": "ok",
+ "message": "微信开发者工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "miniprogram-ci",
+ "status": "ok",
+ "message": "npm工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "上传密钥",
+ "status": "warning",
+ "message": "未配置私钥",
+ "fix_hint": "在小程序后台下载代码上传密钥"
+ }
+ ],
+ "summary": {
+ "total": 10,
+ "ok": 8,
+ "warning": 2,
+ "error": 0,
+ "can_deploy": true,
+ "can_release": false
+ }
+}
\ No newline at end of file
diff --git a/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113434.json b/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113434.json
new file mode 100644
index 00000000..83e19c8e
--- /dev/null
+++ b/开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113434.json
@@ -0,0 +1,76 @@
+{
+ "app_id": "soul-party",
+ "app_name": "Soul派对",
+ "appid": "wxb8bbb2b10dec74aa",
+ "check_time": "2026-01-25T11:34:28.854418",
+ "checks": [
+ {
+ "name": "项目路径",
+ "status": "ok",
+ "message": "路径存在: /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram",
+ "fix_hint": ""
+ },
+ {
+ "name": "AppID配置",
+ "status": "ok",
+ "message": "AppID正确: wxb8bbb2b10dec74aa",
+ "fix_hint": ""
+ },
+ {
+ "name": "API域名",
+ "status": "ok",
+ "message": "已配置HTTPS域名",
+ "fix_hint": ""
+ },
+ {
+ "name": "入口文件",
+ "status": "ok",
+ "message": "app.js存在",
+ "fix_hint": ""
+ },
+ {
+ "name": "页面配置",
+ "status": "ok",
+ "message": "共9个页面",
+ "fix_hint": ""
+ },
+ {
+ "name": "隐私配置",
+ "status": "ok",
+ "message": "已启用隐私检查",
+ "fix_hint": ""
+ },
+ {
+ "name": "企业认证",
+ "status": "warning",
+ "message": "认证审核中",
+ "fix_hint": "等待审核结果"
+ },
+ {
+ "name": "开发者工具",
+ "status": "ok",
+ "message": "微信开发者工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "miniprogram-ci",
+ "status": "ok",
+ "message": "npm工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "上传密钥",
+ "status": "warning",
+ "message": "未配置私钥",
+ "fix_hint": "在小程序后台下载代码上传密钥"
+ }
+ ],
+ "summary": {
+ "total": 10,
+ "ok": 8,
+ "warning": 2,
+ "error": 0,
+ "can_deploy": true,
+ "can_release": false
+ }
+}
\ No newline at end of file
diff --git a/开发文档/小程序管理/scripts/reports/summary_20260125_113255.json b/开发文档/小程序管理/scripts/reports/summary_20260125_113255.json
new file mode 100644
index 00000000..88ed994a
--- /dev/null
+++ b/开发文档/小程序管理/scripts/reports/summary_20260125_113255.json
@@ -0,0 +1,88 @@
+{
+ "generated_at": "2026-01-25T11:32:55.447833",
+ "total_apps": 1,
+ "summary": {
+ "ok": 0,
+ "warning": 1,
+ "error": 0,
+ "can_release": 0
+ },
+ "apps": [
+ {
+ "app_id": "soul-party",
+ "app_name": "Soul派对",
+ "appid": "wxb8bbb2b10dec74aa",
+ "check_time": "2026-01-25T11:32:55.428736",
+ "checks": [
+ {
+ "name": "项目路径",
+ "status": "ok",
+ "message": "路径存在: /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram",
+ "fix_hint": ""
+ },
+ {
+ "name": "AppID配置",
+ "status": "ok",
+ "message": "AppID正确: wxb8bbb2b10dec74aa",
+ "fix_hint": ""
+ },
+ {
+ "name": "API域名",
+ "status": "ok",
+ "message": "已配置HTTPS域名",
+ "fix_hint": ""
+ },
+ {
+ "name": "入口文件",
+ "status": "ok",
+ "message": "app.js存在",
+ "fix_hint": ""
+ },
+ {
+ "name": "页面配置",
+ "status": "ok",
+ "message": "共9个页面",
+ "fix_hint": ""
+ },
+ {
+ "name": "隐私配置",
+ "status": "warning",
+ "message": "未启用隐私检查",
+ "fix_hint": "建议添加 __usePrivacyCheck__: true"
+ },
+ {
+ "name": "企业认证",
+ "status": "warning",
+ "message": "认证审核中",
+ "fix_hint": "等待审核结果"
+ },
+ {
+ "name": "开发者工具",
+ "status": "ok",
+ "message": "微信开发者工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "miniprogram-ci",
+ "status": "ok",
+ "message": "npm工具已安装",
+ "fix_hint": ""
+ },
+ {
+ "name": "上传密钥",
+ "status": "warning",
+ "message": "未配置私钥",
+ "fix_hint": "在小程序后台下载代码上传密钥"
+ }
+ ],
+ "summary": {
+ "total": 10,
+ "ok": 7,
+ "warning": 3,
+ "error": 0,
+ "can_deploy": true,
+ "can_release": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/开发文档/小程序管理/scripts/requirements.txt b/开发文档/小程序管理/scripts/requirements.txt
new file mode 100644
index 00000000..29b7c9d3
--- /dev/null
+++ b/开发文档/小程序管理/scripts/requirements.txt
@@ -0,0 +1,7 @@
+# 微信小程序管理工具依赖
+
+# HTTP客户端
+httpx>=0.25.0
+
+# 环境变量管理
+python-dotenv>=1.0.0
diff --git a/开发文档/服务器管理/SKILL.md b/开发文档/服务器管理/SKILL.md
new file mode 100644
index 00000000..8bce80cd
--- /dev/null
+++ b/开发文档/服务器管理/SKILL.md
@@ -0,0 +1,314 @@
+---
+name: 服务器管理
+description: 宝塔服务器统一管理与自动化部署。触发词:服务器、宝塔、部署、上线、发布、Node项目、SSL证书、HTTPS、DNS解析、域名配置、端口、PM2、Nginx、MySQL数据库、服务器状态。涵盖多服务器资产管理、Node.js项目一键部署、SSL证书管理、DNS配置、系统诊断等运维能力。
+---
+
+# 服务器管理
+
+让 AI 写完代码后,无需人工介入,自动把项目「变成一个在线网站」。
+
+---
+
+## 快速入口(复制即用)
+
+### 服务器资产
+
+| 服务器 | IP | 配置 | 用途 | 宝塔面板 |
+|--------|-----|------|------|----------|
+| **小型宝塔** | 42.194.232.22 | 2核4G 5M | 主力部署(Node项目) | https://42.194.232.22:9988/ckbpanel |
+| **存客宝** | 42.194.245.239 | 2核16G 50M | 私域银行业务 | https://42.194.245.239:9988 |
+| **kr宝塔** | 43.139.27.93 | 2核4G 5M | 辅助服务器 | https://43.139.27.93:9988 |
+
+### 凭证速查
+
+```bash
+# SSH连接(小型宝塔为例)
+ssh root@42.194.232.22
+密码: Zhiqun1984
+
+# 宝塔面板登录(小型宝塔)
+地址: https://42.194.232.22:9988/ckbpanel
+账号: ckb
+密码: zhiqun1984
+
+# 宝塔API密钥
+小型宝塔: hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd
+存客宝: TNKjqDv5N1QLOU20gcmGVgr82Z4mXzRi
+kr宝塔: qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT
+```
+
+---
+
+## 一键操作
+
+### 1. 检查服务器状态
+
+```bash
+# 运行快速检查脚本
+python3 /Users/karuo/Documents/个人/卡若AI/01_系统管理/服务器管理/scripts/快速检查服务器.py
+```
+
+### 2. 部署 Node 项目(标准流程)
+
+```bash
+# 1. 压缩项目(排除无用目录)
+cd /项目路径
+tar --exclude='node_modules' --exclude='.next' --exclude='.git' \
+ -czf /tmp/项目名_update.tar.gz .
+
+# 2. 上传到服务器
+sshpass -p 'Zhiqun1984' scp /tmp/项目名_update.tar.gz root@42.194.232.22:/tmp/
+
+# 3. SSH部署
+ssh root@42.194.232.22
+cd /www/wwwroot/项目名
+rm -rf app components lib public styles *.json *.js *.ts *.mjs *.md .next
+tar -xzf /tmp/项目名_update.tar.gz
+pnpm install
+pnpm run build
+rm /tmp/项目名_update.tar.gz
+
+# 4. 宝塔面板重启项目
+# 【网站】→【Node项目】→ 找到项目 → 点击【重启】
+```
+
+### 3. SSL证书检查/修复
+
+```bash
+# 检查所有服务器SSL证书状态
+python3 /Users/karuo/Documents/个人/卡若AI/01_系统管理/服务器管理/scripts/ssl证书检查.py
+
+# 自动修复过期证书
+python3 /Users/karuo/Documents/个人/卡若AI/01_系统管理/服务器管理/scripts/ssl证书检查.py --fix
+```
+
+### 4. 常用诊断命令
+
+```bash
+# 检查端口占用
+ssh root@42.194.232.22 "ss -tlnp | grep :3006"
+
+# 检查PM2进程
+ssh root@42.194.232.22 "/www/server/nodejs/v22.14.0/bin/pm2 list"
+
+# 测试HTTP响应
+ssh root@42.194.232.22 "curl -I http://localhost:3006"
+
+# 检查Nginx配置
+ssh root@42.194.232.22 "nginx -t"
+
+# 重载Nginx
+ssh root@42.194.232.22 "nginx -s reload"
+
+# DNS解析检查
+dig soul.quwanzhi.com +short @8.8.8.8
+```
+
+---
+
+## 端口配置表(小型宝塔 42.194.232.22)
+
+| 端口 | 项目名 | 类型 | 域名 | 状态 |
+|------|--------|------|------|------|
+| 3000 | cunkebao | Next.js | mckb.quwanzhi.com | ✅ |
+| 3001 | ai_hair | NestJS | ai-hair.quwanzhi.com | ✅ |
+| 3002 | kr_wb | Next.js | kr_wb.quwanzhi.com | ✅ |
+| 3003 | hx | Vue | krjzk.quwanzhi.com | ⚠️ |
+| 3004 | dlmdashboard | Next.js | dlm.quwanzhi.com | ✅ |
+| 3005 | document | Next.js | docc.quwanzhi.com | ✅ |
+| 3006 | soul | Next.js | soul.quwanzhi.com | ✅ |
+| 3015 | 神射手 | Next.js | kr-users.quwanzhi.com | ⚠️ |
+| 3018 | zhaoping | Next.js | zp.quwanzhi.com | ✅ |
+| 3021 | is_phone | Next.js | is-phone.quwanzhi.com | ✅ |
+| 3031 | word | Next.js | word.quwanzhi.com | ✅ |
+| 3036 | ymao | Next.js | ymao.quwanzhi.com | ✅ |
+| 3043 | tongzhi | Next.js | touzhi.lkdie.com | ✅ |
+| 3045 | 玩值大屏 | Next.js | wz-screen.quwanzhi.com | ✅ |
+| 3050 | zhiji | Next.js | zhiji.quwanzhi.com | ✅ |
+| 3051 | zhiji1 | Next.js | zhiji1.quwanzhi.com | ✅ |
+| 3055 | wzdj | Next.js | wzdj.quwanzhi.com | ✅ |
+| 3305 | AITOUFA | Next.js | ai-tf.quwanzhi.com | ✅ |
+| 9528 | mbti | Vue | mbtiadmin.quwanzhi.com | ✅ |
+
+### 端口分配原则
+
+- **3000-3099**: Next.js / React 项目
+- **3100-3199**: Vue 项目
+- **3200-3299**: NestJS / Express 后端
+- **3300-3399**: AI相关项目
+- **9000-9999**: 管理面板 / 特殊用途
+
+---
+
+## 核心工作流程
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Node项目一键部署流程 │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ START │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 1. 压缩本地代码 │ 排除 node_modules, .next, .git │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 2. 上传到服务器 │ scp 到 /tmp/ │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 3. 清理旧文件 │ 保留 .env 等配置文件 │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 4. 解压新代码 │ tar -xzf │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 5. 安装依赖 │ pnpm install │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 6. 构建项目 │ pnpm run build │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 7. 宝塔面板重启 │ Node项目 → 重启 │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────┐ │
+│ │ 8. 验证访问 │ curl https://域名 │
+│ └────────┬─────────┘ │
+│ │ │
+│ ▼ │
+│ SUCCESS │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 操作优先级矩阵
+
+| 操作类型 | 优先方式 | 备选方式 | 说明 |
+|---------|---------|---------|------|
+| 查询信息 | ✅ 宝塔API | SSH | API稳定 |
+| 文件操作 | ✅ 宝塔API | SSH | API支持 |
+| 配置Nginx | ✅ 宝塔API | SSH | API可读写 |
+| 重载服务 | ⚠️ SSH | - | API无接口 |
+| 上传代码 | ⚠️ SSH/scp | - | 大文件 |
+| 添加项目 | ❌ 宝塔界面 | - | API不稳定 |
+
+---
+
+## 常见问题速查
+
+### Q1: 外网无法访问(ERR_EMPTY_RESPONSE)
+
+**原因**: 腾讯云安全组只开放443端口
+
+**解决**:
+1. 必须配置SSL证书
+2. Nginx配置添加443监听
+
+### Q2: Node项目启动失败(Could not find production build)
+
+**原因**: 使用 `npm run start` 但未执行 `npm run build`
+
+**解决**: 先 `pnpm run build` 再重启
+
+### Q3: 端口冲突(EADDRINUSE)
+
+**解决**:
+```bash
+# 检查端口占用
+ss -tlnp | grep :端口号
+
+# 修改package.json中的端口
+"start": "next start -p 新端口"
+```
+
+### Q4: DNS被代理劫持
+
+**现象**: 本地DNS解析到198.18.x.x
+
+**解决**:
+- 关闭代理软件
+- 或用手机4G网络测试
+
+### Q5: 宝塔与PM2冲突
+
+**原因**: 同时使用root用户PM2和宝塔PM2
+
+**解决**:
+- 停止所有独立PM2: `pm2 kill`
+- 只使用宝塔界面管理
+
+---
+
+## 安全约束
+
+### 绝对禁止
+
+- ❌ 输出完整密码/密钥到聊天
+- ❌ 执行危险命令(rm -rf /, reboot等)
+- ❌ 跳过验证步骤
+- ❌ 使用独立PM2(避免与宝塔冲突)
+
+### 必须遵守
+
+- ✅ 操作前检查服务器状态
+- ✅ 操作后验证结果
+- ✅ 生成操作报告
+
+---
+
+## 相关脚本
+
+| 脚本 | 功能 | 位置 |
+|------|------|------|
+| `快速检查服务器.py` | 一键检查所有服务器状态 | `./scripts/` |
+| `一键部署.py` | 根据配置文件部署项目 | `./scripts/` |
+| `ssl证书检查.py` | 检查/修复SSL证书 | `./scripts/` |
+
+---
+
+## 相关文档
+
+| 文档 | 内容 | 位置 |
+|------|------|------|
+| `宝塔API接口文档.md` | 宝塔API完整接口说明 | `./references/` |
+| `端口配置表.md` | 完整端口分配表 | `./references/` |
+| `常见问题手册.md` | 问题解决方案大全 | `./references/` |
+| `部署配置模板.md` | JSON配置文件模板 | `./references/` |
+| `系统架构说明.md` | 完整架构图和流程图 | `./references/` |
+
+---
+
+## 历史对话整理
+
+### kr_wb白板项目部署(2026-01-23)
+
+- 项目类型: Next.js
+- 部署位置: /www/wwwroot/kr_wb
+- 域名: kr_wb.quwanzhi.com
+- 端口: 3002
+- 遇到问题: AI功能401错误(API密钥未配置)
+- 解决方案: 修改 lib/ai-client.ts,改用 SiliconFlow 作为默认服务
+
+### soul项目部署(2026-01-23)
+
+- 项目类型: Next.js
+- 部署位置: /www/wwwroot/soul
+- 域名: soul.quwanzhi.com
+- 端口: 3006
+- 部署流程: 压缩→上传→解压→安装依赖→构建→PM2启动→配置Nginx→配置SSL
diff --git a/开发文档/服务器管理/scripts/ssl证书检查.py b/开发文档/服务器管理/scripts/ssl证书检查.py
new file mode 100644
index 00000000..4ac56952
--- /dev/null
+++ b/开发文档/服务器管理/scripts/ssl证书检查.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+SSL证书检查脚本
+===============
+用途:检查所有服务器的SSL证书状态
+
+使用方法:
+python3 ssl证书检查.py
+python3 ssl证书检查.py --fix # 自动修复过期证书
+"""
+
+import sys
+import time
+import hashlib
+import requests
+import urllib3
+from datetime import datetime
+
+# 禁用SSL警告
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+# 服务器配置
+服务器列表 = {
+ "小型宝塔": {
+ "面板地址": "https://42.194.232.22:9988",
+ "密钥": "hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd"
+ },
+ "存客宝": {
+ "面板地址": "https://42.194.245.239:9988",
+ "密钥": "TNKjqDv5N1QLOU20gcmGVgr82Z4mXzRi"
+ },
+ "kr宝塔": {
+ "面板地址": "https://43.139.27.93:9988",
+ "密钥": "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
+ }
+}
+
+def 生成签名(api_key: str) -> tuple:
+ """生成宝塔API签名"""
+ now_time = int(time.time())
+ sign_str = str(now_time) + hashlib.md5(api_key.encode('utf-8')).hexdigest()
+ request_token = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
+ return now_time, request_token
+
+def 获取证书列表(面板地址: str, 密钥: str) -> dict:
+ """获取SSL证书列表"""
+ now_time, request_token = 生成签名(密钥)
+
+ url = f"{面板地址}/ssl?action=GetCertList"
+ data = {
+ "request_time": now_time,
+ "request_token": request_token
+ }
+
+ try:
+ response = requests.post(url, data=data, timeout=10, verify=False)
+ return response.json()
+ except Exception as e:
+ return {"error": str(e)}
+
+def 获取网站列表(面板地址: str, 密钥: str) -> dict:
+ """获取网站列表"""
+ now_time, request_token = 生成签名(密钥)
+
+ url = f"{面板地址}/data?action=getData&table=sites"
+ data = {
+ "request_time": now_time,
+ "request_token": request_token,
+ "limit": 100,
+ "p": 1
+ }
+
+ try:
+ response = requests.post(url, data=data, timeout=10, verify=False)
+ return response.json()
+ except Exception as e:
+ return {"error": str(e)}
+
+def 检查服务器证书(名称: str, 配置: dict) -> dict:
+ """检查单台服务器的证书状态"""
+ print(f"\n检查服务器: {名称}")
+ print("-" * 40)
+
+ try:
+ # 获取网站列表
+ 网站数据 = 获取网站列表(配置["面板地址"], 配置["密钥"])
+
+ if "error" in 网站数据:
+ print(f" ❌ API错误: {网站数据['error']}")
+ return {"error": 网站数据['error']}
+
+ 网站列表 = 网站数据.get("data", [])
+
+ if not 网站列表:
+ print(" ⚠️ 没有找到网站")
+ return {"网站数": 0}
+
+ print(f" 📊 共 {len(网站列表)} 个网站")
+
+ # 统计
+ 已配置SSL = 0
+ 未配置SSL = 0
+
+ for 网站 in 网站列表:
+ 网站名 = 网站.get("name", "未知")
+ ssl状态 = 网站.get("ssl", 0)
+
+ if ssl状态:
+ 已配置SSL += 1
+ 状态标识 = "🔒"
+ else:
+ 未配置SSL += 1
+ 状态标识 = "🔓"
+
+ print(f" {状态标识} {网站名}")
+
+ print(f"\n 统计: 已配置SSL {已配置SSL} 个, 未配置 {未配置SSL} 个")
+
+ return {
+ "网站数": len(网站列表),
+ "已配置SSL": 已配置SSL,
+ "未配置SSL": 未配置SSL
+ }
+
+ except Exception as e:
+ print(f" ❌ 检查失败: {e}")
+ return {"error": str(e)}
+
+def main():
+ 自动修复 = "--fix" in sys.argv
+
+ print("=" * 60)
+ print(" SSL证书状态检查报告")
+ print(f" {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+ print("=" * 60)
+
+ 总统计 = {
+ "服务器数": 0,
+ "网站总数": 0,
+ "已配置SSL": 0,
+ "未配置SSL": 0
+ }
+
+ for 服务器名称, 配置 in 服务器列表.items():
+ 结果 = 检查服务器证书(服务器名称, 配置)
+
+ if "error" not in 结果:
+ 总统计["服务器数"] += 1
+ 总统计["网站总数"] += 结果.get("网站数", 0)
+ 总统计["已配置SSL"] += 结果.get("已配置SSL", 0)
+ 总统计["未配置SSL"] += 结果.get("未配置SSL", 0)
+
+ print("\n" + "=" * 60)
+ print(" 汇总统计")
+ print("=" * 60)
+ print(f" 服务器数量: {总统计['服务器数']}")
+ print(f" 网站总数: {总统计['网站总数']}")
+ print(f" 已配置SSL: {总统计['已配置SSL']} 🔒")
+ print(f" 未配置SSL: {总统计['未配置SSL']} 🔓")
+ print("=" * 60)
+
+ if 自动修复 and 总统计['未配置SSL'] > 0:
+ print("\n⚠️ --fix 模式需要手动在宝塔面板配置SSL证书")
+ print(" 建议使用通配符证书 *.quwanzhi.com")
+
+if __name__ == "__main__":
+ main()
diff --git a/开发文档/服务器管理/scripts/一键部署.py b/开发文档/服务器管理/scripts/一键部署.py
new file mode 100644
index 00000000..a5660e18
--- /dev/null
+++ b/开发文档/服务器管理/scripts/一键部署.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+一键部署脚本
+============
+用途:根据配置文件一键部署Node项目到宝塔服务器
+
+使用方法:
+python3 一键部署.py 项目名称 本地项目路径
+
+示例:
+python3 一键部署.py soul /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验
+"""
+
+import sys
+import os
+import subprocess
+import time
+
+# 默认服务器配置
+默认配置 = {
+ "服务器IP": "42.194.232.22",
+ "SSH用户": "root",
+ "SSH密码": "Zhiqun1984",
+ "服务器根目录": "/www/wwwroot"
+}
+
+def 执行命令(命令: str, 显示输出: bool = True) -> tuple:
+ """执行shell命令"""
+ result = subprocess.run(命令, shell=True, capture_output=True, text=True)
+ if 显示输出 and result.stdout:
+ print(result.stdout)
+ if result.stderr and "Warning" not in result.stderr:
+ print(f"错误: {result.stderr}")
+ return result.returncode, result.stdout
+
+def 部署项目(项目名称: str, 本地路径: str):
+ """执行部署流程"""
+ 服务器路径 = f"{默认配置['服务器根目录']}/{项目名称}"
+ 压缩文件 = f"/tmp/{项目名称}_update.tar.gz"
+
+ print(f"\n{'='*60}")
+ print(f"开始部署: {项目名称}")
+ print(f"本地路径: {本地路径}")
+ print(f"服务器路径: {服务器路径}")
+ print(f"{'='*60}\n")
+
+ # 步骤1: 压缩项目
+ print("📦 步骤1: 压缩项目文件...")
+ 排除项 = "--exclude='node_modules' --exclude='.next' --exclude='.git' --exclude='android' --exclude='out'"
+ 压缩命令 = f"cd '{本地路径}' && tar {排除项} -czf {压缩文件} ."
+ code, _ = 执行命令(压缩命令, False)
+ if code != 0:
+ print("❌ 压缩失败")
+ return False
+
+ # 获取文件大小
+ 大小 = os.path.getsize(压缩文件) / 1024 / 1024
+ print(f" ✅ 压缩完成,大小: {大小:.2f} MB")
+
+ # 步骤2: 上传到服务器
+ print("\n📤 步骤2: 上传到服务器...")
+ 上传命令 = f"sshpass -p '{默认配置['SSH密码']}' scp -o StrictHostKeyChecking=no {压缩文件} {默认配置['SSH用户']}@{默认配置['服务器IP']}:/tmp/"
+ code, _ = 执行命令(上传命令, False)
+ if code != 0:
+ print("❌ 上传失败")
+ return False
+ print(" ✅ 上传完成")
+
+ # 步骤3-6: SSH远程执行
+ print("\n🔧 步骤3-6: 服务器端操作...")
+
+ SSH前缀 = f"sshpass -p '{默认配置['SSH密码']}' ssh -o StrictHostKeyChecking=no {默认配置['SSH用户']}@{默认配置['服务器IP']}"
+
+ # 清理旧文件
+ 清理命令 = f"{SSH前缀} 'cd {服务器路径} && rm -rf app components lib public styles *.json *.js *.ts *.mjs *.md .next 2>/dev/null || true'"
+ 执行命令(清理命令, False)
+ print(" ✅ 清理旧文件")
+
+ # 解压
+ 解压命令 = f"{SSH前缀} 'cd {服务器路径} && tar -xzf /tmp/{项目名称}_update.tar.gz'"
+ 执行命令(解压命令, False)
+ print(" ✅ 解压新代码")
+
+ # 安装依赖
+ print("\n📚 安装依赖 (这可能需要几分钟)...")
+ 安装命令 = f"{SSH前缀} 'export PATH=/www/server/nodejs/v22.14.0/bin:$PATH && cd {服务器路径} && npm install --legacy-peer-deps 2>&1'"
+ 执行命令(安装命令, True)
+
+ # 构建
+ print("\n🏗️ 构建项目...")
+ 构建命令 = f"{SSH前缀} 'export PATH=/www/server/nodejs/v22.14.0/bin:$PATH && cd {服务器路径} && npm run build 2>&1'"
+ 执行命令(构建命令, True)
+
+ # 重启PM2
+ print("\n🔄 重启服务...")
+ 重启命令 = f"{SSH前缀} 'export PATH=/www/server/nodejs/v22.14.0/bin:$PATH && pm2 restart {项目名称} 2>&1'"
+ 执行命令(重启命令, True)
+
+ # 清理临时文件
+ 清理临时命令 = f"{SSH前缀} 'rm -f /tmp/{项目名称}_update.tar.gz'"
+ 执行命令(清理临时命令, False)
+ os.remove(压缩文件)
+
+ print(f"\n{'='*60}")
+ print("✅ 部署完成!")
+ print(f"{'='*60}")
+ print("\n⚠️ 请在宝塔面板手动重启项目:")
+ print(f" 1. 登录 https://42.194.232.22:9988/ckbpanel")
+ print(f" 2. 进入【网站】→【Node项目】")
+ print(f" 3. 找到 {项目名称},点击【重启】")
+
+ return True
+
+def main():
+ if len(sys.argv) < 3:
+ print("用法: python3 一键部署.py <项目名称> <本地项目路径>")
+ print("\n示例:")
+ print(" python3 一键部署.py soul /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验")
+ print(" python3 一键部署.py kr_wb /Users/karuo/Documents/开发/4、小工具/whiteboard")
+ sys.exit(1)
+
+ 项目名称 = sys.argv[1]
+ 本地路径 = sys.argv[2]
+
+ if not os.path.exists(本地路径):
+ print(f"❌ 本地路径不存在: {本地路径}")
+ sys.exit(1)
+
+ 确认 = input(f"\n确认部署 {项目名称} 到服务器? (y/n): ")
+ if 确认.lower() != 'y':
+ print("已取消部署")
+ sys.exit(0)
+
+ 部署项目(项目名称, 本地路径)
+
+if __name__ == "__main__":
+ main()
diff --git a/开发文档/服务器管理/scripts/快速检查服务器.py b/开发文档/服务器管理/scripts/快速检查服务器.py
new file mode 100644
index 00000000..5667788c
--- /dev/null
+++ b/开发文档/服务器管理/scripts/快速检查服务器.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+快速检查服务器状态
+==================
+用途:一键检查所有服务器的基本状态
+
+使用方法:
+python3 快速检查服务器.py
+"""
+
+import time
+import hashlib
+import requests
+import urllib3
+
+# 禁用SSL警告
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+# 服务器配置
+服务器列表 = {
+ "小型宝塔": {
+ "面板地址": "https://42.194.232.22:9988",
+ "密钥": "hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd"
+ },
+ "存客宝": {
+ "面板地址": "https://42.194.245.239:9988",
+ "密钥": "TNKjqDv5N1QLOU20gcmGVgr82Z4mXzRi"
+ },
+ "kr宝塔": {
+ "面板地址": "https://43.139.27.93:9988",
+ "密钥": "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
+ }
+}
+
+def 生成签名(api_key: str) -> tuple:
+ """生成宝塔API签名"""
+ now_time = int(time.time())
+ sign_str = str(now_time) + hashlib.md5(api_key.encode('utf-8')).hexdigest()
+ request_token = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
+ return now_time, request_token
+
+def 获取系统信息(面板地址: str, 密钥: str) -> dict:
+ """获取系统基础统计信息"""
+ now_time, request_token = 生成签名(密钥)
+
+ url = f"{面板地址}/system?action=GetSystemTotal"
+ data = {
+ "request_time": now_time,
+ "request_token": request_token
+ }
+
+ try:
+ response = requests.post(url, data=data, timeout=10, verify=False)
+ return response.json()
+ except Exception as e:
+ return {"error": str(e)}
+
+def 检查单台服务器(名称: str, 配置: dict) -> dict:
+ """检查单台服务器状态"""
+ try:
+ 系统信息 = 获取系统信息(配置["面板地址"], 配置["密钥"])
+
+ if isinstance(系统信息, dict) and "error" not in 系统信息 and 系统信息.get("status") != False:
+ return {
+ "名称": 名称,
+ "状态": "✅ 正常",
+ "CPU": f"{系统信息.get('cpuRealUsed', 'N/A')}%",
+ "内存": f"{系统信息.get('memRealUsed', 'N/A')}%",
+ "磁盘": f"{系统信息.get('diskPer', 'N/A')}%"
+ }
+ else:
+ return {
+ "名称": 名称,
+ "状态": "❌ API错误",
+ "错误": str(系统信息)
+ }
+ except Exception as e:
+ return {
+ "名称": 名称,
+ "状态": "❌ 连接失败",
+ "错误": str(e)
+ }
+
+def main():
+ print("=" * 60)
+ print(" 服务器状态检查报告")
+ print("=" * 60)
+ print()
+
+ for 名称, 配置 in 服务器列表.items():
+ 结果 = 检查单台服务器(名称, 配置)
+ print(f"📦 {结果['名称']}")
+ print(f" 状态: {结果['状态']}")
+ if "CPU" in 结果:
+ print(f" CPU: {结果['CPU']} | 内存: {结果['内存']} | 磁盘: {结果['磁盘']}")
+ if "错误" in 结果:
+ print(f" 错误: {结果['错误'][:50]}...")
+ print()
+
+ print("=" * 60)
+
+if __name__ == "__main__":
+ main()
diff --git a/归档/miniprogram/.gitignore b/归档/miniprogram/.gitignore
deleted file mode 100644
index 14ea590c..00000000
--- a/归档/miniprogram/.gitignore
+++ /dev/null
@@ -1,14 +0,0 @@
-# Windows
-[Dd]esktop.ini
-Thumbs.db
-$RECYCLE.BIN/
-
-# macOS
-.DS_Store
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-
-# Node.js
-node_modules/
diff --git a/归档/miniprogram/README.md b/归档/miniprogram/README.md
deleted file mode 100644
index 2cc11dbf..00000000
--- a/归档/miniprogram/README.md
+++ /dev/null
@@ -1,138 +0,0 @@
-# Soul创业实验 - 微信小程序
-
-> 一场SOUL的创业实验场 - 来自Soul派对房的真实商业故事
-
-## 📱 项目简介
-
-本项目是《一场SOUL的创业实验场》的微信小程序版本,完整还原了Web端的所有UI界面和功能。
-
-## 🎨 设计特点
-
-- **主题色**: Soul青色 (#00CED1)
-- **设计风格**: 深色主题 + 毛玻璃效果
-- **1:1还原**: 完全复刻Web端的UI设计
-
-## 📂 项目结构
-
-```
-miniprogram/
-├── app.js # 应用入口
-├── app.json # 应用配置
-├── app.wxss # 全局样式
-├── custom-tab-bar/ # 自定义TabBar组件
-│ ├── index.js
-│ ├── index.json
-│ ├── index.wxml
-│ └── index.wxss
-├── pages/
-│ ├── index/ # 首页
-│ ├── chapters/ # 目录页
-│ ├── match/ # 找伙伴页
-│ ├── my/ # 我的页面
-│ ├── read/ # 阅读页
-│ ├── about/ # 关于作者
-│ ├── referral/ # 推广中心
-│ ├── purchases/ # 订单页
-│ └── settings/ # 设置页
-├── utils/
-│ ├── util.js # 工具函数
-│ └── payment.js # 支付工具
-├── assets/
-│ └── icons/ # 图标资源
-├── project.config.json # 项目配置
-└── sitemap.json # 站点地图
-```
-
-## 🚀 功能列表
-
-### 核心功能
-- ✅ 首页 - 书籍展示、推荐章节、阅读进度
-- ✅ 目录 - 完整章节列表、篇章折叠展开
-- ✅ 找伙伴 - 匹配动画、匹配类型选择
-- ✅ 我的 - 个人信息、订单、推广中心
-- ✅ 阅读 - 付费墙、章节导航、分享功能
-
-### 特色功能
-- ✅ 自定义TabBar(中间突出的找伙伴按钮)
-- ✅ 阅读进度条
-- ✅ 匹配动画效果
-- ✅ 付费墙与购买流程
-- ✅ 分享海报功能
-- ✅ 推广佣金系统
-
-## 🛠 开发指南
-
-### 环境要求
-- 微信开发者工具 >= 1.06.2308310
-- 基础库版本 >= 3.3.4
-
-### 快速开始
-
-1. **下载微信开发者工具**
- - 前往 [微信开发者工具下载页面](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)
-
-2. **导入项目**
- - 打开微信开发者工具
- - 选择"导入项目"
- - 项目目录选择 `miniprogram` 文件夹
- - AppID 使用: `wx432c93e275548671`
-
-3. **编译运行**
- - 点击"编译"按钮
- - 在模拟器中预览效果
-
-### 真机调试
-
-1. 点击工具栏的"预览"按钮
-2. 使用微信扫描二维码
-3. 在真机上测试所有功能
-
-## 📝 配置说明
-
-### API配置
-在 `app.js` 中修改 `globalData.baseUrl`:
-
-```javascript
-globalData: {
- baseUrl: 'https://soul.ckb.fit', // 你的API地址
- // ...
-}
-```
-
-### AppID配置
-在 `project.config.json` 中修改:
-
-```json
-{
- "appid": "你的小程序AppID"
-}
-```
-
-## 🎯 上线发布
-
-1. **准备工作**
- - 确保所有功能测试通过
- - 检查API接口是否正常
- - 确认支付功能已配置
-
-2. **上传代码**
- - 在开发者工具中点击"上传"
- - 填写版本号和项目备注
-
-3. **提交审核**
- - 登录[微信公众平台](https://mp.weixin.qq.com)
- - 进入"版本管理"
- - 提交审核
-
-4. **发布上线**
- - 审核通过后点击"发布"
-
-## 🔗 相关链接
-
-- **Web版本**: https://soul.ckb.fit
-- **作者微信**: 28533368
-- **技术支持**: 存客宝
-
-## 📄 版权信息
-
-© 2024 卡若. All rights reserved.
diff --git a/归档/miniprogram/app.js b/归档/miniprogram/app.js
deleted file mode 100644
index 8b507f28..00000000
--- a/归档/miniprogram/app.js
+++ /dev/null
@@ -1,540 +0,0 @@
-/**
- * Soul创业派对 - 小程序入口
- * 开发: 卡若
- */
-
-App({
- globalData: {
- // API基础地址 - 连接真实后端
- baseUrl: 'https://soul.quwanzhi.com',
-
- // 小程序配置 - 真实AppID
- appId: 'wxb8bbb2b10dec74aa',
-
- // 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗
- withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE',
-
- // 微信支付配置
- mchId: '1318592501', // 商户号
-
- // 用户信息
- userInfo: null,
- openId: null, // 微信openId,支付必需
- isLoggedIn: false,
-
- // 书籍数据
- bookData: null,
- totalSections: 62,
-
- // 购买记录
- purchasedSections: [],
- hasFullBook: false,
-
- // 已读章节(仅统计有权限打开过的章节,用于首页「已读/待读」)
- readSectionIds: [],
-
- // 推荐绑定
- pendingReferralCode: null, // 待绑定的推荐码
-
- // 主题配置
- theme: {
- brandColor: '#00CED1',
- brandSecondary: '#20B2AA',
- goldColor: '#FFD700',
- bgColor: '#000000',
- cardBg: '#1c1c1e'
- },
-
- // 系统信息
- systemInfo: null,
- statusBarHeight: 44,
- navBarHeight: 88,
-
- // TabBar相关
- currentTab: 0
- },
-
- onLaunch(options) {
- this.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || []
- // 获取系统信息
- this.getSystemInfo()
-
- // 检查登录状态
- this.checkLoginStatus()
-
- // 加载书籍数据
- this.loadBookData()
-
- // 检查更新
- this.checkUpdate()
-
- // 处理分享参数(推荐码绑定)
- this.handleReferralCode(options)
- },
-
- // 小程序显示时也检查分享参数
- onShow(options) {
- this.handleReferralCode(options)
- },
-
- // 处理推荐码绑定
- handleReferralCode(options) {
- const query = options?.query || {}
- const refCode = query.ref || query.referralCode
-
- if (refCode) {
- console.log('[App] 检测到推荐码:', refCode)
-
- // 立即记录访问(不需要登录,用于统计"通过链接进的人数")
- this.recordReferralVisit(refCode)
-
- // 保存待绑定的推荐码(不再在前端做"只能绑定一次"的限制,让后端根据30天规则判断续期/抢夺)
- this.globalData.pendingReferralCode = refCode
- wx.setStorageSync('pendingReferralCode', refCode)
- // 同步写入 referral_code,供章节/找伙伴支付时传给后端,订单会记录 referrer_id 与 referral_code
- wx.setStorageSync('referral_code', refCode)
-
- // 如果已登录,立即尝试绑定,由 /api/miniprogram/referral/bind 按 30 天规则决定 new / renew / takeover
- if (this.globalData.isLoggedIn && this.globalData.userInfo) {
- this.bindReferralCode(refCode)
- }
- }
- },
-
- // 记录推荐访问(不需要登录,用于统计)
- async recordReferralVisit(refCode) {
- try {
- // 获取openId(如果有)
- const openId = this.globalData.openId || wx.getStorageSync('openId') || ''
- const userId = this.globalData.userInfo?.id || ''
-
- await this.request('/api/miniprogram/referral/visit', {
- method: 'POST',
- data: {
- referralCode: refCode,
- visitorOpenId: openId,
- visitorId: userId,
- source: 'miniprogram',
- page: getCurrentPages()[getCurrentPages().length - 1]?.route || ''
- },
- silent: true
- })
- console.log('[App] 记录推荐访问成功')
- } catch (e) {
- console.log('[App] 记录推荐访问失败:', e.message)
- // 忽略错误,不影响用户体验
- }
- },
-
- // 绑定推荐码到用户
- async bindReferralCode(refCode) {
- try {
- const userId = this.globalData.userInfo?.id
- if (!userId || !refCode) return
-
- console.log('[App] 绑定推荐码:', refCode, '到用户:', userId)
-
- // 调用API绑定推荐关系
- const res = await this.request('/api/miniprogram/referral/bind', {
- method: 'POST',
- data: {
- userId,
- referralCode: refCode
- },
- silent: true
- })
-
- if (res.success) {
- console.log('[App] 推荐码绑定成功')
- // 仅记录当前已绑定的推荐码,用于展示/调试;是否允许更换由后端根据30天规则判断
- wx.setStorageSync('boundReferralCode', refCode)
- this.globalData.pendingReferralCode = null
- wx.removeStorageSync('pendingReferralCode')
- }
- } catch (e) {
- console.error('[App] 绑定推荐码失败:', e)
- }
- },
-
- // 获取系统信息
- getSystemInfo() {
- try {
- const systemInfo = wx.getSystemInfoSync()
- this.globalData.systemInfo = systemInfo
- this.globalData.statusBarHeight = systemInfo.statusBarHeight || 44
-
- // 计算导航栏高度
- const menuButton = wx.getMenuButtonBoundingClientRect()
- if (menuButton) {
- this.globalData.navBarHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.height + systemInfo.statusBarHeight
- }
- } catch (e) {
- console.error('获取系统信息失败:', e)
- }
- },
-
- // 检查登录状态
- checkLoginStatus() {
- try {
- const userInfo = wx.getStorageSync('userInfo')
- const token = wx.getStorageSync('token')
-
- if (userInfo && token) {
- this.globalData.userInfo = userInfo
- this.globalData.isLoggedIn = true
- this.globalData.purchasedSections = userInfo.purchasedSections || []
- this.globalData.hasFullBook = userInfo.hasFullBook || false
- }
- } catch (e) {
- console.error('检查登录状态失败:', e)
- }
- },
-
- // 加载书籍数据
- async loadBookData() {
- try {
- // 先从缓存加载
- const cachedData = wx.getStorageSync('bookData')
- if (cachedData) {
- this.globalData.bookData = cachedData
- }
-
- // 从服务器获取最新数据
- const res = await this.request('/api/book/all-chapters')
- if (res && (res.data || res.chapters)) {
- const chapters = res.data || res.chapters || []
- this.globalData.bookData = chapters
- wx.setStorageSync('bookData', chapters)
- }
- } catch (e) {
- console.error('加载书籍数据失败:', e)
- }
- },
-
- // 检查更新
- checkUpdate() {
- if (wx.canIUse('getUpdateManager')) {
- const updateManager = wx.getUpdateManager()
-
- updateManager.onCheckForUpdate((res) => {
- if (res.hasUpdate) {
- console.log('发现新版本')
- }
- })
-
- updateManager.onUpdateReady(() => {
- wx.showModal({
- title: '更新提示',
- content: '新版本已准备好,是否重启应用?',
- success: (res) => {
- if (res.confirm) {
- updateManager.applyUpdate()
- }
- }
- })
- })
-
- updateManager.onUpdateFailed(() => {
- wx.showToast({
- title: '更新失败,请稍后重试',
- icon: 'none'
- })
- })
- }
- },
-
- /**
- * 从 soul-api 返回体中取错误提示文案(兼容 message / error 字段)
- */
- _getApiErrorMsg(data, defaultMsg = '请求失败') {
- if (!data || typeof data !== 'object') return defaultMsg
- const msg = data.message || data.error
- return (msg && String(msg).trim()) ? String(msg).trim() : defaultMsg
- },
-
- /**
- * 统一请求方法。接口失败时会弹窗提示(与 soul-api 返回的 message/error 一致)。
- * @param {string|object} urlOrOptions - 接口路径,或 { url, method, data, header, silent }
- * @param {object} options - { method, data, header, silent }
- * @param {boolean} options.silent - 为 true 时不弹窗,仅 reject(用于静默请求如访问统计)
- */
- request(urlOrOptions, options = {}) {
- let url
- if (typeof urlOrOptions === 'string') {
- url = urlOrOptions
- } else if (urlOrOptions && typeof urlOrOptions === 'object' && urlOrOptions.url) {
- url = urlOrOptions.url
- options = { ...urlOrOptions, url: undefined }
- } else {
- url = ''
- }
- const silent = !!options.silent
- const showError = (msg) => {
- if (!silent && msg) {
- wx.showToast({ title: msg, icon: 'none', duration: 2500 })
- }
- }
-
- return new Promise((resolve, reject) => {
- const token = wx.getStorageSync('token')
-
- wx.request({
- url: this.globalData.baseUrl + url,
- method: options.method || 'GET',
- data: options.data || {},
- header: {
- 'Content-Type': 'application/json',
- 'Authorization': token ? `Bearer ${token}` : '',
- ...options.header
- },
- success: (res) => {
- const data = res.data
- if (res.statusCode === 200) {
- // 业务失败:success === false,soul-api 用 message 或 error 返回原因
- if (data && data.success === false) {
- const msg = this._getApiErrorMsg(data, '操作失败')
- showError(msg)
- reject(new Error(msg))
- return
- }
- resolve(data)
- return
- }
- if (res.statusCode === 401) {
- this.logout()
- showError('未授权,请重新登录')
- reject(new Error('未授权'))
- return
- }
- // 4xx/5xx:优先用返回体的 message/error
- const msg = this._getApiErrorMsg(data, res.statusCode >= 500 ? '服务器异常,请稍后重试' : '请求失败')
- showError(msg)
- reject(new Error(msg))
- },
- fail: (err) => {
- const msg = (err && err.errMsg) ? (err.errMsg.indexOf('timeout') !== -1 ? '请求超时,请重试' : '网络异常,请重试') : '网络异常,请重试'
- showError(msg)
- reject(new Error(msg))
- }
- })
- })
- },
-
- // 登录方法 - 获取openId用于支付(加固错误处理,避免审核报“登录报错”)
- async login() {
- try {
- const loginRes = await new Promise((resolve, reject) => {
- wx.login({ success: resolve, fail: reject })
- })
- if (!loginRes || !loginRes.code) {
- console.warn('[App] wx.login 未返回 code')
- wx.showToast({ title: '获取登录态失败,请重试', icon: 'none' })
- return null
- }
- try {
- const res = await this.request('/api/miniprogram/login', {
- method: 'POST',
- data: { code: loginRes.code }
- })
-
- if (res.success && res.data) {
- // 保存openId
- if (res.data.openId) {
- this.globalData.openId = res.data.openId
- wx.setStorageSync('openId', res.data.openId)
- console.log('[App] 获取openId成功')
- }
-
- // 保存用户信息
- if (res.data.user) {
- this.globalData.userInfo = res.data.user
- this.globalData.isLoggedIn = true
- this.globalData.purchasedSections = res.data.user.purchasedSections || []
- this.globalData.hasFullBook = res.data.user.hasFullBook || false
-
- wx.setStorageSync('userInfo', res.data.user)
- wx.setStorageSync('token', res.data.token || '')
-
- // 登录成功后,检查待绑定的推荐码并执行绑定
- const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode
- if (pendingRef) {
- console.log('[App] 登录后自动绑定推荐码:', pendingRef)
- this.bindReferralCode(pendingRef)
- }
- }
-
- return res.data
- }
- } catch (apiError) {
- console.log('[App] API登录失败:', apiError.message)
- // 不使用模拟登录,提示用户网络问题
- wx.showToast({ title: '网络异常,请重试', icon: 'none' })
- return null
- }
-
- return null
- } catch (e) {
- console.error('[App] 登录失败:', e)
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- return null
- }
- },
-
- // 获取openId (支付必需)
- async getOpenId() {
- // 先检查缓存
- const cachedOpenId = wx.getStorageSync('openId')
- if (cachedOpenId) {
- this.globalData.openId = cachedOpenId
- return cachedOpenId
- }
-
- // 没有缓存则登录获取
- try {
- const loginRes = await new Promise((resolve, reject) => {
- wx.login({ success: resolve, fail: reject })
- })
-
- const res = await this.request('/api/miniprogram/login', {
- method: 'POST',
- data: { code: loginRes.code }
- })
-
- if (res.success && res.data?.openId) {
- this.globalData.openId = res.data.openId
- wx.setStorageSync('openId', res.data.openId)
- // 接口同时返回 user 时视为登录,补全登录态并从登录开始绑定推荐码
- if (res.data.user) {
- this.globalData.userInfo = res.data.user
- this.globalData.isLoggedIn = true
- this.globalData.purchasedSections = res.data.user.purchasedSections || []
- this.globalData.hasFullBook = res.data.user.hasFullBook || false
- wx.setStorageSync('userInfo', res.data.user)
- wx.setStorageSync('token', res.data.token || '')
- const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode
- if (pendingRef) {
- console.log('[App] getOpenId 登录后自动绑定推荐码:', pendingRef)
- this.bindReferralCode(pendingRef)
- }
- }
- return res.data.openId
- }
- } catch (e) {
- console.error('[App] 获取openId失败:', e)
- }
-
- return null
- },
-
- // 模拟登录已废弃 - 不再使用
- // 现在必须使用真实的微信登录获取openId作为唯一标识
- mockLogin() {
- console.warn('[App] mockLogin已废弃,请使用真实登录')
- return null
- },
-
- // 手机号登录:需同时传 wx.login 的 code 与 getPhoneNumber 的 phoneCode
- async loginWithPhone(phoneCode) {
- try {
- const loginRes = await new Promise((resolve, reject) => {
- wx.login({ success: resolve, fail: reject })
- })
- if (!loginRes.code) {
- wx.showToast({ title: '获取登录态失败', icon: 'none' })
- return null
- }
- const res = await this.request('/api/miniprogram/phone-login', {
- method: 'POST',
- data: { code: loginRes.code, phoneCode }
- })
-
- if (res.success && res.data) {
- this.globalData.userInfo = res.data.user
- this.globalData.isLoggedIn = true
- this.globalData.purchasedSections = res.data.user.purchasedSections || []
- this.globalData.hasFullBook = res.data.user.hasFullBook || false
-
- wx.setStorageSync('userInfo', res.data.user)
- wx.setStorageSync('token', res.data.token)
-
- // 登录成功后绑定推荐码
- const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode
- if (pendingRef) {
- console.log('[App] 手机号登录后自动绑定推荐码:', pendingRef)
- this.bindReferralCode(pendingRef)
- }
-
- return res.data
- }
- } catch (e) {
- console.log('[App] 手机号登录失败:', e)
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- }
-
- return null
- },
-
- // 退出登录
- logout() {
- this.globalData.userInfo = null
- this.globalData.isLoggedIn = false
- this.globalData.purchasedSections = []
- this.globalData.hasFullBook = false
-
- wx.removeStorageSync('userInfo')
- wx.removeStorageSync('token')
- },
-
- // 检查是否已购买章节
- hasPurchased(sectionId) {
- if (this.globalData.hasFullBook) return true
- return this.globalData.purchasedSections.includes(sectionId)
- },
-
- // 标记章节为已读(仅在有权限打开时由阅读页调用,用于首页已读/待读统计)
- markSectionAsRead(sectionId) {
- if (!sectionId) return
- const list = this.globalData.readSectionIds || []
- if (list.includes(sectionId)) return
- list.push(sectionId)
- this.globalData.readSectionIds = list
- wx.setStorageSync('readSectionIds', list)
- },
-
- // 已读章节数(用于首页展示)
- getReadCount() {
- return (this.globalData.readSectionIds || []).length
- },
-
- // 获取章节总数
- getTotalSections() {
- return this.globalData.totalSections
- },
-
- // 切换TabBar
- switchTab(index) {
- this.globalData.currentTab = index
- },
-
- // 显示Toast
- showToast(title, icon = 'none') {
- wx.showToast({
- title,
- icon,
- duration: 2000
- })
- },
-
- // 显示Loading
- showLoading(title = '加载中...') {
- wx.showLoading({
- title,
- mask: true
- })
- },
-
- // 隐藏Loading
- hideLoading() {
- wx.hideLoading()
- }
-})
diff --git a/归档/miniprogram/app.json b/归档/miniprogram/app.json
deleted file mode 100644
index 4bbe3721..00000000
--- a/归档/miniprogram/app.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
- "pages": [
- "pages/index/index",
- "pages/chapters/chapters",
- "pages/match/match",
- "pages/my/my",
- "pages/read/read",
- "pages/about/about",
- "pages/agreement/agreement",
- "pages/privacy/privacy",
- "pages/referral/referral",
- "pages/purchases/purchases",
- "pages/settings/settings",
- "pages/search/search",
- "pages/addresses/addresses",
- "pages/addresses/edit",
- "pages/withdraw-records/withdraw-records",
- "pages/vip/vip",
- "pages/member-detail/member-detail"
- ],
- "window": {
- "backgroundTextStyle": "light",
- "navigationBarBackgroundColor": "#000000",
- "navigationBarTitleText": "Soul创业派对",
- "navigationBarTextStyle": "white",
- "backgroundColor": "#000000",
- "navigationStyle": "custom"
- },
- "tabBar": {
- "custom": true,
- "color": "#8e8e93",
- "selectedColor": "#00CED1",
- "backgroundColor": "#1c1c1e",
- "borderStyle": "black",
- "list": [
- {
- "pagePath": "pages/index/index",
- "text": "首页"
- },
- {
- "pagePath": "pages/chapters/chapters",
- "text": "目录"
- },
- {
- "pagePath": "pages/match/match",
- "text": "找伙伴"
- },
- {
- "pagePath": "pages/my/my",
- "text": "我的"
- }
- ]
- },
- "usingComponents": {},
- "__usePrivacyCheck__": true,
- "permission": {
- "scope.userLocation": {
- "desc": "用于匹配附近的书友"
- }
- },
- "requiredPrivateInfos": [
- "getLocation"
- ],
- "lazyCodeLoading": "requiredComponents",
- "style": "v2",
- "sitemapLocation": "sitemap.json"
-}
\ No newline at end of file
diff --git a/归档/miniprogram/app.wxss b/归档/miniprogram/app.wxss
deleted file mode 100644
index 9ce22a06..00000000
--- a/归档/miniprogram/app.wxss
+++ /dev/null
@@ -1,606 +0,0 @@
-/**
- * Soul创业实验 - 全局样式
- * 主题色: #00CED1 (Soul青色)
- * 开发: 卡若
- */
-
-/* ===== CSS 变量系统 ===== */
-page {
- /* 品牌色 */
- --app-brand: #00CED1;
- --app-brand-light: rgba(0, 206, 209, 0.1);
- --app-brand-dark: #20B2AA;
-
- /* 背景色 */
- --app-bg-primary: #000000;
- --app-bg-secondary: #1c1c1e;
- --app-bg-tertiary: #2c2c2e;
-
- /* 文字色 */
- --app-text-primary: #ffffff;
- --app-text-secondary: rgba(255, 255, 255, 0.7);
- --app-text-tertiary: rgba(255, 255, 255, 0.4);
-
- /* 分隔线 */
- --app-separator: rgba(255, 255, 255, 0.05);
-
- /* iOS 系统色 */
- --ios-indigo: #5856D6;
- --ios-green: #30d158;
- --ios-red: #FF3B30;
- --ios-orange: #FF9500;
- --ios-yellow: #FFD700;
-
- /* 金色 */
- --gold: #FFD700;
- --gold-light: #FFA500;
-
- /* 粉色 */
- --pink: #E91E63;
-
- /* 紫色 */
- --purple: #7B61FF;
-}
-
-/* ===== 页面基础样式 ===== */
-page {
- background-color: var(--app-bg-primary);
- color: var(--app-text-primary);
- font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', 'Microsoft YaHei', sans-serif;
- font-size: 28rpx;
- line-height: 1.5;
- -webkit-font-smoothing: antialiased;
-}
-
-/* ===== 全局容器 ===== */
-.container {
- min-height: 100vh;
- padding: 0;
- background: #000000;
- padding-bottom: env(safe-area-inset-bottom);
-}
-
-/* ===== 品牌色系 ===== */
-.brand-color {
- color: #00CED1;
-}
-
-.brand-bg {
- background-color: #00CED1;
-}
-
-.brand-gradient {
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
-}
-
-.gold-color {
- color: #FFD700;
-}
-
-.gold-bg {
- background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
-}
-
-/* ===== 文字渐变 ===== */
-.gradient-text {
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
-}
-
-.gold-gradient-text {
- background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
-}
-
-/* ===== 按钮样式 ===== */
-.btn-primary {
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- color: #ffffff;
- border: none;
- border-radius: 48rpx;
- padding: 28rpx 48rpx;
- font-size: 32rpx;
- font-weight: 600;
- box-shadow: 0 8rpx 24rpx rgba(0, 206, 209, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.btn-primary::after {
- border: none;
-}
-
-.btn-primary:active {
- opacity: 0.85;
- transform: scale(0.98);
-}
-
-.btn-secondary {
- background: rgba(0, 206, 209, 0.1);
- color: #00CED1;
- border: 2rpx solid rgba(0, 206, 209, 0.3);
- border-radius: 48rpx;
- padding: 28rpx 48rpx;
- font-size: 32rpx;
- font-weight: 500;
-}
-
-.btn-secondary::after {
- border: none;
-}
-
-.btn-secondary:active {
- background: rgba(0, 206, 209, 0.2);
-}
-
-.btn-ghost {
- background: rgba(255, 255, 255, 0.05);
- color: #ffffff;
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 48rpx;
- padding: 28rpx 48rpx;
- font-size: 32rpx;
-}
-
-.btn-ghost::after {
- border: none;
-}
-
-.btn-ghost:active {
- background: rgba(255, 255, 255, 0.1);
-}
-
-.btn-gold {
- background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
- color: #000000;
- border: none;
- border-radius: 48rpx;
- padding: 28rpx 48rpx;
- font-size: 32rpx;
- font-weight: 600;
- box-shadow: 0 8rpx 24rpx rgba(255, 215, 0, 0.3);
-}
-
-.btn-gold::after {
- border: none;
-}
-
-/* ===== 卡片样式 ===== */
-.card {
- background: rgba(28, 28, 30, 0.9);
- border-radius: 32rpx;
- padding: 32rpx;
- margin: 24rpx 32rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.card-light {
- background: rgba(44, 44, 46, 0.8);
- border-radius: 24rpx;
- padding: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.08);
-}
-
-.card-gradient {
- background: linear-gradient(135deg, rgba(28, 28, 30, 1) 0%, rgba(44, 44, 46, 1) 100%);
- border-radius: 32rpx;
- padding: 32rpx;
- border: 2rpx solid rgba(0, 206, 209, 0.2);
-}
-
-.card-brand {
- background: linear-gradient(135deg, rgba(0, 206, 209, 0.1) 0%, rgba(32, 178, 170, 0.05) 100%);
- border-radius: 32rpx;
- padding: 32rpx;
- border: 2rpx solid rgba(0, 206, 209, 0.2);
-}
-
-/* ===== 输入框样式 ===== */
-.input-ios {
- background: rgba(0, 0, 0, 0.3);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 24rpx;
- padding: 28rpx 32rpx;
- font-size: 32rpx;
- color: #ffffff;
-}
-
-.input-ios:focus {
- border-color: rgba(0, 206, 209, 0.5);
-}
-
-.input-ios-placeholder {
- color: rgba(255, 255, 255, 0.3);
-}
-
-/* ===== 列表项样式 ===== */
-.list-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 28rpx 32rpx;
- background: rgba(28, 28, 30, 0.9);
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.list-item:first-child {
- border-radius: 24rpx 24rpx 0 0;
-}
-
-.list-item:last-child {
- border-radius: 0 0 24rpx 24rpx;
- border-bottom: none;
-}
-
-.list-item:only-child {
- border-radius: 24rpx;
-}
-
-.list-item:active {
- background: rgba(44, 44, 46, 1);
-}
-
-/* ===== 标签样式 ===== */
-.tag {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 8rpx 20rpx;
- min-width: 80rpx;
- border-radius: 8rpx;
- font-size: 22rpx;
- font-weight: 500;
- box-sizing: border-box;
- text-align: center;
-}
-
-.tag-brand {
- background: rgba(0, 206, 209, 0.1);
- color: #00CED1;
-}
-
-.tag-gold {
- background: rgba(255, 215, 0, 0.1);
- color: #FFD700;
-}
-
-.tag-pink {
- background: rgba(233, 30, 99, 0.1);
- color: #E91E63;
-}
-
-.tag-purple {
- background: rgba(123, 97, 255, 0.1);
- color: #7B61FF;
-}
-
-.tag-free {
- background: rgba(0, 206, 209, 0.1);
- color: #00CED1;
-}
-
-/* ===== 分隔线 ===== */
-.divider {
- height: 1rpx;
- background: rgba(255, 255, 255, 0.05);
- margin: 24rpx 0;
-}
-
-.divider-vertical {
- width: 2rpx;
- height: 48rpx;
- background: rgba(255, 255, 255, 0.1);
-}
-
-/* ===== 骨架屏动画 ===== */
-.skeleton {
- background: linear-gradient(90deg,
- rgba(28, 28, 30, 1) 25%,
- rgba(44, 44, 46, 1) 50%,
- rgba(28, 28, 30, 1) 75%
- );
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s ease-in-out infinite;
- border-radius: 8rpx;
-}
-
-@keyframes skeleton-loading {
- 0% {
- background-position: 200% 0;
- }
- 100% {
- background-position: -200% 0;
- }
-}
-
-/* ===== 页面过渡动画 ===== */
-.page-transition {
- animation: fadeIn 0.3s ease-out;
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(20rpx);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-/* ===== 弹窗动画 ===== */
-.modal-overlay {
- animation: modalOverlayIn 0.25s ease-out;
-}
-
-.modal-content {
- animation: modalContentIn 0.3s cubic-bezier(0.32, 0.72, 0, 1);
-}
-
-@keyframes modalOverlayIn {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-
-@keyframes modalContentIn {
- from {
- opacity: 0;
- transform: scale(0.95) translateY(20rpx);
- }
- to {
- opacity: 1;
- transform: scale(1) translateY(0);
- }
-}
-
-/* ===== 脉动动画 ===== */
-.pulse {
- animation: pulse 2s ease-in-out infinite;
-}
-
-@keyframes pulse {
- 0%, 100% {
- transform: scale(1);
- opacity: 1;
- }
- 50% {
- transform: scale(1.05);
- opacity: 0.8;
- }
-}
-
-/* ===== 发光效果 ===== */
-.glow {
- box-shadow: 0 0 40rpx rgba(0, 206, 209, 0.3);
-}
-
-.glow-gold {
- box-shadow: 0 0 40rpx rgba(255, 215, 0, 0.3);
-}
-
-/* ===== 文字样式 ===== */
-.text-xs {
- font-size: 22rpx;
-}
-
-.text-sm {
- font-size: 26rpx;
-}
-
-.text-base {
- font-size: 28rpx;
-}
-
-.text-lg {
- font-size: 32rpx;
-}
-
-.text-xl {
- font-size: 36rpx;
-}
-
-.text-2xl {
- font-size: 44rpx;
-}
-
-.text-3xl {
- font-size: 56rpx;
-}
-
-.text-white {
- color: #ffffff;
-}
-
-.text-gray {
- color: rgba(255, 255, 255, 0.6);
-}
-
-.text-muted {
- color: rgba(255, 255, 255, 0.4);
-}
-
-.text-center {
- text-align: center;
-}
-
-.font-medium {
- font-weight: 500;
-}
-
-.font-semibold {
- font-weight: 600;
-}
-
-.font-bold {
- font-weight: 700;
-}
-
-/* ===== Flex布局 ===== */
-.flex {
- display: flex;
-}
-
-.flex-col {
- flex-direction: column;
-}
-
-.items-center {
- align-items: center;
-}
-
-.justify-center {
- justify-content: center;
-}
-
-.justify-between {
- justify-content: space-between;
-}
-
-.justify-around {
- justify-content: space-around;
-}
-
-.flex-1 {
- flex: 1;
-}
-
-.gap-1 {
- gap: 8rpx;
-}
-
-.gap-2 {
- gap: 16rpx;
-}
-
-.gap-3 {
- gap: 24rpx;
-}
-
-.gap-4 {
- gap: 32rpx;
-}
-
-/* ===== 间距 ===== */
-.p-2 { padding: 16rpx; }
-.p-3 { padding: 24rpx; }
-.p-4 { padding: 32rpx; }
-.p-5 { padding: 40rpx; }
-
-.px-4 { padding-left: 32rpx; padding-right: 32rpx; }
-.py-2 { padding-top: 16rpx; padding-bottom: 16rpx; }
-.py-3 { padding-top: 24rpx; padding-bottom: 24rpx; }
-
-.m-4 { margin: 32rpx; }
-.mx-4 { margin-left: 32rpx; margin-right: 32rpx; }
-.my-3 { margin-top: 24rpx; margin-bottom: 24rpx; }
-.mb-2 { margin-bottom: 16rpx; }
-.mb-3 { margin-bottom: 24rpx; }
-.mb-4 { margin-bottom: 32rpx; }
-.mt-4 { margin-top: 32rpx; }
-
-/* ===== 圆角 ===== */
-.rounded { border-radius: 8rpx; }
-.rounded-lg { border-radius: 16rpx; }
-.rounded-xl { border-radius: 24rpx; }
-.rounded-2xl { border-radius: 32rpx; }
-.rounded-full { border-radius: 50%; }
-
-/* ===== 安全区域 ===== */
-.safe-bottom {
- padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
-}
-
-.pb-tabbar {
- padding-bottom: 200rpx;
-}
-
-/* ===== 头部导航占位 ===== */
-.nav-placeholder {
- height: calc(88rpx + env(safe-area-inset-top, 44rpx));
-}
-
-/* ===== 隐藏滚动条 ===== */
-::-webkit-scrollbar {
- display: none;
- width: 0;
- height: 0;
-}
-
-/* ===== 触摸反馈 ===== */
-.touch-feedback {
- transition: all 0.15s ease;
-}
-
-.touch-feedback:active {
- opacity: 0.7;
- transform: scale(0.98);
-}
-
-/* ===== 进度条 ===== */
-.progress-bar {
- height: 8rpx;
- background: rgba(44, 44, 46, 1);
- border-radius: 4rpx;
- overflow: hidden;
-}
-
-.progress-fill {
- height: 100%;
- background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%);
- border-radius: 4rpx;
- transition: width 0.3s ease;
-}
-
-/* ===== 头像样式 ===== */
-.avatar {
- width: 80rpx;
- height: 80rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, rgba(32, 178, 170, 0.1) 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- color: #00CED1;
- font-weight: 700;
- font-size: 32rpx;
- border: 4rpx solid rgba(0, 206, 209, 0.3);
-}
-
-.avatar-lg {
- width: 120rpx;
- height: 120rpx;
- font-size: 48rpx;
-}
-
-/* ===== 图标容器 ===== */
-.icon-box {
- width: 64rpx;
- height: 64rpx;
- border-radius: 16rpx;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.icon-box-brand {
- background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, rgba(32, 178, 170, 0.1) 100%);
-}
-
-.icon-box-gold {
- background: linear-gradient(135deg, rgba(255, 215, 0, 0.2) 0%, rgba(255, 165, 0, 0.1) 100%);
-}
-
-/* ===== 渐变背景 ===== */
-.bg-gradient-dark {
- background: linear-gradient(180deg, #000000 0%, #1a1a1a 100%);
-}
-
-.bg-gradient-brand {
- background: linear-gradient(135deg, rgba(0, 206, 209, 0.1) 0%, transparent 100%);
-}
diff --git a/归档/miniprogram/assets/icons/alert-circle.svg b/归档/miniprogram/assets/icons/alert-circle.svg
deleted file mode 100644
index f5a441f3..00000000
--- a/归档/miniprogram/assets/icons/alert-circle.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/arrow-right.svg b/归档/miniprogram/assets/icons/arrow-right.svg
deleted file mode 100644
index 1dc64d3f..00000000
--- a/归档/miniprogram/assets/icons/arrow-right.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/bell.svg b/归档/miniprogram/assets/icons/bell.svg
deleted file mode 100644
index 0e7e405b..00000000
--- a/归档/miniprogram/assets/icons/bell.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/book-open.svg b/归档/miniprogram/assets/icons/book-open.svg
deleted file mode 100644
index d833e86b..00000000
--- a/归档/miniprogram/assets/icons/book-open.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/book.svg b/归档/miniprogram/assets/icons/book.svg
deleted file mode 100644
index 93579576..00000000
--- a/归档/miniprogram/assets/icons/book.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/chevron-left.svg b/归档/miniprogram/assets/icons/chevron-left.svg
deleted file mode 100644
index e406b2b9..00000000
--- a/归档/miniprogram/assets/icons/chevron-left.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/gift.svg b/归档/miniprogram/assets/icons/gift.svg
deleted file mode 100644
index 66ac806c..00000000
--- a/归档/miniprogram/assets/icons/gift.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/home-active.png b/归档/miniprogram/assets/icons/home-active.png
deleted file mode 100644
index b6090d87..00000000
Binary files a/归档/miniprogram/assets/icons/home-active.png and /dev/null differ
diff --git a/归档/miniprogram/assets/icons/home.png b/归档/miniprogram/assets/icons/home.png
deleted file mode 100644
index 0ffba614..00000000
Binary files a/归档/miniprogram/assets/icons/home.png and /dev/null differ
diff --git a/归档/miniprogram/assets/icons/home.svg b/归档/miniprogram/assets/icons/home.svg
deleted file mode 100644
index 76244091..00000000
--- a/归档/miniprogram/assets/icons/home.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/image.svg b/归档/miniprogram/assets/icons/image.svg
deleted file mode 100644
index 50ed9e6d..00000000
--- a/归档/miniprogram/assets/icons/image.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/list.svg b/归档/miniprogram/assets/icons/list.svg
deleted file mode 100644
index 688326aa..00000000
--- a/归档/miniprogram/assets/icons/list.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/match-active.png b/归档/miniprogram/assets/icons/match-active.png
deleted file mode 100644
index da62b436..00000000
Binary files a/归档/miniprogram/assets/icons/match-active.png and /dev/null differ
diff --git a/归档/miniprogram/assets/icons/match.png b/归档/miniprogram/assets/icons/match.png
deleted file mode 100644
index b15582e3..00000000
Binary files a/归档/miniprogram/assets/icons/match.png and /dev/null differ
diff --git a/归档/miniprogram/assets/icons/message-circle.svg b/归档/miniprogram/assets/icons/message-circle.svg
deleted file mode 100644
index 037560e9..00000000
--- a/归档/miniprogram/assets/icons/message-circle.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/my-active.png b/归档/miniprogram/assets/icons/my-active.png
deleted file mode 100644
index da62b436..00000000
Binary files a/归档/miniprogram/assets/icons/my-active.png and /dev/null differ
diff --git a/归档/miniprogram/assets/icons/my.png b/归档/miniprogram/assets/icons/my.png
deleted file mode 100644
index b15582e3..00000000
Binary files a/归档/miniprogram/assets/icons/my.png and /dev/null differ
diff --git a/归档/miniprogram/assets/icons/partners.svg b/归档/miniprogram/assets/icons/partners.svg
deleted file mode 100644
index 80668312..00000000
--- a/归档/miniprogram/assets/icons/partners.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/settings.svg b/归档/miniprogram/assets/icons/settings.svg
deleted file mode 100644
index c7006ea8..00000000
--- a/归档/miniprogram/assets/icons/settings.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/share.svg b/归档/miniprogram/assets/icons/share.svg
deleted file mode 100644
index 93179fc2..00000000
--- a/归档/miniprogram/assets/icons/share.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/sparkles.svg b/归档/miniprogram/assets/icons/sparkles.svg
deleted file mode 100644
index e2a4461f..00000000
--- a/归档/miniprogram/assets/icons/sparkles.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/user.svg b/归档/miniprogram/assets/icons/user.svg
deleted file mode 100644
index 8b190427..00000000
--- a/归档/miniprogram/assets/icons/user.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/users.svg b/归档/miniprogram/assets/icons/users.svg
deleted file mode 100644
index 4816094b..00000000
--- a/归档/miniprogram/assets/icons/users.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/归档/miniprogram/assets/icons/wallet.svg b/归档/miniprogram/assets/icons/wallet.svg
deleted file mode 100644
index 6d431e54..00000000
--- a/归档/miniprogram/assets/icons/wallet.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/归档/miniprogram/components/icon/README.md b/归档/miniprogram/components/icon/README.md
deleted file mode 100644
index 34e394c8..00000000
--- a/归档/miniprogram/components/icon/README.md
+++ /dev/null
@@ -1,175 +0,0 @@
-# Icon 图标组件
-
-SVG 图标组件,参考 lucide-react 实现,用于在小程序中使用矢量图标。
-
-**技术实现**: 使用 Base64 编码的 SVG + image 组件(小程序不支持直接使用 SVG 标签)
-
----
-
-## 使用方法
-
-### 1. 在页面 JSON 中引入组件
-
-```json
-{
- "usingComponents": {
- "icon": "/components/icon/icon"
- }
-}
-```
-
-### 2. 在 WXML 中使用
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
----
-
-## 属性说明
-
-| 属性 | 类型 | 默认值 | 说明 |
-|-----|------|--------|-----|
-| name | String | 'share' | 图标名称 |
-| size | Number | 48 | 图标大小(rpx) |
-| color | String | 'currentColor' | 图标颜色 |
-| customClass | String | '' | 自定义类名 |
-| customStyle | String | '' | 自定义样式 |
-
----
-
-## 可用图标
-
-| 图标名称 | 说明 | 对应 lucide-react |
-|---------|------|-------------------|
-| `share` | 分享 | `` |
-| `arrow-up-right` | 右上箭头 | `` |
-| `chevron-left` | 左箭头 | `` |
-| `search` | 搜索 | `` |
-| `heart` | 心形 | `` |
-
----
-
-## 添加新图标
-
-在 `icon.js` 的 `getSvgPath` 方法中添加新图标:
-
-```javascript
-getSvgPath(name) {
- const svgMap = {
- 'new-icon': '',
- // ... 其他图标
- }
- return svgMap[name] || ''
-}
-```
-
-**获取 SVG 代码**: 访问 [lucide.dev](https://lucide.dev) 搜索图标,复制 SVG 内容。
-**注意**: 颜色使用 `COLOR` 占位符,组件会自动替换。
-
----
-
-## 样式定制
-
-### 1. 使用 customClass
-
-```xml
-
-```
-
-```css
-.my-icon-class {
- opacity: 0.8;
-}
-```
-
-### 2. 使用 customStyle
-
-```xml
-
-```
-
----
-
-## 技术说明
-
-### 为什么使用 Base64 + image?
-
-1. **矢量图标**:任意缩放不失真
-2. **灵活着色**:通过 `COLOR` 占位符动态改变颜色
-3. **轻量级**:无需加载字体文件或外部图片
-4. **兼容性**:小程序不支持直接使用 SVG 标签,image 组件支持 Base64 SVG
-
-### 为什么不用字体图标?
-
-小程序对字体文件有限制,Base64 编码字体文件会增加包体积,SVG 图标更轻量。
-
-### 与 lucide-react 的对应关系
-
-- **lucide-react**: React 组件库,使用 SVG
-- **本组件**: 小程序自定义组件,也使用 SVG
-- **SVG path 数据**: 完全相同,从 lucide 官网复制
-
----
-
-## 示例
-
-### 悬浮分享按钮
-
-```xml
-
-```
-
-```css
-.fab-share {
- position: fixed;
- right: 32rpx;
- bottom: calc(120rpx + env(safe-area-inset-bottom));
- width: 96rpx;
- height: 96rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-```
-
----
-
-## 扩展图标库
-
-可以继续添加更多 lucide-react 图标:
-
-- `star` - 星星
-- `wallet` - 钱包
-- `gift` - 礼物
-- `info` - 信息
-- `settings` - 设置
-- `user` - 用户
-- `book-open` - 打开的书
-- `eye` - 眼睛
-- `clock` - 时钟
-- `users` - 用户组
-
----
-
-**图标组件创建完成!** 🎉
diff --git a/归档/miniprogram/components/icon/icon.js b/归档/miniprogram/components/icon/icon.js
deleted file mode 100644
index b2dec23f..00000000
--- a/归档/miniprogram/components/icon/icon.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// components/icon/icon.js
-Component({
- properties: {
- // 图标名称
- name: {
- type: String,
- value: 'share',
- observer: 'updateIcon'
- },
- // 图标大小(rpx)
- size: {
- type: Number,
- value: 48
- },
- // 图标颜色
- color: {
- type: String,
- value: '#ffffff',
- observer: 'updateIcon'
- },
- // 自定义类名
- customClass: {
- type: String,
- value: ''
- },
- // 自定义样式
- customStyle: {
- type: String,
- value: ''
- }
- },
-
- data: {
- svgData: ''
- },
-
- lifetimes: {
- attached() {
- this.updateIcon()
- }
- },
-
- methods: {
- // SVG 图标数据映射
- getSvgPath(name) {
- const svgMap = {
- 'share': '',
-
- 'arrow-up-right': '',
-
- 'chevron-left': '',
-
- 'search': '',
-
- 'heart': ''
- }
-
- return svgMap[name] || ''
- },
-
- // 更新图标
- updateIcon() {
- const { name, color } = this.data
- let svgString = this.getSvgPath(name)
-
- if (svgString) {
- // 替换颜色占位符
- svgString = svgString.replace(/COLOR/g, color)
-
- // 转换为 Base64 Data URL
- const svgData = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`
-
- this.setData({
- svgData: svgData
- })
- } else {
- this.setData({
- svgData: ''
- })
- }
- }
- }
-})
diff --git a/归档/miniprogram/components/icon/icon.json b/归档/miniprogram/components/icon/icon.json
deleted file mode 100644
index a89ef4db..00000000
--- a/归档/miniprogram/components/icon/icon.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "component": true,
- "usingComponents": {}
-}
diff --git a/归档/miniprogram/components/icon/icon.wxml b/归档/miniprogram/components/icon/icon.wxml
deleted file mode 100644
index b1c29a25..00000000
--- a/归档/miniprogram/components/icon/icon.wxml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
- {{name}}
-
diff --git a/归档/miniprogram/components/icon/icon.wxss b/归档/miniprogram/components/icon/icon.wxss
deleted file mode 100644
index d12d2a0a..00000000
--- a/归档/miniprogram/components/icon/icon.wxss
+++ /dev/null
@@ -1,18 +0,0 @@
-/* components/icon/icon.wxss */
-.icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.icon-image {
- display: block;
- width: 100%;
- height: 100%;
-}
-
-.icon-text {
- font-size: 24rpx;
- color: currentColor;
-}
diff --git a/归档/miniprogram/custom-tab-bar/index.js b/归档/miniprogram/custom-tab-bar/index.js
deleted file mode 100644
index 4acd9546..00000000
--- a/归档/miniprogram/custom-tab-bar/index.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- * Soul创业实验 - 自定义TabBar组件
- * 根据后台配置动态显示/隐藏"找伙伴"按钮
- */
-
-console.log('[TabBar] ===== 组件文件开始加载 =====')
-
-const app = getApp()
-console.log('[TabBar] App 对象:', app)
-
-Component({
- data: {
- selected: 0,
- color: '#8e8e93',
- selectedColor: '#00CED1',
- matchEnabled: false, // 找伙伴功能开关,默认关闭
- list: [
- {
- pagePath: '/pages/index/index',
- text: '首页',
- iconType: 'home'
- },
- {
- pagePath: '/pages/chapters/chapters',
- text: '目录',
- iconType: 'list'
- },
- {
- pagePath: '/pages/match/match',
- text: '找伙伴',
- iconType: 'match',
- isSpecial: true
- },
- {
- pagePath: '/pages/my/my',
- text: '我的',
- iconType: 'user'
- }
- ]
- },
-
- lifetimes: {
- attached() {
- console.log('[TabBar] Component attached 生命周期触发')
- this.loadFeatureConfig()
- },
- ready() {
- console.log('[TabBar] Component ready 生命周期触发')
- // 如果 attached 中没有成功加载,在 ready 中再次尝试
- if (this.data.matchEnabled === undefined || this.data.matchEnabled === null) {
- console.log('[TabBar] 在 ready 中重新加载配置')
- this.loadFeatureConfig()
- }
- }
- },
-
- // 页面加载时也调用(兼容性更好)
- attached() {
- console.log('[TabBar] attached() 方法触发')
- this.loadFeatureConfig()
- },
-
- methods: {
- // 加载功能配置
- async loadFeatureConfig() {
- try {
- console.log('[TabBar] 开始加载功能配置...')
- console.log('[TabBar] API地址:', app.globalData.baseUrl + '/api/miniprogram/config')
-
- // app.request 的第一个参数是 url 字符串,第二个参数是 options 对象
- const res = await app.request('/api/miniprogram/config', {
- method: 'GET'
- })
-
-
- // 兼容两种返回格式
- let matchEnabled = false
-
- if (res && res.success && res.features) {
- console.log('[TabBar] features配置:', JSON.stringify(res.features))
- matchEnabled = res.features.matchEnabled === true
- console.log('[TabBar] matchEnabled值:', matchEnabled)
- } else if (res && res.configs && res.configs.feature_config) {
- // 备用格式:从 configs.feature_config 读取
- console.log('[TabBar] 使用备用格式,从configs读取')
- matchEnabled = res.configs.feature_config.matchEnabled === true
- console.log('[TabBar] matchEnabled值:', matchEnabled)
- } else {
- console.log('[TabBar] ⚠️ 未找到features配置,使用默认值false')
- console.log('[TabBar] res对象keys:', Object.keys(res || {}))
- }
-
- this.setData({ matchEnabled }, () => {
- console.log('[TabBar] ✅ matchEnabled已设置为:', this.data.matchEnabled)
- // 配置加载完成后,根据当前路由设置选中状态
- this.updateSelected()
- })
-
- // 如果当前在找伙伴页面,但功能已关闭,跳转到首页
- if (!matchEnabled) {
- const pages = getCurrentPages()
- const currentPage = pages[pages.length - 1]
- if (currentPage && currentPage.route === 'pages/match/match') {
- console.log('[TabBar] 找伙伴功能已关闭,从match页面跳转到首页')
- wx.switchTab({ url: '/pages/index/index' })
- }
- }
- } catch (error) {
- console.log('[TabBar] ❌ 加载功能配置失败:', error)
- console.log('[TabBar] 错误详情:', error.message || error)
- // 默认关闭找伙伴功能
- this.setData({ matchEnabled: false }, () => {
- this.updateSelected()
- })
- }
- },
-
- // 根据当前路由更新选中状态
- updateSelected() {
- const pages = getCurrentPages()
- if (pages.length === 0) return
-
- const currentPage = pages[pages.length - 1]
- const route = currentPage.route
-
- let selected = 0
- const { matchEnabled } = this.data
-
- // 根据路由匹配对应的索引
- if (route === 'pages/index/index') {
- selected = 0
- } else if (route === 'pages/chapters/chapters') {
- selected = 1
- } else if (route === 'pages/match/match') {
- selected = 2
- } else if (route === 'pages/my/my') {
- selected = matchEnabled ? 3 : 2
- }
-
- this.setData({ selected })
- },
-
- switchTab(e) {
- const data = e.currentTarget.dataset
- const url = data.path
- const index = data.index
-
- if (this.data.selected === index) return
-
- wx.switchTab({ url })
- }
- }
-})
diff --git a/归档/miniprogram/custom-tab-bar/index.json b/归档/miniprogram/custom-tab-bar/index.json
deleted file mode 100644
index 467ce294..00000000
--- a/归档/miniprogram/custom-tab-bar/index.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "component": true
-}
diff --git a/归档/miniprogram/custom-tab-bar/index.wxml b/归档/miniprogram/custom-tab-bar/index.wxml
deleted file mode 100644
index 73369b2a..00000000
--- a/归档/miniprogram/custom-tab-bar/index.wxml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{list[0].text}}
-
-
-
-
-
-
-
- {{list[1].text}}
-
-
-
-
-
-
-
- {{list[2].text}}
-
-
-
-
-
-
-
- {{list[3].text}}
-
-
diff --git a/归档/miniprogram/custom-tab-bar/index.wxss b/归档/miniprogram/custom-tab-bar/index.wxss
deleted file mode 100644
index 98036655..00000000
--- a/归档/miniprogram/custom-tab-bar/index.wxss
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * Soul创业实验 - 自定义TabBar样式
- * 实现中间突出的"找伙伴"按钮
- */
-
-.tab-bar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- height: 100rpx;
- background: rgba(28, 28, 30, 0.95);
- backdrop-filter: blur(40rpx);
- -webkit-backdrop-filter: blur(40rpx);
- display: flex;
- align-items: flex-end;
- padding-bottom: env(safe-area-inset-bottom);
- z-index: 999;
-}
-
-/* 三个tab布局(找伙伴功能关闭时) */
-.tab-bar-three .tab-bar-item {
- flex: 1;
-}
-
-/* 四个tab布局(找伙伴功能开启时) */
-.tab-bar-four .tab-bar-item {
- flex: 1;
-}
-
-.tab-bar-border {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 1rpx;
- background: rgba(255, 255, 255, 0.05);
-}
-
-.tab-bar-item {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 10rpx 0 16rpx;
-}
-
-.icon-wrapper {
- width: 48rpx;
- height: 48rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 4rpx;
-}
-
-.icon {
- width: 44rpx;
- height: 44rpx;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.tab-bar-text {
- font-size: 22rpx;
- line-height: 1;
-}
-
-/* ===== SVG 图标样式 ===== */
-.tab-icon {
- width: 48rpx;
- height: 48rpx;
- display: block;
- filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%);
-}
-
-.tab-icon.icon-active {
- filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%);
-}
-
-
-/* ===== 找伙伴 - 中间特殊按钮 ===== */
-.special-item {
- position: relative;
- margin-top: -32rpx;
-}
-
-.special-button {
- width: 112rpx;
- height: 112rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 8rpx 32rpx rgba(0, 206, 209, 0.4);
- margin-bottom: 4rpx;
- transition: all 0.2s ease;
-}
-
-.special-button:active {
- transform: scale(0.95);
-}
-
-.special-active {
- box-shadow: 0 8rpx 40rpx rgba(0, 206, 209, 0.6);
-}
-
-.special-text {
- margin-top: 4rpx;
-}
-
-/* ===== 找伙伴特殊按钮图标 ===== */
-.special-icon {
- width: 80rpx;
- height: 80rpx;
- display: block;
- filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%);
-}
diff --git a/归档/miniprogram/pages/about/about.js b/归档/miniprogram/pages/about/about.js
deleted file mode 100644
index 8f19cc60..00000000
--- a/归档/miniprogram/pages/about/about.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * Soul创业派对 - 关于作者页
- * 开发: 卡若
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- author: {
- name: '卡若',
- avatar: 'K',
- title: 'Soul派对房主理人 · 私域运营专家',
- bio: '每天早上6点到9点,在Soul派对房分享真实的创业故事。专注私域运营与项目变现,用"云阿米巴"模式帮助创业者构建可持续的商业体系。本书记录了62个真实商业案例,涵盖电商、内容、传统行业等多个领域。',
- stats: [
- { label: '商业案例', value: '62' },
- { label: '连续直播', value: '365天' },
- { label: '派对分享', value: '1000+' }
- ],
- // 联系方式已移至后台配置
- contact: null,
- highlights: [
- '5年私域运营经验',
- '帮助100+品牌从0到1增长',
- '连续创业者,擅长商业模式设计'
- ]
- },
- bookInfo: {
- title: '一场Soul的创业实验',
- totalChapters: 62,
- parts: [
- { name: '真实的人', chapters: 10 },
- { name: '真实的行业', chapters: 15 },
- { name: '真实的错误', chapters: 9 },
- { name: '真实的赚钱', chapters: 20 },
- { name: '真实的社会', chapters: 9 }
- ],
- price: 9.9
- }
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight
- })
- this.loadBookStats()
- },
-
- // 加载书籍统计
- async loadBookStats() {
- try {
- const res = await app.request('/api/miniprogram/book/stats')
- if (res && res.success) {
- this.setData({
- 'bookInfo.totalChapters': res.data?.totalChapters || 62,
- 'author.stats': [
- { label: '商业案例', value: String(res.data?.totalChapters || 62) },
- { label: '连续直播', value: '365天' },
- { label: '派对分享', value: '1000+' }
- ]
- })
- }
- } catch (e) {
- console.log('[About] 加载书籍统计失败,使用默认值')
- }
- },
-
- // 联系方式功能已禁用
- copyWechat() {
- wx.showToast({ title: '请在派对房联系作者', icon: 'none' })
- },
-
- callPhone() {
- wx.showToast({ title: '请在派对房联系作者', icon: 'none' })
- },
-
- // 返回
- goBack() {
- wx.navigateBack()
- }
-})
diff --git a/归档/miniprogram/pages/about/about.json b/归档/miniprogram/pages/about/about.json
deleted file mode 100644
index e90e9960..00000000
--- a/归档/miniprogram/pages/about/about.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom"
-}
diff --git a/归档/miniprogram/pages/about/about.wxml b/归档/miniprogram/pages/about/about.wxml
deleted file mode 100644
index 598e9464..00000000
--- a/归档/miniprogram/pages/about/about.wxml
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
- ←
- 关于作者
-
-
-
-
-
-
-
- {{author.avatar}}
- {{author.name}}
- {{author.title}}
- {{author.bio}}
-
-
-
-
- {{item.value}}
- {{item.label}}
-
-
-
-
-
-
- ✓
- {{item}}
-
-
-
-
-
-
- 📚 {{bookInfo.title}}
-
-
- {{bookInfo.totalChapters}}
- 篇章节
-
-
- 5
- 大篇章
-
-
- ¥{{bookInfo.price}}
- 全书价格
-
-
-
-
- {{item.name}}
- {{item.chapters}}节
-
-
-
-
-
-
- 联系作者
-
- 🎉
-
- Soul派对房
- 每天早上6-9点开播
-
-
-
- 在Soul App搜索"创业实验"或"卡若",加入派对房直接交流
-
-
-
-
diff --git a/归档/miniprogram/pages/about/about.wxss b/归档/miniprogram/pages/about/about.wxss
deleted file mode 100644
index 337aa041..00000000
--- a/归档/miniprogram/pages/about/about.wxss
+++ /dev/null
@@ -1,40 +0,0 @@
-.page { min-height: 100vh; background: #000; }
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-back { width: 72rpx; height: 72rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: #fff; }
-.nav-title { font-size: 36rpx; font-weight: 600; color: #00CED1; }
-.nav-placeholder { width: 72rpx; }
-.content { padding: 32rpx; }
-.author-card { background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%); border-radius: 32rpx; padding: 48rpx; text-align: center; margin-bottom: 24rpx; border: 2rpx solid rgba(0,206,209,0.2); }
-.author-avatar { width: 160rpx; height: 160rpx; border-radius: 50%; background: linear-gradient(135deg, #00CED1, #20B2AA); display: flex; align-items: center; justify-content: center; margin: 0 auto 24rpx; font-size: 64rpx; color: #fff; font-weight: 700; border: 4rpx solid rgba(0,206,209,0.3); }
-.author-name { font-size: 40rpx; font-weight: 700; color: #fff; display: block; margin-bottom: 8rpx; }
-.author-title { font-size: 26rpx; color: #00CED1; display: block; margin-bottom: 24rpx; }
-.author-bio { font-size: 26rpx; color: rgba(255,255,255,0.7); line-height: 1.8; display: block; margin-bottom: 32rpx; }
-.stats-row { display: flex; justify-content: space-around; padding-top: 32rpx; border-top: 2rpx solid rgba(255,255,255,0.1); }
-.stat-item { text-align: center; }
-.stat-value { font-size: 36rpx; font-weight: 700; color: #00CED1; display: block; }
-.stat-label { font-size: 22rpx; color: rgba(255,255,255,0.5); }
-.contact-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; }
-.card-title { font-size: 28rpx; font-weight: 600; color: #fff; display: block; margin-bottom: 24rpx; }
-.contact-item { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 16rpx; }
-.contact-item:last-child { margin-bottom: 0; }
-.contact-icon { font-size: 40rpx; }
-.contact-info { flex: 1; }
-.contact-label { font-size: 22rpx; color: rgba(255,255,255,0.5); display: block; }
-.contact-value { font-size: 28rpx; color: #fff; }
-.contact-btn { padding: 12rpx 24rpx; background: rgba(0,206,209,0.2); color: #00CED1; font-size: 24rpx; border-radius: 16rpx; }
-
-/* 亮点标签 */
-.highlights { display: flex; flex-wrap: wrap; gap: 16rpx; margin-top: 32rpx; padding-top: 24rpx; border-top: 2rpx solid rgba(255,255,255,0.1); justify-content: center; }
-.highlight-tag { display: flex; align-items: center; gap: 8rpx; padding: 12rpx 24rpx; background: rgba(0,206,209,0.15); border-radius: 24rpx; font-size: 24rpx; color: rgba(255,255,255,0.8); }
-.tag-icon { color: #00CED1; font-size: 22rpx; }
-
-/* 书籍信息卡片 */
-.book-info-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; margin-bottom: 24rpx; }
-.book-stats { display: flex; justify-content: space-around; padding: 24rpx 0; margin: 16rpx 0; background: rgba(0,0,0,0.3); border-radius: 16rpx; }
-.book-stat { text-align: center; }
-.book-stat-value { font-size: 36rpx; font-weight: 700; color: #FFD700; display: block; }
-.book-stat-label { font-size: 22rpx; color: rgba(255,255,255,0.5); }
-.parts-list { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 16rpx; }
-.part-item { display: flex; align-items: center; gap: 8rpx; padding: 12rpx 20rpx; background: rgba(255,255,255,0.05); border-radius: 12rpx; }
-.part-name { font-size: 24rpx; color: rgba(255,255,255,0.8); }
-.part-chapters { font-size: 22rpx; color: #00CED1; }
diff --git a/归档/miniprogram/pages/addresses/addresses.js b/归档/miniprogram/pages/addresses/addresses.js
deleted file mode 100644
index 685528cf..00000000
--- a/归档/miniprogram/pages/addresses/addresses.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * 收货地址列表页
- * 参考 Next.js: app/view/my/addresses/page.tsx
- */
-
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- isLoggedIn: false,
- addressList: [],
- loading: true
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight || 44
- })
- this.checkLogin()
- },
-
- onShow() {
- if (this.data.isLoggedIn) {
- this.loadAddresses()
- }
- },
-
- // 检查登录状态
- checkLogin() {
- const isLoggedIn = app.globalData.isLoggedIn
- const userId = app.globalData.userInfo?.id
-
- if (!isLoggedIn || !userId) {
- wx.showModal({
- title: '需要登录',
- content: '请先登录后再管理收货地址',
- confirmText: '去登录',
- success: (res) => {
- if (res.confirm) {
- wx.switchTab({ url: '/pages/my/my' })
- } else {
- wx.navigateBack()
- }
- }
- })
- return
- }
-
- this.setData({ isLoggedIn: true })
- this.loadAddresses()
- },
-
- // 加载地址列表
- async loadAddresses() {
- const userId = app.globalData.userInfo?.id
- if (!userId) return
-
- this.setData({ loading: true })
-
- try {
- const res = await app.request(`/api/miniprogram/user/addresses?userId=${userId}`)
- if (res.success && res.list) {
- this.setData({
- addressList: res.list,
- loading: false
- })
- } else {
- this.setData({ addressList: [], loading: false })
- }
- } catch (e) {
- console.error('加载地址列表失败:', e)
- this.setData({ loading: false })
- wx.showToast({ title: '加载失败', icon: 'none' })
- }
- },
-
- // 编辑地址
- editAddress(e) {
- const id = e.currentTarget.dataset.id
- wx.navigateTo({ url: `/pages/addresses/edit?id=${id}` })
- },
-
- // 删除地址
- deleteAddress(e) {
- const id = e.currentTarget.dataset.id
-
- wx.showModal({
- title: '确认删除',
- content: '确定要删除该收货地址吗?',
- confirmColor: '#FF3B30',
- success: async (res) => {
- if (res.confirm) {
- try {
- const result = await app.request(`/api/miniprogram/user/addresses/${id}`, {
- method: 'DELETE'
- })
-
- if (result.success) {
- wx.showToast({ title: '删除成功', icon: 'success' })
- this.loadAddresses()
- } else {
- wx.showToast({ title: result.message || '删除失败', icon: 'none' })
- }
- } catch (e) {
- console.error('删除地址失败:', e)
- wx.showToast({ title: '删除失败', icon: 'none' })
- }
- }
- }
- })
- },
-
- // 新增地址
- addAddress() {
- wx.navigateTo({ url: '/pages/addresses/edit' })
- },
-
- // 返回
- goBack() {
- wx.navigateBack()
- }
-})
diff --git a/归档/miniprogram/pages/addresses/addresses.json b/归档/miniprogram/pages/addresses/addresses.json
deleted file mode 100644
index 2e45b65e..00000000
--- a/归档/miniprogram/pages/addresses/addresses.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom",
- "enablePullDownRefresh": false
-}
diff --git a/归档/miniprogram/pages/addresses/addresses.wxml b/归档/miniprogram/pages/addresses/addresses.wxml
deleted file mode 100644
index cec2ef6e..00000000
--- a/归档/miniprogram/pages/addresses/addresses.wxml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
- ‹
-
- 收货地址
-
-
-
-
-
-
-
- 加载中...
-
-
-
-
- 📍
- 暂无收货地址
- 点击下方按钮添加
-
-
-
-
-
-
- {{item.fullAddress}}
-
-
- ✏️
- 编辑
-
-
- 🗑️
- 删除
-
-
-
-
-
-
-
- ➕
- 新增收货地址
-
-
-
diff --git a/归档/miniprogram/pages/addresses/addresses.wxss b/归档/miniprogram/pages/addresses/addresses.wxss
deleted file mode 100644
index 9ff21637..00000000
--- a/归档/miniprogram/pages/addresses/addresses.wxss
+++ /dev/null
@@ -1,217 +0,0 @@
-/**
- * 收货地址列表页样式
- */
-
-.page {
- min-height: 100vh;
- background: #000000;
- padding-bottom: 200rpx;
-}
-
-/* ===== 导航栏 ===== */
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(0, 0, 0, 0.9);
- backdrop-filter: blur(40rpx);
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.nav-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 32rpx;
- height: 88rpx;
-}
-
-.nav-back {
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.nav-back:active {
- background: rgba(255, 255, 255, 0.15);
-}
-
-.back-icon {
- font-size: 48rpx;
- color: #ffffff;
- line-height: 1;
-}
-
-.nav-title {
- flex: 1;
- text-align: center;
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.nav-placeholder {
- width: 64rpx;
-}
-
-/* ===== 内容区 ===== */
-.content {
- padding: 32rpx;
-}
-
-/* ===== 加载状态 ===== */
-.loading-state {
- padding: 240rpx 0;
- text-align: center;
-}
-
-.loading-text {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 空状态 ===== */
-.empty-state {
- padding: 240rpx 0;
- text-align: center;
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-.empty-icon {
- font-size: 96rpx;
- margin-bottom: 24rpx;
- opacity: 0.3;
-}
-
-.empty-text {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
- margin-bottom: 16rpx;
-}
-
-.empty-tip {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 地址列表 ===== */
-.address-list {
- margin-bottom: 24rpx;
-}
-
-.address-card {
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- padding: 32rpx;
- margin-bottom: 24rpx;
-}
-
-/* 地址头部 */
-.address-header {
- display: flex;
- align-items: center;
- gap: 16rpx;
- margin-bottom: 16rpx;
-}
-
-.receiver-name {
- font-size: 32rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.receiver-phone {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.5);
-}
-
-.default-tag {
- font-size: 22rpx;
- color: #00CED1;
- background: rgba(0, 206, 209, 0.2);
- padding: 6rpx 16rpx;
- border-radius: 8rpx;
- margin-left: auto;
-}
-
-/* 地址文本 */
-.address-text {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
- line-height: 1.6;
- display: block;
- margin-bottom: 24rpx;
- padding-bottom: 24rpx;
- border-bottom: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-/* 操作按钮 */
-.address-actions {
- display: flex;
- justify-content: flex-end;
- gap: 32rpx;
-}
-
-.action-btn {
- display: flex;
- align-items: center;
- gap: 8rpx;
- padding: 8rpx 0;
-}
-
-.action-btn:active {
- opacity: 0.6;
-}
-
-.edit-btn {
- color: #00CED1;
-}
-
-.delete-btn {
- color: #FF3B30;
-}
-
-.action-icon {
- font-size: 28rpx;
-}
-
-.action-text {
- font-size: 28rpx;
-}
-
-/* ===== 新增按钮 ===== */
-.add-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 16rpx;
- padding: 32rpx;
- background: #00CED1;
- border-radius: 24rpx;
- font-weight: 600;
- margin-top: 48rpx;
-}
-
-.add-btn:active {
- opacity: 0.8;
- transform: scale(0.98);
-}
-
-.add-icon {
- font-size: 36rpx;
- color: #000000;
-}
-
-.add-text {
- font-size: 32rpx;
- color: #000000;
-}
diff --git a/归档/miniprogram/pages/addresses/edit.js b/归档/miniprogram/pages/addresses/edit.js
deleted file mode 100644
index 9542c1dc..00000000
--- a/归档/miniprogram/pages/addresses/edit.js
+++ /dev/null
@@ -1,201 +0,0 @@
-/**
- * 地址编辑页(新增/编辑)
- * 参考 Next.js: app/view/my/addresses/[id]/page.tsx
- */
-
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- isEdit: false, // 是否为编辑模式
- addressId: null,
-
- // 表单数据
- name: '',
- phone: '',
- province: '',
- city: '',
- district: '',
- detail: '',
- isDefault: false,
-
- // 地区选择器
- region: [],
-
- saving: false
- },
-
- onLoad(options) {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight || 44
- })
-
- // 如果有 id 参数,则为编辑模式
- if (options.id) {
- this.setData({
- isEdit: true,
- addressId: options.id
- })
- this.loadAddress(options.id)
- }
- },
-
- // 加载地址详情(编辑模式)
- async loadAddress(id) {
- wx.showLoading({ title: '加载中...', mask: true })
-
- try {
- const res = await app.request(`/api/miniprogram/user/addresses/${id}`)
- if (res.success && res.data) {
- const addr = res.data
- this.setData({
- name: addr.name || '',
- phone: addr.phone || '',
- province: addr.province || '',
- city: addr.city || '',
- district: addr.district || '',
- detail: addr.detail || '',
- isDefault: addr.isDefault || false,
- region: [addr.province, addr.city, addr.district]
- })
- } else {
- wx.showToast({ title: '加载失败', icon: 'none' })
- }
- } catch (e) {
- console.error('加载地址详情失败:', e)
- wx.showToast({ title: '加载失败', icon: 'none' })
- } finally {
- wx.hideLoading()
- }
- },
-
- // 表单输入
- onNameInput(e) {
- this.setData({ name: e.detail.value })
- },
-
- onPhoneInput(e) {
- this.setData({ phone: e.detail.value.replace(/\D/g, '').slice(0, 11) })
- },
-
- onDetailInput(e) {
- this.setData({ detail: e.detail.value })
- },
-
- // 地区选择
- onRegionChange(e) {
- const region = e.detail.value
- this.setData({
- region,
- province: region[0],
- city: region[1],
- district: region[2]
- })
- },
-
- // 切换默认地址
- onDefaultChange(e) {
- this.setData({ isDefault: e.detail.value })
- },
-
- // 表单验证
- validateForm() {
- const { name, phone, province, city, district, detail } = this.data
-
- if (!name || name.trim().length === 0) {
- wx.showToast({ title: '请输入收货人姓名', icon: 'none' })
- return false
- }
-
- if (!phone || phone.length !== 11) {
- wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
- return false
- }
-
- if (!province || !city || !district) {
- wx.showToast({ title: '请选择省市区', icon: 'none' })
- return false
- }
-
- if (!detail || detail.trim().length === 0) {
- wx.showToast({ title: '请输入详细地址', icon: 'none' })
- return false
- }
-
- return true
- },
-
- // 保存地址
- async saveAddress() {
- if (!this.validateForm()) return
- if (this.data.saving) return
-
- this.setData({ saving: true })
- wx.showLoading({ title: '保存中...', mask: true })
-
- const { isEdit, addressId, name, phone, province, city, district, detail, isDefault } = this.data
- const userId = app.globalData.userInfo?.id
-
- if (!userId) {
- wx.hideLoading()
- wx.showToast({ title: '请先登录', icon: 'none' })
- this.setData({ saving: false })
- return
- }
-
- const addressData = {
- userId,
- name,
- phone,
- province,
- city,
- district,
- detail,
- fullAddress: `${province}${city}${district}${detail}`,
- isDefault
- }
-
- try {
- let res
- if (isEdit) {
- // 编辑模式 - PUT 请求
- res = await app.request(`/api/miniprogram/user/addresses/${addressId}`, {
- method: 'PUT',
- data: addressData
- })
- } else {
- // 新增模式 - POST 请求
- res = await app.request('/api/miniprogram/user/addresses', {
- method: 'POST',
- data: addressData
- })
- }
-
- if (res.success) {
- wx.hideLoading()
- wx.showToast({
- title: isEdit ? '保存成功' : '添加成功',
- icon: 'success'
- })
- setTimeout(() => {
- wx.navigateBack()
- }, 1500)
- } else {
- wx.hideLoading()
- wx.showToast({ title: res.message || '保存失败', icon: 'none' })
- this.setData({ saving: false })
- }
- } catch (e) {
- console.error('保存地址失败:', e)
- wx.hideLoading()
- wx.showToast({ title: '保存失败', icon: 'none' })
- this.setData({ saving: false })
- }
- },
-
- // 返回
- goBack() {
- wx.navigateBack()
- }
-})
diff --git a/归档/miniprogram/pages/addresses/edit.json b/归档/miniprogram/pages/addresses/edit.json
deleted file mode 100644
index 2e45b65e..00000000
--- a/归档/miniprogram/pages/addresses/edit.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom",
- "enablePullDownRefresh": false
-}
diff --git a/归档/miniprogram/pages/addresses/edit.wxml b/归档/miniprogram/pages/addresses/edit.wxml
deleted file mode 100644
index c5429207..00000000
--- a/归档/miniprogram/pages/addresses/edit.wxml
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
- ‹
-
- {{isEdit ? '编辑地址' : '新增地址'}}
-
-
-
-
-
-
-
-
-
- 👤
- 收货人
-
-
-
-
-
-
-
- 📱
- 手机号
-
-
-
-
-
-
-
- 📍
- 所在地区
-
-
-
- {{province || city || district ? province + ' ' + city + ' ' + district : '请选择省市区'}}
-
-
-
-
-
-
-
- 🏠
- 详细地址
-
-
-
-
-
-
-
- ⭐
- 设为默认地址
-
-
-
-
-
-
-
- {{saving ? '保存中...' : '保存'}}
-
-
-
diff --git a/归档/miniprogram/pages/addresses/edit.wxss b/归档/miniprogram/pages/addresses/edit.wxss
deleted file mode 100644
index 1045a287..00000000
--- a/归档/miniprogram/pages/addresses/edit.wxss
+++ /dev/null
@@ -1,186 +0,0 @@
-/**
- * 地址编辑页样式
- */
-
-.page {
- min-height: 100vh;
- background: #000000;
- padding-bottom: 200rpx;
-}
-
-/* ===== 导航栏 ===== */
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(0, 0, 0, 0.9);
- backdrop-filter: blur(40rpx);
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 32rpx;
- height: 88rpx;
-}
-
-.nav-back {
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.nav-back:active {
- background: rgba(255, 255, 255, 0.15);
-}
-
-.back-icon {
- font-size: 48rpx;
- color: #ffffff;
- line-height: 1;
-}
-
-.nav-title {
- flex: 1;
- text-align: center;
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.nav-placeholder {
- width: 64rpx;
-}
-
-/* ===== 内容区 ===== */
-.content {
- padding: 32rpx;
-}
-
-/* ===== 表单卡片 ===== */
-.form-card {
- background: #1c1c1e;
- border-radius: 32rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- padding: 32rpx;
- margin-bottom: 32rpx;
-}
-
-/* 表单项 */
-.form-item {
- margin-bottom: 32rpx;
-}
-
-.form-item:last-child {
- margin-bottom: 0;
-}
-
-.form-label {
- display: flex;
- align-items: center;
- gap: 12rpx;
- margin-bottom: 16rpx;
-}
-
-.label-icon {
- font-size: 28rpx;
-}
-
-.label-text {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.7);
-}
-
-/* 输入框 */
-.form-input {
- width: 100%;
- padding: 24rpx 32rpx;
- background: rgba(0, 0, 0, 0.3);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 24rpx;
- color: #ffffff;
- font-size: 28rpx;
-}
-
-.form-input:focus {
- border-color: rgba(0, 206, 209, 0.5);
-}
-
-.input-placeholder {
- color: rgba(255, 255, 255, 0.3);
-}
-
-/* 地区选择器 */
-.region-picker {
- width: 100%;
- padding: 24rpx 32rpx;
- background: rgba(0, 0, 0, 0.3);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 24rpx;
-}
-
-.picker-value {
- color: #ffffff;
- font-size: 28rpx;
-}
-
-.picker-value:empty::before {
- content: '请选择省市区';
- color: rgba(255, 255, 255, 0.3);
-}
-
-/* 多行文本框 */
-.form-textarea {
- width: 100%;
- padding: 24rpx 32rpx;
- background: rgba(0, 0, 0, 0.3);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 24rpx;
- color: #ffffff;
- font-size: 28rpx;
- min-height: 160rpx;
- line-height: 1.6;
-}
-
-.form-textarea:focus {
- border-color: rgba(0, 206, 209, 0.5);
-}
-
-/* 开关项 */
-.form-switch {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16rpx 0;
-}
-
-.form-switch .form-label {
- margin-bottom: 0;
-}
-
-/* ===== 保存按钮 ===== */
-.save-btn {
- padding: 32rpx;
- background: #00CED1;
- border-radius: 24rpx;
- text-align: center;
- font-size: 32rpx;
- font-weight: 600;
- color: #000000;
- margin-top: 48rpx;
-}
-
-.save-btn:active {
- opacity: 0.8;
- transform: scale(0.98);
-}
-
-.btn-disabled {
- opacity: 0.5;
- pointer-events: none;
-}
diff --git a/归档/miniprogram/pages/agreement/agreement.js b/归档/miniprogram/pages/agreement/agreement.js
deleted file mode 100644
index aedd4c68..00000000
--- a/归档/miniprogram/pages/agreement/agreement.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Soul创业派对 - 用户协议
- * 审核要求:登录前可点击《用户协议》查看完整内容
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight || 44
- })
- },
-
- goBack() {
- wx.navigateBack()
- }
-})
diff --git a/归档/miniprogram/pages/agreement/agreement.json b/归档/miniprogram/pages/agreement/agreement.json
deleted file mode 100644
index 9fff5b87..00000000
--- a/归档/miniprogram/pages/agreement/agreement.json
+++ /dev/null
@@ -1 +0,0 @@
-{"usingComponents":{},"navigationStyle":"custom","navigationBarTitleText":"用户协议"}
diff --git a/归档/miniprogram/pages/agreement/agreement.wxml b/归档/miniprogram/pages/agreement/agreement.wxml
deleted file mode 100644
index 4060b50c..00000000
--- a/归档/miniprogram/pages/agreement/agreement.wxml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
- ←
- 用户协议
-
-
-
-
-
-
- Soul创业实验 用户服务协议
- 更新日期:以小程序内展示为准
-
- 一、接受条款
- 欢迎使用 Soul创业实验 小程序。使用本服务即表示您已阅读、理解并同意受本协议约束。若不同意,请勿使用本服务。
-
- 二、服务说明
- 本小程序提供《一场Soul的创业实验》等数字内容阅读、推广与相关服务。我们保留变更、中断或终止部分或全部服务的权利。
-
- 三、用户行为规范
- 您应合法、合规使用本服务,不得利用本服务从事违法违规活动,不得侵犯他人权益。违规行为可能导致账号限制或追究责任。
-
- 四、知识产权
- 本小程序内全部内容(包括但不限于文字、图片、音频、视频)的知识产权归本小程序或权利人所有,未经授权不得复制、传播或用于商业用途。
-
- 五、免责与限制
- 在法律允许范围内,因网络、设备或不可抗力导致的服务中断或数据丢失,我们尽力减少损失但不承担超出法律规定的责任。
-
- 六、协议变更
- 我们可能适时修订本协议,修订后将在小程序内公示。若您继续使用服务,即视为接受修订后的协议。
-
- 七、联系我们
- 如有疑问,请通过小程序内「关于作者」或 Soul 派对房与我们联系。
-
-
-
diff --git a/归档/miniprogram/pages/agreement/agreement.wxss b/归档/miniprogram/pages/agreement/agreement.wxss
deleted file mode 100644
index 08fadc43..00000000
--- a/归档/miniprogram/pages/agreement/agreement.wxss
+++ /dev/null
@@ -1,11 +0,0 @@
-.page { min-height: 100vh; background: #000; }
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.95); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-back { width: 72rpx; height: 72rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: #fff; }
-.nav-title { font-size: 36rpx; font-weight: 600; color: #00CED1; }
-.nav-placeholder { width: 72rpx; }
-.content { height: calc(100vh - 132rpx); padding: 32rpx; box-sizing: border-box; }
-.doc-card { background: #1c1c1e; border-radius: 24rpx; padding: 40rpx; border: 2rpx solid rgba(0,206,209,0.2); }
-.doc-title { font-size: 34rpx; font-weight: 700; color: #fff; display: block; margin-bottom: 16rpx; }
-.doc-update { font-size: 24rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 32rpx; }
-.doc-section { font-size: 28rpx; font-weight: 600; color: #00CED1; display: block; margin: 24rpx 0 12rpx; }
-.doc-p { font-size: 26rpx; color: rgba(255,255,255,0.85); line-height: 1.75; display: block; margin-bottom: 16rpx; }
diff --git a/归档/miniprogram/pages/chapters/chapters.js b/归档/miniprogram/pages/chapters/chapters.js
deleted file mode 100644
index d002ed2a..00000000
--- a/归档/miniprogram/pages/chapters/chapters.js
+++ /dev/null
@@ -1,298 +0,0 @@
-/**
- * Soul创业派对 - 目录页
- * 开发: 卡若
- * 技术支持: 存客宝
- * 数据: 完整真实文章标题
- */
-
-const app = getApp()
-
-Page({
- data: {
- // 系统信息
- statusBarHeight: 44,
- navBarHeight: 88,
-
- // 用户状态
- isLoggedIn: false,
- hasFullBook: false,
- purchasedSections: [],
-
- // 书籍数据 - 完整真实标题
- totalSections: 62,
- bookData: [
- {
- id: 'part-1',
- number: '一',
- title: '真实的人',
- subtitle: '人与人之间的底层逻辑',
- chapters: [
- {
- id: 'chapter-1',
- title: '第1章|人与人之间的底层逻辑',
- sections: [
- { id: '1.1', title: '荷包:电动车出租的被动收入模式', isFree: true, price: 1 },
- { id: '1.2', title: '老墨:资源整合高手的社交方法', isFree: false, price: 1 },
- { id: '1.3', title: '笑声背后的MBTI:为什么ENTJ适合做资源,INTP适合做系统', isFree: false, price: 1 },
- { id: '1.4', title: '人性的三角结构:利益、情感、价值观', isFree: false, price: 1 },
- { id: '1.5', title: '沟通差的问题:为什么你说的别人听不懂', isFree: false, price: 1 }
- ]
- },
- {
- id: 'chapter-2',
- title: '第2章|人性困境案例',
- sections: [
- { id: '2.1', title: '相亲故事:你以为找的是人,实际是在找模式', isFree: false, price: 1 },
- { id: '2.2', title: '找工作迷茫者:为什么简历解决不了人生', isFree: false, price: 1 },
- { id: '2.3', title: '撸运费险:小钱困住大脑的真实心理', isFree: false, price: 1 },
- { id: '2.4', title: '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力', isFree: false, price: 1 },
- { id: '2.5', title: '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒', isFree: false, price: 1 }
- ]
- }
- ]
- },
- {
- id: 'part-2',
- number: '二',
- title: '真实的行业',
- subtitle: '电商、内容、传统行业解析',
- chapters: [
- {
- id: 'chapter-3',
- title: '第3章|电商篇',
- sections: [
- { id: '3.1', title: '3000万流水如何跑出来(退税模式解析)', isFree: false, price: 1 },
- { id: '3.2', title: '供应链之王 vs 打工人:利润不在前端', isFree: false, price: 1 },
- { id: '3.3', title: '社区团购的底层逻辑', isFree: false, price: 1 },
- { id: '3.4', title: '跨境电商与退税套利', isFree: false, price: 1 }
- ]
- },
- {
- id: 'chapter-4',
- title: '第4章|内容商业篇',
- sections: [
- { id: '4.1', title: '旅游号:30天10万粉的真实逻辑', isFree: false, price: 1 },
- { id: '4.2', title: '做号工厂:如何让一个号变成一个机器', isFree: false, price: 1 },
- { id: '4.3', title: '情绪内容为什么比专业内容更赚钱', isFree: false, price: 1 },
- { id: '4.4', title: '猫与宠物号:为什么宠物赛道永不过时', isFree: false, price: 1 },
- { id: '4.5', title: '直播间里的三种人:演员、技术工、系统流', isFree: false, price: 1 }
- ]
- },
- {
- id: 'chapter-5',
- title: '第5章|传统行业篇',
- sections: [
- { id: '5.1', title: '拍卖行抱朴:一天240万的摇号生意', isFree: false, price: 1 },
- { id: '5.2', title: '土地拍卖:招拍挂背后的游戏规则', isFree: false, price: 1 },
- { id: '5.3', title: '地摊经济数字化:一个月900块的餐车生意', isFree: false, price: 1 },
- { id: '5.4', title: '不良资产拍卖:我错过的一个亿佣金', isFree: false, price: 1 },
- { id: '5.5', title: '桶装水李总:跟物业合作的轻资产模式', isFree: false, price: 1 }
- ]
- }
- ]
- },
- {
- id: 'part-3',
- number: '三',
- title: '真实的错误',
- subtitle: '我和别人犯过的错',
- chapters: [
- {
- id: 'chapter-6',
- title: '第6章|我人生错过的4件大钱',
- sections: [
- { id: '6.1', title: '电商财税窗口:2016年的千万级机会', isFree: false, price: 1 },
- { id: '6.2', title: '供应链金融:我不懂的杠杆游戏', isFree: false, price: 1 },
- { id: '6.3', title: '内容红利:2019年我为什么没做抖音', isFree: false, price: 1 },
- { id: '6.4', title: '数据资产化:我还在观望的未来机会', isFree: false, price: 1 }
- ]
- },
- {
- id: 'chapter-7',
- title: '第7章|别人犯的错误',
- sections: [
- { id: '7.1', title: '投资房年轻人的迷茫:资金 vs 能力', isFree: false, price: 1 },
- { id: '7.2', title: '信息差骗局:永远有人靠卖学习赚钱', isFree: false, price: 1 },
- { id: '7.3', title: '在Soul找恋爱但想赚钱的人', isFree: false, price: 1 },
- { id: '7.4', title: '创业者的三种死法:冲动、轻信、没结构', isFree: false, price: 1 },
- { id: '7.5', title: '人情生意的终点:关系越多亏得越多', isFree: false, price: 1 }
- ]
- }
- ]
- },
- {
- id: 'part-4',
- number: '四',
- title: '真实的赚钱',
- subtitle: '底层结构与真实案例',
- chapters: [
- {
- id: 'chapter-8',
- title: '第8章|底层结构',
- sections: [
- { id: '8.1', title: '流量杠杆:抖音、Soul、飞书', isFree: false, price: 1 },
- { id: '8.2', title: '价格杠杆:供应链与信息差', isFree: false, price: 1 },
- { id: '8.3', title: '时间杠杆:自动化 + AI', isFree: false, price: 1 },
- { id: '8.4', title: '情绪杠杆:咨询、婚恋、生意场', isFree: false, price: 1 },
- { id: '8.5', title: '社交杠杆:认识谁比你会什么更重要', isFree: false, price: 1 },
- { id: '8.6', title: '云阿米巴:分不属于自己的钱', isFree: false, price: 1 }
- ]
- },
- {
- id: 'chapter-9',
- title: '第9章|我在Soul上亲访的赚钱案例',
- sections: [
- { id: '9.1', title: '游戏账号私域:账号即资产', isFree: false, price: 1 },
- { id: '9.2', title: '健康包模式:高复购、高毛利', isFree: false, price: 1 },
- { id: '9.3', title: '药物私域:长期关系赛道', isFree: false, price: 1 },
- { id: '9.4', title: '残疾机构合作:退税 × AI × 人力成本', isFree: false, price: 1 },
- { id: '9.5', title: '私域银行:粉丝即小股东', isFree: false, price: 1 },
- { id: '9.6', title: 'Soul派对房:陌生人成交的最快场景', isFree: false, price: 1 },
- { id: '9.7', title: '飞书中台:从聊天到成交的流程化体系', isFree: false, price: 1 },
- { id: '9.8', title: '餐饮女孩:6万营收、1万利润的死撑生意', isFree: false, price: 1 },
- { id: '9.9', title: '电竞生态:从陪玩到签约到酒店的完整链条', isFree: false, price: 1 },
- { id: '9.10', title: '淘客大佬:损耗30%的白色通道', isFree: false, price: 1 },
- { id: '9.11', title: '蔬菜供应链:农户才是最赚钱的人', isFree: false, price: 1 },
- { id: '9.12', title: '美业整合:一个人的公司如何月入十万', isFree: false, price: 1 },
- { id: '9.13', title: 'AI工具推广:一个隐藏的高利润赛道', isFree: false, price: 1 },
- { id: '9.14', title: '大健康私域:一个月150万的70后', isFree: false, price: 1 }
- ]
- }
- ]
- },
- {
- id: 'part-5',
- number: '五',
- title: '真实的社会',
- subtitle: '未来职业与商业生态',
- chapters: [
- {
- id: 'chapter-10',
- title: '第10章|未来职业的变化趋势',
- sections: [
- { id: '10.1', title: 'AI时代:哪些工作会消失,哪些会崛起', isFree: false, price: 1 },
- { id: '10.2', title: '一人公司:为什么越来越多人选择单干', isFree: false, price: 1 },
- { id: '10.3', title: '为什么链接能力会成为第一价值', isFree: false, price: 1 },
- { id: '10.4', title: '新型公司:Soul-飞书-线下的三位一体', isFree: false, price: 1 }
- ]
- },
- {
- id: 'chapter-11',
- title: '第11章|中国社会商业生态的未来',
- sections: [
- { id: '11.1', title: '私域经济:为什么流量越来越贵', isFree: false, price: 1 },
- { id: '11.2', title: '银发经济与孤独经济:两个被忽视的万亿市场', isFree: false, price: 1 },
- { id: '11.3', title: '流量红利的终局', isFree: false, price: 1 },
- { id: '11.4', title: '大模型 + 供应链的组合拳', isFree: false, price: 1 },
- { id: '11.5', title: '社会分层的最终逻辑', isFree: false, price: 1 }
- ]
- }
- ]
- }
- ],
-
- // 展开状态
- expandedPart: 'part-1',
-
- // 附录
- appendixList: [
- { id: 'appendix-1', title: '附录1|Soul派对房精选对话' },
- { id: 'appendix-2', title: '附录2|创业者自检清单' },
- { id: 'appendix-3', title: '附录3|本书提到的工具和资源' }
- ],
-
- // 每日新增章节
- dailyChapters: []
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight,
- navBarHeight: app.globalData.navBarHeight
- })
- this.updateUserStatus()
- this.loadDailyChapters()
- this.loadTotalFromServer()
- },
-
- async loadTotalFromServer() {
- try {
- const res = await app.request({ url: '/api/book/all-chapters', silent: true })
- if (res && res.total) {
- this.setData({ totalSections: res.total })
- }
- } catch (e) {}
- },
-
- onShow() {
- // 设置TabBar选中状态
- if (typeof this.getTabBar === 'function' && this.getTabBar()) {
- const tabBar = this.getTabBar()
- if (tabBar.updateSelected) {
- tabBar.updateSelected()
- } else {
- tabBar.setData({ selected: 1 })
- }
- }
- this.updateUserStatus()
- },
-
- // 更新用户状态
- updateUserStatus() {
- const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
- this.setData({ isLoggedIn, hasFullBook, purchasedSections })
- },
-
- // 切换展开状态
- togglePart(e) {
- const partId = e.currentTarget.dataset.id
- this.setData({
- expandedPart: this.data.expandedPart === partId ? null : partId
- })
- },
-
- // 跳转到阅读页
- goToRead(e) {
- const id = e.currentTarget.dataset.id
- wx.navigateTo({ url: `/pages/read/read?id=${id}` })
- },
-
- // 检查是否已购买
- hasPurchased(sectionId) {
- if (this.data.hasFullBook) return true
- return this.data.purchasedSections.includes(sectionId)
- },
-
- // 返回首页
- goBack() {
- wx.switchTab({ url: '/pages/index/index' })
- },
-
- async loadDailyChapters() {
- try {
- const res = await app.request({ url: '/api/book/all-chapters', silent: true })
- const chapters = (res && res.data) || (res && res.chapters) || []
- const daily = chapters
- .filter(c => (c.sectionOrder || c.sort_order || 0) > 62)
- .sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
- .slice(0, 20)
- .map(c => {
- const d = new Date(c.updatedAt || c.updated_at || Date.now())
- return {
- id: c.id,
- title: c.section_title || c.title || c.sectionTitle,
- price: c.price || 1,
- dateStr: `${d.getMonth()+1}/${d.getDate()}`
- }
- })
- if (daily.length > 0) {
- this.setData({ dailyChapters: daily, totalSections: 62 + daily.length })
- }
- } catch (e) { console.log('[Chapters] 加载最新新增失败:', e) }
- },
-
- // 跳转到搜索页
- goToSearch() {
- wx.navigateTo({ url: '/pages/search/search' })
- }
-})
diff --git a/归档/miniprogram/pages/chapters/chapters.json b/归档/miniprogram/pages/chapters/chapters.json
deleted file mode 100644
index e7696321..00000000
--- a/归档/miniprogram/pages/chapters/chapters.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "usingComponents": {},
- "enablePullDownRefresh": false,
- "backgroundTextStyle": "light",
- "backgroundColor": "#000000"
-}
diff --git a/归档/miniprogram/pages/chapters/chapters.wxml b/归档/miniprogram/pages/chapters/chapters.wxml
deleted file mode 100644
index f769c6ee..00000000
--- a/归档/miniprogram/pages/chapters/chapters.wxml
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
-
-
-
-
- 🔍
-
-
- 目录
-
-
-
-
-
-
-
-
-
-
- 📚
-
-
- 一场SOUL的创业实验场
- 来自Soul派对房的真实商业故事
-
-
- {{totalSections}}
- 章节
-
-
-
-
-
-
-
-
- 📖
- 序言|为什么我每天早上6点在Soul开播?
-
-
- 免费
- →
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{section.isFree || hasFullBook || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}}
- {{section.id}} {{section.title}}
-
-
- 免费
- 已购
- ¥{{section.price}}
- ›
-
-
-
-
-
-
-
-
-
-
-
-
-
- 📖
- 尾声|这本书的真实目的
-
-
- 免费
- →
-
-
-
-
-
- 附录
-
-
- {{item.title}}
- →
-
-
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/chapters/chapters.wxss b/归档/miniprogram/pages/chapters/chapters.wxss
deleted file mode 100644
index 7ff3d64c..00000000
--- a/归档/miniprogram/pages/chapters/chapters.wxss
+++ /dev/null
@@ -1,497 +0,0 @@
-/**
- * Soul创业实验 - 目录页样式
- * 1:1还原Web版本UI
- */
-
-.page {
- min-height: 100vh;
- background: #000000;
- padding-bottom: 200rpx;
-}
-
-/* ===== 自定义导航栏 ===== */
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(0, 0, 0, 0.9);
- backdrop-filter: blur(40rpx);
- -webkit-backdrop-filter: blur(40rpx);
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.nav-content {
- height: 88rpx;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 32rpx;
-}
-
-.nav-left,
-.nav-right {
- width: 64rpx;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.nav-title {
- font-size: 36rpx;
- font-weight: 600;
- flex: 1;
- text-align: center;
-}
-
-/* 搜索按钮 */
-.search-btn {
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background: #2c2c2e;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.3s ease;
-}
-
-.search-btn:active {
- background: #3c3c3e;
- transform: scale(0.95);
-}
-
-.search-icon {
- font-size: 32rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.brand-color {
- color: #00CED1;
-}
-
-.nav-placeholder {
- width: 100%;
-}
-
-/* ===== 书籍信息卡 ===== */
-.book-info-card {
- display: flex;
- align-items: center;
- gap: 24rpx;
- margin: 32rpx 32rpx 24rpx 32rpx;
- padding: 32rpx;
-}
-
-.card-gradient {
- background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%);
- border-radius: 32rpx;
- border: 2rpx solid rgba(0, 206, 209, 0.2);
- box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.2);
-}
-
-.book-icon {
- width: 96rpx;
- height: 96rpx;
- border-radius: 24rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.book-icon-inner {
- font-size: 48rpx;
-}
-
-.book-info {
- flex: 1;
- min-width: 0;
-}
-
-.book-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #ffffff;
- display: block;
- margin-bottom: 4rpx;
-}
-
-.book-subtitle {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-.book-count {
- text-align: right;
-}
-
-.count-value {
- font-size: 40rpx;
- font-weight: 700;
- display: block;
-}
-
-.count-label {
- font-size: 20rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 目录内容 ===== */
-.chapters-content {
- padding: 0 32rpx;
- width: 100%;
- box-sizing: border-box;
-}
-
-/* ===== 章节项 ===== */
-.chapter-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 24rpx;
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- margin-bottom: 24rpx;
-}
-
-.chapter-item:active {
- background: #2c2c2e;
-}
-
-.item-left {
- display: flex;
- align-items: center;
- gap: 24rpx;
- flex: 1;
- min-width: 0;
-}
-
-.item-icon {
- width: 64rpx;
- height: 64rpx;
- border-radius: 16rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 32rpx;
- flex-shrink: 0;
-}
-
-.icon-brand {
- background: rgba(0, 206, 209, 0.2);
-}
-
-.item-title {
- font-size: 28rpx;
- font-weight: 500;
- color: #ffffff;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.item-right {
- display: flex;
- align-items: center;
- gap: 16rpx;
- flex-shrink: 0;
-}
-
-.item-arrow {
- font-size: 32rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 标签 ===== */
-.tag {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-size: 22rpx;
- padding: 6rpx 16rpx;
- min-width: 80rpx;
- border-radius: 8rpx;
- box-sizing: border-box;
- text-align: center;
-}
-
-.tag-free {
- background: rgba(0, 206, 209, 0.1);
- color: #00CED1;
-}
-
-.text-brand {
- color: #00CED1;
-}
-
-.text-muted {
- color: rgba(255, 255, 255, 0.4);
-}
-
-.text-xs {
- font-size: 22rpx;
-}
-
-/* ===== 篇章列表 ===== */
-.part-list {
- margin-bottom: 24rpx;
-}
-
-.part-item {
- margin-bottom: 24rpx;
-}
-
-.part-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 24rpx;
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.part-header:active {
- background: #2c2c2e;
-}
-
-.part-left {
- display: flex;
- align-items: center;
- gap: 24rpx;
-}
-
-.part-icon {
- width: 64rpx;
- height: 64rpx;
- border-radius: 16rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- font-weight: 700;
- color: #ffffff;
- flex-shrink: 0;
-}
-
-.part-info {
- display: flex;
- flex-direction: column;
-}
-
-.part-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.part-subtitle {
- font-size: 20rpx;
- color: rgba(255, 255, 255, 0.4);
- margin-top: 4rpx;
-}
-
-.part-right {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-
-.part-count {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-.part-arrow {
- font-size: 32rpx;
- color: rgba(255, 255, 255, 0.4);
- transition: transform 0.3s ease;
-}
-
-.arrow-down {
- transform: rotate(90deg);
-}
-
-/* ===== 章节组 ===== */
-.chapters-list {
- margin-top: 16rpx;
- margin-left: 16rpx;
-}
-
-.chapter-group {
- background: rgba(28, 28, 30, 0.5);
- border-radius: 16rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- overflow: hidden;
- margin-bottom: 8rpx;
-}
-
-.chapter-header {
- padding: 16rpx 24rpx;
- font-size: 24rpx;
- font-weight: 500;
- color: rgba(255, 255, 255, 0.6);
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.section-list {
- /* 小节列表 */
-}
-
-.section-item {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- padding: 20rpx 24rpx;
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.section-item:last-child {
- border-bottom: none;
-}
-
-.section-item:active {
- background: rgba(255, 255, 255, 0.05);
-}
-
-.section-left {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 16rpx;
- flex: 1;
- min-width: 0;
-}
-
-/* 小节锁图标 */
-.section-lock {
- width: 32rpx;
- min-width: 32rpx;
- font-size: 24rpx;
- text-align: center;
- flex-shrink: 0;
-}
-
-.lock-open {
- color: #00CED1;
-}
-
-.lock-closed {
- color: rgba(255, 255, 255, 0.3);
-}
-
-/* 小节标题 */
-.section-title {
- font-size: 26rpx;
- color: #ffffff;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex: 1;
-}
-
-/* 小节价格 */
-.section-price {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.5);
-}
-
-/* 已购标签 */
-.tag-purchased {
- background: rgba(0, 206, 209, 0.15);
- color: #00CED1;
-}
-
-.section-right {
- display: flex;
- align-items: center;
- gap: 16rpx;
- flex-shrink: 0;
- margin-left: 16rpx;
-}
-
-.section-arrow {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.3);
-}
-
-/* ===== 附录 ===== */
-.card {
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- margin: 0 0 24rpx 0;
- width: 100%;
- box-sizing: border-box;
-}
-
-.appendix-card {
- padding: 24rpx;
- width: 100%;
- box-sizing: border-box;
- margin: 0 0 24rpx 0;
-}
-
-.appendix-title {
- font-size: 24rpx;
- font-weight: 500;
- color: rgba(255, 255, 255, 0.6);
- display: block;
- margin-bottom: 16rpx;
-}
-
-.appendix-list {
- /* 附录列表 */
-}
-
-.appendix-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16rpx 0;
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.appendix-item:last-child {
- border-bottom: none;
-}
-
-.appendix-item:active {
- opacity: 0.7;
-}
-
-.appendix-text {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.8);
-}
-
-.appendix-arrow {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.3);
-}
-
-/* ===== 每日新增章节 ===== */
-.daily-section { margin: 20rpx 0; padding: 24rpx; background: rgba(255,215,0,0.04); border: 1rpx solid rgba(255,215,0,0.12); border-radius: 16rpx; }
-.daily-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 16rpx; }
-.daily-title { font-size: 30rpx; font-weight: 600; color: #FFD700; }
-.daily-badge { font-size: 22rpx; background: #FFD700; color: #000; padding: 2rpx 12rpx; border-radius: 20rpx; font-weight: bold; }
-.daily-list { display: flex; flex-direction: column; gap: 12rpx; }
-.daily-item { display: flex; justify-content: space-between; align-items: center; padding: 16rpx; background: rgba(255,255,255,0.03); border-radius: 12rpx; }
-.daily-left { display: flex; align-items: center; gap: 10rpx; flex: 1; min-width: 0; }
-.daily-new-tag { font-size: 18rpx; background: #FF4444; color: #fff; padding: 2rpx 8rpx; border-radius: 6rpx; font-weight: bold; flex-shrink: 0; }
-.daily-item-title { font-size: 26rpx; color: rgba(255,255,255,0.85); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.daily-right { display: flex; align-items: center; gap: 12rpx; flex-shrink: 0; }
-.daily-price { font-size: 26rpx; color: #FFD700; font-weight: 600; }
-.daily-date { font-size: 20rpx; color: rgba(255,255,255,0.35); }
-.daily-note { display: block; font-size: 22rpx; color: rgba(255,215,0,0.5); margin-top: 12rpx; text-align: center; }
-
-/* ===== 底部留白 ===== */
-.bottom-space {
- height: 40rpx;
-}
diff --git a/归档/miniprogram/pages/index/index.js b/归档/miniprogram/pages/index/index.js
deleted file mode 100644
index 1cf16fc9..00000000
--- a/归档/miniprogram/pages/index/index.js
+++ /dev/null
@@ -1,292 +0,0 @@
-/**
- * Soul创业派对 - 首页
- * 开发: 卡若
- * 技术支持: 存客宝
- */
-
-console.log('[Index] ===== 首页文件开始加载 =====')
-
-const app = getApp()
-
-Page({
- data: {
- // 系统信息
- statusBarHeight: 44,
- navBarHeight: 88,
-
- // 用户信息
- isLoggedIn: false,
- hasFullBook: false,
- readCount: 0,
-
- // 书籍数据
- totalSections: 62,
- bookData: [],
-
- // 推荐章节
- featuredSections: [
- { 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: '真实的赚钱' }
- ],
-
- // 最新章节(动态计算)
- latestSection: null,
- latestLabel: '最新更新',
-
- // 内容概览
- partsList: [
- { id: 'part-1', number: '一', title: '真实的人', subtitle: '人与人之间的底层逻辑' },
- { id: 'part-2', number: '二', title: '真实的行业', subtitle: '电商、内容、传统行业解析' },
- { id: 'part-3', number: '三', title: '真实的错误', subtitle: '我和别人犯过的错' },
- { id: 'part-4', number: '四', title: '真实的赚钱', subtitle: '底层结构与真实案例' },
- { id: 'part-5', number: '五', title: '真实的社会', subtitle: '未来职业与商业生态' }
- ],
-
- // 超级个体(VIP会员)
- superMembers: [],
-
- // 最新新增章节
- latestChapters: [],
-
- // 加载状态
- loading: true
- },
-
- onLoad(options) {
- console.log('[Index] ===== onLoad 触发 =====')
-
- // 获取系统信息
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight,
- navBarHeight: app.globalData.navBarHeight
- })
-
- // 处理分享参数(推荐码绑定)
- if (options && options.ref) {
- console.log('[Index] 检测到推荐码:', options.ref)
- app.handleReferralCode({ query: options })
- }
-
- // 初始化数据
- this.initData()
- },
-
- onShow() {
- console.log('[Index] onShow 触发')
-
- // 设置TabBar选中状态
- if (typeof this.getTabBar === 'function' && this.getTabBar()) {
- const tabBar = this.getTabBar()
- console.log('[Index] TabBar 组件:', tabBar ? '已找到' : '未找到')
-
- // 主动触发配置加载
- if (tabBar && tabBar.loadFeatureConfig) {
- console.log('[Index] 主动调用 TabBar.loadFeatureConfig()')
- tabBar.loadFeatureConfig()
- }
-
- // 更新选中状态
- if (tabBar && tabBar.updateSelected) {
- tabBar.updateSelected()
- } else if (tabBar) {
- tabBar.setData({ selected: 0 })
- }
- } else {
- console.log('[Index] TabBar 组件未找到或 getTabBar 方法不存在')
- }
-
- // 更新用户状态
- this.updateUserStatus()
- },
-
- // 初始化数据
- async initData() {
- this.setData({ loading: true })
- try {
- await this.loadBookData()
- await this.loadFeaturedFromServer()
- this.loadSuperMembers()
- this.loadLatestChapters()
- } catch (e) {
- console.error('初始化失败:', e)
- } finally {
- this.setData({ loading: false })
- }
- },
-
- async loadSuperMembers() {
- try {
- // 优先加载VIP会员
- let members = []
- try {
- const res = await app.request({ url: '/api/vip/members', silent: true })
- if (res && res.success && res.data) {
- members = res.data.filter(u => u.avatar || u.vip_avatar).slice(0, 4).map(u => ({
- id: u.id, name: u.vip_name || u.nickname || '会员',
- avatar: u.vip_avatar || u.avatar, isVip: true
- }))
- }
- } catch (e) {}
- // 不足4个则用有头像的普通用户补充
- if (members.length < 4) {
- try {
- const dbRes = await app.request({ url: '/api/miniprogram/users?limit=20', silent: true })
- if (dbRes && dbRes.success && dbRes.data) {
- const existIds = new Set(members.map(m => m.id))
- const extra = dbRes.data
- .filter(u => u.avatar && u.nickname && !existIds.has(u.id))
- .slice(0, 4 - members.length)
- .map(u => ({ id: u.id, name: u.nickname, avatar: u.avatar, isVip: u.is_vip === 1 }))
- members = members.concat(extra)
- }
- } catch (e) {}
- }
- this.setData({ superMembers: members })
- } catch (e) { console.log('[Index] 加载超级个体失败:', e) }
- },
-
- // 从服务端获取精选推荐(加权算法:阅读量50% + 时效30% + 付款率20%)和最新更新
- async loadFeaturedFromServer() {
- try {
- const res = await app.request({ url: '/api/book/all-chapters', silent: true })
- const chapters = (res && res.data) ? res.data : (res && res.chapters) ? res.chapters : []
- let featured = (res && res.featuredSections) ? res.featuredSections : []
- // 服务端未返回精选时,从前端按更新时间取前3条有效章节作为回退
- if (featured.length === 0 && chapters.length > 0) {
- const valid = chapters.filter(c => {
- const id = (c.id || '').toLowerCase()
- const pt = (c.part_title || c.partTitle || '').toLowerCase()
- return !id.includes('preface') && !id.includes('epilogue') && !id.includes('appendix')
- && !pt.includes('序言') && !pt.includes('尾声') && !pt.includes('附录')
- })
- featured = valid
- .sort((a, b) => new Date(b.updated_at || b.updatedAt || 0) - new Date(a.updated_at || a.updatedAt || 0))
- .slice(0, 5)
- }
- if (featured.length > 0) {
- this.setData({
- featuredSections: featured.slice(0, 3).map(s => ({
- id: s.id || s.section_id,
- title: s.section_title || s.title,
- part: (s.cleanPartTitle || s.part_title || '').replace(/[_||]/g, ' ').trim()
- }))
- })
- }
-
- // 最新更新 = 按 updated_at 排序第1篇(排除序言/尾声/附录)
- const validChapters = chapters.filter(c => {
- const id = (c.id || '').toLowerCase()
- const pt = (c.part_title || c.partTitle || '').toLowerCase()
- return !id.includes('preface') && !id.includes('epilogue') && !id.includes('appendix')
- && !pt.includes('序言') && !pt.includes('尾声') && !pt.includes('附录')
- })
- if (validChapters.length > 0) {
- validChapters.sort((a, b) => new Date(b.updated_at || b.updatedAt || 0) - new Date(a.updated_at || a.updatedAt || 0))
- const latest = validChapters[0]
- this.setData({
- latestSection: {
- id: latest.id || latest.section_id,
- title: latest.section_title || latest.title,
- part: latest.cleanPartTitle || latest.part_title || ''
- }
- })
- }
- } catch (e) {
- console.log('[Index] 从服务端加载推荐失败,使用默认:', e)
- }
- },
-
- async loadBookData() {
- try {
- const res = await app.request({ url: '/api/book/all-chapters', silent: true })
- if (res && (res.data || res.chapters)) {
- const chapters = res.data || res.chapters || []
- this.setData({
- bookData: chapters,
- totalSections: res.total || chapters.length || 62
- })
- }
- } catch (e) {
- console.error('加载书籍数据失败:', e)
- }
- },
-
- // 更新用户状态(已读数 = 用户实际打开过的章节数,仅统计有权限阅读的)
- updateUserStatus() {
- const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
- const readCount = Math.min(app.getReadCount(), this.data.totalSections || 62)
- this.setData({
- isLoggedIn,
- hasFullBook,
- readCount
- })
- },
-
- // 跳转到目录
- goToChapters() {
- wx.switchTab({ url: '/pages/chapters/chapters' })
- },
-
- // 跳转到搜索页
- goToSearch() {
- wx.navigateTo({ url: '/pages/search/search' })
- },
-
- // 跳转到阅读页
- goToRead(e) {
- const id = e.currentTarget.dataset.id
- wx.navigateTo({ url: `/pages/read/read?id=${id}` })
- },
-
- // 跳转到匹配页
- goToMatch() {
- wx.switchTab({ url: '/pages/match/match' })
- },
-
- goToVip() {
- wx.navigateTo({ url: '/pages/vip/vip' })
- },
-
- goToSuperList() {
- wx.switchTab({ url: '/pages/match/match' })
- },
-
- async loadLatestChapters() {
- try {
- const res = await app.request({ url: '/api/book/all-chapters', silent: true })
- const chapters = (res && res.data) || (res && res.chapters) || []
- const latest = chapters
- .filter(c => (c.sectionOrder || c.sort_order || 0) > 62)
- .sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
- .slice(0, 10)
- .map(c => {
- const d = new Date(c.updatedAt || c.updated_at || Date.now())
- return {
- id: c.id,
- title: c.section_title || c.title || c.sectionTitle,
- price: c.price || 1,
- dateStr: `${d.getMonth() + 1}/${d.getDate()}`
- }
- })
- this.setData({ latestChapters: latest })
- } catch (e) { console.log('[Index] 加载最新新增失败:', e) }
- },
-
- goToMemberDetail(e) {
- const id = e.currentTarget.dataset.id
- wx.navigateTo({ url: `/pages/member-detail/member-detail?id=${id}` })
- },
-
- // 跳转到我的页面
- goToMy() {
- wx.switchTab({ url: '/pages/my/my' })
- },
-
- // 下拉刷新
- async onPullDownRefresh() {
- await this.initData()
- this.updateUserStatus()
- wx.stopPullDownRefresh()
- }
-})
diff --git a/归档/miniprogram/pages/index/index.json b/归档/miniprogram/pages/index/index.json
deleted file mode 100644
index 1246275b..00000000
--- a/归档/miniprogram/pages/index/index.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "usingComponents": {},
- "enablePullDownRefresh": true,
- "backgroundTextStyle": "light",
- "backgroundColor": "#000000"
-}
diff --git a/归档/miniprogram/pages/index/index.wxml b/归档/miniprogram/pages/index/index.wxml
deleted file mode 100644
index 470c18b5..00000000
--- a/归档/miniprogram/pages/index/index.wxml
+++ /dev/null
@@ -1,166 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 最新更新
- {{latestSection.title}}
- {{latestSection.part}}
-
- 开始阅读
- →
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{readCount}}
- 已读
-
-
- {{totalSections - readCount}}
- 待读
-
-
- 5
- 篇章
-
-
- 11
- 章节
-
-
-
-
-
-
-
-
-
-
-
- {{item.name[0] || '会'}}
-
- {{item.name}}
-
-
-
- 成为会员,展示你的项目
- 加入创业派对 →
-
-
-
-
-
-
-
-
-
-
- {{item.id}}
-
- {{item.title}}
- {{item.part}}
-
- →
-
-
-
-
-
-
-
-
-
-
- NEW
- {{item.title}}
-
-
- ¥{{item.price}}
- {{item.dateStr}}
-
-
-
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/index/index.wxss b/归档/miniprogram/pages/index/index.wxss
deleted file mode 100644
index b3d68715..00000000
--- a/归档/miniprogram/pages/index/index.wxss
+++ /dev/null
@@ -1,635 +0,0 @@
-/**
- * Soul创业实验 - 首页样式
- * 1:1还原Web版本UI
- */
-
-.page {
- min-height: 100vh;
- background: #000000;
- padding-bottom: 200rpx;
-}
-
-/* ===== 导航栏占位 ===== */
-.nav-placeholder {
- width: 100%;
-}
-
-/* ===== 顶部区域 ===== */
-.header {
- padding: 0 32rpx 32rpx;
-}
-
-.header-content {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 32rpx;
- padding-top: 24rpx;
-}
-
-.logo-section {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-
-.logo-icon {
- width: 80rpx;
- height: 80rpx;
- border-radius: 20rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 8rpx 24rpx rgba(0, 206, 209, 0.3);
-}
-
-.logo-text {
- color: #ffffff;
- font-size: 36rpx;
- font-weight: 700;
-}
-
-.logo-info {
- display: flex;
- flex-direction: column;
-}
-
-.logo-title {
- font-size: 36rpx;
- font-weight: 700;
-}
-
-.text-white {
- color: #ffffff;
-}
-
-.brand-color {
- color: #00CED1;
-}
-
-.logo-subtitle {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
- margin-top: 4rpx;
-}
-
-.header-right {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-
-.chapter-badge {
- font-size: 22rpx;
- color: #00CED1;
- background: rgba(0, 206, 209, 0.1);
- padding: 8rpx 16rpx;
- border-radius: 32rpx;
-}
-
-/* ===== 搜索栏 ===== */
-.search-bar {
- display: flex;
- align-items: center;
- gap: 24rpx;
- padding: 24rpx 32rpx;
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.search-icon {
- position: relative;
- width: 32rpx;
- height: 32rpx;
-}
-
-.search-circle {
- width: 20rpx;
- height: 20rpx;
- border: 4rpx solid rgba(255, 255, 255, 0.4);
- border-radius: 50%;
-}
-
-.search-handle {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 12rpx;
- height: 4rpx;
- background: rgba(255, 255, 255, 0.4);
- transform: rotate(45deg);
- border-radius: 2rpx;
-}
-
-.search-placeholder {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 主内容区 ===== */
-.main-content {
- padding: 0 32rpx;
- width: 100%;
- box-sizing: border-box;
-}
-
-/* ===== Banner卡片 ===== */
-.banner-card {
- position: relative;
- padding: 40rpx;
- border-radius: 32rpx;
- overflow: hidden;
- background: linear-gradient(135deg, #0d3331 0%, #1a1a2e 50%, #16213e 100%);
- margin-bottom: 24rpx;
-}
-
-.banner-glow {
- position: absolute;
- top: 0;
- right: 0;
- width: 256rpx;
- height: 256rpx;
- background: #00CED1;
- border-radius: 50%;
- filter: blur(120rpx);
- opacity: 0.2;
-}
-
-.banner-tag {
- display: inline-block;
- padding: 8rpx 16rpx;
- background: #00CED1;
- color: #000000;
- font-size: 22rpx;
- font-weight: 500;
- border-radius: 8rpx;
- margin-bottom: 24rpx;
-}
-
-.banner-title {
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
- margin-bottom: 16rpx;
- padding-right: 64rpx;
-}
-
-.banner-part {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
- margin-bottom: 24rpx;
-}
-
-.banner-action {
- display: flex;
- align-items: center;
- gap: 8rpx;
-}
-
-.banner-action-text {
- font-size: 28rpx;
- color: #00CED1;
- font-weight: 500;
-}
-
-.banner-arrow {
- color: #00CED1;
- font-size: 28rpx;
-}
-
-/* ===== 通用卡片 ===== */
-.card {
- background: #1c1c1e;
- border-radius: 32rpx;
- padding: 32rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- margin: 0 0 24rpx 0;
- width: 100%;
- box-sizing: border-box;
-}
-
-/* ===== 阅读进度卡 ===== */
-.progress-card {
- width: 100%;
- background: #1c1c1e;
- border-radius: 24rpx;
- padding: 28rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- margin: 0 0 24rpx 0;
- box-sizing: border-box;
-}
-
-.progress-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 24rpx;
-}
-
-.progress-title {
- font-size: 28rpx;
- color: #ffffff;
- font-weight: 500;
-}
-
-.progress-count {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-.progress-bar-wrapper {
- margin-bottom: 24rpx;
-}
-
-.progress-bar-bg {
- width: 100%;
- height: 16rpx;
- background: #2c2c2e;
- border-radius: 8rpx;
- overflow: hidden;
-}
-
-.progress-bar-fill {
- height: 100%;
- background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%);
- border-radius: 8rpx;
- transition: width 0.3s ease;
-}
-
-.progress-stats {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 24rpx;
-}
-
-.stat-item {
- text-align: center;
-}
-
-.stat-value {
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
- display: block;
-}
-
-.stat-label {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 区块标题 ===== */
-.section {
- margin-bottom: 24rpx;
-}
-
-.section-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 24rpx;
-}
-
-.section-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.section-more {
- display: flex;
- align-items: center;
- gap: 8rpx;
-}
-
-.more-text {
- font-size: 24rpx;
- color: #00CED1;
-}
-
-.more-arrow {
- font-size: 24rpx;
- color: #00CED1;
-}
-
-/* ===== 精选推荐列表 ===== */
-.featured-list {
- display: flex;
- flex-direction: column;
- gap: 24rpx;
-}
-
-.featured-item {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- padding: 32rpx;
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.featured-item:active {
- transform: scale(0.98);
- background: #2c2c2e;
-}
-
-.featured-content {
- flex: 1;
-}
-
-.featured-meta {
- display: flex;
- align-items: center;
- gap: 16rpx;
- margin-bottom: 16rpx;
-}
-
-.featured-id {
- font-size: 24rpx;
- font-weight: 500;
-}
-
-.tag {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-size: 22rpx;
- padding: 6rpx 16rpx;
- min-width: 80rpx;
- border-radius: 8rpx;
- box-sizing: border-box;
- text-align: center;
-}
-
-.tag-free {
- background: rgba(0, 206, 209, 0.1);
- color: #00CED1;
-}
-
-.tag-pink {
- background: rgba(233, 30, 99, 0.1);
- color: #E91E63;
-}
-
-.tag-purple {
- background: rgba(123, 97, 255, 0.1);
- color: #7B61FF;
-}
-
-.featured-title {
- font-size: 28rpx;
- color: #ffffff;
- font-weight: 500;
- display: block;
- margin-bottom: 8rpx;
-}
-
-.featured-part {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-.featured-arrow {
- font-size: 32rpx;
- color: rgba(255, 255, 255, 0.3);
- margin-top: 8rpx;
-}
-
-/* ===== 内容概览列表 ===== */
-.parts-list {
- display: flex;
- flex-direction: column;
- gap: 24rpx;
-}
-
-.part-item {
- display: flex;
- align-items: center;
- gap: 24rpx;
- padding: 32rpx;
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.part-item:active {
- transform: scale(0.98);
- background: #2c2c2e;
-}
-
-.part-icon {
- width: 80rpx;
- height: 80rpx;
- border-radius: 16rpx;
- background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, rgba(32, 178, 170, 0.1) 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.part-number {
- font-size: 28rpx;
- font-weight: 700;
- color: #00CED1;
-}
-
-.part-info {
- flex: 1;
- min-width: 0;
-}
-
-.part-title {
- font-size: 28rpx;
- color: #ffffff;
- font-weight: 500;
- display: block;
- margin-bottom: 4rpx;
-}
-
-.part-subtitle {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.part-arrow {
- font-size: 32rpx;
- color: rgba(255, 255, 255, 0.3);
- flex-shrink: 0;
-}
-
-/* ===== 序言入口 ===== */
-.preface-card {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 32rpx;
- border-radius: 24rpx;
- background: linear-gradient(90deg, rgba(0, 206, 209, 0.1) 0%, transparent 100%);
- border: 2rpx solid rgba(0, 206, 209, 0.2);
- margin-bottom: 24rpx;
-}
-
-.preface-card:active {
- opacity: 0.8;
-}
-
-.preface-content {
- flex: 1;
-}
-
-.preface-title {
- font-size: 28rpx;
- color: #ffffff;
- font-weight: 500;
- display: block;
- margin-bottom: 8rpx;
-}
-
-.preface-desc {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 超级个体 ===== */
-.super-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 24rpx 16rpx;
-}
-.super-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 10rpx;
-}
-.super-avatar {
- width: 108rpx;
- height: 108rpx;
- border-radius: 50%;
- overflow: hidden;
- background: rgba(0,206,209,0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- border: 3rpx solid rgba(255,255,255,0.1);
-}
-.super-avatar-vip {
- border: 3rpx solid #FFD700;
- box-shadow: 0 0 12rpx rgba(255,215,0,0.3);
-}
-.super-avatar-img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-.super-avatar-text {
- font-size: 40rpx;
- font-weight: 600;
- color: #00CED1;
-}
-.super-name {
- font-size: 22rpx;
- color: rgba(255,255,255,0.7);
- text-align: center;
- width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-.super-empty {
- padding: 32rpx;
- text-align: center;
- background: rgba(255,255,255,0.03);
- border-radius: 16rpx;
-}
-.super-empty-text {
- font-size: 24rpx;
- color: rgba(255,255,255,0.4);
- display: block;
- margin-bottom: 16rpx;
-}
-.super-empty-btn {
- font-size: 26rpx;
- color: #00CED1;
-}
-
-/* ===== 最新新增 ===== */
-.daily-badge-wrap {
- display: inline-flex;
- align-items: center;
-}
-.daily-badge {
- background: #FF4500;
- color: #fff;
- font-size: 20rpx;
- font-weight: 600;
- padding: 4rpx 12rpx;
- border-radius: 16rpx;
- margin-left: 8rpx;
-}
-.latest-list {
- display: flex;
- flex-direction: column;
- gap: 12rpx;
-}
-.latest-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20rpx 24rpx;
- background: rgba(255,255,255,0.04);
- border-radius: 12rpx;
- border-left: 4rpx solid #FF4500;
-}
-.latest-left {
- flex: 1;
- display: flex;
- align-items: center;
- gap: 12rpx;
- min-width: 0;
-}
-.latest-new-tag {
- font-size: 18rpx;
- font-weight: 700;
- color: #FF4500;
- background: rgba(255,69,0,0.15);
- padding: 2rpx 10rpx;
- border-radius: 6rpx;
- flex-shrink: 0;
-}
-.latest-title {
- font-size: 26rpx;
- color: rgba(255,255,255,0.9);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-.latest-right {
- display: flex;
- align-items: center;
- gap: 12rpx;
- flex-shrink: 0;
- margin-left: 12rpx;
-}
-.latest-price {
- font-size: 26rpx;
- font-weight: 600;
- color: #FFD700;
-}
-.latest-date {
- font-size: 22rpx;
- color: rgba(255,255,255,0.4);
-}
-
-/* ===== 底部留白 ===== */
-.bottom-space {
- height: 40rpx;
-}
diff --git a/归档/miniprogram/pages/match/match.js b/归档/miniprogram/pages/match/match.js
deleted file mode 100644
index 6d3b9d87..00000000
--- a/归档/miniprogram/pages/match/match.js
+++ /dev/null
@@ -1,663 +0,0 @@
-/**
- * Soul创业派对 - 找伙伴页
- * 按H5网页端完全重构
- * 开发: 卡若
- */
-
-const app = getApp()
-
-// 默认匹配类型配置
-// 找伙伴:真正的匹配功能,匹配数据库中的真实用户
-// 资源对接:需要登录+购买章节才能使用,填写2项信息(我能帮到你什么、我需要什么帮助)
-// 导师顾问:跳转到存客宝添加微信
-// 团队招募:跳转到存客宝添加微信
-let MATCH_TYPES = [
- { id: 'partner', label: '找伙伴', matchLabel: '找伙伴', icon: '⭐', matchFromDB: true, showJoinAfterMatch: false },
- { id: 'investor', label: '资源对接', matchLabel: '资源对接', icon: '👥', matchFromDB: true, showJoinAfterMatch: true, requirePurchase: true },
- { id: 'mentor', label: '导师顾问', matchLabel: '立即咨询', icon: '❤️', matchFromDB: true, showJoinAfterMatch: true },
- { id: 'team', label: '团队招募', matchLabel: '团队招募', icon: '🎮', matchFromDB: true, showJoinAfterMatch: true }
-]
-
-let FREE_MATCH_LIMIT = 3 // 每日免费匹配次数
-
-Page({
- data: {
- statusBarHeight: 44,
-
- // 匹配类型
- matchTypes: MATCH_TYPES,
- selectedType: 'partner',
- currentTypeLabel: '找伙伴',
-
- // 用户状态
- isLoggedIn: false,
- hasPurchased: false,
- hasFullBook: false,
-
- // 匹配次数
- todayMatchCount: 0,
- totalMatchesAllowed: FREE_MATCH_LIMIT,
- matchesRemaining: FREE_MATCH_LIMIT,
- needPayToMatch: false,
-
- // 匹配状态
- isMatching: false,
- matchAttempts: 0,
- currentMatch: null,
-
- // 加入弹窗
- showJoinModal: false,
- joinType: null,
- joinTypeLabel: '',
- contactType: 'phone',
- phoneNumber: '',
- wechatId: '',
- userPhone: '',
- isJoining: false,
- joinSuccess: false,
- joinError: '',
- needBindFirst: false,
-
- // 资源对接表单
- canHelp: '',
- needHelp: '',
- goodAt: '',
-
- // 解锁弹窗
- showUnlockModal: false,
-
- // 匹配价格(可配置)
- matchPrice: 1,
- extraMatches: 0
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight || 44
- })
- this.loadMatchConfig()
- this.loadStoredContact()
- this.loadTodayMatchCount()
- this.initUserStatus()
- },
-
- onShow() {
- if (typeof this.getTabBar === 'function' && this.getTabBar()) {
- const tabBar = this.getTabBar()
- if (tabBar.updateSelected) {
- tabBar.updateSelected()
- } else {
- tabBar.setData({ selected: 2 })
- }
- }
- this.initUserStatus()
- },
-
- // 加载匹配配置
- async loadMatchConfig() {
- try {
- const res = await app.request({ url: '/api/match/config', silent: true, method: 'GET',
- method: 'GET'
- })
-
- if (res.success && res.data) {
- // 更新全局配置
- MATCH_TYPES = res.data.matchTypes || MATCH_TYPES
- FREE_MATCH_LIMIT = res.data.freeMatchLimit || FREE_MATCH_LIMIT
- const matchPrice = res.data.matchPrice || 1
-
- this.setData({
- matchTypes: MATCH_TYPES,
- totalMatchesAllowed: FREE_MATCH_LIMIT,
- matchPrice: matchPrice
- })
-
- console.log('[Match] 加载匹配配置成功:', {
- types: MATCH_TYPES.length,
- freeLimit: FREE_MATCH_LIMIT,
- price: matchPrice
- })
- }
- } catch (e) {
- console.log('[Match] 加载匹配配置失败,使用默认配置:', e)
- }
- },
-
- // 加载本地存储的联系方式
- loadStoredContact() {
- const phone = wx.getStorageSync('user_phone') || ''
- const wechat = wx.getStorageSync('user_wechat') || ''
- this.setData({
- phoneNumber: phone,
- wechatId: wechat,
- userPhone: phone
- })
- },
-
- // 加载今日匹配次数
- loadTodayMatchCount() {
- try {
- const today = new Date().toISOString().split('T')[0]
- const stored = wx.getStorageSync('match_count_data')
- if (stored) {
- const data = typeof stored === 'string' ? JSON.parse(stored) : stored
- if (data.date === today) {
- this.setData({ todayMatchCount: data.count })
- }
- }
- } catch (e) {
- console.error('加载匹配次数失败:', e)
- }
- },
-
- // 保存今日匹配次数
- saveTodayMatchCount(count) {
- const today = new Date().toISOString().split('T')[0]
- wx.setStorageSync('match_count_data', { date: today, count })
- },
-
- // 初始化用户状态
- initUserStatus() {
- const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
-
- // 获取额外购买的匹配次数
- const extraMatches = wx.getStorageSync('extra_match_count') || 0
-
- // 总匹配次数 = 每日免费(3) + 额外购买次数
- // 全书用户无限制
- const totalMatchesAllowed = hasFullBook ? 999999 : FREE_MATCH_LIMIT + extraMatches
- const matchesRemaining = hasFullBook ? 999999 : Math.max(0, totalMatchesAllowed - this.data.todayMatchCount)
- const needPayToMatch = !hasFullBook && matchesRemaining <= 0
-
- this.setData({
- isLoggedIn,
- hasFullBook,
- hasPurchased: true, // 所有用户都可以使用匹配功能
- totalMatchesAllowed,
- matchesRemaining,
- needPayToMatch,
- extraMatches
- })
- },
-
- // 选择匹配类型
- selectType(e) {
- const typeId = e.currentTarget.dataset.type
- const type = MATCH_TYPES.find(t => t.id === typeId)
- this.setData({
- selectedType: typeId,
- currentTypeLabel: type?.matchLabel || type?.label || '创业伙伴'
- })
- },
-
- // 点击匹配按钮
- handleMatchClick() {
- const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType)
-
- // 资源对接类型需要登录+购买章节才能使用
- if (currentType && currentType.id === 'investor') {
- // 检查是否登录
- if (!this.data.isLoggedIn) {
- wx.showModal({
- title: '需要登录',
- content: '请先登录后再使用资源对接功能',
- confirmText: '去登录',
- success: (res) => {
- if (res.confirm) {
- wx.switchTab({ url: '/pages/my/my' })
- }
- }
- })
- return
- }
-
- // 检查是否购买过章节
- const hasPurchased = app.globalData.purchasedSections?.length > 0 || app.globalData.hasFullBook
- if (!hasPurchased) {
- wx.showModal({
- title: '需要购买章节',
- content: '购买任意章节后即可使用资源对接功能',
- confirmText: '去购买',
- success: (res) => {
- if (res.confirm) {
- wx.switchTab({ url: '/pages/catalog/catalog' })
- }
- }
- })
- return
- }
- }
-
- // 如果是需要填写联系方式的类型(资源对接、导师顾问、团队招募)
- if (currentType && currentType.showJoinAfterMatch) {
- // 先检查是否已绑定联系方式
- const hasPhone = !!this.data.phoneNumber
- const hasWechat = !!this.data.wechatId
-
- if (!hasPhone && !hasWechat) {
- // 没有绑定联系方式,先显示绑定提示
- this.setData({
- showJoinModal: true,
- joinType: currentType.id,
- joinTypeLabel: currentType.matchLabel || currentType.label,
- joinSuccess: false,
- joinError: '',
- needBindFirst: true
- })
- return
- }
-
- // 已绑定联系方式,先显示匹配动画1-3秒,再弹出确认
- this.startMatchingAnimation(currentType)
- return
- }
-
- // 创业合伙类型 - 真正的匹配功能
- if (this.data.needPayToMatch) {
- this.setData({ showUnlockModal: true })
- return
- }
-
- this.startMatch()
- },
-
- // 匹配动画后弹出加入确认
- startMatchingAnimation(currentType) {
- // 显示匹配中状态
- this.setData({
- isMatching: true,
- matchAttempts: 0,
- currentMatch: null
- })
-
- // 动画计时
- const timer = setInterval(() => {
- this.setData({ matchAttempts: this.data.matchAttempts + 1 })
- }, 500)
-
- // 1-3秒随机延迟后显示弹窗
- const delay = Math.random() * 2000 + 1000
- setTimeout(() => {
- clearInterval(timer)
- this.setData({
- isMatching: false,
- showJoinModal: true,
- joinType: currentType.id,
- joinTypeLabel: currentType.matchLabel || currentType.label,
- joinSuccess: false,
- joinError: '',
- needBindFirst: false
- })
- }, delay)
- },
-
- // 显示购买提示
- showPurchaseTip() {
- wx.showModal({
- title: '需要购买书籍',
- content: '购买《Soul创业派对》后即可使用匹配功能,仅需9.9元',
- confirmText: '去购买',
- success: (res) => {
- if (res.confirm) {
- this.goToChapters()
- }
- }
- })
- },
-
- // 开始匹配 - 只匹配数据库中的真实用户
- async startMatch() {
- this.setData({
- isMatching: true,
- matchAttempts: 0,
- currentMatch: null
- })
-
- // 匹配动画计时器
- const timer = setInterval(() => {
- this.setData({ matchAttempts: this.data.matchAttempts + 1 })
- }, 1000)
-
- // 从数据库获取真实用户匹配
- let matchedUser = null
- try {
- const res = await app.request({ url: '/api/match/users', silent: true,
- method: 'POST',
- data: {
- matchType: this.data.selectedType,
- userId: app.globalData.userInfo?.id || ''
- }
- })
-
- if (res.success && res.data) {
- matchedUser = res.data
- console.log('[Match] 从数据库匹配到用户:', matchedUser.nickname)
- }
- } catch (e) {
- console.log('[Match] 数据库匹配失败:', e)
- }
-
- // 延迟显示结果(模拟匹配过程)
- const delay = Math.random() * 2000 + 2000
- setTimeout(() => {
- clearInterval(timer)
-
- // 如果没有匹配到用户,提示用户
- if (!matchedUser) {
- this.setData({ isMatching: false })
- wx.showModal({
- title: '暂无匹配',
- content: '当前暂无合适的匹配用户,请稍后再试',
- showCancel: false,
- confirmText: '知道了'
- })
- return
- }
-
- // 增加今日匹配次数
- const newCount = this.data.todayMatchCount + 1
- const matchesRemaining = this.data.hasFullBook ? 999999 : Math.max(0, this.data.totalMatchesAllowed - newCount)
-
- this.setData({
- isMatching: false,
- currentMatch: matchedUser,
- todayMatchCount: newCount,
- matchesRemaining,
- needPayToMatch: !this.data.hasFullBook && matchesRemaining <= 0
- })
- this.saveTodayMatchCount(newCount)
-
- // 上报匹配行为到存客宝
- this.reportMatch(matchedUser)
-
- }, delay)
- },
-
- // 生成模拟匹配数据
- generateMockMatch() {
- const nicknames = ['创业先锋', '资源整合者', '私域专家', '商业导师', '连续创业者']
- const concepts = [
- '专注私域流量运营5年,帮助100+品牌实现从0到1的增长。',
- '连续创业者,擅长商业模式设计和资源整合。',
- '在Soul分享真实创业故事,希望找到志同道合的合作伙伴。'
- ]
- const wechats = ['soul_partner_1', 'soul_business_2024', 'soul_startup_fan']
-
- const index = Math.floor(Math.random() * nicknames.length)
- const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType)
-
- return {
- id: `user_${Date.now()}`,
- nickname: nicknames[index],
- avatar: `https://picsum.photos/200/200?random=${Date.now()}`,
- tags: ['创业者', '私域运营', currentType?.label || '创业合伙'],
- matchScore: Math.floor(Math.random() * 20) + 80,
- concept: concepts[index % concepts.length],
- wechat: wechats[index % wechats.length],
- commonInterests: [
- { icon: '📚', text: '都在读《创业派对》' },
- { icon: '💼', text: '对私域运营感兴趣' },
- { icon: '🎯', text: '相似的创业方向' }
- ]
- }
- },
-
- // 上报匹配行为
- async reportMatch(matchedUser) {
- try {
- await app.request({ url: '/api/ckb/match', silent: true,
- method: 'POST',
- data: {
- matchType: this.data.selectedType,
- phone: this.data.phoneNumber,
- wechat: this.data.wechatId,
- userId: app.globalData.userInfo?.id || '',
- nickname: app.globalData.userInfo?.nickname || '',
- matchedUser: {
- id: matchedUser.id,
- nickname: matchedUser.nickname,
- matchScore: matchedUser.matchScore
- }
- }
- })
- } catch (e) {
- console.log('上报匹配失败:', e)
- }
- },
-
- // 取消匹配
- cancelMatch() {
- this.setData({ isMatching: false, matchAttempts: 0 })
- },
-
- // 重置匹配(返回)
- resetMatch() {
- this.setData({ currentMatch: null })
- },
-
- // 添加微信好友
- handleAddWechat() {
- if (!this.data.currentMatch) return
-
- wx.setClipboardData({
- data: this.data.currentMatch.wechat,
- success: () => {
- wx.showModal({
- title: '微信号已复制',
- content: `微信号:${this.data.currentMatch.wechat}\n\n请打开微信添加好友,备注"创业合作"即可`,
- showCancel: false,
- confirmText: '知道了'
- })
- }
- })
- },
-
- // 切换联系方式类型
- switchContactType(e) {
- const type = e.currentTarget.dataset.type
- this.setData({ contactType: type, joinError: '' })
- },
-
- // 手机号输入
- onPhoneInput(e) {
- this.setData({
- phoneNumber: e.detail.value.replace(/\D/g, '').slice(0, 11),
- joinError: ''
- })
- },
-
- // 资源对接表单输入
- onCanHelpInput(e) {
- this.setData({ canHelp: e.detail.value })
- },
- onNeedHelpInput(e) {
- this.setData({ needHelp: e.detail.value })
- },
- onGoodAtInput(e) {
- this.setData({ goodAt: e.detail.value })
- },
-
- // 微信号输入
- onWechatInput(e) {
- this.setData({
- wechatId: e.detail.value,
- joinError: ''
- })
- },
-
- // 提交加入
- async handleJoinSubmit() {
- const { contactType, phoneNumber, wechatId, joinType, isJoining, canHelp, needHelp } = this.data
-
- if (isJoining) return
-
- // 验证联系方式
- if (contactType === 'phone') {
- if (!phoneNumber || phoneNumber.length !== 11) {
- this.setData({ joinError: '请输入正确的11位手机号' })
- return
- }
- } else {
- if (!wechatId || wechatId.length < 6) {
- this.setData({ joinError: '请输入正确的微信号(至少6位)' })
- return
- }
- }
-
- // 资源对接需要填写两项信息
- if (joinType === 'investor') {
- if (!canHelp || canHelp.trim().length < 2) {
- this.setData({ joinError: '请填写"我能帮到你什么"' })
- return
- }
- if (!needHelp || needHelp.trim().length < 2) {
- this.setData({ joinError: '请填写"我需要什么帮助"' })
- return
- }
- }
-
- this.setData({ isJoining: true, joinError: '' })
-
- try {
- const res = await app.request('/api/miniprogram/ckb/join', {
- method: 'POST',
- data: {
- type: joinType,
- phone: contactType === 'phone' ? phoneNumber : '',
- wechat: contactType === 'wechat' ? wechatId : '',
- userId: app.globalData.userInfo?.id || '',
- // 资源对接专属字段
- canHelp: joinType === 'investor' ? canHelp : '',
- needHelp: joinType === 'investor' ? needHelp : ''
- }
- })
-
- // 保存联系方式到本地
- if (phoneNumber) wx.setStorageSync('user_phone', phoneNumber)
- if (wechatId) wx.setStorageSync('user_wechat', wechatId)
-
- if (res.success) {
- this.setData({ joinSuccess: true })
- setTimeout(() => {
- this.setData({ showJoinModal: false, joinSuccess: false })
- }, 2000)
- } else {
- // 即使API返回失败,也模拟成功(因为已保存本地)
- this.setData({ joinSuccess: true })
- setTimeout(() => {
- this.setData({ showJoinModal: false, joinSuccess: false })
- }, 2000)
- }
- } catch (e) {
- // 网络错误时也模拟成功
- this.setData({ joinSuccess: true })
- setTimeout(() => {
- this.setData({ showJoinModal: false, joinSuccess: false })
- }, 2000)
- } finally {
- this.setData({ isJoining: false })
- }
- },
-
- // 关闭加入弹窗
- closeJoinModal() {
- if (this.data.isJoining) return
- this.setData({ showJoinModal: false, joinError: '' })
- },
-
- // 显示解锁弹窗
- showUnlockModal() {
- this.setData({ showUnlockModal: true })
- },
-
- // 关闭解锁弹窗
- closeUnlockModal() {
- this.setData({ showUnlockModal: false })
- },
-
- // 购买匹配次数
- async buyMatchCount() {
- this.setData({ showUnlockModal: false })
-
- try {
- // 获取openId
- let openId = app.globalData.openId || wx.getStorageSync('openId')
- if (!openId) {
- openId = await app.getOpenId()
- }
-
- if (!openId) {
- wx.showToast({ title: '请先登录', icon: 'none' })
- return
- }
-
- // 邀请码:与章节支付一致,写入订单便于分销归属与对账
- const referralCode = wx.getStorageSync('referral_code') || ''
- // 调用支付接口购买匹配次数
- const res = await app.request('/api/miniprogram/pay', {
- method: 'POST',
- data: {
- openId,
- productType: 'match',
- productId: 'match_1',
- amount: 1,
- description: '匹配次数x1',
- userId: app.globalData.userInfo?.id || '',
- referralCode: referralCode || undefined
- }
- })
-
- if (res.success && res.data?.payParams) {
- // 调用微信支付
- await new Promise((resolve, reject) => {
- wx.requestPayment({
- ...res.data.payParams,
- success: resolve,
- fail: reject
- })
- })
-
- // 支付成功,增加匹配次数
- const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
- wx.setStorageSync('extra_match_count', extraMatches)
-
- wx.showToast({ title: '购买成功', icon: 'success' })
- this.initUserStatus()
- } else {
- throw new Error(res.error || '创建订单失败')
- }
- } catch (e) {
- if (e.errMsg && e.errMsg.includes('cancel')) {
- wx.showToast({ title: '已取消', icon: 'none' })
- } else {
- // 测试模式
- wx.showModal({
- title: '支付服务暂不可用',
- content: '是否使用测试模式购买?',
- success: (res) => {
- if (res.confirm) {
- const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
- wx.setStorageSync('extra_match_count', extraMatches)
- wx.showToast({ title: '测试购买成功', icon: 'success' })
- this.initUserStatus()
- }
- }
- })
- }
- }
- },
-
- // 跳转到目录页购买
- goToChapters() {
- this.setData({ showUnlockModal: false })
- wx.switchTab({ url: '/pages/chapters/chapters' })
- },
-
- // 打开设置
- openSettings() {
- wx.navigateTo({ url: '/pages/settings/settings' })
- },
-
- // 阻止事件冒泡
- preventBubble() {}
-})
diff --git a/归档/miniprogram/pages/match/match.json b/归档/miniprogram/pages/match/match.json
deleted file mode 100644
index e7696321..00000000
--- a/归档/miniprogram/pages/match/match.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "usingComponents": {},
- "enablePullDownRefresh": false,
- "backgroundTextStyle": "light",
- "backgroundColor": "#000000"
-}
diff --git a/归档/miniprogram/pages/match/match.wxml b/归档/miniprogram/pages/match/match.wxml
deleted file mode 100644
index 3585aee0..00000000
--- a/归档/miniprogram/pages/match/match.wxml
+++ /dev/null
@@ -1,295 +0,0 @@
-
-
-
-
-
-
- 找伙伴
-
- ⚙️
-
-
-
-
-
-
-
-
-
-
- ⚡
- 今日免费次数已用完
- 购买次数
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ⚡
- 购买次数
- ¥1 = 1次匹配
-
-
- 👥
- 开始匹配
- 匹配{{currentTypeLabel}}
-
-
-
-
-
-
-
- 当前模式: {{currentTypeLabel}}
-
-
-
-
-
-
-
- 选择匹配类型
-
-
- {{item.icon}}
- {{item.label}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 🔍
-
-
-
- ✨
- 💫
- ⭐
- 🌟
-
-
-
-
-
- 正在匹配{{currentTypeLabel}}...
- 正在从 {{matchAttempts * 127 + 89}} 位创业者中为你寻找
-
- ✓ 分析兴趣标签
- ✓ 匹配创业方向
- ✓ 筛选优质伙伴
-
- 取消
-
-
-
-
-
-
-
-
- ✨
-
-
-
-
-
-
-
-
- 共同兴趣
-
-
- {{item.icon}}
- {{item.text}}
-
-
-
-
-
-
- 核心理念
- {{currentMatch.concept}}
-
-
-
-
-
- 一键加好友
- 返回
-
-
-
-
-
-
-
-
-
-
-
- ✅
- 提交成功
- 工作人员将在24小时内与您联系
-
-
-
-
-
-
-
-
-
-
-
- 📱
- 手机号
-
-
- 💬
- 微信号
-
-
-
-
-
-
-
- 我能帮到你什么 *
-
-
-
- 我需要什么帮助 *
-
-
-
-
-
-
-
-
- {{contactType === 'phone' ? '+86' : '@'}}
-
-
-
- {{joinError}}
-
-
-
-
- {{isJoining ? '提交中...' : '确认提交'}}
-
-
- 提交后我们会尽快与您联系
-
-
-
-
-
-
-
- ⚡
- 购买匹配次数
- 今日3次免费匹配已用完,可付费购买额外次数
-
-
-
- 单价
- ¥{{matchPrice || 1}} / 次
-
-
- 已购买
- {{extraMatches || 0}} 次
-
-
-
-
- 立即购买 ¥{{matchPrice || 1}}
- 明天再来
-
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/match/match.wxss b/归档/miniprogram/pages/match/match.wxss
deleted file mode 100644
index 378e1c99..00000000
--- a/归档/miniprogram/pages/match/match.wxss
+++ /dev/null
@@ -1,1202 +0,0 @@
-/**
- * Soul创业实验 - 找伙伴页样式
- * 按H5网页端完全重构
- */
-
-.page {
- min-height: 100vh;
- background: #000000;
- padding-bottom: 200rpx;
-}
-
-/* ===== 导航栏 ===== */
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(0, 0, 0, 0.9);
- backdrop-filter: blur(40rpx);
-}
-
-.nav-content {
- height: 88rpx;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 32rpx;
-}
-
-.nav-title {
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
-}
-
-.nav-settings {
- width: 80rpx;
- height: 80rpx;
- border-radius: 50%;
- background: #1c1c1e;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.settings-icon {
- font-size: 36rpx;
-}
-
-.nav-placeholder {
- width: 100%;
-}
-
-/* ===== 匹配提示条 - 简化版 ===== */
-.match-tip-bar {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 16rpx;
- margin: 24rpx 32rpx;
- padding: 20rpx 32rpx;
- background: rgba(255, 215, 0, 0.1);
- border-radius: 16rpx;
- border: 1rpx solid rgba(255, 215, 0, 0.2);
-}
-
-.tip-icon {
- font-size: 28rpx;
-}
-
-.tip-text {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.7);
-}
-
-.tip-btn {
- padding: 10rpx 24rpx;
- background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
- color: #000;
- font-size: 24rpx;
- font-weight: 500;
- border-radius: 20rpx;
-}
-
-.text-brand {
- color: #00CED1;
-}
-
-.text-red {
- color: #ff4444;
-}
-
-.text-gray {
- color: #666666;
-}
-
-.text-muted {
- color: rgba(255, 255, 255, 0.4);
-}
-
-.gold-text {
- color: #FFD700;
-}
-
-/* ===== 主内容区 ===== */
-.main-content {
- padding: 0 32rpx;
-}
-
-/* ===== 匹配圆环 ===== */
-.match-circle-wrapper {
- position: relative;
- width: 480rpx;
- height: 480rpx;
- margin: 48rpx auto;
-}
-
-.outer-glow {
- position: absolute;
- inset: -60rpx;
- border-radius: 50%;
- animation: pulseGlow 2s ease-in-out infinite;
-}
-
-.outer-glow.glow-active {
- background: radial-gradient(circle, transparent 50%, rgba(0, 229, 255, 0.1) 70%, transparent 100%);
-}
-
-.outer-glow.glow-inactive {
- background: radial-gradient(circle, transparent 50%, rgba(100, 100, 100, 0.1) 70%, transparent 100%);
-}
-
-@keyframes pulseGlow {
- 0%, 100% { transform: scale(1); opacity: 0.5; }
- 50% { transform: scale(1.1); opacity: 0.8; }
-}
-
-.middle-ring {
- position: absolute;
- inset: -30rpx;
- border-radius: 50%;
- border: 4rpx solid;
- animation: pulseRing 1.5s ease-in-out infinite;
-}
-
-.middle-ring.ring-active {
- border-color: rgba(0, 229, 255, 0.3);
-}
-
-.middle-ring.ring-inactive {
- border-color: rgba(100, 100, 100, 0.3);
-}
-
-@keyframes pulseRing {
- 0%, 100% { transform: scale(1); opacity: 0.3; }
- 50% { transform: scale(1.05); opacity: 0.6; }
-}
-
-.inner-sphere {
- position: absolute;
- inset: 0;
- border-radius: 50%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- animation: floatSphere 3s ease-in-out infinite;
- overflow: hidden;
-}
-
-.inner-sphere.sphere-active {
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
- box-shadow: 0 0 120rpx rgba(0, 229, 255, 0.3), inset 0 0 120rpx rgba(123, 97, 255, 0.2);
-}
-
-.inner-sphere.sphere-inactive {
- background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 50%, #1a1a1a 100%);
- box-shadow: 0 0 60rpx rgba(100, 100, 100, 0.2);
-}
-
-@keyframes floatSphere {
- 0%, 100% { transform: translateY(0); }
- 50% { transform: translateY(-10rpx); }
-}
-
-.sphere-gradient {
- position: absolute;
- inset: 0;
- border-radius: 50%;
- background: radial-gradient(circle at 30% 30%, rgba(123, 97, 255, 0.4) 0%, transparent 50%),
- radial-gradient(circle at 70% 70%, rgba(233, 30, 99, 0.3) 0%, transparent 50%);
-}
-
-.sphere-content {
- position: relative;
- z-index: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
-}
-
-.sphere-icon {
- font-size: 96rpx;
- margin-bottom: 16rpx;
-}
-
-.sphere-title {
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
- margin-bottom: 8rpx;
-}
-
-.sphere-desc {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 当前模式 ===== */
-.current-mode {
- text-align: center;
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.5);
- margin-bottom: 16rpx;
-}
-
-/* ===== 免费次数提示 ===== */
-.free-tip {
- text-align: center;
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.4);
- margin-bottom: 32rpx;
-}
-
-/* ===== 购买提示卡片 ===== */
-.purchase-tip-card {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 32rpx;
- background: linear-gradient(90deg, rgba(0, 229, 255, 0.1) 0%, transparent 100%);
- border: 2rpx solid rgba(0, 229, 255, 0.2);
- border-radius: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.tip-left {
- flex: 1;
-}
-
-.tip-title {
- display: block;
- font-size: 28rpx;
- font-weight: 500;
- color: #ffffff;
- margin-bottom: 8rpx;
-}
-
-.tip-desc {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.tip-btn {
- padding: 16rpx 32rpx;
- background: #00CED1;
- color: #000000;
- font-size: 26rpx;
- font-weight: 500;
- border-radius: 16rpx;
-}
-
-/* ===== 分隔线 ===== */
-.divider {
- height: 2rpx;
- background: rgba(255, 255, 255, 0.1);
- margin: 32rpx 0;
-}
-
-/* ===== 匹配类型选择 ===== */
-.type-section {
- margin-bottom: 32rpx;
-}
-
-.type-section-title {
- display: block;
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.4);
- text-align: center;
- margin-bottom: 24rpx;
-}
-
-.type-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 20rpx;
-}
-
-.type-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 16rpx;
- padding: 32rpx 16rpx;
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid transparent;
- transition: all 0.2s;
-}
-
-.type-item.type-active {
- background: rgba(0, 229, 255, 0.1);
- border-color: rgba(0, 229, 255, 0.5);
-}
-
-.type-icon {
- font-size: 48rpx;
-}
-
-.type-label {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.6);
- text-align: center;
-}
-
-/* ===== 匹配中状态 ===== */
-.matching-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 48rpx 0;
-}
-
-.matching-animation {
- position: relative;
- width: 400rpx;
- height: 400rpx;
- margin-bottom: 48rpx;
-}
-
-.matching-ring {
- position: absolute;
- inset: 0;
- border-radius: 50%;
- background: linear-gradient(135deg, #00CED1, #7B61FF, #E91E63);
- animation: rotateRing 3s linear infinite;
-}
-
-@keyframes rotateRing {
- to { transform: rotate(360deg); }
-}
-
-.matching-center {
- position: absolute;
- inset: 16rpx;
- border-radius: 50%;
- background: #000000;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.matching-icon {
- font-size: 96rpx;
- animation: pulseIcon 1s ease-in-out infinite;
-}
-
-@keyframes pulseIcon {
- 0%, 100% { transform: scale(1); }
- 50% { transform: scale(1.2); }
-}
-
-.ripple {
- position: absolute;
- inset: 0;
- border-radius: 50%;
- border: 4rpx solid rgba(0, 229, 255, 0.3);
- animation: rippleExpand 2s ease-out infinite;
-}
-
-.ripple-1 { animation-delay: 0s; }
-.ripple-2 { animation-delay: 0.5s; }
-.ripple-3 { animation-delay: 1s; }
-
-@keyframes rippleExpand {
- 0% { transform: scale(1); opacity: 0.6; }
- 100% { transform: scale(2); opacity: 0; }
-}
-
-.matching-title {
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
- margin-bottom: 16rpx;
-}
-
-.matching-count {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.5);
- margin-bottom: 48rpx;
-}
-
-.cancel-btn {
- padding: 24rpx 64rpx;
- background: #1c1c1e;
- color: #ffffff;
- font-size: 28rpx;
- border-radius: 48rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-/* ===== 匹配成功状态 ===== */
-.matched-state {
- padding: 32rpx 0;
-}
-
-.success-icon-wrapper {
- text-align: center;
- margin-bottom: 32rpx;
-}
-
-.success-icon {
- font-size: 120rpx;
-}
-
-.match-card {
- background: #1c1c1e;
- border-radius: 32rpx;
- padding: 40rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- margin-bottom: 32rpx;
-}
-
-.card-header {
- display: flex;
- align-items: center;
- gap: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.match-avatar {
- width: 128rpx;
- height: 128rpx;
- border-radius: 50%;
- border: 4rpx solid #00CED1;
- flex-shrink: 0;
-}
-
-.match-info {
- flex: 1;
- min-width: 0;
-}
-
-.match-name {
- display: block;
- font-size: 32rpx;
- font-weight: 600;
- color: #ffffff;
- margin-bottom: 12rpx;
-}
-
-.match-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 8rpx;
-}
-
-.match-tag {
- padding: 8rpx 16rpx;
- background: rgba(0, 229, 255, 0.2);
- color: #00CED1;
- font-size: 20rpx;
- border-radius: 8rpx;
-}
-
-.match-score-box {
- text-align: center;
- flex-shrink: 0;
-}
-
-.score-value {
- display: block;
- font-size: 48rpx;
- font-weight: 700;
- color: #00CED1;
-}
-
-.score-label {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.5);
-}
-
-.card-section {
- padding-top: 24rpx;
- border-top: 2rpx solid rgba(255, 255, 255, 0.1);
- margin-top: 24rpx;
-}
-
-.section-title {
- display: block;
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
- margin-bottom: 16rpx;
-}
-
-.interest-list {
- display: flex;
- flex-direction: column;
- gap: 12rpx;
-}
-
-.interest-item {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-
-.interest-icon {
- font-size: 28rpx;
-}
-
-.interest-text {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.8);
-}
-
-.concept-text {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.7);
- line-height: 1.6;
-}
-
-/* ===== 操作按钮 ===== */
-.action-buttons {
- display: flex;
- flex-direction: column;
- gap: 20rpx;
-}
-
-.btn-primary {
- padding: 32rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- color: #ffffff;
- font-size: 32rpx;
- font-weight: 600;
- text-align: center;
- border-radius: 24rpx;
-}
-
-.btn-secondary {
- padding: 32rpx;
- background: #1c1c1e;
- color: #ffffff;
- font-size: 32rpx;
- text-align: center;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.btn-disabled {
- opacity: 0.5;
-}
-
-/* ===== 弹窗 ===== */
-.modal-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.6);
- backdrop-filter: blur(20rpx);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- padding: 48rpx;
-}
-
-.modal-content {
- width: 100%;
- max-width: 640rpx;
- background: #1c1c1e;
- border-radius: 32rpx;
- overflow: hidden;
-}
-
-/* ===== 加入弹窗 ===== */
-.join-modal {
- padding: 40rpx;
-}
-
-.modal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 24rpx;
-}
-
-.modal-title {
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.close-btn {
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 32rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.form-tip {
- display: block;
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
- margin-bottom: 24rpx;
-}
-
-.contact-tabs {
- display: flex;
- gap: 16rpx;
- margin-bottom: 24rpx;
-}
-
-.contact-tab {
- flex: 1;
- padding: 20rpx;
- text-align: center;
- font-size: 28rpx;
- font-weight: 500;
- color: rgba(255, 255, 255, 0.6);
- background: rgba(255, 255, 255, 0.05);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 16rpx;
-}
-
-.contact-tab.tab-active-phone {
- background: rgba(0, 229, 255, 0.2);
- color: #00CED1;
- border-color: rgba(0, 229, 255, 0.3);
-}
-
-.contact-tab.tab-active-wechat {
- background: rgba(7, 193, 96, 0.2);
- color: #07C160;
- border-color: rgba(7, 193, 96, 0.3);
-}
-
-.input-group {
- margin-bottom: 24rpx;
-}
-
-.input-label {
- display: block;
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.4);
- margin-bottom: 12rpx;
-}
-
-.form-input {
- width: 100%;
- padding: 28rpx;
- background: rgba(0, 0, 0, 0.3);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 20rpx;
- font-size: 32rpx;
- color: #ffffff;
- box-sizing: border-box;
-}
-
-.input-placeholder {
- color: rgba(255, 255, 255, 0.3);
-}
-
-.error-text {
- display: block;
- font-size: 24rpx;
- color: #ff4444;
- margin-bottom: 16rpx;
-}
-
-.submit-btn {
- margin-top: 16rpx;
-}
-
-.form-notice {
- display: block;
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.3);
- text-align: center;
- margin-top: 24rpx;
-}
-
-/* ===== 新版加入弹窗 ===== */
-.join-modal-new {
- padding: 0;
- border-radius: 32rpx;
- overflow: hidden;
-}
-
-.join-header {
- position: relative;
- padding: 48rpx 40rpx 32rpx;
- background: linear-gradient(135deg, rgba(0, 206, 209, 0.15) 0%, rgba(123, 97, 255, 0.1) 100%);
- text-align: center;
-}
-
-.join-icon-wrap {
- width: 100rpx;
- height: 100rpx;
- margin: 0 auto 20rpx;
- background: rgba(0, 0, 0, 0.3);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.join-icon {
- font-size: 48rpx;
-}
-
-.join-title {
- display: block;
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
- margin-bottom: 8rpx;
-}
-
-.join-subtitle {
- display: block;
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.close-btn-new {
- position: absolute;
- top: 24rpx;
- right: 24rpx;
- width: 56rpx;
- height: 56rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.contact-switch {
- display: flex;
- gap: 16rpx;
- padding: 24rpx 40rpx;
-}
-
-.switch-item {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 12rpx;
- padding: 24rpx;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 16rpx;
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
- border: 2rpx solid transparent;
-}
-
-.switch-item.switch-active {
- background: rgba(0, 206, 209, 0.15);
- color: #00CED1;
- border-color: rgba(0, 206, 209, 0.3);
-}
-
-.switch-icon {
- font-size: 32rpx;
-}
-
-.input-area {
- padding: 0 40rpx 24rpx;
-}
-
-.input-wrapper {
- display: flex;
- align-items: center;
- background: rgba(0, 0, 0, 0.3);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 20rpx;
- overflow: hidden;
-}
-
-.input-prefix {
- padding: 0 24rpx;
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.5);
- border-right: 1rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.input-field {
- flex: 1;
- padding: 28rpx 24rpx;
- font-size: 32rpx;
- color: #ffffff;
-}
-
-.input-placeholder-new {
- color: rgba(255, 255, 255, 0.3);
-}
-
-.error-msg {
- display: block;
- font-size: 24rpx;
- color: #ff4444;
- margin-top: 12rpx;
- padding-left: 8rpx;
-}
-
-.submit-btn-new {
- margin: 8rpx 40rpx 24rpx;
- padding: 28rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- color: #ffffff;
- font-size: 32rpx;
- font-weight: 600;
- text-align: center;
- border-radius: 20rpx;
-}
-
-.btn-disabled-new {
- opacity: 0.5;
-}
-
-.form-notice-new {
- display: block;
- text-align: center;
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.3);
- padding-bottom: 32rpx;
-}
-
-/* ===== 新版加入成功 ===== */
-.join-success-new {
- padding: 64rpx 40rpx;
- text-align: center;
-}
-
-.success-icon-big {
- font-size: 96rpx;
- display: block;
- margin-bottom: 24rpx;
-}
-
-.success-title-new {
- display: block;
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
- margin-bottom: 12rpx;
-}
-
-.success-desc-new {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 旧版加入成功 (保留兼容) ===== */
-.join-success {
- padding: 48rpx;
- text-align: center;
-}
-
-.success-check {
- font-size: 128rpx;
- display: block;
- margin-bottom: 24rpx;
-}
-
-.success-title {
- display: block;
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
- margin-bottom: 12rpx;
-}
-
-.success-desc {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 解锁弹窗 ===== */
-.unlock-modal {
- padding: 48rpx;
- text-align: center;
-}
-
-.unlock-icon {
- width: 128rpx;
- height: 128rpx;
- margin: 0 auto 24rpx;
- background: rgba(255, 215, 0, 0.2);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 64rpx;
-}
-
-.unlock-title {
- display: block;
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
- margin-bottom: 12rpx;
-}
-
-.unlock-desc {
- display: block;
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
- margin-bottom: 32rpx;
-}
-
-.unlock-info {
- background: rgba(0, 0, 0, 0.3);
- border-radius: 20rpx;
- padding: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.info-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12rpx 0;
-}
-
-.info-label {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.info-value {
- font-size: 26rpx;
- font-weight: 500;
- color: #ffffff;
-}
-
-.unlock-buttons {
- display: flex;
- flex-direction: column;
- gap: 16rpx;
-}
-
-.btn-gold {
- padding: 28rpx;
- background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
- color: #000000;
- font-size: 30rpx;
- font-weight: 600;
- text-align: center;
- border-radius: 24rpx;
-}
-
-.btn-ghost {
- padding: 28rpx;
- background: rgba(255, 255, 255, 0.05);
- color: rgba(255, 255, 255, 0.6);
- font-size: 28rpx;
- text-align: center;
- border-radius: 24rpx;
-}
-
-/* ===== 底部留白 ===== */
-.bottom-space {
- height: 40rpx;
-}
-
-/* ===== 新版匹配动画 V2 ===== */
-.matching-animation-v2 {
- position: relative;
- width: 440rpx;
- height: 440rpx;
- margin: 0 auto 48rpx;
-}
-
-/* 外层旋转光环 */
-.matching-outer-ring {
- position: absolute;
- inset: -20rpx;
- border-radius: 50%;
- background: conic-gradient(
- from 0deg,
- transparent 0deg,
- #00CED1 60deg,
- #7B61FF 120deg,
- #E91E63 180deg,
- #FFD700 240deg,
- #00CED1 300deg,
- transparent 360deg
- );
- animation: rotateRingV2 2s linear infinite;
- opacity: 0.8;
-}
-
-.matching-outer-ring::before {
- content: '';
- position: absolute;
- inset: 8rpx;
- border-radius: 50%;
- background: #000;
-}
-
-@keyframes rotateRingV2 {
- to { transform: rotate(360deg); }
-}
-
-/* 中层脉冲环 */
-.matching-pulse-ring {
- position: absolute;
- inset: 20rpx;
- border-radius: 50%;
- border: 4rpx solid rgba(0, 206, 209, 0.5);
- animation: pulseRingV2 1.5s ease-in-out infinite;
-}
-
-@keyframes pulseRingV2 {
- 0%, 100% { transform: scale(1); opacity: 0.5; }
- 50% { transform: scale(1.1); opacity: 1; }
-}
-
-/* 内层核心球体 */
-.matching-core {
- position: absolute;
- inset: 60rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #1a2a4a 0%, #0a1628 50%, #16213e 100%);
- box-shadow:
- 0 0 60rpx rgba(0, 206, 209, 0.4),
- 0 0 120rpx rgba(123, 97, 255, 0.2),
- inset 0 0 80rpx rgba(0, 206, 209, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- animation: floatCoreV2 2s ease-in-out infinite;
-}
-
-.matching-core-inner {
- width: 160rpx;
- height: 160rpx;
- border-radius: 50%;
- background: radial-gradient(circle, rgba(0, 206, 209, 0.3) 0%, transparent 70%);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-@keyframes floatCoreV2 {
- 0%, 100% { transform: translateY(0) scale(1); }
- 50% { transform: translateY(-10rpx) scale(1.02); }
-}
-
-.matching-icon-v2 {
- font-size: 80rpx;
- animation: searchIconV2 1s ease-in-out infinite;
-}
-
-@keyframes searchIconV2 {
- 0%, 100% { transform: rotate(-15deg); }
- 50% { transform: rotate(15deg); }
-}
-
-/* 粒子效果 */
-.particle {
- position: absolute;
- font-size: 32rpx;
- animation: floatParticle 3s ease-in-out infinite;
- opacity: 0.8;
-}
-
-.particle-1 { top: 10%; left: 15%; animation-delay: 0s; }
-.particle-2 { top: 20%; right: 10%; animation-delay: 0.5s; }
-.particle-3 { bottom: 20%; left: 10%; animation-delay: 1s; }
-.particle-4 { bottom: 15%; right: 15%; animation-delay: 1.5s; }
-
-@keyframes floatParticle {
- 0%, 100% { transform: translateY(0) rotate(0deg); opacity: 0.4; }
- 50% { transform: translateY(-20rpx) rotate(180deg); opacity: 1; }
-}
-
-/* 扩散波纹 V2 */
-.ripple-v2 {
- position: absolute;
- inset: 40rpx;
- border-radius: 50%;
- border: 3rpx solid;
- border-color: rgba(0, 206, 209, 0.6);
- animation: rippleExpandV2 2.5s ease-out infinite;
-}
-
-.ripple-v2-1 { animation-delay: 0s; }
-.ripple-v2-2 { animation-delay: 0.8s; }
-.ripple-v2-3 { animation-delay: 1.6s; }
-
-@keyframes rippleExpandV2 {
- 0% { transform: scale(1); opacity: 0.8; }
- 100% { transform: scale(1.8); opacity: 0; }
-}
-
-/* 新版匹配文字 */
-.matching-title-v2 {
- display: block;
- font-size: 38rpx;
- font-weight: 700;
- color: #ffffff;
- text-align: center;
- margin-bottom: 12rpx;
- background: linear-gradient(90deg, #00CED1, #7B61FF, #00CED1);
- background-size: 200% auto;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- animation: shineText 2s linear infinite;
-}
-
-@keyframes shineText {
- to { background-position: 200% center; }
-}
-
-.matching-subtitle-v2 {
- display: block;
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.5);
- text-align: center;
- margin-bottom: 32rpx;
-}
-
-.matching-tips {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 16rpx;
- margin-bottom: 40rpx;
-}
-
-.tip-item {
- font-size: 26rpx;
- color: #00CED1;
- animation: fadeInUp 0.5s ease-out forwards;
- opacity: 0;
-}
-
-.tip-item:nth-child(1) { animation-delay: 0.5s; }
-.tip-item:nth-child(2) { animation-delay: 1.5s; }
-.tip-item:nth-child(3) { animation-delay: 2.5s; }
-
-@keyframes fadeInUp {
- from { opacity: 0; transform: translateY(20rpx); }
- to { opacity: 1; transform: translateY(0); }
-}
-
-.cancel-btn-v2 {
- display: inline-block;
- padding: 20rpx 60rpx;
- background: rgba(255, 255, 255, 0.1);
- color: rgba(255, 255, 255, 0.6);
- font-size: 28rpx;
- border-radius: 40rpx;
- border: 1rpx solid rgba(255, 255, 255, 0.2);
-}
-
-/* 资源对接表单 */
-.resource-form {
- display: flex;
- flex-direction: column;
- gap: 20rpx;
- margin-bottom: 24rpx;
-}
-.resource-form .form-item {
- display: flex;
- flex-direction: column;
- gap: 8rpx;
-}
-.resource-form .form-label {
- font-size: 26rpx;
- color: rgba(255,255,255,0.6);
-}
-.resource-form .form-input-new {
- background: #1c1c1e;
- border: 2rpx solid rgba(0,206,209,0.3);
- border-radius: 16rpx;
- padding: 20rpx;
- font-size: 28rpx;
- color: #fff;
-}
diff --git a/归档/miniprogram/pages/member-detail/member-detail.js b/归档/miniprogram/pages/member-detail/member-detail.js
deleted file mode 100644
index 04533440..00000000
--- a/归档/miniprogram/pages/member-detail/member-detail.js
+++ /dev/null
@@ -1,91 +0,0 @@
-const app = getApp()
-
-const MOCK_ENRICHMENT = [
- { mbti: 'ENTJ', region: '深圳', skills: '电商运营、供应链管理、团队搭建', contactRaw: '13800138001', bestMonth: '做跨境电商独立站,单月GMV突破200万,净利润35万', achievement: '从0到1搭建了30人的电商团队,年营收破3000万', turningPoint: '2019年从传统外贸转型跨境电商,放弃稳定薪资All in创业', canHelp: '电商选品、供应链对接、团队管理SOP', needHelp: '寻找品牌合作方和内容营销人才', project: '跨境电商独立站+亚马逊多店铺运营,主营家居类目' },
- { mbti: 'INFP', region: '杭州', skills: '短视频制作、IP打造、私域运营', contactRaw: '13900139002', bestMonth: '旅游账号30天涨粉10万,带货佣金收入12万', achievement: '帮助3个素人打造个人IP,每个月稳定变现5万+', turningPoint: '辞去互联网大厂工作开始做自媒体,第三个月就超过原薪资', canHelp: '短视频脚本、账号冷启动、私域转化设计', needHelp: '寻找供应链资源和线下活动合作', project: '旅游+生活方式自媒体矩阵,全网粉丝50万+' },
- { mbti: 'INTP', region: '厦门', skills: 'AI开发、小程序开发、系统架构', contactRaw: '13700137003', bestMonth: 'AI客服系统外包项目,单月收入18万', achievement: '独立开发的SaaS产品获得天使轮200万融资', turningPoint: '从程序员转型技术创业者,学会用技术解决商业问题', canHelp: '技术方案设计、AI应用落地、小程序开发', needHelp: '需要商业化运营和市场推广合伙人', project: 'AI+私域运营工具SaaS平台' },
- { mbti: 'ESTP', region: '成都', skills: '资源对接、商务BD、活动策划', contactRaw: '13600136004', bestMonth: '撮合景区合作,居间费收入25万', achievement: '组建覆盖全国50+城市创业者社群,活跃成员3000+', turningPoint: '在Soul派对房认识第一个合伙人,打开了社交创业的大门', canHelp: '各行业资源对接、活动策划、社群引荐', needHelp: '寻找技术合伙人和内容创作者', project: '创业者资源对接平台+线下创业者沙龙' }
-]
-
-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({ url: `/api/vip/members?id=${id}`, silent: true })
- if (res?.success && res.data) {
- const d = Array.isArray(res.data) ? res.data[0] : res.data
- if (d) { this.setData({ member: this.enrichAndFormat(d), loading: false }); return }
- }
- } catch (e) {}
-
- try {
- const dbRes = await app.request({ url: `/api/miniprogram/users?id=${id}`, silent: true })
- if (dbRes?.success && dbRes.data) {
- const u = Array.isArray(dbRes.data) ? dbRes.data[0] : dbRes.data
- if (u) {
- this.setData({ member: this.enrichAndFormat({
- id: u.id, name: u.vip_name || u.nickname || '创业者',
- avatar: u.vip_avatar || u.avatar || '', isVip: u.is_vip === 1,
- contactRaw: u.vip_contact || u.phone || '', project: u.vip_project || '',
- bio: u.vip_bio || '', mbti: '', region: '', skills: '',
- }), loading: false })
- return
- }
- }
- } catch (e) {}
- this.setData({ loading: false })
- },
-
- enrichAndFormat(raw) {
- const hash = (raw.id || '').split('').reduce((a, c) => a + c.charCodeAt(0), 0)
- const mock = MOCK_ENRICHMENT[hash % MOCK_ENRICHMENT.length]
-
- const merged = {
- id: raw.id,
- name: raw.name || raw.vip_name || raw.nickname || '创业者',
- avatar: raw.avatar || raw.vip_avatar || '',
- isVip: raw.isVip || raw.is_vip === 1,
- mbti: raw.mbti || mock.mbti,
- region: raw.region || mock.region,
- skills: raw.skills || mock.skills,
- contactRaw: raw.contactRaw || raw.vip_contact || mock.contactRaw,
- bestMonth: raw.bestMonth || mock.bestMonth,
- achievement: raw.achievement || mock.achievement,
- turningPoint: raw.turningPoint || mock.turningPoint,
- canHelp: raw.canHelp || mock.canHelp,
- needHelp: raw.needHelp || mock.needHelp,
- project: raw.project || raw.vip_project || mock.project
- }
-
- const contact = merged.contactRaw || ''
- const isMatched = (app.globalData.matchedUsers || []).includes(merged.id)
- merged.contactDisplay = contact ? (contact.slice(0, 3) + '****' + (contact.length > 7 ? contact.slice(-2) : '')) : ''
- merged.contactUnlocked = isMatched
- merged.contactFull = contact
- return merged
- },
-
- unlockContact() {
- wx.showModal({
- title: '解锁完整联系方式', content: '成为VIP会员并完成匹配后,即可查看完整联系方式',
- confirmText: '去匹配', cancelText: '知道了',
- success: (res) => { if (res.confirm) wx.switchTab({ url: '/pages/match/match' }) }
- })
- },
-
- copyContact() {
- const c = this.data.member?.contactFull
- if (!c) return
- wx.setClipboardData({ data: c, success: () => wx.showToast({ title: '已复制', icon: 'success' }) })
- },
-
- goToMatch() { wx.switchTab({ url: '/pages/match/match' }) },
- goToVip() { wx.navigateTo({ url: '/pages/vip/vip' }) },
- goBack() { wx.navigateBack() }
-})
diff --git a/归档/miniprogram/pages/member-detail/member-detail.json b/归档/miniprogram/pages/member-detail/member-detail.json
deleted file mode 100644
index 52bdd937..00000000
--- a/归档/miniprogram/pages/member-detail/member-detail.json
+++ /dev/null
@@ -1 +0,0 @@
-{ "usingComponents": {}, "navigationStyle": "custom" }
diff --git a/归档/miniprogram/pages/member-detail/member-detail.wxml b/归档/miniprogram/pages/member-detail/member-detail.wxml
deleted file mode 100644
index ca8b155f..00000000
--- a/归档/miniprogram/pages/member-detail/member-detail.wxml
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
- ‹
- 超级个体
-
-
-
-
-
-
-
-
-
-
-
-
- {{member.name[0] || '创'}}
- VIP
-
- {{member.name}}
-
- {{member.mbti}}
- 📍{{member.region}}
-
-
-
-
-
-
- 👤基本信息
-
- 我擅长
- {{member.skills}}
-
-
- 联系方式
-
- {{member.contactDisplay}}
-
- 🔒匹配解锁
-
- 复制
-
-
-
-
-
-
- 💡个人故事
-
- 🏆 最赚钱的一个月做的是什么
- {{member.bestMonth}}
-
-
-
- ⭐ 最有成就感的一件事
- {{member.achievement}}
-
-
-
- 🔄 人生的转折点
- {{member.turningPoint}}
-
-
-
-
-
- 🤝互助需求
-
- 我能帮到你
- {{member.canHelp}}
-
-
- 我需要帮助
- {{member.needHelp}}
-
-
-
-
-
- 🚀项目介绍
- {{member.project}}
-
-
-
-
- 开始匹配 · 解锁联系方式
- 成为超级个体 →
-
-
-
-
-
-
-
- 加载中...
-
-
- 👤暂无该超级个体信息
-
-
diff --git a/归档/miniprogram/pages/member-detail/member-detail.wxss b/归档/miniprogram/pages/member-detail/member-detail.wxss
deleted file mode 100644
index 7afe12e4..00000000
--- a/归档/miniprogram/pages/member-detail/member-detail.wxss
+++ /dev/null
@@ -1,75 +0,0 @@
-.page { background: #050a10; min-height: 100vh; color: #fff; }
-
-/* 导航 */
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 999; display: flex; align-items: center; justify-content: space-between; padding: 0 24rpx; height: 44px; background: rgba(5,10,16,.92); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border-bottom: 1rpx solid rgba(56,189,172,.08); }
-.back-arrow { font-size: 48rpx; color: #38bdac; font-weight: 300; }
-.nav-title { font-size: 32rpx; font-weight: 600; letter-spacing: 2rpx; }
-.nav-ph { width: 48rpx; }
-.nav-back { width: 48rpx; height: 48rpx; display: flex; align-items: center; justify-content: center; }
-
-.scroll-wrap { height: calc(100vh - 88px); }
-
-/* ===== 顶部名片 ===== */
-.card-hero { position: relative; margin: 24rpx 24rpx 0; padding: 56rpx 0 40rpx; border-radius: 28rpx; overflow: hidden; background: linear-gradient(160deg, #0d1f2d 0%, #0a1620 40%, #101828 100%); border: 1rpx solid rgba(56,189,172,.15); }
-.hero-deco { position: absolute; top: -60rpx; right: -60rpx; width: 240rpx; height: 240rpx; border-radius: 50%; background: radial-gradient(circle, rgba(56,189,172,.12) 0%, transparent 70%); }
-.hero-deco2 { position: absolute; bottom: -40rpx; left: -40rpx; width: 180rpx; height: 180rpx; border-radius: 50%; background: radial-gradient(circle, rgba(245,166,35,.06) 0%, transparent 70%); }
-.hero-body { position: relative; display: flex; flex-direction: column; align-items: center; z-index: 1; }
-
-.avatar-ring { position: relative; width: 168rpx; height: 168rpx; border-radius: 50%; padding: 6rpx; background: linear-gradient(135deg, #1a3a3a, #0d1f2d); margin-bottom: 20rpx; }
-.avatar-ring.vip-ring { background: linear-gradient(135deg, #f5a623, #38bdac, #f5a623); background-size: 300% 300%; animation: vipGlow 4s ease infinite; }
-@keyframes vipGlow { 0%,100%{background-position:0% 50%} 50%{background-position:100% 50%} }
-.avatar-img { width: 100%; height: 100%; border-radius: 50%; border: 4rpx solid #0a1620; }
-.avatar-ph { width: 100%; height: 100%; border-radius: 50%; border: 4rpx solid #0a1620; background: #152030; display: flex; align-items: center; justify-content: center; font-size: 52rpx; color: #38bdac; font-weight: 700; }
-.vip-tag { position: absolute; bottom: 4rpx; right: 4rpx; background: linear-gradient(135deg, #f5a623, #e8920d); color: #000; font-size: 18rpx; font-weight: 800; padding: 4rpx 14rpx; border-radius: 16rpx; letter-spacing: 1rpx; box-shadow: 0 4rpx 12rpx rgba(245,166,35,.4); }
-
-.hero-name { font-size: 38rpx; font-weight: 700; letter-spacing: 2rpx; margin-bottom: 12rpx; text-shadow: 0 2rpx 8rpx rgba(0,0,0,.5); }
-.hero-tags { display: flex; gap: 12rpx; flex-wrap: wrap; justify-content: center; }
-.tag-item { font-size: 22rpx; padding: 6rpx 18rpx; border-radius: 24rpx; }
-.tag-mbti { color: #38bdac; background: rgba(56,189,172,.12); border: 1rpx solid rgba(56,189,172,.2); }
-.tag-region { color: #ccc; background: rgba(255,255,255,.06); border: 1rpx solid rgba(255,255,255,.08); }
-
-/* ===== 通用卡片 ===== */
-.card { margin: 20rpx 24rpx; padding: 28rpx 28rpx; border-radius: 24rpx; background: rgba(15,25,40,.8); border: 1rpx solid rgba(255,255,255,.06); backdrop-filter: blur(10px); }
-.card-head { display: flex; align-items: center; gap: 10rpx; margin-bottom: 24rpx; }
-.card-icon { font-size: 28rpx; }
-.card-label { font-size: 28rpx; font-weight: 600; color: #fff; letter-spacing: 1rpx; }
-
-.field { margin-bottom: 20rpx; }
-.field:last-child { margin-bottom: 0; }
-.f-key { display: block; font-size: 22rpx; color: #6b7b8e; margin-bottom: 8rpx; text-transform: uppercase; letter-spacing: 2rpx; }
-.f-val { font-size: 28rpx; color: #e0e8f0; line-height: 1.7; }
-.f-contact { display: flex; align-items: center; gap: 16rpx; }
-.masked { letter-spacing: 3rpx; font-family: 'Courier New', monospace; }
-.lock-chip { display: flex; align-items: center; gap: 6rpx; font-size: 20rpx; color: #f5a623; background: rgba(245,166,35,.1); border: 1rpx solid rgba(245,166,35,.2); padding: 6rpx 16rpx; border-radius: 20rpx; white-space: nowrap; }
-.lock-icon { font-size: 18rpx; }
-.copy-chip { font-size: 20rpx; color: #38bdac; background: rgba(56,189,172,.1); border: 1rpx solid rgba(56,189,172,.2); padding: 6rpx 16rpx; border-radius: 20rpx; white-space: nowrap; }
-
-/* ===== 故事 ===== */
-.story { padding: 4rpx 0; }
-.story-q { display: block; font-size: 24rpx; color: #7a8fa3; margin-bottom: 10rpx; }
-.story-a { display: block; font-size: 28rpx; color: #e0e8f0; line-height: 1.8; }
-.divider { height: 1rpx; background: rgba(255,255,255,.04); margin: 20rpx 0; }
-
-/* ===== 互助 ===== */
-.help-box { padding: 20rpx; border-radius: 16rpx; margin-bottom: 16rpx; }
-.help-box:last-child { margin-bottom: 0; }
-.help-give { background: rgba(56,189,172,.06); border: 1rpx solid rgba(56,189,172,.1); }
-.help-need { background: rgba(245,166,35,.06); border: 1rpx solid rgba(245,166,35,.1); }
-.help-tag { display: inline-block; font-size: 22rpx; font-weight: 600; color: #38bdac; margin-bottom: 10rpx; padding: 4rpx 14rpx; border-radius: 12rpx; background: rgba(56,189,172,.12); }
-.help-tag.need { color: #f5a623; background: rgba(245,166,35,.12); }
-.help-txt { display: block; font-size: 26rpx; color: #ccd4de; line-height: 1.7; }
-
-/* ===== 项目 ===== */
-.proj-txt { font-size: 26rpx; color: #ccd4de; line-height: 1.8; }
-
-/* ===== 底部按钮 ===== */
-.bottom-actions { padding: 32rpx 24rpx 0; display: flex; flex-direction: column; gap: 16rpx; }
-.btn-match { text-align: center; padding: 26rpx 0; border-radius: 48rpx; font-size: 30rpx; font-weight: 700; letter-spacing: 2rpx; background: linear-gradient(135deg, #38bdac 0%, #2ca898 50%, #249e8c 100%); color: #fff; box-shadow: 0 8rpx 24rpx rgba(56,189,172,.3); }
-.btn-vip { text-align: center; padding: 22rpx 0; border-radius: 48rpx; font-size: 26rpx; color: #f5a623; background: transparent; border: 1rpx solid rgba(245,166,35,.3); }
-
-/* ===== 状态 ===== */
-.state-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 60vh; gap: 16rpx; }
-.state-txt { font-size: 28rpx; color: #4a5a6e; }
-.state-emoji { font-size: 80rpx; }
-.loading-dot { width: 48rpx; height: 48rpx; border-radius: 50%; border: 4rpx solid rgba(56,189,172,.2); border-top-color: #38bdac; animation: spin 1s linear infinite; }
-@keyframes spin { to { transform: rotate(360deg); } }
diff --git a/归档/miniprogram/pages/my/my.js b/归档/miniprogram/pages/my/my.js
deleted file mode 100644
index becc5a0f..00000000
--- a/归档/miniprogram/pages/my/my.js
+++ /dev/null
@@ -1,715 +0,0 @@
-/**
- * Soul创业派对 - 我的页面
- * 开发: 卡若
- * 技术支持: 存客宝
- */
-
-const app = getApp()
-
-Page({
- data: {
- // 系统信息
- statusBarHeight: 44,
- navBarHeight: 88,
-
- // 用户状态
- isLoggedIn: false,
- userInfo: null,
-
- // 统计数据
- totalSections: 62,
- readCount: 0,
- referralCount: 0,
- earnings: '-',
- pendingEarnings: '-',
- earningsLoading: true,
- earningsRefreshing: false,
-
- // 阅读统计
- totalReadTime: 0,
- matchHistory: 0,
-
- // 最近阅读
- recentChapters: [],
-
- // 功能配置
- matchEnabled: false,
-
- // VIP状态
- isVip: false,
- vipExpireDate: '',
-
- // 待确认收款
- pendingConfirmList: [],
- withdrawMchId: '',
- withdrawAppId: '',
-
- // 未登录假资料(展示用)
- guestNickname: '游客',
- guestAvatar: '',
-
- // 登录弹窗
- showLoginModal: false,
- isLoggingIn: false,
- // 用户须主动勾选同意协议(审核要求:不得默认同意)
- agreeProtocol: false,
-
- // 修改昵称弹窗
- showNicknameModal: false,
- editingNickname: ''
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight,
- navBarHeight: app.globalData.navBarHeight
- })
- this.loadFeatureConfig()
- this.initUserStatus()
- },
-
- onShow() {
- // 设置TabBar选中状态(根据 matchEnabled 动态设置)
- if (typeof this.getTabBar === 'function' && this.getTabBar()) {
- const tabBar = this.getTabBar()
- if (tabBar.updateSelected) {
- tabBar.updateSelected()
- } else {
- const selected = tabBar.data.matchEnabled ? 3 : 2
- tabBar.setData({ selected })
- }
- }
- this.initUserStatus()
- },
-
- async loadFeatureConfig() {
- try {
- const res = await app.request('/api/miniprogram/config')
- const features = (res && res.features) || (res && res.data && res.data.features) || {}
- this.setData({ matchEnabled: features.matchEnabled === true })
- } catch (error) {
- console.log('加载功能配置失败:', error)
- this.setData({ matchEnabled: false })
- }
- },
-
- // 初始化用户状态
- initUserStatus() {
- const { isLoggedIn, userInfo } = app.globalData
-
- if (isLoggedIn && userInfo) {
- const readIds = app.globalData.readSectionIds || []
- const recentList = readIds.slice(-5).reverse().map(id => ({
- id: id,
- title: `章节 ${id}`
- }))
-
- const userId = userInfo.id || ''
- const userIdShort = userId.length > 20 ? userId.slice(0, 10) + '...' + userId.slice(-6) : userId
- const userWechat = wx.getStorageSync('user_wechat') || userInfo.wechat || ''
-
- // 先设基础信息;收益由 loadMyEarnings 专用接口拉取,加载前用 - 占位
- this.setData({
- isLoggedIn: true,
- userInfo,
- userIdShort,
- userWechat,
- readCount: Math.min(app.getReadCount(), this.data.totalSections || 62),
- referralCount: userInfo.referralCount || 0,
- earnings: '-',
- pendingEarnings: '-',
- earningsLoading: true,
- recentChapters: recentList,
- totalReadTime: Math.floor(Math.random() * 200) + 50
- })
- this.loadMyEarnings()
- this.loadPendingConfirm()
- this.loadVipStatus()
- } else {
- this.setData({
- isLoggedIn: false,
- userInfo: null,
- userIdShort: '',
- readCount: app.getReadCount(),
- referralCount: 0,
- earnings: '-',
- pendingEarnings: '-',
- earningsLoading: false,
- recentChapters: []
- })
- }
- },
-
- // 拉取待确认收款列表(用于「确认收款」按钮)
- async loadPendingConfirm() {
- const userInfo = app.globalData.userInfo
- if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) return
- try {
- const res = await app.request({ url: '/api/miniprogram/withdraw/pending-confirm?userId=' + userInfo.id, silent: true })
- if (res && res.success && res.data) {
- const list = (res.data.list || []).map(item => ({
- id: item.id,
- amount: (item.amount || 0).toFixed(2),
- package: item.package,
- createdAt: (item.createdAt ?? item.created_at) ? this.formatDateMy(item.createdAt ?? item.created_at) : '--'
- }))
- this.setData({
- pendingConfirmList: list,
- withdrawMchId: res.data.mchId ?? res.data.mch_id ?? '',
- withdrawAppId: res.data.appId ?? res.data.app_id ?? ''
- })
- } else {
- this.setData({ pendingConfirmList: [], withdrawMchId: '', withdrawAppId: '' })
- }
- } catch (e) {
- this.setData({ pendingConfirmList: [] })
- }
- },
-
- formatDateMy(dateStr) {
- if (!dateStr) return '--'
- const d = new Date(dateStr)
- const m = (d.getMonth() + 1).toString().padStart(2, '0')
- const day = d.getDate().toString().padStart(2, '0')
- return `${m}-${day}`
- },
-
- // 确认收款:有 package 时调起微信收款页,成功后记录;无 package 时仅调用后端记录「已确认收款」
- async confirmReceive(e) {
- const index = e.currentTarget.dataset.index
- const id = e.currentTarget.dataset.id
- const list = this.data.pendingConfirmList || []
- let item = (typeof index === 'number' || (index !== undefined && index !== '')) ? list[index] : null
- if (!item && id) item = list.find(x => x.id === id) || null
- if (!item) {
- wx.showToast({ title: '请稍后刷新再试', icon: 'none' })
- return
- }
- const mchId = this.data.withdrawMchId
- const appId = this.data.withdrawAppId
- const hasPackage = item.package && mchId && appId && wx.canIUse('requestMerchantTransfer')
-
- const recordConfirmReceived = async () => {
- const userInfo = app.globalData.userInfo
- if (userInfo && userInfo.id) {
- try {
- await app.request({
- url: '/api/miniprogram/withdraw/confirm-received',
- method: 'POST',
- data: { withdrawalId: item.id, userId: userInfo.id }
- })
- } catch (e) { /* 仅记录,不影响前端展示 */ }
- }
- const newList = list.filter(x => x.id !== item.id)
- this.setData({ pendingConfirmList: newList })
- this.loadPendingConfirm()
- }
-
- if (hasPackage) {
- wx.showLoading({ title: '调起收款...', mask: true })
- wx.requestMerchantTransfer({
- mchId,
- appId,
- package: item.package,
- success: async () => {
- wx.hideLoading()
- wx.showToast({ title: '收款成功', icon: 'success' })
- await recordConfirmReceived()
- },
- fail: (err) => {
- wx.hideLoading()
- const msg = (err.errMsg || '').includes('cancel') ? '已取消' : (err.errMsg || '收款失败')
- wx.showToast({ title: msg, icon: 'none' })
- },
- complete: () => { wx.hideLoading() }
- })
- return
- }
-
- // 无 package 时仅记录「确认已收款」(当前直接打款无 package,用户点按钮即记录)
- wx.showLoading({ title: '提交中...', mask: true })
- try {
- await recordConfirmReceived()
- wx.hideLoading()
- wx.showToast({ title: '已记录确认收款', icon: 'success' })
- } catch (e) {
- wx.hideLoading()
- wx.showToast({ title: (e && e.message) || '操作失败', icon: 'none' })
- }
- },
-
- // 专用接口:拉取「我的收益」卡片数据(累计、可提现、推荐人数)
- async loadMyEarnings() {
- const userInfo = app.globalData.userInfo
- if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) {
- this.setData({ earningsLoading: false })
- return
- }
- const formatMoney = (num) => (typeof num === 'number' ? num.toFixed(2) : '0.00')
- try {
- const res = await app.request({ url: '/api/miniprogram/earnings?userId=' + userInfo.id, silent: true })
- if (!res || !res.success || !res.data) {
- this.setData({ earningsLoading: false, earnings: '0.00', pendingEarnings: '0.00' })
- return
- }
- const d = res.data
- this.setData({
- earnings: formatMoney(d.totalCommission),
- pendingEarnings: formatMoney(d.availableEarnings),
- referralCount: d.referralCount ?? this.data.referralCount,
- earningsLoading: false,
- earningsRefreshing: false
- })
- } catch (e) {
- console.log('[My] 拉取我的收益失败:', e && e.message)
- this.setData({
- earningsLoading: false,
- earningsRefreshing: false,
- earnings: '0.00',
- pendingEarnings: '0.00'
- })
- }
- },
-
- // 点击刷新图标:刷新我的收益
- async refreshEarnings() {
- if (!this.data.isLoggedIn) return
- if (this.data.earningsRefreshing) return
- this.setData({ earningsRefreshing: true })
- wx.showToast({ title: '刷新中...', icon: 'loading', duration: 2000 })
- await this.loadMyEarnings()
- wx.showToast({ title: '已刷新', icon: 'success' })
- },
-
- // 微信原生获取头像(button open-type="chooseAvatar" 回调)
- async onChooseAvatar(e) {
- const tempAvatarUrl = e.detail.avatarUrl
- if (!tempAvatarUrl) return
-
- wx.showLoading({ title: '上传中...', mask: true })
-
- try {
- // 1. 先上传图片到服务器
- console.log('[My] 开始上传头像:', tempAvatarUrl)
-
- const uploadRes = await new Promise((resolve, reject) => {
- wx.uploadFile({
- url: app.globalData.baseUrl + '/api/miniprogram/upload',
- filePath: tempAvatarUrl,
- name: 'file',
- formData: {
- folder: 'avatars'
- },
- success: (res) => {
- try {
- const data = JSON.parse(res.data)
- if (data.success) {
- resolve(data)
- } else {
- reject(new Error(data.error || '上传失败'))
- }
- } catch (err) {
- reject(new Error('解析响应失败'))
- }
- },
- fail: (err) => {
- reject(err)
- }
- })
- })
-
- // 2. 获取上传后的完整URL
- const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
- console.log('[My] 头像上传成功:', avatarUrl)
-
- // 3. 更新本地头像
- const userInfo = this.data.userInfo
- userInfo.avatar = avatarUrl
- this.setData({ userInfo })
- app.globalData.userInfo = userInfo
- wx.setStorageSync('userInfo', userInfo)
-
- // 4. 同步到服务器数据库
- await app.request('/api/miniprogram/user/update', {
- method: 'POST',
- data: { userId: userInfo.id, avatar: avatarUrl }
- })
-
- wx.hideLoading()
- wx.showToast({ title: '头像更新成功', icon: 'success' })
-
- } catch (e) {
- wx.hideLoading()
- console.error('[My] 上传头像失败:', e)
- wx.showToast({
- title: e.message || '上传失败,请重试',
- icon: 'none'
- })
- }
- },
-
- // 微信原生获取昵称回调(针对 input type="nickname" 的 bindblur 或 bindchange)
- async handleNicknameChange(nickname) {
- if (!nickname || nickname === this.data.userInfo?.nickname) return
-
- try {
- const userInfo = this.data.userInfo
- userInfo.nickname = nickname
- this.setData({ userInfo })
- app.globalData.userInfo = userInfo
- wx.setStorageSync('userInfo', userInfo)
-
- // 同步到服务器
- await app.request('/api/miniprogram/user/update', {
- method: 'POST',
- data: { userId: userInfo.id, nickname }
- })
-
- wx.showToast({ title: '昵称已更新', icon: 'success' })
- } catch (e) {
- console.error('[My] 同步昵称失败:', e)
- }
- },
-
- // 打开昵称修改弹窗
- editNickname() {
- this.setData({
- showNicknameModal: true,
- editingNickname: this.data.userInfo?.nickname || ''
- })
- },
-
- // 关闭昵称弹窗
- closeNicknameModal() {
- this.setData({
- showNicknameModal: false,
- editingNickname: ''
- })
- },
-
- // 阻止事件冒泡
- stopPropagation() {},
-
- // 昵称输入实时更新
- onNicknameInput(e) {
- this.setData({
- editingNickname: e.detail.value
- })
- },
-
- // 昵称变化(微信自动填充时触发)
- onNicknameChange(e) {
- const nickname = e.detail.value
- console.log('[My] 昵称已自动填充:', nickname)
- this.setData({
- editingNickname: nickname
- })
- // 自动填充时也尝试直接同步
- this.handleNicknameChange(nickname)
- },
-
- // 确认修改昵称
- async confirmNickname() {
- const newNickname = this.data.editingNickname.trim()
-
- if (!newNickname) {
- wx.showToast({ title: '昵称不能为空', icon: 'none' })
- return
- }
-
- if (newNickname.length < 1 || newNickname.length > 20) {
- wx.showToast({ title: '昵称1-20个字符', icon: 'none' })
- return
- }
-
- // 关闭弹窗
- this.closeNicknameModal()
-
- // 显示加载
- wx.showLoading({ title: '更新中...', mask: true })
-
- try {
- // 1. 同步到服务器
- const res = await app.request('/api/miniprogram/user/update', {
- method: 'POST',
- data: {
- userId: this.data.userInfo.id,
- nickname: newNickname
- }
- })
-
- if (res && res.success) {
- // 2. 更新本地状态
- const userInfo = this.data.userInfo
- userInfo.nickname = newNickname
- this.setData({ userInfo })
-
- // 3. 更新全局和缓存
- app.globalData.userInfo = userInfo
- wx.setStorageSync('userInfo', userInfo)
-
- wx.hideLoading()
- wx.showToast({ title: '昵称已修改', icon: 'success' })
- } else {
- throw new Error(res?.message || '更新失败')
- }
- } catch (e) {
- wx.hideLoading()
- console.error('[My] 修改昵称失败:', e)
- wx.showToast({ title: '修改失败,请重试', icon: 'none' })
- }
- },
-
- // 复制用户ID
- copyUserId() {
- const userId = this.data.userInfo?.id || ''
- if (!userId) {
- wx.showToast({ title: '暂无ID', icon: 'none' })
- return
- }
- wx.setClipboardData({
- data: userId,
- success: () => {
- wx.showToast({ title: 'ID已复制', icon: 'success' })
- }
- })
- },
-
- // 切换Tab
- switchTab(e) {
- const tab = e.currentTarget.dataset.tab
- this.setData({ activeTab: tab })
- },
-
- // 显示登录弹窗(每次打开时协议未勾选,符合审核要求)
- showLogin() {
- try {
- this.setData({ showLoginModal: true, agreeProtocol: false })
- } catch (e) {
- console.error('[My] showLogin error:', e)
- this.setData({ showLoginModal: true })
- }
- },
-
- // 切换协议勾选(用户主动勾选,非默认同意)
- toggleAgree() {
- this.setData({ agreeProtocol: !this.data.agreeProtocol })
- },
-
- // 打开用户协议页(审核要求:点击《用户协议》需有响应)
- openUserProtocol() {
- wx.navigateTo({ url: '/pages/agreement/agreement' })
- },
-
- // 打开隐私政策页(审核要求:点击《隐私政策》需有响应)
- openPrivacy() {
- wx.navigateTo({ url: '/pages/privacy/privacy' })
- },
-
- // 关闭登录弹窗
- closeLoginModal() {
- if (this.data.isLoggingIn) return
- this.setData({ showLoginModal: false })
- },
-
- // 微信登录(须已勾选同意协议,且做好错误处理避免审核报错)
- async handleWechatLogin() {
- if (!this.data.agreeProtocol) {
- wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
- return
- }
- this.setData({ isLoggingIn: true })
- try {
- const result = await app.login()
- if (result) {
- this.initUserStatus()
- this.setData({ showLoginModal: false, agreeProtocol: false })
- wx.showToast({ title: '登录成功', icon: 'success' })
- } else {
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- }
- } catch (e) {
- console.error('[My] 微信登录错误:', e)
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- } finally {
- this.setData({ isLoggingIn: false })
- }
- },
-
- // 手机号登录(需要用户授权)
- async handlePhoneLogin(e) {
- // 检查是否有授权code
- if (!e.detail.code) {
- // 用户拒绝授权或获取失败,尝试使用微信登录
- console.log('手机号授权失败,尝试微信登录')
- return this.handleWechatLogin()
- }
-
- this.setData({ isLoggingIn: true })
-
- try {
- const result = await app.loginWithPhone(e.detail.code)
- if (result) {
- this.initUserStatus()
- this.setData({ showLoginModal: false })
- wx.showToast({ title: '登录成功', icon: 'success' })
- } else {
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- }
- } catch (e) {
- console.error('手机号登录错误:', e)
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- } finally {
- this.setData({ isLoggingIn: false })
- }
- },
-
- // 点击菜单
- handleMenuTap(e) {
- const id = e.currentTarget.dataset.id
-
- if (!this.data.isLoggedIn && id !== 'about') {
- this.showLogin()
- return
- }
-
- const routes = {
- orders: '/pages/purchases/purchases',
- referral: '/pages/referral/referral',
- withdrawRecords: '/pages/withdraw-records/withdraw-records',
- about: '/pages/about/about',
- settings: '/pages/settings/settings'
- }
-
- if (routes[id]) {
- wx.navigateTo({ url: routes[id] })
- }
- },
-
- // 跳转到阅读页
- goToRead(e) {
- const id = e.currentTarget.dataset.id
- wx.navigateTo({ url: `/pages/read/read?id=${id}` })
- },
-
- // 跳转到目录
- goToChapters() {
- wx.switchTab({ url: '/pages/chapters/chapters' })
- },
-
- // 跳转到关于页
- goToAbout() {
- wx.navigateTo({ url: '/pages/about/about' })
- },
-
- // 跳转到匹配
- goToMatch() {
- wx.switchTab({ url: '/pages/match/match' })
- },
-
- // 跳转到推广中心
- goToReferral() {
- if (!this.data.isLoggedIn) {
- this.showLogin()
- return
- }
- wx.navigateTo({ url: '/pages/referral/referral' })
- },
-
- // 跳转到找伙伴页面
- goToMatch() {
- wx.switchTab({ url: '/pages/match/match' })
- },
-
- // 退出登录
- handleLogout() {
- wx.showModal({
- title: '退出登录',
- content: '确定要退出登录吗?',
- success: (res) => {
- if (res.confirm) {
- app.logout()
- this.initUserStatus()
- wx.showToast({ title: '已退出登录', icon: 'success' })
- }
- }
- })
- },
-
- // VIP状态查询
- async loadVipStatus() {
- const userId = app.globalData.userInfo?.id
- if (!userId) return
- try {
- const res = await app.request({ url: `/api/vip/status?userId=${userId}`, silent: true })
- if (res?.success) {
- this.setData({ isVip: res.data?.isVip, vipExpireDate: res.data?.expireDate || '' })
- }
- } catch (e) { console.log('[My] VIP查询失败', e) }
- },
-
- // 头像点击:已登录弹出选项(改头像/进VIP)
- onAvatarTap() {
- if (!this.data.isLoggedIn) { this.showLogin(); return }
- wx.showActionSheet({
- itemList: ['获取微信头像', '开通/管理VIP'],
- success: (res) => {
- if (res.tapIndex === 0) this.chooseAvatarFallback()
- if (res.tapIndex === 1) this.goToVip()
- }
- })
- },
-
- chooseAvatarFallback() {
- wx.chooseMedia({
- count: 1, mediaType: ['image'], sourceType: ['album', 'camera'],
- success: async (res) => {
- const tempPath = res.tempFiles[0].tempFilePath
- const userInfo = this.data.userInfo
- userInfo.avatar = tempPath
- this.setData({ userInfo })
- app.globalData.userInfo = userInfo
- wx.setStorageSync('userInfo', userInfo)
- try {
- await app.request('/api/user/update', { method: 'POST', data: { userId: userInfo.id, avatar: tempPath } })
- } catch (e) { console.log('头像同步失败', e) }
- wx.showToast({ title: '头像已更新', icon: 'success' })
- }
- })
- },
-
- goToVip() {
- if (!this.data.isLoggedIn) { this.showLogin(); return }
- wx.navigateTo({ url: '/pages/vip/vip' })
- },
-
- async handleWithdraw() {
- if (!this.data.isLoggedIn) { this.showLogin(); return }
- const amount = parseFloat(this.data.pendingEarnings)
- if (isNaN(amount) || amount <= 0) {
- wx.showToast({ title: '暂无可提现金额', icon: 'none' })
- return
- }
- wx.showModal({
- title: '申请提现',
- content: `确认提现 ¥${amount.toFixed(2)} ?`,
- success: async (res) => {
- if (!res.confirm) return
- wx.showLoading({ title: '提交中...', mask: true })
- try {
- const userId = app.globalData.userInfo?.id
- await app.request({ url: '/api/withdraw', method: 'POST', data: { userId, amount } })
- wx.hideLoading()
- wx.showToast({ title: '提现申请已提交', icon: 'success' })
- this.loadMyEarnings()
- } catch (e) {
- wx.hideLoading()
- wx.showToast({ title: e.message || '提现失败', icon: 'none' })
- }
- }
- })
- },
-
- // 阻止冒泡
- stopPropagation() {}
-})
diff --git a/归档/miniprogram/pages/my/my.json b/归档/miniprogram/pages/my/my.json
deleted file mode 100644
index e7696321..00000000
--- a/归档/miniprogram/pages/my/my.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "usingComponents": {},
- "enablePullDownRefresh": false,
- "backgroundTextStyle": "light",
- "backgroundColor": "#000000"
-}
diff --git a/归档/miniprogram/pages/my/my.wxml b/归档/miniprogram/pages/my/my.wxml
deleted file mode 100644
index ac0d2042..00000000
--- a/归档/miniprogram/pages/my/my.wxml
+++ /dev/null
@@ -1,246 +0,0 @@
-
-
-
-
-
-
- 我的
-
-
-
-
-
-
-
-
-
-
-
- --
- 已读章节
-
-
- --
- 推荐好友
-
-
- --
- 待领收益
-
-
-
-
-
-
-
-
-
-
- {{readCount}}
- 已读章节
-
-
- {{referralCount}}
- 推荐好友
-
-
- {{pendingEarnings > 0 ? '¥' + pendingEarnings : '--'}}
- 我的收益
-
-
-
-
-
-
-
-
-
-
-
-
- 👁️
- 阅读统计
-
-
-
- 📖
- {{readCount}}
- 已读章节
-
-
- ⏱️
- {{totalReadTime}}
- 阅读分钟
-
-
- 👥
- {{matchHistory}}
- 匹配伙伴
-
-
-
-
-
-
-
- 📖
- 最近阅读
-
-
-
-
- {{index + 1}}
- {{item.title}}
-
- 继续阅读
-
-
-
- 📖
- 暂无阅读记录
- 去阅读 →
-
-
-
-
-
-
-
-
-
-
- ✕
- 🔐
- 登录 Soul创业实验
- 登录后可购买章节、解锁更多内容
-
-
- 取消
-
-
- {{agreeProtocol ? '✓' : ''}}
- 我已阅读并同意
- 《用户协议》
- 和
- 《隐私政策》
-
-
-
-
-
-
-
- ✕
-
-
-
-
- 微信用户可点击自动填充昵称
-
-
-
- 取消
- 确定
-
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/my/my.wxss b/归档/miniprogram/pages/my/my.wxss
deleted file mode 100644
index bbdb848a..00000000
--- a/归档/miniprogram/pages/my/my.wxss
+++ /dev/null
@@ -1,1373 +0,0 @@
-/**
- * Soul创业实验 - 我的页面样式
- * 1:1还原Web版本UI
- */
-
-.page {
- min-height: 100vh;
- background: #000000;
- padding-bottom: 200rpx;
-}
-
-/* ===== 导航栏 ===== */
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(0, 0, 0, 0.9);
- backdrop-filter: blur(40rpx);
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.nav-content {
- height: 88rpx;
- display: flex;
- align-items: center;
- justify-content: flex-start;
- padding: 0 32rpx;
-}
-
-.nav-title {
- font-size: 36rpx;
- font-weight: 600;
-}
-
-.nav-title-left {
- font-size: 36rpx;
- font-weight: 600;
-}
-
-.brand-color {
- color: #00CED1;
-}
-
-.gold-color {
- color: #FFD700;
-}
-
-.pink-color {
- color: #E91E63;
-}
-
-.nav-placeholder {
- width: 100%;
-}
-
-/* ===== 用户卡片 ===== */
-.user-card {
- margin: 32rpx;
- padding: 32rpx;
-}
-.margin-partner-badge{
-}
-/* 创业伙伴按钮 - inline 布局 */
-.partner-badge {
- display: flex;
- align-items: center;
- gap: 6rpx;
- padding: 10rpx 18rpx;
- background: rgba(0, 206, 209, 0.15);
- border: 1rpx solid rgba(0, 206, 209, 0.3);
- border-radius: 20rpx;
- flex-shrink: 0;
- transition: all 0.2s;
- margin-top:-20px;
-}
-
-.partner-badge:active {
- background: rgba(0, 206, 209, 0.3);
- transform: scale(0.95);
-}
-
-.partner-icon {
- font-size: 18rpx;
- color: #00CED1;
- line-height: 1;
-}
-
-.partner-text {
- font-size: 20rpx;
- font-weight: 500;
- color: #00CED1;
- line-height: 1;
- white-space: nowrap;
-}
-
-.card-gradient {
- background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%);
- border-radius: 32rpx;
- border: 2rpx solid rgba(0, 206, 209, 0.2);
-}
-
-/* ===== 新版用户头部布局 ===== */
-.user-header-row {
- display: flex;
- align-items: center;
- gap: 20rpx;
- margin-bottom: 32rpx;
- width: 100%;
-}
-
-/* 头像容器 */
-.avatar-wrapper {
- position: relative;
- flex-shrink: 0;
- width: 120rpx;
- height: 120rpx;
-}
-
-/* 头像按钮样式 - 简化版 */
-.avatar-btn-simple {
- flex-shrink: 0;
- width: 60rpx!important;
- height: 120rpx;
- min-width: 120rpx;
- min-height: 120rpx;
- padding: 0;
- margin: 0;
- background: transparent !important;
- border: none;
- line-height: normal;
- border-radius: 50%;
- overflow: visible;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-.avatar-btn-simple::after { border: none; }
-
-.edit-icon-small {
- font-size: 24rpx;
- color: rgba(255,255,255,0.5);
- margin-left: 12rpx;
-}
-
-.avatar {
- width: 120rpx;
- height: 120rpx;
- border-radius: 50%;
- border: 3rpx solid #00CED1;
- display: flex;
- align-items: center;
- justify-content: center;
- background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, transparent 100%);
- overflow: hidden;
- box-sizing: border-box;
- flex-shrink: 0;
-}
-
-.avatar-img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.avatar-edit-hint {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 36rpx;
- height: 36rpx;
- background: #00CED1;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 3rpx solid #000;
-}
-
-.edit-icon {
- font-size: 20rpx;
- color: #000;
-}
-
-.avatar-empty {
- border-style: dashed;
- border-color: rgba(0, 206, 209, 0.5);
-}
-
-.avatar-icon {
- font-size: 64rpx;
- opacity: 0.3;
-}
-
-.avatar-text {
- font-size: 48rpx;
- font-weight: 700;
- color: #00CED1;
- line-height: 1;
-}
-
-.user-info-block {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 12rpx;
- min-width: 0;
-}
-
-.user-name-row {
- display: flex;
- align-items: center;
- gap: 8rpx;
- line-height: 1.3;
-}
-
-.user-name {
- font-size: 34rpx;
- font-weight: 600;
- color: #ffffff;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex: 1;
- min-width: 0;
-}
-
-.edit-name-icon {
- font-size: 24rpx;
- color: rgba(255,255,255,0.4);
- flex-shrink: 0;
-}
-
-.user-id-row {
- display: flex;
- align-items: center;
- gap: 8rpx;
- line-height: 1.3;
-}
-
-.user-id {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.4);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex: 1;
- min-width: 0;
-}
-
-.copy-icon {
- font-size: 22rpx;
- opacity: 0.5;
- flex-shrink: 0;
-}
-
-.user-wechat {
- font-size: 24rpx;
- color: #00CED1;
-}
-
-.user-badge-small {
- display: inline-flex;
- align-items: center;
- gap: 6rpx;
- padding: 6rpx 14rpx;
- background: rgba(0, 206, 209, 0.15);
- border: 1rpx solid rgba(0, 206, 209, 0.3);
- border-radius: 20rpx;
-}
-
-.badge-star {
- font-size: 18rpx;
-}
-
-.badge-label {
- font-size: 20rpx;
- color: #00CED1;
-}
-
-/* 兼容旧样式 */
-.user-header {
- display: flex;
- align-items: center;
- gap: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.user-info {
- flex: 1;
-}
-
-.login-btn {
- font-size: 36rpx;
- font-weight: 600;
- color: #00CED1;
-}
-
-.user-subtitle {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.3);
- display: block;
- margin-top: 4rpx;
-}
-
-/* ===== 未登录假资料样式 ===== */
-.avatar-placeholder {
- cursor: default;
-}
-
-.user-id-guest {
- color: rgba(255, 255, 255, 0.35) !important;
-}
-
-.btn-login-inline {
- flex-shrink: 0;
- margin-left: 16rpx;
- padding: 10rpx 24rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- color: #000;
- font-size: 24rpx;
- font-weight: 600;
- border-radius: 24rpx;
-}
-
-.user-name {
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
- display: block;
-}
-
-.user-id {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.3);
- display: block;
- margin-top: 4rpx;
-}
-
-.user-badge {
- padding: 8rpx 20rpx;
- background: rgba(0, 206, 209, 0.2);
- border: 2rpx solid rgba(0, 206, 209, 0.3);
- border-radius: 32rpx;
- display: flex;
- align-items: center;
- gap: 8rpx;
-}
-
-.badge-icon {
- font-size: 22rpx;
-}
-
-.badge-text {
- font-size: 22rpx;
- color: #00CED1;
-}
-
-.stats-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 16rpx;
- padding-top: 32rpx;
- border-top: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.stat-item {
- text-align: center;
- padding: 16rpx 8rpx;
-}
-
-.stat-value {
- font-size: 40rpx;
- font-weight: 700;
- display: block;
-}
-
-.stat-label {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===== 收益卡片 - 艺术化设计 ===== */
-.earnings-card {
- margin: 32rpx;
- padding: 32rpx;
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
- border-radius: 32rpx;
- border: 2rpx solid rgba(0, 206, 209, 0.2);
- position: relative;
- overflow: hidden;
- box-shadow: 0 16rpx 32rpx rgba(0, 0, 0, 0.3);
-}
-
-/* 背景装饰圆 */
-.bg-decoration {
- position: absolute;
- border-radius: 50%;
- z-index: 0;
-}
-
-.bg-decoration-gold {
- top: 0;
- right: 0;
- width: 256rpx;
- height: 256rpx;
- background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, transparent 100%);
- transform: translate(50%, -50%);
-}
-
-.bg-decoration-brand {
- bottom: 0;
- left: 0;
- width: 192rpx;
- height: 192rpx;
- background: linear-gradient(45deg, rgba(0, 206, 209, 0.1) 0%, transparent 100%);
- transform: translate(-50%, 50%);
-}
-
-.earnings-content {
- position: relative;
- z-index: 1;
-}
-
-/* 标题行 */
-.earnings-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 40rpx;
-}
-
-.earnings-title-wrap {
- display: flex;
- align-items: center;
- gap: 12rpx;
-}
-
-.earnings-icon {
- font-size: 32rpx;
-}
-
-.earnings-title {
- font-size: 30rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.earnings-link {
- display: flex;
- align-items: center;
- gap: 6rpx;
- padding: 8rpx 16rpx;
- background: rgba(0, 206, 209, 0.1);
- border-radius: 16rpx;
-}
-
-.earnings-link:active {
- background: rgba(0, 206, 209, 0.2);
-}
-
-.link-text {
- font-size: 24rpx;
- font-weight: 500;
-}
-
-.link-arrow {
- font-size: 20rpx;
- font-weight: 600;
-}
-
-/* 我的收益 - 刷新图标 */
-.earnings-refresh-wrap {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background: rgba(0, 206, 209, 0.15);
-}
-
-.earnings-refresh-wrap:active {
- background: rgba(0, 206, 209, 0.3);
-}
-
-.earnings-refresh-icon {
- font-size: 36rpx;
- font-weight: 600;
- color: #00CED1;
-}
-
-.earnings-refresh-spin {
- animation: earnings-spin 0.8s linear infinite;
-}
-
-@keyframes earnings-spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
-}
-
-/* 收益数据 */
-.earnings-data {
- display: flex;
- align-items: flex-end;
- gap: 48rpx;
- margin-bottom: 32rpx;
-}
-
-.earnings-main,
-.earnings-secondary {
- display: flex;
- flex-direction: column;
- gap: 8rpx;
-}
-
-.earnings-main {
- flex: 1;
-}
-
-.earnings-secondary {
- flex: 1;
-}
-
-.earnings-label {
- font-size: 24rpx;
- font-weight: 500;
- color: rgba(255, 255, 255, 0.6);
- letter-spacing: 0.5rpx;
-}
-
-.earnings-amount-large {
- font-size: 64rpx;
- font-weight: 700;
- line-height: 1;
- display: block;
-}
-
-.earnings-amount-medium {
- font-size: 48rpx;
- font-weight: 700;
- line-height: 1;
- color: #ffffff;
-}
-
-/* 渐变文字效果 */
-.gold-gradient {
- background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
- -webkit-background-clip: text;
- background-clip: text;
- -webkit-text-fill-color: transparent;
- color: transparent;
-}
-
-/* 操作按钮 */
-.earnings-action {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 16rpx;
- padding: 24rpx;
- background: linear-gradient(90deg, rgba(255, 215, 0, 0.9) 0%, rgba(255, 165, 0, 0.9) 100%);
- border-radius: 24rpx;
- font-weight: 700;
- box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
- margin-top: 8rpx;
-}
-
-.earnings-action:active {
- opacity: 0.85;
- transform: scale(0.98);
-}
-
-.action-icon {
- font-size: 32rpx;
-}
-
-.action-text {
- font-size: 30rpx;
- font-weight: 700;
- color: #000000;
- letter-spacing: 1rpx;
-}
-
-/* ===== 推广入口 ===== */
-.referral-card {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin: 32rpx;
- padding: 32rpx;
- background: linear-gradient(90deg, rgba(255, 215, 0, 0.1) 0%, #1c1c1e 100%);
- border: 2rpx solid rgba(255, 215, 0, 0.2);
- border-radius: 32rpx;
-}
-
-.referral-left {
- display: flex;
- align-items: center;
- gap: 24rpx;
-}
-
-.referral-icon {
- width: 80rpx;
- height: 80rpx;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 40rpx;
-}
-
-.gold-bg {
- background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
-}
-
-.referral-info {
- display: flex;
- flex-direction: column;
-}
-
-.referral-title {
- font-size: 28rpx;
- font-weight: 500;
- color: #ffffff;
-}
-
-.referral-desc {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.4);
- margin-top: 4rpx;
-}
-
-.referral-btn {
- padding: 16rpx 32rpx;
- background: rgba(255, 215, 0, 0.2);
- color: #FFD700;
- font-size: 26rpx;
- font-weight: 500;
- border-radius: 16rpx;
-}
-
-/* ===== Tab切换 ===== */
-.tab-bar-custom {
- display: flex;
- gap: 16rpx;
- margin: 32rpx;
-}
-
-.tab-item {
- flex: 1;
- padding: 20rpx;
- text-align: center;
- font-size: 28rpx;
- font-weight: 500;
- color: rgba(255, 255, 255, 0.6);
- background: #1c1c1e;
- border-radius: 24rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8rpx;
-}
-
-.tab-active {
- background: rgba(0, 206, 209, 0.2);
- color: #00CED1;
- border: 2rpx solid rgba(0, 206, 209, 0.3);
-}
-
-.tab-icon {
- font-size: 28rpx;
-}
-
-/* ===== Tab内容 ===== */
-.tab-content {
- padding: 0 32rpx;
- width: 100%;
- box-sizing: border-box;
-}
-
-/* ===== 菜单卡片 ===== */
-.card {
- background: #1c1c1e;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- overflow: hidden;
- margin: 0 0 24rpx 0;
- width: 100%;
- box-sizing: border-box;
-}
-
-.menu-card {
- padding: 0;
- width: 100%;
- box-sizing: border-box;
-}
-
-.menu-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 28rpx 32rpx;
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.menu-item:last-child {
- border-bottom: none;
-}
-
-.menu-item:active {
- background: rgba(255, 255, 255, 0.05);
-}
-
-.menu-left {
- display: flex;
- align-items: center;
- gap: 24rpx;
-}
-
-.menu-icon {
- width: 52rpx;
- height: 52rpx;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 32rpx;
- flex-shrink: 0;
-}
-
-/* 有背景的图标样式 */
-.icon-brand,
-.icon-gold,
-.icon-gray {
- width: 52rpx;
- height: 52rpx;
- font-size: 20rpx;
-}
-
-.icon-brand {
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
-}
-
-.icon-gold {
- background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
-}
-
-.icon-gray {
- background: rgba(128, 128, 128, 0.2);
-}
-
-.menu-title {
- font-size: 28rpx;
- color: #ffffff;
-}
-
-.menu-right {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-
-.menu-count {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-.menu-badge {
- font-size: 26rpx;
- font-weight: 500;
-}
-
-.menu-arrow {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.3);
-}
-
-/* ===== 统计卡片 ===== */
-.stats-card {
- padding: 32rpx;
-}
-
-.card-title {
- display: flex;
- align-items: center;
- gap: 16rpx;
- font-size: 28rpx;
- font-weight: 500;
- color: #ffffff;
- margin-bottom: 24rpx;
-}
-
-.stats-row {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 24rpx;
-}
-
-/* 两列布局(当找伙伴功能关闭时) */
-.stats-row-two-cols {
- grid-template-columns: repeat(2, 1fr) !important;
-}
-
-.stat-box {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 24rpx;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 24rpx;
-}
-
-.stat-icon {
- font-size: 36rpx;
- margin-bottom: 8rpx;
-}
-
-.stat-num {
- font-size: 32rpx;
- font-weight: 700;
- color: #ffffff;
-}
-
-.stat-text {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
- margin-top: 4rpx;
-}
-
-/* ===== 最近阅读 ===== */
-.recent-card {
- padding: 32rpx;
-}
-
-.recent-list {
- display: flex;
- flex-direction: column;
- gap: 16rpx;
-}
-
-.recent-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 24rpx;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 24rpx;
-}
-
-.recent-left {
- display: flex;
- align-items: center;
- gap: 24rpx;
-}
-
-.recent-index {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.3);
-}
-
-.recent-title {
- font-size: 26rpx;
- color: #ffffff;
-}
-
-.recent-btn {
- font-size: 24rpx;
- color: #00CED1;
-}
-
-/* ===== 空状态 ===== */
-.empty-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 48rpx;
-}
-
-.empty-icon {
- font-size: 64rpx;
- opacity: 0.5;
- margin-bottom: 16rpx;
-}
-
-.empty-text {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-.empty-btn {
- margin-top: 16rpx;
- font-size: 26rpx;
- color: #00CED1;
-}
-
-/* ===== 匹配记录 ===== */
-.match-card {
- padding: 32rpx;
-}
-
-/* ===== 登录弹窗 ===== */
-.modal-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.6);
- backdrop-filter: blur(20rpx);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- padding: 48rpx;
-}
-
-.modal-content {
- width: 100%;
- max-width: 640rpx;
- background: #1c1c1e;
- border-radius: 32rpx;
- padding: 48rpx;
- position: relative;
-}
-
-.modal-close {
- position: absolute;
- top: 24rpx;
- right: 24rpx;
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.login-icon {
- font-size: 96rpx;
- text-align: center;
- display: block;
- margin-bottom: 24rpx;
-}
-
-.login-title {
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
- text-align: center;
- display: block;
- margin-bottom: 16rpx;
-}
-
-.login-desc {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
- text-align: center;
- display: block;
- margin-bottom: 48rpx;
-}
-
-.btn-wechat {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 16rpx;
- padding: 28rpx;
- background: #07C160;
- color: #ffffff;
- font-size: 28rpx;
- font-weight: 500;
- border-radius: 24rpx;
- margin-bottom: 24rpx;
- border: none;
-}
-
-.btn-wechat::after {
- border: none;
-}
-
-.btn-wechat-icon {
- width: 40rpx;
- height: 40rpx;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 8rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 24rpx;
-}
-
-.btn-phone {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 16rpx;
- padding: 28rpx;
- background: rgba(255, 255, 255, 0.1);
- color: #ffffff;
- font-size: 28rpx;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.btn-phone::after {
- border: none;
-}
-
-.btn-phone-icon {
- font-size: 32rpx;
-}
-
-/* 登录弹窗内取消按钮 */
-.login-modal-cancel {
- margin-top: 24rpx;
- padding: 24rpx;
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.5);
- text-align: center;
-}
-
-/* 协议勾选行(审核:用户须主动勾选,协议可点击查看) */
-.login-agree-row {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: center;
- margin-top: 32rpx;
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.5);
-}
-.agree-checkbox {
- width: 32rpx;
- height: 32rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.5);
- border-radius: 6rpx;
- margin-right: 12rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 22rpx;
- color: #fff;
- flex-shrink: 0;
-}
-.agree-checked {
- background: #00CED1;
- border-color: #00CED1;
-}
-.agree-text {
- color: rgba(255, 255, 255, 0.6);
-}
-.agree-link {
- color: #00CED1;
- text-decoration: underline;
- padding: 0 4rpx;
-}
-.btn-wechat-disabled {
- opacity: 0.6;
-}
-
-/* ===== 底部留白 ===== */
-.bottom-space {
- height: 40rpx;
-}
-
-/* ===== 推广入口卡片 ===== */
-.promo-entry-card {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin: 24rpx 24rpx 0;
- padding: 32rpx;
- background: linear-gradient(135deg, rgba(255, 215, 0, 0.15) 0%, rgba(255, 165, 0, 0.1) 100%);
- border: 2rpx solid rgba(255, 215, 0, 0.3);
- border-radius: 24rpx;
-}
-
-.promo-entry-left {
- display: flex;
- align-items: center;
- gap: 20rpx;
-}
-
-.promo-entry-icon {
- width: 80rpx;
- height: 80rpx;
- background: rgba(255, 215, 0, 0.2);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 36rpx;
-}
-
-.promo-entry-info {
- display: flex;
- flex-direction: column;
- gap: 4rpx;
-}
-
-.promo-entry-title {
- font-size: 30rpx;
- color: #ffffff;
- font-weight: 600;
-}
-
-.promo-entry-desc {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.5);
-}
-
-.promo-entry-right {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-
-.promo-entry-earnings {
- font-size: 32rpx;
- color: #FFD700;
- font-weight: 600;
-}
-
-.promo-entry-arrow {
- font-size: 28rpx;
- color: #FFD700;
-}
-
-/* ===== 修改昵称弹窗 ===== */
-.nickname-modal {
- width: 600rpx;
- max-width: 90%;
-}
-
-.modal-header {
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-bottom: 40rpx;
-}
-
-.modal-icon {
- font-size: 60rpx;
- margin-bottom: 16rpx;
-}
-
-.modal-title {
- font-size: 32rpx;
- color: #ffffff;
- font-weight: 600;
-}
-
-.nickname-input-wrap {
- margin-bottom: 40rpx;
-}
-
-.nickname-input {
- width: 100%;
- height: 88rpx;
- padding: 0 24rpx;
- background: rgba(255, 255, 255, 0.05);
- border: 2rpx solid rgba(56, 189, 172, 0.3);
- border-radius: 12rpx;
- font-size: 28rpx;
- color: #ffffff;
- box-sizing: border-box;
-}
-
-.nickname-placeholder {
- color: rgba(255, 255, 255, 0.3);
-}
-
-.input-tip {
- display: block;
- margin-top: 12rpx;
- font-size: 22rpx;
- color: rgba(56, 189, 172, 0.6);
- text-align: center;
-}
-
-.modal-actions {
- display: flex;
- gap: 20rpx;
-}
-
-.modal-btn {
- flex: 1;
- height: 80rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 12rpx;
- font-size: 28rpx;
- font-weight: 500;
- transition: all 0.3s;
-}
-
-.modal-btn-cancel {
- background: rgba(255, 255, 255, 0.05);
- color: rgba(255, 255, 255, 0.5);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.modal-btn-confirm {
- background: linear-gradient(135deg, #38bdac 0%, #2da396 100%);
- color: #ffffff;
- box-shadow: 0 8rpx 24rpx rgba(56, 189, 172, 0.3);
-}
-
-/* 待确认收款 */
-.pending-confirm-card {
- margin: 32rpx;
- padding: 28rpx 32rpx;
- background: rgba(76, 175, 80, 0.08);
- border: 2rpx solid rgba(76, 175, 80, 0.25);
- border-radius: 24rpx;
-}
-.pending-confirm-header { margin-bottom: 20rpx; }
-.pending-confirm-title { font-size: 28rpx; font-weight: 600; color: #fff; display: block; }
-.pending-confirm-desc { font-size: 24rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
-.pending-confirm-list { display: flex; flex-direction: column; gap: 16rpx; }
-.pending-confirm-item {
- display: flex; align-items: center; justify-content: space-between;
- padding: 20rpx 24rpx; background: rgba(28,28,30,0.6); border-radius: 16rpx;
-}
-.pending-confirm-info { display: flex; flex-direction: column; gap: 4rpx; }
-.pending-confirm-amount { font-size: 32rpx; font-weight: 600; color: #4CAF50; }
-.pending-confirm-time { font-size: 22rpx; color: rgba(255,255,255,0.5); }
-.pending-confirm-btn {
- padding: 16rpx 32rpx;
- background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%);
- color: #fff; font-size: 26rpx; font-weight: 500; border-radius: 20rpx;
-}
-
-/* ===== 收益面板(内嵌) ===== */
-.earnings-inline {
- margin: 0 32rpx 24rpx;
- padding: 24rpx 28rpx;
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
- border-radius: 24rpx;
- border: 2rpx solid rgba(0, 206, 209, 0.15);
-}
-.earnings-inline-row {
- display: flex;
- align-items: center;
- gap: 16rpx;
-}
-.earnings-inline-item {
- display: flex;
- flex-direction: column;
- gap: 4rpx;
- flex: 1;
-}
-.earnings-inline-label {
- font-size: 22rpx;
- color: rgba(255,255,255,0.5);
-}
-.earnings-inline-val {
- font-size: 36rpx;
- font-weight: 700;
- color: #fff;
-}
-.earnings-inline-divider {
- width: 2rpx;
- height: 48rpx;
- background: rgba(255,255,255,0.1);
- flex-shrink: 0;
-}
-.earnings-inline-btn {
- padding: 12rpx 28rpx;
- background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
- border-radius: 20rpx;
- font-size: 26rpx;
- font-weight: 600;
- color: #000;
- flex-shrink: 0;
-}
-.earnings-inline-refresh {
- width: 48rpx;
- height: 48rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- color: #00CED1;
- flex-shrink: 0;
-}
-.pending-inline {
- margin-top: 16rpx;
- padding-top: 16rpx;
- border-top: 1rpx solid rgba(255,255,255,0.08);
-}
-.pending-inline-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8rpx 0;
-}
-.pending-inline-text {
- font-size: 24rpx;
- color: #4CAF50;
-}
-.pending-inline-btn {
- padding: 8rpx 20rpx;
- background: #4CAF50;
- color: #fff;
- font-size: 22rpx;
- border-radius: 12rpx;
-}
-
-/* VIP头像标识 */
-.avatar-wrap { position: relative; }
-.avatar-vip { border: 4rpx solid #FFD700 !important; box-shadow: 0 0 20rpx rgba(255,215,0,0.4); }
-.vip-badge { position: absolute; bottom: -4rpx; right: -4rpx; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 16rpx; font-weight: bold; padding: 2rpx 8rpx; border-radius: 10rpx; line-height: 1.4; }
-.vip-badge-gray { background: rgba(255,255,255,0.2); color: rgba(255,255,255,0.5); }
-
-/* 会员权益小标签 */
-.vip-tags-row {
- display: flex;
- gap: 6rpx;
- margin-left: 8rpx;
- flex-shrink: 0;
-}
-.vip-tag-mini {
- padding: 2rpx 10rpx;
- font-size: 18rpx;
- border-radius: 8rpx;
- background: rgba(255,255,255,0.08);
- color: rgba(255,255,255,0.3);
- border: 1rpx solid rgba(255,255,255,0.1);
-}
-.vip-tag-active {
- background: rgba(255,215,0,0.15);
- color: #FFD700;
- border-color: rgba(255,215,0,0.3);
-}
-
-/* 阅读统计 */
-.stats-card { padding: 24rpx 28rpx; }
-.stats-row { display: flex; gap: 16rpx; margin-top: 16rpx; }
-.stat-box {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 6rpx;
- padding: 20rpx 12rpx;
- border-radius: 16rpx;
- background: rgba(139,92,246,0.06);
-}
-.stat-icon { font-size: 32rpx; }
-.stat-num { font-size: 36rpx; font-weight: bold; color: #fff; }
-.stat-text { font-size: 22rpx; color: rgba(255,255,255,0.5); }
-.pink-color { color: #ec4899; }
-
-/* 成为会员小按钮 */
-.become-vip-chip {
- display: inline-flex;
- align-items: center;
- gap: 4rpx;
- padding: 4rpx 14rpx;
- border-radius: 20rpx;
- background: linear-gradient(135deg, rgba(245,166,35,.15), rgba(245,166,35,.08));
- border: 1rpx solid rgba(245,166,35,.3);
- margin-left: 8rpx;
-}
-.chip-star { font-size: 18rpx; }
-.chip-text { font-size: 20rpx; color: #f5a623; font-weight: 600; white-space: nowrap; }
diff --git a/归档/miniprogram/pages/privacy/privacy.js b/归档/miniprogram/pages/privacy/privacy.js
deleted file mode 100644
index 0cd665db..00000000
--- a/归档/miniprogram/pages/privacy/privacy.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Soul创业派对 - 隐私政策
- * 审核要求:登录前可点击《隐私政策》查看完整内容
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight || 44
- })
- },
-
- goBack() {
- wx.navigateBack()
- }
-})
diff --git a/归档/miniprogram/pages/privacy/privacy.json b/归档/miniprogram/pages/privacy/privacy.json
deleted file mode 100644
index f567904d..00000000
--- a/归档/miniprogram/pages/privacy/privacy.json
+++ /dev/null
@@ -1 +0,0 @@
-{"usingComponents":{},"navigationStyle":"custom","navigationBarTitleText":"隐私政策"}
diff --git a/归档/miniprogram/pages/privacy/privacy.wxml b/归档/miniprogram/pages/privacy/privacy.wxml
deleted file mode 100644
index cf414ad7..00000000
--- a/归档/miniprogram/pages/privacy/privacy.wxml
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
- ←
- 隐私政策
-
-
-
-
-
-
- Soul创业实验 隐私政策
- 更新日期:以小程序内展示为准
-
- 一、信息收集
- 为向您提供阅读、购买、推广与提现等服务,我们可能收集:微信昵称、头像、openId、手机号(在您授权时)、订单与收益相关数据。我们仅在法律允许及您同意的范围内收集必要信息。
-
- 二、信息使用
- 所收集信息用于账号识别、订单与收益结算、客服与纠纷处理、产品优化及法律义务履行,不会用于与上述目的无关的营销或向第三方出售。
-
- 三、信息存储与安全
- 数据存储在中华人民共和国境内,我们采取合理技术和管理措施保障数据安全,防止未经授权的访问、泄露或篡改。
-
- 四、信息共享
- 未经您同意,我们不会将您的个人信息共享给第三方,法律法规要求或为完成支付、提现等必要合作除外(如微信支付、微信商家转账)。
-
- 五、您的权利
- 您有权查询、更正、删除您的个人信息,或撤回授权。部分权限撤回可能影响相关功能使用。您可通过小程序设置或联系我们就隐私问题提出请求。
-
- 六、未成年人
- 如您为未成年人,请在监护人同意下使用本服务。我们不会主动收集未成年人个人信息。
-
- 七、政策更新
- 我们可能适时更新本政策,更新后将通过小程序内公示等方式通知您。继续使用即视为接受更新后的政策。
-
- 八、联系我们
- 如有隐私相关疑问或投诉,请通过小程序内「关于作者」或 Soul 派对房与我们联系。
-
-
-
diff --git a/归档/miniprogram/pages/privacy/privacy.wxss b/归档/miniprogram/pages/privacy/privacy.wxss
deleted file mode 100644
index 08fadc43..00000000
--- a/归档/miniprogram/pages/privacy/privacy.wxss
+++ /dev/null
@@ -1,11 +0,0 @@
-.page { min-height: 100vh; background: #000; }
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.95); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-back { width: 72rpx; height: 72rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: #fff; }
-.nav-title { font-size: 36rpx; font-weight: 600; color: #00CED1; }
-.nav-placeholder { width: 72rpx; }
-.content { height: calc(100vh - 132rpx); padding: 32rpx; box-sizing: border-box; }
-.doc-card { background: #1c1c1e; border-radius: 24rpx; padding: 40rpx; border: 2rpx solid rgba(0,206,209,0.2); }
-.doc-title { font-size: 34rpx; font-weight: 700; color: #fff; display: block; margin-bottom: 16rpx; }
-.doc-update { font-size: 24rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 32rpx; }
-.doc-section { font-size: 28rpx; font-weight: 600; color: #00CED1; display: block; margin: 24rpx 0 12rpx; }
-.doc-p { font-size: 26rpx; color: rgba(255,255,255,0.85); line-height: 1.75; display: block; margin-bottom: 16rpx; }
diff --git a/归档/miniprogram/pages/purchases/purchases.js b/归档/miniprogram/pages/purchases/purchases.js
deleted file mode 100644
index c458ad74..00000000
--- a/归档/miniprogram/pages/purchases/purchases.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * Soul创业实验 - 订单页
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- orders: [],
- loading: true
- },
-
- onLoad() {
- this.setData({ statusBarHeight: app.globalData.statusBarHeight })
- this.loadOrders()
- },
-
- async loadOrders() {
- this.setData({ loading: true })
- try {
- const userId = app.globalData.userInfo?.id
- if (userId) {
- const res = await app.request(`/api/orders?userId=${userId}`)
- if (res && res.success && res.data) {
- const orders = (res.data || []).map(item => ({
- id: item.id || item.order_sn,
- sectionId: item.product_id || item.section_id,
- title: item.product_name || `章节 ${item.product_id || ''}`,
- amount: item.amount || 0,
- status: item.status || 'completed',
- createTime: item.created_at ? new Date(item.created_at).toLocaleDateString() : '--'
- }))
- this.setData({ orders })
- return
- }
- }
- const purchasedSections = app.globalData.purchasedSections || []
- const orders = purchasedSections.map((id, index) => ({
- id: `order_${index}`,
- sectionId: id,
- title: `章节 ${id}`,
- amount: 1,
- status: 'completed',
- createTime: new Date(Date.now() - index * 86400000).toLocaleDateString()
- }))
- this.setData({ orders })
- } catch (e) {
- console.error('加载订单失败:', e)
- const purchasedSections = app.globalData.purchasedSections || []
- this.setData({
- orders: purchasedSections.map((id, i) => ({
- id: `order_${i}`, sectionId: id, title: `章节 ${id}`, amount: 1, status: 'completed',
- createTime: new Date(Date.now() - i * 86400000).toLocaleDateString()
- }))
- })
- } finally {
- this.setData({ loading: false })
- }
- },
-
- goToRead(e) {
- const id = e.currentTarget.dataset.id
- wx.navigateTo({ url: `/pages/read/read?id=${id}` })
- },
-
- goBack() { wx.navigateBack() }
-})
diff --git a/归档/miniprogram/pages/purchases/purchases.json b/归档/miniprogram/pages/purchases/purchases.json
deleted file mode 100644
index e90e9960..00000000
--- a/归档/miniprogram/pages/purchases/purchases.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom"
-}
diff --git a/归档/miniprogram/pages/purchases/purchases.wxml b/归档/miniprogram/pages/purchases/purchases.wxml
deleted file mode 100644
index 0c5942cd..00000000
--- a/归档/miniprogram/pages/purchases/purchases.wxml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
- ←
- 我的订单
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{item.title}}
- {{item.createTime}}
-
-
- ¥{{item.amount}}
- 已完成
-
-
-
-
-
- 📦
- 暂无订单
-
-
-
diff --git a/归档/miniprogram/pages/purchases/purchases.wxss b/归档/miniprogram/pages/purchases/purchases.wxss
deleted file mode 100644
index c2f6cf3e..00000000
--- a/归档/miniprogram/pages/purchases/purchases.wxss
+++ /dev/null
@@ -1,21 +0,0 @@
-.page { min-height: 100vh; background: #000; }
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-back { width: 72rpx; height: 72rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: #fff; }
-.nav-title { font-size: 36rpx; font-weight: 600; color: #00CED1; }
-.nav-placeholder { width: 72rpx; }
-.content { padding: 32rpx; }
-.loading { display: flex; flex-direction: column; gap: 24rpx; }
-.skeleton { height: 120rpx; background: linear-gradient(90deg, #1c1c1e 25%, #2c2c2e 50%, #1c1c1e 75%); background-size: 200% 100%; animation: skeleton 1.5s ease-in-out infinite; border-radius: 24rpx; }
-@keyframes skeleton { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
-.orders-list { display: flex; flex-direction: column; gap: 16rpx; }
-.order-item { display: flex; align-items: center; justify-content: space-between; padding: 24rpx; background: #1c1c1e; border-radius: 24rpx; }
-.order-item:active { background: #2c2c2e; }
-.order-info { flex: 1; }
-.order-title { font-size: 28rpx; color: #fff; display: block; margin-bottom: 8rpx; }
-.order-time { font-size: 22rpx; color: rgba(255,255,255,0.4); }
-.order-right { text-align: right; }
-.order-amount { font-size: 28rpx; font-weight: 600; color: #00CED1; display: block; margin-bottom: 4rpx; }
-.order-status { font-size: 22rpx; color: rgba(255,255,255,0.4); }
-.empty { display: flex; flex-direction: column; align-items: center; padding: 96rpx; }
-.empty-icon { font-size: 96rpx; margin-bottom: 24rpx; opacity: 0.5; }
-.empty-text { font-size: 28rpx; color: rgba(255,255,255,0.4); }
diff --git a/归档/miniprogram/pages/read/read.js b/归档/miniprogram/pages/read/read.js
deleted file mode 100644
index 6e9fface..00000000
--- a/归档/miniprogram/pages/read/read.js
+++ /dev/null
@@ -1,1196 +0,0 @@
-/**
- * Soul创业派对 - 阅读页(标准流程版)
- * 开发: 卡若
- * 技术支持: 存客宝
- *
- * 更新: 2026-02-04
- * - 引入权限管理器(chapterAccessManager)统一权限判断
- * - 引入阅读追踪器(readingTracker)记录阅读进度、时长、是否读完
- * - 使用状态机(accessState)规范权限流转
- * - 异常统一保守处理,避免误解锁
- */
-
-import accessManager from '../../utils/chapterAccessManager'
-import readingTracker from '../../utils/readingTracker'
-
-const app = getApp()
-
-Page({
- data: {
- // 系统信息
- statusBarHeight: 44,
- navBarHeight: 88,
-
- // 章节信息
- sectionId: '',
- section: null,
- partTitle: '',
- chapterTitle: '',
-
- // 内容
- content: '',
- previewContent: '',
- contentParagraphs: [],
- previewParagraphs: [],
- loading: true,
-
- // 【新增】权限状态机(替代 canAccess)
- // unknown: 加载中 | free: 免费 | locked_not_login: 未登录 | locked_not_purchased: 未购买 | unlocked_purchased: 已购买 | error: 错误
- accessState: 'unknown',
-
- // 用户状态
- isLoggedIn: false,
- hasFullBook: false,
- canAccess: false, // 保留兼容性,从 accessState 派生
- purchasedCount: 0,
-
- // 阅读进度
- readingProgress: 0,
- showPaywall: false,
-
- // 上一篇/下一篇
- prevSection: null,
- nextSection: null,
-
- // 价格
- sectionPrice: 1,
- fullBookPrice: 9.9,
- totalSections: 62,
-
- // 弹窗
- showShareModal: false,
- showLoginModal: false,
- agreeProtocol: false,
- showPosterModal: false,
- isPaying: false,
- isGeneratingPoster: false,
-
- // 免费章节
- freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3']
- },
-
- async onLoad(options) {
- const { id, ref } = options
-
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight,
- navBarHeight: app.globalData.navBarHeight,
- sectionId: id,
- loading: true,
- accessState: 'unknown'
- })
-
- // 处理推荐码绑定(异步不阻塞)
- if (ref) {
- console.log('[Read] 检测到推荐码:', ref)
- wx.setStorageSync('referral_code', ref)
- app.handleReferralCode({ query: { ref } })
- }
-
- try {
- // 【标准流程】1. 拉取最新配置(免费列表、价格)
- const config = await accessManager.fetchLatestConfig()
- this.setData({
- freeIds: config.freeChapters,
- sectionPrice: config.prices?.section ?? 1,
- fullBookPrice: config.prices?.fullbook ?? 9.9
- })
-
- // 【标准流程】2. 确定权限状态
- const accessState = await accessManager.determineAccessState(id, config.freeChapters)
- const canAccess = accessManager.canAccessFullContent(accessState)
-
- this.setData({
- accessState,
- canAccess,
- isLoggedIn: !!app.globalData.userInfo?.id,
- showPaywall: !canAccess
- })
-
- // 【标准流程】3. 加载内容
- await this.loadContent(id, accessState)
-
- // 【标准流程】4. 如果有权限,初始化阅读追踪
- if (canAccess) {
- readingTracker.init(id)
- }
-
- // 5. 加载导航
- this.loadNavigation(id)
-
- } catch (e) {
- console.error('[Read] 初始化失败:', e)
- wx.showToast({ title: '加载失败,请重试', icon: 'none' })
- this.setData({ accessState: 'error', loading: false })
- } finally {
- this.setData({ loading: false })
- }
- },
-
- // 从后端加载免费章节配置
- onPageScroll(e) {
- // 只在有权限时追踪阅读进度
- if (!accessManager.canAccessFullContent(this.data.accessState)) {
- return
- }
-
- // 获取滚动信息并更新追踪器
- const query = wx.createSelectorQuery()
- query.select('.page').boundingClientRect()
- query.selectViewport().scrollOffset()
- query.exec((res) => {
- if (res[0] && res[1]) {
- const scrollInfo = {
- scrollTop: res[1].scrollTop,
- scrollHeight: res[0].height,
- clientHeight: res[1].height
- }
-
- // 计算进度条显示(用于 UI)
- const totalScrollable = scrollInfo.scrollHeight - scrollInfo.clientHeight
- const progress = totalScrollable > 0
- ? Math.min((scrollInfo.scrollTop / totalScrollable) * 100, 100)
- : 0
- this.setData({ readingProgress: progress })
-
- // 更新阅读追踪器(记录最大进度、判断是否读完)
- readingTracker.updateProgress(scrollInfo)
- }
- })
- },
-
- // 【重构】加载章节内容(专注于内容加载,权限判断已在 onLoad 中由 accessManager 完成)
- async loadContent(id, accessState) {
- try {
- const section = this.getSectionInfo(id)
- const sectionPrice = this.data.sectionPrice ?? 1
- if (section.price === undefined || section.price === null) {
- section.price = sectionPrice
- }
- this.setData({ section })
-
- // 从 API 获取内容
- const res = await app.request({ url: `/api/miniprogram/book/chapter/${id}`, silent: true })
-
- if (res && res.content) {
- const lines = res.content.split('\n').filter(line => line.trim())
- const previewCount = Math.ceil(lines.length * 0.2)
-
- this.setData({
- content: res.content,
- contentParagraphs: lines,
- previewParagraphs: lines.slice(0, previewCount),
- partTitle: res.partTitle || '',
- chapterTitle: res.chapterTitle || ''
- })
-
- // 如果有权限,标记为已读
- if (accessManager.canAccessFullContent(accessState)) {
- app.markSectionAsRead(id)
- }
- }
- } catch (e) {
- console.error('[Read] 加载内容失败:', e)
- // 尝试从本地缓存加载
- const cacheKey = `chapter_${id}`
- try {
- const cached = wx.getStorageSync(cacheKey)
- if (cached && cached.content) {
- const lines = cached.content.split('\n').filter(line => line.trim())
- const previewCount = Math.ceil(lines.length * 0.2)
-
- this.setData({
- content: cached.content,
- contentParagraphs: lines,
- previewParagraphs: lines.slice(0, previewCount)
- })
- console.log('[Read] 从本地缓存加载成功')
- }
- } catch (cacheErr) {
- console.warn('[Read] 本地缓存也失败:', cacheErr)
- }
- throw e
- }
- },
-
- // 获取章节信息
- getSectionInfo(id) {
- // 特殊章节
- if (id === 'preface') {
- return { id: 'preface', title: '为什么我每天早上6点在Soul开播?', isFree: true, price: 0 }
- }
- if (id === 'epilogue') {
- return { id: 'epilogue', title: '这本书的真实目的', isFree: true, price: 0 }
- }
- if (id.startsWith('appendix')) {
- const appendixTitles = {
- 'appendix-1': 'Soul派对房精选对话',
- 'appendix-2': '创业者自检清单',
- 'appendix-3': '本书提到的工具和资源'
- }
- return { id, title: appendixTitles[id] || '附录', isFree: true, price: 0 }
- }
-
- // 普通章节
- return {
- id: id,
- title: this.getSectionTitle(id),
- isFree: id === '1.1',
- price: 1
- }
- },
-
- // 获取章节标题
- getSectionTitle(id) {
- const titles = {
- '1.1': '荷包:电动车出租的被动收入模式',
- '1.2': '老墨:资源整合高手的社交方法',
- '1.3': '笑声背后的MBTI',
- '1.4': '人性的三角结构:利益、情感、价值观',
- '1.5': '沟通差的问题:为什么你说的别人听不懂',
- '2.1': '相亲故事:你以为找的是人,实际是在找模式',
- '2.2': '找工作迷茫者:为什么简历解决不了人生',
- '2.3': '撸运费险:小钱困住大脑的真实心理',
- '2.4': '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力',
- '2.5': '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒',
- '3.1': '3000万流水如何跑出来(退税模式解析)',
- '8.1': '流量杠杆:抖音、Soul、飞书',
- '9.14': '大健康私域:一个月150万的70后'
- }
- return titles[id] || `章节 ${id}`
- },
-
- // 加载内容 - 三级降级方案:API → 本地缓存 → 备用API
- async loadContent(id) {
- const cacheKey = `chapter_${id}`
-
- // 1. 优先从API获取
- try {
- const res = await this.fetchChapterWithTimeout(id, 5000)
- if (res && res.content) {
- this.setChapterContent(res)
- // 成功后缓存到本地
- wx.setStorageSync(cacheKey, res)
- console.log('[Read] 从API加载成功:', id)
- return
- }
- } catch (e) {
- console.warn('[Read] API加载失败,尝试本地缓存:', e.message)
- }
-
- // 2. API失败,尝试从本地缓存读取
- try {
- const cached = wx.getStorageSync(cacheKey)
- if (cached && cached.content) {
- this.setChapterContent(cached)
- console.log('[Read] 从本地缓存加载成功:', id)
- // 后台静默刷新
- this.silentRefresh(id)
- return
- }
- } catch (e) {
- console.warn('[Read] 本地缓存读取失败')
- }
-
- // 3. 都失败,显示加载中并持续重试
- this.setData({
- contentParagraphs: ['章节内容加载中...', '正在尝试连接服务器,请稍候...'],
- previewParagraphs: ['章节内容加载中...']
- })
-
- // 延迟重试(最多3次)
- this.retryLoadContent(id, 3)
- },
-
- // 带超时的章节请求
- fetchChapterWithTimeout(id, timeout = 5000) {
- return new Promise((resolve, reject) => {
- const timer = setTimeout(() => {
- reject(new Error('请求超时'))
- }, timeout)
-
- app.request(`/api/miniprogram/book/chapter/${id}`)
- .then(res => {
- clearTimeout(timer)
- resolve(res)
- })
- .catch(err => {
- clearTimeout(timer)
- reject(err)
- })
- })
- },
-
- // 设置章节内容
- setChapterContent(res) {
- const lines = res.content.split('\n').filter(line => line.trim())
- const previewCount = Math.ceil(lines.length * 0.2)
-
- this.setData({
- content: res.content,
- previewContent: lines.slice(0, previewCount).join('\n'),
- contentParagraphs: lines,
- previewParagraphs: lines.slice(0, previewCount),
- partTitle: res.partTitle || '',
- chapterTitle: res.chapterTitle || ''
- })
- },
-
- // 静默刷新(后台更新缓存)
- async silentRefresh(id) {
- try {
- const res = await this.fetchChapterWithTimeout(id, 10000)
- if (res && res.content) {
- wx.setStorageSync(`chapter_${id}`, res)
- console.log('[Read] 后台缓存更新成功:', id)
- }
- } catch (e) {
- // 静默失败不处理
- }
- },
-
- // 重试加载
- retryLoadContent(id, maxRetries, currentRetry = 0) {
- if (currentRetry >= maxRetries) {
- this.setData({
- contentParagraphs: ['内容加载失败', '请检查网络连接后下拉刷新重试'],
- previewParagraphs: ['内容加载失败']
- })
- return
- }
-
- setTimeout(async () => {
- try {
- const res = await this.fetchChapterWithTimeout(id, 8000)
- if (res && res.content) {
- this.setChapterContent(res)
- wx.setStorageSync(`chapter_${id}`, res)
- console.log('[Read] 重试成功:', id, '第', currentRetry + 1, '次')
- return
- }
- } catch (e) {
- console.warn('[Read] 重试失败,继续重试:', currentRetry + 1)
- }
- this.retryLoadContent(id, maxRetries, currentRetry + 1)
- }, 2000 * (currentRetry + 1))
- },
-
-
- // 加载导航
- loadNavigation(id) {
- const sectionOrder = [
- 'preface', '1.1', '1.2', '1.3', '1.4', '1.5',
- '2.1', '2.2', '2.3', '2.4', '2.5',
- '3.1', '3.2', '3.3', '3.4',
- '4.1', '4.2', '4.3', '4.4', '4.5',
- '5.1', '5.2', '5.3', '5.4', '5.5',
- '6.1', '6.2', '6.3', '6.4',
- '7.1', '7.2', '7.3', '7.4', '7.5',
- '8.1', '8.2', '8.3', '8.4', '8.5', '8.6',
- '9.1', '9.2', '9.3', '9.4', '9.5', '9.6', '9.7', '9.8', '9.9', '9.10', '9.11', '9.12', '9.13', '9.14',
- '10.1', '10.2', '10.3', '10.4',
- '11.1', '11.2', '11.3', '11.4', '11.5',
- 'epilogue'
- ]
-
- const currentIndex = sectionOrder.indexOf(id)
- const prevId = currentIndex > 0 ? sectionOrder[currentIndex - 1] : null
- const nextId = currentIndex < sectionOrder.length - 1 ? sectionOrder[currentIndex + 1] : null
-
- this.setData({
- prevSection: prevId ? { id: prevId, title: this.getSectionTitle(prevId) } : null,
- nextSection: nextId ? { id: nextId, title: this.getSectionTitle(nextId) } : null
- })
- },
-
- // 返回
- goBack() {
- wx.navigateBack({
- fail: () => wx.switchTab({ url: '/pages/chapters/chapters' })
- })
- },
-
- // 分享弹窗
- showShare() {
- this.setData({ showShareModal: true })
- },
-
- closeShareModal() {
- this.setData({ showShareModal: false })
- },
-
- // 复制链接
- copyLink() {
- const userInfo = app.globalData.userInfo
- const referralCode = userInfo?.referralCode || ''
- const shareUrl = `https://soul.quwanzhi.com/read/${this.data.sectionId}${referralCode ? '?ref=' + referralCode : ''}`
-
- wx.setClipboardData({
- data: shareUrl,
- success: () => {
- wx.showToast({ title: '链接已复制', icon: 'success' })
- this.setData({ showShareModal: false })
- }
- })
- },
-
- // 复制分享文案(朋友圈风格)
- copyShareText() {
- const { section } = this.data
-
- const shareText = `🔥 刚看完这篇《${section?.title || 'Soul创业派对'}》,太上头了!
-
-62个真实商业案例,每个都是从0到1的实战经验。私域运营、资源整合、商业变现,干货满满。
-
-推荐给正在创业或想创业的朋友,搜"Soul创业派对"小程序就能看!
-
-#创业派对 #私域运营 #商业案例`
-
- wx.setClipboardData({
- data: shareText,
- success: () => {
- wx.showToast({ title: '文案已复制', icon: 'success' })
- }
- })
- },
-
- // 分享到微信 - 自动带分享人ID
- onShareAppMessage() {
- const { section, sectionId } = this.data
- const userInfo = app.globalData.userInfo
- const referralCode = userInfo?.referralCode || wx.getStorageSync('referralCode') || ''
-
- // 分享标题优化
- const shareTitle = section?.title
- ? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
- : '📚 Soul创业派对 - 真实商业故事'
-
- return {
- title: shareTitle,
- path: `/pages/read/read?id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`,
- imageUrl: '/assets/share-cover.png' // 可配置分享封面图
- }
- },
-
- // 分享到朋友圈
- onShareTimeline() {
- const { section, sectionId } = this.data
- const userInfo = app.globalData.userInfo
- const referralCode = userInfo?.referralCode || ''
-
- return {
- title: `${section?.title || 'Soul创业派对'} - 来自派对房的真实故事`,
- query: `id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`
- }
- },
-
- // 显示登录弹窗(每次打开协议未勾选,符合审核要求)
- showLoginModal() {
- try {
- this.setData({ showLoginModal: true, agreeProtocol: false })
- } catch (e) {
- console.error('[Read] showLoginModal error:', e)
- this.setData({ showLoginModal: true })
- }
- },
-
- closeLoginModal() {
- this.setData({ showLoginModal: false })
- },
-
- toggleAgree() {
- this.setData({ agreeProtocol: !this.data.agreeProtocol })
- },
-
- openUserProtocol() {
- wx.navigateTo({ url: '/pages/agreement/agreement' })
- },
-
- openPrivacy() {
- wx.navigateTo({ url: '/pages/privacy/privacy' })
- },
-
- // 从服务端刷新购买状态,避免登录后误用旧数据导致误解锁
- // 【重构】微信登录(须先勾选同意协议,符合审核要求)
- async handleWechatLogin() {
- if (!this.data.agreeProtocol) {
- wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
- return
- }
- try {
- const result = await app.login()
- if (!result) return
-
- this.setData({ showLoginModal: false, agreeProtocol: false })
- await this.onLoginSuccess()
- wx.showToast({ title: '登录成功', icon: 'success' })
-
- } catch (e) {
- console.error('[Read] 登录失败:', e)
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- }
- },
-
- // 【重构】手机号登录(标准流程)
- async handlePhoneLogin(e) {
- if (!e.detail.code) {
- return this.handleWechatLogin()
- }
-
- try {
- const result = await app.loginWithPhone(e.detail.code)
- if (!result) return
-
- this.setData({ showLoginModal: false })
- await this.onLoginSuccess()
- wx.showToast({ title: '登录成功', icon: 'success' })
-
- } catch (e) {
- console.error('[Read] 手机号登录失败:', e)
- wx.showToast({ title: '登录失败', icon: 'none' })
- }
- },
-
- // 【新增】登录成功后的标准处理流程
- async onLoginSuccess() {
- wx.showLoading({ title: '更新状态中...', mask: true })
-
- try {
- // 1. 刷新用户购买状态(从 orders 表拉取最新)
- await accessManager.refreshUserPurchaseStatus()
-
- // 2. 重新拉取免费列表(极端情况:刚登录时当前章节可能改免费了)
- const config = await accessManager.fetchLatestConfig()
- this.setData({ freeIds: config.freeChapters })
-
- // 3. 重新判断当前章节权限
- const newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- config.freeChapters
- )
- const canAccess = accessManager.canAccessFullContent(newAccessState)
-
- this.setData({
- accessState: newAccessState,
- canAccess,
- isLoggedIn: true,
- showPaywall: !canAccess
- })
-
- // 4. 如果已解锁,重新加载内容并初始化阅读追踪
- if (canAccess) {
- await this.loadContent(this.data.sectionId, newAccessState)
- readingTracker.init(this.data.sectionId)
- }
-
- wx.hideLoading()
-
- } catch (e) {
- wx.hideLoading()
- console.error('[Read] 登录后更新状态失败:', e)
- wx.showToast({ title: '状态更新失败,请重试', icon: 'none' })
- }
- },
-
- // 购买章节 - 直接调起支付
- async handlePurchaseSection() {
- console.log('[Pay] 点击购买章节按钮')
- wx.showLoading({ title: '处理中...', mask: true })
-
- if (!this.data.isLoggedIn) {
- wx.hideLoading()
- console.log('[Pay] 用户未登录,显示登录弹窗')
- this.setData({ showLoginModal: true })
- return
- }
-
- const price = this.data.section?.price || 1
- console.log('[Pay] 开始支付流程:', { sectionId: this.data.sectionId, price })
- wx.hideLoading()
- await this.processPayment('section', this.data.sectionId, price)
- },
-
- // 购买全书 - 直接调起支付
- async handlePurchaseFullBook() {
- console.log('[Pay] 点击购买全书按钮')
- wx.showLoading({ title: '处理中...', mask: true })
-
- if (!this.data.isLoggedIn) {
- wx.hideLoading()
- console.log('[Pay] 用户未登录,显示登录弹窗')
- this.setData({ showLoginModal: true })
- return
- }
-
- console.log('[Pay] 开始支付流程: 全书', { price: this.data.fullBookPrice })
- wx.hideLoading()
- await this.processPayment('fullbook', null, this.data.fullBookPrice)
- },
-
- // 处理支付 - 调用真实微信支付接口
- async processPayment(type, sectionId, amount) {
- console.log('[Pay] processPayment开始:', { type, sectionId, amount })
-
- // 检查金额是否有效
- if (!amount || amount <= 0) {
- console.error('[Pay] 金额无效:', amount)
- wx.showToast({ title: '价格信息错误', icon: 'none' })
- return
- }
-
- // ✅ 从服务器查询是否已购买(基于 orders 表)
- try {
- wx.showLoading({ title: '检查购买状态...', mask: true })
- const userId = app.globalData.userInfo?.id
-
- if (userId) {
- const checkRes = await app.request(`/api/miniprogram/user/purchase-status?userId=${userId}`)
-
- if (checkRes.success && checkRes.data) {
- // 更新本地购买状态
- app.globalData.hasFullBook = checkRes.data.hasFullBook
- app.globalData.purchasedSections = checkRes.data.purchasedSections || []
-
- // 检查是否已购买
- if (type === 'section' && sectionId) {
- if (checkRes.data.purchasedSections.includes(sectionId)) {
- wx.hideLoading()
- wx.showToast({ title: '已购买过此章节', icon: 'none' })
- return
- }
- }
-
- if (type === 'fullbook' && checkRes.data.hasFullBook) {
- wx.hideLoading()
- wx.showToast({ title: '已购买全书', icon: 'none' })
- return
- }
- }
- }
- } catch (e) {
- console.warn('[Pay] 查询购买状态失败,继续支付流程:', e)
- // 查询失败不影响支付
- }
-
- this.setData({ isPaying: true })
- wx.showLoading({ title: '正在发起支付...', mask: true })
-
- try {
- // 1. 先获取openId (支付必需)
- let openId = app.globalData.openId || wx.getStorageSync('openId')
-
- if (!openId) {
- console.log('[Pay] 需要先获取openId,尝试静默获取')
- wx.showLoading({ title: '获取支付凭证...', mask: true })
- openId = await app.getOpenId()
-
- if (!openId) {
- // openId获取失败,但已登录用户可以使用用户ID替代
- if (app.globalData.isLoggedIn && app.globalData.userInfo?.id) {
- console.log('[Pay] 使用用户ID作为替代')
- openId = app.globalData.userInfo.id
- } else {
- wx.hideLoading()
- wx.showModal({
- title: '提示',
- content: '需要登录后才能支付,请先登录',
- showCancel: false
- })
- this.setData({ showLoginModal: true, isPaying: false })
- return
- }
- }
- }
-
- console.log('[Pay] 开始创建订单:', { type, sectionId, amount, openId: openId.slice(0, 10) + '...' })
- wx.showLoading({ title: '创建订单中...', mask: true })
-
- // 2. 调用后端创建预支付订单
- let paymentData = null
-
- try {
- // 获取章节完整名称用于支付描述
- const sectionTitle = this.data.section?.title || sectionId
- const description = type === 'fullbook'
- ? '《一场Soul的创业实验》全书'
- : `章节${sectionId}-${sectionTitle.length > 20 ? sectionTitle.slice(0, 20) + '...' : sectionTitle}`
-
- // 邀请码:谁邀请了我(从落地页 ref 或 storage 带入),会写入订单 referrer_id / referral_code 便于分销与对账
- const referralCode = wx.getStorageSync('referral_code') || ''
- const res = await app.request('/api/miniprogram/pay', {
- method: 'POST',
- data: {
- openId,
- productType: type,
- productId: sectionId,
- amount,
- description,
- userId: app.globalData.userInfo?.id || '',
- referralCode: referralCode || undefined
- }
- })
-
- console.log('[Pay] 创建订单响应:', res)
-
- if (res.success && res.data?.payParams) {
- paymentData = res.data.payParams
- console.log('[Pay] 获取支付参数成功:', paymentData)
- } else {
- throw new Error(res.error || res.message || '创建订单失败')
- }
- } catch (apiError) {
- console.error('[Pay] API创建订单失败:', apiError)
- wx.hideLoading()
- // 支付接口失败时,显示客服联系方式
- wx.showModal({
- title: '支付通道维护中',
- content: '微信支付正在审核中,请添加客服微信(28533368)手动购买,感谢理解!',
- confirmText: '复制微信号',
- cancelText: '稍后再说',
- success: (res) => {
- if (res.confirm) {
- wx.setClipboardData({
- data: '28533368',
- success: () => {
- wx.showToast({ title: '微信号已复制', icon: 'success' })
- }
- })
- }
- }
- })
- this.setData({ isPaying: false })
- return
- }
-
- // 3. 调用微信支付
- wx.hideLoading()
- console.log('[Pay] 调起微信支付, paymentData:', paymentData)
-
- try {
- await this.callWechatPay(paymentData)
-
- // 4. 【标准流程】支付成功后刷新权限并解锁内容
- console.log('[Pay] 微信支付成功!')
- await this.onPaymentSuccess()
-
- } catch (payErr) {
- console.error('[Pay] 微信支付调起失败:', payErr)
- if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
- wx.showToast({ title: '已取消支付', icon: 'none' })
- } else if (payErr.errMsg && payErr.errMsg.includes('requestPayment:fail')) {
- // 支付失败,可能是参数错误或权限问题
- wx.showModal({
- title: '支付失败',
- content: '微信支付暂不可用,请添加客服微信(28533368)手动购买',
- confirmText: '复制微信号',
- cancelText: '取消',
- success: (res) => {
- if (res.confirm) {
- wx.setClipboardData({
- data: '28533368',
- success: () => wx.showToast({ title: '微信号已复制', icon: 'success' })
- })
- }
- }
- })
- } else {
- wx.showToast({ title: payErr.errMsg || '支付失败', icon: 'none' })
- }
- }
-
- } catch (e) {
- console.error('[Pay] 支付流程异常:', e)
- wx.hideLoading()
- wx.showToast({ title: '支付出错,请重试', icon: 'none' })
- } finally {
- this.setData({ isPaying: false })
- }
- },
-
- // 【新增】支付成功后的标准处理流程
- async onPaymentSuccess() {
- wx.showLoading({ title: '确认购买中...', mask: true })
-
- try {
- // 1. 等待服务端处理支付回调(1-2秒)
- await this.sleep(2000)
-
- // 2. 刷新用户购买状态
- await accessManager.refreshUserPurchaseStatus()
-
- // 3. 重新判断当前章节权限(应为 unlocked_purchased)
- let newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- this.data.freeIds
- )
-
- // 如果权限未生效,再重试一次(可能回调延迟)
- if (newAccessState !== 'unlocked_purchased') {
- console.log('[Pay] 权限未生效,1秒后重试...')
- await this.sleep(1000)
- newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- this.data.freeIds
- )
- }
-
- const canAccess = accessManager.canAccessFullContent(newAccessState)
-
- this.setData({
- accessState: newAccessState,
- canAccess,
- showPaywall: !canAccess
- })
-
- // 4. 重新加载全文
- await this.loadContent(this.data.sectionId, newAccessState)
-
- // 5. 初始化阅读追踪
- if (canAccess) {
- readingTracker.init(this.data.sectionId)
- }
-
- wx.hideLoading()
- wx.showToast({ title: '购买成功', icon: 'success' })
-
- } catch (e) {
- wx.hideLoading()
- console.error('[Pay] 支付后更新失败:', e)
- wx.showModal({
- title: '提示',
- content: '购买成功,但内容加载失败,请返回重新进入',
- showCancel: false
- })
- }
- },
-
- // ✅ 刷新用户购买状态(从服务器获取最新数据)
- async refreshUserPurchaseStatus() {
- try {
- const userId = app.globalData.userInfo?.id
- if (!userId) {
- console.warn('[Pay] 用户未登录,无法刷新购买状态')
- return
- }
-
- // 调用专门的购买状态查询接口
- const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${userId}`)
-
- if (res.success && res.data) {
- // 更新全局购买状态
- app.globalData.hasFullBook = res.data.hasFullBook
- app.globalData.purchasedSections = res.data.purchasedSections || []
-
- // 更新用户信息中的购买记录
- const userInfo = app.globalData.userInfo || {}
- userInfo.hasFullBook = res.data.hasFullBook
- userInfo.purchasedSections = res.data.purchasedSections || []
- app.globalData.userInfo = userInfo
- wx.setStorageSync('userInfo', userInfo)
-
- console.log('[Pay] ✅ 购买状态已刷新:', {
- hasFullBook: res.data.hasFullBook,
- purchasedCount: res.data.purchasedSections.length
- })
- }
- } catch (e) {
- console.error('[Pay] 刷新购买状态失败:', e)
- // 刷新失败时不影响用户体验,只是记录日志
- }
- },
-
- // 调用微信支付
- callWechatPay(paymentData) {
- return new Promise((resolve, reject) => {
- wx.requestPayment({
- timeStamp: paymentData.timeStamp,
- nonceStr: paymentData.nonceStr,
- package: paymentData.package,
- signType: paymentData.signType || 'MD5',
- paySign: paymentData.paySign,
- success: resolve,
- fail: reject
- })
- })
- },
-
- // 跳转到上一篇
- goToPrev() {
- if (this.data.prevSection) {
- wx.redirectTo({ url: `/pages/read/read?id=${this.data.prevSection.id}` })
- }
- },
-
- // 跳转到下一篇
- goToNext() {
- if (this.data.nextSection) {
- wx.redirectTo({ url: `/pages/read/read?id=${this.data.nextSection.id}` })
- }
- },
-
- // 跳转到推广中心
- goToReferral() {
- wx.navigateTo({ url: '/pages/referral/referral' })
- },
-
- // 生成海报
- async generatePoster() {
- wx.showLoading({ title: '生成中...' })
- this.setData({ showPosterModal: true, isGeneratingPoster: true })
-
- try {
- const ctx = wx.createCanvasContext('posterCanvas', this)
- const { section, contentParagraphs, sectionId } = this.data
- const userInfo = app.globalData.userInfo
- const userId = userInfo?.id || ''
-
- // 获取小程序码(带推荐人参数)
- let qrcodeImage = null
- try {
- const scene = userId ? `id=${sectionId}&ref=${userId.slice(0,10)}` : `id=${sectionId}`
- const qrRes = await app.request('/api/miniprogram/qrcode', {
- method: 'POST',
- data: { scene, page: 'pages/read/read', width: 280 }
- })
- if (qrRes.success && qrRes.image) {
- qrcodeImage = qrRes.image
- }
- } catch (e) {
- console.log('[Poster] 获取小程序码失败,使用占位符')
- }
-
- // 海报尺寸 300x450
- const width = 300
- const height = 450
-
- // 背景渐变
- const grd = ctx.createLinearGradient(0, 0, 0, height)
- grd.addColorStop(0, '#1a1a2e')
- grd.addColorStop(1, '#16213e')
- ctx.setFillStyle(grd)
- ctx.fillRect(0, 0, width, height)
-
- // 顶部装饰条
- ctx.setFillStyle('#00CED1')
- ctx.fillRect(0, 0, width, 4)
-
- // 标题区域
- ctx.setFillStyle('#ffffff')
- ctx.setFontSize(14)
- ctx.fillText('📚 Soul创业派对', 20, 35)
-
- // 章节标题
- ctx.setFontSize(18)
- ctx.setFillStyle('#ffffff')
- const title = section?.title || '精彩内容'
- const titleLines = this.wrapText(ctx, title, width - 40, 18)
- let y = 70
- titleLines.forEach(line => {
- ctx.fillText(line, 20, y)
- y += 26
- })
-
- // 分隔线
- ctx.setStrokeStyle('rgba(255,255,255,0.1)')
- ctx.beginPath()
- ctx.moveTo(20, y + 10)
- ctx.lineTo(width - 20, y + 10)
- ctx.stroke()
-
- // 内容摘要
- ctx.setFontSize(12)
- ctx.setFillStyle('rgba(255,255,255,0.8)')
- y += 30
- const summary = contentParagraphs.slice(0, 3).join(' ').slice(0, 150) + '...'
- const summaryLines = this.wrapText(ctx, summary, width - 40, 12)
- summaryLines.slice(0, 6).forEach(line => {
- ctx.fillText(line, 20, y)
- y += 20
- })
-
- // 底部区域背景
- ctx.setFillStyle('rgba(0,206,209,0.1)')
- ctx.fillRect(0, height - 100, width, 100)
-
- // 左侧提示文字
- ctx.setFillStyle('#ffffff')
- ctx.setFontSize(13)
- ctx.fillText('长按识别小程序码', 20, height - 60)
- ctx.setFillStyle('rgba(255,255,255,0.6)')
- ctx.setFontSize(11)
- ctx.fillText('长按小程序码阅读全文', 20, height - 38)
-
- // 绘制小程序码或占位符
- const drawQRCode = () => {
- return new Promise((resolve) => {
- if (qrcodeImage) {
- // 下载base64图片并绘制
- const fs = wx.getFileSystemManager()
- const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`
- const base64Data = qrcodeImage.replace(/^data:image\/\w+;base64,/, '')
-
- fs.writeFile({
- filePath,
- data: base64Data,
- encoding: 'base64',
- success: () => {
- ctx.drawImage(filePath, width - 85, height - 85, 70, 70)
- resolve()
- },
- fail: () => {
- this.drawQRPlaceholder(ctx, width, height)
- resolve()
- }
- })
- } else {
- this.drawQRPlaceholder(ctx, width, height)
- resolve()
- }
- })
- }
-
- await drawQRCode()
-
- ctx.draw(true, () => {
- wx.hideLoading()
- this.setData({ isGeneratingPoster: false })
- })
- } catch (e) {
- console.error('生成海报失败:', e)
- wx.hideLoading()
- wx.showToast({ title: '生成失败', icon: 'none' })
- this.setData({ showPosterModal: false, isGeneratingPoster: false })
- }
- },
-
- // 绘制小程序码占位符
- drawQRPlaceholder(ctx, width, height) {
- ctx.setFillStyle('#ffffff')
- ctx.beginPath()
- ctx.arc(width - 50, height - 50, 35, 0, Math.PI * 2)
- ctx.fill()
- ctx.setFillStyle('#00CED1')
- ctx.setFontSize(9)
- ctx.fillText('扫码', width - 57, height - 52)
- ctx.fillText('阅读', width - 57, height - 40)
- },
-
- // 文字换行处理
- wrapText(ctx, text, maxWidth, fontSize) {
- const lines = []
- let line = ''
- for (let i = 0; i < text.length; i++) {
- const testLine = line + text[i]
- const metrics = ctx.measureText(testLine)
- if (metrics.width > maxWidth && line) {
- lines.push(line)
- line = text[i]
- } else {
- line = testLine
- }
- }
- if (line) lines.push(line)
- return lines
- },
-
- // 关闭海报弹窗
- closePosterModal() {
- this.setData({ showPosterModal: false })
- },
-
- // 保存海报到相册
- savePoster() {
- wx.canvasToTempFilePath({
- canvasId: 'posterCanvas',
- success: (res) => {
- wx.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success: () => {
- wx.showToast({ title: '已保存到相册', icon: 'success' })
- this.setData({ showPosterModal: false })
- },
- fail: (err) => {
- if (err.errMsg.includes('auth deny')) {
- wx.showModal({
- title: '提示',
- content: '需要相册权限才能保存海报',
- confirmText: '去设置',
- success: (res) => {
- if (res.confirm) {
- wx.openSetting()
- }
- }
- })
- } else {
- wx.showToast({ title: '保存失败', icon: 'none' })
- }
- }
- })
- },
- fail: () => {
- wx.showToast({ title: '生成图片失败', icon: 'none' })
- }
- }, this)
- },
-
- // 阻止冒泡
- stopPropagation() {},
-
- // 【新增】页面隐藏时上报阅读进度
- onHide() {
- readingTracker.onPageHide()
- },
-
- // 【新增】页面卸载时清理追踪器
- onUnload() {
- readingTracker.cleanup()
- },
-
- // 【新增】重试加载(当 accessState 为 error 时)
- async handleRetry() {
- wx.showLoading({ title: '重试中...', mask: true })
-
- try {
- // 重新拉取配置
- const config = await accessManager.fetchLatestConfig()
- this.setData({ freeIds: config.freeChapters })
-
- // 重新判断权限
- const newAccessState = await accessManager.determineAccessState(
- this.data.sectionId,
- config.freeChapters
- )
- const canAccess = accessManager.canAccessFullContent(newAccessState)
-
- this.setData({
- accessState: newAccessState,
- canAccess,
- showPaywall: !canAccess
- })
-
- // 重新加载内容
- await this.loadContent(this.data.sectionId, newAccessState)
-
- // 如果有权限,初始化阅读追踪
- if (canAccess) {
- readingTracker.init(this.data.sectionId)
- }
-
- // 加载导航
- this.loadNavigation(this.data.sectionId)
-
- wx.hideLoading()
- wx.showToast({ title: '加载成功', icon: 'success' })
-
- } catch (e) {
- wx.hideLoading()
- console.error('[Read] 重试失败:', e)
- wx.showToast({ title: '重试失败,请检查网络', icon: 'none' })
- }
- },
-
- // 工具:延迟
- sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms))
- }
-})
diff --git a/归档/miniprogram/pages/read/read.js.backup b/归档/miniprogram/pages/read/read.js.backup
deleted file mode 100644
index 65ea2b60..00000000
--- a/归档/miniprogram/pages/read/read.js.backup
+++ /dev/null
@@ -1,1055 +0,0 @@
-/**
- * Soul创业派对 - 阅读页
- * 开发: 卡若
- * 技术支持: 存客宝
- */
-
-const app = getApp()
-
-Page({
- data: {
- // 系统信息
- statusBarHeight: 44,
- navBarHeight: 88,
-
- // 章节信息
- sectionId: '',
- section: null,
- partTitle: '',
- chapterTitle: '',
-
- // 内容
- content: '',
- previewContent: '',
- contentParagraphs: [],
- previewParagraphs: [],
- loading: true,
-
- // 用户状态
- isLoggedIn: false,
- hasFullBook: false,
- canAccess: false,
- purchasedCount: 0,
-
- // 阅读进度
- readingProgress: 0,
- showPaywall: false,
-
- // 上一篇/下一篇
- prevSection: null,
- nextSection: null,
-
- // 价格
- sectionPrice: 1,
- fullBookPrice: 9.9,
- totalSections: 62,
-
- // 弹窗
- showShareModal: false,
- showLoginModal: false,
- showPosterModal: false,
- isPaying: false,
- isGeneratingPoster: false,
-
- // 免费章节
- freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3']
- },
-
- onLoad(options) {
- const { id, ref } = options
-
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight,
- navBarHeight: app.globalData.navBarHeight,
- sectionId: id
- })
-
- // 处理推荐码绑定
- if (ref) {
- console.log('[Read] 检测到推荐码:', ref)
- wx.setStorageSync('referral_code', ref)
- app.handleReferralCode({ query: { ref } })
- }
-
- // 先拉取免费章节配置再初始化,避免首帧误判免费/付费
- const run = async () => {
- await this.loadFreeChaptersConfig()
- this.initSection(id)
- }
- run()
- },
-
- // 从后端加载免费章节配置
- async loadFreeChaptersConfig() {
- try {
- const res = await app.request('/api/db/config')
- if (res.success && res.freeChapters) {
- this.setData({ freeIds: res.freeChapters })
- console.log('[Read] 加载免费章节配置:', res.freeChapters)
- }
- } catch (e) {
- console.log('[Read] 使用默认免费章节配置')
- }
- },
-
- onPageScroll(e) {
- // 计算阅读进度
- const query = wx.createSelectorQuery()
- query.select('.page').boundingClientRect()
- query.exec((res) => {
- if (res[0]) {
- const scrollTop = e.scrollTop
- const pageHeight = res[0].height - this.data.statusBarHeight - 200
- const progress = pageHeight > 0 ? Math.min((scrollTop / pageHeight) * 100, 100) : 0
- this.setData({ readingProgress: progress })
- }
- })
- },
-
- // 初始化章节:免费直接可看;付费则请求接口校验是否已购买
- async initSection(id) {
- this.setData({ loading: true })
-
- try {
- const section = this.getSectionInfo(id)
- const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
- const isFree = this.data.freeIds.includes(id)
-
- let canAccess = isFree
- let isPurchased = false
-
- if (!isFree) {
- if (!isLoggedIn || !app.globalData.userInfo?.id) {
- canAccess = false
- } else {
- try {
- const userId = app.globalData.userInfo.id
- const res = await app.request(
- `/api/user/check-purchased?userId=${encodeURIComponent(userId)}&type=section&productId=${encodeURIComponent(id)}`
- )
- if (res.success && res.data && res.data.isPurchased) {
- isPurchased = true
- canAccess = true
- if (!purchasedSections.includes(id)) {
- app.globalData.purchasedSections = [...(app.globalData.purchasedSections || []), id]
- }
- if (res.data.reason === 'has_full_book') {
- app.globalData.hasFullBook = true
- }
- }
- } catch (e) {
- console.warn('[Read] 校验购买状态失败,保守处理为未购买:', e)
- isPurchased = false
- canAccess = false
- }
- }
- }
-
- const purchasedCount = (app.globalData.purchasedSections || []).length
-
- this.setData({
- section,
- isLoggedIn,
- hasFullBook: app.globalData.hasFullBook,
- canAccess,
- purchasedCount,
- showPaywall: !canAccess
- })
-
- await this.loadContent(id)
-
- if (canAccess) {
- app.markSectionAsRead(id)
- }
-
- this.loadNavigation(id)
-
- } catch (e) {
- console.error('初始化章节失败:', e)
- wx.showToast({ title: '加载失败', icon: 'none' })
- } finally {
- this.setData({ loading: false })
- }
- },
-
- // 获取章节信息
- getSectionInfo(id) {
- // 特殊章节
- if (id === 'preface') {
- return { id: 'preface', title: '为什么我每天早上6点在Soul开播?', isFree: true, price: 0 }
- }
- if (id === 'epilogue') {
- return { id: 'epilogue', title: '这本书的真实目的', isFree: true, price: 0 }
- }
- if (id.startsWith('appendix')) {
- const appendixTitles = {
- 'appendix-1': 'Soul派对房精选对话',
- 'appendix-2': '创业者自检清单',
- 'appendix-3': '本书提到的工具和资源'
- }
- return { id, title: appendixTitles[id] || '附录', isFree: true, price: 0 }
- }
-
- // 普通章节
- return {
- id: id,
- title: this.getSectionTitle(id),
- isFree: id === '1.1',
- price: 1
- }
- },
-
- // 获取章节标题
- getSectionTitle(id) {
- const titles = {
- '1.1': '荷包:电动车出租的被动收入模式',
- '1.2': '老墨:资源整合高手的社交方法',
- '1.3': '笑声背后的MBTI',
- '1.4': '人性的三角结构:利益、情感、价值观',
- '1.5': '沟通差的问题:为什么你说的别人听不懂',
- '2.1': '相亲故事:你以为找的是人,实际是在找模式',
- '2.2': '找工作迷茫者:为什么简历解决不了人生',
- '2.3': '撸运费险:小钱困住大脑的真实心理',
- '2.4': '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力',
- '2.5': '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒',
- '3.1': '3000万流水如何跑出来(退税模式解析)',
- '8.1': '流量杠杆:抖音、Soul、飞书',
- '9.14': '大健康私域:一个月150万的70后'
- }
- return titles[id] || `章节 ${id}`
- },
-
- // 加载内容 - 三级降级方案:API → 本地缓存 → 备用API
- async loadContent(id) {
- const cacheKey = `chapter_${id}`
-
- // 1. 优先从API获取
- try {
- const res = await this.fetchChapterWithTimeout(id, 5000)
- if (res && res.content) {
- this.setChapterContent(res)
- // 成功后缓存到本地
- wx.setStorageSync(cacheKey, res)
- console.log('[Read] 从API加载成功:', id)
- return
- }
- } catch (e) {
- console.warn('[Read] API加载失败,尝试本地缓存:', e.message)
- }
-
- // 2. API失败,尝试从本地缓存读取
- try {
- const cached = wx.getStorageSync(cacheKey)
- if (cached && cached.content) {
- this.setChapterContent(cached)
- console.log('[Read] 从本地缓存加载成功:', id)
- // 后台静默刷新
- this.silentRefresh(id)
- return
- }
- } catch (e) {
- console.warn('[Read] 本地缓存读取失败')
- }
-
- // 3. 都失败,显示加载中并持续重试
- this.setData({
- contentParagraphs: ['章节内容加载中...', '正在尝试连接服务器,请稍候...'],
- previewParagraphs: ['章节内容加载中...']
- })
-
- // 延迟重试(最多3次)
- this.retryLoadContent(id, 3)
- },
-
- // 带超时的章节请求
- fetchChapterWithTimeout(id, timeout = 5000) {
- return new Promise((resolve, reject) => {
- const timer = setTimeout(() => {
- reject(new Error('请求超时'))
- }, timeout)
-
- app.request(`/api/book/chapter/${id}`)
- .then(res => {
- clearTimeout(timer)
- resolve(res)
- })
- .catch(err => {
- clearTimeout(timer)
- reject(err)
- })
- })
- },
-
- // 设置章节内容
- setChapterContent(res) {
- const lines = res.content.split('\n').filter(line => line.trim())
- const previewCount = Math.ceil(lines.length * 0.2)
-
- this.setData({
- content: res.content,
- previewContent: lines.slice(0, previewCount).join('\n'),
- contentParagraphs: lines,
- previewParagraphs: lines.slice(0, previewCount),
- partTitle: res.partTitle || '',
- chapterTitle: res.chapterTitle || ''
- })
- },
-
- // 静默刷新(后台更新缓存)
- async silentRefresh(id) {
- try {
- const res = await this.fetchChapterWithTimeout(id, 10000)
- if (res && res.content) {
- wx.setStorageSync(`chapter_${id}`, res)
- console.log('[Read] 后台缓存更新成功:', id)
- }
- } catch (e) {
- // 静默失败不处理
- }
- },
-
- // 重试加载
- retryLoadContent(id, maxRetries, currentRetry = 0) {
- if (currentRetry >= maxRetries) {
- this.setData({
- contentParagraphs: ['内容加载失败', '请检查网络连接后下拉刷新重试'],
- previewParagraphs: ['内容加载失败']
- })
- return
- }
-
- setTimeout(async () => {
- try {
- const res = await this.fetchChapterWithTimeout(id, 8000)
- if (res && res.content) {
- this.setChapterContent(res)
- wx.setStorageSync(`chapter_${id}`, res)
- console.log('[Read] 重试成功:', id, '第', currentRetry + 1, '次')
- return
- }
- } catch (e) {
- console.warn('[Read] 重试失败,继续重试:', currentRetry + 1)
- }
- this.retryLoadContent(id, maxRetries, currentRetry + 1)
- }, 2000 * (currentRetry + 1))
- },
-
-
- // 加载导航
- loadNavigation(id) {
- const sectionOrder = [
- 'preface', '1.1', '1.2', '1.3', '1.4', '1.5',
- '2.1', '2.2', '2.3', '2.4', '2.5',
- '3.1', '3.2', '3.3', '3.4',
- '4.1', '4.2', '4.3', '4.4', '4.5',
- '5.1', '5.2', '5.3', '5.4', '5.5',
- '6.1', '6.2', '6.3', '6.4',
- '7.1', '7.2', '7.3', '7.4', '7.5',
- '8.1', '8.2', '8.3', '8.4', '8.5', '8.6',
- '9.1', '9.2', '9.3', '9.4', '9.5', '9.6', '9.7', '9.8', '9.9', '9.10', '9.11', '9.12', '9.13', '9.14',
- '10.1', '10.2', '10.3', '10.4',
- '11.1', '11.2', '11.3', '11.4', '11.5',
- 'epilogue'
- ]
-
- const currentIndex = sectionOrder.indexOf(id)
- const prevId = currentIndex > 0 ? sectionOrder[currentIndex - 1] : null
- const nextId = currentIndex < sectionOrder.length - 1 ? sectionOrder[currentIndex + 1] : null
-
- this.setData({
- prevSection: prevId ? { id: prevId, title: this.getSectionTitle(prevId) } : null,
- nextSection: nextId ? { id: nextId, title: this.getSectionTitle(nextId) } : null
- })
- },
-
- // 返回
- goBack() {
- wx.navigateBack({
- fail: () => wx.switchTab({ url: '/pages/chapters/chapters' })
- })
- },
-
- // 分享弹窗
- showShare() {
- this.setData({ showShareModal: true })
- },
-
- closeShareModal() {
- this.setData({ showShareModal: false })
- },
-
- // 复制链接
- copyLink() {
- const userInfo = app.globalData.userInfo
- const referralCode = userInfo?.referralCode || ''
- const shareUrl = `https://soul.quwanzhi.com/read/${this.data.sectionId}${referralCode ? '?ref=' + referralCode : ''}`
-
- wx.setClipboardData({
- data: shareUrl,
- success: () => {
- wx.showToast({ title: '链接已复制', icon: 'success' })
- this.setData({ showShareModal: false })
- }
- })
- },
-
- // 复制分享文案(朋友圈风格)
- copyShareText() {
- const { section } = this.data
-
- const shareText = `🔥 刚看完这篇《${section?.title || 'Soul创业派对'}》,太上头了!
-
-62个真实商业案例,每个都是从0到1的实战经验。私域运营、资源整合、商业变现,干货满满。
-
-推荐给正在创业或想创业的朋友,搜"Soul创业派对"小程序就能看!
-
-#创业派对 #私域运营 #商业案例`
-
- wx.setClipboardData({
- data: shareText,
- success: () => {
- wx.showToast({ title: '文案已复制', icon: 'success' })
- }
- })
- },
-
- // 分享到微信 - 自动带分享人ID
- onShareAppMessage() {
- const { section, sectionId } = this.data
- const userInfo = app.globalData.userInfo
- const referralCode = userInfo?.referralCode || wx.getStorageSync('referralCode') || ''
-
- // 分享标题优化
- const shareTitle = section?.title
- ? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
- : '📚 Soul创业派对 - 真实商业故事'
-
- return {
- title: shareTitle,
- path: `/pages/read/read?id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`,
- imageUrl: '/assets/share-cover.png' // 可配置分享封面图
- }
- },
-
- // 分享到朋友圈
- onShareTimeline() {
- const { section, sectionId } = this.data
- const userInfo = app.globalData.userInfo
- const referralCode = userInfo?.referralCode || ''
-
- return {
- title: `${section?.title || 'Soul创业派对'} - 来自派对房的真实故事`,
- query: `id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`
- }
- },
-
- // 显示登录弹窗
- showLoginModal() {
- this.setData({ showLoginModal: true })
- },
-
- closeLoginModal() {
- this.setData({ showLoginModal: false })
- },
-
- // 从服务端刷新购买状态,避免登录后误用旧数据导致误解锁
- async refreshPurchaseFromServer() {
- const userId = app.globalData.userInfo?.id
- if (!userId) return
- try {
- const res = await app.request(`/api/user/purchase-status?userId=${encodeURIComponent(userId)}`)
- if (res.success && res.data) {
- app.globalData.hasFullBook = res.data.hasFullBook || false
- app.globalData.purchasedSections = res.data.purchasedSections || []
- wx.setStorageSync('userInfo', { ...(app.globalData.userInfo || {}), purchasedSections: app.globalData.purchasedSections, hasFullBook: app.globalData.hasFullBook })
- }
- } catch (e) {
- console.warn('[Read] 刷新购买状态失败:', e)
- }
- },
-
- // 微信登录(含:因付款弹窗发起的登录)
- async handleWechatLogin() {
- try {
- const result = await app.login()
- if (result) {
- this.setData({ showLoginModal: false })
- // 登录后必须重新向服务端拉取购买状态,并重新校验当前章节是否已购买,再决定是否解锁(避免误解锁)
- await this.refreshPurchaseFromServer()
- await this.recheckCurrentSectionAndRefresh()
- wx.showToast({ title: '登录成功', icon: 'success' })
- }
- } catch (e) {
- wx.showToast({ title: '登录失败', icon: 'none' })
- }
- },
-
- // 手机号登录(含:因付款弹窗发起的登录)
- async handlePhoneLogin(e) {
- if (!e.detail.code) {
- return this.handleWechatLogin()
- }
-
- try {
- const result = await app.loginWithPhone(e.detail.code)
- if (result) {
- this.setData({ showLoginModal: false })
- // 登录后必须重新向服务端拉取购买状态,并重新校验当前章节是否已购买,再决定是否解锁(避免误解锁)
- await this.refreshPurchaseFromServer()
- await this.recheckCurrentSectionAndRefresh()
- wx.showToast({ title: '登录成功', icon: 'success' })
- }
- } catch (e) {
- wx.showToast({ title: '登录失败', icon: 'none' })
- }
- },
-
- // 登录后专用:重新向服务端校验当前章节是否已付费购买(或是否已改为免费),再刷新页面状态
- async recheckCurrentSectionAndRefresh() {
- const sectionId = this.data.sectionId
- // 极端情况:用户登录后,当前章节可能刚被后台改为免费,先拉取最新免费列表再判断
- await this.loadFreeChaptersConfig()
- const isFree = this.data.freeIds.includes(sectionId)
- if (isFree) {
- this.setData({ isLoggedIn: true, canAccess: true, showPaywall: false })
- await this.initSection(sectionId)
- return
- }
- const userId = app.globalData.userInfo?.id
- if (!userId) {
- this.setData({ canAccess: false, showPaywall: true })
- return
- }
- try {
- const res = await app.request(
- `/api/user/check-purchased?userId=${encodeURIComponent(userId)}&type=section&productId=${encodeURIComponent(sectionId)}`
- )
- const isPurchased = res.success && res.data && res.data.isPurchased
- if (isPurchased) {
- if (!(app.globalData.purchasedSections || []).includes(sectionId)) {
- app.globalData.purchasedSections = [...(app.globalData.purchasedSections || []), sectionId]
- }
- if (res.data.reason === 'has_full_book') {
- app.globalData.hasFullBook = true
- }
- }
- this.setData({
- isLoggedIn: true,
- hasFullBook: app.globalData.hasFullBook,
- canAccess: isPurchased,
- purchasedCount: (app.globalData.purchasedSections || []).length,
- showPaywall: !isPurchased
- })
- if (isPurchased) {
- app.markSectionAsRead(sectionId)
- }
- await this.initSection(sectionId)
- } catch (e) {
- console.warn('[Read] 登录后校验当前章节购买状态失败,保守处理为未购买:', e)
- this.setData({
- isLoggedIn: true,
- canAccess: false,
- showPaywall: true
- })
- await this.initSection(sectionId)
- }
- },
-
- // 购买章节 - 直接调起支付
- async handlePurchaseSection() {
- console.log('[Pay] 点击购买章节按钮')
- wx.showLoading({ title: '处理中...', mask: true })
-
- if (!this.data.isLoggedIn) {
- wx.hideLoading()
- console.log('[Pay] 用户未登录,显示登录弹窗')
- this.setData({ showLoginModal: true })
- return
- }
-
- const price = this.data.section?.price || 1
- console.log('[Pay] 开始支付流程:', { sectionId: this.data.sectionId, price })
- wx.hideLoading()
- await this.processPayment('section', this.data.sectionId, price)
- },
-
- // 购买全书 - 直接调起支付
- async handlePurchaseFullBook() {
- console.log('[Pay] 点击购买全书按钮')
- wx.showLoading({ title: '处理中...', mask: true })
-
- if (!this.data.isLoggedIn) {
- wx.hideLoading()
- console.log('[Pay] 用户未登录,显示登录弹窗')
- this.setData({ showLoginModal: true })
- return
- }
-
- console.log('[Pay] 开始支付流程: 全书', { price: this.data.fullBookPrice })
- wx.hideLoading()
- await this.processPayment('fullbook', null, this.data.fullBookPrice)
- },
-
- // 处理支付 - 调用真实微信支付接口
- async processPayment(type, sectionId, amount) {
- console.log('[Pay] processPayment开始:', { type, sectionId, amount })
-
- // 检查金额是否有效
- if (!amount || amount <= 0) {
- console.error('[Pay] 金额无效:', amount)
- wx.showToast({ title: '价格信息错误', icon: 'none' })
- return
- }
-
- // ✅ 从服务器查询是否已购买(基于 orders 表)
- try {
- wx.showLoading({ title: '检查购买状态...', mask: true })
- const userId = app.globalData.userInfo?.id
-
- if (userId) {
- const checkRes = await app.request(`/api/user/purchase-status?userId=${userId}`)
-
- if (checkRes.success && checkRes.data) {
- // 更新本地购买状态
- app.globalData.hasFullBook = checkRes.data.hasFullBook
- app.globalData.purchasedSections = checkRes.data.purchasedSections || []
-
- // 检查是否已购买
- if (type === 'section' && sectionId) {
- if (checkRes.data.purchasedSections.includes(sectionId)) {
- wx.hideLoading()
- wx.showToast({ title: '已购买过此章节', icon: 'none' })
- return
- }
- }
-
- if (type === 'fullbook' && checkRes.data.hasFullBook) {
- wx.hideLoading()
- wx.showToast({ title: '已购买全书', icon: 'none' })
- return
- }
- }
- }
- } catch (e) {
- console.warn('[Pay] 查询购买状态失败,继续支付流程:', e)
- // 查询失败不影响支付
- }
-
- this.setData({ isPaying: true })
- wx.showLoading({ title: '正在发起支付...', mask: true })
-
- try {
- // 1. 先获取openId (支付必需)
- let openId = app.globalData.openId || wx.getStorageSync('openId')
-
- if (!openId) {
- console.log('[Pay] 需要先获取openId,尝试静默获取')
- wx.showLoading({ title: '获取支付凭证...', mask: true })
- openId = await app.getOpenId()
-
- if (!openId) {
- // openId获取失败,但已登录用户可以使用用户ID替代
- if (app.globalData.isLoggedIn && app.globalData.userInfo?.id) {
- console.log('[Pay] 使用用户ID作为替代')
- openId = app.globalData.userInfo.id
- } else {
- wx.hideLoading()
- wx.showModal({
- title: '提示',
- content: '需要登录后才能支付,请先登录',
- showCancel: false
- })
- this.setData({ showLoginModal: true, isPaying: false })
- return
- }
- }
- }
-
- console.log('[Pay] 开始创建订单:', { type, sectionId, amount, openId: openId.slice(0, 10) + '...' })
- wx.showLoading({ title: '创建订单中...', mask: true })
-
- // 2. 调用后端创建预支付订单
- let paymentData = null
-
- try {
- // 获取章节完整名称用于支付描述
- const sectionTitle = this.data.section?.title || sectionId
- const description = type === 'fullbook'
- ? '《一场Soul的创业实验》全书'
- : `章节${sectionId}-${sectionTitle.length > 20 ? sectionTitle.slice(0, 20) + '...' : sectionTitle}`
-
- // 邀请码:谁邀请了我(从落地页 ref 或 storage 带入),用于订单分销归属
- const referralCode = wx.getStorageSync('referral_code') || ''
- const res = await app.request('/api/miniprogram/pay', {
- method: 'POST',
- data: {
- openId,
- productType: type,
- productId: sectionId,
- amount,
- description,
- userId: app.globalData.userInfo?.id || '',
- referralCode: referralCode || undefined
- }
- })
-
- console.log('[Pay] 创建订单响应:', res)
-
- if (res.success && res.data?.payParams) {
- paymentData = res.data.payParams
- console.log('[Pay] 获取支付参数成功:', paymentData)
- } else {
- throw new Error(res.error || res.message || '创建订单失败')
- }
- } catch (apiError) {
- console.error('[Pay] API创建订单失败:', apiError)
- wx.hideLoading()
- // 支付接口失败时,显示客服联系方式
- wx.showModal({
- title: '支付通道维护中',
- content: '微信支付正在审核中,请添加客服微信(28533368)手动购买,感谢理解!',
- confirmText: '复制微信号',
- cancelText: '稍后再说',
- success: (res) => {
- if (res.confirm) {
- wx.setClipboardData({
- data: '28533368',
- success: () => {
- wx.showToast({ title: '微信号已复制', icon: 'success' })
- }
- })
- }
- }
- })
- this.setData({ isPaying: false })
- return
- }
-
- // 3. 调用微信支付
- wx.hideLoading()
- console.log('[Pay] 调起微信支付, paymentData:', paymentData)
-
- try {
- await this.callWechatPay(paymentData)
-
- // 4. 支付成功,刷新用户购买状态
- console.log('[Pay] 微信支付成功!')
- wx.showLoading({ title: '正在确认购买...', mask: true })
-
- // 等待后端处理支付回调(1-3秒)
- await new Promise(resolve => setTimeout(resolve, 2000))
-
- // 重新获取用户信息(包含最新购买记录)
- await this.refreshUserPurchaseStatus()
-
- wx.hideLoading()
- wx.showToast({ title: '购买成功', icon: 'success' })
-
- // 5. 刷新页面
- this.initSection(this.data.sectionId)
- } catch (payErr) {
- console.error('[Pay] 微信支付调起失败:', payErr)
- if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
- wx.showToast({ title: '已取消支付', icon: 'none' })
- } else if (payErr.errMsg && payErr.errMsg.includes('requestPayment:fail')) {
- // 支付失败,可能是参数错误或权限问题
- wx.showModal({
- title: '支付失败',
- content: '微信支付暂不可用,请添加客服微信(28533368)手动购买',
- confirmText: '复制微信号',
- cancelText: '取消',
- success: (res) => {
- if (res.confirm) {
- wx.setClipboardData({
- data: '28533368',
- success: () => wx.showToast({ title: '微信号已复制', icon: 'success' })
- })
- }
- }
- })
- } else {
- wx.showToast({ title: payErr.errMsg || '支付失败', icon: 'none' })
- }
- }
-
- } catch (e) {
- console.error('[Pay] 支付流程异常:', e)
- wx.hideLoading()
- wx.showToast({ title: '支付出错,请重试', icon: 'none' })
- } finally {
- this.setData({ isPaying: false })
- }
- },
-
- // ✅ 刷新用户购买状态(从服务器获取最新数据)
- async refreshUserPurchaseStatus() {
- try {
- const userId = app.globalData.userInfo?.id
- if (!userId) {
- console.warn('[Pay] 用户未登录,无法刷新购买状态')
- return
- }
-
- // 调用专门的购买状态查询接口
- const res = await app.request(`/api/user/purchase-status?userId=${userId}`)
-
- if (res.success && res.data) {
- // 更新全局购买状态
- app.globalData.hasFullBook = res.data.hasFullBook
- app.globalData.purchasedSections = res.data.purchasedSections || []
-
- // 更新用户信息中的购买记录
- const userInfo = app.globalData.userInfo || {}
- userInfo.hasFullBook = res.data.hasFullBook
- userInfo.purchasedSections = res.data.purchasedSections || []
- app.globalData.userInfo = userInfo
- wx.setStorageSync('userInfo', userInfo)
-
- console.log('[Pay] ✅ 购买状态已刷新:', {
- hasFullBook: res.data.hasFullBook,
- purchasedCount: res.data.purchasedSections.length
- })
- }
- } catch (e) {
- console.error('[Pay] 刷新购买状态失败:', e)
- // 刷新失败时不影响用户体验,只是记录日志
- }
- },
-
- // 调用微信支付
- callWechatPay(paymentData) {
- return new Promise((resolve, reject) => {
- wx.requestPayment({
- timeStamp: paymentData.timeStamp,
- nonceStr: paymentData.nonceStr,
- package: paymentData.package,
- signType: paymentData.signType || 'MD5',
- paySign: paymentData.paySign,
- success: resolve,
- fail: reject
- })
- })
- },
-
- // 跳转到上一篇
- goToPrev() {
- if (this.data.prevSection) {
- wx.redirectTo({ url: `/pages/read/read?id=${this.data.prevSection.id}` })
- }
- },
-
- // 跳转到下一篇
- goToNext() {
- if (this.data.nextSection) {
- wx.redirectTo({ url: `/pages/read/read?id=${this.data.nextSection.id}` })
- }
- },
-
- // 跳转到推广中心
- goToReferral() {
- wx.navigateTo({ url: '/pages/referral/referral' })
- },
-
- // 生成海报
- async generatePoster() {
- wx.showLoading({ title: '生成中...' })
- this.setData({ showPosterModal: true, isGeneratingPoster: true })
-
- try {
- const ctx = wx.createCanvasContext('posterCanvas', this)
- const { section, contentParagraphs, sectionId } = this.data
- const userInfo = app.globalData.userInfo
- const userId = userInfo?.id || ''
-
- // 获取小程序码(带推荐人参数)
- let qrcodeImage = null
- try {
- const scene = userId ? `id=${sectionId}&ref=${userId.slice(0,10)}` : `id=${sectionId}`
- const qrRes = await app.request('/api/miniprogram/qrcode', {
- method: 'POST',
- data: { scene, page: 'pages/read/read', width: 280 }
- })
- if (qrRes.success && qrRes.image) {
- qrcodeImage = qrRes.image
- }
- } catch (e) {
- console.log('[Poster] 获取小程序码失败,使用占位符')
- }
-
- // 海报尺寸 300x450
- const width = 300
- const height = 450
-
- // 背景渐变
- const grd = ctx.createLinearGradient(0, 0, 0, height)
- grd.addColorStop(0, '#1a1a2e')
- grd.addColorStop(1, '#16213e')
- ctx.setFillStyle(grd)
- ctx.fillRect(0, 0, width, height)
-
- // 顶部装饰条
- ctx.setFillStyle('#00CED1')
- ctx.fillRect(0, 0, width, 4)
-
- // 标题区域
- ctx.setFillStyle('#ffffff')
- ctx.setFontSize(14)
- ctx.fillText('📚 Soul创业派对', 20, 35)
-
- // 章节标题
- ctx.setFontSize(18)
- ctx.setFillStyle('#ffffff')
- const title = section?.title || '精彩内容'
- const titleLines = this.wrapText(ctx, title, width - 40, 18)
- let y = 70
- titleLines.forEach(line => {
- ctx.fillText(line, 20, y)
- y += 26
- })
-
- // 分隔线
- ctx.setStrokeStyle('rgba(255,255,255,0.1)')
- ctx.beginPath()
- ctx.moveTo(20, y + 10)
- ctx.lineTo(width - 20, y + 10)
- ctx.stroke()
-
- // 内容摘要
- ctx.setFontSize(12)
- ctx.setFillStyle('rgba(255,255,255,0.8)')
- y += 30
- const summary = contentParagraphs.slice(0, 3).join(' ').slice(0, 150) + '...'
- const summaryLines = this.wrapText(ctx, summary, width - 40, 12)
- summaryLines.slice(0, 6).forEach(line => {
- ctx.fillText(line, 20, y)
- y += 20
- })
-
- // 底部区域背景
- ctx.setFillStyle('rgba(0,206,209,0.1)')
- ctx.fillRect(0, height - 100, width, 100)
-
- // 左侧提示文字
- ctx.setFillStyle('#ffffff')
- ctx.setFontSize(13)
- ctx.fillText('长按识别小程序码', 20, height - 60)
- ctx.setFillStyle('rgba(255,255,255,0.6)')
- ctx.setFontSize(11)
- ctx.fillText('长按小程序码阅读全文', 20, height - 38)
-
- // 绘制小程序码或占位符
- const drawQRCode = () => {
- return new Promise((resolve) => {
- if (qrcodeImage) {
- // 下载base64图片并绘制
- const fs = wx.getFileSystemManager()
- const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`
- const base64Data = qrcodeImage.replace(/^data:image\/\w+;base64,/, '')
-
- fs.writeFile({
- filePath,
- data: base64Data,
- encoding: 'base64',
- success: () => {
- ctx.drawImage(filePath, width - 85, height - 85, 70, 70)
- resolve()
- },
- fail: () => {
- this.drawQRPlaceholder(ctx, width, height)
- resolve()
- }
- })
- } else {
- this.drawQRPlaceholder(ctx, width, height)
- resolve()
- }
- })
- }
-
- await drawQRCode()
-
- ctx.draw(true, () => {
- wx.hideLoading()
- this.setData({ isGeneratingPoster: false })
- })
- } catch (e) {
- console.error('生成海报失败:', e)
- wx.hideLoading()
- wx.showToast({ title: '生成失败', icon: 'none' })
- this.setData({ showPosterModal: false, isGeneratingPoster: false })
- }
- },
-
- // 绘制小程序码占位符
- drawQRPlaceholder(ctx, width, height) {
- ctx.setFillStyle('#ffffff')
- ctx.beginPath()
- ctx.arc(width - 50, height - 50, 35, 0, Math.PI * 2)
- ctx.fill()
- ctx.setFillStyle('#00CED1')
- ctx.setFontSize(9)
- ctx.fillText('扫码', width - 57, height - 52)
- ctx.fillText('阅读', width - 57, height - 40)
- },
-
- // 文字换行处理
- wrapText(ctx, text, maxWidth, fontSize) {
- const lines = []
- let line = ''
- for (let i = 0; i < text.length; i++) {
- const testLine = line + text[i]
- const metrics = ctx.measureText(testLine)
- if (metrics.width > maxWidth && line) {
- lines.push(line)
- line = text[i]
- } else {
- line = testLine
- }
- }
- if (line) lines.push(line)
- return lines
- },
-
- // 关闭海报弹窗
- closePosterModal() {
- this.setData({ showPosterModal: false })
- },
-
- // 保存海报到相册
- savePoster() {
- wx.canvasToTempFilePath({
- canvasId: 'posterCanvas',
- success: (res) => {
- wx.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success: () => {
- wx.showToast({ title: '已保存到相册', icon: 'success' })
- this.setData({ showPosterModal: false })
- },
- fail: (err) => {
- if (err.errMsg.includes('auth deny')) {
- wx.showModal({
- title: '提示',
- content: '需要相册权限才能保存海报',
- confirmText: '去设置',
- success: (res) => {
- if (res.confirm) {
- wx.openSetting()
- }
- }
- })
- } else {
- wx.showToast({ title: '保存失败', icon: 'none' })
- }
- }
- })
- },
- fail: () => {
- wx.showToast({ title: '生成图片失败', icon: 'none' })
- }
- }, this)
- },
-
- // 阻止冒泡
- stopPropagation() {}
-})
diff --git a/归档/miniprogram/pages/read/read.json b/归档/miniprogram/pages/read/read.json
deleted file mode 100644
index d182eac9..00000000
--- a/归档/miniprogram/pages/read/read.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "usingComponents": {
- "icon": "/components/icon/icon"
- },
- "enablePullDownRefresh": false,
- "backgroundTextStyle": "light",
- "backgroundColor": "#000000",
- "navigationStyle": "custom"
-}
diff --git a/归档/miniprogram/pages/read/read.wxml b/归档/miniprogram/pages/read/read.wxml
deleted file mode 100644
index a17cc3b3..00000000
--- a/归档/miniprogram/pages/read/read.wxml
+++ /dev/null
@@ -1,300 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- ←
-
-
- {{partTitle}}
- {{chapterTitle}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
-
- 上一篇
- {{prevSection.title}}
-
-
-
-
- 下一篇
-
- {{nextSection.title}}
- →
-
-
-
- 已是最后一篇 🎉
-
-
-
-
-
-
-
-
- 🖼️
- 生成海报
-
-
-
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
-
-
- 🔒
- 登录后继续阅读
- 已阅读20%,登录后查看完整内容
-
-
- 立即登录
-
-
-
-
-
-
-
- 上一篇
- 章节 {{prevSection.id}}
-
-
-
-
- 下一篇
-
- {{nextSection.title}}
- →
-
-
-
- 已是最后一篇 🎉
-
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
-
-
- 🔒
- 解锁完整内容
- 已阅读20%,购买后继续阅读
-
-
-
-
-
- 购买本章
- ¥{{section && section.price != null ? section.price : sectionPrice}}
-
-
-
-
-
- ✨
- 解锁全部 {{totalSections}} 章
-
-
- ¥{{fullBookPrice || 9.9}}
- 省82%
-
-
-
-
- 分享给好友一起学习,还能赚取佣金
-
-
-
-
-
-
- 上一篇
- 章节 {{prevSection.id}}
-
-
-
-
- 下一篇
-
- {{nextSection.title}}
- →
-
-
-
- 已是最后一篇 🎉
-
-
-
-
-
-
-
-
- {{item}}
-
-
-
-
-
-
-
- ⚠️
- 网络异常
- 无法确认权限,请检查网络后重试
-
-
- 重新加载
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 💾
- 保存到相册
-
-
-
- 长按海报可直接分享到微信
-
-
-
-
-
-
- ✕
- 🔐
- 登录 Soul创业派对
- 登录后可购买章节、解锁更多内容
-
-
-
-
- {{agreeProtocol ? '✓' : ''}}
- 我已阅读并同意
- 《用户协议》
- 和
- 《隐私政策》
-
-
-
-
-
-
-
-
- 支付处理中...
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/read/read.wxss b/归档/miniprogram/pages/read/read.wxss
deleted file mode 100644
index 8b0ecca2..00000000
--- a/归档/miniprogram/pages/read/read.wxss
+++ /dev/null
@@ -1,984 +0,0 @@
-/**
- * Soul创业实验 - 阅读页样式
- * 1:1还原Web版本UI
- */
-
-.page {
- min-height: 100vh;
- background: #000000;
-}
-
-/* ===== 阅读进度条 ===== */
-.progress-bar-fixed {
- position: fixed;
- left: 0;
- right: 0;
- height: 4rpx;
- background: #1c1c1e;
- z-index: 200;
-}
-
-.progress-fill {
- height: 100%;
- background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%);
- transition: width 0.15s ease;
-}
-
-/* ===== 导航栏 ===== */
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(0, 0, 0, 0.8);
- backdrop-filter: blur(40rpx);
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.nav-content {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 24rpx;
- height: 88rpx;
-}
-
-.nav-back, .nav-right-placeholder {
- width: 72rpx;
- height: 72rpx;
- flex-shrink: 0;
-}
-
-.nav-back {
- border-radius: 50%;
- background: #1c1c1e;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.nav-right-placeholder {
- /* 占位保持标题居中 */
-}
-
-.back-arrow {
- font-size: 36rpx;
- color: rgba(255, 255, 255, 0.8);
-}
-
-.nav-info {
- flex: 1;
- text-align: center;
- padding: 0 16rpx;
-}
-
-.nav-part {
- font-size: 20rpx;
- color: rgba(255, 255, 255, 0.4);
- display: block;
-}
-
-.nav-chapter {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.nav-placeholder {
- width: 100%;
-}
-
-/* ===== 阅读内容 ===== */
-.read-content {
- max-width: 750rpx;
- margin: 0 auto;
- padding: 48rpx 40rpx 200rpx;
-}
-
-/* ===== 章节标题 ===== */
-.chapter-header {
- margin-bottom: 48rpx;
-}
-
-.chapter-meta {
- display: flex;
- align-items: center;
- gap: 16rpx;
- margin-bottom: 24rpx;
-}
-
-.chapter-id {
- font-size: 28rpx;
- font-weight: 500;
- color: #00CED1;
- background: rgba(0, 206, 209, 0.1);
- padding: 8rpx 24rpx;
- border-radius: 32rpx;
-}
-
-.tag {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-size: 22rpx;
- padding: 6rpx 16rpx;
- min-width: 80rpx;
- border-radius: 8rpx;
- box-sizing: border-box;
- text-align: center;
-}
-
-.tag-free {
- background: rgba(0, 206, 209, 0.1);
- color: #00CED1;
-}
-
-.chapter-title {
- font-size: 44rpx;
- font-weight: 700;
- color: #ffffff;
- line-height: 1.4;
-}
-
-/* ===== 加载状态 ===== */
-.loading-state {
- display: flex;
- flex-direction: column;
- gap: 32rpx;
-}
-
-.skeleton {
- height: 32rpx;
- background: linear-gradient(90deg, #1c1c1e 25%, #2c2c2e 50%, #1c1c1e 75%);
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s ease-in-out infinite;
- border-radius: 8rpx;
-}
-
-.skeleton-1 { width: 75%; }
-.skeleton-2 { width: 90%; }
-.skeleton-3 { width: 65%; }
-.skeleton-4 { width: 85%; }
-.skeleton-5 { width: 70%; }
-
-@keyframes skeleton-loading {
- 0% { background-position: 200% 0; }
- 100% { background-position: -200% 0; }
-}
-
-/* ===== 文章内容 ===== */
-.article {
- color: rgba(255, 255, 255, 0.85);
- font-size: 34rpx;
- line-height: 1.9;
-}
-
-.paragraph {
- margin-bottom: 48rpx;
- text-align: justify;
-}
-
-.preview {
- position: relative;
-}
-
-/* ===== 渐变遮罩 ===== */
-.fade-mask {
- position: absolute;
- bottom: 0;
- left: -40rpx;
- right: -40rpx;
- height: 300rpx;
- background: linear-gradient(to top, #000000 0%, transparent 100%);
- pointer-events: none;
-}
-
-/* ===== 付费墙 ===== */
-.paywall {
- position: relative;
- z-index: 10;
- margin-top: 48rpx;
- padding: 48rpx;
- background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%);
- border-radius: 32rpx;
- border: 2rpx solid rgba(0, 206, 209, 0.2);
-}
-
-.paywall-icon {
- width: 128rpx;
- height: 128rpx;
- margin: 0 auto 32rpx;
- background: rgba(0, 206, 209, 0.1);
- border-radius: 32rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 64rpx;
-}
-
-.paywall-title {
- font-size: 40rpx;
- font-weight: 600;
- color: #ffffff;
- text-align: center;
- display: block;
- margin-bottom: 16rpx;
-}
-
-.paywall-desc {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
- text-align: center;
- display: block;
- margin-bottom: 48rpx;
-}
-
-/* ===== 购买选项 ===== */
-.purchase-options {
- display: flex;
- flex-direction: column;
- gap: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.purchase-btn {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 28rpx 32rpx;
- border-radius: 24rpx;
-}
-
-.purchase-section {
- background: #2c2c2e;
- border: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.purchase-fullbook {
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- box-shadow: 0 8rpx 32rpx rgba(0, 206, 209, 0.3);
-}
-
-.purchase-section .btn-label {
- font-size: 28rpx;
- color: #ffffff;
-}
-
-.purchase-section .btn-price {
- font-size: 28rpx;
- font-weight: 600;
-}
-
-.brand-color {
- color: #00CED1;
-}
-
-.purchase-fullbook .btn-left {
- display: flex;
- align-items: center;
- gap: 8rpx;
-}
-
-.purchase-fullbook .btn-sparkle {
- font-size: 28rpx;
-}
-
-.purchase-fullbook .btn-label {
- font-size: 28rpx;
- color: #ffffff;
- font-weight: 500;
-}
-
-.purchase-fullbook .btn-right {
- text-align: right;
-}
-
-.purchase-fullbook .btn-price {
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
-}
-
-.purchase-fullbook .btn-discount {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.7);
- margin-left: 8rpx;
-}
-
-.paywall-tip {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.4);
- text-align: center;
- display: block;
-}
-
-/* ===== 章节导航 ===== */
-.chapter-nav {
- margin-top: 96rpx;
- padding-top: 64rpx;
- border-top: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.nav-buttons {
- display: flex;
- gap: 24rpx;
- margin-bottom: 48rpx;
-}
-
-.nav-btn {
- flex: 1;
- padding: 24rpx;
- border-radius: 24rpx;
- max-width: 48%;
-}
-
-.nav-btn-placeholder {
- flex: 1;
- max-width: 48%;
-}
-
-.nav-prev {
- background: #1c1c1e;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
-}
-
-.nav-next {
- background: linear-gradient(90deg, rgba(0, 206, 209, 0.1) 0%, rgba(32, 178, 170, 0.1) 100%);
- border: 2rpx solid rgba(0, 206, 209, 0.2);
-}
-
-.nav-end {
- background: #1c1c1e;
- border: 2rpx solid rgba(255, 255, 255, 0.05);
- text-align: center;
-}
-
-.nav-disabled {
- opacity: 0.5;
-}
-
-.btn-label {
- font-size: 20rpx;
- color: rgba(255, 255, 255, 0.5);
- display: block;
- margin-bottom: 4rpx;
-}
-
-.nav-next .btn-label {
- color: #00CED1;
-}
-
-.btn-title {
- font-size: 24rpx;
- color: #ffffff;
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.btn-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.btn-arrow {
- font-size: 24rpx;
- color: #00CED1;
- flex-shrink: 0;
- margin-left: 8rpx;
-}
-
-.btn-end-text {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 分享操作区 ===== */
-.action-section {
- margin-top: 48rpx;
-}
-
-.action-row-inline {
- display: flex;
- gap: 16rpx;
-}
-
-.action-btn-inline {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8rpx;
- padding: 24rpx 16rpx;
- border-radius: 16rpx;
- border: none;
- background: transparent;
- line-height: normal;
- box-sizing: border-box;
-}
-
-.action-btn-inline::after {
- border: none;
-}
-
-.btn-share-inline {
- background: rgba(7, 193, 96, 0.15);
- border: 2rpx solid rgba(7, 193, 96, 0.3);
-}
-
-.btn-poster-inline {
- background: rgba(255, 215, 0, 0.15);
- border: 2rpx solid rgba(255, 215, 0, 0.3);
-}
-
-
-.action-icon-small {
- font-size: 28rpx;
-}
-
-.action-text-small {
- font-size: 24rpx;
- color: #ffffff;
- font-weight: 500;
-}
-
-/* ===== 推广提示区 ===== */
-.promo-section {
- margin-top: 32rpx;
-}
-
-.promo-card {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 32rpx;
- background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, rgba(255, 165, 0, 0.05) 100%);
- border: 2rpx solid rgba(255, 215, 0, 0.2);
- border-radius: 24rpx;
-}
-
-.promo-left {
- display: flex;
- align-items: center;
- gap: 20rpx;
-}
-
-.promo-icon {
- width: 80rpx;
- height: 80rpx;
- background: rgba(255, 215, 0, 0.2);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 36rpx;
-}
-
-.promo-info {
- flex: 1;
-}
-
-.promo-title {
- font-size: 30rpx;
- color: #ffffff;
- font-weight: 600;
- display: block;
-}
-
-.promo-desc {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.5);
- display: block;
- margin-top: 8rpx;
-}
-
-.promo-right {
- padding-left: 20rpx;
-}
-
-.promo-arrow {
- font-size: 32rpx;
- color: #FFD700;
-}
-
-/* ===== 弹窗 ===== */
-.modal-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.6);
- backdrop-filter: blur(20rpx);
- display: flex;
- align-items: flex-end;
- justify-content: center;
- z-index: 1000;
-}
-
-.modal-content {
- width: 100%;
- max-width: 750rpx;
- background: #1c1c1e;
- border-radius: 48rpx 48rpx 0 0;
- padding: 48rpx;
- padding-bottom: calc(48rpx + env(safe-area-inset-bottom));
- animation: slideUp 0.3s ease;
-}
-
-@keyframes slideUp {
- from { transform: translateY(100%); }
- to { transform: translateY(0); }
-}
-
-.modal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 32rpx;
-}
-
-.modal-title {
- font-size: 36rpx;
- font-weight: 600;
- color: #ffffff;
-}
-
-.modal-close {
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 分享弹窗 ===== */
-.share-link-box {
- padding: 32rpx;
- background: rgba(0, 0, 0, 0.3);
- border: 2rpx solid rgba(255, 255, 255, 0.1);
- border-radius: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.link-label {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.5);
- display: block;
- margin-bottom: 16rpx;
-}
-
-.link-url {
- font-size: 26rpx;
- color: #00CED1;
- display: block;
- word-break: break-all;
- font-family: monospace;
-}
-
-.link-tip {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
- display: block;
- margin-top: 16rpx;
-}
-
-.share-buttons {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.share-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 16rpx;
- padding: 24rpx;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 24rpx;
- border: none;
- line-height: normal;
-}
-
-.share-btn::after {
- border: none;
-}
-
-.share-btn-icon {
- width: 96rpx;
- height: 96rpx;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 40rpx;
-}
-
-.icon-copy {
- background: rgba(0, 206, 209, 0.2);
-}
-
-.icon-wechat {
- background: rgba(7, 193, 96, 0.2);
- color: #07C160;
- font-size: 32rpx;
-}
-
-.icon-poster {
- background: rgba(255, 215, 0, 0.2);
-}
-
-.share-btn-text {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 支付弹窗 ===== */
-.payment-info {
- padding: 24rpx;
- background: rgba(0, 0, 0, 0.3);
- border-radius: 24rpx;
- margin-bottom: 32rpx;
-}
-
-.payment-type {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.6);
- display: block;
- margin-bottom: 16rpx;
-}
-
-.payment-amount {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.amount-label {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-.amount-value {
- font-size: 48rpx;
- font-weight: 700;
- color: #00CED1;
-}
-
-.payment-methods {
- margin-bottom: 32rpx;
-}
-
-.method-item {
- display: flex;
- align-items: center;
- gap: 16rpx;
- padding: 24rpx;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 16rpx;
- border: 2rpx solid transparent;
-}
-
-.method-active {
- border-color: #07C160;
-}
-
-.method-icon {
- width: 48rpx;
- height: 48rpx;
- background: #07C160;
- color: #ffffff;
- font-size: 24rpx;
- border-radius: 8rpx;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.method-name {
- flex: 1;
- font-size: 28rpx;
- color: #ffffff;
-}
-
-.method-check {
- color: #07C160;
- font-size: 28rpx;
-}
-
-.btn-primary {
- width: 100%;
- padding: 28rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- color: #ffffff;
- font-size: 32rpx;
- font-weight: 600;
- border-radius: 24rpx;
- text-align: center;
- margin-bottom: 16rpx;
-}
-
-.payment-notice {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.3);
- text-align: center;
- display: block;
-}
-
-/* ===== 登录提示 ===== */
-.login-prompt {
- margin-top: 32rpx;
-}
-
-.login-btn {
- padding: 28rpx;
- background: rgba(255, 255, 255, 0.1);
- border: 2rpx solid rgba(255, 255, 255, 0.2);
- border-radius: 24rpx;
- text-align: center;
-}
-
-.login-btn-text {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* ===== 登录弹窗 ===== */
-.login-modal {
- padding: 48rpx 32rpx;
- text-align: center;
-}
-
-.login-icon {
- font-size: 80rpx;
- display: block;
- margin-bottom: 24rpx;
-}
-
-.login-title {
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
- display: block;
- margin-bottom: 16rpx;
-}
-
-.login-desc {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.5);
- display: block;
- margin-bottom: 48rpx;
-}
-
-.btn-wechat {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 16rpx;
- padding: 28rpx;
- background: #07C160;
- color: #ffffff;
- font-size: 30rpx;
- font-weight: 600;
- border-radius: 24rpx;
- margin-bottom: 20rpx;
- border: none;
-}
-
-.btn-wechat::after {
- border: none;
-}
-
-.btn-wechat-icon {
- width: 40rpx;
- height: 40rpx;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 8rpx;
- font-size: 24rpx;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.btn-phone {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 16rpx;
- padding: 28rpx;
- background: rgba(255, 255, 255, 0.05);
- color: #ffffff;
- font-size: 30rpx;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.1);
-}
-
-.btn-phone::after {
- border: none;
-}
-
-.btn-phone-icon {
- font-size: 32rpx;
-}
-
-.login-agree-row {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: center;
- margin-top: 32rpx;
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.5);
-}
-.agree-checkbox {
- width: 32rpx;
- height: 32rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.5);
- border-radius: 6rpx;
- margin-right: 12rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 22rpx;
- color: #fff;
- flex-shrink: 0;
-}
-.agree-checked {
- background: #00CED1;
- border-color: #00CED1;
-}
-.agree-text { color: rgba(255, 255, 255, 0.6); }
-.agree-link {
- color: #00CED1;
- text-decoration: underline;
- padding: 0 4rpx;
-}
-.btn-wechat-disabled { opacity: 0.6; }
-
-/* ===== 支付中加载 ===== */
-.loading-box {
- background: rgba(0, 0, 0, 0.8);
- border-radius: 24rpx;
- padding: 48rpx 64rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 24rpx;
-}
-
-.loading-spinner {
- width: 64rpx;
- height: 64rpx;
- border: 4rpx solid rgba(255, 255, 255, 0.2);
- border-top-color: #00CED1;
- border-radius: 50%;
- animation: spin 0.8s linear infinite;
-}
-
-@keyframes spin {
- to { transform: rotate(360deg); }
-}
-
-.loading-text {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.8);
-}
-
-/* ===== 海报弹窗 ===== */
-.poster-modal {
- padding-bottom: calc(64rpx + env(safe-area-inset-bottom));
-}
-
-.poster-preview {
- display: flex;
- justify-content: center;
- margin: 32rpx 0;
- padding: 24rpx;
- background: rgba(0, 0, 0, 0.3);
- border-radius: 24rpx;
-}
-
-.poster-canvas {
- border-radius: 16rpx;
- box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.5);
-}
-
-.poster-actions {
- display: flex;
- gap: 24rpx;
- margin-bottom: 24rpx;
-}
-
-.poster-btn {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 12rpx;
- padding: 28rpx;
- border-radius: 24rpx;
- font-size: 30rpx;
- font-weight: 500;
-}
-
-.btn-save {
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- color: #ffffff;
-}
-
-.btn-icon {
- font-size: 32rpx;
-}
-
-.poster-tip {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.4);
- text-align: center;
- display: block;
-}
-
-/* ===== 右下角悬浮分享按钮 ===== */
-.fab-share {
- position: fixed;
- right: 32rpx;
- width:70rpx!important;
- bottom: calc(120rpx + env(safe-area-inset-bottom));
- height: 70rpx;
- border-radius: 60rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- box-shadow: 0 8rpx 32rpx rgba(0, 206, 209, 0.4);
- padding: 0;
- margin: 0;
- border: none;
- z-index: 9999;
- display:flex;
- align-items: center;
- justify-content: center;
- transition: transform 0.2s ease, box-shadow 0.2s ease;
-}
-
-.fab-share::after {
- border: none;
-}
-
-.fab-share:active {
- transform: scale(0.95);
- box-shadow: 0 4rpx 20rpx rgba(0, 206, 209, 0.5);
-}
-
-.fab-icon {
- padding:16rpx;
- width: 50rpx;
- height: 50rpx;
- display: block;
-}
-
diff --git a/归档/miniprogram/pages/referral/earnings-detail-styles.wxss b/归档/miniprogram/pages/referral/earnings-detail-styles.wxss
deleted file mode 100644
index a64f5b59..00000000
--- a/归档/miniprogram/pages/referral/earnings-detail-styles.wxss
+++ /dev/null
@@ -1,182 +0,0 @@
-/* ===================================
- 收益明细卡片样式 - 重构版
- 创建时间:2026-02-04
- 说明:修复布局错乱问题,优化文本显示
- =================================== */
-
-/* 收益明细卡片容器 */
-.earnings-detail-card {
- background: rgba(28, 28, 30, 0.8);
- backdrop-filter: blur(40rpx);
- border: 2rpx solid rgba(255,255,255,0.1);
- border-radius: 32rpx;
- overflow: hidden;
- margin-bottom: 24rpx;
- width: 100%;
- box-sizing: border-box;
-}
-
-/* 卡片头部 */
-.detail-header {
- padding: 40rpx 40rpx 24rpx;
- border-bottom: 2rpx solid rgba(255,255,255,0.05);
-}
-
-.detail-title {
- font-size: 30rpx;
- font-weight: 600;
- color: #fff;
-}
-
-/* 列表容器 */
-.detail-list {
- max-height: 480rpx;
- overflow-y: auto;
- padding: 16rpx 0;
-}
-
-/* ===================================
- 收益明细列表项 - 核心样式
- =================================== */
-
-/* 列表项容器 - 使用flex布局 */
-.earnings-detail-card .detail-item {
- display: flex;
- align-items: center;
- gap: 24rpx;
- padding: 24rpx 40rpx;
- background: transparent;
- border-bottom: 2rpx solid rgba(255,255,255,0.03);
- transition: background 0.3s;
-}
-
-.earnings-detail-card .detail-item:last-child {
- border-bottom: none;
-}
-
-.earnings-detail-card .detail-item:active {
- background: rgba(255, 255, 255, 0.05);
-}
-
-/* 头像容器 - 固定宽度,不收缩 */
-.earnings-detail-card .detail-avatar-wrap {
- width: 88rpx;
- height: 88rpx;
- flex-shrink: 0;
-}
-
-.earnings-detail-card .detail-avatar {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- border: 2rpx solid rgba(56, 189, 172, 0.2);
- display: block;
-}
-
-.earnings-detail-card .detail-avatar-text {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: linear-gradient(135deg, #38bdac 0%, #2da396 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 36rpx;
- font-weight: 700;
- color: #ffffff;
-}
-
-/* 详细信息容器 - 占据剩余空间,允许收缩 */
-.earnings-detail-card .detail-content {
- flex: 1;
- min-width: 0; /* 关键:允许flex子元素收缩到比内容更小 */
- display: flex;
- flex-direction: column;
- gap: 8rpx;
-}
-
-/* 顶部行:昵称 + 金额 */
-.earnings-detail-card .detail-top {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 16rpx;
-}
-
-/* 买家昵称 - 允许收缩,显示省略号 */
-.earnings-detail-card .detail-buyer {
- font-size: 28rpx;
- font-weight: 500;
- color: #ffffff;
- flex: 1;
- min-width: 0; /* 关键:允许收缩 */
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-/* 佣金金额 - 固定宽度,不收缩 */
-.earnings-detail-card .detail-amount {
- font-size: 32rpx;
- font-weight: 700;
- color: #38bdac;
- flex-shrink: 0;
- white-space: nowrap;
-}
-
-/* 商品信息行:书名 + 章节 */
-.earnings-detail-card .detail-product {
- display: flex;
- align-items: baseline;
- gap: 4rpx;
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
- min-width: 0; /* 关键:允许收缩 */
- overflow: hidden;
-}
-
-/* 书名 - 限制最大宽度,显示省略号 */
-.earnings-detail-card .detail-book {
- color: rgba(255, 255, 255, 0.7);
- font-weight: 500;
- max-width: 50%; /* 限制书名最多占一半宽度 */
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex-shrink: 1;
-}
-
-/* 章节 - 占据剩余空间,显示省略号 */
-.earnings-detail-card .detail-chapter {
- color: rgba(255, 255, 255, 0.5);
- flex: 1;
- min-width: 0; /* 关键:允许收缩 */
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-/* 时间信息 */
-.earnings-detail-card .detail-time {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.4);
-}
-
-/* ===================================
- 响应式优化
- =================================== */
-
-/* 小屏幕优化 */
-@media (max-width: 375px) {
- .earnings-detail-card .detail-buyer {
- font-size: 26rpx;
- }
-
- .earnings-detail-card .detail-amount {
- font-size: 30rpx;
- }
-
- .earnings-detail-card .detail-book {
- max-width: 45%;
- }
-}
diff --git a/归档/miniprogram/pages/referral/referral.js b/归档/miniprogram/pages/referral/referral.js
deleted file mode 100644
index b56fdc74..00000000
--- a/归档/miniprogram/pages/referral/referral.js
+++ /dev/null
@@ -1,939 +0,0 @@
-/**
- * Soul创业派对 - 分销中心页
- *
- * 可见数据:
- * - 绑定用户数(当前有效绑定)
- * - 通过链接进的人数(总访问量)
- * - 带来的付款人数(已转化购买)
- * - 收益统计(90%归分发者)
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- isLoggedIn: false,
- userInfo: null,
-
- // === 核心可见数据 ===
- bindingCount: 0, // 绑定用户数(当前有效)
- visitCount: 0, // 通过链接进的人数
- paidCount: 0, // 带来的付款人数
- unboughtCount: 0, // 待购买人数(绑定但未付款)
- expiredCount: 0, // 已过期人数
-
- // === 收益数据 ===
- totalCommission: 0, // 累计佣金总额(所有获得的佣金)
- availableEarnings: 0, // 可提现金额(未申请提现的佣金)- 字符串格式用于显示
- availableEarningsNum: 0, // 可提现金额 - 数字格式用于判断
- pendingWithdrawAmount: 0, // 待审核金额(已申请提现但未审核)
- withdrawnEarnings: 0, // 已提现金额
- earnings: 0, // 已结算收益(保留兼容)
- pendingEarnings: 0, // 待结算收益(保留兼容)
- shareRate: 90, // 分成比例(90%),从 referral/data 或 config 获取
- minWithdrawAmount: 10, // 最低提现金额,从 referral/data 获取
- bindingDays: 30, // 绑定期天数,从 referral/data 获取
- userDiscount: 5, // 好友购买优惠%,从 referral/data 获取
- hasWechatId: false, // 是否已绑定微信号(未绑定时需引导去设置)
-
- // === 统计数据 ===
- referralCount: 0, // 总推荐人数
- expiringCount: 0, // 即将过期人数
-
- // 邀请码
- referralCode: '',
-
- // 绑定用户列表
- showBindingList: true,
- activeTab: 'active',
- activeBindings: [],
- convertedBindings: [],
- expiredBindings: [],
- currentBindings: [],
- totalBindings: 0,
-
- // 收益明细
- earningsDetails: [],
-
- // 海报
- showPosterModal: false,
- isGeneratingPoster: false,
- posterQrSrc: '',
- posterReferralLink: '',
- posterNickname: '',
- posterNicknameInitial: '',
- posterCaseCount: 62,
-
- },
-
- onLoad() {
- this.setData({ statusBarHeight: app.globalData.statusBarHeight })
- this.initData()
- },
-
- onShow() {
- // 从设置页返回时同步微信号绑定状态,便于提现按钮立即更新
- const hasWechatId = !!(app.globalData.userInfo?.wechat || app.globalData.userInfo?.wechatId || wx.getStorageSync('user_wechat'))
- this.setData({ hasWechatId })
- this.initData()
- },
-
- // 初始化数据
- async initData() {
- const { isLoggedIn, userInfo } = app.globalData
- if (isLoggedIn && userInfo) {
- // 显示加载提示
- wx.showLoading({
- title: '加载中...',
- mask: true // 防止触摸穿透
- })
-
- // 生成邀请码
- const referralCode = userInfo.referralCode || 'SOUL' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6)
-
- console.log('[Referral] 开始加载分销数据,userId:', userInfo.id)
-
- // 从API获取真实数据
- let realData = null
- try {
- // app.request 第一个参数是 URL 字符串(会自动拼接 baseUrl)
- const res = await app.request('/api/miniprogram/referral/data?userId=' + userInfo.id)
- console.log('[Referral] API返回:', JSON.stringify(res).substring(0, 200))
-
- if (res && res.success && res.data) {
- realData = res.data
- console.log('[Referral] ✅ 获取推广数据成功')
- console.log('[Referral] - bindingCount:', realData.bindingCount)
- console.log('[Referral] - paidCount:', realData.paidCount)
- console.log('[Referral] - earnings:', realData.earnings)
- console.log('[Referral] - expiringCount:', realData.stats?.expiringCount)
- } else {
- console.log('[Referral] ❌ API返回格式错误:', res?.error || 'unknown')
- }
- } catch (e) {
- console.log('[Referral] ❌ API调用失败:', e.message || e)
- console.log('[Referral] 错误详情:', e)
- }
-
- // 使用真实数据或默认值
- let activeBindings = realData?.activeUsers || []
- let convertedBindings = realData?.convertedUsers || []
- let expiredBindings = realData?.expiredUsers || []
-
- console.log('[Referral] activeBindings:', activeBindings.length)
- console.log('[Referral] convertedBindings:', convertedBindings.length)
- console.log('[Referral] expiredBindings:', expiredBindings.length)
-
- // 计算即将过期的数量(7天内)
- const expiringCount = realData?.stats?.expiringCount || activeBindings.filter(b => b.daysRemaining <= 7 && b.daysRemaining > 0).length
-
- console.log('[Referral] expiringCount:', expiringCount)
-
- // 计算各类统计
- const bindingCount = realData?.bindingCount || activeBindings.length
- const paidCount = realData?.paidCount || convertedBindings.length
- const expiredCount = realData?.expiredCount || expiredBindings.length
- const unboughtCount = bindingCount - paidCount // 绑定中但未付款的
-
- // 格式化用户数据
- const formatUser = (user, type) => {
- const formatted = {
- id: user.id,
- nickname: user.nickname || '用户' + (user.id || '').slice(-4),
- avatar: user.avatar,
- status: type,
- daysRemaining: user.daysRemaining || 0,
- bindingDate: user.bindingDate ? this.formatDate(user.bindingDate) : '--',
- expiryDate: user.expiryDate ? this.formatDate(user.expiryDate) : '--',
- commission: (user.commission || 0).toFixed(2),
- orderAmount: (user.orderAmount || 0).toFixed(2),
- purchaseCount: user.purchaseCount || 0,
- conversionDate: user.conversionDate ? this.formatDate(user.conversionDate) : '--'
- }
- console.log('[Referral] 格式化用户:', formatted.nickname, formatted.status, formatted.daysRemaining + '天')
- return formatted
- }
-
- // 格式化金额(保留两位小数)
- const formatMoney = (num) => {
- return typeof num === 'number' ? num.toFixed(2) : '0.00'
- }
-
- // ✅ 可提现金额 = 累计佣金 - 已提现金额 - 待审核金额,且不低于 0(防止数据不同步时出现负数)
- const totalCommissionNum = realData?.totalCommission || 0
- const withdrawnNum = realData?.withdrawnEarnings || 0
- const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
- const availableEarningsNum = Math.max(0, totalCommissionNum - withdrawnNum - pendingWithdrawNum)
- const minWithdrawAmount = realData?.minWithdrawAmount || 10
-
- console.log('=== [Referral] 收益计算(完整版)===')
- console.log('累计佣金 (totalCommission):', totalCommissionNum)
- console.log('已提现金额 (withdrawnEarnings):', withdrawnNum)
- console.log('待审核金额 (pendingWithdrawAmount):', pendingWithdrawNum)
- console.log('可提现金额 = 累计 - 已提现 - 待审核 =', totalCommissionNum, '-', withdrawnNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
- console.log('最低提现金额 (minWithdrawAmount):', minWithdrawAmount)
- console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
- console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
-
- const hasWechatId = !!(userInfo?.wechat || userInfo?.wechatId || wx.getStorageSync('user_wechat'))
- this.setData({
- isLoggedIn: true,
- userInfo,
- hasWechatId,
-
- // 核心可见数据
- bindingCount,
- visitCount: realData?.visitCount || 0,
- paidCount,
- unboughtCount: expiringCount, // "即将过期"显示的是 expiringCount
- expiredCount,
-
- // 收益数据 - 格式化为两位小数
- totalCommission: formatMoney(totalCommissionNum),
- availableEarnings: formatMoney(availableEarningsNum), // ✅ 使用计算后的可提现金额
- availableEarningsNum: availableEarningsNum, // ✅ 数字格式用于按钮判断
- pendingWithdrawAmount: formatMoney(pendingWithdrawNum),
- withdrawnEarnings: formatMoney(realData?.withdrawnEarnings || 0),
- earnings: formatMoney(realData?.earnings || 0),
- pendingEarnings: formatMoney(realData?.pendingEarnings || 0),
- shareRate: realData?.shareRate ?? 90,
- minWithdrawAmount: minWithdrawAmount,
- bindingDays: realData?.bindingDays ?? 30,
- userDiscount: realData?.userDiscount ?? 5,
-
- // 统计
- referralCount: realData?.referralCount || realData?.stats?.totalBindings || activeBindings.length + convertedBindings.length,
- expiringCount,
-
- referralCode,
- activeBindings: activeBindings.map(u => formatUser(u, 'active')),
- convertedBindings: convertedBindings.map(u => formatUser(u, 'converted')),
- expiredBindings: expiredBindings.map(u => formatUser(u, 'expired')),
- currentBindings: activeBindings.map(u => formatUser(u, 'active')),
- totalBindings: activeBindings.length + convertedBindings.length + expiredBindings.length,
-
- // 收益明细
- earningsDetails: (realData?.earningsDetails || []).map(item => {
- // 解析商品描述,获取书名和章节
- const productInfo = this.parseProductDescription(item.description, item.productType)
-
- return {
- id: item.id,
- productType: item.productType,
- bookTitle: productInfo.bookTitle,
- chapterTitle: productInfo.chapterTitle,
- commission: (item.commission || 0).toFixed(2),
- payTime: item.payTime ? this.formatDate(item.payTime) : '--',
- buyerNickname: item.buyerNickname || '用户',
- buyerAvatar: item.buyerAvatar
- }
- })
- })
-
-
- console.log('[Referral] ✅ 数据设置完成')
- console.log('[Referral] - 绑定中:', this.data.bindingCount)
- console.log('[Referral] - 即将过期:', this.data.expiringCount)
- console.log('[Referral] - 收益:', this.data.earnings)
-
- console.log('=== [Referral] 按钮状态验证 ===')
- console.log('累计佣金 (totalCommission):', this.data.totalCommission)
- console.log('待审核金额 (pendingWithdrawAmount):', this.data.pendingWithdrawAmount)
- console.log('可提现金额 (availableEarnings 显示):', this.data.availableEarnings)
- console.log('可提现金额 (availableEarningsNum 判断):', this.data.availableEarningsNum, typeof this.data.availableEarningsNum)
- console.log('最低提现金额 (minWithdrawAmount):', this.data.minWithdrawAmount, typeof this.data.minWithdrawAmount)
- console.log('按钮启用条件:', this.data.availableEarningsNum, '>=', this.data.minWithdrawAmount, '=', this.data.availableEarningsNum >= this.data.minWithdrawAmount)
- console.log('✅ 最终结果: 按钮应该', this.data.availableEarningsNum >= this.data.minWithdrawAmount ? '🟢 启用' : '⚫ 禁用')
-
- // 隐藏加载提示
- wx.hideLoading()
- } else {
- // 未登录时也隐藏loading
- this.setData({ isLoading: false })
- }
- },
-
- // 切换Tab
- switchTab(e) {
- const tab = e.currentTarget.dataset.tab
- let currentBindings = []
-
- if (tab === 'active') {
- currentBindings = this.data.activeBindings
- } else if (tab === 'converted') {
- currentBindings = this.data.convertedBindings
- } else {
- currentBindings = this.data.expiredBindings
- }
-
- this.setData({ activeTab: tab, currentBindings })
- },
-
- // 切换绑定列表显示
- toggleBindingList() {
- this.setData({ showBindingList: !this.data.showBindingList })
- },
-
- // 复制邀请链接
- copyLink() {
- const link = `https://soul.quwanzhi.com/?ref=${this.data.referralCode}`
- wx.setClipboardData({
- data: link,
- success: () => wx.showToast({ title: '链接已复制', icon: 'success' })
- })
- },
-
- // 分享到朋友圈 - 1:1 迁移 Next.js 的 handleShareToWechat
- shareToWechat() {
- const { referralCode } = this.data
- const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
-
- // 与 Next.js 完全相同的文案
- const shareText = `📖 推荐一本好书《一场SOUL的创业实验场》
-
-这是卡若每天早上6-9点在Soul派对房分享的真实商业故事,55个真实案例,讲透创业的底层逻辑。
-
-👉 点击阅读: ${referralLink}
-
-#创业 #商业思维 #Soul派对`
-
- wx.setClipboardData({
- data: shareText,
- success: () => {
- wx.showModal({
- title: '朋友圈文案已复制!',
- content: '打开微信 → 发朋友圈 → 粘贴即可',
- showCancel: false,
- confirmText: '知道了'
- })
- }
- })
- },
-
- // 更多分享方式 - 1:1 迁移 Next.js 的 handleShare
- handleMoreShare() {
- const { referralCode } = this.data
- const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
-
- // 与 Next.js 完全相同的文案
- const shareText = `我正在读《一场SOUL的创业实验场》,每天6-9点的真实商业故事,推荐给你!${referralLink}`
-
- wx.setClipboardData({
- data: shareText,
- success: () => {
- wx.showToast({
- title: '分享文案已复制',
- icon: 'success',
- duration: 2000
- })
- }
- })
- },
-
- // 生成推广海报 - 1:1 对齐 Next.js 设计
- async generatePoster() {
- wx.showLoading({ title: '生成中...', mask: true })
- this.setData({ showPosterModal: true, isGeneratingPoster: true })
-
- try {
- const { referralCode, userInfo } = this.data
- const nickname = userInfo?.nickname || '用户'
- const scene = `ref=${referralCode}`
-
- console.log('[Poster] 请求小程序码, scene:', scene)
-
- // 调用后端接口生成「小程序码」(官方 getwxacodeunlimit),不再使用 H5 二维码
- const res = await app.request('/api/miniprogram/qrcode', {
- method: 'POST',
- data: {
- scene, // ref=XXXX
- page: 'pages/index/index',
- width: 280,
- },
- })
-
- if (!res || !res.success || !res.image) {
- console.error('[Poster] 生成小程序码失败:', res)
- throw new Error(res?.error || '生成小程序码失败')
- }
-
- // 后端返回的是 data:image/png;base64,... 需要先写入本地临时文件,再作为 的 src
- const base64Data = String(res.image).replace(/^data:image\/\w+;base64,/, '')
- const fs = wx.getFileSystemManager()
- const filePath = `${wx.env.USER_DATA_PATH}/poster_qrcode_${Date.now()}.png`
-
- await new Promise((resolve, reject) => {
- fs.writeFile({
- filePath,
- data: base64Data,
- encoding: 'base64',
- success: () => resolve(true),
- fail: (err) => {
- console.error('[Poster] 小程序码写入本地失败:', err)
- reject(err)
- },
- })
- })
-
- console.log('[Poster] 小程序码已保存到本地:', filePath)
-
- this.setData({
- posterQrSrc: filePath,
- posterReferralLink: '', // 小程序版本不再使用 H5 链接
- posterNickname: nickname,
- posterNicknameInitial: (nickname || '用').charAt(0),
- isGeneratingPoster: false
- })
- wx.hideLoading()
- } catch (e) {
- console.error('[Poster] 生成二维码失败:', e)
- wx.hideLoading()
- wx.showToast({ title: '生成失败', icon: 'none' })
- this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterReferralLink: '' })
- }
- },
-
- // 绘制数据卡片
- drawDataCard(ctx, x, y, width, height, value, label, color) {
- // 卡片背景
- ctx.setFillStyle('rgba(255,255,255,0.05)')
- this.drawRoundRect(ctx, x, y, width, height, 8)
- ctx.setStrokeStyle('rgba(255,255,255,0.1)')
- ctx.setLineWidth(1)
- ctx.stroke()
-
- // 数值
- ctx.setFillStyle(color)
- ctx.setFontSize(24)
- ctx.setTextAlign('center')
- ctx.fillText(value, x + width / 2, y + 24)
-
- // 标签
- ctx.setFillStyle('rgba(255,255,255,0.5)')
- ctx.setFontSize(10)
- ctx.fillText(label, x + width / 2, y + 40)
- },
-
- // 绘制圆角矩形
- drawRoundRect(ctx, x, y, width, height, radius) {
- ctx.beginPath()
- ctx.moveTo(x + radius, y)
- ctx.lineTo(x + width - radius, y)
- ctx.arc(x + width - radius, y + radius, radius, -Math.PI / 2, 0)
- ctx.lineTo(x + width, y + height - radius)
- ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2)
- ctx.lineTo(x + radius, y + height)
- ctx.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI)
- ctx.lineTo(x, y + radius)
- ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5)
- ctx.closePath()
- ctx.fill()
- },
-
- // 光晕(替代 createRadialGradient):用同心圆叠加模拟模糊
- // centerX/centerY: 圆心坐标;radius: 最大半径;rgb: [r,g,b];maxAlpha: 最内层透明度
- drawGlow(ctx, centerX, centerY, radius, rgb, maxAlpha = 0.10) {
- const steps = 14
- for (let i = steps; i >= 1; i--) {
- const r = (radius * i) / steps
- const alpha = (maxAlpha * i) / steps
- ctx.setFillStyle(`rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`)
- ctx.beginPath()
- ctx.arc(centerX, centerY, r, 0, Math.PI * 2)
- ctx.fill()
- }
- },
-
- // 绘制二维码(支持Base64和URL两种格式)
- async drawQRCode(ctx, qrcodeImage, x, y, size) {
- return new Promise((resolve) => {
- if (!qrcodeImage) {
- console.log('[Poster] 无二维码数据,绘制占位符')
- this.drawQRPlaceholder(ctx, x, y, size)
- resolve()
- return
- }
-
- // 判断是Base64还是URL
- if (qrcodeImage.startsWith('data:image') || !qrcodeImage.startsWith('http')) {
- // Base64格式(小程序码)
- console.log('[Poster] 绘制Base64二维码')
- const fs = wx.getFileSystemManager()
- const filePath = `${wx.env.USER_DATA_PATH}/qrcode_promo_${Date.now()}.png`
- const base64Data = qrcodeImage.replace(/^data:image\/\w+;base64,/, '')
-
- fs.writeFile({
- filePath,
- data: base64Data,
- encoding: 'base64',
- success: () => {
- console.log('[Poster] ✅ Base64写入成功')
- ctx.drawImage(filePath, x, y, size, size)
- resolve()
- },
- fail: (err) => {
- console.error('[Poster] ❌ Base64写入失败:', err)
- this.drawQRPlaceholder(ctx, x, y, size)
- resolve()
- }
- })
- } else {
- // URL格式(第三方二维码)
- console.log('[Poster] 下载在线二维码:', qrcodeImage)
- wx.downloadFile({
- url: qrcodeImage,
- success: (res) => {
- if (res.statusCode === 200) {
- console.log('[Poster] ✅ 二维码下载成功')
- ctx.drawImage(res.tempFilePath, x, y, size, size)
- resolve()
- } else {
- console.error('[Poster] ❌ 二维码下载失败, status:', res.statusCode)
- this.drawQRPlaceholder(ctx, x, y, size)
- resolve()
- }
- },
- fail: (err) => {
- console.error('[Poster] ❌ 二维码下载失败:', err)
- this.drawQRPlaceholder(ctx, x, y, size)
- resolve()
- }
- })
- }
- })
- },
-
- // 绘制小程序码占位符
- drawQRPlaceholder(ctx, x, y, size) {
- // 绘制占位符方框
- ctx.setFillStyle('rgba(200,200,200,0.3)')
- this.drawRoundRect(ctx, x, y, size, size, 8)
-
- ctx.setFillStyle('#00CED1')
- ctx.setFontSize(11)
- ctx.setTextAlign('center')
- ctx.fillText('小程序码', x + size / 2, y + size / 2)
- },
-
- // 关闭海报弹窗
- closePosterModal() {
- this.setData({ showPosterModal: false })
- },
-
- // 保存海报
- savePoster() {
- const { posterQrSrc } = this.data
- if (!posterQrSrc) {
- wx.showToast({ title: '二维码未生成', icon: 'none' })
- return
- }
-
- wx.showLoading({ title: '保存中...', mask: true })
- wx.downloadFile({
- url: posterQrSrc,
- success: (res) => {
- if (res.statusCode !== 200) {
- wx.hideLoading()
- wx.showToast({ title: '下载失败', icon: 'none' })
- return
- }
-
- wx.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success: () => {
- wx.hideLoading()
- wx.showToast({ title: '已保存到相册', icon: 'success' })
- },
- fail: (err) => {
- wx.hideLoading()
- if (String(err.errMsg || '').includes('auth deny')) {
- wx.showModal({
- title: '提示',
- content: '需要相册权限才能保存二维码',
- confirmText: '去设置',
- success: (r) => {
- if (r.confirm) wx.openSetting()
- }
- })
- return
- }
- wx.showToast({ title: '保存失败', icon: 'none' })
- }
- })
- },
- fail: () => {
- wx.hideLoading()
- wx.showToast({ title: '下载失败', icon: 'none' })
- }
- })
- },
-
- // 预览二维码
- previewPosterQr() {
- const { posterQrSrc } = this.data
- if (!posterQrSrc) return
- wx.previewImage({ urls: [posterQrSrc] })
- },
-
- // 阻止冒泡
- stopPropagation() {},
-
- // 分享到朋友圈 - 随机文案
- shareToMoments() {
- // 10条随机文案,基于书的内容
- const shareTexts = [
- `🔥 在派对房里听到的真实故事,比虚构的小说精彩100倍!\n\n电动车出租月入5万、私域一年赚1000万、一个人的公司月入10万...\n\n62个真实案例,搜"Soul创业派对"小程序看全部!\n\n#创业 #私域 #商业`,
-
- `💡 今天终于明白:会赚钱的人,都在用"流量杠杆"\n\n抖音、Soul、飞书...同一套内容,撬动不同平台的流量。\n\n《Soul创业派对》里的实战方法,受用终身!\n\n#流量 #副业 #创业派对`,
-
- `📚 一个70后大健康私域,一个月150万流水是怎么做到的?\n\n答案在《Soul创业派对》第9章,全是干货。\n\n搜小程序"Soul创业派对",我在里面等你\n\n#大健康 #私域运营 #真实案例`,
-
- `🎯 "分钱不是分你的钱,是分不属于对方的钱"\n\n这句话改变了我对商业合作的认知。\n\n推荐《Soul创业派对》,创业者必读!\n\n#云阿米巴 #商业思维 #创业`,
-
- `✨ 资源整合高手的社交方法论,在派对房里学到了\n\n"先让对方赚到钱,自己才能长久赚钱"\n\n这本《Soul创业派对》,每章都是实战经验\n\n#资源整合 #社交 #创业故事`,
-
- `🚀 AI工具推广:一个隐藏的高利润赛道\n\n客单价高、复购率高、需求旺盛...\n\n《Soul创业派对》里的商业机会,你发现了吗?\n\n#AI #副业 #商业机会`,
-
- `💰 美业整合:一个人的公司如何月入十万?\n\n不开店、不囤货、轻资产运营...\n\n《Soul创业派对》告诉你答案!\n\n#美业 #轻创业 #月入十万`,
-
- `🌟 3000万流水是怎么跑出来的?\n\n不是靠运气,是靠系统。\n\n《Soul创业派对》里的电商底层逻辑,值得反复看\n\n#电商 #创业 #商业系统`,
-
- `📖 "人与人之间的关系,归根结底就三个东西:利益、情感、价值观"\n\n在派对房里聊出的金句,都在《Soul创业派对》里\n\n#人性 #商业 #创业派对`,
-
- `🔔 未来职业的三个方向:技术型、资源型、服务型\n\n你属于哪一种?\n\n《Soul创业派对》帮你找到答案!\n\n#职业规划 #创业 #未来`
- ]
-
- // 随机选择一条文案
- const randomIndex = Math.floor(Math.random() * shareTexts.length)
- const shareText = shareTexts[randomIndex]
-
- wx.setClipboardData({
- data: shareText,
- success: () => {
- wx.showModal({
- title: '文案已复制',
- content: '请打开微信朋友圈,粘贴分享文案,配合推广海报一起发布效果更佳!\n\n再次点击可获取新的随机文案',
- showCancel: false,
- confirmText: '去发朋友圈'
- })
- }
- })
- },
-
- // 提现 - 直接到微信零钱
- async handleWithdraw() {
- const availableEarnings = this.data.availableEarningsNum || 0
- const minWithdrawAmount = this.data.minWithdrawAmount || 10
- const hasWechatId = this.data.hasWechatId
-
- if (availableEarnings <= 0) {
- wx.showToast({ title: '暂无可提现收益', icon: 'none' })
- return
- }
- // 任意金额可提现,不再设最低限额
-
- // 未绑定微信号时引导去设置
- if (!hasWechatId) {
- wx.showModal({
- title: '请先绑定微信号',
- content: '提现需先绑定微信号,便于到账核对。请到「设置」中绑定后再提现。',
- confirmText: '去绑定',
- cancelText: '取消',
- success: (res) => {
- if (res.confirm) wx.navigateTo({ url: '/pages/settings/settings' })
- }
- })
- return
- }
-
- wx.showModal({
- title: '确认提现',
- content: `将提现 ¥${availableEarnings.toFixed(2)} 到您的微信零钱`,
- confirmText: '立即提现',
- success: async (res) => {
- if (!res.confirm) return
- const tmplId = app.globalData.withdrawSubscribeTmplId
- if (tmplId && tmplId.length > 10) {
- wx.requestSubscribeMessage({
- tmplIds: [tmplId],
- success: () => { this.doWithdraw(availableEarnings) },
- fail: () => { this.doWithdraw(availableEarnings) }
- })
- } else {
- await this.doWithdraw(availableEarnings)
- }
- }
- })
- },
-
- // 跳转提现记录页
- goToWithdrawRecords() {
- wx.navigateTo({ url: '/pages/withdraw-records/withdraw-records' })
- },
-
- // 执行提现
- async doWithdraw(amount) {
- wx.showLoading({ title: '提现中...' })
-
- try {
- const userId = app.globalData.userInfo?.id
- if (!userId) {
- wx.hideLoading()
- wx.showToast({ title: '请先登录', icon: 'none' })
- return
- }
-
- const res = await app.request('/api/miniprogram/withdraw', {
- method: 'POST',
- data: { userId, amount }
- })
-
- wx.hideLoading()
-
- if (res.success) {
- wx.showModal({
- title: '提现申请已提交 ✅',
- content: res.message || '正在审核中,通过后会自动到账您的微信零钱',
- showCancel: false,
- confirmText: '知道了'
- })
-
- // 刷新数据(此时待审核金额会增加,可提现金额会减少)
- this.initData()
- } else {
- if (res.needBind || res.needBindWechat) {
- wx.showModal({
- title: res.needBindWechat ? '请先绑定微信号' : '需要绑定微信',
- content: res.needBindWechat ? '请到「设置」中绑定微信号后再提现,便于到账核对。' : '请先在设置中绑定微信账号后再提现',
- confirmText: '去绑定',
- success: (modalRes) => {
- if (modalRes.confirm) wx.navigateTo({ url: '/pages/settings/settings' })
- }
- })
- } else {
- wx.showToast({ title: res.message || res.error || '提现失败', icon: 'none', duration: 3000 })
- }
- }
- } catch (e) {
- wx.hideLoading()
- console.error('[Referral] 提现失败:', e)
- wx.showToast({ title: '提现失败,请重试', icon: 'none' })
- }
- },
-
- // 显示通知
- showNotification() {
- wx.showToast({ title: '暂无新消息', icon: 'none' })
- },
-
- // 显示设置
- showSettings() {
- wx.showActionSheet({
- itemList: ['自动提现设置', '收益通知设置'],
- success: (res) => {
- if (res.tapIndex === 0) {
- this.showAutoWithdrawSettings()
- } else {
- this.showNotificationSettings()
- }
- }
- })
- },
-
- // 自动提现设置
- async showAutoWithdrawSettings() {
- const app = getApp()
- const { userInfo } = app.globalData
-
- if (!userInfo) {
- wx.showToast({ title: '请先登录', icon: 'none' })
- return
- }
-
- // 获取当前设置
- let autoWithdrawEnabled = wx.getStorageSync(`autoWithdraw_${userInfo.id}`) || false
- let autoWithdrawThreshold = wx.getStorageSync(`autoWithdrawThreshold_${userInfo.id}`) || this.data.minWithdrawAmount || 10
-
- wx.showModal({
- title: '自动提现设置',
- content: `当前状态:${autoWithdrawEnabled ? '已开启' : '已关闭'}\n自动提现阈值:¥${autoWithdrawThreshold}\n\n开启后,当可提现金额达到阈值时将自动发起提现申请。`,
- confirmText: autoWithdrawEnabled ? '关闭' : '开启',
- cancelText: '修改阈值',
- success: (res) => {
- if (res.confirm) {
- // 切换开关
- this.toggleAutoWithdraw(!autoWithdrawEnabled, autoWithdrawThreshold)
- } else if (res.cancel) {
- // 修改阈值
- this.setAutoWithdrawThreshold(autoWithdrawEnabled, autoWithdrawThreshold)
- }
- }
- })
- },
-
- // 切换自动提现开关
- toggleAutoWithdraw(enabled, threshold) {
- const app = getApp()
- const { userInfo } = app.globalData
-
- wx.setStorageSync(`autoWithdraw_${userInfo.id}`, enabled)
-
- wx.showToast({
- title: enabled ? '自动提现已开启' : '自动提现已关闭',
- icon: 'success'
- })
-
- // 如果开启,检查当前金额是否达到阈值
- if (enabled && this.data.availableEarningsNum >= threshold) {
- wx.showModal({
- title: '提示',
- content: `当前可提现金额¥${this.data.availableEarnings}已达到阈值¥${threshold},是否立即提现?`,
- success: (res) => {
- if (res.confirm) {
- this.handleWithdraw()
- }
- }
- })
- }
- },
-
- // 设置自动提现阈值
- setAutoWithdrawThreshold(currentEnabled, currentThreshold) {
- const minAmount = this.data.minWithdrawAmount || 10
-
- wx.showModal({
- title: '设置提现阈值',
- content: `请输入自动提现金额阈值(最低¥${minAmount})`,
- editable: true,
- placeholderText: currentThreshold.toString(),
- success: (res) => {
- if (res.confirm && res.content) {
- const threshold = parseFloat(res.content)
-
- if (isNaN(threshold) || threshold < minAmount) {
- wx.showToast({
- title: `请输入不小于¥${minAmount}的金额`,
- icon: 'none'
- })
- return
- }
-
- const app = getApp()
- const { userInfo } = app.globalData
-
- wx.setStorageSync(`autoWithdrawThreshold_${userInfo.id}`, threshold)
-
- wx.showToast({
- title: `阈值已设置为¥${threshold}`,
- icon: 'success'
- })
-
- // 重新显示设置界面
- setTimeout(() => {
- this.showAutoWithdrawSettings()
- }, 1500)
- }
- }
- })
- },
-
- // 收益通知设置
- showNotificationSettings() {
- const app = getApp()
- const { userInfo } = app.globalData
-
- if (!userInfo) {
- wx.showToast({ title: '请先登录', icon: 'none' })
- return
- }
-
- // 获取当前设置
- let notifyEnabled = wx.getStorageSync(`earningsNotify_${userInfo.id}`) !== false // 默认开启
-
- wx.showModal({
- title: '收益通知设置',
- content: `当前状态:${notifyEnabled ? '已开启' : '已关闭'}\n\n开启后,将在有新收益时收到小程序通知提醒。`,
- confirmText: notifyEnabled ? '关闭通知' : '开启通知',
- success: (res) => {
- if (res.confirm) {
- const newState = !notifyEnabled
- wx.setStorageSync(`earningsNotify_${userInfo.id}`, newState)
-
- wx.showToast({
- title: newState ? '收益通知已开启' : '收益通知已关闭',
- icon: 'success'
- })
-
- // 如果开启,请求通知权限
- if (newState) {
- wx.requestSubscribeMessage({
- tmplIds: [''] // 需要配置模板ID
- }).catch(() => {
- // 用户拒绝授权,不影响功能
- })
- }
- }
- }
- })
- },
-
- // 分享 - 带推荐码
- onShareAppMessage() {
- console.log('[Referral] 分享给好友,推荐码:', this.data.referralCode)
- return {
- title: 'Soul创业派对 - 来自派对房的真实商业故事',
- path: `/pages/index/index?ref=${this.data.referralCode}`
- // 不设置 imageUrl,使用小程序默认截图
- // 如需自定义图片,请将图片放在 /assets/ 目录并配置路径
- }
- },
-
- // 分享到朋友圈
- onShareTimeline() {
- console.log('[Referral] 分享到朋友圈,推荐码:', this.data.referralCode)
- return {
- title: `Soul创业派对 - 62个真实商业案例`,
- query: `ref=${this.data.referralCode}`
- // 不设置 imageUrl,使用小程序默认截图
- }
- },
-
- goBack() {
- wx.navigateBack()
- },
-
- // 解析商品描述,获取书名和章节
- parseProductDescription(description, productType) {
- if (!description) {
- return {
- bookTitle: '未知商品',
- chapterTitle: ''
- }
- }
-
- // 匹配格式:《书名》- 章节名
- const match = description.match(/《(.+?)》(?:\s*-\s*(.+))?/)
-
- if (match) {
- return {
- bookTitle: match[1] || '未知书籍',
- chapterTitle: match[2] || (productType === 'fullbook' ? '全书购买' : '')
- }
- }
-
- // 如果匹配失败,直接返回原始描述
- return {
- bookTitle: description.split('-')[0] || description,
- chapterTitle: description.split('-')[1] || ''
- }
- },
-
- // 格式化日期
- formatDate(dateStr) {
- if (!dateStr) return '--'
- const d = new Date(dateStr)
- const month = (d.getMonth() + 1).toString().padStart(2, '0')
- const day = d.getDate().toString().padStart(2, '0')
- return `${month}-${day}`
- }
-})
diff --git a/归档/miniprogram/pages/referral/referral.json b/归档/miniprogram/pages/referral/referral.json
deleted file mode 100644
index 64f02792..00000000
--- a/归档/miniprogram/pages/referral/referral.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom",
- "enableShareAppMessage": true,
- "enableShareTimeline": true
-}
diff --git a/归档/miniprogram/pages/referral/referral.wxml b/归档/miniprogram/pages/referral/referral.wxml
deleted file mode 100644
index 38c587aa..00000000
--- a/归档/miniprogram/pages/referral/referral.wxml
+++ /dev/null
@@ -1,332 +0,0 @@
-
-
-
-
-
-
-
-
-
- 分销中心
-
-
-
-
-
-
-
-
-
-
-
- {{expiringCount}} 位用户绑定即将过期
- {{bindingDays}}天内未付款将解除绑定关系
-
-
-
-
-
-
-
-
-
- {{availableEarningsNum <= 0 ? '暂无可提现' : !hasWechatId ? '请先绑定微信号' : '申请提现 ¥' + availableEarnings}}
-
- 为便于提现到账,请先到「设置」中绑定微信号
- 查看提现记录
-
-
-
-
-
-
- {{bindingCount}}
- 绑定中
-
-
- {{paidCount}}
- 已付款
-
-
- {{unboughtCount}}
- 即将过期
-
-
- {{referralCount}}
- 总邀请
-
-
-
-
-
-
-
- • 好友通过你的链接购买,立享{{userDiscount}}%优惠
- • 好友成功付款后,你获得 {{shareRate}}% 收益
- • 绑定期{{bindingDays}}天,期满未付款自动解除
-
-
-
-
-
-
-
-
-
-
- 绑定中 ({{activeBindings.length}})
- 已付款 ({{convertedBindings.length}})
- 已过期 ({{expiredBindings.length}})
-
-
-
-
-
-
- 👤
- 暂无用户
-
-
-
-
-
- ✓
- ⏰
- {{item.nickname[0] || '用'}}
-
-
- {{item.nickname || '匿名用户'}}
- 绑定于 {{item.bindingDate}}
-
-
-
- +¥{{item.commission}}
- 已购{{item.purchaseCount || 1}}次
-
-
- 已过期
- {{item.expiryDate}}
-
-
-
- {{item.daysRemaining}}天
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 生成推广海报
- 一键生成精美海报分享
-
-
-
-
-
-
-
-
-
- 分享到朋友圈
- 复制文案发朋友圈
-
-
-
-
-
-
-
-
-
- 更多分享方式
- 使用系统分享功能
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{item.buyerNickname.charAt(0)}}
-
-
-
-
-
-
- {{item.buyerNickname}}
- +¥{{item.commission}}
-
-
- {{item.bookTitle}}
- - {{item.chapterTitle}}
-
- {{item.payTime}}
-
-
-
-
-
-
-
-
-
-
- 暂无收益记录
- 分享专属链接,好友购买即可获得 {{shareRate}}% 返利
-
-
-
-
-
-
- ✕
-
-
-
-
-
-
-
-
-
-
-
- 真实商业案例
- 每日更新
-
-
-
-
- 一场SOUL的
- 创业实验场
-
- 来自Soul派对房的真实商业故事
-
-
-
-
- {{posterCaseCount}}
- 真实案例
-
-
- {{userDiscount}}%
- 好友优惠
-
-
- {{shareRate}}%
- 你的收益
-
-
-
-
-
- 人性观察
- 行业揭秘
- 赚钱逻辑
- 创业复盘
- 资源对接
-
-
-
-
-
- {{posterNicknameInitial}}
-
- {{posterNickname}} 推荐你来读
-
-
-
-
- 通过我的链接购买,立省{{userDiscount}}%
-
-
-
-
-
-
-
- 长按识别 · 立即试读
- 邀请码: {{referralCode}}
-
-
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/referral/referral.wxss b/归档/miniprogram/pages/referral/referral.wxss
deleted file mode 100644
index b75eb535..00000000
--- a/归档/miniprogram/pages/referral/referral.wxss
+++ /dev/null
@@ -1,302 +0,0 @@
-/* ???????? - 1:1??Web?? */
-.page { min-height: 100vh; background: #000; padding-bottom: 64rpx; }
-
-/* ??? */
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-left { display: flex; gap: 16rpx; align-items: center; }
-.nav-back { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
-.nav-icon { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; flex: 1; text-align: center; }
-.nav-right-placeholder { width: 144rpx; }
-.nav-btn { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
-
-.content { padding: 24rpx; width: 100%; box-sizing: border-box; }
-
-/* ?????? */
-.expiring-banner { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 24rpx; margin-bottom: 24rpx; }
-.banner-icon { width: 80rpx; height: 80rpx; background: rgba(255,165,0,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
-.icon-bell-warning { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(64%) sepia(89%) saturate(1363%) hue-rotate(4deg) brightness(101%) contrast(102%); }
-.banner-content { flex: 1; }
-.banner-title { font-size: 28rpx; font-weight: 500; color: #fff; display: block; }
-.banner-desc { font-size: 24rpx; color: rgba(255,165,0,0.8); margin-top: 4rpx; display: block; }
-
-/* ???? - ?? Next.js */
-.earnings-card { position: relative; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 48rpx; margin-bottom: 24rpx; overflow: hidden; width: 100%; box-sizing: border-box; }
-.earnings-bg { position: absolute; top: 0; right: 0; width: 256rpx; height: 256rpx; background: rgba(0,206,209,0.15); border-radius: 50%; filter: blur(100rpx); }
-.earnings-main { position: relative; }
-.earnings-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 32rpx; }
-.earnings-left { display: flex; align-items: center; gap: 16rpx; }
-.wallet-icon { width: 80rpx; height: 80rpx; background: rgba(0,206,209,0.2); border-radius: 20rpx; display: flex; align-items: center; justify-content: center; }
-.icon-wallet { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.earnings-info { display: flex; flex-direction: column; gap: 8rpx; }
-.earnings-label { font-size: 24rpx; color: rgba(255,255,255,0.6); }
-.commission-rate { font-size: 24rpx; color: #00CED1; font-weight: 500; }
-.earnings-right { text-align: right; }
-.earnings-value { font-size: 60rpx; font-weight: 700; color: #fff; display: block; line-height: 1; }
-.pending-text { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; display: block; }
-
-.withdraw-btn { padding: 28rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #fff; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,206,209,0.3); }
-.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.2); color: rgba(255,255,255,0.3); box-shadow: none; }
-.wechat-tip { display: block; font-size: 24rpx; color: rgba(255,165,0,0.9); margin-top: 16rpx; text-align: center; }
-.withdraw-records-link { display: block; margin-top: 16rpx; text-align: center; font-size: 26rpx; color: #00CED1; }
-
-/* ???? - ?? Next.js 4??? */
-.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.stat-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 24rpx 12rpx; text-align: center; }
-.stat-value { font-size: 40rpx; font-weight: 700; color: #fff; display: block; }
-.stat-value.orange { color: #FFA500; }
-.stat-label { font-size: 20rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
-
-/* ????? */
-.visit-stat { display: flex; align-items: center; justify-content: center; gap: 12rpx; padding: 20rpx 32rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 24rpx; }
-.visit-label { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-.visit-value { font-size: 32rpx; font-weight: 700; color: #00CED1; }
-.visit-tip { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-
-/* ???? - ?? Next.js */
-.rules-card { background: rgba(0,206,209,0.05); border: 2rpx solid rgba(0,206,209,0.2); border-radius: 24rpx; padding: 32rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.rules-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
-.rules-icon { width: 64rpx; height: 64rpx; background: rgba(0,206,209,0.2); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; }
-.icon-alert { width: 32rpx; height: 32rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.rules-title { font-size: 28rpx; font-weight: 500; color: #fff; }
-.rules-list { padding-left: 8rpx; }
-.rule-item { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 2; display: block; margin-bottom: 4rpx; }
-.rule-item .gold { color: #FFD700; font-weight: 500; }
-.rule-item .brand { color: #00CED1; font-weight: 500; }
-.rule-item .orange { color: #FFA500; font-weight: 500; }
-
-/* ?????? - ?? Next.js */
-.binding-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.binding-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.binding-title { display: flex; align-items: center; gap: 12rpx; }
-.binding-icon-img { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.title-text { font-size: 30rpx; font-weight: 600; color: #fff; }
-.binding-count { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-.toggle-icon { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-
-/* Tab?? */
-.binding-tabs { display: flex; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.tab-item { flex: 1; padding: 24rpx 0; text-align: center; font-size: 26rpx; color: rgba(255,255,255,0.5); position: relative; }
-.tab-item.tab-active { color: #00CED1; }
-.tab-item.tab-active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 80rpx; height: 4rpx; background: #00CED1; border-radius: 4rpx; }
-
-/* ???? */
-.binding-list { max-height: 640rpx; overflow-y: auto; }
-.empty-state { padding: 80rpx 0; text-align: center; }
-.empty-icon { font-size: 64rpx; display: block; margin-bottom: 16rpx; }
-.empty-text { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-
-/* ?????? - ??? */
-.binding-item { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); gap: 24rpx; }
-.binding-item:last-child { border-bottom: none; }
-
-/* ?? */
-.user-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: 600; color: #00CED1; flex-shrink: 0; }
-.user-avatar.avatar-converted { background: rgba(76,175,80,0.2); color: #4CAF50; }
-.user-avatar.avatar-expired { background: rgba(158,158,158,0.2); color: #9E9E9E; }
-
-/* ???? */
-.user-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
-.user-name { font-size: 28rpx; color: #fff; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.user-time { font-size: 22rpx; color: rgba(255,255,255,0.5); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-
-/* ???? */
-.user-status { flex-shrink: 0; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 4rpx; min-width: 100rpx; }
-.status-amount { font-size: 28rpx; color: #4CAF50; font-weight: 600; white-space: nowrap; }
-.status-order { font-size: 20rpx; color: rgba(255,255,255,0.5); white-space: nowrap; }
-.status-time { font-size: 20rpx; color: rgba(255,255,255,0.5); white-space: nowrap; }
-
-/* ???? */
-.status-tag { font-size: 24rpx; font-weight: 600; padding: 6rpx 16rpx; border-radius: 16rpx; white-space: nowrap; }
-.status-tag.tag-green { background: rgba(76,175,80,0.2); color: #4CAF50; }
-.status-tag.tag-orange { background: rgba(255,165,0,0.2); color: #FFA500; }
-.status-tag.tag-red { background: rgba(244,67,54,0.2); color: #F44336; }
-.status-tag.tag-gray { background: rgba(158,158,158,0.2); color: #9E9E9E; }
-
-/* ????? - ?? Next.js */
-.invite-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 40rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.invite-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
-.invite-title { font-size: 30rpx; font-weight: 600; color: #fff; }
-.invite-code-box { background: rgba(0,206,209,0.2); padding: 12rpx 24rpx; border-radius: 16rpx; }
-.invite-code { font-size: 28rpx; font-weight: 600; color: #00CED1; font-family: 'Courier New', monospace; letter-spacing: 2rpx; }
-.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 1.5; display: block; }
-.invite-tip .gold { color: #FFD700; }
-.invite-tip .brand { color: #00CED1; }
-
-/* ?????? - ??? */
-.earnings-detail-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.detail-header { padding: 40rpx 40rpx 24rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.detail-title { font-size: 30rpx; font-weight: 600; color: #fff; }
-.detail-list { max-height: 480rpx; overflow-y: auto; padding: 16rpx 0; }
-
-/* ??????? */
-.earnings-detail-card .detail-item { display: flex; align-items: center; gap: 24rpx; padding: 24rpx 40rpx; background: transparent; border-bottom: 2rpx solid rgba(255,255,255,0.03); }
-.earnings-detail-card .detail-item:last-child { border-bottom: none; }
-.earnings-detail-card .detail-item:active { background: rgba(255, 255, 255, 0.05); }
-
-/* ???? */
-.earnings-detail-card .detail-avatar-wrap { width: 88rpx; height: 88rpx; flex-shrink: 0; }
-.earnings-detail-card .detail-avatar { width: 100%; height: 100%; border-radius: 50%; border: 2rpx solid rgba(56, 189, 172, 0.2); }
-.earnings-detail-card .detail-avatar-text { width: 100%; height: 100%; border-radius: 50%; background: linear-gradient(135deg, #38bdac 0%, #2da396 100%); display: flex; align-items: center; justify-content: center; font-size: 36rpx; font-weight: 700; color: #ffffff; }
-
-/* ???? */
-.earnings-detail-card .detail-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8rpx; }
-.earnings-detail-card .detail-top { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }
-.earnings-detail-card .detail-buyer { font-size: 28rpx; font-weight: 500; color: #ffffff; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.earnings-detail-card .detail-amount { font-size: 32rpx; font-weight: 700; color: #38bdac; flex-shrink: 0; white-space: nowrap; }
-
-/* ???? */
-.earnings-detail-card .detail-product { display: flex; align-items: baseline; gap: 4rpx; font-size: 24rpx; color: rgba(255, 255, 255, 0.6); min-width: 0; overflow: hidden; }
-.earnings-detail-card .detail-book { color: rgba(255, 255, 255, 0.7); font-weight: 500; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.earnings-detail-card .detail-chapter { color: rgba(255, 255, 255, 0.5); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.earnings-detail-card .detail-time { font-size: 22rpx; color: rgba(255, 255, 255, 0.4); }
-
-/* ???? - ?? Next.js */
-.share-section { display: flex; flex-direction: column; gap: 12rpx; width: 100%; margin-bottom: 24rpx; }
-.share-item { display: flex; align-items: center; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 32rpx; border: none; margin: 0; text-align: left; width: 100%; box-sizing: border-box; }
-.share-item::after { border: none; }
-.share-icon { width: 96rpx; height: 96rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; margin-right: 24rpx; flex-shrink: 0; }
-.share-icon.poster { background: rgba(103,58,183,0.2); }
-.share-icon.wechat { background: rgba(7,193,96,0.2); }
-.share-icon.link { background: rgba(158,158,158,0.2); }
-.icon-share-btn { width: 48rpx; height: 48rpx; display: block; }
-.share-icon.poster .icon-share-btn { filter: brightness(0) saturate(100%) invert(37%) sepia(73%) saturate(2296%) hue-rotate(252deg) brightness(96%) contrast(92%); }
-.share-icon.wechat .icon-share-btn { filter: brightness(0) saturate(100%) invert(58%) sepia(91%) saturate(1255%) hue-rotate(105deg) brightness(96%) contrast(97%); }
-.share-icon.link .icon-share-btn { filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.share-info { flex: 1; text-align: left; }
-.share-title { font-size: 28rpx; color: #fff; font-weight: 500; display: block; text-align: left; }
-.share-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; text-align: left; }
-.share-arrow-icon { width: 40rpx; height: 40rpx; display: block; flex-shrink: 0; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.share-btn-wechat { line-height: normal; font-size: inherit; padding: 24rpx 32rpx !important; margin: 0 !important; width: 100% !important; }
-
-/* ?????????????? + ???? + ???????? */
-/* ???????? backdrop-filter??????????????? */
-.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 32rpx; box-sizing: border-box; }
-
-.poster-dialog { width: 686rpx; border-radius: 24rpx; overflow: hidden; position: relative; background: transparent; }
-.poster-close { position: absolute; top: 20rpx; right: 20rpx; width: 56rpx; height: 56rpx; border-radius: 28rpx; background: rgba(0,0,0,0.25); color: rgba(255,255,255,0.9); display: flex; align-items: center; justify-content: center; z-index: 5; font-size: 28rpx; }
-
-/* ???? */
-.poster-card { position: relative; background: linear-gradient(135deg, #0a1628 0%, #0f2137 50%, #1a3a5c 100%); color: #fff; padding: 44rpx 40rpx 36rpx; }
-.poster-inner { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; text-align: center; }
-
-/* ???? */
-/* ???????? filter: blur ??????????????? + ???? */
-.poster-glow { position: absolute; width: 320rpx; height: 320rpx; border-radius: 50%; opacity: 0.6; z-index: 1; }
-.poster-glow-left { top: -120rpx; left: -160rpx; background: rgba(0,206,209,0.12); box-shadow: 0 0 140rpx 40rpx rgba(0,206,209,0.18); }
-.poster-glow-right { bottom: -140rpx; right: -160rpx; background: rgba(255,215,0,0.10); box-shadow: 0 0 160rpx 50rpx rgba(255,215,0,0.14); }
-.poster-ring { position: absolute; width: 520rpx; height: 520rpx; border-radius: 50%; border: 2rpx solid rgba(0,206,209,0.06); left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 1; }
-
-/* ???? */
-.poster-badges { display: flex; gap: 16rpx; margin-bottom: 24rpx; }
-.poster-badge { padding: 10rpx 22rpx; font-size: 20rpx; font-weight: 700; border-radius: 999rpx; border: 2rpx solid transparent; }
-.poster-badge-gold { background: rgba(255,215,0,0.18); color: #FFD700; border-color: rgba(255,215,0,0.28); }
-.poster-badge-brand { background: rgba(0,206,209,0.18); color: #00CED1; border-color: rgba(0,206,209,0.28); }
-
-/* ?? */
-.poster-title { margin-bottom: 8rpx; }
-.poster-title-line1 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #00CED1; }
-.poster-title-line2 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #fff; margin-top: 6rpx; }
-.poster-subtitle { display: block; font-size: 22rpx; color: rgba(255,255,255,0.6); margin-bottom: 28rpx; }
-
-/* ???? */
-.poster-stats { width: 100%; display: flex; gap: 16rpx; justify-content: center; margin-bottom: 24rpx; }
-.poster-stat { flex: 1; max-width: 190rpx; background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 16rpx; padding: 18rpx 10rpx; }
-.poster-stat-value { display: block; font-size: 44rpx; font-weight: 900; line-height: 1; }
-.poster-stat-label { display: block; font-size: 20rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; }
-.poster-stat-gold { color: #FFD700; }
-.poster-stat-brand { color: #00CED1; }
-.poster-stat-pink { color: #E91E63; }
-
-/* ?? */
-.poster-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: 10rpx; margin: 0 24rpx 26rpx; }
-.poster-tag { font-size: 20rpx; color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 12rpx; padding: 6rpx 14rpx; }
-
-/* ??? */
-.poster-recommender { display: flex; align-items: center; gap: 12rpx; background: rgba(0,206,209,0.10); border: 2rpx solid rgba(0,206,209,0.20); border-radius: 999rpx; padding: 12rpx 22rpx; margin-bottom: 22rpx; }
-.poster-avatar { width: 44rpx; height: 44rpx; border-radius: 22rpx; background: rgba(0,206,209,0.30); display: flex; align-items: center; justify-content: center; }
-.poster-avatar-text { font-size: 20rpx; font-weight: 800; color: #00CED1; }
-.poster-recommender-text { font-size: 22rpx; color: #00CED1; }
-
-/* ??? */
-.poster-discount { width: 100%; background: linear-gradient(90deg, rgba(255,215,0,0.10) 0%, rgba(233,30,99,0.10) 100%); border: 2rpx solid rgba(255,215,0,0.20); border-radius: 18rpx; padding: 18rpx 18rpx; margin-bottom: 26rpx; }
-.poster-discount-text { font-size: 22rpx; color: rgba(255,255,255,0.80); }
-.poster-discount-highlight { color: #00CED1; font-weight: 800; }
-
-/* ??? */
-.poster-qr-wrap { background: #fff; padding: 14rpx; border-radius: 16rpx; margin-bottom: 12rpx; box-shadow: 0 16rpx 40rpx rgba(0,0,0,0.35); }
-.poster-qr-img { width: 240rpx; height: 240rpx; display: block; }
-.poster-qr-tip { font-size: 20rpx; color: rgba(255,255,255,0.40); margin-bottom: 8rpx; }
-.poster-code { font-size: 22rpx; font-family: monospace; letter-spacing: 2rpx; color: rgba(0,206,209,0.80); }
-
-/* ??????? */
-.poster-footer { background: #fff; padding: 28rpx 28rpx 32rpx; display: flex; flex-direction: column; gap: 18rpx; }
-.poster-footer-tip { font-size: 22rpx; color: rgba(0,0,0,0.55); text-align: center; }
-.poster-footer-btn { height: 72rpx; border-radius: 18rpx; border: 2rpx solid rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: rgba(0,0,0,0.75); background: #fff; }
-
-
-
-/* ????- ?? Next.js */
-.empty-earnings { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 64rpx 40rpx; text-align: center; margin-bottom: 24rpx; }
-.empty-icon-wrapper { width: 128rpx; height: 128rpx; border-radius: 50%; background: rgba(28, 28, 30, 0.8); display: flex; align-items: center; justify-content: center; margin: 0 auto 32rpx; }
-.empty-gift-icon { width: 64rpx; height: 64rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.empty-title { font-size: 30rpx; font-weight: 500; color: #fff; display: block; margin-bottom: 16rpx; }
-.empty-desc { font-size: 26rpx; color: rgba(255,255,255,0.6); display: block; line-height: 1.5; }
-
-
-/* ===== Loading 遮罩(备用) ===== */
-.loading-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0,0,0,0.7);
- backdrop-filter: blur(10rpx);
- z-index: 999;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-.loading-content {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 24rpx;
-}
-.loading-spinner {
- width: 80rpx;
- height: 80rpx;
- border: 6rpx solid rgba(56,189,172,0.2);
- border-top-color: #38bdac;
- border-radius: 50%;
- animation: spin 1s linear infinite;
-}
-@keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
-}
-.loading-text {
- font-size: 28rpx;
- color: rgba(255,255,255,0.8);
- font-weight: 500;
-}
-.content-loading {
- opacity: 0.3;
- pointer-events: none;
-}
-
-/* ===== 收益明细独立块 ===== */
-.detail-item { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,255,255,0.02); border-radius: 16rpx; margin-bottom: 16rpx; transition: all 0.3s; }
-.detail-item:active { background: rgba(255,255,255,0.05); }
-.detail-avatar-wrap { flex-shrink: 0; }
-.detail-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; border: 2rpx solid rgba(56,189,172,0.2); }
-.detail-avatar-text { width: 88rpx; height: 88rpx; border-radius: 50%; background: linear-gradient(135deg, #38bdac 0%, #2da396 100%); display: flex; align-items: center; justify-content: center; font-size: 36rpx; font-weight: 700; color: #ffffff; }
-.detail-content { flex: 1; display: flex; flex-direction: column; gap: 8rpx; min-width: 0; }
-.detail-top { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }
-.detail-buyer { font-size: 28rpx; font-weight: 500; color: #ffffff; flex-shrink: 0; }
-.detail-amount { font-size: 32rpx; font-weight: 700; color: #38bdac; flex-shrink: 0; }
-.detail-product { display: flex; align-items: center; font-size: 24rpx; color: rgba(255,255,255,0.6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.detail-book { color: rgba(255,255,255,0.7); font-weight: 500; }
-.detail-chapter { color: rgba(255,255,255,0.5); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.detail-time { font-size: 22rpx; color: rgba(255,255,255,0.4); }
diff --git a/归档/miniprogram/pages/referral/referral.wxss.backup b/归档/miniprogram/pages/referral/referral.wxss.backup
deleted file mode 100644
index 3c9e7c32..00000000
--- a/归档/miniprogram/pages/referral/referral.wxss.backup
+++ /dev/null
@@ -1,379 +0,0 @@
-/* ???????? - 1:1??Web?? */
-.page { min-height: 100vh; background: #000; padding-bottom: 64rpx; }
-
-/* ??? */
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-left { display: flex; gap: 16rpx; align-items: center; }
-.nav-back { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
-.nav-icon { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; flex: 1; text-align: center; }
-.nav-right-placeholder { width: 144rpx; }
-.nav-btn { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
-
-.content { padding: 24rpx; width: 100%; box-sizing: border-box; }
-
-/* ?????? */
-.expiring-banner { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 24rpx; margin-bottom: 24rpx; }
-.banner-icon { width: 80rpx; height: 80rpx; background: rgba(255,165,0,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
-.icon-bell-warning { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(64%) sepia(89%) saturate(1363%) hue-rotate(4deg) brightness(101%) contrast(102%); }
-.banner-content { flex: 1; }
-.banner-title { font-size: 28rpx; font-weight: 500; color: #fff; display: block; }
-.banner-desc { font-size: 24rpx; color: rgba(255,165,0,0.8); margin-top: 4rpx; display: block; }
-
-/* ???? - ?? Next.js */
-.earnings-card { position: relative; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 48rpx; margin-bottom: 24rpx; overflow: hidden; width: 100%; box-sizing: border-box; }
-.earnings-bg { position: absolute; top: 0; right: 0; width: 256rpx; height: 256rpx; background: rgba(0,206,209,0.15); border-radius: 50%; filter: blur(100rpx); }
-.earnings-main { position: relative; }
-.earnings-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 32rpx; }
-.earnings-left { display: flex; align-items: center; gap: 16rpx; }
-.wallet-icon { width: 80rpx; height: 80rpx; background: rgba(0,206,209,0.2); border-radius: 20rpx; display: flex; align-items: center; justify-content: center; }
-.icon-wallet { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.earnings-info { display: flex; flex-direction: column; gap: 8rpx; }
-.earnings-label { font-size: 24rpx; color: rgba(255,255,255,0.6); }
-.commission-rate { font-size: 24rpx; color: #00CED1; font-weight: 500; }
-.earnings-right { text-align: right; }
-.earnings-value { font-size: 60rpx; font-weight: 700; color: #fff; display: block; line-height: 1; }
-.pending-text { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; display: block; }
-
-.withdraw-btn { padding: 28rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #fff; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,206,209,0.3); }
-.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.2); color: rgba(255,255,255,0.3); box-shadow: none; }
-
-/* ???? - ?? Next.js 4??? */
-.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.stat-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 24rpx 12rpx; text-align: center; }
-.stat-value { font-size: 40rpx; font-weight: 700; color: #fff; display: block; }
-.stat-value.orange { color: #FFA500; }
-.stat-label { font-size: 20rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
-
-/* ????? */
-.visit-stat { display: flex; align-items: center; justify-content: center; gap: 12rpx; padding: 20rpx 32rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 24rpx; }
-.visit-label { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-.visit-value { font-size: 32rpx; font-weight: 700; color: #00CED1; }
-.visit-tip { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-
-/* ???? - ?? Next.js */
-.rules-card { background: rgba(0,206,209,0.05); border: 2rpx solid rgba(0,206,209,0.2); border-radius: 24rpx; padding: 32rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.rules-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
-.rules-icon { width: 64rpx; height: 64rpx; background: rgba(0,206,209,0.2); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; }
-.icon-alert { width: 32rpx; height: 32rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.rules-title { font-size: 28rpx; font-weight: 500; color: #fff; }
-.rules-list { padding-left: 8rpx; }
-.rule-item { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 2; display: block; margin-bottom: 4rpx; }
-.rule-item .gold { color: #FFD700; font-weight: 500; }
-.rule-item .brand { color: #00CED1; font-weight: 500; }
-.rule-item .orange { color: #FFA500; font-weight: 500; }
-
-/* ?????? - ?? Next.js */
-.binding-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.binding-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.binding-title { display: flex; align-items: center; gap: 12rpx; }
-.binding-icon-img { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.title-text { font-size: 30rpx; font-weight: 600; color: #fff; }
-.binding-count { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-.toggle-icon { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-
-/* Tab?? */
-.binding-tabs { display: flex; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.tab-item { flex: 1; padding: 24rpx 0; text-align: center; font-size: 26rpx; color: rgba(255,255,255,0.5); position: relative; }
-.tab-item.tab-active { color: #00CED1; }
-.tab-item.tab-active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 80rpx; height: 4rpx; background: #00CED1; border-radius: 4rpx; }
-
-/* ???? */
-.binding-list { max-height: 640rpx; overflow-y: auto; }
-.empty-state { padding: 80rpx 0; text-align: center; }
-.empty-icon { font-size: 64rpx; display: block; margin-bottom: 16rpx; }
-.empty-text { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-
-.binding-item { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.binding-item:last-child { border-bottom: none; }
-.user-avatar { width: 80rpx; height: 80rpx; border-radius: 50%; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; color: #00CED1; margin-right: 24rpx; flex-shrink: 0; }
-.user-avatar.avatar-converted { background: rgba(76,175,80,0.2); color: #4CAF50; }
-.user-avatar.avatar-expired { background: rgba(158,158,158,0.2); color: #9E9E9E; }
-.user-info { flex: 1; }
-.user-name { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
-.user-time { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
-.user-status { text-align: right; }
-.status-amount { font-size: 28rpx; color: #4CAF50; font-weight: 600; display: block; }
-.status-order { font-size: 22rpx; color: rgba(255,255,255,0.5); }
-.status-tag { font-size: 22rpx; padding: 8rpx 16rpx; border-radius: 16rpx; }
-.status-tag.tag-green { background: rgba(76,175,80,0.2); color: #4CAF50; }
-.status-tag.tag-orange { background: rgba(255,165,0,0.2); color: #FFA500; }
-.status-tag.tag-red { background: rgba(244,67,54,0.2); color: #F44336; }
-
-/* ????? - ?? Next.js */
-.invite-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 40rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.invite-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
-.invite-title { font-size: 30rpx; font-weight: 600; color: #fff; }
-.invite-code-box { background: rgba(0,206,209,0.2); padding: 12rpx 24rpx; border-radius: 16rpx; }
-.invite-code { font-size: 28rpx; font-weight: 600; color: #00CED1; font-family: 'Courier New', monospace; letter-spacing: 2rpx; }
-.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 1.5; display: block; }
-.invite-tip .gold { color: #FFD700; }
-.invite-tip .brand { color: #00CED1; }
-
-/* ?????? - ?? Next.js */
-.earnings-detail-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.detail-header { padding: 40rpx 40rpx 24rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.detail-title { font-size: 30rpx; font-weight: 600; color: #fff; }
-.detail-list { max-height: 480rpx; overflow-y: auto; }
-.detail-item { display: flex; align-items: center; justify-content: space-between; padding: 32rpx 40rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.detail-item:last-child { border-bottom: none; }
-.detail-left { display: flex; align-items: center; gap: 24rpx; flex: 1; }
-.detail-icon { width: 80rpx; height: 80rpx; border-radius: 20rpx; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
-.icon-gift { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.detail-info { flex: 1; }
-.detail-type { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
-.detail-time { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
-.detail-amount { font-size: 30rpx; font-weight: 600; color: #00CED1; }
-
-/* ???? - ?? Next.js */
-.share-section { display: flex; flex-direction: column; gap: 12rpx; width: 100%; margin-bottom: 24rpx; }
-.share-item { display: flex; align-items: center; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 32rpx; border: none; margin: 0; text-align: left; width: 100%; box-sizing: border-box; }
-.share-item::after { border: none; }
-.share-icon { width: 96rpx; height: 96rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; margin-right: 24rpx; flex-shrink: 0; }
-.share-icon.poster { background: rgba(103,58,183,0.2); }
-.share-icon.wechat { background: rgba(7,193,96,0.2); }
-.share-icon.link { background: rgba(158,158,158,0.2); }
-.icon-share-btn { width: 48rpx; height: 48rpx; display: block; }
-.share-icon.poster .icon-share-btn { filter: brightness(0) saturate(100%) invert(37%) sepia(73%) saturate(2296%) hue-rotate(252deg) brightness(96%) contrast(92%); }
-.share-icon.wechat .icon-share-btn { filter: brightness(0) saturate(100%) invert(58%) sepia(91%) saturate(1255%) hue-rotate(105deg) brightness(96%) contrast(97%); }
-.share-icon.link .icon-share-btn { filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.share-info { flex: 1; text-align: left; }
-.share-title { font-size: 28rpx; color: #fff; font-weight: 500; display: block; text-align: left; }
-.share-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; text-align: left; }
-.share-arrow-icon { width: 40rpx; height: 40rpx; display: block; flex-shrink: 0; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.share-btn-wechat { line-height: normal; font-size: inherit; padding: 24rpx 32rpx !important; margin: 0 !important; width: 100% !important; }
-
-/* ?????????????? + ???? + ???????? */
-/* ???????? backdrop-filter??????????????? */
-.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 32rpx; box-sizing: border-box; }
-
-.poster-dialog { width: 686rpx; border-radius: 24rpx; overflow: hidden; position: relative; background: transparent; }
-.poster-close { position: absolute; top: 20rpx; right: 20rpx; width: 56rpx; height: 56rpx; border-radius: 28rpx; background: rgba(0,0,0,0.25); color: rgba(255,255,255,0.9); display: flex; align-items: center; justify-content: center; z-index: 5; font-size: 28rpx; }
-
-/* ???? */
-.poster-card { position: relative; background: linear-gradient(135deg, #0a1628 0%, #0f2137 50%, #1a3a5c 100%); color: #fff; padding: 44rpx 40rpx 36rpx; }
-.poster-inner { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; text-align: center; }
-
-/* ???? */
-/* ???????? filter: blur ??????????????? + ???? */
-.poster-glow { position: absolute; width: 320rpx; height: 320rpx; border-radius: 50%; opacity: 0.6; z-index: 1; }
-.poster-glow-left { top: -120rpx; left: -160rpx; background: rgba(0,206,209,0.12); box-shadow: 0 0 140rpx 40rpx rgba(0,206,209,0.18); }
-.poster-glow-right { bottom: -140rpx; right: -160rpx; background: rgba(255,215,0,0.10); box-shadow: 0 0 160rpx 50rpx rgba(255,215,0,0.14); }
-.poster-ring { position: absolute; width: 520rpx; height: 520rpx; border-radius: 50%; border: 2rpx solid rgba(0,206,209,0.06); left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 1; }
-
-/* ???? */
-.poster-badges { display: flex; gap: 16rpx; margin-bottom: 24rpx; }
-.poster-badge { padding: 10rpx 22rpx; font-size: 20rpx; font-weight: 700; border-radius: 999rpx; border: 2rpx solid transparent; }
-.poster-badge-gold { background: rgba(255,215,0,0.18); color: #FFD700; border-color: rgba(255,215,0,0.28); }
-.poster-badge-brand { background: rgba(0,206,209,0.18); color: #00CED1; border-color: rgba(0,206,209,0.28); }
-
-/* ?? */
-.poster-title { margin-bottom: 8rpx; }
-.poster-title-line1 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #00CED1; }
-.poster-title-line2 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #fff; margin-top: 6rpx; }
-.poster-subtitle { display: block; font-size: 22rpx; color: rgba(255,255,255,0.6); margin-bottom: 28rpx; }
-
-/* ???? */
-.poster-stats { width: 100%; display: flex; gap: 16rpx; justify-content: center; margin-bottom: 24rpx; }
-.poster-stat { flex: 1; max-width: 190rpx; background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 16rpx; padding: 18rpx 10rpx; }
-.poster-stat-value { display: block; font-size: 44rpx; font-weight: 900; line-height: 1; }
-.poster-stat-label { display: block; font-size: 20rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; }
-.poster-stat-gold { color: #FFD700; }
-.poster-stat-brand { color: #00CED1; }
-.poster-stat-pink { color: #E91E63; }
-
-/* ?? */
-.poster-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: 10rpx; margin: 0 24rpx 26rpx; }
-.poster-tag { font-size: 20rpx; color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 12rpx; padding: 6rpx 14rpx; }
-
-/* ??? */
-.poster-recommender { display: flex; align-items: center; gap: 12rpx; background: rgba(0,206,209,0.10); border: 2rpx solid rgba(0,206,209,0.20); border-radius: 999rpx; padding: 12rpx 22rpx; margin-bottom: 22rpx; }
-.poster-avatar { width: 44rpx; height: 44rpx; border-radius: 22rpx; background: rgba(0,206,209,0.30); display: flex; align-items: center; justify-content: center; }
-.poster-avatar-text { font-size: 20rpx; font-weight: 800; color: #00CED1; }
-.poster-recommender-text { font-size: 22rpx; color: #00CED1; }
-
-/* ??? */
-.poster-discount { width: 100%; background: linear-gradient(90deg, rgba(255,215,0,0.10) 0%, rgba(233,30,99,0.10) 100%); border: 2rpx solid rgba(255,215,0,0.20); border-radius: 18rpx; padding: 18rpx 18rpx; margin-bottom: 26rpx; }
-.poster-discount-text { font-size: 22rpx; color: rgba(255,255,255,0.80); }
-.poster-discount-highlight { color: #00CED1; font-weight: 800; }
-
-/* ??? */
-.poster-qr-wrap { background: #fff; padding: 14rpx; border-radius: 16rpx; margin-bottom: 12rpx; box-shadow: 0 16rpx 40rpx rgba(0,0,0,0.35); }
-.poster-qr-img { width: 240rpx; height: 240rpx; display: block; }
-.poster-qr-tip { font-size: 20rpx; color: rgba(255,255,255,0.40); margin-bottom: 8rpx; }
-.poster-code { font-size: 22rpx; font-family: monospace; letter-spacing: 2rpx; color: rgba(0,206,209,0.80); }
-
-/* ??????? */
-.poster-footer { background: #fff; padding: 28rpx 28rpx 32rpx; display: flex; flex-direction: column; gap: 18rpx; }
-.poster-footer-tip { font-size: 22rpx; color: rgba(0,0,0,0.55); text-align: center; }
-.poster-footer-btn { height: 72rpx; border-radius: 18rpx; border: 2rpx solid rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: rgba(0,0,0,0.75); background: #fff; }
-
-
-
-/* ????- ?? Next.js */
-.empty-earnings { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 64rpx 40rpx; text-align: center; margin-bottom: 24rpx; }
-.empty-icon-wrapper { width: 128rpx; height: 128rpx; border-radius: 50%; background: rgba(28, 28, 30, 0.8); display: flex; align-items: center; justify-content: center; margin: 0 auto 32rpx; }
-.empty-gift-icon { width: 64rpx; height: 64rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.empty-title { font-size: 30rpx; font-weight: 500; color: #fff; display: block; margin-bottom: 16rpx; }
-.empty-desc { font-size: 26rpx; color: rgba(255,255,255,0.6); display: block; line-height: 1.5; }
-
-
- / * = = = = = TrGm5 ? = = = = = * /
- . l o a d i n g - o v e r l a y {
- p o s i t i o n : f i x e d ;
- t o p : 0 ;
- l e f t : 0 ;
- r i g h t : 0 ;
- b o t t o m : 0 ;
- b a c k g r o u n d : r g b a ( 0 , 0 , 0 , 0 . 7 ) ;
- b a c k d r o p - f i l t e r : b l u r ( 1 0 r p x ) ;
- z - i n d e x : 9 9 9 ;
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- j u s t i f y - c o n t e n t : c e n t e r ;
- }
-
- . l o a d i n g - c o n t e n t {
- d i s p l a y : f l e x ;
- f l e x - d i r e c t i o n : c o l u m n ;
- a l i g n - i t e m s : c e n t e r ;
- g a p : 2 4 r p x ;
- }
-
- . l o a d i n g - s p i n n e r {
- w i d t h : 8 0 r p x ;
- h e i g h t : 8 0 r p x ;
- b o r d e r : 6 r p x s o l i d r g b a ( 5 6 , 1 8 9 , 1 7 2 , 0 . 2 ) ;
- b o r d e r - t o p - c o l o r : # 3 8 b d a c ;
- b o r d e r - r a d i u s : 5 0 % ;
- a n i m a t i o n : s p i n 1 s l i n e a r i n f i n i t e ;
- }
-
- @ k e y f r a m e s s p i n {
- 0 % { t r a n s f o r m : r o t a t e ( 0 d e g ) ; }
- 1 0 0 % { t r a n s f o r m : r o t a t e ( 3 6 0 d e g ) ; }
- }
-
- . l o a d i n g - t e x t {
- f o n t - s i z e : 2 8 r p x ;
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 8 ) ;
- f o n t - w e i g h t : 5 0 0 ;
- }
-
- . c o n t e n t - l o a d i n g {
- o p a c i t y : 0 . 3 ;
- p o i n t e r - e v e n t s : n o n e ;
- }
-
- / * = = = = = ARlē^|op]͓\!} = = = = = * /
- . d e t a i l - i t e m {
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- g a p : 2 4 r p x ;
- p a d d i n g : 2 4 r p x ;
- b a c k g r o u n d : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 0 2 ) ;
- b o r d e r - r a d i u s : 1 6 r p x ;
- m a r g i n - b o t t o m : 1 6 r p x ;
- t r a n s i t i o n : a l l 0 . 3 s ;
- }
-
- . d e t a i l - i t e m : a c t i v e {
- b a c k g r o u n d : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 0 5 ) ;
- }
-
- . d e t a i l - a v a t a r - w r a p {
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - a v a t a r {
- w i d t h : 8 8 r p x ;
- h e i g h t : 8 8 r p x ;
- b o r d e r - r a d i u s : 5 0 % ;
- b o r d e r : 2 r p x s o l i d r g b a ( 5 6 , 1 8 9 , 1 7 2 , 0 . 2 ) ;
- }
-
- . d e t a i l - a v a t a r - t e x t {
- w i d t h : 8 8 r p x ;
- h e i g h t : 8 8 r p x ;
- b o r d e r - r a d i u s : 5 0 % ;
- b a c k g r o u n d : l i n e a r - g r a d i e n t ( 1 3 5 d e g , # 3 8 b d a c 0 % , # 2 d a 3 9 6 1 0 0 % ) ;
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- j u s t i f y - c o n t e n t : c e n t e r ;
- f o n t - s i z e : 3 6 r p x ;
- f o n t - w e i g h t : 7 0 0 ;
- c o l o r : # f f f f f f ;
- }
-
- . d e t a i l - c o n t e n t {
- f l e x : 1 ;
- d i s p l a y : f l e x ;
- f l e x - d i r e c t i o n : c o l u m n ;
- g a p : 8 r p x ;
- m i n - w i d t h : 0 ;
- }
-
- . d e t a i l - t o p {
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- j u s t i f y - c o n t e n t : s p a c e - b e t w e e n ;
- g a p : 1 6 r p x ;
- }
-
- . d e t a i l - b u y e r {
- f o n t - s i z e : 2 8 r p x ;
- f o n t - w e i g h t : 5 0 0 ;
- c o l o r : # f f f f f f ;
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - a m o u n t {
- f o n t - s i z e : 3 2 r p x ;
- f o n t - w e i g h t : 7 0 0 ;
- c o l o r : # 3 8 b d a c ;
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - p r o d u c t {
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- f o n t - s i z e : 2 4 r p x ;
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 6 ) ;
- o v e r f l o w : h i d d e n ;
- t e x t - o v e r f l o w : e l l i p s i s ;
- w h i t e - s p a c e : n o w r a p ;
- }
-
- . d e t a i l - b o o k {
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 7 ) ;
- f o n t - w e i g h t : 5 0 0 ;
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - c h a p t e r {
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 5 ) ;
- o v e r f l o w : h i d d e n ;
- t e x t - o v e r f l o w : e l l i p s i s ;
- w h i t e - s p a c e : n o w r a p ;
- }
-
- . d e t a i l - t i m e {
- f o n t - s i z e : 2 2 r p x ;
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 4 ) ;
- }
-
- / * = = = ARlē^|͓\!}m2 = = = * /
- . d e t a i l - i t e m { d i s p l a y : f l e x ; a l i g n - i t e m s : c e n t e r ; g a p : 2 4 r p x ; p a d d i n g : 2 4 r p x ; b a c k g r o u n d : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 0 2 ) ; b o r d e r - r a d i u s : 1 6 r p x ; m a r g i n - b o t t o m : 1 6 r p x ; }
- . d e t a i l - a v a t a r - w r a p { f l e x - s h r i n k : 0 ; }
- . d e t a i l - a v a t a r { w i d t h : 8 8 r p x ; h e i g h t : 8 8 r p x ; b o r d e r - r a d i u s : 5 0 % ; }
- . d e t a i l - a v a t a r - t e x t { w i d t h : 8 8 r p x ; h e i g h t : 8 8 r p x ; b o r d e r - r a d i u s : 5 0 % ; b a c k g r o u n d : l i n e a r - g r a d i e n t ( 1 3 5 d e g , # 3 8 b d a c 0 % , # 2 d a 3 9 6 1 0 0 % ) ; d i s p l a y : f l e x ; a l i g n - i t e m s : c e n t e r ; j u s t i f y - c o n t e n t : c e n t e r ; f o n t - s i z e : 3 6 r p x ; f o n t - w e i g h t : 7 0 0 ; c o l o r : # f f f f f f ; }
- . d e t a i l - c o n t e n t { f l e x : 1 ; d i s p l a y : f l e x ; f l e x - d i r e c t i o n : c o l u m n ; g a p : 8 r p x ; m i n - w i d t h : 0 ; o v e r f l o w : h i d d e n ; }
- . d e t a i l - t o p { d i s p l a y : f l e x ; a l i g n - i t e m s : c e n t e r ; j u s t i f y - c o n t e n t : s p a c e - b e t w e e n ; g a p : 1 6 r p x ; }
- . d e t a i l - b u y e r { f o n t - s i z e : 2 8 r p x ; f o n t - w e i g h t : 5 0 0 ; c o l o r : # f f f f f f ; f l e x : 1 ; o v e r f l o w : h i d d e n ; t e x t - o v e r f l o w : e l l i p s i s ; w h i t e - s p a c e : n o w r a p ; }
- . d e t a i l - a m o u n t { f o n t - s i z e : 3 2 r p x ; f o n t - w e i g h t : 7 0 0 ; c o l o r : # 3 8 b d a c ; f l e x - s h r i n k : 0 ; }
- . d e t a i l - p r o d u c t { d i s p l a y : f l e x ; a l i g n - i t e m s : b a s e l i n e ; f o n t - s i z e : 2 4 r p x ; c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 6 ) ; o v e r f l o w : h i d d e n ; }
- . d e t a i l - b o o k { c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 7 ) ; f o n t - w e i g h t : 5 0 0 ; m a x - w i d t h : 2 0 0 r p x ; o v e r f l o w : h i d d e n ; t e x t - o v e r f l o w : e l l i p s i s ; w h i t e - s p a c e : n o w r a p ; f l e x - s h r i n k : 1 ; }
- . d e t a i l - c h a p t e r { c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 5 ) ; f l e x : 1 ; o v e r f l o w : h i d d e n ; t e x t - o v e r f l o w : e l l i p s i s ; w h i t e - s p a c e : n o w r a p ; m a r g i n - l e f t : 4 r p x ; }
- . d e t a i l - t i m e { f o n t - s i z e : 2 2 r p x ; c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 4 ) ; }
-
-
\ No newline at end of file
diff --git a/归档/miniprogram/pages/referral/referral.wxss.broken b/归档/miniprogram/pages/referral/referral.wxss.broken
deleted file mode 100644
index 3c9e7c32..00000000
--- a/归档/miniprogram/pages/referral/referral.wxss.broken
+++ /dev/null
@@ -1,379 +0,0 @@
-/* ???????? - 1:1??Web?? */
-.page { min-height: 100vh; background: #000; padding-bottom: 64rpx; }
-
-/* ??? */
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-left { display: flex; gap: 16rpx; align-items: center; }
-.nav-back { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
-.nav-icon { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; flex: 1; text-align: center; }
-.nav-right-placeholder { width: 144rpx; }
-.nav-btn { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
-
-.content { padding: 24rpx; width: 100%; box-sizing: border-box; }
-
-/* ?????? */
-.expiring-banner { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 24rpx; margin-bottom: 24rpx; }
-.banner-icon { width: 80rpx; height: 80rpx; background: rgba(255,165,0,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
-.icon-bell-warning { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(64%) sepia(89%) saturate(1363%) hue-rotate(4deg) brightness(101%) contrast(102%); }
-.banner-content { flex: 1; }
-.banner-title { font-size: 28rpx; font-weight: 500; color: #fff; display: block; }
-.banner-desc { font-size: 24rpx; color: rgba(255,165,0,0.8); margin-top: 4rpx; display: block; }
-
-/* ???? - ?? Next.js */
-.earnings-card { position: relative; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 48rpx; margin-bottom: 24rpx; overflow: hidden; width: 100%; box-sizing: border-box; }
-.earnings-bg { position: absolute; top: 0; right: 0; width: 256rpx; height: 256rpx; background: rgba(0,206,209,0.15); border-radius: 50%; filter: blur(100rpx); }
-.earnings-main { position: relative; }
-.earnings-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 32rpx; }
-.earnings-left { display: flex; align-items: center; gap: 16rpx; }
-.wallet-icon { width: 80rpx; height: 80rpx; background: rgba(0,206,209,0.2); border-radius: 20rpx; display: flex; align-items: center; justify-content: center; }
-.icon-wallet { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.earnings-info { display: flex; flex-direction: column; gap: 8rpx; }
-.earnings-label { font-size: 24rpx; color: rgba(255,255,255,0.6); }
-.commission-rate { font-size: 24rpx; color: #00CED1; font-weight: 500; }
-.earnings-right { text-align: right; }
-.earnings-value { font-size: 60rpx; font-weight: 700; color: #fff; display: block; line-height: 1; }
-.pending-text { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; display: block; }
-
-.withdraw-btn { padding: 28rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #fff; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,206,209,0.3); }
-.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.2); color: rgba(255,255,255,0.3); box-shadow: none; }
-
-/* ???? - ?? Next.js 4??? */
-.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.stat-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 24rpx 12rpx; text-align: center; }
-.stat-value { font-size: 40rpx; font-weight: 700; color: #fff; display: block; }
-.stat-value.orange { color: #FFA500; }
-.stat-label { font-size: 20rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
-
-/* ????? */
-.visit-stat { display: flex; align-items: center; justify-content: center; gap: 12rpx; padding: 20rpx 32rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 24rpx; }
-.visit-label { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-.visit-value { font-size: 32rpx; font-weight: 700; color: #00CED1; }
-.visit-tip { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-
-/* ???? - ?? Next.js */
-.rules-card { background: rgba(0,206,209,0.05); border: 2rpx solid rgba(0,206,209,0.2); border-radius: 24rpx; padding: 32rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.rules-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
-.rules-icon { width: 64rpx; height: 64rpx; background: rgba(0,206,209,0.2); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; }
-.icon-alert { width: 32rpx; height: 32rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.rules-title { font-size: 28rpx; font-weight: 500; color: #fff; }
-.rules-list { padding-left: 8rpx; }
-.rule-item { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 2; display: block; margin-bottom: 4rpx; }
-.rule-item .gold { color: #FFD700; font-weight: 500; }
-.rule-item .brand { color: #00CED1; font-weight: 500; }
-.rule-item .orange { color: #FFA500; font-weight: 500; }
-
-/* ?????? - ?? Next.js */
-.binding-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.binding-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.binding-title { display: flex; align-items: center; gap: 12rpx; }
-.binding-icon-img { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.title-text { font-size: 30rpx; font-weight: 600; color: #fff; }
-.binding-count { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-.toggle-icon { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-
-/* Tab?? */
-.binding-tabs { display: flex; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.tab-item { flex: 1; padding: 24rpx 0; text-align: center; font-size: 26rpx; color: rgba(255,255,255,0.5); position: relative; }
-.tab-item.tab-active { color: #00CED1; }
-.tab-item.tab-active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 80rpx; height: 4rpx; background: #00CED1; border-radius: 4rpx; }
-
-/* ???? */
-.binding-list { max-height: 640rpx; overflow-y: auto; }
-.empty-state { padding: 80rpx 0; text-align: center; }
-.empty-icon { font-size: 64rpx; display: block; margin-bottom: 16rpx; }
-.empty-text { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-
-.binding-item { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.binding-item:last-child { border-bottom: none; }
-.user-avatar { width: 80rpx; height: 80rpx; border-radius: 50%; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; color: #00CED1; margin-right: 24rpx; flex-shrink: 0; }
-.user-avatar.avatar-converted { background: rgba(76,175,80,0.2); color: #4CAF50; }
-.user-avatar.avatar-expired { background: rgba(158,158,158,0.2); color: #9E9E9E; }
-.user-info { flex: 1; }
-.user-name { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
-.user-time { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
-.user-status { text-align: right; }
-.status-amount { font-size: 28rpx; color: #4CAF50; font-weight: 600; display: block; }
-.status-order { font-size: 22rpx; color: rgba(255,255,255,0.5); }
-.status-tag { font-size: 22rpx; padding: 8rpx 16rpx; border-radius: 16rpx; }
-.status-tag.tag-green { background: rgba(76,175,80,0.2); color: #4CAF50; }
-.status-tag.tag-orange { background: rgba(255,165,0,0.2); color: #FFA500; }
-.status-tag.tag-red { background: rgba(244,67,54,0.2); color: #F44336; }
-
-/* ????? - ?? Next.js */
-.invite-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 40rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.invite-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
-.invite-title { font-size: 30rpx; font-weight: 600; color: #fff; }
-.invite-code-box { background: rgba(0,206,209,0.2); padding: 12rpx 24rpx; border-radius: 16rpx; }
-.invite-code { font-size: 28rpx; font-weight: 600; color: #00CED1; font-family: 'Courier New', monospace; letter-spacing: 2rpx; }
-.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 1.5; display: block; }
-.invite-tip .gold { color: #FFD700; }
-.invite-tip .brand { color: #00CED1; }
-
-/* ?????? - ?? Next.js */
-.earnings-detail-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
-.detail-header { padding: 40rpx 40rpx 24rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.detail-title { font-size: 30rpx; font-weight: 600; color: #fff; }
-.detail-list { max-height: 480rpx; overflow-y: auto; }
-.detail-item { display: flex; align-items: center; justify-content: space-between; padding: 32rpx 40rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.detail-item:last-child { border-bottom: none; }
-.detail-left { display: flex; align-items: center; gap: 24rpx; flex: 1; }
-.detail-icon { width: 80rpx; height: 80rpx; border-radius: 20rpx; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
-.icon-gift { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
-.detail-info { flex: 1; }
-.detail-type { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
-.detail-time { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
-.detail-amount { font-size: 30rpx; font-weight: 600; color: #00CED1; }
-
-/* ???? - ?? Next.js */
-.share-section { display: flex; flex-direction: column; gap: 12rpx; width: 100%; margin-bottom: 24rpx; }
-.share-item { display: flex; align-items: center; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 32rpx; border: none; margin: 0; text-align: left; width: 100%; box-sizing: border-box; }
-.share-item::after { border: none; }
-.share-icon { width: 96rpx; height: 96rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; margin-right: 24rpx; flex-shrink: 0; }
-.share-icon.poster { background: rgba(103,58,183,0.2); }
-.share-icon.wechat { background: rgba(7,193,96,0.2); }
-.share-icon.link { background: rgba(158,158,158,0.2); }
-.icon-share-btn { width: 48rpx; height: 48rpx; display: block; }
-.share-icon.poster .icon-share-btn { filter: brightness(0) saturate(100%) invert(37%) sepia(73%) saturate(2296%) hue-rotate(252deg) brightness(96%) contrast(92%); }
-.share-icon.wechat .icon-share-btn { filter: brightness(0) saturate(100%) invert(58%) sepia(91%) saturate(1255%) hue-rotate(105deg) brightness(96%) contrast(97%); }
-.share-icon.link .icon-share-btn { filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.share-info { flex: 1; text-align: left; }
-.share-title { font-size: 28rpx; color: #fff; font-weight: 500; display: block; text-align: left; }
-.share-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; text-align: left; }
-.share-arrow-icon { width: 40rpx; height: 40rpx; display: block; flex-shrink: 0; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.share-btn-wechat { line-height: normal; font-size: inherit; padding: 24rpx 32rpx !important; margin: 0 !important; width: 100% !important; }
-
-/* ?????????????? + ???? + ???????? */
-/* ???????? backdrop-filter??????????????? */
-.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 32rpx; box-sizing: border-box; }
-
-.poster-dialog { width: 686rpx; border-radius: 24rpx; overflow: hidden; position: relative; background: transparent; }
-.poster-close { position: absolute; top: 20rpx; right: 20rpx; width: 56rpx; height: 56rpx; border-radius: 28rpx; background: rgba(0,0,0,0.25); color: rgba(255,255,255,0.9); display: flex; align-items: center; justify-content: center; z-index: 5; font-size: 28rpx; }
-
-/* ???? */
-.poster-card { position: relative; background: linear-gradient(135deg, #0a1628 0%, #0f2137 50%, #1a3a5c 100%); color: #fff; padding: 44rpx 40rpx 36rpx; }
-.poster-inner { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; text-align: center; }
-
-/* ???? */
-/* ???????? filter: blur ??????????????? + ???? */
-.poster-glow { position: absolute; width: 320rpx; height: 320rpx; border-radius: 50%; opacity: 0.6; z-index: 1; }
-.poster-glow-left { top: -120rpx; left: -160rpx; background: rgba(0,206,209,0.12); box-shadow: 0 0 140rpx 40rpx rgba(0,206,209,0.18); }
-.poster-glow-right { bottom: -140rpx; right: -160rpx; background: rgba(255,215,0,0.10); box-shadow: 0 0 160rpx 50rpx rgba(255,215,0,0.14); }
-.poster-ring { position: absolute; width: 520rpx; height: 520rpx; border-radius: 50%; border: 2rpx solid rgba(0,206,209,0.06); left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 1; }
-
-/* ???? */
-.poster-badges { display: flex; gap: 16rpx; margin-bottom: 24rpx; }
-.poster-badge { padding: 10rpx 22rpx; font-size: 20rpx; font-weight: 700; border-radius: 999rpx; border: 2rpx solid transparent; }
-.poster-badge-gold { background: rgba(255,215,0,0.18); color: #FFD700; border-color: rgba(255,215,0,0.28); }
-.poster-badge-brand { background: rgba(0,206,209,0.18); color: #00CED1; border-color: rgba(0,206,209,0.28); }
-
-/* ?? */
-.poster-title { margin-bottom: 8rpx; }
-.poster-title-line1 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #00CED1; }
-.poster-title-line2 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #fff; margin-top: 6rpx; }
-.poster-subtitle { display: block; font-size: 22rpx; color: rgba(255,255,255,0.6); margin-bottom: 28rpx; }
-
-/* ???? */
-.poster-stats { width: 100%; display: flex; gap: 16rpx; justify-content: center; margin-bottom: 24rpx; }
-.poster-stat { flex: 1; max-width: 190rpx; background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 16rpx; padding: 18rpx 10rpx; }
-.poster-stat-value { display: block; font-size: 44rpx; font-weight: 900; line-height: 1; }
-.poster-stat-label { display: block; font-size: 20rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; }
-.poster-stat-gold { color: #FFD700; }
-.poster-stat-brand { color: #00CED1; }
-.poster-stat-pink { color: #E91E63; }
-
-/* ?? */
-.poster-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: 10rpx; margin: 0 24rpx 26rpx; }
-.poster-tag { font-size: 20rpx; color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 12rpx; padding: 6rpx 14rpx; }
-
-/* ??? */
-.poster-recommender { display: flex; align-items: center; gap: 12rpx; background: rgba(0,206,209,0.10); border: 2rpx solid rgba(0,206,209,0.20); border-radius: 999rpx; padding: 12rpx 22rpx; margin-bottom: 22rpx; }
-.poster-avatar { width: 44rpx; height: 44rpx; border-radius: 22rpx; background: rgba(0,206,209,0.30); display: flex; align-items: center; justify-content: center; }
-.poster-avatar-text { font-size: 20rpx; font-weight: 800; color: #00CED1; }
-.poster-recommender-text { font-size: 22rpx; color: #00CED1; }
-
-/* ??? */
-.poster-discount { width: 100%; background: linear-gradient(90deg, rgba(255,215,0,0.10) 0%, rgba(233,30,99,0.10) 100%); border: 2rpx solid rgba(255,215,0,0.20); border-radius: 18rpx; padding: 18rpx 18rpx; margin-bottom: 26rpx; }
-.poster-discount-text { font-size: 22rpx; color: rgba(255,255,255,0.80); }
-.poster-discount-highlight { color: #00CED1; font-weight: 800; }
-
-/* ??? */
-.poster-qr-wrap { background: #fff; padding: 14rpx; border-radius: 16rpx; margin-bottom: 12rpx; box-shadow: 0 16rpx 40rpx rgba(0,0,0,0.35); }
-.poster-qr-img { width: 240rpx; height: 240rpx; display: block; }
-.poster-qr-tip { font-size: 20rpx; color: rgba(255,255,255,0.40); margin-bottom: 8rpx; }
-.poster-code { font-size: 22rpx; font-family: monospace; letter-spacing: 2rpx; color: rgba(0,206,209,0.80); }
-
-/* ??????? */
-.poster-footer { background: #fff; padding: 28rpx 28rpx 32rpx; display: flex; flex-direction: column; gap: 18rpx; }
-.poster-footer-tip { font-size: 22rpx; color: rgba(0,0,0,0.55); text-align: center; }
-.poster-footer-btn { height: 72rpx; border-radius: 18rpx; border: 2rpx solid rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: rgba(0,0,0,0.75); background: #fff; }
-
-
-
-/* ????- ?? Next.js */
-.empty-earnings { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 64rpx 40rpx; text-align: center; margin-bottom: 24rpx; }
-.empty-icon-wrapper { width: 128rpx; height: 128rpx; border-radius: 50%; background: rgba(28, 28, 30, 0.8); display: flex; align-items: center; justify-content: center; margin: 0 auto 32rpx; }
-.empty-gift-icon { width: 64rpx; height: 64rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
-.empty-title { font-size: 30rpx; font-weight: 500; color: #fff; display: block; margin-bottom: 16rpx; }
-.empty-desc { font-size: 26rpx; color: rgba(255,255,255,0.6); display: block; line-height: 1.5; }
-
-
- / * = = = = = TrGm5 ? = = = = = * /
- . l o a d i n g - o v e r l a y {
- p o s i t i o n : f i x e d ;
- t o p : 0 ;
- l e f t : 0 ;
- r i g h t : 0 ;
- b o t t o m : 0 ;
- b a c k g r o u n d : r g b a ( 0 , 0 , 0 , 0 . 7 ) ;
- b a c k d r o p - f i l t e r : b l u r ( 1 0 r p x ) ;
- z - i n d e x : 9 9 9 ;
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- j u s t i f y - c o n t e n t : c e n t e r ;
- }
-
- . l o a d i n g - c o n t e n t {
- d i s p l a y : f l e x ;
- f l e x - d i r e c t i o n : c o l u m n ;
- a l i g n - i t e m s : c e n t e r ;
- g a p : 2 4 r p x ;
- }
-
- . l o a d i n g - s p i n n e r {
- w i d t h : 8 0 r p x ;
- h e i g h t : 8 0 r p x ;
- b o r d e r : 6 r p x s o l i d r g b a ( 5 6 , 1 8 9 , 1 7 2 , 0 . 2 ) ;
- b o r d e r - t o p - c o l o r : # 3 8 b d a c ;
- b o r d e r - r a d i u s : 5 0 % ;
- a n i m a t i o n : s p i n 1 s l i n e a r i n f i n i t e ;
- }
-
- @ k e y f r a m e s s p i n {
- 0 % { t r a n s f o r m : r o t a t e ( 0 d e g ) ; }
- 1 0 0 % { t r a n s f o r m : r o t a t e ( 3 6 0 d e g ) ; }
- }
-
- . l o a d i n g - t e x t {
- f o n t - s i z e : 2 8 r p x ;
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 8 ) ;
- f o n t - w e i g h t : 5 0 0 ;
- }
-
- . c o n t e n t - l o a d i n g {
- o p a c i t y : 0 . 3 ;
- p o i n t e r - e v e n t s : n o n e ;
- }
-
- / * = = = = = ARlē^|op]͓\!} = = = = = * /
- . d e t a i l - i t e m {
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- g a p : 2 4 r p x ;
- p a d d i n g : 2 4 r p x ;
- b a c k g r o u n d : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 0 2 ) ;
- b o r d e r - r a d i u s : 1 6 r p x ;
- m a r g i n - b o t t o m : 1 6 r p x ;
- t r a n s i t i o n : a l l 0 . 3 s ;
- }
-
- . d e t a i l - i t e m : a c t i v e {
- b a c k g r o u n d : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 0 5 ) ;
- }
-
- . d e t a i l - a v a t a r - w r a p {
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - a v a t a r {
- w i d t h : 8 8 r p x ;
- h e i g h t : 8 8 r p x ;
- b o r d e r - r a d i u s : 5 0 % ;
- b o r d e r : 2 r p x s o l i d r g b a ( 5 6 , 1 8 9 , 1 7 2 , 0 . 2 ) ;
- }
-
- . d e t a i l - a v a t a r - t e x t {
- w i d t h : 8 8 r p x ;
- h e i g h t : 8 8 r p x ;
- b o r d e r - r a d i u s : 5 0 % ;
- b a c k g r o u n d : l i n e a r - g r a d i e n t ( 1 3 5 d e g , # 3 8 b d a c 0 % , # 2 d a 3 9 6 1 0 0 % ) ;
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- j u s t i f y - c o n t e n t : c e n t e r ;
- f o n t - s i z e : 3 6 r p x ;
- f o n t - w e i g h t : 7 0 0 ;
- c o l o r : # f f f f f f ;
- }
-
- . d e t a i l - c o n t e n t {
- f l e x : 1 ;
- d i s p l a y : f l e x ;
- f l e x - d i r e c t i o n : c o l u m n ;
- g a p : 8 r p x ;
- m i n - w i d t h : 0 ;
- }
-
- . d e t a i l - t o p {
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- j u s t i f y - c o n t e n t : s p a c e - b e t w e e n ;
- g a p : 1 6 r p x ;
- }
-
- . d e t a i l - b u y e r {
- f o n t - s i z e : 2 8 r p x ;
- f o n t - w e i g h t : 5 0 0 ;
- c o l o r : # f f f f f f ;
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - a m o u n t {
- f o n t - s i z e : 3 2 r p x ;
- f o n t - w e i g h t : 7 0 0 ;
- c o l o r : # 3 8 b d a c ;
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - p r o d u c t {
- d i s p l a y : f l e x ;
- a l i g n - i t e m s : c e n t e r ;
- f o n t - s i z e : 2 4 r p x ;
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 6 ) ;
- o v e r f l o w : h i d d e n ;
- t e x t - o v e r f l o w : e l l i p s i s ;
- w h i t e - s p a c e : n o w r a p ;
- }
-
- . d e t a i l - b o o k {
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 7 ) ;
- f o n t - w e i g h t : 5 0 0 ;
- f l e x - s h r i n k : 0 ;
- }
-
- . d e t a i l - c h a p t e r {
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 5 ) ;
- o v e r f l o w : h i d d e n ;
- t e x t - o v e r f l o w : e l l i p s i s ;
- w h i t e - s p a c e : n o w r a p ;
- }
-
- . d e t a i l - t i m e {
- f o n t - s i z e : 2 2 r p x ;
- c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 4 ) ;
- }
-
- / * = = = ARlē^|͓\!}m2 = = = * /
- . d e t a i l - i t e m { d i s p l a y : f l e x ; a l i g n - i t e m s : c e n t e r ; g a p : 2 4 r p x ; p a d d i n g : 2 4 r p x ; b a c k g r o u n d : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 0 2 ) ; b o r d e r - r a d i u s : 1 6 r p x ; m a r g i n - b o t t o m : 1 6 r p x ; }
- . d e t a i l - a v a t a r - w r a p { f l e x - s h r i n k : 0 ; }
- . d e t a i l - a v a t a r { w i d t h : 8 8 r p x ; h e i g h t : 8 8 r p x ; b o r d e r - r a d i u s : 5 0 % ; }
- . d e t a i l - a v a t a r - t e x t { w i d t h : 8 8 r p x ; h e i g h t : 8 8 r p x ; b o r d e r - r a d i u s : 5 0 % ; b a c k g r o u n d : l i n e a r - g r a d i e n t ( 1 3 5 d e g , # 3 8 b d a c 0 % , # 2 d a 3 9 6 1 0 0 % ) ; d i s p l a y : f l e x ; a l i g n - i t e m s : c e n t e r ; j u s t i f y - c o n t e n t : c e n t e r ; f o n t - s i z e : 3 6 r p x ; f o n t - w e i g h t : 7 0 0 ; c o l o r : # f f f f f f ; }
- . d e t a i l - c o n t e n t { f l e x : 1 ; d i s p l a y : f l e x ; f l e x - d i r e c t i o n : c o l u m n ; g a p : 8 r p x ; m i n - w i d t h : 0 ; o v e r f l o w : h i d d e n ; }
- . d e t a i l - t o p { d i s p l a y : f l e x ; a l i g n - i t e m s : c e n t e r ; j u s t i f y - c o n t e n t : s p a c e - b e t w e e n ; g a p : 1 6 r p x ; }
- . d e t a i l - b u y e r { f o n t - s i z e : 2 8 r p x ; f o n t - w e i g h t : 5 0 0 ; c o l o r : # f f f f f f ; f l e x : 1 ; o v e r f l o w : h i d d e n ; t e x t - o v e r f l o w : e l l i p s i s ; w h i t e - s p a c e : n o w r a p ; }
- . d e t a i l - a m o u n t { f o n t - s i z e : 3 2 r p x ; f o n t - w e i g h t : 7 0 0 ; c o l o r : # 3 8 b d a c ; f l e x - s h r i n k : 0 ; }
- . d e t a i l - p r o d u c t { d i s p l a y : f l e x ; a l i g n - i t e m s : b a s e l i n e ; f o n t - s i z e : 2 4 r p x ; c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 6 ) ; o v e r f l o w : h i d d e n ; }
- . d e t a i l - b o o k { c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 7 ) ; f o n t - w e i g h t : 5 0 0 ; m a x - w i d t h : 2 0 0 r p x ; o v e r f l o w : h i d d e n ; t e x t - o v e r f l o w : e l l i p s i s ; w h i t e - s p a c e : n o w r a p ; f l e x - s h r i n k : 1 ; }
- . d e t a i l - c h a p t e r { c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 5 ) ; f l e x : 1 ; o v e r f l o w : h i d d e n ; t e x t - o v e r f l o w : e l l i p s i s ; w h i t e - s p a c e : n o w r a p ; m a r g i n - l e f t : 4 r p x ; }
- . d e t a i l - t i m e { f o n t - s i z e : 2 2 r p x ; c o l o r : r g b a ( 2 5 5 , 2 5 5 , 2 5 5 , 0 . 4 ) ; }
-
-
\ No newline at end of file
diff --git a/归档/miniprogram/pages/search/search.js b/归档/miniprogram/pages/search/search.js
deleted file mode 100644
index 1ac887d9..00000000
--- a/归档/miniprogram/pages/search/search.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * Soul创业派对 - 章节搜索页
- * 搜索章节标题和内容
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- keyword: '',
- results: [],
- loading: false,
- searched: false,
- total: 0,
- // 热门搜索关键词
- hotKeywords: ['私域', '电商', '流量', '赚钱', '创业', 'Soul', '抖音', '变现'],
- // 热门章节推荐
- 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: '真实的赚钱' }
- ]
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight || 44
- })
- // 加载热门章节
- this.loadHotChapters()
- },
-
- // 加载热门章节(从服务器获取点击量高的章节)
- async loadHotChapters() {
- try {
- const res = await app.request('/api/miniprogram/book/hot')
- if (res && res.success && res.chapters?.length > 0) {
- this.setData({ hotChapters: res.chapters })
- }
- } catch (e) {
- console.log('加载热门章节失败,使用默认数据')
- }
- },
-
- // 输入关键词
- onInput(e) {
- this.setData({ keyword: e.detail.value })
- },
-
- // 清空搜索
- clearSearch() {
- this.setData({
- keyword: '',
- results: [],
- searched: false,
- total: 0
- })
- },
-
- // 点击热门关键词
- onHotKeyword(e) {
- const keyword = e.currentTarget.dataset.keyword
- this.setData({ keyword })
- this.doSearch()
- },
-
- // 执行搜索
- async doSearch() {
- const { keyword } = this.data
- if (!keyword || keyword.trim().length < 1) {
- wx.showToast({ title: '请输入搜索关键词', icon: 'none' })
- return
- }
-
- this.setData({ loading: true, searched: true })
-
- try {
- const res = await app.request(`/api/miniprogram/book/search?q=${encodeURIComponent(keyword.trim())}`)
-
- if (res && res.success) {
- this.setData({
- results: res.results || [],
- total: res.total || 0
- })
- } else {
- this.setData({ results: [], total: 0 })
- }
- } catch (e) {
- console.error('搜索失败:', e)
- wx.showToast({ title: '搜索失败', icon: 'none' })
- this.setData({ results: [], total: 0 })
- } finally {
- this.setData({ loading: false })
- }
- },
-
- // 跳转阅读
- goToRead(e) {
- const id = e.currentTarget.dataset.id
- wx.navigateTo({ url: `/pages/read/read?id=${id}` })
- },
-
- // 返回上一页
- goBack() {
- wx.navigateBack()
- }
-})
diff --git a/归档/miniprogram/pages/search/search.json b/归档/miniprogram/pages/search/search.json
deleted file mode 100644
index 877955df..00000000
--- a/归档/miniprogram/pages/search/search.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom",
- "navigationBarTitleText": "搜索"
-}
diff --git a/归档/miniprogram/pages/search/search.wxml b/归档/miniprogram/pages/search/search.wxml
deleted file mode 100644
index 60fa91fe..00000000
--- a/归档/miniprogram/pages/search/search.wxml
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
-
-
- ←
-
-
- 🔍
-
- ×
-
- 搜索
-
-
-
-
-
-
-
-
- 热门搜索
-
- {{item}}
-
-
-
-
-
- 热门章节
-
-
- {{index + 1}}
-
- {{item.title}}
- {{item.part}}
-
- {{item.tag}}
-
-
-
-
-
-
-
-
-
- 搜索中...
-
-
-
-
-
-
-
-
-
- {{item.title}}
- {{item.part}}
-
- {{item.matchedContent}}
-
- →
-
-
-
-
-
-
- 🔍
- 未找到相关章节
- 换个关键词试试
-
-
-
-
diff --git a/归档/miniprogram/pages/search/search.wxss b/归档/miniprogram/pages/search/search.wxss
deleted file mode 100644
index aa56b19b..00000000
--- a/归档/miniprogram/pages/search/search.wxss
+++ /dev/null
@@ -1,335 +0,0 @@
-/* 章节搜索页样式 */
-.page {
- min-height: 100vh;
- background: linear-gradient(180deg, #0a0a0a 0%, #111111 100%);
-}
-
-/* 导航栏 */
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(10, 10, 10, 0.95);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
-}
-
-.nav-content {
- display: flex;
- align-items: center;
- padding: 8rpx 24rpx;
- height: 88rpx;
-}
-
-.back-btn {
- width: 60rpx;
- height: 60rpx;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.back-icon {
- font-size: 40rpx;
- color: #00CED1;
-}
-
-.search-input-wrap {
- flex: 1;
- display: flex;
- align-items: center;
- background: rgba(255,255,255,0.08);
- border-radius: 40rpx;
- padding: 0 24rpx;
- height: 64rpx;
- margin: 0 16rpx;
-}
-
-.search-icon-small {
- font-size: 28rpx;
- margin-right: 12rpx;
-}
-
-.search-input {
- flex: 1;
- font-size: 28rpx;
- color: #fff;
-}
-
-.search-input::placeholder {
- color: rgba(255,255,255,0.4);
-}
-
-.clear-btn {
- width: 40rpx;
- height: 40rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 32rpx;
- color: rgba(255,255,255,0.5);
-}
-
-.search-btn {
- font-size: 28rpx;
- color: #00CED1;
- padding: 0 16rpx;
-}
-
-/* 主内容 */
-.main-content {
- padding: 24rpx;
-}
-
-/* 热门搜索 */
-.hot-section {
- padding: 24rpx 0;
-}
-
-.section-title {
- font-size: 28rpx;
- color: rgba(255,255,255,0.6);
- margin-bottom: 24rpx;
- display: block;
-}
-
-.hot-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
-}
-
-.hot-tag {
- background: rgba(0, 206, 209, 0.15);
- color: #00CED1;
- padding: 16rpx 32rpx;
- border-radius: 32rpx;
- font-size: 28rpx;
- border: 1rpx solid rgba(0, 206, 209, 0.3);
-}
-
-/* 热门章节 */
-.hot-chapters {
- padding: 32rpx 0;
- margin-top: 16rpx;
-}
-
-.chapter-list {
- display: flex;
- flex-direction: column;
- gap: 16rpx;
-}
-
-.chapter-item {
- display: flex;
- align-items: center;
- background: rgba(255,255,255,0.05);
- border-radius: 20rpx;
- padding: 24rpx;
- gap: 20rpx;
-}
-
-.chapter-rank {
- width: 48rpx;
- height: 48rpx;
- background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 26rpx;
- font-weight: 600;
- color: #000;
- flex-shrink: 0;
-}
-
-.chapter-item:nth-child(1) .chapter-rank { background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); }
-.chapter-item:nth-child(2) .chapter-rank { background: linear-gradient(135deg, #C0C0C0 0%, #A9A9A9 100%); }
-.chapter-item:nth-child(3) .chapter-rank { background: linear-gradient(135deg, #CD7F32 0%, #8B4513 100%); }
-
-.chapter-info {
- flex: 1;
- min-width: 0;
-}
-
-.chapter-title {
- font-size: 28rpx;
- color: #fff;
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.chapter-part {
- font-size: 22rpx;
- color: rgba(255,255,255,0.4);
- margin-top: 6rpx;
- display: block;
-}
-
-.chapter-tag {
- padding: 8rpx 16rpx;
- border-radius: 16rpx;
- font-size: 22rpx;
- flex-shrink: 0;
-}
-
-.chapter-tag.tag-free { background: rgba(76, 175, 80, 0.2); color: #4CAF50; }
-.chapter-tag.tag-hot { background: rgba(255, 87, 34, 0.2); color: #FF5722; }
-.chapter-tag.tag-new { background: rgba(233, 30, 99, 0.2); color: #E91E63; }
-
-/* 搜索结果 */
-.results-section {
- padding: 16rpx 0;
-}
-
-.results-header {
- margin-bottom: 24rpx;
-}
-
-.results-count {
- font-size: 26rpx;
- color: rgba(255,255,255,0.5);
-}
-
-.results-list {
- display: flex;
- flex-direction: column;
- gap: 24rpx;
-}
-
-.result-item {
- background: rgba(255,255,255,0.05);
- border-radius: 24rpx;
- padding: 28rpx;
- position: relative;
- border: 1rpx solid rgba(255,255,255,0.08);
-}
-
-.result-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 16rpx;
-}
-
-.result-chapter {
- font-size: 24rpx;
- color: #00CED1;
- font-weight: 500;
-}
-
-.result-tags {
- display: flex;
- gap: 12rpx;
-}
-
-.tag {
- font-size: 20rpx;
- padding: 6rpx 16rpx;
- border-radius: 20rpx;
-}
-
-.tag-match {
- background: rgba(147, 112, 219, 0.2);
- color: #9370DB;
-}
-
-.tag-free {
- background: rgba(76, 175, 80, 0.2);
- color: #4CAF50;
-}
-
-.result-title {
- font-size: 30rpx;
- color: #fff;
- font-weight: 500;
- line-height: 1.5;
- display: block;
- margin-bottom: 8rpx;
-}
-
-.result-part {
- font-size: 24rpx;
- color: rgba(255,255,255,0.5);
- display: block;
-}
-
-.result-content {
- margin-top: 16rpx;
- padding-top: 16rpx;
- border-top: 1rpx solid rgba(255,255,255,0.1);
-}
-
-.content-preview {
- font-size: 24rpx;
- color: rgba(255,255,255,0.6);
- line-height: 1.6;
- display: -webkit-box;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
-}
-
-.result-arrow {
- position: absolute;
- right: 28rpx;
- top: 50%;
- transform: translateY(-50%);
- font-size: 32rpx;
- color: rgba(255,255,255,0.3);
-}
-
-/* 加载状态 */
-.loading-wrap {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 100rpx 0;
-}
-
-.loading-spinner {
- width: 60rpx;
- height: 60rpx;
- border: 4rpx solid rgba(0, 206, 209, 0.3);
- border-top-color: #00CED1;
- border-radius: 50%;
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- to { transform: rotate(360deg); }
-}
-
-.loading-text {
- margin-top: 24rpx;
- font-size: 28rpx;
- color: rgba(255,255,255,0.5);
-}
-
-/* 空状态 */
-.empty-wrap {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 100rpx 0;
-}
-
-.empty-icon {
- font-size: 80rpx;
- margin-bottom: 24rpx;
-}
-
-.empty-text {
- font-size: 32rpx;
- color: rgba(255,255,255,0.6);
- margin-bottom: 12rpx;
-}
-
-.empty-hint {
- font-size: 26rpx;
- color: rgba(255,255,255,0.4);
-}
diff --git a/归档/miniprogram/pages/settings/settings.js b/归档/miniprogram/pages/settings/settings.js
deleted file mode 100644
index 157af2f0..00000000
--- a/归档/miniprogram/pages/settings/settings.js
+++ /dev/null
@@ -1,497 +0,0 @@
-/**
- * Soul创业派对 - 设置页
- * 账号绑定功能
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- isLoggedIn: false,
- userInfo: null,
- version: '1.0.0',
-
- // 绑定信息
- phoneNumber: '',
- wechatId: '',
- alipayAccount: '',
- address: '',
-
- // 自动提现(默认开启)
- autoWithdrawEnabled: true,
-
- // 绑定弹窗
- showBindModal: false,
- bindType: '', // phone | wechat | alipay
- bindValue: ''
- },
-
- onLoad() {
- this.setData({
- statusBarHeight: app.globalData.statusBarHeight,
- isLoggedIn: app.globalData.isLoggedIn,
- userInfo: app.globalData.userInfo
- })
- this.loadBindingInfo()
- },
-
- onShow() {
- this.loadBindingInfo()
- },
-
- // 加载绑定信息
- loadBindingInfo() {
- const { userInfo, isLoggedIn } = app.globalData
- if (isLoggedIn && userInfo) {
- // 从本地存储或用户信息中获取绑定数据
- const phoneNumber = wx.getStorageSync('user_phone') || userInfo.phone || ''
- const wechatId = wx.getStorageSync('user_wechat') || userInfo.wechat || ''
- const alipayAccount = wx.getStorageSync('user_alipay') || userInfo.alipay || ''
- const address = wx.getStorageSync('user_address') || userInfo.address || ''
- // 默认开启自动提现
- const autoWithdrawEnabled = wx.getStorageSync('auto_withdraw_enabled') !== false
-
- this.setData({
- isLoggedIn: true,
- userInfo,
- phoneNumber,
- wechatId,
- alipayAccount,
- address,
- autoWithdrawEnabled
- })
- }
- },
-
- // 一键获取收货地址
- getAddress() {
- wx.chooseAddress({
- success: (res) => {
- console.log('[Settings] 获取地址成功:', res)
- const fullAddress = `${res.provinceName || ''}${res.cityName || ''}${res.countyName || ''}${res.detailInfo || ''}`
-
- if (fullAddress.trim()) {
- wx.setStorageSync('user_address', fullAddress)
- this.setData({ address: fullAddress })
-
- // 更新用户信息
- if (app.globalData.userInfo) {
- app.globalData.userInfo.address = fullAddress
- wx.setStorageSync('userInfo', app.globalData.userInfo)
- }
-
- // 同步到服务器
- this.syncAddressToServer(fullAddress)
-
- wx.showToast({ title: '地址已获取', icon: 'success' })
- }
- },
- fail: (e) => {
- console.log('[Settings] 获取地址失败:', e)
- if (e.errMsg?.includes('cancel')) {
- // 用户取消,不提示
- return
- }
- if (e.errMsg?.includes('auth deny') || e.errMsg?.includes('authorize')) {
- wx.showModal({
- title: '需要授权',
- content: '请在设置中允许获取收货地址',
- confirmText: '去设置',
- success: (res) => {
- if (res.confirm) wx.openSetting()
- }
- })
- } else {
- wx.showToast({ title: '获取失败,请重试', icon: 'none' })
- }
- }
- })
- },
-
- // 同步地址到服务器
- async syncAddressToServer(address) {
- try {
- const userId = app.globalData.userInfo?.id
- if (!userId) return
-
- await app.request('/api/miniprogram/user/update', {
- method: 'POST',
- data: { userId, address }
- })
- console.log('[Settings] 地址已同步到服务器')
- } catch (e) {
- console.log('[Settings] 同步地址失败:', e)
- }
- },
-
- // 切换自动提现
- async toggleAutoWithdraw(e) {
- const enabled = e.detail.value
-
- // 检查是否绑定了支付方式
- if (enabled && !this.data.wechatId && !this.data.alipayAccount) {
- wx.showToast({ title: '请先绑定微信号或支付宝', icon: 'none' })
- this.setData({ autoWithdrawEnabled: false })
- return
- }
-
- // 开启时需要确认
- if (enabled) {
- wx.showModal({
- title: '开启自动提现',
- content: `收益将自动打款到您的${this.data.alipayAccount ? '支付宝' : '微信'}账户,确认开启吗?`,
- success: async (res) => {
- if (res.confirm) {
- this.setData({ autoWithdrawEnabled: true })
- wx.setStorageSync('auto_withdraw_enabled', true)
-
- // 同步到服务器
- try {
- await app.request('/api/miniprogram/user/update', {
- method: 'POST',
- data: {
- userId: app.globalData.userInfo?.id,
- autoWithdraw: true,
- withdrawAccount: this.data.alipayAccount || this.data.wechatId
- }
- })
- } catch (e) {
- console.log('同步自动提现设置失败', e)
- }
-
- wx.showToast({ title: '已开启自动提现', icon: 'success' })
- } else {
- this.setData({ autoWithdrawEnabled: false })
- }
- }
- })
- } else {
- this.setData({ autoWithdrawEnabled: false })
- wx.setStorageSync('auto_withdraw_enabled', false)
- wx.showToast({ title: '已关闭自动提现', icon: 'success' })
- }
- },
-
- // 绑定手机号
- bindPhone() {
- this.setData({
- showBindModal: true,
- bindType: 'phone',
- bindValue: ''
- })
- },
-
- // 微信号输入
- onWechatInput(e) {
- this.setData({ wechatId: e.detail.value })
- },
-
- // 保存微信号
- async saveWechat() {
- const { wechatId } = this.data
- if (!wechatId || wechatId.length < 6) return
-
- wx.setStorageSync('user_wechat', wechatId)
-
- // 更新用户信息
- if (app.globalData.userInfo) {
- app.globalData.userInfo.wechat = wechatId
- wx.setStorageSync('userInfo', app.globalData.userInfo)
- }
-
- // 同步到服务器
- try {
- await app.request('/api/miniprogram/user/update', {
- method: 'POST',
- data: {
- userId: app.globalData.userInfo?.id,
- wechat: wechatId
- }
- })
- wx.showToast({ title: '微信号已保存', icon: 'success' })
- } catch (e) {
- console.log('保存微信号失败', e)
- }
- },
-
- // 输入绑定值
- onBindInput(e) {
- let value = e.detail.value
- if (this.data.bindType === 'phone') {
- value = value.replace(/\D/g, '').slice(0, 11)
- }
- this.setData({ bindValue: value })
- },
-
- // 确认绑定
- confirmBind() {
- const { bindType, bindValue } = this.data
-
- if (!bindValue) {
- wx.showToast({ title: '请输入内容', icon: 'none' })
- return
- }
-
- // 验证
- if (bindType === 'phone' && !/^1[3-9]\d{9}$/.test(bindValue)) {
- wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
- return
- }
-
- if (bindType === 'wechat' && bindValue.length < 6) {
- wx.showToast({ title: '微信号至少6位', icon: 'none' })
- return
- }
-
- if (bindType === 'alipay' && !bindValue.includes('@') && !/^1[3-9]\d{9}$/.test(bindValue)) {
- wx.showToast({ title: '请输入正确的支付宝账号', icon: 'none' })
- return
- }
-
- // 保存绑定信息到本地
- if (bindType === 'phone') {
- wx.setStorageSync('user_phone', bindValue)
- this.setData({ phoneNumber: bindValue })
- } else if (bindType === 'wechat') {
- wx.setStorageSync('user_wechat', bindValue)
- this.setData({ wechatId: bindValue })
- } else if (bindType === 'alipay') {
- wx.setStorageSync('user_alipay', bindValue)
- this.setData({ alipayAccount: bindValue })
- }
-
- // 同步到服务器
- this.syncProfileToServer()
-
- this.setData({ showBindModal: false })
- wx.showToast({ title: '绑定成功', icon: 'success' })
- },
-
- // 同步资料到服务器
- async syncProfileToServer() {
- try {
- const userId = app.globalData.userInfo?.id
- if (!userId) return
-
- const res = await app.request('/api/miniprogram/user/profile', {
- method: 'POST',
- data: {
- userId,
- phone: this.data.phoneNumber || undefined,
- wechatId: this.data.wechatId || undefined
- }
- })
-
- if (res.success) {
- console.log('[Settings] 资料同步成功')
- // 更新本地用户信息
- if (app.globalData.userInfo) {
- app.globalData.userInfo.phone = this.data.phoneNumber
- app.globalData.userInfo.wechatId = this.data.wechatId
- wx.setStorageSync('userInfo', app.globalData.userInfo)
- }
- }
- } catch (e) {
- console.log('[Settings] 资料同步失败:', e)
- }
- },
-
- // 获取微信头像(新版授权)
- async getWechatAvatar() {
- try {
- const res = await wx.getUserProfile({
- desc: '用于完善会员资料'
- })
-
- if (res.userInfo) {
- const { nickName, avatarUrl: tempAvatarUrl } = res.userInfo
-
- wx.showLoading({ title: '上传中...', mask: true })
-
- // 1. 先上传图片到服务器
- console.log('[Settings] 开始上传头像:', tempAvatarUrl)
-
- const uploadRes = await new Promise((resolve, reject) => {
- wx.uploadFile({
- url: app.globalData.baseUrl + '/api/miniprogram/upload',
- filePath: tempAvatarUrl,
- name: 'file',
- formData: {
- folder: 'avatars'
- },
- success: (uploadResult) => {
- try {
- const data = JSON.parse(uploadResult.data)
- if (data.success) {
- resolve(data)
- } else {
- reject(new Error(data.error || '上传失败'))
- }
- } catch (err) {
- reject(new Error('解析响应失败'))
- }
- },
- fail: (err) => {
- reject(err)
- }
- })
- })
-
- // 2. 获取上传后的完整URL
- const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
- console.log('[Settings] 头像上传成功:', avatarUrl)
-
- // 3. 更新本地
- this.setData({
- userInfo: {
- ...this.data.userInfo,
- nickname: nickName,
- avatar: avatarUrl
- }
- })
-
- // 4. 同步到服务器数据库
- const userId = app.globalData.userInfo?.id
- if (userId) {
- await app.request('/api/miniprogram/user/profile', {
- method: 'POST',
- data: { userId, nickname: nickName, avatar: avatarUrl }
- })
- }
-
- // 5. 更新全局
- if (app.globalData.userInfo) {
- app.globalData.userInfo.nickname = nickName
- app.globalData.userInfo.avatar = avatarUrl
- wx.setStorageSync('userInfo', app.globalData.userInfo)
- }
-
- wx.hideLoading()
- wx.showToast({ title: '头像更新成功', icon: 'success' })
- }
- } catch (e) {
- wx.hideLoading()
- console.error('[Settings] 获取头像失败:', e)
- wx.showToast({
- title: e.message || '获取头像失败',
- icon: 'none'
- })
- }
- },
-
- // 一键获取微信手机号(button组件回调)
- async onGetPhoneNumber(e) {
- console.log('[Settings] 获取手机号回调:', e.detail)
-
- if (e.detail.errMsg !== 'getPhoneNumber:ok') {
- wx.showToast({ title: '授权失败', icon: 'none' })
- return
- }
-
- try {
- // 需要将code发送到服务器解密获取手机号
- const code = e.detail.code
- if (!code) {
- // 如果没有code,弹出手动输入
- this.bindPhone()
- return
- }
-
- wx.showLoading({ title: '获取中...', mask: true })
-
- // 调用服务器解密手机号(传入userId以便同步到数据库)
- const userId = app.globalData.userInfo?.id
- const res = await app.request('/api/miniprogram/phone', {
- method: 'POST',
- data: { code, userId }
- })
-
- wx.hideLoading()
-
- if (res.success && res.phoneNumber) {
- wx.setStorageSync('user_phone', res.phoneNumber)
- this.setData({ phoneNumber: res.phoneNumber })
-
- // 更新用户信息
- if (app.globalData.userInfo) {
- app.globalData.userInfo.phone = res.phoneNumber
- wx.setStorageSync('userInfo', app.globalData.userInfo)
- }
-
- // 同步到服务器
- this.syncProfileToServer()
-
- wx.showToast({ title: '手机号绑定成功', icon: 'success' })
- } else {
- // 获取失败,弹出手动输入
- this.bindPhone()
- }
- } catch (e) {
- wx.hideLoading()
- console.log('[Settings] 获取手机号失败:', e)
- // 获取失败,弹出手动输入
- this.bindPhone()
- }
- },
-
- // 关闭绑定弹窗
- closeBindModal() {
- this.setData({ showBindModal: false })
- },
-
- // 清除缓存
- clearCache() {
- wx.showModal({
- title: '清除缓存',
- content: '确定要清除本地缓存吗?',
- success: (res) => {
- if (res.confirm) {
- // 保留登录信息,只清除其他缓存
- const token = wx.getStorageSync('token')
- const userInfo = wx.getStorageSync('userInfo')
- wx.clearStorageSync()
- if (token) wx.setStorageSync('token', token)
- if (userInfo) wx.setStorageSync('userInfo', userInfo)
- wx.showToast({ title: '缓存已清除', icon: 'success' })
- }
- }
- })
- },
-
- // 退出登录
- handleLogout() {
- wx.showModal({
- title: '退出登录',
- content: '确定要退出登录吗?',
- success: (res) => {
- if (res.confirm) {
- app.logout()
- this.setData({
- isLoggedIn: false,
- userInfo: null,
- phoneNumber: '',
- wechatId: '',
- alipayAccount: ''
- })
- wx.showToast({ title: '已退出登录', icon: 'success' })
- setTimeout(() => wx.navigateBack(), 1500)
- }
- }
- })
- },
-
- // 联系客服 - 跳转到Soul派对房
- contactService() {
- wx.showToast({ title: '请在Soul派对房联系客服', icon: 'none' })
- },
-
- // 阻止冒泡
- stopPropagation() {},
-
- goBack() { wx.navigateBack() },
-
- // 跳转到地址管理页
- goToAddresses() {
- wx.navigateTo({ url: '/pages/addresses/addresses' })
- }
-})
diff --git a/归档/miniprogram/pages/settings/settings.json b/归档/miniprogram/pages/settings/settings.json
deleted file mode 100644
index e90e9960..00000000
--- a/归档/miniprogram/pages/settings/settings.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom"
-}
diff --git a/归档/miniprogram/pages/settings/settings.wxml b/归档/miniprogram/pages/settings/settings.wxml
deleted file mode 100644
index 2338a79b..00000000
--- a/归档/miniprogram/pages/settings/settings.wxml
+++ /dev/null
@@ -1,146 +0,0 @@
-
-
-
-
- ‹
-
- 设置
-
-
-
-
-
-
-
-
-
-
-
-
-
- 📱
-
- 手机号
- {{phoneNumber || '未绑定'}}
-
-
-
- ✓
-
-
-
-
-
-
-
- 💬
-
- 微信号
-
-
-
-
- ✓
-
-
-
-
-
-
- 📍
-
- 收货地址
- 管理收货地址,用于发货与邮寄
-
-
-
- 管理
-
-
-
-
-
-
-
-
-
-
-
- 开启自动提现
-
-
-
-
-
- 提现方式
- 微信零钱
-
-
- 提现账户
- {{wechatId}}
-
- 收益将在每笔订单完成后自动打款
-
-
-
-
-
-
- 提示:绑定微信号才能使用提现功能
-
-
- 退出登录
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{bindType === 'phone' ? '绑定手机号后可用于找伙伴匹配' : bindType === 'wechat' ? '绑定微信号后可用于找伙伴匹配和好友添加' : '绑定支付宝后可用于提现收益'}}
-
-
-
- 确认绑定
-
-
-
-
-
diff --git a/归档/miniprogram/pages/settings/settings.wxss b/归档/miniprogram/pages/settings/settings.wxss
deleted file mode 100644
index 5e7a4254..00000000
--- a/归档/miniprogram/pages/settings/settings.wxss
+++ /dev/null
@@ -1,114 +0,0 @@
-/* 设置页样式 */
-.page { min-height: 100vh; background: #000; padding-bottom: 64rpx; }
-
-/* 导航栏 */
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
-.nav-back { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
-.back-icon { font-size: 40rpx; color: rgba(255,255,255,0.6); font-weight: 300; }
-.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; }
-.nav-placeholder { width: 64rpx; }
-
-.content { padding: 24rpx; }
-
-/* 账号绑定卡片 */
-.bind-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; margin-bottom: 24rpx; border: 2rpx solid rgba(0,206,209,0.2); }
-.card-header { display: flex; align-items: flex-start; gap: 16rpx; margin-bottom: 24rpx; padding-bottom: 24rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.card-icon { font-size: 40rpx; }
-.card-title-wrap { flex: 1; }
-.card-title { font-size: 30rpx; font-weight: 600; color: #fff; display: block; }
-.card-desc { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
-
-.bind-list { display: flex; flex-direction: column; gap: 24rpx; }
-.bind-item { display: flex; align-items: center; justify-content: space-between; padding: 16rpx 0; }
-.bind-left { display: flex; align-items: center; gap: 20rpx; }
-.bind-icon { width: 72rpx; height: 72rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; }
-.bind-icon.phone-icon { background: rgba(0,206,209,0.2); }
-.bind-icon.wechat-icon { background: rgba(158,158,158,0.2); }
-.bind-icon.alipay-icon { background: rgba(158,158,158,0.2); }
-.bind-info { display: flex; flex-direction: column; gap: 4rpx; flex: 1; }
-.bind-label { font-size: 28rpx; color: #fff; font-weight: 500; }
-.bind-value { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-.address-text { max-width: 360rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.bind-icon.address-icon { background: rgba(255,165,0,0.2); }
-.required { color: #FF6B6B; font-size: 24rpx; }
-.bind-input { font-size: 24rpx; color: #00CED1; background: transparent; padding: 8rpx 0; }
-.bind-right { display: flex; align-items: center; }
-.bind-check { color: #00CED1; font-size: 32rpx; }
-.bind-btn { color: #00CED1; font-size: 26rpx; }
-.bind-manage { color: #00CED1; font-size: 26rpx; }
-.brand-color { color: #00CED1; }
-
-/* 一键获取手机号按钮 */
-.get-phone-btn {
- padding: 12rpx 24rpx;
- background: rgba(0,206,209,0.2);
- border: 2rpx solid rgba(0,206,209,0.3);
- border-radius: 16rpx;
- font-size: 24rpx;
- color: #00CED1;
- line-height: normal;
-}
-.get-phone-btn::after { border: none; }
-
-/* 自动提现卡片 */
-.auto-withdraw-card { margin-top: 24rpx; }
-.auto-withdraw-content { padding-top: 16rpx; }
-.withdraw-switch-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16rpx 0;
-}
-.switch-label { font-size: 28rpx; color: #fff; }
-.withdraw-info {
- background: rgba(0,206,209,0.1);
- border-radius: 16rpx;
- padding: 20rpx;
- margin-top: 16rpx;
-}
-.info-item {
- display: flex;
- justify-content: space-between;
- padding: 8rpx 0;
-}
-.info-label { font-size: 26rpx; color: rgba(255,255,255,0.6); }
-.info-value { font-size: 26rpx; color: #00CED1; }
-.withdraw-tip {
- display: block;
- font-size: 22rpx;
- color: rgba(255,255,255,0.4);
- margin-top: 12rpx;
- text-align: center;
-}
-
-/* 提现提示 */
-.tip-banner { background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 20rpx; padding: 20rpx 24rpx; margin-bottom: 24rpx; }
-.tip-text { font-size: 24rpx; color: #FFA500; line-height: 1.5; }
-
-/* 设置组 */
-.settings-group { background: #1c1c1e; border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; }
-.settings-item { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
-.settings-item:last-child { border-bottom: none; }
-.item-left { display: flex; align-items: center; gap: 16rpx; }
-.item-icon { font-size: 36rpx; }
-.item-title { font-size: 28rpx; color: #fff; }
-.item-arrow { font-size: 28rpx; color: rgba(255,255,255,0.3); }
-.item-value { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-
-/* 退出登录按钮 */
-.logout-btn { margin-top: 48rpx; padding: 28rpx; background: rgba(244,67,54,0.1); border: 2rpx solid rgba(244,67,54,0.3); border-radius: 24rpx; text-align: center; font-size: 28rpx; color: #F44336; }
-
-/* 弹窗 - 简洁大气风格 */
-.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); backdrop-filter: blur(20rpx); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 48rpx; }
-.modal-content { width: 100%; max-width: 640rpx; background: linear-gradient(180deg, #1c1c1e 0%, #0d0d0d 100%); border-radius: 40rpx; overflow: hidden; border: 2rpx solid rgba(255,255,255,0.08); }
-.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 40rpx 40rpx 24rpx; }
-.modal-title { font-size: 36rpx; font-weight: 700; color: #fff; }
-.modal-close { width: 64rpx; height: 64rpx; background: rgba(255,255,255,0.08); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: rgba(255,255,255,0.5); }
-.modal-body { padding: 16rpx 40rpx 48rpx; }
-.input-wrapper { margin-bottom: 32rpx; }
-.form-input { width: 100%; padding: 32rpx 24rpx; background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; font-size: 32rpx; color: #fff; box-sizing: border-box; transition: all 0.2s; }
-.form-input:focus { border-color: rgba(0,206,209,0.5); background: rgba(0,206,209,0.05); }
-.input-placeholder { color: rgba(255,255,255,0.25); }
-.bind-tip { font-size: 24rpx; color: rgba(255,255,255,0.4); margin-bottom: 40rpx; display: block; line-height: 1.6; text-align: center; }
-.btn-primary { padding: 32rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #000; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 28rpx; }
-.btn-primary.btn-disabled { background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.3); }
diff --git a/归档/miniprogram/pages/vip/vip.js b/归档/miniprogram/pages/vip/vip.js
deleted file mode 100644
index 1f9795b0..00000000
--- a/归档/miniprogram/pages/vip/vip.js
+++ /dev/null
@@ -1,132 +0,0 @@
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- isVip: false,
- daysRemaining: 0,
- expireDateStr: '',
- price: 1980,
- originalPrice: 6980,
- contentRights: [
- { title: '解锁全部章节', desc: '365天全部章节内容' },
- { title: '案例库', desc: '30-100个创业项目案例' },
- { title: '智能纪要', desc: '每天推送派对精华' },
- { title: '会议纪要库', desc: '之前所有场次的会议纪要' }
- ],
- socialRights: [
- { title: '匹配创业伙伴', desc: '匹配所有创业伙伴' },
- { title: '创业老板排行', desc: '排行榜展示您的项目' },
- { title: '链接资源', desc: '进群聊天、链接资源的权利' },
- { title: '专属VIP标识', desc: '头像金色VIP光圈' }
- ],
- 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({ url: `/api/vip/status?userId=${userId}`, silent: true })
- 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
- })
- if (d.isVip) this.loadProfile(userId)
- }
- } catch (e) { console.log('[VIP] 加载失败', e) }
- },
-
- 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() {
- let userId = app.globalData.userInfo?.id
- let openId = app.globalData.openId || app.globalData.userInfo?.open_id
- if (!userId || !openId) {
- wx.showLoading({ title: '登录中...', mask: true })
- try {
- await app.login()
- userId = app.globalData.userInfo?.id
- openId = app.globalData.openId || app.globalData.userInfo?.open_id
- wx.hideLoading()
- if (!userId || !openId) {
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- return
- }
- } catch (e) {
- wx.hideLoading()
- wx.showToast({ title: '登录失败', icon: 'none' })
- return
- }
- }
- this.setData({ purchasing: true })
- try {
- const payRes = await app.request('/api/miniprogram/pay', {
- method: 'POST',
- data: {
- openId,
- userId,
- productType: 'vip',
- productId: 'vip_annual',
- amount: this.data.price,
- description: '卡若创业派对VIP年度会员(365天)'
- }
- })
- if (payRes?.success && payRes.data?.payParams) {
- wx.requestPayment({
- ...payRes.data.payParams,
- success: () => {
- wx.showToast({ title: 'VIP开通成功', icon: 'success' })
- this.loadVipInfo()
- },
- fail: () => wx.showToast({ title: '支付取消', icon: 'none' })
- })
- } else {
- wx.showToast({ title: payRes?.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
deleted file mode 100644
index 52bdd937..00000000
--- a/归档/miniprogram/pages/vip/vip.json
+++ /dev/null
@@ -1 +0,0 @@
-{ "usingComponents": {}, "navigationStyle": "custom" }
diff --git a/归档/miniprogram/pages/vip/vip.wxml b/归档/miniprogram/pages/vip/vip.wxml
deleted file mode 100644
index c7ebfb09..00000000
--- a/归档/miniprogram/pages/vip/vip.wxml
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
- ‹
- 卡若创业派对
-
-
-
-
-
-
- 卡若创业派对
- 加入卡若的创业派对会员
- 有效期至 {{expireDateStr}}(剩余{{daysRemaining}}天)
- 专属会员尊享权益
-
-
-
-
- 内容权益
-
- ✓
-
- {{item.title}}
- {{item.desc}}
-
-
-
-
-
-
- 社交权益
-
- ✓
-
- {{item.title}}
- {{item.desc}}
-
-
-
-
-
-
-
- ¥{{originalPrice}}
- ¥{{price}}
- /年
-
-
- 加入卡若创业派对,获取创业资讯与优质人脉资源
-
-
-
-
- 会员资料(展示在创业老板排行)
-
- 姓名
-
-
-
- 项目名称
-
-
-
- 联系方式
-
-
-
- 一句话简介
-
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/vip/vip.wxss b/归档/miniprogram/pages/vip/vip.wxss
deleted file mode 100644
index 8bbf1960..00000000
--- a/归档/miniprogram/pages/vip/vip.wxss
+++ /dev/null
@@ -1,46 +0,0 @@
-.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; border-radius: 24rpx; background: linear-gradient(135deg, rgba(0,206,209,0.08), rgba(255,215,0,0.06)); border: 1rpx solid rgba(0,206,209,0.2); }
-.vip-hero-active { border-color: rgba(255,215,0,0.4); background: linear-gradient(135deg, rgba(255,215,0,0.15), rgba(0,206,209,0.08)); }
-.vip-hero-tag { display: inline-block; background: rgba(0,206,209,0.15); color: #00CED1; font-size: 22rpx; padding: 6rpx 16rpx; border-radius: 16rpx; margin-bottom: 20rpx; }
-.vip-hero-title { display: block; font-size: 44rpx; font-weight: bold; color: #fff; margin-top: 12rpx; }
-.gold { color: #FFD700; }
-.vip-hero-sub { display: block; font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 12rpx; }
-
-.rights-card { margin: 24rpx; }
-.rights-item { display: flex; align-items: flex-start; gap: 20rpx; padding: 24rpx; margin-bottom: 16rpx; background: rgba(255,255,255,0.04); border: 1rpx solid rgba(255,255,255,0.06); border-radius: 16rpx; }
-.rights-check-wrap { width: 44rpx; height: 44rpx; border-radius: 50%; background: rgba(0,206,209,0.15); display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 4rpx; }
-.rights-check { color: #00CED1; font-size: 24rpx; font-weight: bold; }
-.rights-info { display: flex; flex-direction: column; }
-.rights-title { font-size: 30rpx; font-weight: 600; color: rgba(255,255,255,0.95); }
-.rights-desc { font-size: 24rpx; color: rgba(255,255,255,0.45); margin-top: 6rpx; }
-
-.rights-section-title { display: block; font-size: 26rpx; color: #00CED1; font-weight: 600; margin-bottom: 16rpx; padding-bottom: 12rpx; border-bottom: 1rpx solid rgba(0,206,209,0.15); }
-
-.buy-area { margin: 24rpx; padding: 32rpx; text-align: center; background: rgba(255,255,255,0.03); border-radius: 20rpx; }
-.price-row { display: flex; align-items: baseline; justify-content: center; gap: 12rpx; margin-bottom: 24rpx; }
-.price-original { font-size: 28rpx; color: rgba(255,255,255,0.35); text-decoration: line-through; }
-.price-current { font-size: 64rpx; font-weight: bold; color: #FF4444; }
-.price-unit { font-size: 26rpx; color: rgba(255,255,255,0.5); }
-.buy-btn { width: 90%; height: 88rpx; line-height: 88rpx; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 32rpx; font-weight: bold; border-radius: 44rpx; border: none; margin: 0 auto; }
-.buy-btn[disabled] { opacity: 0.5; }
-.buy-sub { display: block; font-size: 22rpx; color: rgba(255,255,255,0.4); margin-top: 16rpx; }
-
-.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; }
-
-.author-section { margin: 32rpx 24rpx; padding: 24rpx; border-top: 1rpx solid rgba(255,255,255,0.08); }
-.author-row { display: flex; justify-content: space-between; padding: 8rpx 0; }
-.author-label { font-size: 24rpx; color: rgba(255,255,255,0.4); }
-.author-name { font-size: 24rpx; color: rgba(255,255,255,0.7); }
-
-.bottom-space { height: 120rpx; }
diff --git a/归档/miniprogram/pages/withdraw-records/withdraw-records.js b/归档/miniprogram/pages/withdraw-records/withdraw-records.js
deleted file mode 100644
index 5ef1c2fd..00000000
--- a/归档/miniprogram/pages/withdraw-records/withdraw-records.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * 提现记录 - 独立页面
- */
-const app = getApp()
-
-Page({
- data: {
- statusBarHeight: 44,
- list: [],
- loading: true
- },
-
- onLoad() {
- this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
- this.loadRecords()
- },
-
- onShow() {
- this.loadRecords()
- },
-
- async loadRecords() {
- const userInfo = app.globalData.userInfo
- if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) {
- this.setData({ list: [], loading: false })
- return
- }
- this.setData({ loading: true })
- try {
- const res = await app.request('/api/miniprogram/withdraw/records?userId=' + userInfo.id)
- if (res && res.success && res.data && Array.isArray(res.data.list)) {
- const list = (res.data.list || []).map(item => ({
- id: item.id,
- amount: (item.amount != null ? item.amount : 0).toFixed(2),
- status: this.statusText(item.status),
- statusRaw: item.status,
- createdAt: (item.createdAt ?? item.created_at) ? this.formatDate(item.createdAt ?? item.created_at) : '--',
- canReceive: !!item.canReceive
- }))
- this.setData({ list, loading: false })
- } else {
- this.setData({ list: [], loading: false })
- }
- } catch (e) {
- console.log('[WithdrawRecords] 加载失败:', e)
- this.setData({ list: [], loading: false })
- }
- },
-
- statusText(status) {
- const map = {
- pending: '待审核',
- pending_confirm: '待确认收款',
- processing: '处理中',
- success: '已到账',
- failed: '已拒绝'
- }
- return map[status] || status || '--'
- },
-
- formatDate(dateStr) {
- if (!dateStr) return '--'
- const d = new Date(dateStr)
- const y = d.getFullYear()
- const m = (d.getMonth() + 1).toString().padStart(2, '0')
- const day = d.getDate().toString().padStart(2, '0')
- return `${y}-${m}-${day}`
- },
-
- goBack() {
- wx.navigateBack()
- },
-
- async onReceiveTap(e) {
- const id = e.currentTarget.dataset.id
- if (!id) return
- wx.showLoading({ title: '加载中...' })
- try {
- const res = await app.request('/api/miniprogram/withdraw/confirm-info?id=' + encodeURIComponent(id))
- wx.hideLoading()
- if (!res || !res.success || !res.data) {
- wx.showToast({ title: res?.message || '获取领取信息失败', icon: 'none' })
- return
- }
- const { mchId, appId, package: pkg } = res.data
- if (!pkg || pkg === '') {
- wx.showToast({
- title: '打款已发起,请到微信零钱中查看',
- icon: 'none',
- duration: 2500
- })
- return
- }
- if (!wx.canIUse('requestMerchantTransfer')) {
- wx.showToast({ title: '当前微信版本过低,请更新后重试', icon: 'none' })
- return
- }
- wx.requestMerchantTransfer({
- mchId: mchId || '',
- appId: appId || wx.getAccountInfoSync().miniProgram.appId,
- package: pkg,
- success: (res) => {
- if (res.errMsg === 'requestMerchantTransfer:ok') {
- wx.showToast({ title: '已调起收款页', icon: 'success' })
- this.loadRecords()
- } else {
- wx.showToast({ title: res.errMsg || '操作完成', icon: 'none' })
- }
- },
- fail: (err) => {
- if (err.errMsg && err.errMsg.indexOf('cancel') !== -1) {
- wx.showToast({ title: '已取消', icon: 'none' })
- } else {
- wx.showToast({ title: err.errMsg || '调起失败', icon: 'none' })
- }
- }
- })
- } catch (e) {
- wx.hideLoading()
- wx.showToast({ title: '网络异常,请重试', icon: 'none' })
- }
- }
-})
diff --git a/归档/miniprogram/pages/withdraw-records/withdraw-records.json b/归档/miniprogram/pages/withdraw-records/withdraw-records.json
deleted file mode 100644
index e90e9960..00000000
--- a/归档/miniprogram/pages/withdraw-records/withdraw-records.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "usingComponents": {},
- "navigationStyle": "custom"
-}
diff --git a/归档/miniprogram/pages/withdraw-records/withdraw-records.wxml b/归档/miniprogram/pages/withdraw-records/withdraw-records.wxml
deleted file mode 100644
index def185dc..00000000
--- a/归档/miniprogram/pages/withdraw-records/withdraw-records.wxml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
- ←
- 提现记录
-
-
-
-
-
- 加载中...
- 暂无提现记录
-
-
-
- ¥{{item.amount}}
- {{item.createdAt}}
-
-
- {{item.status}}
-
-
-
-
-
-
diff --git a/归档/miniprogram/pages/withdraw-records/withdraw-records.wxss b/归档/miniprogram/pages/withdraw-records/withdraw-records.wxss
deleted file mode 100644
index 4de9e2bb..00000000
--- a/归档/miniprogram/pages/withdraw-records/withdraw-records.wxss
+++ /dev/null
@@ -1,71 +0,0 @@
-.page {
- min-height: 100vh;
- background: #000;
-}
-.nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: rgba(0,0,0,0.9);
- display: flex;
- align-items: center;
- padding: 0 24rpx;
- height: 88rpx;
-}
-.nav-back {
- color: #00CED1;
- font-size: 36rpx;
- padding: 16rpx;
-}
-.nav-title {
- flex: 1;
- text-align: center;
- font-size: 34rpx;
- font-weight: 600;
- color: #fff;
-}
-.nav-placeholder { width: 80rpx; }
-.nav-placeholder-bar { width: 100%; }
-
-.content {
- padding: 32rpx;
-}
-.loading-tip, .empty {
- text-align: center;
- color: rgba(255,255,255,0.6);
- font-size: 28rpx;
- padding: 80rpx 0;
-}
-.list { }
-.item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 28rpx 0;
- border-bottom: 2rpx solid rgba(255,255,255,0.06);
-}
-.item:last-child { border-bottom: none; }
-.item-left { display: flex; flex-direction: column; gap: 8rpx; }
-.item-right { display: flex; flex-direction: column; align-items: flex-end; gap: 12rpx; }
-.amount { font-size: 32rpx; font-weight: 600; color: #fff; }
-.time { font-size: 24rpx; color: rgba(255,255,255,0.5); }
-.status { font-size: 26rpx; }
-.status.status-pending { color: #FFA500; }
-.status.status-processing { color: #4CAF50; }
-.status.status-pending_confirm { color: #4CAF50; }
-.status.status-success { color: #4CAF50; }
-.status.status-failed { color: rgba(255,255,255,0.5); }
-.btn-receive {
- margin: 0;
- padding: 0 24rpx;
- height: 56rpx;
- line-height: 56rpx;
- font-size: 24rpx;
- color: #00CED1;
- background: transparent;
- border: 2rpx solid #00CED1;
- border-radius: 8rpx;
-}
-.btn-receive::after { border: none; }
diff --git a/归档/miniprogram/project.config.json b/归档/miniprogram/project.config.json
deleted file mode 100644
index b1752da7..00000000
--- a/归档/miniprogram/project.config.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
- "compileType": "miniprogram",
- "miniprogramRoot": "",
- "description": "Soul创业派对 - 来自派对房的真实商业故事",
- "appid": "wxb8bbb2b10dec74aa",
- "setting": {
- "urlCheck": false,
- "es6": true,
- "enhance": true,
- "postcss": true,
- "preloadBackgroundData": false,
- "minified": true,
- "newFeature": true,
- "coverView": true,
- "nodeModules": false,
- "autoAudits": false,
- "showShadowRootInWxmlPanel": true,
- "scopeDataCheck": false,
- "uglifyFileName": false,
- "checkInvalidKey": true,
- "checkSiteMap": true,
- "uploadWithSourceMap": true,
- "compileHotReLoad": true,
- "lazyloadPlaceholderEnable": false,
- "useMultiFrameRuntime": true,
- "babelSetting": {
- "ignore": [],
- "disablePlugins": [],
- "outputPath": ""
- },
- "enableEngineNative": false,
- "useIsolateContext": true,
- "userConfirmedBundleSwitch": false,
- "packNpmManually": false,
- "packNpmRelationList": [],
- "minifyWXSS": true,
- "disableUseStrict": false,
- "minifyWXML": true,
- "showES6CompileOption": false,
- "useCompilerPlugins": false,
- "ignoreUploadUnusedFiles": true,
- "compileWorklet": false,
- "localPlugins": false,
- "condition": false,
- "swc": false,
- "disableSWC": true
- },
- "condition": {},
- "editorSetting": {
- "tabIndent": "insertSpaces",
- "tabSize": 2
- },
- "simulatorPluginLibVersion": {},
- "packOptions": {
- "ignore": [],
- "include": []
- }
-}
\ No newline at end of file
diff --git a/归档/miniprogram/project.private.config.json b/归档/miniprogram/project.private.config.json
deleted file mode 100644
index 9df0293d..00000000
--- a/归档/miniprogram/project.private.config.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
- "projectname": "miniprogram",
- "setting": {
- "compileHotReLoad": true,
- "urlCheck": true,
- "coverView": true,
- "lazyloadPlaceholderEnable": false,
- "skylineRenderEnable": false,
- "preloadBackgroundData": false,
- "autoAudits": false,
- "useApiHook": true,
- "showShadowRootInWxmlPanel": true,
- "useStaticServer": false,
- "useLanDebug": false,
- "showES6CompileOption": false,
- "checkInvalidKey": true,
- "ignoreDevUnusedFiles": true,
- "bigPackageSizeSupport": false,
- "useIsolateContext": true
- },
- "libVersion": "3.13.2",
- "condition": {
- "miniprogram": {
- "list": [
- {
- "name": "pages/read/read",
- "pathName": "pages/read/read",
- "query": "id=1.1",
- "launchMode": "default",
- "scene": null
- },
- {
- "name": "pages/match/match",
- "pathName": "pages/match/match",
- "query": "",
- "launchMode": "default",
- "scene": null
- },
- {
- "name": "看书",
- "pathName": "pages/read/read",
- "query": "id=1.4",
- "launchMode": "default",
- "scene": null
- },
- {
- "name": "分销中心",
- "pathName": "pages/referral/referral",
- "query": "",
- "launchMode": "default",
- "scene": null
- },
- {
- "name": "阅读",
- "pathName": "pages/read/read",
- "query": "id=1.1",
- "launchMode": "default",
- "scene": null
- },
- {
- "name": "分销中心",
- "pathName": "pages/referral/referral",
- "query": "",
- "launchMode": "default",
- "scene": null
- },
- {
- "name": "我的",
- "pathName": "pages/my/my",
- "query": "",
- "launchMode": "default",
- "scene": null
- },
- {
- "name": "新增地址",
- "pathName": "pages/addresses/edit",
- "query": "",
- "launchMode": "default",
- "scene": null
- }
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/归档/miniprogram/sitemap.json b/归档/miniprogram/sitemap.json
deleted file mode 100644
index 55d1d29e..00000000
--- a/归档/miniprogram/sitemap.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
- "rules": [{
- "action": "allow",
- "page": "*"
- }]
-}
diff --git a/归档/miniprogram/utils/chapterAccessManager.js b/归档/miniprogram/utils/chapterAccessManager.js
deleted file mode 100644
index 8b66c587..00000000
--- a/归档/miniprogram/utils/chapterAccessManager.js
+++ /dev/null
@@ -1,201 +0,0 @@
-/**
- * 章节权限管理器
- * 统一管理章节权限判断、状态流转、异常处理
- */
-
-const app = getApp()
-
-class ChapterAccessManager {
- constructor() {
- this.accessStates = {
- UNKNOWN: 'unknown',
- FREE: 'free',
- LOCKED_NOT_LOGIN: 'locked_not_login',
- LOCKED_NOT_PURCHASED: 'locked_not_purchased',
- UNLOCKED_PURCHASED: 'unlocked_purchased',
- ERROR: 'error'
- }
- }
-
- /**
- * 拉取最新配置(免费章节列表、价格等)
- */
- async fetchLatestConfig() {
- try {
- const res = await app.request({ url: '/api/miniprogram/config', silent: true, timeout: 3000 })
- if (res.success && res.freeChapters) {
- return {
- freeChapters: res.freeChapters,
- prices: res.prices || { section: 1, fullbook: 9.9 }
- }
- }
- } catch (e) {
- console.warn('[AccessManager] 获取配置失败,使用默认配置:', e)
- }
-
- // 默认配置
- return {
- freeChapters: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'],
- prices: { section: 1, fullbook: 9.9 }
- }
- }
-
- /**
- * 判断章节是否免费
- */
- isFreeChapter(sectionId, freeList) {
- return freeList.includes(sectionId)
- }
-
- /**
- * 【核心方法】确定章节权限状态
- * @param {string} sectionId - 章节ID
- * @param {Array} freeList - 免费章节列表
- * @returns {Promise} accessState
- */
- async determineAccessState(sectionId, freeList) {
- try {
- // 1. 检查是否免费
- if (this.isFreeChapter(sectionId, freeList)) {
- console.log('[AccessManager] 免费章节:', sectionId)
- return this.accessStates.FREE
- }
-
- // 2. 检查是否登录
- const userId = app.globalData.userInfo?.id
- if (!userId) {
- console.log('[AccessManager] 未登录,需要登录:', sectionId)
- return this.accessStates.LOCKED_NOT_LOGIN
- }
-
- // 3. 请求服务端校验是否已购买(带重试)
- const res = await this.requestWithRetry(
- `/api/miniprogram/user/check-purchased?userId=${encodeURIComponent(userId)}&type=section&productId=${encodeURIComponent(sectionId)}`,
- { timeout: 5000 },
- 2 // 最多重试2次
- )
-
- if (res.success && res.data?.isPurchased) {
- console.log('[AccessManager] 已购买:', sectionId, res.data.reason)
-
- // 同步更新本地缓存(仅用于展示,不作权限依据)
- this.syncLocalCache(sectionId, res.data)
-
- return this.accessStates.UNLOCKED_PURCHASED
- }
-
- console.log('[AccessManager] 未购买:', sectionId)
- return this.accessStates.LOCKED_NOT_PURCHASED
-
- } catch (error) {
- console.error('[AccessManager] 权限判断失败:', error)
- // 网络/服务端错误 → 保守策略:返回错误状态
- return this.accessStates.ERROR
- }
- }
-
- /**
- * 带重试的请求
- */
- async requestWithRetry(url, options = {}, maxRetries = 3) {
- let lastError = null
-
- for (let i = 0; i < maxRetries; i++) {
- try {
- const res = await app.request(url, options)
- return res
- } catch (e) {
- lastError = e
- console.warn(`[AccessManager] 第 ${i+1} 次请求失败:`, url, e.message)
-
- // 如果不是最后一次,等待后重试(指数退避)
- if (i < maxRetries - 1) {
- await this.sleep(1000 * (i + 1))
- }
- }
- }
-
- throw lastError
- }
-
- /**
- * 同步更新本地购买缓存(仅用于展示,不作权限依据)
- */
- syncLocalCache(sectionId, purchaseData) {
- if (purchaseData.reason === 'has_full_book') {
- app.globalData.hasFullBook = true
- }
-
- if (!app.globalData.purchasedSections.includes(sectionId)) {
- app.globalData.purchasedSections = [...app.globalData.purchasedSections, sectionId]
- }
-
- // 更新 storage
- const userInfo = app.globalData.userInfo || {}
- userInfo.hasFullBook = app.globalData.hasFullBook
- userInfo.purchasedSections = app.globalData.purchasedSections
- wx.setStorageSync('userInfo', userInfo)
- }
-
- /**
- * 刷新用户购买状态(从 orders 表拉取最新)
- */
- async refreshUserPurchaseStatus() {
- const userId = app.globalData.userInfo?.id
- if (!userId) return
-
- try {
- const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(userId)}`)
-
- if (res.success && res.data) {
- app.globalData.hasFullBook = res.data.hasFullBook || false
- app.globalData.purchasedSections = res.data.purchasedSections || []
-
- const userInfo = app.globalData.userInfo || {}
- userInfo.hasFullBook = res.data.hasFullBook
- userInfo.purchasedSections = res.data.purchasedSections
- wx.setStorageSync('userInfo', userInfo)
-
- console.log('[AccessManager] 购买状态已刷新:', {
- hasFullBook: res.data.hasFullBook,
- purchasedCount: res.data.purchasedSections.length
- })
- }
- } catch (e) {
- console.error('[AccessManager] 刷新购买状态失败:', e)
- }
- }
-
- /**
- * 获取状态对应的用户提示文案
- */
- getStateMessage(accessState) {
- const messages = {
- [this.accessStates.UNKNOWN]: '加载中...',
- [this.accessStates.FREE]: '免费阅读',
- [this.accessStates.LOCKED_NOT_LOGIN]: '登录后继续阅读',
- [this.accessStates.LOCKED_NOT_PURCHASED]: '购买后继续阅读',
- [this.accessStates.UNLOCKED_PURCHASED]: '已解锁',
- [this.accessStates.ERROR]: '网络异常,请重试'
- }
- return messages[accessState] || '未知状态'
- }
-
- /**
- * 判断是否可访问全文
- */
- canAccessFullContent(accessState) {
- return [this.accessStates.FREE, this.accessStates.UNLOCKED_PURCHASED].includes(accessState)
- }
-
- /**
- * 工具:延迟
- */
- sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms))
- }
-}
-
-// 导出单例
-const accessManager = new ChapterAccessManager()
-export default accessManager
diff --git a/归档/miniprogram/utils/payment.js b/归档/miniprogram/utils/payment.js
deleted file mode 100644
index 9e048d1a..00000000
--- a/归档/miniprogram/utils/payment.js
+++ /dev/null
@@ -1,211 +0,0 @@
-// miniprogram/utils/payment.js
-// 微信支付工具类
-
-const app = getApp()
-
-/**
- * 发起微信支付
- * @param {Object} options - 支付选项
- * @param {String} options.orderId - 订单ID
- * @param {Number} options.amount - 支付金额(元)
- * @param {String} options.description - 商品描述
- * @param {Function} options.success - 成功回调
- * @param {Function} options.fail - 失败回调
- */
-function wxPay(options) {
- const { orderId, amount, description, success, fail } = options
-
- wx.showLoading({
- title: '正在支付...',
- mask: true
- })
-
- // 1. 调用后端创建支付订单
- wx.request({
- url: `${app.globalData.apiBase}/payment/create`,
- method: 'POST',
- header: {
- 'Authorization': `Bearer ${wx.getStorageSync('token')}`
- },
- data: {
- orderId,
- amount,
- description,
- paymentMethod: 'wechat'
- },
- success: (res) => {
- wx.hideLoading()
-
- if (res.statusCode === 200) {
- const paymentData = res.data
-
- // 2. 调起微信支付
- wx.requestPayment({
- timeStamp: paymentData.timeStamp,
- nonceStr: paymentData.nonceStr,
- package: paymentData.package,
- signType: paymentData.signType || 'RSA',
- paySign: paymentData.paySign,
- success: (payRes) => {
- console.log('支付成功', payRes)
-
- // 3. 通知后端支付成功
- notifyPaymentSuccess(orderId, paymentData.prepayId)
-
- wx.showToast({
- title: '支付成功',
- icon: 'success',
- duration: 2000
- })
-
- success && success(payRes)
- },
- fail: (payErr) => {
- console.error('支付失败', payErr)
-
- if (payErr.errMsg.indexOf('cancel') !== -1) {
- wx.showToast({
- title: '支付已取消',
- icon: 'none'
- })
- } else {
- wx.showToast({
- title: '支付失败',
- icon: 'none'
- })
- }
-
- fail && fail(payErr)
- }
- })
- } else {
- wx.showToast({
- title: res.data.message || '创建订单失败',
- icon: 'none'
- })
- fail && fail(res)
- }
- },
- fail: (err) => {
- wx.hideLoading()
- console.error('请求失败', err)
-
- wx.showToast({
- title: '网络请求失败',
- icon: 'none'
- })
-
- fail && fail(err)
- }
- })
-}
-
-/**
- * 通知后端支付成功
- * @param {String} orderId
- * @param {String} prepayId
- */
-function notifyPaymentSuccess(orderId, prepayId) {
- wx.request({
- url: `${app.globalData.apiBase}/payment/notify`,
- method: 'POST',
- header: {
- 'Authorization': `Bearer ${wx.getStorageSync('token')}`
- },
- data: {
- orderId,
- prepayId,
- status: 'success'
- },
- success: (res) => {
- console.log('支付通知成功', res)
- },
- fail: (err) => {
- console.error('支付通知失败', err)
- }
- })
-}
-
-/**
- * 查询订单状态
- * @param {String} orderId
- * @param {Function} callback
- */
-function queryOrderStatus(orderId, callback) {
- wx.request({
- url: `${app.globalData.apiBase}/payment/query`,
- method: 'GET',
- header: {
- 'Authorization': `Bearer ${wx.getStorageSync('token')}`
- },
- data: { orderId },
- success: (res) => {
- if (res.statusCode === 200) {
- callback && callback(true, res.data)
- } else {
- callback && callback(false, null)
- }
- },
- fail: () => {
- callback && callback(false, null)
- }
- })
-}
-
-/**
- * 购买完整电子书
- * @param {Function} success
- * @param {Function} fail
- */
-function purchaseFullBook(success, fail) {
- // 计算动态价格:9.9 + (天数 * 1元)
- const basePrice = 9.9
- const startDate = new Date('2025-01-01') // 书籍上架日期
- const today = new Date()
- const daysPassed = Math.floor((today - startDate) / (1000 * 60 * 60 * 24))
- const currentPrice = basePrice + daysPassed
-
- const orderId = `ORDER_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
-
- wxPay({
- orderId,
- amount: currentPrice,
- description: 'Soul派对·创业实验 完整版',
- success: (res) => {
- // 更新本地购买状态
- updatePurchaseStatus(true)
- success && success(res)
- },
- fail
- })
-}
-
-/**
- * 更新购买状态
- * @param {Boolean} isPurchased
- */
-function updatePurchaseStatus(isPurchased) {
- const userInfo = app.getUserInfo()
- if (userInfo) {
- userInfo.isPurchased = isPurchased
- wx.setStorageSync('userInfo', userInfo)
- app.globalData.userInfo = userInfo
- }
-}
-
-/**
- * 检查是否已购买
- * @returns {Boolean}
- */
-function checkPurchaseStatus() {
- const userInfo = app.getUserInfo()
- return userInfo ? userInfo.isPurchased : false
-}
-
-module.exports = {
- wxPay,
- queryOrderStatus,
- purchaseFullBook,
- checkPurchaseStatus,
- updatePurchaseStatus
-}
diff --git a/归档/miniprogram/utils/readingTracker.js b/归档/miniprogram/utils/readingTracker.js
deleted file mode 100644
index a52fb896..00000000
--- a/归档/miniprogram/utils/readingTracker.js
+++ /dev/null
@@ -1,246 +0,0 @@
-/**
- * 阅读进度追踪器
- * 记录阅读进度、时长、是否读完,支持断点续读
- */
-
-const app = getApp()
-
-class ReadingTracker {
- constructor() {
- this.activeTracker = null
- this.reportInterval = null
- }
-
- /**
- * 初始化阅读追踪
- */
- init(sectionId) {
- // 清理旧的追踪器
- this.cleanup()
-
- this.activeTracker = {
- sectionId,
- startTime: Date.now(),
- lastScrollTime: Date.now(),
- totalDuration: 0,
- maxProgress: 0,
- lastPosition: 0,
- isCompleted: false,
- completedAt: null,
- scrollTimer: null
- }
-
- console.log('[ReadingTracker] 初始化追踪:', sectionId)
-
- // 恢复上次阅读位置
- this.restoreLastPosition(sectionId)
-
- // 开始定期上报(每30秒)
- this.startProgressReport()
- }
-
- /**
- * 恢复上次阅读位置(断点续读)
- */
- restoreLastPosition(sectionId) {
- try {
- const progressData = wx.getStorageSync('reading_progress') || {}
- const lastProgress = progressData[sectionId]
-
- if (lastProgress && lastProgress.lastPosition > 100) {
- setTimeout(() => {
- wx.pageScrollTo({
- scrollTop: lastProgress.lastPosition,
- duration: 300
- })
-
- wx.showToast({
- title: `继续阅读 (${lastProgress.progress}%)`,
- icon: 'none',
- duration: 2000
- })
- }, 500)
- }
- } catch (e) {
- console.warn('[ReadingTracker] 恢复位置失败:', e)
- }
- }
-
- /**
- * 更新阅读进度(由页面滚动事件调用)
- */
- updateProgress(scrollInfo) {
- if (!this.activeTracker) return
-
- const { scrollTop, scrollHeight, clientHeight } = scrollInfo
- const totalScrollable = scrollHeight - clientHeight
-
- if (totalScrollable <= 0) return
-
- const progress = Math.min(100, Math.round((scrollTop / totalScrollable) * 100))
-
- // 更新最大进度
- if (progress > this.activeTracker.maxProgress) {
- this.activeTracker.maxProgress = progress
- this.activeTracker.lastPosition = scrollTop
- this.saveProgressLocal()
-
- console.log('[ReadingTracker] 进度更新:', progress + '%')
- }
-
- // 检查是否读完(≥90%)
- if (progress >= 90 && !this.activeTracker.isCompleted) {
- this.checkCompletion()
- }
- }
-
- /**
- * 检查是否读完(需要停留3秒)
- */
- async checkCompletion() {
- if (!this.activeTracker || this.activeTracker.isCompleted) return
-
- // 等待3秒,确认用户真的读到底部
- await this.sleep(3000)
-
- if (this.activeTracker && this.activeTracker.maxProgress >= 90 && !this.activeTracker.isCompleted) {
- this.activeTracker.isCompleted = true
- this.activeTracker.completedAt = Date.now()
-
- console.log('[ReadingTracker] 阅读完成:', this.activeTracker.sectionId)
-
- // 标记已读(app.js 里的已读章节列表)
- app.markSectionAsRead(this.activeTracker.sectionId)
-
- // 立即上报完成状态
- await this.reportProgressToServer(true)
-
- // 触发埋点
- this.trackEvent('chapter_completed', {
- sectionId: this.activeTracker.sectionId,
- duration: this.activeTracker.totalDuration
- })
-
- wx.showToast({
- title: '已完成阅读',
- icon: 'success',
- duration: 1500
- })
- }
- }
-
- /**
- * 保存进度到本地
- */
- saveProgressLocal() {
- if (!this.activeTracker) return
-
- try {
- const progressData = wx.getStorageSync('reading_progress') || {}
- progressData[this.activeTracker.sectionId] = {
- progress: this.activeTracker.maxProgress,
- lastPosition: this.activeTracker.lastPosition,
- lastOpenAt: Date.now()
- }
- wx.setStorageSync('reading_progress', progressData)
- } catch (e) {
- console.warn('[ReadingTracker] 保存本地进度失败:', e)
- }
- }
-
- /**
- * 开始定期上报
- */
- startProgressReport() {
- // 每30秒上报一次
- this.reportInterval = setInterval(() => {
- this.reportProgressToServer(false)
- }, 30000)
- }
-
- /**
- * 上报进度到服务端
- */
- async reportProgressToServer(isCompletion = false) {
- if (!this.activeTracker) return
-
- const userId = app.globalData.userInfo?.id
- if (!userId) return
-
- // 计算本次上报的时长
- const now = Date.now()
- const duration = Math.round((now - this.activeTracker.lastScrollTime) / 1000)
- this.activeTracker.totalDuration += duration
- this.activeTracker.lastScrollTime = now
-
- try {
- await app.request('/api/miniprogram/user/reading-progress', {
- method: 'POST',
- data: {
- userId,
- sectionId: this.activeTracker.sectionId,
- progress: this.activeTracker.maxProgress,
- duration: this.activeTracker.totalDuration,
- status: this.activeTracker.isCompleted ? 'completed' : 'reading',
- completedAt: this.activeTracker.completedAt
- }
- })
-
- if (isCompletion) {
- console.log('[ReadingTracker] 完成状态已上报')
- }
- } catch (e) {
- console.warn('[ReadingTracker] 上报进度失败,下次重试:', e)
- }
- }
-
- /**
- * 页面隐藏/卸载时调用(立即上报)
- */
- onPageHide() {
- if (this.activeTracker) {
- this.reportProgressToServer(false)
- }
- }
-
- /**
- * 清理追踪器
- */
- cleanup() {
- if (this.reportInterval) {
- clearInterval(this.reportInterval)
- this.reportInterval = null
- }
-
- if (this.activeTracker) {
- this.reportProgressToServer(false)
- this.activeTracker = null
- }
- }
-
- /**
- * 获取当前章节的阅读进度(用于展示)
- */
- getCurrentProgress() {
- return this.activeTracker ? this.activeTracker.maxProgress : 0
- }
-
- /**
- * 数据埋点(可对接统计平台)
- */
- trackEvent(eventName, eventData) {
- console.log('[Analytics]', eventName, eventData)
- // TODO: 接入微信小程序数据助手 / 第三方统计
- }
-
- /**
- * 工具:延迟
- */
- sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms))
- }
-}
-
-// 导出单例
-const readingTracker = new ReadingTracker()
-export default readingTracker
diff --git a/归档/miniprogram/utils/util.js b/归档/miniprogram/utils/util.js
deleted file mode 100644
index 855e96fd..00000000
--- a/归档/miniprogram/utils/util.js
+++ /dev/null
@@ -1,182 +0,0 @@
-/**
- * Soul创业实验 - 工具函数
- */
-
-// 格式化时间
-const formatTime = date => {
- const year = date.getFullYear()
- const month = date.getMonth() + 1
- const day = date.getDate()
- const hour = date.getHours()
- const minute = date.getMinutes()
- const second = date.getSeconds()
-
- return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
-}
-
-const formatNumber = n => {
- n = n.toString()
- return n[1] ? n : `0${n}`
-}
-
-// 格式化日期
-const formatDate = date => {
- const year = date.getFullYear()
- const month = date.getMonth() + 1
- const day = date.getDate()
- return `${year}-${formatNumber(month)}-${formatNumber(day)}`
-}
-
-// 格式化金额
-const formatMoney = (amount, decimals = 2) => {
- return Number(amount).toFixed(decimals)
-}
-
-// 防抖函数
-const debounce = (fn, delay = 300) => {
- let timer = null
- return function (...args) {
- if (timer) clearTimeout(timer)
- timer = setTimeout(() => {
- fn.apply(this, args)
- }, delay)
- }
-}
-
-// 节流函数
-const throttle = (fn, delay = 300) => {
- let last = 0
- return function (...args) {
- const now = Date.now()
- if (now - last >= delay) {
- fn.apply(this, args)
- last = now
- }
- }
-}
-
-// 生成唯一ID
-const generateId = () => {
- return 'id_' + Date.now().toString(36) + Math.random().toString(36).substr(2)
-}
-
-// 检查手机号格式
-const isValidPhone = phone => {
- return /^1[3-9]\d{9}$/.test(phone)
-}
-
-// 检查微信号格式
-const isValidWechat = wechat => {
- return wechat && wechat.length >= 6 && wechat.length <= 20
-}
-
-// 深拷贝
-const deepClone = obj => {
- if (obj === null || typeof obj !== 'object') return obj
- if (obj instanceof Date) return new Date(obj)
- if (obj instanceof Array) return obj.map(item => deepClone(item))
- if (obj instanceof Object) {
- const copy = {}
- Object.keys(obj).forEach(key => {
- copy[key] = deepClone(obj[key])
- })
- return copy
- }
-}
-
-// 获取URL参数
-const getQueryParams = url => {
- const params = {}
- const queryString = url.split('?')[1]
- if (queryString) {
- queryString.split('&').forEach(pair => {
- const [key, value] = pair.split('=')
- params[decodeURIComponent(key)] = decodeURIComponent(value || '')
- })
- }
- return params
-}
-
-// 存储操作
-const storage = {
- get(key) {
- try {
- return wx.getStorageSync(key)
- } catch (e) {
- console.error('获取存储失败:', e)
- return null
- }
- },
- set(key, value) {
- try {
- wx.setStorageSync(key, value)
- return true
- } catch (e) {
- console.error('设置存储失败:', e)
- return false
- }
- },
- remove(key) {
- try {
- wx.removeStorageSync(key)
- return true
- } catch (e) {
- console.error('删除存储失败:', e)
- return false
- }
- },
- clear() {
- try {
- wx.clearStorageSync()
- return true
- } catch (e) {
- console.error('清除存储失败:', e)
- return false
- }
- }
-}
-
-// 显示Toast
-const showToast = (title, icon = 'none', duration = 2000) => {
- wx.showToast({ title, icon, duration })
-}
-
-// 显示Loading
-const showLoading = (title = '加载中...') => {
- wx.showLoading({ title, mask: true })
-}
-
-// 隐藏Loading
-const hideLoading = () => {
- wx.hideLoading()
-}
-
-// 显示确认框
-const showConfirm = (title, content) => {
- return new Promise((resolve) => {
- wx.showModal({
- title,
- content,
- success: res => resolve(res.confirm)
- })
- })
-}
-
-module.exports = {
- formatTime,
- formatDate,
- formatMoney,
- formatNumber,
- debounce,
- throttle,
- generateId,
- isValidPhone,
- isValidWechat,
- deepClone,
- getQueryParams,
- storage,
- showToast,
- showLoading,
- hideLoading,
- showConfirm
-}