diff --git a/.env.port.example b/.env.port.example
index 3e5c61ba..8607bfda 100644
--- a/.env.port.example
+++ b/.env.port.example
@@ -7,13 +7,13 @@
# 方式1: 本地开发启动(pnpm start)
# 在终端中设置:
-# Windows PowerShell: $env:PORT=3006; pnpm start
-# Windows CMD: set PORT=3006 && pnpm start
-# Linux/Mac: PORT=3006 pnpm start
+# Windows PowerShell: $env:PORT=30006; pnpm start
+# Windows CMD: set PORT=30006 && pnpm start
+# Linux/Mac: PORT=30006 pnpm start
# 方式2: Docker Compose 部署
# 设置 APP_PORT 变量,容器内外端口都使用此值
-APP_PORT=3006
+APP_PORT=30006
# 方式3: Docker 直接运行
# docker run -e PORT=3007 -p 3007:3007 soul-book
@@ -21,7 +21,7 @@ APP_PORT=3006
# ========================================
# 多项目端口规划建议
# ========================================
-# soul-book: 3006
+# soul-book: 30006
# other-project: 3007
# api-service: 3008
# ...
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 72051b87..f56603ec 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -11,7 +11,7 @@
- ✅ 使用 `standalone` 模式,构建产物独立完整
- ✅ 使用 pnpm 包管理器
- ✅ 已配置 PM2 启动方式(`node server.js`)
-- ✅ 端口配置为 3006
+- ✅ 端口配置为 30006
## 🔧 配置步骤
diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md
index e5e55c6e..bb565ae3 100644
--- a/DEPLOYMENT.md
+++ b/DEPLOYMENT.md
@@ -83,8 +83,8 @@ python scripts/deploy_baota_pure_api.py --task-id 1 # 触发计划任务 ID=1
若服务器上尚未有代码,需先在宝塔上:
1. 在网站目录(如 `/www/wwwroot/soul`)创建目录,或从本地上传/克隆代码。
-2. 在宝塔「PM2 管理器」中新增项目:项目目录选该路径,启动文件为 `node server.js`,环境变量 `PORT=3006`。
-3. 配置 Nginx 反向代理到 `127.0.0.1:3006`,并绑定域名 soul.quwanzhi.com。
+2. 在宝塔「PM2 管理器」中新增项目:项目目录选该路径,启动文件为 `node server.js`,环境变量 `PORT=30006`。
+3. 配置 Nginx 反向代理到 `127.0.0.1:30006`,并绑定域名 soul.quwanzhi.com。
4. 之后日常部署执行 `python scripts/devlop.py` 即可。
---
diff --git a/Dockerfile b/Dockerfile
index 726d74fa..36d127c8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -56,7 +56,7 @@ USER nextjs
# 端口由环境变量指定,不设默认值避免冲突
# 部署时通过 docker run -e PORT=xxxx 或 docker-compose 设置
-EXPOSE ${PORT:-3006}
+EXPOSE ${PORT:-30006}
ENV HOSTNAME "0.0.0.0"
diff --git a/README-Phase4.md b/README-Phase4.md
new file mode 100644
index 00000000..84261286
--- /dev/null
+++ b/README-Phase4.md
@@ -0,0 +1,139 @@
+# Phase 4 完成总结
+
+## 概述
+
+Phase 4 成功迁移了"找伙伴"、"搜索"页面,实现了底部 TabBar 导航与安全区适配,完成了 Next.js C 端应用的**全量页面迁移**。
+
+---
+
+## 完成的核心功能
+
+### 1. 找伙伴页(AI 智能匹配)
+
+- **匹配类型**:创业合伙、资源对接、导师顾问、团队招募
+- **匹配逻辑**:
+ - 每日免费 1 次
+ - 购买章节获得更多次数
+ - 全书用户无限匹配
+- **匹配结果**:匹配度、创业理念、共同兴趣、微信号
+- **加入池功能**:提交手机号加入匹配池
+- **次数展示**:剩余次数、已用次数、解锁提示
+
+### 2. 搜索页(章节检索)
+
+- **实时搜索**:输入关键词实时过滤章节
+- **搜索结果**:显示所属篇章、标题、免费标签
+- **跳转阅读**:点击结果直接进入阅读页
+- **空态处理**:无搜索词、无结果的友好提示
+
+### 3. 底部 TabBar 导航
+
+- **4 个 Tab**:首页🏠、目录📚、找伙伴👥、我的👤
+- **激活态**:当前页 Tab 高亮显示(#00CED1)
+- **动态显示**:找伙伴 Tab 根据 `matchEnabled` 配置显示/隐藏
+- **安全区适配**:`paddingBottom = env(safe-area-inset-bottom)`
+- **跨端路由**:小程序用 `wx.switchTab`,Web 用 `window.location.href`
+
+### 4. 各页面集成底部导航
+
+在以下 4 个 Tab 页添加了 `` 组件:
+
+- HomePage (current="/")
+- ChaptersPage (current="/chapters")
+- MatchPage (current="/match")
+- MyPage (current="/my")
+
+---
+
+## 文件清单
+
+**页面组件**:
+- `src/pages/MatchPage.jsx`(找伙伴)
+- `src/pages/SearchPage.jsx`(搜索)
+
+**入口文件**:
+- `src/match.jsx`
+- `src/search.jsx`
+
+**公共组件**:
+- `src/components/BottomNav.jsx`(底部导航)
+
+**配置更新**:
+- `build/webpack.mp.config.js`(新增 match、search 入口)
+- `build/miniprogram.config.js`(router.other 新增 /match、/search)
+
+---
+
+## 完整页面映射表(Phase 1-4)
+
+| Next 路由 | 小程序页面 | 入口文件 | 状态 |
+|-----------|-----------|----------|------|
+| app/page.tsx | pages/index/index | src/index.jsx | ✅ Phase 2 |
+| app/chapters/page.tsx | pages/chapters/chapters | src/chapters.jsx | ✅ Phase 2 |
+| app/read/[id]/page.tsx | pages/read/read | src/read.jsx | ✅ Phase 2 |
+| app/my/page.tsx | pages/my/my | src/my.jsx | ✅ Phase 3 |
+| app/my/referral/page.tsx | pages/referral/referral | src/referral.jsx | ✅ Phase 3 |
+| app/my/settings/page.tsx | pages/settings/settings | src/settings.jsx | ✅ Phase 3 |
+| app/my/purchases/page.tsx | pages/purchases/purchases | src/purchases.jsx | ✅ Phase 3 |
+| app/about/page.tsx | pages/about/about | src/about.jsx | ✅ Phase 3 |
+| app/match/page.tsx | pages/match/match | src/match.jsx | ✅ Phase 4 |
+| app/search/page.tsx | pages/search/search | src/search.jsx | ✅ Phase 4 |
+
+**C 端页面 100% 迁移完成!**
+
+---
+
+## 安全区适配
+
+### 底部安全区
+```css
+paddingBottom: 'env(safe-area-inset-bottom)'
+```
+确保在有底部刘海的设备(如 iPhone X)上,TabBar 不被遮挡。
+
+### 顶部安全区
+小程序自动处理 statusBar,无需额外适配。若使用自定义导航栏,可读取 `app.globalData.navBarHeight`。
+
+### 右侧安全区(胶囊按钮)
+若使用自定义导航栏,需在右侧预留 `capsulePaddingRight`,避免遮挡胶囊按钮。
+
+---
+
+## 测试与验收
+
+1. **构建**:`cd newpp && npm run build:mp`
+2. **合并**:`node scripts/merge-kbone-to-miniprogram.js`
+3. **测试路径**:
+ - **TabBar 切换**:首页 ↔ 目录 ↔ 找伙伴 ↔ 我的
+ - **找伙伴流程**:选择类型 → 开始匹配 → 查看结果 → 复制微信 → 加入池
+ - **搜索流程**:首页 → 搜索 icon(或直接访问 /search)→ 输入关键词 → 点击结果 → 阅读
+ - **底部导航**:各 Tab 页底部导航高亮正确、点击切换正常
+
+---
+
+## 当前进度
+
+- ✅ **Phase 1**:搭架子(适配层、构建、首页/目录/阅读占位)
+- ✅ **Phase 2**:核心页(阅读页接口、ChapterContent、完整目录)
+- ✅ **Phase 3**:我的与子页(Zustand、我的、推广、设置、购买记录、关于)
+- ✅ **Phase 4**:找伙伴与其余(match、search、底部 tabBar、安全区)
+- ⏳ **Phase 5**:收尾(全量自检、样式对齐、发布流程)
+
+---
+
+## 下一步
+
+进入 Phase 5 收尾阶段:
+
+1. **全量自检**:对照「Web转小程序并上传-提示词」逐项检查
+2. **样式对齐**:确保颜色、间距、圆角、阴影与 Web 一致
+3. **踩坑修复**:
+ - WXML 不能调用 JS 方法
+ - 启动不阻塞(async onLaunch)
+ - safe-area 边界处理
+ - TabBar 默认隐藏项逻辑
+4. **发布流程**:预览码 → 体验版 → 提审 → 发布
+
+---
+
+**Phase 1-4 已完成 C 端全量迁移,所有核心功能已就位!**
diff --git a/docker-compose.yml b/docker-compose.yml
index 7a8efb56..a2e47325 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,32 +8,32 @@ services:
container_name: soul_book_app
restart: always
ports:
- - "${APP_PORT:-3006}:${APP_PORT:-3006}"
+ - "${APP_PORT:-30006}:${APP_PORT:-30006}"
environment:
- NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1
- - PORT=${APP_PORT:-3006}
+ - PORT=${APP_PORT:-30006}
# 支付宝配置
- ALIPAY_PARTNER_ID=${ALIPAY_PARTNER_ID:-2088511801157159}
- ALIPAY_KEY=${ALIPAY_KEY:-lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp}
- ALIPAY_APP_ID=${ALIPAY_APP_ID:-wx432c93e275548671}
- - ALIPAY_RETURN_URL=${ALIPAY_RETURN_URL:-http://192.168.2.201:${APP_PORT:-3006}/payment/success}
- - ALIPAY_NOTIFY_URL=${ALIPAY_NOTIFY_URL:-http://192.168.2.201:${APP_PORT:-3006}/api/payment/alipay/notify}
+ - ALIPAY_RETURN_URL=${ALIPAY_RETURN_URL:-http://192.168.2.201:${APP_PORT:-30006}/payment/success}
+ - ALIPAY_NOTIFY_URL=${ALIPAY_NOTIFY_URL:-http://192.168.2.201:${APP_PORT:-30006}/api/payment/alipay/notify}
# 微信支付配置
- WECHAT_APP_ID=${WECHAT_APP_ID:-wx432c93e275548671}
- WECHAT_APP_SECRET=${WECHAT_APP_SECRET:-25b7e7fdb7998e5107e242ebb6ddabd0}
- WECHAT_MCH_ID=${WECHAT_MCH_ID:-1318592501}
- WECHAT_API_KEY=${WECHAT_API_KEY:-wx3e31b068be59ddc131b068be59ddc2}
- - WECHAT_NOTIFY_URL=${WECHAT_NOTIFY_URL:-http://192.168.2.201:${APP_PORT:-3006}/api/payment/wechat/notify}
+ - WECHAT_NOTIFY_URL=${WECHAT_NOTIFY_URL:-http://192.168.2.201:${APP_PORT:-30006}/api/payment/wechat/notify}
# 基础配置
- - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:-http://192.168.2.201:${APP_PORT:-3006}}
+ - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:-http://192.168.2.201:${APP_PORT:-30006}}
volumes:
- ./book:/app/book:ro
- ./public:/app/public:ro
networks:
- nas-network
healthcheck:
- test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:${APP_PORT:-3006}"]
+ test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:${APP_PORT:-30006}"]
interval: 30s
timeout: 10s
retries: 3
diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs
index 267254b3..2c8e2af5 100644
--- a/ecosystem.config.cjs
+++ b/ecosystem.config.cjs
@@ -1,7 +1,7 @@
/**
* PM2 配置:用于 standalone 部署的服务器
* 启动方式:node server.js(不要用 npm start / next start,standalone 无 next 命令)
- * 使用:pm2 start ecosystem.config.cjs 或 PORT=3006 pm2 start server.js --name soul
+ * 使用:pm2 start ecosystem.config.cjs 或 PORT=30006 pm2 start server.js --name soul
*/
module.exports = {
apps: [
@@ -11,7 +11,7 @@ module.exports = {
interpreter: 'node',
env: {
NODE_ENV: 'production',
- PORT: 3006,
+ PORT: 30006,
},
cwd: undefined, // 以当前目录为准,部署时在 /www/wwwroot/soul
},
diff --git a/miniprogram/pages/address-edit/address-edit.js b/miniprogram/pages/address-edit/address-edit.js
index 8dfcb509..42c72248 100644
--- a/miniprogram/pages/address-edit/address-edit.js
+++ b/miniprogram/pages/address-edit/address-edit.js
@@ -1,68 +1,117 @@
-// pages/address-edit/address-edit.js
const app = getApp()
Page({
data: {
statusBarHeight: 44,
- navBarHeight: 88
+ navBarHeight: 88,
+ id: '',
+ isEdit: false,
+ name: '',
+ phone: '',
+ province: '',
+ city: '',
+ district: '',
+ detail: '',
+ isDefault: false,
+ loading: false,
+ saving: false
},
onLoad(options) {
const statusBarHeight = app.globalData.statusBarHeight || 44
const navBarHeight = app.globalData.navBarHeight || (statusBarHeight + 44)
- this.setData({ statusBarHeight, navBarHeight })
+ const id = (options && options.id) ? decodeURIComponent(options.id) : ''
+ this.setData({ statusBarHeight, navBarHeight, id, isEdit: !!id })
+ if (id) this.loadAddress(id)
+ },
+
+ loadAddress(id) {
+ this.setData({ loading: true })
+ app.request('/api/user/addresses/' + encodeURIComponent(id))
+ .then(res => {
+ const item = res && res.item ? res.item : null
+ if (!item) {
+ this.setData({ loading: false })
+ return
+ }
+ this.setData({
+ loading: false,
+ name: item.name || '',
+ phone: item.phone || '',
+ province: item.province || '',
+ city: item.city || '',
+ district: item.district || '',
+ detail: item.detail || '',
+ isDefault: !!item.isDefault
+ })
+ })
+ .catch(() => this.setData({ loading: false }))
},
goBack() {
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/my/my' }) })
},
- /**
- * 生命周期函数--监听页面初次渲染完成
- */
- onReady() {
-
- },
-
- /**
- * 生命周期函数--监听页面显示
- */
- onShow() {
-
- },
-
- /**
- * 生命周期函数--监听页面隐藏
- */
- onHide() {
-
- },
-
- /**
- * 生命周期函数--监听页面卸载
- */
- onUnload() {
-
- },
-
- /**
- * 页面相关事件处理函数--监听用户下拉动作
- */
- onPullDownRefresh() {
-
- },
-
- /**
- * 页面上拉触底事件的处理函数
- */
- onReachBottom() {
-
- },
-
- /**
- * 用户点击右上角分享
- */
- onShareAppMessage() {
+ onNameInput(e) { this.setData({ name: (e.detail && e.detail.value) || '' }) },
+ onPhoneInput(e) { this.setData({ phone: (e.detail && e.detail.value) || '' }) },
+ onProvinceInput(e) { this.setData({ province: (e.detail && e.detail.value) || '' }) },
+ onCityInput(e) { this.setData({ city: (e.detail && e.detail.value) || '' }) },
+ onDistrictInput(e) { this.setData({ district: (e.detail && e.detail.value) || '' }) },
+ onDetailInput(e) { this.setData({ detail: (e.detail && e.detail.value) || '' }) },
+ onDefaultChange(e) { this.setData({ isDefault: !!e.detail.value }) },
+ submit() {
+ const { id, isEdit, name, phone, province, city, district, detail, isDefault } = this.data
+ const user = app.globalData.userInfo
+ if (!user || !user.id) {
+ wx.showToast({ title: '请先登录', icon: 'none' })
+ return
+ }
+ if (!name || !name.trim()) {
+ wx.showToast({ title: '请输入姓名', icon: 'none' })
+ return
+ }
+ if (!phone || !phone.trim()) {
+ wx.showToast({ title: '请输入手机号', icon: 'none' })
+ return
+ }
+ if (!/^1[3-9]\d{9}$/.test(phone.trim())) {
+ wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
+ return
+ }
+ if (!detail || !detail.trim()) {
+ wx.showToast({ title: '请输入详细地址', icon: 'none' })
+ return
+ }
+ this.setData({ saving: true })
+ const body = {
+ userId: user.id,
+ name: name.trim(),
+ phone: phone.trim(),
+ province: (province || '').trim(),
+ city: (city || '').trim(),
+ district: (district || '').trim(),
+ detail: detail.trim(),
+ isDefault: !!isDefault
+ }
+ if (isEdit && id) {
+ app.request('/api/user/addresses/' + encodeURIComponent(id), {
+ method: 'PUT',
+ data: body
+ }).then(() => {
+ this.setData({ saving: false })
+ wx.showToast({ title: '保存成功', icon: 'success' })
+ setTimeout(() => wx.navigateBack(), 1500)
+ }).catch(() => this.setData({ saving: false }))
+ } else {
+ app.request('/api/user/addresses', {
+ method: 'POST',
+ data: body
+ }).then(() => {
+ this.setData({ saving: false })
+ wx.showToast({ title: '添加成功', icon: 'success' })
+ setTimeout(() => wx.navigateBack(), 1500)
+ }).catch(() => this.setData({ saving: false }))
+ }
}
-})
\ No newline at end of file
+})
diff --git a/miniprogram/pages/address-edit/address-edit.wxml b/miniprogram/pages/address-edit/address-edit.wxml
index e53946ef..5d0542fa 100644
--- a/miniprogram/pages/address-edit/address-edit.wxml
+++ b/miniprogram/pages/address-edit/address-edit.wxml
@@ -2,7 +2,49 @@
- 待开发
+
+
+
+ 加载中...
+
+
+
+
+
+
+ 姓名
+
+
+
+ 手机号
+
+
+
+ 省份
+
+
+
+ 城市
+
+
+
+ 区/县
+
+
+
+ 详细地址
+
+
+
+ 设为默认地址
+
+
+
+
+
+ {{saving ? '保存中...' : '保存'}}
+
+
diff --git a/miniprogram/pages/address-edit/address-edit.wxss b/miniprogram/pages/address-edit/address-edit.wxss
index e767c367..5ca78707 100644
--- a/miniprogram/pages/address-edit/address-edit.wxss
+++ b/miniprogram/pages/address-edit/address-edit.wxss
@@ -1,8 +1,20 @@
page { background: #000; color: #fff; }
-.page { min-height: 100vh; }
+.page { min-height: 100vh; padding-bottom: 80rpx; box-sizing: border-box; }
.nav-placeholder { width: 100%; }
.header { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.nav-back { font-size: 32rpx; color: #00CED1; margin-right: 24rpx; }
.header-title { flex: 1; text-align: center; font-size: 34rpx; color: #00CED1; }
-.placeholder-body { padding: 48rpx; text-align: center; color: rgba(255,255,255,0.4); font-size: 28rpx; }
-.container { padding: 32rpx; }
+
+.empty-wrap { padding: 80rpx 48rpx; text-align: center; }
+.empty-desc { font-size: 28rpx; color: rgba(255,255,255,0.5); }
+
+.form { padding: 32rpx; }
+.form-item { margin-bottom: 32rpx; }
+.form-item.row { display: flex; align-items: center; justify-content: space-between; }
+.form-label { font-size: 28rpx; color: rgba(255,255,255,0.6); display: block; margin-bottom: 16rpx; }
+.form-item.row .form-label { margin-bottom: 0; }
+.form-input { width: 100%; padding: 24rpx 32rpx; border-radius: 16rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.1); color: #fff; font-size: 28rpx; box-sizing: border-box; }
+.form-textarea { width: 100%; min-height: 160rpx; padding: 24rpx 32rpx; border-radius: 16rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.1); color: #fff; font-size: 28rpx; box-sizing: border-box; }
+
+.btn-save { margin: 32rpx; padding: 28rpx; border-radius: 24rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #000; font-size: 30rpx; font-weight: 600; text-align: center; box-sizing: border-box; }
+.btn-save.disabled { opacity: 0.5; }
diff --git a/miniprogram/pages/address-list/address-list.js b/miniprogram/pages/address-list/address-list.js
index e0036c49..dcc3d988 100644
--- a/miniprogram/pages/address-list/address-list.js
+++ b/miniprogram/pages/address-list/address-list.js
@@ -1,68 +1,80 @@
-// pages/address-list/address-list.js
const app = getApp()
Page({
data: {
statusBarHeight: 44,
- navBarHeight: 88
+ navBarHeight: 88,
+ user: null,
+ list: [],
+ loading: true
},
- onLoad(options) {
+ onLoad() {
const statusBarHeight = app.globalData.statusBarHeight || 44
const navBarHeight = app.globalData.navBarHeight || (statusBarHeight + 44)
this.setData({ statusBarHeight, navBarHeight })
+ this.syncUser()
+ this.loadList()
+ },
+
+ onShow() {
+ this.loadList()
+ },
+
+ syncUser() {
+ const user = app.globalData.userInfo || null
+ this.setData({ user })
+ },
+
+ loadList() {
+ const user = app.globalData.userInfo
+ if (!user || !user.id) {
+ this.setData({ list: [], loading: false })
+ return
+ }
+ this.setData({ loading: true })
+ app.request('/api/user/addresses?userId=' + encodeURIComponent(user.id))
+ .then(res => {
+ const list = (res && res.list) ? res.list : []
+ this.setData({ list, loading: false })
+ })
+ .catch(() => this.setData({ loading: false }))
},
goBack() {
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/my/my' }) })
},
- /**
- * 生命周期函数--监听页面初次渲染完成
- */
- onReady() {
-
+ goAdd() {
+ wx.navigateTo({ url: '/pages/address-edit/address-edit' })
},
- /**
- * 生命周期函数--监听页面显示
- */
- onShow() {
-
+ goEdit(e) {
+ const id = e.currentTarget.dataset.id
+ if (id) wx.navigateTo({ url: '/pages/address-edit/address-edit?id=' + encodeURIComponent(id) })
},
- /**
- * 生命周期函数--监听页面隐藏
- */
- onHide() {
-
- },
-
- /**
- * 生命周期函数--监听页面卸载
- */
- onUnload() {
-
- },
-
- /**
- * 页面相关事件处理函数--监听用户下拉动作
- */
- onPullDownRefresh() {
-
- },
-
- /**
- * 页面上拉触底事件的处理函数
- */
- onReachBottom() {
-
- },
-
- /**
- * 用户点击右上角分享
- */
- onShareAppMessage() {
-
+ deleteAddr(e) {
+ const id = e.currentTarget.dataset.id
+ if (!id) return
+ const that = this
+ wx.showModal({
+ title: '提示',
+ content: '确定要删除该收货地址吗?',
+ success(res) {
+ if (!res.confirm) return
+ app.request('/api/user/addresses/' + encodeURIComponent(id), { method: 'DELETE' })
+ .then(data => {
+ if (data && data.success) {
+ const list = that.data.list.filter(item => item.id !== id)
+ that.setData({ list })
+ wx.showToast({ title: '已删除', icon: 'success' })
+ } else {
+ wx.showToast({ title: (data && data.message) ? data.message : '删除失败', icon: 'none' })
+ }
+ })
+ .catch(() => wx.showToast({ title: '删除失败', icon: 'none' }))
+ }
+ })
}
-})
\ No newline at end of file
+})
diff --git a/miniprogram/pages/address-list/address-list.wxml b/miniprogram/pages/address-list/address-list.wxml
index ebd8f73d..730bea9e 100644
--- a/miniprogram/pages/address-list/address-list.wxml
+++ b/miniprogram/pages/address-list/address-list.wxml
@@ -2,7 +2,50 @@
- 待开发
+
+
+
+ 请先登录
+ 去登录
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+ 📍
+ 暂无收货地址
+ 点击下方按钮添加
+
+
+
+
+
+
+
+ {{item.name}}
+ {{item.phone}}
+ 默认
+
+ {{item.fullAddress}}
+
+ 编辑
+ 删除
+
+
+
+
+
+ ➕ 新增收货地址
diff --git a/miniprogram/pages/address-list/address-list.wxss b/miniprogram/pages/address-list/address-list.wxss
index e767c367..1126c3af 100644
--- a/miniprogram/pages/address-list/address-list.wxss
+++ b/miniprogram/pages/address-list/address-list.wxss
@@ -1,8 +1,25 @@
page { background: #000; color: #fff; }
-.page { min-height: 100vh; }
+.page { min-height: 100vh; padding-bottom: 160rpx; box-sizing: border-box; }
.nav-placeholder { width: 100%; }
.header { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.nav-back { font-size: 32rpx; color: #00CED1; margin-right: 24rpx; }
.header-title { flex: 1; text-align: center; font-size: 34rpx; color: #00CED1; }
-.placeholder-body { padding: 48rpx; text-align: center; color: rgba(255,255,255,0.4); font-size: 28rpx; }
-.container { padding: 32rpx; }
+
+.empty-wrap { padding: 80rpx 48rpx; text-align: center; }
+.empty-icon { font-size: 96rpx; display: block; margin-bottom: 24rpx; opacity: 0.5; }
+.empty-desc { font-size: 28rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 16rpx; }
+.empty-hint { font-size: 24rpx; color: rgba(255,255,255,0.4); display: block; }
+.btn-primary { display: inline-block; padding: 24rpx 64rpx; border-radius: 48rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #000; font-size: 30rpx; font-weight: 600; }
+
+.addr-list { padding: 32rpx; }
+.addr-card { padding: 32rpx; border-radius: 24rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.05); margin-bottom: 24rpx; box-sizing: border-box; }
+.addr-row { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; flex-wrap: wrap; }
+.addr-name { font-size: 30rpx; color: #fff; font-weight: 500; }
+.addr-phone { font-size: 26rpx; color: rgba(255,255,255,0.5); }
+.addr-default { font-size: 22rpx; padding: 4rpx 16rpx; border-radius: 8rpx; background: rgba(0,206,209,0.2); color: #00CED1; }
+.addr-full { font-size: 26rpx; color: rgba(255,255,255,0.6); line-height: 1.5; display: block; margin-bottom: 24rpx; }
+.addr-actions { display: flex; justify-content: flex-end; gap: 32rpx; padding-top: 24rpx; border-top: 2rpx solid rgba(255,255,255,0.05); }
+.addr-btn { font-size: 28rpx; color: #00CED1; }
+.addr-btn.danger { color: #f87171; }
+
+.btn-add { position: fixed; bottom: 0; left: 0; right: 0; margin: 32rpx; padding: 28rpx; border-radius: 24rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #000; font-size: 30rpx; font-weight: 600; text-align: center; box-sizing: border-box; }
diff --git a/miniprogram/pages/referral/referral.js b/miniprogram/pages/referral/referral.js
index bafcea33..7861a587 100644
--- a/miniprogram/pages/referral/referral.js
+++ b/miniprogram/pages/referral/referral.js
@@ -1,68 +1,104 @@
-// pages/referral/referral.js
const app = getApp()
Page({
data: {
statusBarHeight: 44,
- navBarHeight: 88
+ navBarHeight: 88,
+ isLoggedIn: false,
+ user: null,
+ totalEarnings: '0.00',
+ pendingEarnings: '0.00',
+ referralCode: '',
+ distributorShare: 90,
+ canWithdraw: false
},
- onLoad(options) {
+ onLoad() {
const statusBarHeight = app.globalData.statusBarHeight || 44
const navBarHeight = app.globalData.navBarHeight || (statusBarHeight + 44)
this.setData({ statusBarHeight, navBarHeight })
+ this.syncUser()
+ },
+
+ onShow() {
+ this.syncUser()
+ },
+
+ syncUser() {
+ const isLoggedIn = !!app.globalData.isLoggedIn
+ const user = app.globalData.userInfo || null
+ if (!user) {
+ this.setData({ isLoggedIn: false, user: null })
+ return
+ }
+ const total = Number(user.earnings != null ? user.earnings : 0)
+ const totalEarnings = total.toFixed(2)
+ const pendingEarnings = Number(user.pendingEarnings != null ? user.pendingEarnings : 0).toFixed(2)
+ const referralCode = user.referralCode || ''
+ this.setData({
+ isLoggedIn: true,
+ user,
+ totalEarnings,
+ pendingEarnings,
+ referralCode,
+ canWithdraw: total >= 10
+ })
},
goBack() {
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/my/my' }) })
},
- /**
- * 生命周期函数--监听页面初次渲染完成
- */
- onReady() {
-
+ copyLink() {
+ const user = app.globalData.userInfo
+ if (!user || !user.referralCode) {
+ wx.showToast({ title: '请先登录', icon: 'none' })
+ return
+ }
+ const baseUrl = app.globalData.baseUrl || 'https://soul.quwanzhi.com'
+ const link = baseUrl + '?ref=' + user.referralCode
+ wx.setClipboardData({
+ data: link,
+ success: () => wx.showToast({ title: '链接已复制', icon: 'success' })
+ })
},
- /**
- * 生命周期函数--监听页面显示
- */
- onShow() {
+ shareToMoments() {
+ const user = app.globalData.userInfo
+ if (!user || !user.referralCode) {
+ wx.showToast({ title: '请先登录', icon: 'none' })
+ return
+ }
+ const baseUrl = app.globalData.baseUrl || 'https://soul.quwanzhi.com'
+ const link = baseUrl + '?ref=' + user.referralCode
+ const text = `📖 推荐一本好书《一场SOUL的创业实验场》
+这是卡若每天早上6-9点在Soul派对房分享的真实商业故事,55个真实案例,讲透创业的底层逻辑。
+
+👉 点击阅读: ${link}
+
+#创业 #商业思维 #Soul派对`
+ wx.setClipboardData({
+ data: text,
+ success: () => wx.showToast({ title: '朋友圈文案已复制', icon: 'success' })
+ })
},
- /**
- * 生命周期函数--监听页面隐藏
- */
- onHide() {
-
- },
-
- /**
- * 生命周期函数--监听页面卸载
- */
- onUnload() {
-
- },
-
- /**
- * 页面相关事件处理函数--监听用户下拉动作
- */
- onPullDownRefresh() {
-
- },
-
- /**
- * 页面上拉触底事件的处理函数
- */
- onReachBottom() {
-
- },
-
- /**
- * 用户点击右上角分享
- */
- onShareAppMessage() {
-
+ applyWithdraw() {
+ const user = app.globalData.userInfo
+ if (!user) {
+ wx.showToast({ title: '请先登录', icon: 'none' })
+ return
+ }
+ const total = Number(user.earnings != null ? user.earnings : 0)
+ if (total < 10) {
+ wx.showToast({ title: '满10元可提现', icon: 'none' })
+ return
+ }
+ wx.showToast({
+ title: '请在小程序内联系客服或使用提现功能',
+ icon: 'none',
+ duration: 2500
+ })
}
-})
\ No newline at end of file
+})
diff --git a/miniprogram/pages/referral/referral.wxml b/miniprogram/pages/referral/referral.wxml
index 81173a92..ef59e5d8 100644
--- a/miniprogram/pages/referral/referral.wxml
+++ b/miniprogram/pages/referral/referral.wxml
@@ -4,5 +4,59 @@
← 返回
- 待开发
+
+
+
+ 请先登录
+ 返回我的
+
+
+
+
+
+
+
+ 💰
+
+ 累计收益
+ {{distributorShare}}% 返利
+
+
+
+ ¥{{totalEarnings}}
+ 待结算: ¥{{pendingEarnings}}
+
+
+
+ {{canWithdraw ? '申请提现' : '满10元可提现'}}
+
+
+
+
+
+ 我的邀请码
+ {{referralCode}}
+
+ 好友通过你的链接购买立省5%,你获得{{distributorShare}}%收益
+
+
+
+
+ 🔗
+
+ 复制邀请链接
+ 分享给好友购买
+
+ ›
+
+
+ 💬
+
+ 分享到朋友圈
+ 复制文案发朋友圈
+
+ ›
+
+
+
diff --git a/miniprogram/pages/referral/referral.wxss b/miniprogram/pages/referral/referral.wxss
index e767c367..2e619451 100644
--- a/miniprogram/pages/referral/referral.wxss
+++ b/miniprogram/pages/referral/referral.wxss
@@ -1,8 +1,38 @@
page { background: #000; color: #fff; }
-.page { min-height: 100vh; }
+.page { min-height: 100vh; padding-bottom: 80rpx; box-sizing: border-box; }
.nav-placeholder { width: 100%; }
.header { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.nav-back { font-size: 32rpx; color: #00CED1; margin-right: 24rpx; }
.header-title { flex: 1; text-align: center; font-size: 34rpx; color: #00CED1; }
-.placeholder-body { padding: 48rpx; text-align: center; color: rgba(255,255,255,0.4); font-size: 28rpx; }
-.container { padding: 32rpx; }
+
+.empty-wrap { padding: 80rpx 48rpx; text-align: center; }
+.empty-desc { font-size: 28rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 32rpx; }
+.btn-primary { display: inline-block; padding: 24rpx 64rpx; border-radius: 48rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #000; font-size: 30rpx; font-weight: 600; }
+
+.earnings-card { margin: 32rpx; padding: 32rpx; border-radius: 32rpx; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); border: 2rpx solid rgba(0,206,209,0.2); box-sizing: border-box; }
+.earnings-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; gap: 24rpx; min-width: 0; }
+.earnings-title-row { display: flex; align-items: center; gap: 16rpx; min-width: 0; }
+.earnings-icon { font-size: 40rpx; flex-shrink: 0; }
+.earnings-label { font-size: 24rpx; color: rgba(255,255,255,0.5); display: block; }
+.earnings-rate { font-size: 24rpx; color: #00CED1; display: block; margin-top: 4rpx; }
+.earnings-right { text-align: right; flex-shrink: 0; }
+.earnings-total { font-size: 56rpx; font-weight: 700; color: #fff; display: block; }
+.earnings-pending { font-size: 24rpx; color: rgba(255,255,255,0.5); }
+.btn-withdraw { width: 100%; padding: 24rpx; border-radius: 24rpx; background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%); color: #000; font-size: 30rpx; font-weight: 600; text-align: center; margin-top: 16rpx; box-sizing: border-box; }
+.btn-withdraw.disabled { opacity: 0.5; background: #2c2c2e; color: rgba(255,255,255,0.5); }
+
+.code-card { margin: 32rpx; padding: 32rpx; border-radius: 24rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.05); }
+.code-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16rpx; }
+.code-label { font-size: 30rpx; color: #fff; font-weight: 500; }
+.code-value { font-size: 28rpx; color: #00CED1; font-family: monospace; background: rgba(0,206,209,0.1); padding: 12rpx 24rpx; border-radius: 16rpx; }
+.code-desc { font-size: 24rpx; color: rgba(255,255,255,0.5); }
+
+.action-list { margin: 32rpx; border-radius: 24rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.05); overflow: hidden; }
+.action-item { display: flex; align-items: center; padding: 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
+.action-item:last-child { border-bottom: none; }
+.action-icon { width: 80rpx; height: 80rpx; border-radius: 20rpx; background: rgba(0,206,209,0.15); display: flex; align-items: center; justify-content: center; font-size: 40rpx; margin-right: 24rpx; flex-shrink: 0; }
+.action-icon.wechat { background: rgba(7,193,96,0.15); }
+.action-text { flex: 1; min-width: 0; }
+.action-title { font-size: 30rpx; color: #fff; font-weight: 500; display: block; }
+.action-desc { font-size: 24rpx; color: rgba(255,255,255,0.4); display: block; margin-top: 8rpx; }
+.action-arrow { font-size: 32rpx; color: rgba(255,255,255,0.3); }
diff --git a/package.json b/package.json
index 4cdee5cd..3b4ac018 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"private": true,
"scripts": {
"build": "next build",
- "dev": "next dev -p 3006",
+ "dev": "next dev -p 30006",
"lint": "eslint .",
"start": "node scripts/start-standalone.js"
},
diff --git a/scripts/deploy_soul.py b/scripts/deploy_soul.py
index bca42ea1..6d3a8af7 100644
--- a/scripts/deploy_soul.py
+++ b/scripts/deploy_soul.py
@@ -19,7 +19,7 @@ Soul 创业派对 - 一键部署脚本
BAOTA_PANEL_URL # 宝塔面板地址,默认 https://42.194.232.22:9988
BAOTA_API_KEY # 宝塔 API 密钥,默认 hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd
DEPLOY_PM2_APP # PM2 项目名称,默认 soul
- DEPLOY_PORT # Next.js 监听端口,默认 3006(与 package.json / ecosystem 一致)
+ DEPLOY_PORT # Next.js 监听端口,默认 30006(与 package.json / ecosystem 一致)
DEPLOY_NODE_VERSION # Node 版本,默认 v22.14.0(用于显示)
DEPLOY_NODE_PATH # Node 可执行文件路径,默认 /www/server/nodejs/v22.14.0/bin
# 用于避免多 Node 环境冲突,确保使用指定的 Node 版本
@@ -71,7 +71,7 @@ def get_cfg():
"api_key": os.environ.get("BAOTA_API_KEY", "hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd"),
"pm2_name": os.environ.get("DEPLOY_PM2_APP", "soul"),
"site_url": os.environ.get("DEPLOY_SITE_URL", "https://soul.quwanzhi.com"),
- "port": int(os.environ.get("DEPLOY_PORT", "3006")), # Next.js 监听端口,与 package.json / ecosystem 一致
+ "port": int(os.environ.get("DEPLOY_PORT", "30006")), # Next.js 监听端口,与 package.json / ecosystem 一致
# Node 环境配置
"node_version": os.environ.get("DEPLOY_NODE_VERSION", "v22.14.0"), # 指定 Node 版本
"node_path": os.environ.get("DEPLOY_NODE_PATH", "/www/server/nodejs/v22.14.0/bin"), # Node 可执行文件路径
@@ -188,11 +188,11 @@ def restart_node_project(panel_url, api_key, pm2_name):
return False
-def add_or_update_node_project(panel_url, api_key, pm2_name, project_path, port=3006, node_path=None):
+def add_or_update_node_project(panel_url, api_key, pm2_name, project_path, port=30006, node_path=None):
"""通过宝塔 API 添加或更新 Node 项目配置
Next.js standalone 的 server.js 通过 process.env.PORT 读端口(默认 3000),
- 这里在 run_cmd 中显式设置 PORT=port,与项目 package.json / ecosystem 的 3006 一致。
+ 这里在 run_cmd 中显式设置 PORT=port,与项目 package.json / ecosystem 的 30006 一致。
"""
paths_to_try = [
"/project/nodejs/add_project",
@@ -724,7 +724,7 @@ def deploy_via_baota_api(cfg):
pm2_name = cfg["pm2_name"]
project_path = cfg["project_path"]
node_path = cfg.get("node_path", "/www/server/nodejs/v22.14.0/bin")
- port = cfg.get("port", 3006) # 与 package.json dev/start -p 3006、ecosystem PORT 一致
+ port = cfg.get("port", 30006) # 与 package.json dev/start -p 30006、ecosystem PORT 一致
# 1. 检查项目是否存在
print(" 检查项目状态...")
@@ -796,7 +796,7 @@ def main():
print(" 项目路径: %s" % cfg["project_path"])
print(" PM2 名称: %s" % cfg["pm2_name"])
print(" 站点地址: %s" % cfg["site_url"])
- print(" 端口: %s" % cfg.get("port", 3006))
+ print(" 端口: %s" % cfg.get("port", 30006))
print(" Node 版本: %s" % cfg.get("node_version", "v22.14.0"))
print(" Node 路径: %s" % cfg.get("node_path", "/www/server/nodejs/v22.14.0/bin"))
print("=" * 60)
diff --git a/scripts/start-standalone.js b/scripts/start-standalone.js
index dc3b82de..6c923f72 100644
--- a/scripts/start-standalone.js
+++ b/scripts/start-standalone.js
@@ -73,9 +73,9 @@ const port = process.env.PORT;
if (!port) {
console.error('❌ 错误:未设置 PORT 环境变量');
console.error(' 请设置端口后启动,例如:');
- console.error(' PORT=3006 pnpm start');
+ console.error(' PORT=30006 pnpm start');
console.error(' 或:');
- console.error(' export PORT=3006 && pnpm start');
+ console.error(' export PORT=30006 && pnpm start');
process.exit(1);
}
diff --git a/开发文档/8、部署/Next转小程序Kbone迁移方案.md b/开发文档/8、部署/Next转小程序Kbone迁移方案.md
index e8f66447..a3b07c1b 100644
--- a/开发文档/8、部署/Next转小程序Kbone迁移方案.md
+++ b/开发文档/8、部署/Next转小程序Kbone迁移方案.md
@@ -8,10 +8,12 @@
- ✅ **Phase 1:搭架子**(已完成):适配层、miniprogram.config.js、首页/目录/阅读占位、构建与合并脚本
- ✅ **Phase 2:核心页**(已完成):阅读页接 `/api/book/chapter/[id]`、ChapterContent 组件、完整目录、上下篇切换
-- ⏳ **Phase 3:我的与子页**(待做):我的、推广、设置、购买记录、地址列表/编辑、Zustand + persist
-- ⏳ **Phase 4:找伙伴与其余**(待做):match、关于、搜索、底部 tabBar、安全区
+- ✅ **Phase 3:我的与子页**(已完成):我的、推广、设置、购买记录、关于、Zustand + persist
+- ✅ **Phase 4:找伙伴与其余**(已完成):match、搜索、底部 tabBar、安全区适配
- ⏳ **Phase 5:收尾**(待做):全量自检、样式对齐、踩坑修复、发布流程
+**🎉 C 端页面已 100% 迁移完成!(2026-02-02)**
+
---
## 一、策略选择
diff --git a/开发文档/8、部署/Phase4完成说明.md b/开发文档/8、部署/Phase4完成说明.md
new file mode 100644
index 00000000..0575afee
--- /dev/null
+++ b/开发文档/8、部署/Phase4完成说明.md
@@ -0,0 +1,125 @@
+# Phase 4 完成说明(找伙伴与其余)
+
+## 完成时间
+2026-02-02
+
+## 已完成内容
+
+### 1. 找伙伴页
+
+**newpp/src/pages/MatchPage.jsx + src/match.jsx**
+
+- 匹配类型选择:创业合伙、资源对接、导师顾问、团队招募
+- 匹配次数管理:每日免费1次 + 购买章节获得更多次数
+- 匹配结果展示:匹配度、创业理念、共同兴趣、微信号
+- 加入匹配池功能:提交手机号加入
+- 次数统计:剩余次数、已用次数、解锁提示
+
+### 2. 搜索页
+
+**newpp/src/pages/SearchPage.jsx + src/search.jsx**
+
+- 实时搜索章节标题
+- 搜索结果展示:所属篇章、标题、免费标签
+- 点击结果跳转到阅读页
+- 空态与无结果提示
+
+### 3. 底部 TabBar 组件
+
+**newpp/src/components/BottomNav.jsx**
+
+- 4 个 Tab:首页、目录、找伙伴、我的
+- 当前激活态标识
+- 找伙伴 Tab 动态显示(matchEnabled)
+- 安全区适配:paddingBottom = safe-area-inset-bottom
+- 跨端路由:小程序 wx.switchTab,Web window.location.href
+
+### 4. 各页面集成 BottomNav
+
+已在以下页面添加 `` 组件:
+
+- HomePage (current="/")
+- ChaptersPage (current="/chapters")
+- MatchPage (current="/match")
+- MyPage (current="/my")
+
+### 5. 构建配置更新
+
+**webpack.mp.config.js**
+
+- 新增入口:match、search
+
+**miniprogram.config.js**
+
+- router.other 新增路由:/match、/search
+
+---
+
+## 已完成页面清单(Phase 1-4)
+
+| Next 路由 | 小程序页面 | 入口文件 | 状态 |
+|-----------|-----------|----------|------|
+| app/page.tsx | pages/index/index | src/index.jsx | ✅ Phase 2 |
+| app/chapters/page.tsx | pages/chapters/chapters | src/chapters.jsx | ✅ Phase 2 |
+| app/read/[id]/page.tsx | pages/read/read | src/read.jsx | ✅ Phase 2 |
+| app/my/page.tsx | pages/my/my | src/my.jsx | ✅ Phase 3 |
+| app/my/referral/page.tsx | pages/referral/referral | src/referral.jsx | ✅ Phase 3 |
+| app/my/settings/page.tsx | pages/settings/settings | src/settings.jsx | ✅ Phase 3 |
+| app/my/purchases/page.tsx | pages/purchases/purchases | src/purchases.jsx | ✅ Phase 3 |
+| app/about/page.tsx | pages/about/about | src/about.jsx | ✅ Phase 3 |
+| app/match/page.tsx | pages/match/match | src/match.jsx | ✅ Phase 4 |
+| app/search/page.tsx | pages/search/search | src/search.jsx | ✅ Phase 4 |
+
+---
+
+## 安全区适配说明
+
+### 底部安全区
+在 BottomNav 中使用 `paddingBottom: 'env(safe-area-inset-bottom)'`,确保在有底部刘海的设备上正常显示。
+
+### 顶部安全区
+小程序自动处理,无需额外适配。如需自定义导航栏,可在 app.js globalData 中读取 `navBarHeight`、`statusBarHeight`。
+
+### 右侧安全区(胶囊按钮)
+若使用自定义导航栏,需在右侧预留 `capsulePaddingRight` 空间,避免遮挡胶囊按钮。
+
+---
+
+## 本地测试步骤
+
+1. **构建**
+ ```bash
+ cd newpp
+ npm run build:mp
+ ```
+ 确认生成 `newpp/dist/mp/`,包含 10 个页面(index、chapters、read、my、referral、settings、purchases、about、match、search)。
+
+2. **合并到 miniprogram**
+ ```bash
+ cd ..
+ node scripts/merge-kbone-to-miniprogram.js
+ ```
+
+3. **手动合并 app.js**
+ 确保 `miniprogram/app.js` 包含 globalData(baseUrl、matchEnabled、navBarHeight 等)、request、loadFeatureConfig 等逻辑。
+
+4. **微信开发者工具测试**
+ - 底部 TabBar:首页 ↔ 目录 ↔ 找伙伴 ↔ 我的
+ - 找伙伴:选择类型 → 开始匹配 → 查看结果 → 复制微信
+ - 搜索:首页 → 搜索 icon → 输入关键词 → 查看结果 → 点击跳转阅读
+ - 我的:查看统计 → 推广中心 → 设置 → 关于
+
+---
+
+## 待完成(Phase 5)
+
+- **全量自检**:对照「Web转小程序并上传-提示词」逐项检查
+- **样式对齐**:颜色、间距、圆角、阴影与 Web 保持一致
+- **踩坑修复**:WXML 禁忌、启动不阻塞、safe-area 边界
+- **发布流程**:预览码 → 体验版 → 提审 → 发布
+
+---
+
+## 下一步
+
+Phase 5:收尾(全量自检、样式对齐、踩坑修复、发布流程)
diff --git a/开发文档/8、部署/Standalone模式说明.md b/开发文档/8、部署/Standalone模式说明.md
index 776a3951..90c7808c 100644
--- a/开发文档/8、部署/Standalone模式说明.md
+++ b/开发文档/8、部署/Standalone模式说明.md
@@ -69,7 +69,7 @@ npm install --production
# 使用 next 命令启动
npm start
# 或
-next start -p 3006
+next start -p 30006
```
### Standalone 模式
@@ -79,7 +79,7 @@ next start -p 3006
node server.js
# 或指定端口
-PORT=3006 node server.js
+PORT=30006 node server.js
# 使用 PM2
pm2 start server.js --name soul
diff --git a/开发文档/8、部署/宝塔配置检查说明.md b/开发文档/8、部署/宝塔配置检查说明.md
index cc474110..a0ca7510 100644
--- a/开发文档/8、部署/宝塔配置检查说明.md
+++ b/开发文档/8、部署/宝塔配置检查说明.md
@@ -9,9 +9,9 @@
### 1. 应用端口与 Nginx 不一致(已修复)
- **现象**:部署脚本用 `pm2 start server.js --name soul` 启动,未指定端口。Next.js standalone 默认监听 **3000**。
-- **宝塔约定**:根据 `开发文档/服务器管理/references/端口配置表.md`,soul 使用端口 **3006**,Nginx 反代到 `127.0.0.1:3006`。
-- **结果**:应用实际在 3000 监听,Nginx 请求 3006 → 无进程 → **502 Bad Gateway**。
-- **修复**:部署脚本 `scripts/devlop.py` 通过宝塔 API 重启 Node 项目,服务器上 PM2 启动时需设置 `PORT=3006`(可与 `ecosystem.config.cjs` 或环境变量 `DEPLOY_APP_PORT` 一致),保证与 Nginx 一致。
+- **宝塔约定**:根据 `开发文档/服务器管理/references/端口配置表.md`,soul 使用端口 **30006**,Nginx 反代到 `127.0.0.1:30006`。
+- **结果**:应用实际在 3000 监听,Nginx 请求 30006 → 无进程 → **502 Bad Gateway**。
+- **修复**:部署脚本 `scripts/devlop.py` 通过宝塔 API 重启 Node 项目,服务器上 PM2 启动时需设置 `PORT=30006`(可与 `ecosystem.config.cjs` 或环境变量 `DEPLOY_APP_PORT` 一致),保证与 Nginx 一致。
---
@@ -20,36 +20,36 @@
### 1. Nginx 反向代理
- **域名**:soul.quwanzhi.com
-- **要求**:`proxy_pass http://127.0.0.1:3006;`(与端口配置表一致)
-- **检查**:宝塔 → 网站 → soul.quwanzhi.com → 设置 → 反向代理 / 配置文件,确认 `proxy_pass` 指向 `127.0.0.1:3006`。
+- **要求**:`proxy_pass http://127.0.0.1:30006;`(与端口配置表一致)
+- **检查**:宝塔 → 网站 → soul.quwanzhi.com → 设置 → 反向代理 / 配置文件,确认 `proxy_pass` 指向 `127.0.0.1:30006`。
- **SSL**:若走 HTTPS,确认已配置 443 与证书(端口配置表注明使用通配符证书)。
### 2. PM2 与部署脚本一致
- **项目名**:soul(与 `scripts/devlop.py` 中 `DEPLOY_PM2_APP` 一致)
-- **启动方式**:**必须用 `node server.js`**,工作目录 `/www/wwwroot/soul`,环境变量 `PORT=3006`。
+- **启动方式**:**必须用 `node server.js`**,工作目录 `/www/wwwroot/soul`,环境变量 `PORT=30006`。
- **不要用**:`npm start` / `next start`。standalone 部署后没有完整 node_modules,也没有 `next` 命令,会报 `next: command not found`。
-- **宝塔 PM2 管理器**:启动文件填 `server.js`,启动命令填 `node server.js`(或选「Node 项目」后只填 `server.js`),环境变量添加 `PORT=3006`。也可用 `pm2 start ecosystem.config.cjs`(项目根目录已提供该文件)。
+- **宝塔 PM2 管理器**:启动文件填 `server.js`,启动命令填 `node server.js`(或选「Node 项目」后只填 `server.js`),环境变量添加 `PORT=30006`。也可用 `pm2 start ecosystem.config.cjs`(项目根目录已提供该文件)。
- **注意**:若同时在宝塔「PM2 管理器」里添加了同名项目,可能产生 root 与 www 用户冲突,建议只保留一种方式(要么只用脚本部署 + 命令行 PM2,要么只用宝塔 PM2 界面)。
### 3. 项目目录与端口
- **项目路径**:`/www/wwwroot/soul`(与 `DEPLOY_PROJECT_PATH` 一致)
-- **应用端口**:3006(与端口配置表、Nginx、部署脚本中的 `PORT` 一致)
+- **应用端口**:30006(与端口配置表、Nginx、部署脚本中的 `PORT` 一致)
---
## 三、快速检查命令(SSH 到服务器后执行)
```bash
-# 1. 应用是否在 3006 监听
-ss -tlnp | grep 3006
+# 1. 应用是否在 30006 监听
+ss -tlnp | grep 30006
# 2. PM2 列表(是否有 soul,状态 online)
pm2 list
-# 3. Nginx 配置是否包含 soul 且 proxy_pass 为 3006
-grep -r "soul\|3006" /www/server/panel/vhost/nginx/
+# 3. Nginx 配置是否包含 soul 且 proxy_pass 为 30006
+grep -r "soul\|30006" /www/server/panel/vhost/nginx/
# 4. Nginx 语法
nginx -t
@@ -62,11 +62,11 @@ nginx -t
部署时若需改端口,可在本机执行脚本前设置:
```bash
-set DEPLOY_APP_PORT=3006
+set DEPLOY_APP_PORT=30006
python scripts/devlop.py
```
-或修改 `scripts/devlop.py` 中 `get_cfg()` 的 `app_port` 默认值(当前为 3006)。
+或修改 `scripts/devlop.py` 中 `get_cfg()` 的 `app_port` 默认值(当前为 30006)。
---
diff --git a/开发文档/8、部署/当前项目部署到线上.md b/开发文档/8、部署/当前项目部署到线上.md
index de51ea42..dabb6229 100644
--- a/开发文档/8、部署/当前项目部署到线上.md
+++ b/开发文档/8、部署/当前项目部署到线上.md
@@ -9,7 +9,7 @@
**服务器**:与 开发文档/服务器管理 一致
- 小型宝塔:`42.194.232.22`
- 项目路径:`/www/wwwroot/soul`
-- 端口:3006,域名:https://soul.quwanzhi.com
+- 端口:30006,域名:https://soul.quwanzhi.com
**凭证**:与 服务器管理/SKILL.md 一致(root / Zhiqun1984),已写在项目部署脚本里。
diff --git a/开发文档/8、部署/端口配置说明.md b/开发文档/8、部署/端口配置说明.md
index fd51784b..f96a44a0 100644
--- a/开发文档/8、部署/端口配置说明.md
+++ b/开发文档/8、部署/端口配置说明.md
@@ -9,9 +9,9 @@
### 1. 移除硬编码端口
- ✅ **Dockerfile**: 移除 `ENV PORT 3000`,改为从环境变量读取
-- ✅ **docker-compose.yml**: 使用 `APP_PORT` 环境变量,默认 3006
+- ✅ **docker-compose.yml**: 使用 `APP_PORT` 环境变量,默认 30006
- ✅ **start-standalone.js**: 强制要求设置 `PORT` 环境变量
-- ✅ **deploy_soul.py**: 已使用 3006 端口(与项目一致)
+- ✅ **deploy_soul.py**: 已使用 30006 端口(与项目一致)
### 2. 端口配置方式
@@ -19,17 +19,17 @@
```bash
# Windows PowerShell
-$env:PORT=3006
+$env:PORT=30006
pnpm start
# 或一行命令
-$env:PORT=3006; pnpm start
+$env:PORT=30006; pnpm start
# Windows CMD
-set PORT=3006 && pnpm start
+set PORT=30006 && pnpm start
# Linux/Mac
-PORT=3006 pnpm start
+PORT=30006 pnpm start
```
#### 方式二:Docker Compose 部署
@@ -37,7 +37,7 @@ PORT=3006 pnpm start
1. 创建 `.env` 文件(项目根目录):
```env
-APP_PORT=3006
+APP_PORT=30006
```
2. 启动容器:
@@ -46,7 +46,7 @@ APP_PORT=3006
docker-compose up -d
```
-容器内外端口都会使用 3006。
+容器内外端口都会使用 30006。
#### 方式三:Docker 直接运行
@@ -61,7 +61,7 @@ docker run -e PORT=3007 -p 3007:3007 soul-book
```python
"config": {
- "port": int(os.environ.get("DEPLOY_PORT", "3006")), # 修改此处
+ "port": int(os.environ.get("DEPLOY_PORT", "30006")), # 修改此处
# ...
}
```
@@ -77,7 +77,7 @@ python scripts/deploy_soul.py --action update
| 项目名称 | 端口 | 说明 |
|---------|------|------|
-| soul-book | 3006 | 本项目默认端口 |
+| soul-book | 30006 | 本项目默认端口 |
| other-project-1 | 3007 | 其他项目 |
| api-service | 3008 | API 服务 |
| admin-panel | 3009 | 管理后台 |
@@ -101,7 +101,7 @@ python scripts/deploy_soul.py --action update
```nginx
location / {
- proxy_pass http://localhost:3006; # 修改为实际端口
+ proxy_pass http://localhost:30006; # 修改为实际端口
# ...
}
```
@@ -112,11 +112,11 @@ location / {
```bash
# CentOS/RHEL
-firewall-cmd --zone=public --add-port=3006/tcp --permanent
+firewall-cmd --zone=public --add-port=30006/tcp --permanent
firewall-cmd --reload
# Ubuntu/Debian
-ufw allow 3006/tcp
+ufw allow 30006/tcp
```
### 4. PM2 配置(如使用)
@@ -130,7 +130,7 @@ module.exports = {
script: 'node_modules/next/dist/bin/next',
args: 'start',
env: {
- PORT: 3006
+ PORT: 30006
}
}]
}
@@ -142,10 +142,10 @@ module.exports = {
```bash
# 检查端口监听
-netstat -tlnp | grep 3006
+netstat -tlnp | grep 30006
# 测试访问
-curl http://localhost:3006
+curl http://localhost:30006
# 查看容器端口映射
docker ps | grep soul
diff --git a/开发文档/8、部署/部署脚本备份/devlop.py b/开发文档/8、部署/部署脚本备份/devlop.py
index 172f44ab..a3508d72 100644
--- a/开发文档/8、部署/部署脚本备份/devlop.py
+++ b/开发文档/8、部署/部署脚本备份/devlop.py
@@ -49,7 +49,7 @@ def get_cfg():
"password": os.environ.get("DEPLOY_PASSWORD", "Zhiqun1984"),
"ssh_key": os.environ.get("DEPLOY_SSH_KEY", ""),
"project_path": os.environ.get("DEPLOY_PROJECT_PATH", "/www/wwwroot/soul"),
- "app_port": os.environ.get("DEPLOY_APP_PORT", "3006"),
+ "app_port": os.environ.get("DEPLOY_APP_PORT", "30006"),
"pm2_name": os.environ.get("DEPLOY_PM2_APP", BAOTA_CFG["pm2_name"]),
}