From 2aad5d55fd62a3a9d7ab25e6367d772558ad0525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 2 Feb 2026 18:27:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.port.example | 10 +- .github/workflows/README.md | 2 +- DEPLOYMENT.md | 4 +- Dockerfile | 2 +- README-Phase4.md | 139 ++++++++++++++++ docker-compose.yml | 14 +- ecosystem.config.cjs | 4 +- .../pages/address-edit/address-edit.js | 149 ++++++++++++------ .../pages/address-edit/address-edit.wxml | 46 +++++- .../pages/address-edit/address-edit.wxss | 18 ++- .../pages/address-list/address-list.js | 106 +++++++------ .../pages/address-list/address-list.wxml | 47 +++++- .../pages/address-list/address-list.wxss | 23 ++- miniprogram/pages/referral/referral.js | 128 +++++++++------ miniprogram/pages/referral/referral.wxml | 56 ++++++- miniprogram/pages/referral/referral.wxss | 36 ++++- package.json | 2 +- scripts/deploy_soul.py | 12 +- scripts/start-standalone.js | 4 +- 开发文档/8、部署/Next转小程序Kbone迁移方案.md | 6 +- 开发文档/8、部署/Phase4完成说明.md | 125 +++++++++++++++ 开发文档/8、部署/Standalone模式说明.md | 4 +- 开发文档/8、部署/宝塔配置检查说明.md | 28 ++-- 开发文档/8、部署/当前项目部署到线上.md | 2 +- 开发文档/8、部署/端口配置说明.md | 32 ++-- 开发文档/8、部署/部署脚本备份/devlop.py | 2 +- 26 files changed, 781 insertions(+), 220 deletions(-) create mode 100644 README-Phase4.md create mode 100644 开发文档/8、部署/Phase4完成说明.md 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 @@ ← 返回 - 地址编辑 + {{isEdit ? '编辑地址' : '新增地址'}} - 待开发 + + + + 加载中... + + + + + + + 姓名 + + + + 手机号 + + + + 省份 + + + + 城市 + + + + 区/县 + + + + 详细地址 +