新增订单推荐人和邀请码功能,优化支付流程中的订单插入逻辑,确保订单记录准确。更新小程序支付请求,支持传递邀请码以便于分销归属和对账。同时,调整数据库结构以支持新字段,提升系统的稳定性和用户体验。
This commit is contained in:
@@ -1,260 +0,0 @@
|
||||
# 小程序功能同步更新日志
|
||||
|
||||
## [1.0.0] - 2026-02-04
|
||||
|
||||
### 🎉 重大更新
|
||||
|
||||
本次更新完成了 Next.js 功能到微信小程序的完整同步,实现了 **1:1 功能复刻**。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 新增功能
|
||||
|
||||
### 1. 目录页搜索按钮
|
||||
- 导航栏右上角添加搜索按钮
|
||||
- 点击跳转到搜索页
|
||||
- 样式与 Next.js 完全一致
|
||||
|
||||
**影响文件:**
|
||||
- `pages/chapters/chapters.wxml`
|
||||
- `pages/chapters/chapters.wxss`
|
||||
- `pages/chapters/chapters.js`
|
||||
|
||||
---
|
||||
|
||||
### 2. 我的页收益卡片艺术化
|
||||
- 深蓝渐变背景 (#1a1a2e → #16213e → #0f3460)
|
||||
- 背景装饰圆(金色右上、青色左下)
|
||||
- 收益金额使用金色渐变文字
|
||||
- 推广按钮使用金色渐变背景
|
||||
- 增强视觉冲击力
|
||||
|
||||
**影响文件:**
|
||||
- `pages/my/my.wxml`
|
||||
- `pages/my/my.wxss`
|
||||
|
||||
**视觉效果:**
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ 我的收益 推广中心 ↗│
|
||||
│ │
|
||||
│ 累计收益 可提现 │
|
||||
│ ¥8.91 ¥0.00 │
|
||||
│ │
|
||||
│ 🎁 推广中心 / 提现 │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 地址管理模块(完整新建)
|
||||
|
||||
#### 3.1 地址列表页
|
||||
- 地址卡片展示(姓名、手机、地址)
|
||||
- 默认地址标签
|
||||
- 编辑、删除操作
|
||||
- 新增地址按钮
|
||||
- 空状态提示
|
||||
|
||||
**新增文件:**
|
||||
- `pages/addresses/addresses.js`
|
||||
- `pages/addresses/addresses.wxml`
|
||||
- `pages/addresses/addresses.wxss`
|
||||
- `pages/addresses/addresses.json`
|
||||
|
||||
#### 3.2 地址编辑页
|
||||
- 表单输入(姓名、手机号、详细地址)
|
||||
- 省市区三级联动选择器
|
||||
- 设为默认地址开关
|
||||
- 表单验证
|
||||
- 保存功能
|
||||
|
||||
**新增文件:**
|
||||
- `pages/addresses/edit.js`
|
||||
- `pages/addresses/edit.wxml`
|
||||
- `pages/addresses/edit.wxss`
|
||||
- `pages/addresses/edit.json`
|
||||
|
||||
#### 3.3 设置页入口
|
||||
- 在设置页添加「收货地址」入口
|
||||
- 点击「管理」跳转到地址列表
|
||||
|
||||
**影响文件:**
|
||||
- `pages/settings/settings.wxml`
|
||||
- `pages/settings/settings.wxss`
|
||||
- `pages/settings/settings.js`
|
||||
|
||||
---
|
||||
|
||||
### 4. CSS 变量系统
|
||||
|
||||
在全局样式中添加 CSS 变量,便于统一管理:
|
||||
|
||||
```css
|
||||
page {
|
||||
/* 品牌色 */
|
||||
--app-brand: #00CED1;
|
||||
--app-brand-light: rgba(0, 206, 209, 0.1);
|
||||
|
||||
/* 背景色 */
|
||||
--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);
|
||||
|
||||
/* iOS 系统色 */
|
||||
--ios-indigo: #5856D6;
|
||||
--ios-green: #30d158;
|
||||
|
||||
/* 其他辅助色 */
|
||||
--gold: #FFD700;
|
||||
--pink: #E91E63;
|
||||
--purple: #7B61FF;
|
||||
}
|
||||
```
|
||||
|
||||
**影响文件:**
|
||||
- `app.wxss`
|
||||
|
||||
---
|
||||
|
||||
## 🔄 功能优化
|
||||
|
||||
### 1. 推广中心(已有功能验证)
|
||||
- ✅ 绑定用户列表 Tab切换
|
||||
- ✅ 过期提醒横幅
|
||||
- ✅ 用户状态标签
|
||||
- ✅ 分享按钮组
|
||||
- ✅ 提现功能
|
||||
|
||||
### 2. 搜索功能(已有功能验证)
|
||||
- ✅ 搜索输入框
|
||||
- ✅ 热门关键词
|
||||
- ✅ 热门章节推荐
|
||||
- ✅ 搜索结果列表
|
||||
|
||||
### 3. 海报生成(已有功能验证)
|
||||
- ✅ Canvas 绘制
|
||||
- ✅ 小程序码集成
|
||||
- ✅ 保存到相册
|
||||
|
||||
---
|
||||
|
||||
## 📐 样式统一
|
||||
|
||||
### 颜色规范
|
||||
- 背景: #000000 → 统一使用纯黑
|
||||
- 品牌色: #00CED1 → 所有页面统一
|
||||
- 卡片背景: #1c1c1e / #2c2c2e
|
||||
- 文字颜色: #ffffff / rgba(255,255,255,0.7) / rgba(255,255,255,0.4)
|
||||
|
||||
### 圆角规范
|
||||
- 小标签: 8rpx
|
||||
- 中等卡片: 24rpx
|
||||
- 大卡片: 32rpx
|
||||
- 圆形: 50%
|
||||
|
||||
### 间距规范
|
||||
- 页面边距: 32rpx
|
||||
- 卡片padding: 32rpx
|
||||
- 元素间距: 24rpx
|
||||
- 小间距: 16rpx
|
||||
|
||||
---
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
### 新增代码
|
||||
- 新增文件: 10个
|
||||
- 新增代码行数: ~1200行
|
||||
|
||||
### 修改代码
|
||||
- 修改文件: 7个
|
||||
- 修改代码行数: ~150行
|
||||
|
||||
### 文档
|
||||
- 新增文档: 4个
|
||||
- `功能同步完成报告.md`
|
||||
- `样式检查清单.md`
|
||||
- `交付清单.md`
|
||||
- `快速测试指南.md`
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
| 文档 | 位置 | 说明 |
|
||||
|-----|------|-----|
|
||||
| 转换提示词 | `转换提示词.md` | 原始需求和执行计划 |
|
||||
| 完成报告 | `miniprogram/功能同步完成报告.md` | 详细完成情况 |
|
||||
| 样式检查 | `miniprogram/样式检查清单.md` | 样式统一性检查 |
|
||||
| 交付清单 | `miniprogram/交付清单.md` | 交付成果清单 |
|
||||
| 测试指南 | `miniprogram/快速测试指南.md` | 测试指引 |
|
||||
| 开发约束 | `开发文档/0、Mycontent-book 项目总览.md` | 开发策略约束 |
|
||||
|
||||
---
|
||||
|
||||
## 🚨 重要说明
|
||||
|
||||
### 开发约束(2026-02-04 起生效)
|
||||
|
||||
```
|
||||
┌────────────────┬──────────┬─────────────────────────┐
|
||||
│ 微信小程序 │ ✅ 活跃 │ 所有C端新功能在此开发 │
|
||||
│ Next.js C端 │ 🔒 冻结 │ app/view/ 不再新增功能 │
|
||||
│ Next.js 管理端 │ ✅ 活跃 │ app/admin/ 继续开发 │
|
||||
│ API 接口 │ ✅ 活跃 │ 小程序和管理端共用 │
|
||||
└────────────────┴──────────┴─────────────────────────┘
|
||||
```
|
||||
|
||||
### 登录体系差异
|
||||
|
||||
- **小程序**: 保持微信一键登录(不复刻 Next.js 登录页)
|
||||
- **Next.js**: 保持手机号+密码登录
|
||||
- **统一**: 后端以手机号为唯一标识
|
||||
|
||||
---
|
||||
|
||||
## 📋 下一步计划
|
||||
|
||||
### 短期(本周)
|
||||
- [ ] 完成功能测试
|
||||
- [ ] 修复发现的 Bug
|
||||
- [ ] 优化用户体验细节
|
||||
|
||||
### 中期(本月)
|
||||
- [ ] 性能优化(首屏加载、图片懒加载)
|
||||
- [ ] 动画优化(更流畅的过渡)
|
||||
- [ ] 数据缓存策略
|
||||
|
||||
### 长期(持续)
|
||||
- [ ] 用户反馈收集
|
||||
- [ ] 功能迭代
|
||||
- [ ] 数据分析优化
|
||||
|
||||
---
|
||||
|
||||
## 👥 贡献者
|
||||
|
||||
- **执行**: AI Assistant
|
||||
- **审核**: 待定
|
||||
- **测试**: 待定
|
||||
|
||||
---
|
||||
|
||||
## 📜 版本历史
|
||||
|
||||
### v1.0.0 (2026-02-04)
|
||||
- ✅ 完成 Next.js 功能同步
|
||||
- ✅ 新增地址管理模块
|
||||
- ✅ 优化收益卡片设计
|
||||
- ✅ 添加搜索按钮
|
||||
- ✅ 统一全局样式
|
||||
|
||||
---
|
||||
|
||||
*本次更新标志着小程序功能达到与 Next.js Web端同等水平。*
|
||||
@@ -1,225 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Soul创业派对 - 小程序一键部署脚本
|
||||
功能:
|
||||
1. 打开微信开发者工具
|
||||
2. 自动编译小程序
|
||||
3. 上传到微信平台
|
||||
4. 显示审核指引
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# 修复Windows控制台编码问题
|
||||
if sys.platform == 'win32':
|
||||
import io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
|
||||
# 配置信息
|
||||
CONFIG = {
|
||||
'appid': 'wxb8bbb2b10dec74aa',
|
||||
'project_path': Path(__file__).parent / 'miniprogram',
|
||||
'version': '1.0.1',
|
||||
'desc': 'Soul创业派对 - 1:1完整还原Web功能'
|
||||
}
|
||||
|
||||
# 微信开发者工具可能的路径
|
||||
DEVTOOLS_PATHS = [
|
||||
r"D:\微信web开发者工具\微信开发者工具.exe",
|
||||
r"C:\Program Files (x86)\Tencent\微信web开发者工具\微信开发者工具.exe",
|
||||
r"C:\Program Files\Tencent\微信web开发者工具\微信开发者工具.exe",
|
||||
]
|
||||
|
||||
|
||||
def print_banner():
|
||||
"""打印横幅"""
|
||||
print("\n" + "=" * 70)
|
||||
print(" 🚀 Soul创业派对 - 小程序一键部署")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
|
||||
def find_devtools():
|
||||
"""查找微信开发者工具"""
|
||||
print("🔍 正在查找微信开发者工具...")
|
||||
|
||||
for devtools_path in DEVTOOLS_PATHS:
|
||||
if os.path.exists(devtools_path):
|
||||
print(f"✅ 找到微信开发者工具: {devtools_path}\n")
|
||||
return devtools_path
|
||||
|
||||
print("❌ 未找到微信开发者工具")
|
||||
print("\n请确保已安装微信开发者工具")
|
||||
print("下载地址: https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html\n")
|
||||
return None
|
||||
|
||||
|
||||
def open_devtools(devtools_path):
|
||||
"""打开微信开发者工具"""
|
||||
print("📱 正在打开微信开发者工具...")
|
||||
|
||||
try:
|
||||
# 使用项目路径打开开发者工具
|
||||
subprocess.Popen([devtools_path, str(CONFIG['project_path'])])
|
||||
print("✅ 微信开发者工具已打开\n")
|
||||
print("⏳ 等待开发者工具启动(10秒)...")
|
||||
time.sleep(10)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 打开失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def check_private_key():
|
||||
"""检查上传密钥"""
|
||||
key_path = CONFIG['project_path'] / 'private.key'
|
||||
|
||||
if not key_path.exists():
|
||||
print("\n" + "⚠" * 35)
|
||||
print("\n❌ 未找到上传密钥文件 private.key\n")
|
||||
print("📥 获取密钥步骤:")
|
||||
print(" 1. 访问 https://mp.weixin.qq.com/")
|
||||
print(" 2. 登录小程序后台")
|
||||
print(" 3. 开发管理 → 开发设置 → 小程序代码上传密钥")
|
||||
print(" 4. 点击「生成」,下载密钥文件")
|
||||
print(" 5. 将下载的 private.*.key 重命名为 private.key")
|
||||
print(f" 6. 放到目录: {CONFIG['project_path']}")
|
||||
print("\n💡 温馨提示:")
|
||||
print(" - 密钥只能生成一次,请妥善保管")
|
||||
print(" - 如需重新生成,需要到后台重置密钥")
|
||||
print("\n" + "⚠" * 35 + "\n")
|
||||
return False
|
||||
|
||||
print(f"✅ 找到密钥文件: private.key\n")
|
||||
return True
|
||||
|
||||
|
||||
def upload_miniprogram():
|
||||
"""上传小程序"""
|
||||
print("\n" + "-" * 70)
|
||||
print("📦 准备上传小程序到微信平台...")
|
||||
print("-" * 70 + "\n")
|
||||
|
||||
print(f"📂 项目路径: {CONFIG['project_path']}")
|
||||
print(f"🆔 AppID: {CONFIG['appid']}")
|
||||
print(f"📌 版本号: {CONFIG['version']}")
|
||||
print(f"📝 描述: {CONFIG['desc']}\n")
|
||||
|
||||
# 检查密钥
|
||||
if not check_private_key():
|
||||
return False
|
||||
|
||||
# 切换到miniprogram目录执行上传脚本
|
||||
upload_script = CONFIG['project_path'] / '上传小程序.py'
|
||||
|
||||
if not upload_script.exists():
|
||||
print(f"❌ 未找到上传脚本: {upload_script}")
|
||||
return False
|
||||
|
||||
print("⏳ 正在执行上传脚本...\n")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(upload_script)],
|
||||
cwd=CONFIG['project_path'],
|
||||
capture_output=False, # 直接显示输出
|
||||
text=True
|
||||
)
|
||||
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
print(f"❌ 上传出错: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def show_next_steps():
|
||||
"""显示后续步骤"""
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ 部署完成!")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
print("📱 后续操作:")
|
||||
print("\n1️⃣ 在微信开发者工具中:")
|
||||
print(" - 查看编译结果")
|
||||
print(" - 使用模拟器或真机预览测试")
|
||||
print(" - 确认所有功能正常")
|
||||
|
||||
print("\n2️⃣ 提交审核:")
|
||||
print(" - 访问 https://mp.weixin.qq.com/")
|
||||
print(" - 登录小程序后台")
|
||||
print(" - 版本管理 → 开发版本")
|
||||
print(" - 选择刚上传的版本 → 提交审核")
|
||||
|
||||
print("\n3️⃣ 审核材料准备:")
|
||||
print(" - 小程序演示视频(可选)")
|
||||
print(" - 测试账号(如有登录功能)")
|
||||
print(" - 功能说明(突出核心功能)")
|
||||
|
||||
print("\n4️⃣ 审核通过后:")
|
||||
print(" - 在后台点击「发布」")
|
||||
print(" - 用户即可在微信中搜索使用")
|
||||
|
||||
print("\n" + "=" * 70 + "\n")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print_banner()
|
||||
|
||||
# 1. 查找微信开发者工具
|
||||
devtools_path = find_devtools()
|
||||
if not devtools_path:
|
||||
print("💡 请先安装微信开发者工具,然后重新运行本脚本")
|
||||
return False
|
||||
|
||||
# 2. 打开微信开发者工具
|
||||
if not open_devtools(devtools_path):
|
||||
print("❌ 无法打开微信开发者工具")
|
||||
return False
|
||||
|
||||
print("\n✅ 微信开发者工具已打开,项目已加载")
|
||||
print("\n💡 现在你可以:")
|
||||
print(" 1. 在开发者工具中查看和测试小程序")
|
||||
print(" 2. 使用模拟器或扫码真机预览")
|
||||
print(" 3. 确认功能正常后,准备上传\n")
|
||||
|
||||
# 3. 询问是否立即上传
|
||||
print("-" * 70)
|
||||
user_input = input("\n是否立即上传到微信平台?(y/n,默认n): ").strip().lower()
|
||||
|
||||
if user_input == 'y':
|
||||
if upload_miniprogram():
|
||||
show_next_steps()
|
||||
return True
|
||||
else:
|
||||
print("\n❌ 上传失败")
|
||||
print("\n💡 你可以:")
|
||||
print(" 1. 检查 private.key 是否正确")
|
||||
print(" 2. 确保已开启开发者工具的「服务端口」")
|
||||
print(" 3. 或在开发者工具中手动点击「上传」按钮\n")
|
||||
return False
|
||||
else:
|
||||
print("\n✅ 开发者工具已就绪,你可以:")
|
||||
print(" 1. 在开发者工具中测试小程序")
|
||||
print(" 2. 准备好后,运行本脚本并选择上传")
|
||||
print(" 3. 或直接在开发者工具中点击「上传」按钮\n")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n⚠️ 用户取消操作")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 发生错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -5,7 +5,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev -p 30006",
|
||||
"dev": "next dev -p 3006",
|
||||
"lint": "eslint .",
|
||||
"start": "node scripts/start-standalone.js"
|
||||
},
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
# Web 转小程序 - 完整流程提示词
|
||||
|
||||
> **用法**:在对话中 @ 本文件(`scripts/Web转小程序并上传-提示词.md`),请 AI 按本提示词执行:**先枚举要转换的页面 → 按规则 100% 转为小程序 → 自检清单逐项通过 → 可选打开微信开发者工具**。
|
||||
|
||||
---
|
||||
|
||||
## 一、你的任务(当被 @ 本文件时)
|
||||
|
||||
按**顺序**执行:
|
||||
|
||||
1. **枚举页面**:扫描 `app/` 下所有 `**/page.tsx`(排除 `app/api/`、`app/admin/`),得到需上小程序的页面列表及对应小程序路径(见二)。
|
||||
2. **转换**:将上述每个 Web 页面**完整、一致**转为小程序代码到 `miniprogram/`,**样式、按钮、布局、交互、图标零丢失**(见三、四)。
|
||||
3. **检查**:按「六、自检清单」逐项自检,确保可运行且与 Web 逐页对照无遗漏。
|
||||
4. **打开微信开发者工具**(可选):若用户未说「仅转换」,则执行 `start miniprogram`(Windows)或 `open miniprogram`(Mac),或调用微信开发者工具 CLI 打开 `miniprogram/`。
|
||||
|
||||
**触发约定**:用户只说「转换」或「只转」→ 只做 1~3;用户说「完整流程」或仅 @ 本文件 → 做 1~4。
|
||||
|
||||
---
|
||||
|
||||
## 二、项目结构对照(按规则推导,不写死页面)
|
||||
|
||||
**先扫描 `app/` 得到页面列表,再按规则生成小程序路径与四件套;新增 Web 页面时同样适用。**
|
||||
|
||||
1. **Web 路由 → 小程序页面路径**
|
||||
- 通用:`app/<path>/page.tsx` → `pages/<name>/<name>`,`<name>` 取该路由**最后一层目录名**。
|
||||
- 特例:
|
||||
- `app/page.tsx` → `pages/index/index`。
|
||||
- 动态路由 `app/<a>/[id]/page.tsx` → `pages/<a>/<a>`,参数在 `onLoad(options)` 中取 `options.id` 等。
|
||||
- 嵌套路由(如 `app/my/referral/page.tsx`)→ 单层页面 `pages/referral/referral`,避免深层路径。
|
||||
- **新增**:每增加一个需上小程序的 `app/xxx/page.tsx`,就在 `miniprogram/pages/` 下新增 `pages/xxx/xxx` 四件套(.js/.json/.wxml/.wxss),并在 `app.json` 的 `pages` 中追加 `"pages/xxx/xxx"`。
|
||||
|
||||
2. **枚举要转换的页面**
|
||||
- 遍历 `app/` 下所有含 `page.tsx` 的路径,**排除**:`app/api/`、`app/admin/` 及仅 Web/后台用的路由。
|
||||
- 对每个需上小程序的页面,按上条得到路径,确保 `miniprogram/pages/<name>/` 存在四件套且已在 `app.json` 的 `pages` 中注册。
|
||||
|
||||
3. **API**
|
||||
- `app/api/*` 不转为小程序代码;小程序用 `wx.request` 调**同域名**接口(与 `miniprogram/utils`、baseURL 一致),**不写死 localhost**。接口路径、参数、返回格式与 `app/api/` 保持一致。
|
||||
|
||||
4. **tabBar**
|
||||
- 仅首页、目录、找伙伴、我的等需底部 tab 的页面配置 `app.json` 的 `tabBar.list`;其余为普通页面。新增 tab 时在 `tabBar.list` 与 `pages` 中同步追加。
|
||||
|
||||
---
|
||||
|
||||
## 三、转换规则(Web → 小程序)
|
||||
|
||||
**原则**:以 Web 为唯一真相来源,逐块对照,不猜测、不省略;无法 1:1 处用最接近实现并注释说明。
|
||||
|
||||
### 3.1 完整性要求(零丢失)
|
||||
|
||||
- **样式**:颜色、字体、字号、行高、间距、圆角、阴影、背景、边框与 Web 一致,在 WXSS 中完整实现。
|
||||
- **按钮与可点击**:每个按钮、链接、可点击区域保留,文案、图标、跳转/弹窗/提交与 Web 一致;禁用态、加载态需体现。
|
||||
- **布局与结构**:区块划分、顺序、折叠/展开、列表/卡片与 Web 一致,不漏模块。
|
||||
- **图片与图标**:Web 中出现的图片、图标、占位图在小程序侧存在并正确引用;**菜单、列表、统计、标签、按钮等处图标逐项对照补全**(可用 emoji 或图片),路径用 `miniprogram/images/` 或 assets。
|
||||
- **表单**:输入框、选择器、校验、提交与 Web 一致,不丢字段与校验。
|
||||
|
||||
### 3.2 组件与语法
|
||||
|
||||
- **React/JSX → WXML**:`wx:if`、`wx:for`、`bindtap` 等;**禁止在 WXML 中写 JS 方法**(见 4.1)。
|
||||
- **Tailwind/CSS → WXSS**:逐条对照 Web 样式,可保留 class 名,视觉效果一致;主题色/字体与 `globals.css` 或设计一致。
|
||||
- **状态与生命周期**:`useState`/`useEffect` → Page 的 `data`、`onLoad`、`onShow` 等。
|
||||
- **路由**:`useRouter`/`Link` → `wx.navigateTo`、`wx.switchTab`(tab 页用 switchTab)。
|
||||
- **接口**:`wx.request` + 项目 baseURL;路径、参数、返回与 `app/api/` 一致。图片放 `miniprogram/images/` 或 assets,引用用相对路径或 `/images/xxx`。
|
||||
|
||||
---
|
||||
|
||||
## 四、踩坑与必做项(必须遵守)
|
||||
|
||||
以下为实际转换中的踩坑总结,转换与检查时**必须**按此处理,否则会出现编译错误、模拟器启动失败或界面被遮挡。
|
||||
|
||||
### 4.1 WXML 禁止在模板里调用 JS 方法
|
||||
|
||||
- **禁止**:WXML 中不得出现任何 JS 方法调用,例如:`{{ (user.earnings || 0).toFixed(2) }}`、`{{ user.nickname.charAt(0) }}`、`{{ user.id.slice(-8) }}`、`{{ authorInfo.name.charAt(0) }}`,会报「unexpected token」等编译错误。
|
||||
- **必须**:在对应页的 `.js` 中(`onLoad`、`onShow`、`syncUser`、数据更新处)**预先计算**展示用字符串,写入 `data`,WXML 只引用 data 变量。推荐命名示例:
|
||||
- 金额两位小数 → `earningsText`、`balanceText` 等;
|
||||
- 用户/作者首字 → `userInitial`、`authorInitial`;
|
||||
- 用户 ID 后几位 → `userIdSuffix`。
|
||||
|
||||
### 4.2 启动不阻塞、不因网络报错导致模拟器启动失败
|
||||
|
||||
- **问题**:`App.onLaunch` 里若依赖异步请求(如 `loadFeatureConfig`、`loadBookData`)且未处理好,或对无返回值的函数链式调用 `.catch()`,会导致「模拟器启动失败 / TypeError: Failed to fetch」或「Cannot read property 'catch' of undefined」。
|
||||
- **做法**:
|
||||
- `onLaunch` 中**不要 await** 异步请求;只调用 `loadFeatureConfig().catch(() => {})`(仅对**返回 Promise** 的方法链式 catch),`loadBookData()` 内部已有 catch 则**不要**写 `loadBookData().catch()`(避免对 undefined 调 catch)。
|
||||
- `request` 的 `fail` 回调里统一打日志并 reject 友好错误,不把未捕获异常抛到启动流程。
|
||||
- 本地调试可在 `project.config.json` 的 `setting` 中设 `"urlCheck": false`,避免域名未配置时请求被拦截;正式发布前再按需改回。
|
||||
|
||||
### 4.3 底部「找伙伴」Tab 默认不显示,避免闪一下再隐藏
|
||||
|
||||
- **问题**:若 custom-tab-bar 里「找伙伴」初始为 `hidden: false`,等接口返回 `matchEnabled: false` 后再隐藏,会先显示再消失,观感差。
|
||||
- **做法**:custom-tab-bar 的 `list` 里「找伙伴」项**默认 `hidden: true`**;在 `attached` 里先执行一次 `syncMatchEnabled()`(用当前 `globalData.matchEnabled`),再 `app.loadFeatureConfig().then(() => this.syncMatchEnabled())`,仅当接口返回 `matchEnabled === true` 时把该项设为 `hidden: false`。
|
||||
|
||||
### 4.4 顶部安全区:状态栏 + 胶囊会遮挡,必须预留
|
||||
|
||||
- **问题**:`navigationStyle: "custom"` 时,**状态栏**和**胶囊按钮**会覆盖页面顶部,标题、返回、按钮被遮挡或点不到。
|
||||
- **必须**:
|
||||
- **占位高度**:统一使用 **`navBarHeight`**(不用固定 `statusBarHeight + 44`)。在 `App.getSystemInfo` 中用 `wx.getSystemInfoSync()` + `wx.getMenuButtonBoundingClientRect()` 计算 `navBarHeight`(状态栏 + 胶囊区域总高),无菜单按钮时回退 `statusBarHeight + 44`。每页占位条高度设为 `{{ navBarHeight }}px`,`onLoad`/`onShow` 从 `getApp().globalData` 取 `navBarHeight`、`statusBarHeight` 写入页面 `data`。
|
||||
- **头部右侧留白**:所有带标题/按钮的头部容器加 `.safe-header-right`(`app.wxss` 中定义 `padding-right: 200rpx; box-sizing: border-box;`),或使用 `globalData.capsulePaddingRight` 内联,避免被胶囊遮挡。
|
||||
- **占位页统一模板**:无复杂导航的页面(如 `address-edit`、`address-list`、`purchases`、`referral`、`settings`)必须使用**同一套**顶部安全区:顶部占位条高度 `navBarHeight`,其内 `padding-top: {{ statusBarHeight || 44 }}px`,导航容器使用 `display: flex; flex-direction: column; justify-content: flex-end; box-sizing: border-box;`,并加 `safe-header-right`,保证标题与返回按钮不被遮挡且右侧留白一致。
|
||||
|
||||
### 4.5 图标与样式逐页对照,不得遗漏
|
||||
|
||||
- **问题**:转换后容易漏掉搜索图标、菜单图标、统计/标签图标、返回箭头等,导致与 Web 不一致。
|
||||
- **做法**:逐页对照 Web(如 `components/bottom-nav.tsx`、各 `app/**/page.tsx`):
|
||||
- **底部 Tab**:每个 tab 有图标(如 🏠📋👥👤 或图片),「找伙伴」若居中凸起需保留样式。
|
||||
- **首页**:搜索栏左侧有搜索图标;Banner/卡片/列表中的箭头、标签与 Web 一致。
|
||||
- **我的**:用户卡片「创业伙伴」旁有星标;收益卡片有收益图标;菜单项(订单、推广、关于、设置)各有图标;概览/我的足迹 Tab 及阅读统计、最近阅读、匹配记录等区块有对应图标或标题图标。
|
||||
- **关于**:作者首字用 data 中的 `authorInitial`;标签(直播时间、平台)带图标;统计四项带图标;「加入派对群」按钮带图标;返回为「← 返回」。
|
||||
- **阅读/搜索/目录/找伙伴**:返回、分享、锁、类型图标等与 Web 一致。所有导航返回统一用「← 返回」等可识别样式。
|
||||
|
||||
### 4.6 卡片与按钮布局错位(如「我的收益」与「推广中心」按钮)
|
||||
|
||||
- **问题**:卡片内标题行(如「我的收益」+「推广中心 ›」)与底部全宽按钮(如「推广中心 / 提现」)出现错位、溢出或与卡片边缘不对齐,与 Web 不一致。
|
||||
- **做法**:
|
||||
- **盒子模型**:页面根容器与所有卡片统一加 `box-sizing: border-box`,避免 padding 导致总宽度超出或视觉偏移。
|
||||
- **卡片内标题行**:若为 flex 布局(如左侧标题 + 右侧链接),给容器加 `gap`、`min-width: 0`;左侧标题区加 `flex-shrink: 0`、`min-width: 0`,标题与链接加 `white-space: nowrap`,右侧链接加 `flex-shrink: 0`、`white-space: nowrap`,防止挤压、换行或重叠错位。
|
||||
- **全宽按钮**:卡片内的「全宽」按钮使用 `display: block`、`width: 100%`、`box-sizing: border-box`,保证与卡片内容区同宽、与上方内容左右对齐;不得因缺 box-sizing 或未 block 导致宽度计算错误而错位。
|
||||
|
||||
### 4.7 「我的」-「我的足迹」-「匹配记录」需随 matchEnabled 控制
|
||||
|
||||
- **要求**:与 Web 一致,当全局配置 `matchEnabled === false` 时,「我的」页「我的足迹」Tab 下的「匹配记录」区块**不展示**;仅当 `matchEnabled === true` 时展示。小程序侧从 `getApp().globalData.matchEnabled` 读取,在 WXML 中用 `wx:if="{{ matchEnabled }}"` 控制该区块显隐,并在 `onShow` 或数据刷新时同步该值到页面 `data`。
|
||||
|
||||
---
|
||||
|
||||
## 五、必须保留的小程序配置
|
||||
|
||||
- **AppID**:`wxb8bbb2b10dec74aa`(见 `miniprogram/project.config.json`、`.cursorrules`)。
|
||||
- **app.json**:`pages`、`window`、`tabBar`(含 `custom: true` 时保留 `custom-tab-bar`)、`permission`、`requiredPrivateInfos` 等按现有或微信规范保留。
|
||||
- **project.config.json**:保持现有编译与项目配置,不随意改 appid;本地调试可设 `urlCheck: false`,见 4.2。
|
||||
- **custom-tab-bar**:若使用自定义 tabBar,保留 `custom-tab-bar` 组件实现,并遵守 4.3 的「找伙伴」默认隐藏规则。
|
||||
|
||||
---
|
||||
|
||||
## 六、转换完成后的自检清单
|
||||
|
||||
- [ ] **页面注册**:`app.json` 的 `pages` 与 `miniprogram/pages/` 下目录、四件套一一对应,无缺页。
|
||||
- [ ] **组件引用**:各页 `.json` 的 `usingComponents` 与自定义组件路径正确(若有)。
|
||||
- [ ] **WXML 合规**:无语法错误;**WXML 中无 `.toFixed()`、`.charAt()`、`.slice()` 等 JS 方法**,展示用数值/字符串均已预先写入 data 并在模板中引用。
|
||||
- [ ] **接口**:baseURL 为线上或配置项,非 localhost(仅本地调试可例外)。
|
||||
- [ ] **tabBar**:与 Web 一级入口一致;**「找伙伴」项默认 `hidden: true`,仅当接口返回 `matchEnabled === true` 后显示**(见 4.3)。
|
||||
- [ ] **顶部安全区**:所有自定义头部页使用 `navBarHeight` 占位,头部容器加 `safe-header-right`;占位页(address-edit、address-list、purchases、referral、settings)使用统一顶部安全区模板(见 4.4)。
|
||||
- [ ] **卡片与按钮**:含标题行+链接+全宽按钮的卡片使用 `box-sizing: border-box`,标题行防挤压/换行,全宽按钮 `display: block`、`width: 100%`、`box-sizing: border-box`,与内容区左右对齐无错位(见 4.6)。
|
||||
- [ ] **「我的足迹」-「匹配记录」**:该区块随 `globalData.matchEnabled` 显隐,`matchEnabled === false` 时不展示(见 4.7)。
|
||||
- [ ] **完整性**:逐页对照 Web,样式、按钮、链接、图片、**图标**、表单、列表/卡片无遗漏;无法 1:1 处已用最接近实现并注释说明。
|
||||
|
||||
---
|
||||
|
||||
## 七、转换完成后的步骤
|
||||
|
||||
转换完成后,**打开微信开发者工具**:
|
||||
|
||||
- **方式一**:执行 `start miniprogram`(Windows)或 `open miniprogram`(Mac)打开文件夹,将 `miniprogram` 文件夹拖入微信开发者工具导入项目。
|
||||
- **方式二**:若已安装微信开发者工具 CLI,可直接调用其打开项目(如 Windows:`"C:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat" open --project miniprogram`)。
|
||||
|
||||
在微信开发者工具中可预览、调试;需要上传时,使用工具内的「上传」功能,或执行 `python scripts/autosysc-weixin.py`。
|
||||
|
||||
---
|
||||
|
||||
## 八、参考文件位置
|
||||
|
||||
- **Web 对照**:`app/**/page.tsx`、`components/`;接口路径、参数、返回格式参照 `app/api/`,保证与 Web 一致。
|
||||
- **小程序结构**:`miniprogram/app.json`、`miniprogram/pages/`、`miniprogram/utils/`、`miniprogram/custom-tab-bar/`、`miniprogram/app.js`。
|
||||
- **上传**:`scripts/autosysc-weixin.py`(项目根运行,需先配置 `miniprogram/private.key`)。
|
||||
- **配置说明**:`miniprogram/小程序快速配置指南.md`、`miniprogram/小程序部署说明.md`。
|
||||
|
||||
---
|
||||
|
||||
**当你被 @ 本文件时**:按「一、你的任务」顺序执行(枚举页面 → 转换 → 自检 → 可选打开开发者工具),并**严格遵循**二(结构对照)、三(转换规则)、四(踩坑必做项)、五(配置保留)、六(自检清单);四、六中的条目为必做项,不可省略。
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import paramiko
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('42.194.232.22', port=22022, username='root', password='Zhiqun1984', timeout=15)
|
||||
|
||||
print("=== 检查 chunks 目录文件(前20个)===")
|
||||
cmd = "ls -la /www/wwwroot/soul/.next/static/chunks/ 2>/dev/null | head -25"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "目录不存在")
|
||||
|
||||
print("\n=== 是否有 turbopack 文件 ===")
|
||||
cmd = "find /www/wwwroot/soul/.next/static -name '*turbopack*' 2>/dev/null"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "无 turbopack 文件(正常,这是生产模式)")
|
||||
|
||||
print("\n=== 检查请求的具体文件 ===")
|
||||
files_to_check = [
|
||||
"a954454d2ab1d3ca.css",
|
||||
"6a98f5c6b2554ef3.js",
|
||||
"turbopack-0d89ab930ad9d74d.js",
|
||||
]
|
||||
for f in files_to_check:
|
||||
cmd = "find /www/wwwroot/soul/.next/static -name '%s' 2>/dev/null" % f
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace').strip()
|
||||
status = "[OK] 存在" if result else "[X] 不存在"
|
||||
print("%s: %s" % (f, status))
|
||||
|
||||
print("\n=== 检查实际可用的 css 文件 ===")
|
||||
cmd = "ls /www/wwwroot/soul/.next/static/css/ 2>/dev/null | head -10"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "无 css 文件")
|
||||
|
||||
print("\n=== 构建模式检查 ===")
|
||||
cmd = "head -5 /www/wwwroot/soul/.next/BUILD_ID 2>/dev/null"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print("BUILD_ID: %s" % (result if result else "不存在"))
|
||||
|
||||
client.close()
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import paramiko
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('42.194.232.22', port=22022, username='root', password='Zhiqun1984', timeout=15)
|
||||
|
||||
print("=== soul.quwanzhi.com.conf ===")
|
||||
stdin, stdout, stderr = client.exec_command('cat /www/server/panel/vhost/nginx/soul.quwanzhi.com.conf 2>/dev/null', timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "文件不存在")
|
||||
|
||||
print("\n=== 检查 include 配置 ===")
|
||||
stdin, stdout, stderr = client.exec_command('ls -la /www/server/panel/vhost/nginx/ | grep soul', timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "无 soul 相关配置")
|
||||
|
||||
print("\n=== node_soul.conf ===")
|
||||
stdin, stdout, stderr = client.exec_command('cat /www/server/panel/vhost/nginx/node_soul.conf 2>/dev/null', timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "文件不存在")
|
||||
|
||||
client.close()
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import paramiko
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('42.194.232.22', port=22022, username='root', password='Zhiqun1984', timeout=15)
|
||||
|
||||
print("=== PM2 soul 日志(最后 50 行)===")
|
||||
cmd = "pm2 logs soul --lines 50 --nostream 2>&1 | tail -50"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
print("\n=== PM2 soul 错误日志 ===")
|
||||
cmd = "pm2 logs soul --err --lines 30 --nostream 2>&1 | tail -30"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
print("\n=== 检查 server.js 文件 ===")
|
||||
cmd = "ls -lh /www/wwwroot/soul/server.js"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
print("\n=== 检查 .next 目录结构 ===")
|
||||
cmd = "ls -lh /www/wwwroot/soul/.next/ | head -20"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
print("\n=== 检查端口 30006 ===")
|
||||
cmd = "curl -I http://127.0.0.1:30006 2>&1 | head -10"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
client.close()
|
||||
@@ -1,102 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
快速检查服务器上静态资源是否存在
|
||||
用于排查管理端 404 问题
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import paramiko
|
||||
except ImportError:
|
||||
print("请安装: pip install paramiko")
|
||||
sys.exit(1)
|
||||
|
||||
# 配置(与 devlop.py 一致)
|
||||
DEPLOY_PROJECT_PATH = os.environ.get("DEPLOY_PROJECT_PATH", "/www/wwwroot/soul")
|
||||
DEVLOP_DIST_PATH = "/www/wwwroot/auto-devlop/soul/dist"
|
||||
DEFAULT_SSH_PORT = int(os.environ.get("DEPLOY_SSH_PORT", "22022"))
|
||||
|
||||
def get_cfg():
|
||||
return {
|
||||
"host": os.environ.get("DEPLOY_HOST", "42.194.232.22"),
|
||||
"user": os.environ.get("DEPLOY_USER", "root"),
|
||||
"password": os.environ.get("DEPLOY_PASSWORD", "Zhiqun1984"),
|
||||
"ssh_key": os.environ.get("DEPLOY_SSH_KEY", ""),
|
||||
"project_path": os.environ.get("DEPLOY_PROJECT_PATH", DEPLOY_PROJECT_PATH),
|
||||
}
|
||||
|
||||
def check_static_files():
|
||||
cfg = get_cfg()
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
try:
|
||||
print("正在连接服务器...")
|
||||
if cfg.get("ssh_key") and os.path.isfile(cfg["ssh_key"]):
|
||||
client.connect(cfg["host"], port=DEFAULT_SSH_PORT, username=cfg["user"], key_filename=cfg["ssh_key"], timeout=15)
|
||||
else:
|
||||
client.connect(cfg["host"], port=DEFAULT_SSH_PORT, username=cfg["user"], password=cfg["password"], timeout=15)
|
||||
|
||||
print("\n=== 检查静态资源目录 ===")
|
||||
|
||||
# 检查多个可能的路径(deploy 模式和 devlop 模式)
|
||||
checks = [
|
||||
("%s/.next/static" % DEVLOP_DIST_PATH, "devlop 模式 dist 目录"),
|
||||
("%s/.next/static" % DEPLOY_PROJECT_PATH, "deploy 模式项目目录"),
|
||||
("%s/server.js" % DEVLOP_DIST_PATH, "devlop server.js"),
|
||||
("%s/server.js" % DEPLOY_PROJECT_PATH, "deploy server.js"),
|
||||
]
|
||||
|
||||
for path, desc in checks:
|
||||
# 检查文件或目录是否存在
|
||||
cmd = "test -e '%s' && echo 'EXISTS' || echo 'NOT_FOUND'" % path
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode("utf-8", errors="replace").strip()
|
||||
status = "[OK]" if "EXISTS" in result else "[X]"
|
||||
print("%s %s" % (status, desc))
|
||||
print(" 路径: %s" % path)
|
||||
|
||||
if "EXISTS" in result and "static" in path:
|
||||
# 列出文件数量
|
||||
cmd2 = "find '%s' -type f 2>/dev/null | wc -l" % path
|
||||
stdin2, stdout2, stderr2 = client.exec_command(cmd2, timeout=10)
|
||||
file_count = stdout2.read().decode("utf-8", errors="replace").strip()
|
||||
print(" 文件数: %s" % file_count)
|
||||
|
||||
print("\n=== 检查 PM2 项目配置 ===")
|
||||
cmd = "pm2 describe soul 2>/dev/null | grep -E 'cwd|script|status' | head -5 || echo 'PM2 soul 不存在'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
pm2_info = stdout.read().decode("utf-8", errors="replace").strip()
|
||||
print(pm2_info)
|
||||
|
||||
print("\n=== 检查端口监听 ===")
|
||||
cmd = "ss -tlnp | grep 30006 || echo '端口 30006 未监听'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
port_info = stdout.read().decode("utf-8", errors="replace").strip()
|
||||
print(port_info)
|
||||
|
||||
print("\n=== 检查 Nginx 反向代理 ===")
|
||||
cmd = "grep -r 'proxy_pass' /www/server/panel/vhost/nginx/*soul* 2>/dev/null | head -3 || echo '未找到 soul Nginx 配置'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
nginx_info = stdout.read().decode("utf-8", errors="replace").strip()
|
||||
print(nginx_info)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("诊断建议:")
|
||||
print("1. devlop 模式部署后,PM2 的 cwd 应为: %s" % DEVLOP_DIST_PATH)
|
||||
print("2. .next/static 必须在 PM2 的 cwd 目录下")
|
||||
print("3. Nginx 必须整站反代(location /),不能只反代 /api")
|
||||
print("4. 浏览器强刷: Ctrl+Shift+R 清除缓存")
|
||||
|
||||
except Exception as e:
|
||||
print("错误: %s" % str(e))
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_static_files()
|
||||
@@ -34,9 +34,9 @@ except ImportError:
|
||||
|
||||
# ==================== 配置 ====================
|
||||
|
||||
# 端口统一从环境变量 DEPLOY_PORT 读取,未设置时使用此默认值
|
||||
# 端口统一从环境变量 DEPLOY_PORT 读取,未设置时使用此默认值(需与 Nginx proxy_pass、ecosystem.config.cjs 一致)
|
||||
DEPLOY_PM2_APP = "soul"
|
||||
DEFAULT_DEPLOY_PORT = 3888
|
||||
DEFAULT_DEPLOY_PORT = 30006
|
||||
DEPLOY_PROJECT_PATH = "/www/wwwroot/soul"
|
||||
DEPLOY_SITE_URL = "https://soul.quwanzhi.com"
|
||||
# SSH 端口(支持环境变量 DEPLOY_SSH_PORT,未设置时默认为 22022)
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import paramiko
|
||||
import time
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('42.194.232.22', port=22022, username='root', password='Zhiqun1984', timeout=15)
|
||||
|
||||
print("=== 1. 检查端口占用 ===")
|
||||
cmd = "ss -tlnp | grep ':300' | head -10"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "无 300x 端口监听")
|
||||
|
||||
print("\n=== 2. 检查 server.js 中的端口配置 ===")
|
||||
cmd = "grep -n 'PORT\\|port\\|3006\\|30006' /www/wwwroot/soul/server.js | head -10"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
print("\n=== 3. 检查环境变量配置 ===")
|
||||
cmd = "cat /www/wwwroot/soul/.env 2>/dev/null | grep -i port || echo '无 .env 文件'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
print("\n=== 4. 停止 PM2 soul ===")
|
||||
cmd = "pm2 stop soul 2>&1"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
print("\n=== 5. 杀死占用 3006 端口的进程 ===")
|
||||
cmd = "lsof -ti:3006 | xargs kill -9 2>/dev/null || echo '无进程占用 3006'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
print("\n=== 6. 杀死占用 30006 端口的进程 ===")
|
||||
cmd = "lsof -ti:30006 | xargs kill -9 2>/dev/null || echo '无进程占用 30006'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
print("\n=== 7. 确认端口已释放 ===")
|
||||
cmd = "ss -tlnp | grep ':300'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "[OK] 端口已全部释放")
|
||||
|
||||
client.close()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("下一步:修复 server.js 的端口配置为 30006")
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import paramiko
|
||||
import time
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('42.194.232.22', port=22022, username='root', password='Zhiqun1984', timeout=15)
|
||||
|
||||
print("=== PM2 restart soul ===")
|
||||
stdin, stdout, stderr = client.exec_command('pm2 restart soul 2>&1', timeout=30)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
# 移除可能导致编码问题的特殊字符
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
print("\n=== 等待 3 秒 ===")
|
||||
time.sleep(3)
|
||||
|
||||
print("=== PM2 status ===")
|
||||
stdin, stdout, stderr = client.exec_command('pm2 status soul 2>&1', timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
client.close()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("请在浏览器按 Ctrl+Shift+R 强制刷新页面!")
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import paramiko
|
||||
import time
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect('42.194.232.22', port=22022, username='root', password='Zhiqun1984', timeout=15)
|
||||
|
||||
print("=== 1. 杀死所有相关进程 ===")
|
||||
cmd = "kill -9 1822 2>/dev/null || echo 'Process 1822 already killed'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
print("\n=== 2. 确认端口清理完成 ===")
|
||||
cmd = "ss -tlnp | grep ':300' || echo '[OK] All ports cleared'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
print("\n=== 3. 删除 PM2 soul 配置 ===")
|
||||
cmd = "pm2 delete soul 2>&1"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
print("\n=== 4. 使用正确配置重新启动 ===")
|
||||
cmd = """cd /www/wwwroot/soul && PORT=30006 pm2 start server.js --name soul --update-env 2>&1"""
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
print("\n=== 5. 检查 PM2 状态 ===")
|
||||
cmd = "pm2 status soul 2>&1"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
print("\n=== 6. 确认端口 30006 监听 ===")
|
||||
cmd = "ss -tlnp | grep ':30006'"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result if result else "[X] Port 30006 not listening!")
|
||||
|
||||
print("\n=== 7. 测试 HTTP 响应 ===")
|
||||
cmd = "curl -I http://127.0.0.1:30006 2>&1 | head -5"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
print(result)
|
||||
|
||||
print("\n=== 8. 查看最新日志 ===")
|
||||
cmd = "pm2 logs soul --lines 10 --nostream 2>&1 | tail -15"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
print("\n=== 9. 保存 PM2 配置 ===")
|
||||
cmd = "pm2 save 2>&1"
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=10)
|
||||
result = stdout.read().decode('utf-8', errors='replace')
|
||||
result = result.encode('ascii', errors='replace').decode('ascii')
|
||||
print(result)
|
||||
|
||||
client.close()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("完成!请在浏览器访问: https://soul.quwanzhi.com")
|
||||
print("如果仍是空白,按 Ctrl+Shift+R 强制刷新")
|
||||
@@ -1,240 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
订单状态同步任务(兜底机制)
|
||||
|
||||
功能:
|
||||
1. 定时查询 'created' 状态的订单
|
||||
2. 调用微信支付接口查询真实状态
|
||||
3. 同步订单状态(paid / expired)
|
||||
4. 更新用户购买记录
|
||||
|
||||
运行方式:
|
||||
- 手动: python scripts/sync_order_status.py
|
||||
- 定时: crontab -e 添加 "*/5 * * * * python /path/to/sync_order_status.py"
|
||||
- Node.js: 使用 node-cron 定时调用
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import hashlib
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
try:
|
||||
import pymysql
|
||||
import requests
|
||||
except ImportError:
|
||||
print("[ERROR] 缺少依赖库,请安装:")
|
||||
print(" pip install pymysql requests")
|
||||
sys.exit(1)
|
||||
|
||||
# 数据库配置
|
||||
DB = {
|
||||
"host": "56b4c23f6853c.gz.cdb.myqcloud.com",
|
||||
"port": 14413,
|
||||
"user": "cdb_outerroot",
|
||||
"password": "Zhiqun1984",
|
||||
"database": "soul_miniprogram",
|
||||
"charset": "utf8mb4",
|
||||
"cursorclass": pymysql.cursors.DictCursor,
|
||||
"connect_timeout": 15,
|
||||
}
|
||||
|
||||
# 微信支付配置(从环境变量或配置文件读取)
|
||||
WECHAT_PAY_CONFIG = {
|
||||
"appid": os.environ.get("WECHAT_APPID", "wxb8bbb2b10dec74aa"),
|
||||
"mch_id": os.environ.get("WECHAT_MCH_ID", "1318592501"),
|
||||
"api_key": os.environ.get("WECHAT_API_KEY", "YOUR_API_KEY_HERE"), # 需要配置真实的 API Key
|
||||
}
|
||||
|
||||
# 订单超时时间(分钟)
|
||||
ORDER_TIMEOUT_MINUTES = 30
|
||||
|
||||
def log(message, level="INFO"):
|
||||
"""统一日志输出"""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"[{timestamp}] [{level}] {message}")
|
||||
|
||||
def generate_nonce_str(length=32):
|
||||
"""生成随机字符串"""
|
||||
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
||||
|
||||
def create_sign(params, api_key):
|
||||
"""生成微信支付签名"""
|
||||
# 1. 参数排序
|
||||
sorted_params = sorted(params.items())
|
||||
|
||||
# 2. 拼接字符串
|
||||
string_a = '&'.join([f"{k}={v}" for k, v in sorted_params if v])
|
||||
string_sign_temp = f"{string_a}&key={api_key}"
|
||||
|
||||
# 3. MD5 加密并转大写
|
||||
sign = hashlib.md5(string_sign_temp.encode('utf-8')).hexdigest().upper()
|
||||
|
||||
return sign
|
||||
|
||||
def query_wechat_order_status(out_trade_no):
|
||||
"""
|
||||
查询微信支付订单状态
|
||||
文档: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2
|
||||
"""
|
||||
url = "https://api.mch.weixin.qq.com/pay/orderquery"
|
||||
|
||||
params = {
|
||||
"appid": WECHAT_PAY_CONFIG["appid"],
|
||||
"mch_id": WECHAT_PAY_CONFIG["mch_id"],
|
||||
"out_trade_no": out_trade_no,
|
||||
"nonce_str": generate_nonce_str(),
|
||||
}
|
||||
|
||||
# 生成签名
|
||||
params["sign"] = create_sign(params, WECHAT_PAY_CONFIG["api_key"])
|
||||
|
||||
# 构建 XML 请求体
|
||||
xml_data = "<xml>"
|
||||
for key, value in params.items():
|
||||
xml_data += f"<{key}>{value}</{key}>"
|
||||
xml_data += "</xml>"
|
||||
|
||||
try:
|
||||
response = requests.post(url, data=xml_data.encode('utf-8'), headers={'Content-Type': 'application/xml'}, timeout=10)
|
||||
|
||||
# 解析 XML 响应(简单处理,生产环境建议用 xml.etree.ElementTree)
|
||||
resp_text = response.text
|
||||
|
||||
# 提取关键字段
|
||||
if '<return_code><![CDATA[SUCCESS]]></return_code>' in resp_text:
|
||||
if '<trade_state><![CDATA[SUCCESS]]></trade_state>' in resp_text:
|
||||
return 'SUCCESS'
|
||||
elif '<trade_state><![CDATA[NOTPAY]]></trade_state>' in resp_text:
|
||||
return 'NOTPAY'
|
||||
elif '<trade_state><![CDATA[CLOSED]]></trade_state>' in resp_text:
|
||||
return 'CLOSED'
|
||||
elif '<trade_state><![CDATA[REFUND]]></trade_state>' in resp_text:
|
||||
return 'REFUND'
|
||||
else:
|
||||
return 'UNKNOWN'
|
||||
else:
|
||||
log(f"查询订单失败: {resp_text}", "WARN")
|
||||
return 'ERROR'
|
||||
|
||||
except Exception as e:
|
||||
log(f"查询微信订单异常: {e}", "ERROR")
|
||||
return 'ERROR'
|
||||
|
||||
def sync_order_status():
|
||||
"""同步订单状态(主函数)"""
|
||||
log("========== 订单状态同步任务开始 ==========")
|
||||
|
||||
conn = pymysql.connect(**DB)
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# 1. 查询所有 'created' 状态的订单(最近 2 小时内创建的)
|
||||
two_hours_ago = datetime.now() - timedelta(hours=2)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, order_sn, user_id, product_type, product_id, amount, created_at
|
||||
FROM orders
|
||||
WHERE status = 'created' AND created_at >= %s
|
||||
ORDER BY created_at DESC
|
||||
""", (two_hours_ago,))
|
||||
|
||||
pending_orders = cursor.fetchall()
|
||||
|
||||
if not pending_orders:
|
||||
log("没有需要同步的订单")
|
||||
return
|
||||
|
||||
log(f"找到 {len(pending_orders)} 个待同步订单")
|
||||
|
||||
synced_count = 0
|
||||
expired_count = 0
|
||||
|
||||
for order in pending_orders:
|
||||
order_sn = order['order_sn']
|
||||
created_at = order['created_at']
|
||||
|
||||
# 2. 判断订单是否超时(超过 30 分钟)
|
||||
time_diff = datetime.now() - created_at
|
||||
|
||||
if time_diff > timedelta(minutes=ORDER_TIMEOUT_MINUTES):
|
||||
# 超时订单:标记为 expired
|
||||
log(f"订单 {order_sn} 超时 ({time_diff.seconds // 60} 分钟),标记为 expired")
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE orders
|
||||
SET status = 'expired', updated_at = NOW()
|
||||
WHERE order_sn = %s
|
||||
""", (order_sn,))
|
||||
|
||||
expired_count += 1
|
||||
continue
|
||||
|
||||
# 3. 查询微信支付状态(跳过,因为需要真实 API Key)
|
||||
# 生产环境中取消下面的注释
|
||||
"""
|
||||
log(f"查询订单 {order_sn} 的微信支付状态...")
|
||||
|
||||
wechat_status = query_wechat_order_status(order_sn)
|
||||
|
||||
if wechat_status == 'SUCCESS':
|
||||
# 微信支付成功,更新本地订单为 paid
|
||||
log(f"订单 {order_sn} 微信支付成功,更新为 paid")
|
||||
|
||||
cursor.execute('''
|
||||
UPDATE orders
|
||||
SET status = 'paid', updated_at = NOW()
|
||||
WHERE order_sn = %s
|
||||
''', (order_sn,))
|
||||
|
||||
# 更新用户购买记录
|
||||
if order['product_type'] == 'fullbook':
|
||||
cursor.execute('''
|
||||
UPDATE users
|
||||
SET has_full_book = 1
|
||||
WHERE id = %s
|
||||
''', (order['user_id'],))
|
||||
|
||||
synced_count += 1
|
||||
|
||||
elif wechat_status == 'NOTPAY':
|
||||
log(f"订单 {order_sn} 尚未支付,保持 created 状态")
|
||||
|
||||
elif wechat_status == 'CLOSED':
|
||||
log(f"订单 {order_sn} 已关闭,标记为 cancelled")
|
||||
|
||||
cursor.execute('''
|
||||
UPDATE orders
|
||||
SET status = 'cancelled', updated_at = NOW()
|
||||
WHERE order_sn = %s
|
||||
''', (order_sn,))
|
||||
|
||||
else:
|
||||
log(f"订单 {order_sn} 查询失败或状态未知: {wechat_status}", "WARN")
|
||||
"""
|
||||
|
||||
# 测试环境:模拟查询(跳过微信接口)
|
||||
log(f"[TEST] 订单 {order_sn} 跳过微信查询(需配置 API Key)")
|
||||
|
||||
conn.commit()
|
||||
|
||||
log(f"同步完成: 同步 {synced_count} 个,超时 {expired_count} 个")
|
||||
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
log(f"同步失败: {e}", "ERROR")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
conn.close()
|
||||
log("========== 订单状态同步任务结束 ==========\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
sync_order_status()
|
||||
160
开发文档/8、部署/soul域名访问问题排查.md
Normal file
160
开发文档/8、部署/soul域名访问问题排查.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# soul.quwanzhi.com 更新后仍显示旧站/报错 — 原因分析与排查
|
||||
|
||||
> 现象:宝塔里站点源码已更新,但访问 https://soul.quwanzhi.com 仍显示旧站点或「哎呀, 出错了」;要求域名在 Node 界面访问且后台不出错。
|
||||
|
||||
---
|
||||
|
||||
## 一、可能原因概览
|
||||
|
||||
| 原因 | 说明 | 优先级 |
|
||||
|------|------|--------|
|
||||
| **1. 端口不一致** | 部署脚本或 PM2 使用端口 3888,Nginx 反代 30006 → 请求到不了 Node | 高 |
|
||||
| **2. 未重启 Node** | 代码已更新到服务器,但 PM2 未重启,进程仍在跑旧代码 | 高 |
|
||||
| **3. 运行目录与更新目录不一致** | 默认 `devlop` 模式跑的是 `auto-devlop/soul/dist`,你改的是 `/www/wwwroot/soul` | 高 |
|
||||
| **4. Nginx 未反代整站** | 只反代了 `/api`,`/_next/static` 等未到 Node → 静态 404 → 页面报错 | 中 |
|
||||
| **5. PM2 工作目录错误** | 项目路径填成 `.next/standalone` 或错误目录,缺少 `.next/static` → 404/报错 | 中 |
|
||||
| **6. 浏览器/CDN/Nginx 缓存** | 旧 HTML 引用已不存在的 chunk,或 CDN 缓存旧页面 | 中 |
|
||||
|
||||
下面按「原因 → 如何确认 → 怎么修」说明。
|
||||
|
||||
---
|
||||
|
||||
## 二、原因 1:端口不一致(脚本曾用 3888)
|
||||
|
||||
**说明**:`scripts/devlop.py` 此前默认端口为 **3888**,而 Nginx 与文档约定为 **30006**。若通过脚本创建/更新了宝塔 Node 项目,PM2 可能被配成 `PORT=3888`,应用只监听 3888;Nginx 把请求转到 30006,没有进程监听 → 502 或连接失败,页面上可能看到「出错了」或旧页。
|
||||
|
||||
**如何确认**(SSH 到服务器):
|
||||
|
||||
```bash
|
||||
# 应用应在 30006 监听
|
||||
ss -tlnp | grep 30006
|
||||
|
||||
# 若只有 3888 在监听,说明端口不一致
|
||||
ss -tlnp | grep 3888
|
||||
```
|
||||
|
||||
**修复**:
|
||||
|
||||
- 本项目已把 `scripts/devlop.py` 的默认端口改为 **30006**,与 Nginx、`ecosystem.config.cjs` 一致。
|
||||
- 若你本机已改过环境变量,部署时请统一使用 30006:
|
||||
- Windows: `set DEPLOY_PORT=30006`
|
||||
- 然后执行: `python scripts/devlop.py`(或 `--mode deploy`)
|
||||
- 宝塔 PM2 里为 soul 项目添加环境变量:`PORT=30006`,并重启。
|
||||
|
||||
---
|
||||
|
||||
## 三、原因 2:代码更新后未重启 Node
|
||||
|
||||
**说明**:Node 进程会把代码加载进内存。只更新服务器上的文件而不重启 PM2,运行的仍是旧版本,所以你会看到「旧站」或旧逻辑触发的错误。
|
||||
|
||||
**如何确认**:看文件修改时间 vs 进程启动时间(例如 `pm2 list`、`pm2 describe soul`)。
|
||||
|
||||
**修复**:
|
||||
|
||||
- 每次更新代码后必须重启 Node:
|
||||
- 宝塔:PM2 管理器 → 找到 soul → 重启;
|
||||
- 或 SSH:`pm2 restart soul`
|
||||
- 使用部署脚本时不要加 `--no-api`,这样上传后会自动调宝塔 API 重启。
|
||||
|
||||
---
|
||||
|
||||
## 四、原因 3:运行目录与更新目录不一致(devlop vs deploy)
|
||||
|
||||
**说明**:`python scripts/devlop.py` **默认是 devlop 模式**,部署目标是:
|
||||
|
||||
- 上传到:`/www/wwwroot/auto-devlop/soul/dist2`
|
||||
- 切换后实际运行目录:`/www/wwwroot/auto-devlop/soul/dist`
|
||||
- PM2 的「项目路径」应为上述 **dist**,而不是 `/www/wwwroot/soul`。
|
||||
|
||||
如果你在宝塔文件管理里看到并更新的是 **`/www/wwwroot/soul`**,而线上跑的是 **auto-devlop/soul/dist**,那么你改的目录根本不在跑,所以「源码更新了但访问还是旧站」。
|
||||
|
||||
**如何确认**:
|
||||
|
||||
- 宝塔 PM2 管理器:看 soul 项目的「项目路径」是 `.../soul` 还是 `.../auto-devlop/soul/dist`。
|
||||
- SSH:`pm2 describe soul` 看 `cwd`。
|
||||
|
||||
**修复(二选一)**:
|
||||
|
||||
- **要用 devlop 模式(dist 切换)**:以后只通过 `python scripts/devlop.py` 部署,不要手动改 `/www/wwwroot/soul`;确认 PM2 项目路径为 `.../auto-devlop/soul/dist`,端口 30006。
|
||||
- **要直接更新 /www/wwwroot/soul**:用 **deploy 模式** 部署到该目录,并让 PM2 从该目录启动:
|
||||
```bash
|
||||
python scripts/devlop.py --mode deploy
|
||||
```
|
||||
并确认宝塔里 soul 的项目路径为 `/www/wwwroot/soul`,环境变量 `PORT=30006`。
|
||||
|
||||
---
|
||||
|
||||
## 五、原因 4:Nginx 未把整站反代到 Node
|
||||
|
||||
**说明**:Next 的前台、后台、`/_next/static/*` 都由同一 Node 服务提供。若 Nginx 只配置了 `location /api` 反代到 30006,而 `location /` 指向静态目录或别的后端,则 HTML 可能来自旧静态或错误源,`/_next/static/...` 请求不到 Node → 404,页面报错「出错了」。
|
||||
|
||||
**如何确认**:看 Nginx 配置里是否有 `location /` 反代到 `http://127.0.0.1:30006`。
|
||||
|
||||
**修复**:保证整站走 Node,例如:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:30006;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
```
|
||||
|
||||
然后重载 Nginx。
|
||||
|
||||
---
|
||||
|
||||
## 六、原因 5:PM2 工作目录错误
|
||||
|
||||
**说明**:standalone 下 `server.js` 从**当前工作目录**读 `.next/static`、`public`。若 PM2 的「项目路径」填成 `.next/standalone` 或其它错误目录,会找不到静态资源 → 404 或运行时报错,页面上可能显示「出错了」。
|
||||
|
||||
**修复**:宝塔 PM2 里 soul 的项目路径必须是「解压后包含 `server.js`、`.next/static`、`public` 的那一层」:
|
||||
|
||||
- deploy 模式:`/www/wwwroot/soul`
|
||||
- devlop 模式:`/www/wwwroot/auto-devlop/soul/dist`
|
||||
|
||||
不要填成 `.../soul/.next/standalone`。
|
||||
|
||||
---
|
||||
|
||||
## 七、原因 6:缓存(浏览器 / CDN / Nginx)
|
||||
|
||||
**说明**:构建后 chunk 文件名会变,若浏览器或 CDN 仍用旧 HTML(引用旧 chunk),会 404 或报错。
|
||||
|
||||
**修复**:
|
||||
|
||||
- 浏览器:强刷 `Ctrl+Shift+R` / `Cmd+Shift+R`,或开发者工具 Network 勾选 Disable cache 再刷新。
|
||||
- 若用了 Nginx 的 `proxy_cache` 或 CDN:对 soul 站点 purge 缓存或暂时关缓存再试。
|
||||
|
||||
---
|
||||
|
||||
## 八、推荐自检顺序(保证域名走 Node 且后台不出错)
|
||||
|
||||
1. **端口**:服务器上 `ss -tlnp | grep 30006` 应有 Node 在监听;宝塔 soul 项目环境变量 `PORT=30006`。
|
||||
2. **运行目录**:PM2 的 soul 项目路径 = 你实际更新并希望生效的目录(deploy 用 `/www/wwwroot/soul`,devlop 用 `.../auto-devlop/soul/dist`)。
|
||||
3. **重启**:每次改代码或重新部署后执行一次 `pm2 restart soul`(或宝塔里重启)。
|
||||
4. **Nginx**:`location /` 反代到 `http://127.0.0.1:30006`,重载 Nginx。
|
||||
5. **静态资源**:`ls /www/wwwroot/soul/.next/static/chunks`(或当前运行目录下的 `.next/static`)应有文件;若 404 先看 [管理端静态资源404排查.md](./管理端静态资源404排查.md)。
|
||||
6. **缓存**:强刷、必要时清 CDN/Nginx 缓存。
|
||||
|
||||
使用项目提供的检查脚本(需能 SSH):
|
||||
|
||||
```bash
|
||||
python scripts/check_static_files.py
|
||||
python scripts/check_nginx.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、总结
|
||||
|
||||
- **「源码更新了但访问还是旧站/报错」** 最常见三种情况:**端口不一致**(Node 听 3888、Nginx 转 30006)、**更新了错误目录**(改的是 soul 目录但跑的是 auto-devlop/soul/dist)、**未重启 Node**。
|
||||
- 保证 **soul.quwanzhi.com 在 Node 界面访问且后台不出错**:端口 30006、Nginx 整站反代到 30006、PM2 工作目录正确且每次发布后重启、无静态 404(正确部署 `.next/static`)。
|
||||
|
||||
脚本默认端口已改为 30006;若你之前用默认 3888 部署过,请在宝塔里把 soul 的 `PORT` 改为 30006 并重启,再按上面顺序自检一遍。
|
||||
345
转换提示词.md
345
转换提示词.md
@@ -1,345 +0,0 @@
|
||||
# 微信小程序功能同步提示词
|
||||
|
||||
> **执行状态**: ✅ 所有任务已完成 (2026-02-04)
|
||||
> **完成报告**: 详见 `miniprogram/功能同步完成报告.md`
|
||||
> **样式检查**: 详见 `miniprogram/样式检查清单.md`
|
||||
|
||||
> **重要约束**:2026-02-04 起,所有 C 端新功能只在小程序开发,Next.js `app/view/` 冻结维护。
|
||||
> 详见 `开发文档/0、Mycontent-book 项目总览.md` 第5节。
|
||||
|
||||
---
|
||||
|
||||
## 📊 执行总结
|
||||
|
||||
**任务完成率**: 10/10 (100%)
|
||||
**新增文件**: 10个
|
||||
**修改文件**: 5个
|
||||
**代码质量**: ✅ 符合规范
|
||||
**样式一致性**: ✅ 1:1 复刻
|
||||
|
||||
## 一、功能对比分析报告
|
||||
|
||||
### 1. 开发策略说明
|
||||
|
||||
| 端 | 状态 | 说明 |
|
||||
|---|------|-----|
|
||||
| **微信小程序** | ✅ 活跃开发 | 所有 C 端新功能在此开发 |
|
||||
| **Next.js C端** | 🔒 冻结 | `app/view/` 不再新增功能,仅作参考 |
|
||||
| **Next.js 管理端** | ✅ 活跃 | `app/admin/` 继续开发 |
|
||||
|
||||
### 2. 登录体系差异(不需要同步)
|
||||
|
||||
| 端 | 登录方式 | 处理方式 |
|
||||
|---|---------|---------|
|
||||
| 小程序 | 微信一键登录 / 手机号快速授权 | **保持现状**,不复刻 Next.js 登录页 |
|
||||
| Next.js | 手机号 + 密码 | 保持现状 |
|
||||
| 账号统一 | 以手机号为唯一标识 | 后端处理,两端数据互通 |
|
||||
|
||||
### 3. 需要同步的功能(参考 Next.js 样式和交互)
|
||||
|
||||
| 页面/功能 | Next.js 参考路径 | 小程序状态 | 同步优先级 |
|
||||
|---------|------|---------|-----------|
|
||||
| 目录 | `/view/chapters/page.tsx` | ⚠️ 缺少搜索按钮 | P1 |
|
||||
| 我的 | `/view/my/page.tsx` | ⚠️ 缺少收益卡片艺术化设计 | P1 |
|
||||
| 推广中心 | `/view/my/referral/page.tsx` | ⚠️ 功能不完整 | P1 |
|
||||
| 设置 | `/view/my/settings/page.tsx` | ⚠️ 缺少收货地址入口 | P2 |
|
||||
| 地址管理 | `/view/my/addresses/` | ❌ 缺少整个模块 | P2 |
|
||||
| 首页 | `/view/page.tsx` | ✅ 基本完整 | P3 样式微调 |
|
||||
| 匹配 | `/view/match/page.tsx` | ✅ 基本完整 | P3 动画优化 |
|
||||
|
||||
### 4. 不需要同步的功能
|
||||
|
||||
| 功能 | 原因 |
|
||||
|-----|------|
|
||||
| 登录页 (`pages/login/login`) | 小程序使用微信一键登录,体验更好 |
|
||||
| 忘记密码页 (`pages/forgot/forgot`) | 小程序无密码登录,不需要 |
|
||||
| docs/documentation 页面 | Web 专属文档页,小程序不需要 |
|
||||
|
||||
### 5. 待完善的组件
|
||||
|
||||
- [ ] SearchModal - 搜索弹窗组件
|
||||
- [ ] PosterModal - 海报生成弹窗优化
|
||||
- [ ] WithdrawalModal - 提现弹窗
|
||||
- [ ] AutoWithdrawModal - 自动提现设置弹窗
|
||||
|
||||
---
|
||||
|
||||
## 二、功能同步转换提示词
|
||||
|
||||
### 使用说明
|
||||
将以下提示词复制给AI助手,逐步执行功能同步任务。
|
||||
|
||||
---
|
||||
|
||||
### 【提示词正文】
|
||||
|
||||
```
|
||||
你是一个资深的微信小程序开发专家,现在需要将 Next.js 项目的功能完整同步到微信小程序。
|
||||
|
||||
## 项目背景
|
||||
- Next.js 源码位置: `@Mycontent/app/view/`
|
||||
- 小程序源码位置: `@Mycontent/miniprogram/`
|
||||
- 技术栈: Next.js 14 + TypeScript → 微信小程序原生开发
|
||||
- 设计要求: **1:1 完全复刻**,样式、交互、功能不能有任何丢失
|
||||
|
||||
## 核心原则
|
||||
|
||||
### 1. 样式转换规则
|
||||
- Tailwind CSS → WXSS (需要逐一转换每个类名)
|
||||
- `className="text-white"` → `color: #ffffff;`
|
||||
- `className="bg-black"` → `background-color: #000000;`
|
||||
- `className="rounded-2xl"` → `border-radius: 32rpx;`
|
||||
- `className="px-4 py-3"` → `padding: 24rpx 32rpx;`
|
||||
- `px` 单位转 `rpx` (1px = 2rpx)
|
||||
- 渐变色、阴影、backdrop-blur 需要完整保留
|
||||
- CSS变量 `var(--app-brand)` 等需要在 app.wxss 定义
|
||||
|
||||
### 2. 组件转换规则
|
||||
- React组件 → 小程序Page或Component
|
||||
- `useState` → `this.data` + `this.setData()`
|
||||
- `useEffect` → `onLoad` / `onShow` / `onReady`
|
||||
- `useRouter` → `wx.navigateTo` / `wx.switchTab` / `wx.redirectTo`
|
||||
- `onClick` → `bindtap`
|
||||
- `onChange` → `bindinput` / `bindchange`
|
||||
- `framer-motion` 动画 → `wx.createAnimation()` 或 CSS animation
|
||||
|
||||
### 3. 图标转换规则
|
||||
- lucide-react 图标 → 使用相同名称的 iconfont 或 SVG
|
||||
- 保持图标大小、颜色完全一致
|
||||
- 可以使用小程序 image 组件 + base64 SVG
|
||||
|
||||
### 4. API 调用规则
|
||||
- `fetch` → `wx.request` 或 封装的 `app.request`
|
||||
- 保持接口路径一致
|
||||
- 错误处理逻辑一致
|
||||
|
||||
## 执行任务清单
|
||||
|
||||
> **执行状态**: ✅ 所有任务已完成 (2026-02-04)
|
||||
> **完成报告**: 详见 `miniprogram/功能同步完成报告.md`
|
||||
|
||||
### 第一阶段:完善现有页面功能(P1 优先级)
|
||||
|
||||
#### ✅ 任务1: 完善目录页
|
||||
对比 `@Mycontent/app/view/chapters/page.tsx` 和 `@Mycontent/miniprogram/pages/chapters/`
|
||||
- 添加右上角搜索按钮(点击跳转搜索页或弹出搜索组件)
|
||||
- 样式细节对齐(卡片圆角、间距、颜色)
|
||||
|
||||
> 注:版本标签切换(基础版/最新版)可选,根据业务需要决定是否添加
|
||||
|
||||
#### ✅ 任务2: 完善我的页面
|
||||
对比 `@Mycontent/app/view/my/page.tsx` 和 `@Mycontent/miniprogram/pages/my/`
|
||||
- 收益卡片艺术化设计(渐变背景、装饰元素、毛玻璃效果)
|
||||
- 设置按钮入口
|
||||
|
||||
#### ✅ 任务3: 完善推广中心
|
||||
对比 `@Mycontent/app/view/my/referral/page.tsx` 和 `@Mycontent/miniprogram/pages/referral/`
|
||||
需要添加:
|
||||
- 过期提醒横幅(有即将过期用户时显示)
|
||||
- 绑定用户列表 Tab切换 (绑定中/已付款/已过期)
|
||||
- 用户列表展示(头像、昵称、绑定时间、状态标签)
|
||||
- 分销规则说明卡片
|
||||
- 分享按钮组(海报/朋友圈/更多分享方式)
|
||||
- 收益明细列表
|
||||
|
||||
### 第二阶段:新建缺失页面(P2 优先级)
|
||||
|
||||
#### ✅ 任务4: 完善设置页
|
||||
对比 `@Mycontent/app/view/my/settings/page.tsx` 和 `@Mycontent/miniprogram/pages/settings/`
|
||||
- 添加收货地址管理入口
|
||||
- 绑定状态图标(已绑定显示勾选)
|
||||
- 账号绑定卡片样式优化
|
||||
|
||||
#### ✅ 任务5: 创建地址管理模块
|
||||
读取 `@Mycontent/app/view/my/addresses/` 目录下所有文件,创建:
|
||||
- `miniprogram/pages/addresses/addresses.js/wxml/wxss/json` (地址列表)
|
||||
- `miniprogram/pages/addresses/edit.js/wxml/wxss/json` (编辑地址)
|
||||
|
||||
要求:
|
||||
- 地址卡片样式参考 Next.js
|
||||
- 编辑、删除按钮交互一致
|
||||
- 使用小程序原生省市区选择器
|
||||
|
||||
### 第三阶段:组件优化(P3 优先级)
|
||||
|
||||
#### ✅ 任务6: 优化搜索功能
|
||||
参考 `@Mycontent/components/search-modal.tsx`
|
||||
- 可以使用独立搜索页 `pages/search/search`(已有)
|
||||
- 或创建 `miniprogram/components/search-modal/` 弹窗组件
|
||||
|
||||
#### ✅ 任务7: 优化海报生成功能
|
||||
参考 `@Mycontent/components/modules/referral/poster-modal.tsx`
|
||||
优化现有 `pages/read/read.js` 中的海报生成功能
|
||||
- 样式更精美
|
||||
- 支持保存到相册
|
||||
|
||||
#### ✅ 任务8: 创建提现弹窗组件
|
||||
参考 `@Mycontent/components/modules/referral/withdrawal-modal.tsx`
|
||||
在推广中心页面添加提现功能弹窗
|
||||
|
||||
### 第四阶段:样式统一
|
||||
|
||||
#### ✅ 任务9: 统一全局样式变量
|
||||
在 `miniprogram/app.wxss` 添加:
|
||||
```css
|
||||
page {
|
||||
--app-brand: #00CED1;
|
||||
--app-brand-light: rgba(0, 206, 209, 0.1);
|
||||
--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-indigo: #5856D6;
|
||||
--ios-green: #30d158;
|
||||
}
|
||||
```
|
||||
|
||||
#### ✅ 任务10: 逐页样式核对
|
||||
每个页面对比 Next.js 和小程序的样式:
|
||||
- 字体大小 (font-size)
|
||||
- 颜色值 (color, background)
|
||||
- 间距 (padding, margin)
|
||||
- 圆角 (border-radius)
|
||||
- 阴影 (box-shadow)
|
||||
- 渐变 (linear-gradient)
|
||||
|
||||
## ✅ 检查清单(已完成)
|
||||
|
||||
完成所有任务后的检查结果:
|
||||
|
||||
### 功能检查
|
||||
- [x] 目录页搜索入口可用 ✅
|
||||
- [x] 我的页收益卡片显示正常 ✅
|
||||
- [x] 推广中心绑定用户列表可用(3种状态Tab)✅
|
||||
- [x] 设置页收货地址入口可用 ✅
|
||||
- [x] 地址管理增删改查可用 ✅
|
||||
- [x] 所有弹窗组件正常显示和关闭 ✅
|
||||
- [x] 微信一键登录流程正常 ✅
|
||||
|
||||
### 样式检查
|
||||
- [x] 所有页面背景色一致 (黑色 #000000) ✅
|
||||
- [x] 品牌色一致 (#00CED1 青绿色) ✅
|
||||
- [x] 卡片样式一致 (圆角、背景、边框) ✅
|
||||
- [x] 按钮样式一致 (圆角、颜色、大小) ✅
|
||||
- [x] 图标大小和颜色一致 ✅
|
||||
- [x] 字体大小层级一致 ✅
|
||||
|
||||
### 交互检查
|
||||
- [x] 点击反馈一致 (active状态) ✅
|
||||
- [x] 加载状态一致 (loading动画) ✅
|
||||
- [x] 错误提示一致 (toast/modal) ✅
|
||||
- [x] 页面切换动画流畅 ✅
|
||||
|
||||
**检查结论**: ✅ 所有检查项通过,详见 `miniprogram/样式检查清单.md`
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **小程序优先原则** - 所有 C 端新功能只在小程序开发
|
||||
2. **保持登录体验** - 不要把 Next.js 的手机号密码登录复刻到小程序,保持微信一键登录
|
||||
3. **样式参考不照搬** - Next.js 样式作为参考,但要适配小程序特性
|
||||
4. **API路径保持一致** - 方便统一后端接口
|
||||
5. **测试每个功能** - 完成后逐一测试确保可用
|
||||
|
||||
## 执行顺序
|
||||
|
||||
按照优先级和任务编号顺序执行:
|
||||
1. **P1 任务 (1-3)**: 完善现有页面核心功能
|
||||
2. **P2 任务 (4-5)**: 新建缺失页面
|
||||
3. **P3 任务 (6-8)**: 组件优化
|
||||
4. **P4 任务 (9-10)**: 样式统一
|
||||
|
||||
每完成一个任务,立即测试验证,然后继续下一个。
|
||||
|
||||
现在开始执行任务1:完善目录页。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、样式对照表
|
||||
|
||||
### 常用 Tailwind → WXSS 转换
|
||||
|
||||
| Tailwind | WXSS |
|
||||
|----------|------|
|
||||
| `text-white` | `color: #ffffff;` |
|
||||
| `text-gray-500` | `color: rgba(255,255,255,0.5);` |
|
||||
| `text-[#00CED1]` | `color: #00CED1;` |
|
||||
| `bg-black` | `background-color: #000000;` |
|
||||
| `bg-[#1c1c1e]` | `background-color: #1c1c1e;` |
|
||||
| `rounded-xl` | `border-radius: 24rpx;` |
|
||||
| `rounded-2xl` | `border-radius: 32rpx;` |
|
||||
| `rounded-full` | `border-radius: 50%;` |
|
||||
| `px-4` | `padding-left: 32rpx; padding-right: 32rpx;` |
|
||||
| `py-3` | `padding-top: 24rpx; padding-bottom: 24rpx;` |
|
||||
| `gap-3` | `gap: 24rpx;` (需要flex) |
|
||||
| `flex` | `display: flex;` |
|
||||
| `flex-col` | `flex-direction: column;` |
|
||||
| `items-center` | `align-items: center;` |
|
||||
| `justify-between` | `justify-content: space-between;` |
|
||||
| `border border-white/5` | `border: 1rpx solid rgba(255,255,255,0.05);` |
|
||||
| `shadow-lg` | `box-shadow: 0 10rpx 15rpx -3rpx rgba(0,0,0,0.1);` |
|
||||
| `backdrop-blur-xl` | `backdrop-filter: blur(24px);` |
|
||||
| `transition-all` | `transition: all 0.3s ease;` |
|
||||
|
||||
### 渐变色转换示例
|
||||
|
||||
```css
|
||||
/* Tailwind */
|
||||
bg-gradient-to-br from-[#00CED1] to-[#20B2AA]
|
||||
|
||||
/* WXSS */
|
||||
background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、app.json 页面注册
|
||||
|
||||
完成新页面后,需要在 `app.json` 中注册:
|
||||
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/chapters/chapters",
|
||||
"pages/match/match",
|
||||
"pages/my/my",
|
||||
"pages/read/read",
|
||||
"pages/about/about",
|
||||
"pages/purchases/purchases",
|
||||
"pages/referral/referral",
|
||||
"pages/settings/settings",
|
||||
"pages/search/search",
|
||||
"pages/addresses/addresses",
|
||||
"pages/addresses/edit"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 注:不需要登录页(login)和忘记密码页(forgot),小程序使用微信一键登录。
|
||||
|
||||
---
|
||||
|
||||
## 五、执行建议
|
||||
|
||||
1. **遵循开发约束**: 所有 C 端新功能只在小程序开发,Next.js `app/view/` 已冻结
|
||||
2. **分阶段执行**: 按 P1→P2→P3→P4 优先级顺序执行
|
||||
3. **逐页对比**: 打开 Next.js 页面作为样式参考(但不照搬登录逻辑)
|
||||
4. **实时测试**: 使用微信开发者工具实时预览效果
|
||||
5. **保持原生体验**: 小程序登录保持微信一键登录,不要改成手机号密码
|
||||
|
||||
---
|
||||
|
||||
## 六、相关文档
|
||||
|
||||
- 开发约束详情: `开发文档/0、Mycontent-book 项目总览.md` 第5节
|
||||
- 小程序部署: `开发文档/8、部署/` 目录下相关文档
|
||||
- API 接口: `开发文档/5、接口/API接口.md`
|
||||
|
||||
---
|
||||
|
||||
*文档生成时间: 2026-02-04*
|
||||
*文档更新: 移除登录页/忘记密码页任务,添加开发约束说明*
|
||||
Reference in New Issue
Block a user