From 8eec1ab78c5ac0d5a07db2728527b0ba5c0be0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 2 Feb 2026 18:16:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=8D=E5=86=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=96=87=E4=BB=B6=E5=92=8C=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E4=BB=A5=E6=8F=90=E5=8D=87=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7?= =?UTF-8?q?=EF=BC=9B=E6=96=B0=E5=A2=9E=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=A4=BA=E4=BE=8B=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20Docker=20=E5=92=8C=E9=83=A8=E7=BD=B2=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81=E7=81=B5=E6=B4=BB?= =?UTF-8?q?=E7=9A=84=E7=AB=AF=E5=8F=A3=E8=AE=BE=E7=BD=AE=EF=BC=9B=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E5=A2=9E=E5=BC=BA=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=92=8C=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E6=9B=B4=E5=A5=BD=E7=9A=84=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7=E5=92=8C=E7=A8=B3=E5=AE=9A=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursorrules | 83 - .env.port.example | 32 + .gitignore | 3 + Dockerfile | 5 +- README-Phase3.md | 96 + _tmp_34228_f1906e0fb94e68c4400403820df714df | 0 app/layout.tsx | 2 +- app/match/page.tsx | 36 +- app/my/page.tsx | 76 +- app/page.tsx | 9 +- components/bottom-nav.tsx | 22 +- docker-compose.yml | 13 +- lib/db.ts | 58 +- miniprogram/.gitignore | 10 - miniprogram/README.md | 138 - miniprogram/app.js | 452 +- miniprogram/app.json | 28 +- miniprogram/app.wxss | 568 +- miniprogram/assets/icons/home-active.png | Bin 699 -> 0 bytes miniprogram/assets/icons/home.png | Bin 611 -> 0 bytes miniprogram/assets/icons/match-active.png | Bin 907 -> 0 bytes miniprogram/assets/icons/match.png | Bin 725 -> 0 bytes miniprogram/assets/icons/my-active.png | Bin 907 -> 0 bytes miniprogram/assets/icons/my.png | Bin 725 -> 0 bytes miniprogram/custom-tab-bar/index.js | 98 +- miniprogram/custom-tab-bar/index.json | 3 +- miniprogram/custom-tab-bar/index.wxml | 64 +- miniprogram/custom-tab-bar/index.wxss | 235 +- miniprogram/pages/about/about.js | 100 +- miniprogram/pages/about/about.json | 4 +- miniprogram/pages/about/about.wxml | 111 +- miniprogram/pages/about/about.wxss | 75 +- .../pages/address-edit/address-edit.js | 180 +- .../pages/address-edit/address-edit.wxml | 42 +- .../pages/address-edit/address-edit.wxss | 29 +- .../pages/address-list/address-list.js | 129 +- .../pages/address-list/address-list.json | 2 +- .../pages/address-list/address-list.wxml | 48 +- .../pages/address-list/address-list.wxss | 52 +- miniprogram/pages/chapters/chapters.js | 317 +- miniprogram/pages/chapters/chapters.json | 6 +- miniprogram/pages/chapters/chapters.wxml | 156 +- miniprogram/pages/chapters/chapters.wxss | 496 +- miniprogram/pages/index/index.js | 180 +- miniprogram/pages/index/index.json | 5 +- miniprogram/pages/index/index.wxml | 100 +- miniprogram/pages/index/index.wxss | 569 +- miniprogram/pages/match/match.js | 745 +- miniprogram/pages/match/match.json | 6 +- miniprogram/pages/match/match.wxml | 364 +- miniprogram/pages/match/match.wxss | 1305 +-- miniprogram/pages/my/my.js | 449 +- miniprogram/pages/my/my.json | 6 +- miniprogram/pages/my/my.wxml | 350 +- miniprogram/pages/my/my.wxss | 1084 +-- miniprogram/pages/purchases/purchases.js | 116 +- miniprogram/pages/purchases/purchases.json | 4 +- miniprogram/pages/purchases/purchases.wxml | 83 +- miniprogram/pages/purchases/purchases.wxss | 50 +- miniprogram/pages/read/read.js | 992 +- miniprogram/pages/read/read.json | 7 +- miniprogram/pages/read/read.wxml | 295 +- miniprogram/pages/read/read.wxss | 981 +- miniprogram/pages/referral/referral.js | 575 +- miniprogram/pages/referral/referral.json | 4 +- miniprogram/pages/referral/referral.wxml | 225 +- miniprogram/pages/referral/referral.wxss | 156 +- miniprogram/pages/search/search.js | 132 +- miniprogram/pages/search/search.json | 5 +- miniprogram/pages/search/search.wxml | 146 +- miniprogram/pages/search/search.wxss | 375 +- miniprogram/pages/settings/settings.js | 459 +- miniprogram/pages/settings/settings.json | 4 +- miniprogram/pages/settings/settings.wxml | 196 +- miniprogram/pages/settings/settings.wxss | 150 +- miniprogram/project.config.json | 61 +- miniprogram/project.private.config.json | 7 - miniprogram/sitemap.json | 5 +- miniprogram/upload.js | 4 +- miniprogram/utils/payment.js | 211 - miniprogram/utils/util.js | 182 - miniprogram/小程序快速配置指南.md | 272 - miniprogram/小程序部署说明.md | 463 - miniprogram/测试二维码.html | 366 - miniprogram/生成图标.html | 71 - miniprogram/自动部署.sh | 82 - next.config.mjs | 2 +- package.json | 3 +- pnpm-lock.yaml | 8498 ++++++++++++++++- scripts/Web转小程序并上传-提示词.md | 97 +- .../__pycache__/deploy_soul.cpython-311.pyc | Bin 0 -> 46011 bytes scripts/autosysc-weixin.py | 6 + scripts/demo.py | 147 - scripts/deploy_soul.py | 22 +- scripts/devlop.py | 537 ++ scripts/merge-kbone-to-miniprogram.js | 86 + scripts/start-standalone.js | 106 + 开发文档/8、部署/Next转小程序Kbone迁移方案.md | 236 + 开发文档/8、部署/Phase3完成说明.md | 128 + 开发文档/8、部署/端口配置说明.md | 187 + 开发文档/小程序管理/SKILL.md | 1798 ---- .../小程序管理/references/API接口速查表.md | 176 - .../小程序管理/references/企业认证完整指南.md | 307 - 开发文档/小程序管理/references/审核规范.md | 276 - .../小程序管理/references/隐私协议填写指南.md | 242 - .../scripts/__pycache__/mp_api.cpython-314.pyc | Bin 30696 -> 0 bytes 开发文档/小程序管理/scripts/apps_config.json | 40 - 开发文档/小程序管理/scripts/env_template.txt | 30 - 开发文档/小程序管理/scripts/mp_api.py | 635 -- 开发文档/小程序管理/scripts/mp_deploy.py | 725 -- 开发文档/小程序管理/scripts/mp_full.py | 555 -- 开发文档/小程序管理/scripts/mp_manager.py | 558 -- .../reports/report_soul-party_20260125_113301.json | 76 - .../reports/report_soul-party_20260125_113423.json | 76 - .../reports/report_soul-party_20260125_113434.json | 76 - .../scripts/reports/summary_20260125_113255.json | 88 - 开发文档/小程序管理/scripts/requirements.txt | 7 - 开发文档/服务器管理/SKILL.md | 314 - .../服务器管理/references/宝塔api接口文档.md | 144 - .../服务器管理/references/常见问题手册.md | 184 - 开发文档/服务器管理/references/端口配置表.md | 64 - .../服务器管理/references/系统架构说明.md | 310 - .../服务器管理/references/部署配置模板.md | 154 - 开发文档/服务器管理/scripts/ssl证书检查.py | 168 - 开发文档/服务器管理/scripts/一键部署.py | 138 - 开发文档/服务器管理/scripts/快速检查服务器.py | 104 - 126 files changed, 12536 insertions(+), 20384 deletions(-) delete mode 100644 .cursorrules create mode 100644 .env.port.example create mode 100644 README-Phase3.md create mode 100644 _tmp_34228_f1906e0fb94e68c4400403820df714df delete mode 100644 miniprogram/.gitignore delete mode 100644 miniprogram/README.md delete mode 100644 miniprogram/assets/icons/home-active.png delete mode 100644 miniprogram/assets/icons/home.png delete mode 100644 miniprogram/assets/icons/match-active.png delete mode 100644 miniprogram/assets/icons/match.png delete mode 100644 miniprogram/assets/icons/my-active.png delete mode 100644 miniprogram/assets/icons/my.png delete mode 100644 miniprogram/project.private.config.json delete mode 100644 miniprogram/utils/payment.js delete mode 100644 miniprogram/utils/util.js delete mode 100644 miniprogram/小程序快速配置指南.md delete mode 100644 miniprogram/小程序部署说明.md delete mode 100644 miniprogram/测试二维码.html delete mode 100644 miniprogram/生成图标.html delete mode 100644 miniprogram/自动部署.sh create mode 100644 scripts/__pycache__/deploy_soul.cpython-311.pyc delete mode 100644 scripts/demo.py create mode 100644 scripts/devlop.py create mode 100644 scripts/merge-kbone-to-miniprogram.js create mode 100644 scripts/start-standalone.js create mode 100644 开发文档/8、部署/Next转小程序Kbone迁移方案.md create mode 100644 开发文档/8、部署/Phase3完成说明.md create mode 100644 开发文档/8、部署/端口配置说明.md delete mode 100644 开发文档/小程序管理/SKILL.md delete mode 100644 开发文档/小程序管理/references/API接口速查表.md delete mode 100644 开发文档/小程序管理/references/企业认证完整指南.md delete mode 100644 开发文档/小程序管理/references/审核规范.md delete mode 100644 开发文档/小程序管理/references/隐私协议填写指南.md delete mode 100644 开发文档/小程序管理/scripts/__pycache__/mp_api.cpython-314.pyc delete mode 100644 开发文档/小程序管理/scripts/apps_config.json delete mode 100644 开发文档/小程序管理/scripts/env_template.txt delete mode 100644 开发文档/小程序管理/scripts/mp_api.py delete mode 100644 开发文档/小程序管理/scripts/mp_deploy.py delete mode 100644 开发文档/小程序管理/scripts/mp_full.py delete mode 100644 开发文档/小程序管理/scripts/mp_manager.py delete mode 100644 开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113301.json delete mode 100644 开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113423.json delete mode 100644 开发文档/小程序管理/scripts/reports/report_soul-party_20260125_113434.json delete mode 100644 开发文档/小程序管理/scripts/reports/summary_20260125_113255.json delete mode 100644 开发文档/小程序管理/scripts/requirements.txt delete mode 100644 开发文档/服务器管理/SKILL.md delete mode 100644 开发文档/服务器管理/references/宝塔api接口文档.md delete mode 100644 开发文档/服务器管理/references/常见问题手册.md delete mode 100644 开发文档/服务器管理/references/端口配置表.md delete mode 100644 开发文档/服务器管理/references/系统架构说明.md delete mode 100644 开发文档/服务器管理/references/部署配置模板.md delete mode 100644 开发文档/服务器管理/scripts/ssl证书检查.py delete mode 100644 开发文档/服务器管理/scripts/一键部署.py delete mode 100644 开发文档/服务器管理/scripts/快速检查服务器.py diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index c619cba0..00000000 --- a/.cursorrules +++ /dev/null @@ -1,83 +0,0 @@ -# v0 Code Generation Rules - Claude Opus - -## Model Selection -- Production components: claude-opus (默认) -- Rapid prototyping: v0-1.5-turbo -- Code review: claude-3.5-sonnet - -## Code Standards -- Framework: Next.js App Router -- Styling: Tailwind CSS v4 -- Components: shadcn/ui -- No placeholders -- Production-ready only - -## Design System -- Use design tokens from globals.css -- Follow color system (3-5 colors max) -- Max 2 font families -- Mobile-first approach -- 所有页面组件保持一致性 -- 使用现有导航系统 -- 遵循毛玻璃设计风格 -- 精简文字,增加流程图 - -## v0 Usage -- 使用 @v0 前缀调用v0生成代码 -- 默认使用 claude-opus 模型 -- 生成前先说明需求,确保理解正确 - ---- - -## 自动部署规则 - -### 服务器信息(小型宝塔) -- **服务器IP**: 42.194.232.22 -- **用户**: root -- **密码**: Zhiqun1984 -- **项目路径**: /www/wwwroot/soul -- **PM2进程名**: soul -- **端口**: 3006 -- **宝塔面板**: https://42.194.232.22:9988/ckbpanel (ckb/zhiqun1984) - -### GitHub仓库 -- **地址**: https://github.com/fnvtk/Mycontent.git -- **分支**: soul-content - -### 小程序 -- **AppID**: wxb8bbb2b10dec74aa -- **项目路径**: ./miniprogram - -### 部署流程(每次提交后自动执行) -1. **提交代码到Git** - ```bash - git add -A - git commit -m "描述" - git push origin soul-content - ``` - -2. **部署到小型宝塔服务器** - ```bash - # 在项目根目录执行(本地打包 + SSH 上传 + 宝塔 API 重启) - pip install -r requirements-deploy.txt - python scripts/devlop.py - ``` - - 详见 `DEPLOYMENT.md`、`开发文档/8、部署/当前项目部署到线上.md` - -4. **上传小程序** - ```bash - # 项目根目录一键上传(将 miniprogram/ 代码完整上传到微信公众平台) - python scripts/autosysc-weixin.py - ``` - - 需先在 miniprogram/ 下放置 private.key(公众号后台「开发设置」→ 小程序代码上传密钥)。详见 `开发文档/8、部署/当前项目部署到线上.md`。 - -5. **打开微信公众平台** - ```bash - open "https://mp.weixin.qq.com/" - ``` - 在「版本管理」设为体验版测试 - -### 注意事项 -- 小程序版本号:未发布前保持 1.14,正式发布后递增 -- 后台部署后需等待约30秒生效 -- 数据库:腾讯云MySQL,读取优先级:数据库 > 本地文件 diff --git a/.env.port.example b/.env.port.example new file mode 100644 index 00000000..3e5c61ba --- /dev/null +++ b/.env.port.example @@ -0,0 +1,32 @@ +# 端口配置示例 +# 复制此文件为 .env 并根据实际情况修改 + +# ======================================== +# 应用端口配置(避免多项目端口冲突) +# ======================================== + +# 方式1: 本地开发启动(pnpm start) +# 在终端中设置: +# Windows PowerShell: $env:PORT=3006; pnpm start +# Windows CMD: set PORT=3006 && pnpm start +# Linux/Mac: PORT=3006 pnpm start + +# 方式2: Docker Compose 部署 +# 设置 APP_PORT 变量,容器内外端口都使用此值 +APP_PORT=3006 + +# 方式3: Docker 直接运行 +# docker run -e PORT=3007 -p 3007:3007 soul-book + +# ======================================== +# 多项目端口规划建议 +# ======================================== +# soul-book: 3006 +# other-project: 3007 +# api-service: 3008 +# ... + +# 注意: +# 1. 修改端口后,需要同步更新支付回调地址等配置 +# 2. 部署到宝塔面板时,deploy_soul.py 会使用配置的端口 +# 3. 确保防火墙和反向代理配置正确 diff --git a/.gitignore b/.gitignore index cd6c6f9d..ba64f304 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ node_modules/ .trae/ *.log node_modules +miniprogram +my-app +newpp # 部署配置(含服务器信息,勿提交) deploy_config.json diff --git a/Dockerfile b/Dockerfile index 6017696a..726d74fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,9 +54,10 @@ COPY --from=builder /app/.next/static ./.next/static USER nextjs -EXPOSE 3000 +# 端口由环境变量指定,不设默认值避免冲突 +# 部署时通过 docker run -e PORT=xxxx 或 docker-compose 设置 +EXPOSE ${PORT:-3006} -ENV PORT 3000 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"] diff --git a/README-Phase3.md b/README-Phase3.md new file mode 100644 index 00000000..59b6ee02 --- /dev/null +++ b/README-Phase3.md @@ -0,0 +1,96 @@ +# Phase 3 完成总结 + +## 概述 + +Phase 3 成功将 Next.js 的"我的"页及所有子页完整迁移到 Kbone 小程序,并实现了 Zustand 状态管理的跨端适配。 + +--- + +## 完成的核心功能 + +### 1. 状态管理(Zustand + Storage 适配) + +创建了 `newpp/src/store/index.js`,实现: + +- **用户状态**:user、isLoggedIn、logout、setUser、updateUser +- **购买逻辑**:hasPurchased、addPurchase、purchaseFullBook +- **持久化**:用 `adapters/storage.js` 适配小程序 wx.storage 和 Web localStorage + +### 2. 我的页面(登录态 + 统计 + 菜单) + +- 未登录:登录提示、统计占位 +- 已登录:用户卡片、收益卡片、Tab 切换(概览/我的足迹)、菜单(订单、推广、关于、设置) + +### 3. 推广页(邀请码 + 收益) + +- 收益概览:待领收益、累计收益、已提现 +- 邀请码展示与复制 +- 推广数据与规则说明 + +### 4. 设置页 + +- 账号信息展示 +- 通用设置 +- 退出登录 + +### 5. 购买记录页 + +- 订单列表(暂无数据占位) + +### 6. 关于页 + +- 项目介绍、数据统计、联系方式 + +--- + +## 文件清单 + +**页面组件**: +- `src/pages/MyPage.jsx` +- `src/pages/ReferralPage.jsx` +- `src/pages/SettingsPage.jsx` +- `src/pages/PurchasesPage.jsx` +- `src/pages/AboutPage.jsx` + +**入口文件**: +- `src/my.jsx` +- `src/referral.jsx` +- `src/settings.jsx` +- `src/purchases.jsx` +- `src/about.jsx` + +**状态管理**: +- `src/store/index.js` + +**配置更新**: +- `build/webpack.mp.config.js`(新增 5 个入口) +- `build/miniprogram.config.js`(router.other 新增 5 个路由) + +--- + +## 测试与验收 + +1. **构建**:`cd newpp && npm run build:mp` +2. **合并**:`node scripts/merge-kbone-to-miniprogram.js` +3. **测试路径**: + - 首页 → 底部"我的" → 查看用户卡片 + 统计 + - 我的 → 推广中心 → 查看邀请码 + 收益 + - 我的 → 设置 → 查看账号信息 + - 我的 → 我的订单 → 查看空态 + - 我的 → 关于我们 → 查看项目介绍 + +--- + +## 当前进度 + +- ✅ **Phase 1**:搭架子(适配层、构建、首页/目录/阅读占位) +- ✅ **Phase 2**:核心页(阅读页接口、ChapterContent、完整目录) +- ✅ **Phase 3**:我的与子页(Zustand、我的、推广、设置、购买记录、关于) +- ⏳ **Phase 4**:找伙伴与其余(match、search、底部 tabBar、安全区) +- ⏳ **Phase 5**:收尾(全量自检、样式对齐、发布流程) + +--- + +## 下一步 + +进入 Phase 4,迁移找伙伴、搜索,并实现底部 tabBar 与安全区适配。 diff --git a/_tmp_34228_f1906e0fb94e68c4400403820df714df b/_tmp_34228_f1906e0fb94e68c4400403820df714df new file mode 100644 index 00000000..e69de29b diff --git a/app/layout.tsx b/app/layout.tsx index d8ae77ac..28d8a5d9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -41,7 +41,7 @@ export default function RootLayout({ {children} - + {process.env.NODE_ENV === 'production' && } ) diff --git a/app/match/page.tsx b/app/match/page.tsx index 08ada9af..bf72d24d 100644 --- a/app/match/page.tsx +++ b/app/match/page.tsx @@ -2,9 +2,9 @@ import { useState, useEffect } from "react" import { motion, AnimatePresence } from "framer-motion" -import { Users, X, CheckCircle, Loader2, Lock, Zap, Gift } from "lucide-react" -import { Home, List, User } from "lucide-react" +import { Users, X, CheckCircle, Loader2, Lock, Zap } from "lucide-react" import { useRouter } from "next/navigation" +import { BottomNav } from "@/components/bottom-nav" import { useStore } from "@/lib/store" interface MatchUser { @@ -122,8 +122,7 @@ export default function MatchPage() { setTodayMatchCount(getTodayMatchCount()) }, [user]) - if (!mounted) return null // 彻底解决 Hydration 错误 - + // 处理函数定义(必须在所有 hooks 之后) const handleJoinClick = (typeId: string) => { setJoinType(typeId) setShowJoinModal(true) @@ -333,6 +332,9 @@ export default function MatchPage() { const currentMatchLabel = currentType?.matchLabel || "创业伙伴" const joinTypeLabel = matchTypes.find((t) => t.id === joinType)?.matchLabel || "" + // 等待挂载完成(必须在所有 hooks 和函数定义之后) + if (!mounted) return null + return (
@@ -843,31 +845,7 @@ export default function MatchPage() { )} - +
) } diff --git a/app/my/page.tsx b/app/my/page.tsx index a478bcf0..b9d40f39 100644 --- a/app/my/page.tsx +++ b/app/my/page.tsx @@ -14,6 +14,7 @@ export default function MyPage() { const [showAuthModal, setShowAuthModal] = useState(false) const [mounted, setMounted] = useState(false) const [activeTab, setActiveTab] = useState<"overview" | "footprint">("overview") + const [matchEnabled, setMatchEnabled] = useState(false) // 匹配功能是否启用 // 绑定弹窗状态 const [showBindModal, setShowBindModal] = useState(false) @@ -22,21 +23,29 @@ export default function MyPage() { const [isBinding, setIsBinding] = useState(false) const [bindError, setBindError] = useState("") - useEffect(() => { - setMounted(true) - }, []) - - if (!mounted) { - return ( -
-
-
- ) - } - + // 计算数据(必须在所有 hooks 之后) const totalSections = getTotalSectionCount() const purchasedCount = user?.hasFullBook ? totalSections : user?.purchasedSections?.length || 0 + useEffect(() => { + setMounted(true) + + // 加载功能配置 + const loadConfig = async () => { + try { + const res = await fetch('/api/db/config') + const data = await res.json() + if (data.features) { + setMatchEnabled(data.features.matchEnabled === true) + } + } catch (e) { + console.log('Load feature config error:', e) + setMatchEnabled(false) + } + } + loadConfig() + }, []) + // 绑定账号 const handleBind = async () => { if (!bindValue.trim()) { @@ -93,6 +102,15 @@ export default function MyPage() { setShowBindModal(true) } + // 等待挂载完成 + if (!mounted) { + return ( +
+
+
+ ) + } + // 未登录状态 if (!isLoggedIn) { return ( @@ -435,23 +453,25 @@ export default function MyPage() { )}
- {/* 匹配记录 */} -
-

- - 匹配记录 -

-
- -

暂无匹配记录

- + {/* 匹配记录 - 根据配置显示 */} + {matchEnabled && ( +
+

+ + 匹配记录 +

+
+ +

暂无匹配记录

+ +
-
+ )}
)} diff --git a/app/page.tsx b/app/page.tsx index f1c27421..48c4ced8 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -19,14 +19,11 @@ export default function HomePage() { const [mounted, setMounted] = useState(false) const [searchOpen, setSearchOpen] = useState(false) + // 计算数据(必须在所有 hooks 之后) const totalSections = getTotalSectionCount() const hasFullBook = user?.hasFullBook || false const purchasedCount = hasFullBook ? totalSections : user?.purchasedSections?.length || 0 - useEffect(() => { - setMounted(true) - }, []) - // 推荐章节 const featuredSections = [ { id: "1.1", title: "荷包:电动车出租的被动收入模式", tag: "免费", part: "真实的人" }, @@ -41,6 +38,10 @@ export default function HomePage() { part: "真实的赚钱", } + useEffect(() => { + setMounted(true) + }, []) + if (!mounted) { return null } diff --git a/components/bottom-nav.tsx b/components/bottom-nav.tsx index 826078d1..7631116e 100644 --- a/components/bottom-nav.tsx +++ b/components/bottom-nav.tsx @@ -10,17 +10,7 @@ export function BottomNav() { const [matchEnabled, setMatchEnabled] = useState(false) // 默认隐藏,等配置加载后再显示 const [configLoaded, setConfigLoaded] = useState(false) // 配置是否已加载 - // 在文档页面、管理后台、阅读页面和关于页面不显示底部导航 - if ( - pathname.startsWith("/documentation") || - pathname.startsWith("/admin") || - pathname.startsWith("/read") || - pathname.startsWith("/about") - ) { - return null - } - - // 加载功能配置 + // 加载功能配置(必须在所有条件判断之前) useEffect(() => { const loadConfig = async () => { try { @@ -41,6 +31,16 @@ export function BottomNav() { loadConfig() }, []) + // 在文档页面、管理后台、阅读页面和关于页面不显示底部导航(必须在所有 hooks 之后) + if ( + pathname.startsWith("/documentation") || + pathname.startsWith("/admin") || + pathname.startsWith("/read") || + pathname.startsWith("/about") + ) { + return null + } + const navItems = [ { href: "/", icon: Home, label: "首页" }, { href: "/chapters", icon: List, label: "目录" }, diff --git a/docker-compose.yml b/docker-compose.yml index 6749ee09..7a8efb56 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,31 +8,32 @@ services: container_name: soul_book_app restart: always ports: - - "3000:3000" + - "${APP_PORT:-3006}:${APP_PORT:-3006}" environment: - NODE_ENV=production - NEXT_TELEMETRY_DISABLED=1 + - PORT=${APP_PORT:-3006} # 支付宝配置 - 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:3000/payment/success} - - ALIPAY_NOTIFY_URL=${ALIPAY_NOTIFY_URL:-http://192.168.2.201:3000/api/payment/alipay/notify} + - 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} # 微信支付配置 - 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:3000/api/payment/wechat/notify} + - WECHAT_NOTIFY_URL=${WECHAT_NOTIFY_URL:-http://192.168.2.201:${APP_PORT:-3006}/api/payment/wechat/notify} # 基础配置 - - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:-http://192.168.2.201:3000} + - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:-http://192.168.2.201:${APP_PORT:-3006}} volumes: - ./book:/app/book:ro - ./public:/app/public:ro networks: - nas-network healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000"] + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:${APP_PORT:-3006}"] interval: 30s timeout: 10s retries: 3 diff --git a/lib/db.ts b/lib/db.ts index 3f740964..6db6bd4b 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -1,31 +1,47 @@ /** * 数据库连接配置 * 使用MySQL数据库存储用户、订单、推广关系等数据 + * 优先从环境变量读取,便于本地/部署分离;未设置时使用默认值 */ import mysql from 'mysql2/promise' -// 腾讯云外网数据库配置 const DB_CONFIG = { - host: '56b4c23f6853c.gz.cdb.myqcloud.com', - port: 14413, - user: 'cdb_outerroot', - password: 'Zhiqun1984', - database: 'soul_miniprogram', + host: process.env.MYSQL_HOST || '56b4c23f6853c.gz.cdb.myqcloud.com', + port: Number(process.env.MYSQL_PORT || '14413'), + user: process.env.MYSQL_USER || 'cdb_outerroot', + password: process.env.MYSQL_PASSWORD || 'Zhiqun1984', + database: process.env.MYSQL_DATABASE || 'soul_miniprogram', charset: 'utf8mb4', timezone: '+08:00', - acquireTimeout: 60000, - timeout: 60000, + connectTimeout: 10000, // 10 秒,连接不可达时快速失败,避免长时间挂起 + acquireTimeout: 15000, reconnect: true } +// 本地无数据库时可通过 SKIP_DB=1 跳过连接,接口将使用默认配置 +const SKIP_DB = process.env.SKIP_DB === '1' || process.env.SKIP_DB === 'true' + // 连接池 let pool: mysql.Pool | null = null +// 连接类错误只打一次日志,避免刷屏 +let connectionErrorLogged = false + +function isConnectionError(err: unknown): boolean { + const code = (err as NodeJS.ErrnoException)?.code + return ( + code === 'ETIMEDOUT' || + code === 'ECONNREFUSED' || + code === 'PROTOCOL_CONNECTION_LOST' || + code === 'ENOTFOUND' + ) +} /** - * 获取数据库连接池 + * 获取数据库连接池(SKIP_DB 时不创建) */ -export function getPool() { +export function getPool(): mysql.Pool | null { + if (SKIP_DB) return null if (!pool) { pool = mysql.createPool({ ...DB_CONFIG, @@ -41,12 +57,25 @@ export function getPool() { * 执行SQL查询 */ export async function query(sql: string, params?: any[]) { + const connection = getPool() + if (!connection) { + throw new Error('数据库未配置或已跳过 (SKIP_DB)') + } try { - const connection = getPool() const [results] = await connection.execute(sql, params) return results } catch (error) { - console.error('数据库查询错误:', error) + if (isConnectionError(error)) { + if (!connectionErrorLogged) { + connectionErrorLogged = true + console.warn( + '[DB] 数据库连接不可用,将使用本地默认配置。', + (error as Error).message + ) + } + } else { + console.error('数据库查询错误:', error) + } throw error } } @@ -295,6 +324,7 @@ async function initDefaultConfig() { /** * 获取系统配置 + * 连接不可达时返回 null,由上层使用本地默认配置,不重复打日志 */ export async function getConfig(key: string) { try { @@ -308,7 +338,9 @@ export async function getConfig(key: string) { } return null } catch (error) { - console.error('获取配置失败:', error) + if (!isConnectionError(error)) { + console.error('获取配置失败:', error) + } return null } } diff --git a/miniprogram/.gitignore b/miniprogram/.gitignore deleted file mode 100644 index 50bae59b..00000000 --- a/miniprogram/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# 小程序上传密钥(敏感信息,请勿上传) -private.key -private.*.key - -# 预览二维码 -preview.jpg - -# 微信开发者工具生成的文件 -.DS_Store -node_modules/ diff --git a/miniprogram/README.md b/miniprogram/README.md deleted file mode 100644 index 2cc11dbf..00000000 --- a/miniprogram/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# Soul创业实验 - 微信小程序 - -> 一场SOUL的创业实验场 - 来自Soul派对房的真实商业故事 - -## 📱 项目简介 - -本项目是《一场SOUL的创业实验场》的微信小程序版本,完整还原了Web端的所有UI界面和功能。 - -## 🎨 设计特点 - -- **主题色**: Soul青色 (#00CED1) -- **设计风格**: 深色主题 + 毛玻璃效果 -- **1:1还原**: 完全复刻Web端的UI设计 - -## 📂 项目结构 - -``` -miniprogram/ -├── app.js # 应用入口 -├── app.json # 应用配置 -├── app.wxss # 全局样式 -├── custom-tab-bar/ # 自定义TabBar组件 -│ ├── index.js -│ ├── index.json -│ ├── index.wxml -│ └── index.wxss -├── pages/ -│ ├── index/ # 首页 -│ ├── chapters/ # 目录页 -│ ├── match/ # 找伙伴页 -│ ├── my/ # 我的页面 -│ ├── read/ # 阅读页 -│ ├── about/ # 关于作者 -│ ├── referral/ # 推广中心 -│ ├── purchases/ # 订单页 -│ └── settings/ # 设置页 -├── utils/ -│ ├── util.js # 工具函数 -│ └── payment.js # 支付工具 -├── assets/ -│ └── icons/ # 图标资源 -├── project.config.json # 项目配置 -└── sitemap.json # 站点地图 -``` - -## 🚀 功能列表 - -### 核心功能 -- ✅ 首页 - 书籍展示、推荐章节、阅读进度 -- ✅ 目录 - 完整章节列表、篇章折叠展开 -- ✅ 找伙伴 - 匹配动画、匹配类型选择 -- ✅ 我的 - 个人信息、订单、推广中心 -- ✅ 阅读 - 付费墙、章节导航、分享功能 - -### 特色功能 -- ✅ 自定义TabBar(中间突出的找伙伴按钮) -- ✅ 阅读进度条 -- ✅ 匹配动画效果 -- ✅ 付费墙与购买流程 -- ✅ 分享海报功能 -- ✅ 推广佣金系统 - -## 🛠 开发指南 - -### 环境要求 -- 微信开发者工具 >= 1.06.2308310 -- 基础库版本 >= 3.3.4 - -### 快速开始 - -1. **下载微信开发者工具** - - 前往 [微信开发者工具下载页面](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html) - -2. **导入项目** - - 打开微信开发者工具 - - 选择"导入项目" - - 项目目录选择 `miniprogram` 文件夹 - - AppID 使用: `wx432c93e275548671` - -3. **编译运行** - - 点击"编译"按钮 - - 在模拟器中预览效果 - -### 真机调试 - -1. 点击工具栏的"预览"按钮 -2. 使用微信扫描二维码 -3. 在真机上测试所有功能 - -## 📝 配置说明 - -### API配置 -在 `app.js` 中修改 `globalData.baseUrl`: - -```javascript -globalData: { - baseUrl: 'https://soul.ckb.fit', // 你的API地址 - // ... -} -``` - -### AppID配置 -在 `project.config.json` 中修改: - -```json -{ - "appid": "你的小程序AppID" -} -``` - -## 🎯 上线发布 - -1. **准备工作** - - 确保所有功能测试通过 - - 检查API接口是否正常 - - 确认支付功能已配置 - -2. **上传代码** - - 在开发者工具中点击"上传" - - 填写版本号和项目备注 - -3. **提交审核** - - 登录[微信公众平台](https://mp.weixin.qq.com) - - 进入"版本管理" - - 提交审核 - -4. **发布上线** - - 审核通过后点击"发布" - -## 🔗 相关链接 - -- **Web版本**: https://soul.ckb.fit -- **作者微信**: 28533368 -- **技术支持**: 存客宝 - -## 📄 版权信息 - -© 2024 卡若. All rights reserved. diff --git a/miniprogram/app.js b/miniprogram/app.js index e0c3901b..a3dd71a6 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -1,36 +1,17 @@ /** * Soul创业派对 - 小程序入口 - * 开发: 卡若 */ App({ globalData: { - // API基础地址 - 连接真实后端 baseUrl: 'https://soul.quwanzhi.com', - - // 小程序配置 - 真实AppID appId: 'wxb8bbb2b10dec74aa', - - // 微信支付配置 - mchId: '1318592501', // 商户号 - - // 用户信息 userInfo: null, - openId: null, // 微信openId,支付必需 + openId: null, isLoggedIn: false, - - // 书籍数据 - bookData: null, - totalSections: 62, - - // 购买记录 purchasedSections: [], hasFullBook: false, - - // 推荐绑定 - pendingReferralCode: null, // 待绑定的推荐码 - - // 主题配置 + pendingReferralCode: null, theme: { brandColor: '#00CED1', brandSecondary: '#20B2AA', @@ -38,149 +19,88 @@ App({ bgColor: '#000000', cardBg: '#1c1c1e' }, - - // 系统信息 systemInfo: null, statusBarHeight: 44, navBarHeight: 88, - - // TabBar相关 - currentTab: 0 + capsulePaddingRight: 0, + currentTab: 0, + features: null, + matchEnabled: false, + _featureConfigLastFetch: 0 }, onLaunch(options) { - // 获取系统信息 - this.getSystemInfo() - - // 检查登录状态 - this.checkLoginStatus() - - // 加载书籍数据 - this.loadBookData() - - // 检查更新 - this.checkUpdate() - - // 处理分享参数(推荐码绑定) - this.handleReferralCode(options) - }, - - // 小程序显示时也检查分享参数 - onShow(options) { - this.handleReferralCode(options) - }, - - // 处理推荐码绑定 - handleReferralCode(options) { - const query = options?.query || {} - const refCode = query.ref || query.referralCode - - if (refCode) { - console.log('[App] 检测到推荐码:', refCode) - - // 立即记录访问(不需要登录,用于统计"通过链接进的人数") - this.recordReferralVisit(refCode) - - // 检查是否已经绑定过 - const boundRef = wx.getStorageSync('boundReferralCode') - if (boundRef && boundRef !== refCode) { - console.log('[App] 已绑定过其他推荐码,不更换绑定关系') - // 但仍然记录访问,不return - } else { - // 保存待绑定的推荐码 - this.globalData.pendingReferralCode = refCode - wx.setStorageSync('pendingReferralCode', refCode) - - // 如果已登录,立即绑定 - if (this.globalData.isLoggedIn && this.globalData.userInfo) { - this.bindReferralCode(refCode) - } - } - } - }, - - // 记录推荐访问(不需要登录,用于统计) - async recordReferralVisit(refCode) { try { - // 获取openId(如果有) - const openId = this.globalData.openId || wx.getStorageSync('openId') || '' - const userId = this.globalData.userInfo?.id || '' - - await this.request('/api/referral/visit', { - method: 'POST', - data: { - referralCode: refCode, - visitorOpenId: openId, - visitorId: userId, - source: 'miniprogram', - page: getCurrentPages()[getCurrentPages().length - 1]?.route || '' - } - }) - console.log('[App] 记录推荐访问成功') + this.getSystemInfo() + this.checkLoginStatus() + this.handleReferralCode(options) + // 异步请求不阻塞启动,失败也不影响模拟器启动(loadBookData 内部已 catch) + this.loadFeatureConfig().catch(() => {}) + this.loadBookData() } catch (e) { - console.log('[App] 记录推荐访问失败:', e.message) - // 忽略错误,不影响用户体验 - } - }, - - // 绑定推荐码到用户 - async bindReferralCode(refCode) { - try { - const userId = this.globalData.userInfo?.id - if (!userId || !refCode) return - - // 检查是否已绑定 - const boundRef = wx.getStorageSync('boundReferralCode') - if (boundRef) { - console.log('[App] 已绑定推荐码,跳过') - return - } - - console.log('[App] 绑定推荐码:', refCode, '到用户:', userId) - - // 调用API绑定推荐关系 - const res = await this.request('/api/referral/bind', { - method: 'POST', - data: { - userId, - referralCode: refCode - } - }) - - if (res.success) { - console.log('[App] 推荐码绑定成功') - wx.setStorageSync('boundReferralCode', refCode) - this.globalData.pendingReferralCode = null - wx.removeStorageSync('pendingReferralCode') - } - } catch (e) { - console.error('[App] 绑定推荐码失败:', e) + console.error('[App] onLaunch error', e) + } + }, + + onShow(options) { + this.handleReferralCode(options) + this.loadFeatureConfig() + }, + + loadFeatureConfig(forceRefresh) { + const now = Date.now() + const throttleMs = 15000 + if (!forceRefresh && this.globalData._featureConfigLastFetch && (now - this.globalData._featureConfigLastFetch < throttleMs)) { + return Promise.resolve(this.globalData.features) + } + return this.request('/api/db/config') + .then((res) => { + if (res && res.features) { + this.globalData.features = res.features + this.globalData.matchEnabled = res.features.matchEnabled === true + this.globalData._featureConfigLastFetch = Date.now() + return this.globalData.features + } + return this.globalData.features + }) + .catch((e) => { + console.log('[App] 加载功能配置失败', e) + return this.globalData.features + }) + }, + + handleReferralCode(options) { + const query = options?.query || {} + const refCode = query.ref || query.referralCode + if (refCode) { + this.globalData.pendingReferralCode = refCode + wx.setStorageSync('pendingReferralCode', refCode) } }, - // 获取系统信息 getSystemInfo() { try { const systemInfo = wx.getSystemInfoSync() this.globalData.systemInfo = systemInfo - this.globalData.statusBarHeight = systemInfo.statusBarHeight || 44 - - // 计算导航栏高度 + const statusBarHeight = systemInfo.statusBarHeight || 44 + this.globalData.statusBarHeight = statusBarHeight const menuButton = wx.getMenuButtonBoundingClientRect() - if (menuButton) { - this.globalData.navBarHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.height + systemInfo.statusBarHeight + if (menuButton && menuButton.top != null) { + this.globalData.navBarHeight = (menuButton.top - statusBarHeight) * 2 + menuButton.height + statusBarHeight + const w = systemInfo.windowWidth || 375 + this.globalData.capsulePaddingRight = Math.ceil(w - menuButton.left + 8) + } else { + this.globalData.navBarHeight = statusBarHeight + 44 } } catch (e) { - console.error('获取系统信息失败:', e) + console.error('获取系统信息失败', e) } }, - // 检查登录状态 checkLoginStatus() { try { const userInfo = wx.getStorageSync('userInfo') const token = wx.getStorageSync('token') - if (userInfo && token) { this.globalData.userInfo = userInfo this.globalData.isLoggedIn = true @@ -188,69 +108,27 @@ App({ this.globalData.hasFullBook = userInfo.hasFullBook || false } } catch (e) { - console.error('检查登录状态失败:', e) + console.error('检查登录状态失败', e) } }, - // 加载书籍数据 - async loadBookData() { - try { - // 先从缓存加载 - const cachedData = wx.getStorageSync('bookData') - if (cachedData) { - this.globalData.bookData = cachedData - } - - // 从服务器获取最新数据 - const res = await this.request('/api/book/all-chapters') - if (res && res.data) { - this.globalData.bookData = res.data - wx.setStorageSync('bookData', res.data) - } - } catch (e) { - console.error('加载书籍数据失败:', e) - } - }, - - // 检查更新 - checkUpdate() { - if (wx.canIUse('getUpdateManager')) { - const updateManager = wx.getUpdateManager() - - updateManager.onCheckForUpdate((res) => { - if (res.hasUpdate) { - console.log('发现新版本') + loadBookData() { + this.request('/api/book/all-chapters') + .then((res) => { + if (res && res.data) { + this.globalData.bookData = res.data + wx.setStorageSync('bookData', res.data) } }) - - updateManager.onUpdateReady(() => { - wx.showModal({ - title: '更新提示', - content: '新版本已准备好,是否重启应用?', - success: (res) => { - if (res.confirm) { - updateManager.applyUpdate() - } - } - }) - }) - - updateManager.onUpdateFailed(() => { - wx.showToast({ - title: '更新失败,请稍后重试', - icon: 'none' - }) - }) - } + .catch((e) => console.error('加载书籍数据失败', e)) }, - // 统一请求方法 request(url, options = {}) { return new Promise((resolve, reject) => { const token = wx.getStorageSync('token') - + const fullUrl = this.globalData.baseUrl + url wx.request({ - url: this.globalData.baseUrl + url, + url: fullUrl, method: options.method || 'GET', data: options.data || {}, header: { @@ -259,206 +137,52 @@ App({ ...options.header }, success: (res) => { - if (res.statusCode === 200) { - resolve(res.data) - } else if (res.statusCode === 401) { - // 未授权,清除登录状态 + if (res.statusCode === 200) resolve(res.data) + else if (res.statusCode === 401) { this.logout() reject(new Error('未授权')) - } else { - reject(new Error(res.data?.message || '请求失败')) - } + } else reject(new Error(res.data?.message || '请求失败')) }, fail: (err) => { - reject(err) + console.warn('[Request] fail', fullUrl, err) + reject(err && err.errMsg ? new Error(err.errMsg) : new Error('网络请求失败')) } }) }) }, - // 登录方法 - 获取openId用于支付 async login() { try { - // 获取微信登录code - const loginRes = await new Promise((resolve, reject) => { - wx.login({ - success: resolve, - fail: reject - }) - }) - - console.log('[App] 获取登录code成功') - - try { - // 发送code到服务器获取openId - const res = await this.request('/api/miniprogram/login', { - method: 'POST', - data: { code: loginRes.code } - }) - - if (res.success && res.data) { - // 保存openId - if (res.data.openId) { - this.globalData.openId = res.data.openId - wx.setStorageSync('openId', res.data.openId) - console.log('[App] 获取openId成功') - } - - // 保存用户信息 - if (res.data.user) { - this.globalData.userInfo = res.data.user - this.globalData.isLoggedIn = true - this.globalData.purchasedSections = res.data.user.purchasedSections || [] - this.globalData.hasFullBook = res.data.user.hasFullBook || false - - wx.setStorageSync('userInfo', res.data.user) - wx.setStorageSync('token', res.data.token || '') - - // 登录成功后,检查待绑定的推荐码并执行绑定 - const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode - if (pendingRef) { - console.log('[App] 登录后自动绑定推荐码:', pendingRef) - this.bindReferralCode(pendingRef) - } - } - - return res.data + const loginRes = await new Promise((resolve, reject) => wx.login({ success: resolve, fail: reject })) + const res = await this.request('/api/miniprogram/login', { method: 'POST', data: { code: loginRes.code } }) + if (res && res.success && res.data) { + if (res.data.openId) { + this.globalData.openId = res.data.openId + wx.setStorageSync('openId', res.data.openId) } - } catch (apiError) { - console.log('[App] API登录失败:', apiError.message) - // 不使用模拟登录,提示用户网络问题 - wx.showToast({ title: '网络异常,请重试', icon: 'none' }) - return null - } - - return null - } catch (e) { - console.error('[App] 登录失败:', e) - wx.showToast({ title: '登录失败,请重试', icon: 'none' }) - return null - } - }, - - // 获取openId (支付必需) - async getOpenId() { - // 先检查缓存 - const cachedOpenId = wx.getStorageSync('openId') - if (cachedOpenId) { - this.globalData.openId = cachedOpenId - return cachedOpenId - } - - // 没有缓存则登录获取 - try { - const loginRes = await new Promise((resolve, reject) => { - wx.login({ success: resolve, fail: reject }) - }) - - const res = await this.request('/api/miniprogram/login', { - method: 'POST', - data: { code: loginRes.code } - }) - - if (res.success && res.data?.openId) { - this.globalData.openId = res.data.openId - wx.setStorageSync('openId', res.data.openId) - return res.data.openId - } - } catch (e) { - console.error('[App] 获取openId失败:', e) - } - - return null - }, - - // 模拟登录已废弃 - 不再使用 - // 现在必须使用真实的微信登录获取openId作为唯一标识 - mockLogin() { - console.warn('[App] mockLogin已废弃,请使用真实登录') - return null - }, - - // 手机号登录 - async loginWithPhone(phoneCode) { - try { - // 尝试API登录 - const res = await this.request('/api/wechat/phone-login', { - method: 'POST', - data: { code: phoneCode } - }) - - if (res.success && res.data) { - this.globalData.userInfo = res.data.user - this.globalData.isLoggedIn = true - this.globalData.purchasedSections = res.data.user.purchasedSections || [] - this.globalData.hasFullBook = res.data.user.hasFullBook || false - - wx.setStorageSync('userInfo', res.data.user) - wx.setStorageSync('token', res.data.token) - - // 登录成功后绑定推荐码 - const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode - if (pendingRef) { - console.log('[App] 手机号登录后自动绑定推荐码:', pendingRef) - this.bindReferralCode(pendingRef) + if (res.data.user) { + this.globalData.userInfo = res.data.user + this.globalData.isLoggedIn = true + this.globalData.purchasedSections = res.data.user.purchasedSections || [] + this.globalData.hasFullBook = res.data.user.hasFullBook || false + wx.setStorageSync('userInfo', res.data.user) + wx.setStorageSync('token', res.data.token || '') } - return res.data } } catch (e) { - console.log('[App] 手机号登录失败:', e) + console.error('登录失败', e) wx.showToast({ title: '登录失败,请重试', icon: 'none' }) } - return null }, - // 退出登录 logout() { this.globalData.userInfo = null this.globalData.isLoggedIn = false this.globalData.purchasedSections = [] this.globalData.hasFullBook = false - wx.removeStorageSync('userInfo') wx.removeStorageSync('token') - }, - - // 检查是否已购买章节 - hasPurchased(sectionId) { - if (this.globalData.hasFullBook) return true - return this.globalData.purchasedSections.includes(sectionId) - }, - - // 获取章节总数 - getTotalSections() { - return this.globalData.totalSections - }, - - // 切换TabBar - switchTab(index) { - this.globalData.currentTab = index - }, - - // 显示Toast - showToast(title, icon = 'none') { - wx.showToast({ - title, - icon, - duration: 2000 - }) - }, - - // 显示Loading - showLoading(title = '加载中...') { - wx.showLoading({ - title, - mask: true - }) - }, - - // 隐藏Loading - hideLoading() { - wx.hideLoading() } }) diff --git a/miniprogram/app.json b/miniprogram/app.json index 31f336b6..30ecf360 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -28,34 +28,18 @@ "backgroundColor": "#1c1c1e", "borderStyle": "black", "list": [ - { - "pagePath": "pages/index/index", - "text": "首页" - }, - { - "pagePath": "pages/chapters/chapters", - "text": "目录" - }, - { - "pagePath": "pages/match/match", - "text": "找伙伴" - }, - { - "pagePath": "pages/my/my", - "text": "我的" - } + { "pagePath": "pages/index/index", "text": "首页" }, + { "pagePath": "pages/chapters/chapters", "text": "目录" }, + { "pagePath": "pages/match/match", "text": "找伙伴" }, + { "pagePath": "pages/my/my", "text": "我的" } ] }, "usingComponents": {}, "__usePrivacyCheck__": true, "permission": { - "scope.userLocation": { - "desc": "用于匹配附近的书友" - } + "scope.userLocation": { "desc": "用于匹配附近的书友" } }, - "requiredPrivateInfos": [ - "getLocation" - ], + "requiredPrivateInfos": ["getLocation"], "lazyCodeLoading": "requiredComponents", "style": "v2", "sitemapLocation": "sitemap.json" diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss index 4a79d19f..c2b3ac45 100644 --- a/miniprogram/app.wxss +++ b/miniprogram/app.wxss @@ -1,568 +1,12 @@ -/** - * Soul创业实验 - 全局样式 - * 主题色: #00CED1 (Soul青色) - * 开发: 卡若 - */ - -/* ===== 页面基础样式 ===== */ +/** 全局样式 - 与 Web globals 一致 */ page { - background-color: #000000; - color: #ffffff; - font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', 'Microsoft YaHei', sans-serif; - font-size: 28rpx; - line-height: 1.5; - -webkit-font-smoothing: antialiased; -} - -/* ===== 全局容器 ===== */ -.container { - min-height: 100vh; - padding: 0; background: #000000; - padding-bottom: env(safe-area-inset-bottom); -} - -/* ===== 品牌色系 ===== */ -.brand-color { - color: #00CED1; -} - -.brand-bg { - background-color: #00CED1; -} - -.brand-gradient { - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); -} - -.gold-color { - color: #FFD700; -} - -.gold-bg { - background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); -} - -/* ===== 文字渐变 ===== */ -.gradient-text { - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.gold-gradient-text { - background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -/* ===== 按钮样式 ===== */ -.btn-primary { - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #ffffff; - border: none; - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; - font-weight: 600; - box-shadow: 0 8rpx 24rpx rgba(0, 206, 209, 0.3); - display: flex; - align-items: center; - justify-content: center; -} - -.btn-primary::after { - border: none; -} - -.btn-primary:active { - opacity: 0.85; - transform: scale(0.98); -} - -.btn-secondary { - background: rgba(0, 206, 209, 0.1); - color: #00CED1; - border: 2rpx solid rgba(0, 206, 209, 0.3); - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; - font-weight: 500; -} - -.btn-secondary::after { - border: none; -} - -.btn-secondary:active { - background: rgba(0, 206, 209, 0.2); -} - -.btn-ghost { - background: rgba(255, 255, 255, 0.05); - color: #ffffff; - border: 2rpx solid rgba(255, 255, 255, 0.1); - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; -} - -.btn-ghost::after { - border: none; -} - -.btn-ghost:active { - background: rgba(255, 255, 255, 0.1); -} - -.btn-gold { - background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); - color: #000000; - border: none; - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; - font-weight: 600; - box-shadow: 0 8rpx 24rpx rgba(255, 215, 0, 0.3); -} - -.btn-gold::after { - border: none; -} - -/* ===== 卡片样式 ===== */ -.card { - background: rgba(28, 28, 30, 0.9); - border-radius: 32rpx; - padding: 32rpx; - margin: 24rpx 32rpx; - border: 2rpx solid rgba(255, 255, 255, 0.05); -} - -.card-light { - background: rgba(44, 44, 46, 0.8); - border-radius: 24rpx; - padding: 24rpx; - border: 2rpx solid rgba(255, 255, 255, 0.08); -} - -.card-gradient { - background: linear-gradient(135deg, rgba(28, 28, 30, 1) 0%, rgba(44, 44, 46, 1) 100%); - border-radius: 32rpx; - padding: 32rpx; - border: 2rpx solid rgba(0, 206, 209, 0.2); -} - -.card-brand { - background: linear-gradient(135deg, rgba(0, 206, 209, 0.1) 0%, rgba(32, 178, 170, 0.05) 100%); - border-radius: 32rpx; - padding: 32rpx; - border: 2rpx solid rgba(0, 206, 209, 0.2); -} - -/* ===== 输入框样式 ===== */ -.input-ios { - background: rgba(0, 0, 0, 0.3); - border: 2rpx solid rgba(255, 255, 255, 0.1); - border-radius: 24rpx; - padding: 28rpx 32rpx; - font-size: 32rpx; - color: #ffffff; -} - -.input-ios:focus { - border-color: rgba(0, 206, 209, 0.5); -} - -.input-ios-placeholder { - color: rgba(255, 255, 255, 0.3); -} - -/* ===== 列表项样式 ===== */ -.list-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 28rpx 32rpx; - background: rgba(28, 28, 30, 0.9); - border-bottom: 1rpx solid rgba(255, 255, 255, 0.05); -} - -.list-item:first-child { - border-radius: 24rpx 24rpx 0 0; -} - -.list-item:last-child { - border-radius: 0 0 24rpx 24rpx; - border-bottom: none; -} - -.list-item:only-child { - border-radius: 24rpx; -} - -.list-item:active { - background: rgba(44, 44, 46, 1); -} - -/* ===== 标签样式 ===== */ -.tag { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 8rpx 20rpx; - min-width: 80rpx; - border-radius: 8rpx; - font-size: 22rpx; - font-weight: 500; - box-sizing: border-box; - text-align: center; -} - -.tag-brand { - background: rgba(0, 206, 209, 0.1); - color: #00CED1; -} - -.tag-gold { - background: rgba(255, 215, 0, 0.1); - color: #FFD700; -} - -.tag-pink { - background: rgba(233, 30, 99, 0.1); - color: #E91E63; -} - -.tag-purple { - background: rgba(123, 97, 255, 0.1); - color: #7B61FF; -} - -.tag-free { - background: rgba(0, 206, 209, 0.1); - color: #00CED1; -} - -/* ===== 分隔线 ===== */ -.divider { - height: 1rpx; - background: rgba(255, 255, 255, 0.05); - margin: 24rpx 0; -} - -.divider-vertical { - width: 2rpx; - height: 48rpx; - background: rgba(255, 255, 255, 0.1); -} - -/* ===== 骨架屏动画 ===== */ -.skeleton { - background: linear-gradient(90deg, - rgba(28, 28, 30, 1) 25%, - rgba(44, 44, 46, 1) 50%, - rgba(28, 28, 30, 1) 75% - ); - background-size: 200% 100%; - animation: skeleton-loading 1.5s ease-in-out infinite; - border-radius: 8rpx; -} - -@keyframes skeleton-loading { - 0% { - background-position: 200% 0; - } - 100% { - background-position: -200% 0; - } -} - -/* ===== 页面过渡动画 ===== */ -.page-transition { - animation: fadeIn 0.3s ease-out; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20rpx); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* ===== 弹窗动画 ===== */ -.modal-overlay { - animation: modalOverlayIn 0.25s ease-out; -} - -.modal-content { - animation: modalContentIn 0.3s cubic-bezier(0.32, 0.72, 0, 1); -} - -@keyframes modalOverlayIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes modalContentIn { - from { - opacity: 0; - transform: scale(0.95) translateY(20rpx); - } - to { - opacity: 1; - transform: scale(1) translateY(0); - } -} - -/* ===== 脉动动画 ===== */ -.pulse { - animation: pulse 2s ease-in-out infinite; -} - -@keyframes pulse { - 0%, 100% { - transform: scale(1); - opacity: 1; - } - 50% { - transform: scale(1.05); - opacity: 0.8; - } -} - -/* ===== 发光效果 ===== */ -.glow { - box-shadow: 0 0 40rpx rgba(0, 206, 209, 0.3); -} - -.glow-gold { - box-shadow: 0 0 40rpx rgba(255, 215, 0, 0.3); -} - -/* ===== 文字样式 ===== */ -.text-xs { - font-size: 22rpx; -} - -.text-sm { - font-size: 26rpx; -} - -.text-base { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 28rpx; } +.brand-color { color: #00CED1; } +.gold-color { color: #FFD700; } -.text-lg { - font-size: 32rpx; -} - -.text-xl { - font-size: 36rpx; -} - -.text-2xl { - font-size: 44rpx; -} - -.text-3xl { - font-size: 56rpx; -} - -.text-white { - color: #ffffff; -} - -.text-gray { - color: rgba(255, 255, 255, 0.6); -} - -.text-muted { - color: rgba(255, 255, 255, 0.4); -} - -.text-center { - text-align: center; -} - -.font-medium { - font-weight: 500; -} - -.font-semibold { - font-weight: 600; -} - -.font-bold { - font-weight: 700; -} - -/* ===== Flex布局 ===== */ -.flex { - display: flex; -} - -.flex-col { - flex-direction: column; -} - -.items-center { - align-items: center; -} - -.justify-center { - justify-content: center; -} - -.justify-between { - justify-content: space-between; -} - -.justify-around { - justify-content: space-around; -} - -.flex-1 { - flex: 1; -} - -.gap-1 { - gap: 8rpx; -} - -.gap-2 { - gap: 16rpx; -} - -.gap-3 { - gap: 24rpx; -} - -.gap-4 { - gap: 32rpx; -} - -/* ===== 间距 ===== */ -.p-2 { padding: 16rpx; } -.p-3 { padding: 24rpx; } -.p-4 { padding: 32rpx; } -.p-5 { padding: 40rpx; } - -.px-4 { padding-left: 32rpx; padding-right: 32rpx; } -.py-2 { padding-top: 16rpx; padding-bottom: 16rpx; } -.py-3 { padding-top: 24rpx; padding-bottom: 24rpx; } - -.m-4 { margin: 32rpx; } -.mx-4 { margin-left: 32rpx; margin-right: 32rpx; } -.my-3 { margin-top: 24rpx; margin-bottom: 24rpx; } -.mb-2 { margin-bottom: 16rpx; } -.mb-3 { margin-bottom: 24rpx; } -.mb-4 { margin-bottom: 32rpx; } -.mt-4 { margin-top: 32rpx; } - -/* ===== 圆角 ===== */ -.rounded { border-radius: 8rpx; } -.rounded-lg { border-radius: 16rpx; } -.rounded-xl { border-radius: 24rpx; } -.rounded-2xl { border-radius: 32rpx; } -.rounded-full { border-radius: 50%; } - -/* ===== 安全区域 ===== */ -.safe-bottom { - padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx); -} - -.pb-tabbar { - padding-bottom: 200rpx; -} - -/* ===== 头部导航占位 ===== */ -.nav-placeholder { - height: calc(88rpx + env(safe-area-inset-top, 44rpx)); -} - -/* ===== 隐藏滚动条 ===== */ -::-webkit-scrollbar { - display: none; - width: 0; - height: 0; -} - -/* ===== 触摸反馈 ===== */ -.touch-feedback { - transition: all 0.15s ease; -} - -.touch-feedback:active { - opacity: 0.7; - transform: scale(0.98); -} - -/* ===== 进度条 ===== */ -.progress-bar { - height: 8rpx; - background: rgba(44, 44, 46, 1); - border-radius: 4rpx; - overflow: hidden; -} - -.progress-fill { - height: 100%; - background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%); - border-radius: 4rpx; - transition: width 0.3s ease; -} - -/* ===== 头像样式 ===== */ -.avatar { - width: 80rpx; - height: 80rpx; - border-radius: 50%; - background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, rgba(32, 178, 170, 0.1) 100%); - display: flex; - align-items: center; - justify-content: center; - color: #00CED1; - font-weight: 700; - font-size: 32rpx; - border: 4rpx solid rgba(0, 206, 209, 0.3); -} - -.avatar-lg { - width: 120rpx; - height: 120rpx; - font-size: 48rpx; -} - -/* ===== 图标容器 ===== */ -.icon-box { - width: 64rpx; - height: 64rpx; - border-radius: 16rpx; - display: flex; - align-items: center; - justify-content: center; -} - -.icon-box-brand { - background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, rgba(32, 178, 170, 0.1) 100%); -} - -.icon-box-gold { - background: linear-gradient(135deg, rgba(255, 215, 0, 0.2) 0%, rgba(255, 165, 0, 0.1) 100%); -} - -/* ===== 渐变背景 ===== */ -.bg-gradient-dark { - background: linear-gradient(180deg, #000000 0%, #1a1a1a 100%); -} - -.bg-gradient-brand { - background: linear-gradient(135deg, rgba(0, 206, 209, 0.1) 0%, transparent 100%); -} +/* 顶部安全区:避免被状态栏和胶囊遮挡,头部内容需加此类或预留右侧 */ +.safe-header-right { padding-right: 200rpx; box-sizing: border-box; } diff --git a/miniprogram/assets/icons/home-active.png b/miniprogram/assets/icons/home-active.png deleted file mode 100644 index b6090d87610396c4e046dd531906e3f4aeaeee09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 699 zcmeAS@N?(olHy`uVBq!ia0vp^fgsGm3?%0U?qUH_3dtTpz6=aiY77hwEes65fIUwRG5VK4FYb!C6W$j8E@-(9CP(<^;b zR+8R{-Kzt1UUod~dg>GEyyj}|lD4KBJx$xLPV3HJcKPkh5AU=)(hO?WUYv2w#c|H1 zIcM)iojCeb>rS@0`M!udpYC0}*L{@vz4F`15jLOqUD(%kRQi3xw^*IJy7SCRE*%1b zP8^C~CfhQGev1{DBX_YA=wj6p*NBpo#FA92CCE6dF8*(}7if-ZKuxwC}J8ub)+W zZO+pTZ&M~mym`$VxJYQ~iX(fN>%zHe7ib!}w`$JY@%YR=_h}iYayA_La(qFuRL-qy z|NVG>IM0sMTWQ%Ckkq(r<`VRhu<)JU8pk8;M?1KeKUI$3q=~tmdjjRGW&*f z`py3jCiHtx*Zr0wec()i&()jJDc8T>nDD=Q^|pU<9N)TBCbu8SQu%-6_LV1=8>LR!e>61p+?Q9m|3s^I zji+yw-13xJGbB~D48^=cl60n6D0C`8z^C&3Ki#JC_pKuq0Yh1}#5JNMC9x#cD!C{X zNHG{07@6rB80s3Dgcz7u85mg^8)+LDSQ!|oyG-v#(U6;;l9^VCTSJ(nzA;dP2Hb{{ e%-q!ClEmBs+l8_s;5>q48lEBhUuMByV?@@LH~$ z@<0xIiKnkC`zuC17A7UP*Y34IpH79fui}4y z^g1w!^=MUw^E|V|(UX6BltdS)i>2$mdl%_{%1->}Ed9Y};Is zc6OR=`NyWs8Pj*4iGRAxnANyGHbCF?TGGm#)kTj#Y|FYFt^8un(cQbA?0w|yvdJ;4 zNG$ruwnc6}lcai%_9ab>cr7H~8F(zof1|&N==_fJJx9flG8V45q1q?Wy}ob%--gXU zp2gMt|NE%Dy0FJpC#kScToCB=FVlaqurMBfv2Et&1He#FEpd$~Nl7e8wMs5Z1yT$~ z21aJO28OzZCLsnURt82^#wOYZ237_JE>jk5MbVI(pOTqYiCcr|NhwaC1`W6kC7HRY b#U+Wk1-SJj-Lbv~)WhKE>gTe~DWM4f?pT9{ diff --git a/miniprogram/assets/icons/match.png b/miniprogram/assets/icons/match.png deleted file mode 100644 index b15582e3720ae81bfd63a759fb0aaf1ac555fa51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 725 zcmeAS@N?(olHy`uVBq!ia0vp^fgsGm0wfvQn)U)og=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWSuh@ z85LUuoH!tai*Hc)U$J}IR+YzCt5x2dxNdsEo88}IZ6z2iRX#*6FrShYz! zO#K(xcE0EKv;AMs_I&J{R#Sb}Bzc?kLM7dr1?w{+ySAJ0$LU|*BDytw&&H=KMB+b8 zeXc6L?8UOVk#bXV&cEFBR-tQSLVk$Do*o68C(83raX1N2KFHQ;e^e&&+Kq)%=S|YD zcAg}^<@2qXlb^1X4YS*BcjV{a^v=ZJd=E-DI&5C=`{~|3#)`jZF3zZzTRdBTDnIA{ zg^C9g`~-9&zx*_lJi*zdYxJ~4*6~=#VvDSoO(yTN)!!^pI6C)Tp-I>NoTDeJ{dT1s zJ83^RtjgNNe&$rGzY~*KG7PLTGAnxOV>Feiid7pjRkL$~A-t{l$NqsxsxqRQ% zn-_{#1)RBaYnk?)r*WeBdAX}Cw(bt@;nDQk7&c*%NH(h`7c4B5qW&=*o0Bx}L0Kax zmQ_n!BT7;dOH!?pi&B9UgOP!enXZAMuAxbYfr*uYk(IHDwt<0_fq~1EMO#rcl8_s;5>q48lEBhUuMByV?@@LH~$ z@<0xIiKnkC`zuC17A7UP*Y34IpH79fui}4y z^g1w!^=MUw^E|V|(UX6BltdS)i>2$mdl%_{%1->}Ed9Y};Is zc6OR=`NyWs8Pj*4iGRAxnANyGHbCF?TGGm#)kTj#Y|FYFt^8un(cQbA?0w|yvdJ;4 zNG$ruwnc6}lcai%_9ab>cr7H~8F(zof1|&N==_fJJx9flG8V45q1q?Wy}ob%--gXU zp2gMt|NE%Dy0FJpC#kScToCB=FVlaqurMBfv2Et&1He#FEpd$~Nl7e8wMs5Z1yT$~ z21aJO28OzZCLsnURt82^#wOYZ237_JE>jk5MbVI(pOTqYiCcr|NhwaC1`W6kC7HRY b#U+Wk1-SJj-Lbv~)WhKE>gTe~DWM4f?pT9{ diff --git a/miniprogram/assets/icons/my.png b/miniprogram/assets/icons/my.png deleted file mode 100644 index b15582e3720ae81bfd63a759fb0aaf1ac555fa51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 725 zcmeAS@N?(olHy`uVBq!ia0vp^fgsGm0wfvQn)U)og=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWSuh@ z85LUuoH!tai*Hc)U$J}IR+YzCt5x2dxNdsEo88}IZ6z2iRX#*6FrShYz! zO#K(xcE0EKv;AMs_I&J{R#Sb}Bzc?kLM7dr1?w{+ySAJ0$LU|*BDytw&&H=KMB+b8 zeXc6L?8UOVk#bXV&cEFBR-tQSLVk$Do*o68C(83raX1N2KFHQ;e^e&&+Kq)%=S|YD zcAg}^<@2qXlb^1X4YS*BcjV{a^v=ZJd=E-DI&5C=`{~|3#)`jZF3zZzTRdBTDnIA{ zg^C9g`~-9&zx*_lJi*zdYxJ~4*6~=#VvDSoO(yTN)!!^pI6C)Tp-I>NoTDeJ{dT1s zJ83^RtjgNNe&$rGzY~*KG7PLTGAnxOV>Feiid7pjRkL$~A-t{l$NqsxsxqRQ% zn-_{#1)RBaYnk?)r*WeBdAX}Cw(bt@;nDQk7&c*%NH(h`7c4B5qW&=*o0Bx}L0Kax zmQ_n!BT7;dOH!?pi&B9UgOP!enXZAMuAxbYfr*uYk(IHDwt<0_fq~1EMO#rc { - if (item.iconType === 'match') { - return { ...item, hidden: !matchEnabled } - } - return item - }) - this.setData({ list }) - - console.log('[TabBar] 功能配置加载成功,找伙伴功能:', matchEnabled ? '开启' : '关闭') + syncMatchEnabled() { + const matchEnabled = app.globalData.matchEnabled === true + const list = this.data.list.map((item) => { + if (item.text === '找伙伴') { + return { ...item, hidden: !matchEnabled } } - } catch (e) { - console.log('[TabBar] 加载功能配置失败:', e) - // 失败时默认隐藏找伙伴(与Web版保持一致) - this.setData({ matchEnabled: false }) - } + return item + }) + this.setData({ list }) }, - + switchTab(e) { - const data = e.currentTarget.dataset - const url = data.path - const index = data.index - - if (this.data.selected === index) return - - wx.switchTab({ url }) + const path = e.currentTarget.dataset.path + const index = e.currentTarget.dataset.index + this.setData({ selected: index }) + wx.switchTab({ url: path }) + } + }, + + attached() { + this.syncMatchEnabled() + app.loadFeatureConfig().then(() => this.syncMatchEnabled()) + }, + + pageLifetimes: { + show() { + app.loadFeatureConfig().then(() => this.syncMatchEnabled()) } } }) diff --git a/miniprogram/custom-tab-bar/index.json b/miniprogram/custom-tab-bar/index.json index 467ce294..a89ef4db 100644 --- a/miniprogram/custom-tab-bar/index.json +++ b/miniprogram/custom-tab-bar/index.json @@ -1,3 +1,4 @@ { - "component": true + "component": true, + "usingComponents": {} } diff --git a/miniprogram/custom-tab-bar/index.wxml b/miniprogram/custom-tab-bar/index.wxml index a412ddaf..b2f89dd7 100644 --- a/miniprogram/custom-tab-bar/index.wxml +++ b/miniprogram/custom-tab-bar/index.wxml @@ -1,56 +1,14 @@ - - - - - - - - - - - - - - - {{list[0].text}} - - - - - - - - - - - - - - {{list[1].text}} - - - - - - - - - - - {{list[2].text}} - - - - - - - - - - - - - {{list[3].text}} + + {{item.icon}} + {{item.text}} diff --git a/miniprogram/custom-tab-bar/index.wxss b/miniprogram/custom-tab-bar/index.wxss index 84ad115f..17bf4186 100644 --- a/miniprogram/custom-tab-bar/index.wxss +++ b/miniprogram/custom-tab-bar/index.wxss @@ -1,227 +1,8 @@ -/** - * Soul创业实验 - 自定义TabBar样式 - * 实现中间突出的"找伙伴"按钮 - */ - -.tab-bar { - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: 100rpx; - background: rgba(28, 28, 30, 0.95); - backdrop-filter: blur(40rpx); - -webkit-backdrop-filter: blur(40rpx); - display: flex; - align-items: flex-end; - padding-bottom: env(safe-area-inset-bottom); - z-index: 999; -} - -.tab-bar-border { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 1rpx; - background: rgba(255, 255, 255, 0.05); -} - -.tab-bar-item { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 10rpx 0 16rpx; -} - -.icon-wrapper { - width: 48rpx; - height: 48rpx; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 4rpx; -} - -.icon { - width: 44rpx; - height: 44rpx; - display: flex; - align-items: center; - justify-content: center; -} - -.tab-bar-text { - font-size: 22rpx; - line-height: 1; -} - -/* ===== 首页图标 ===== */ -.icon-home { - position: relative; - width: 40rpx; - height: 40rpx; -} - -.home-roof { - position: absolute; - top: 4rpx; - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: 18rpx solid transparent; - border-right: 18rpx solid transparent; - border-bottom: 14rpx solid #8e8e93; -} - -.home-body { - position: absolute; - bottom: 4rpx; - left: 50%; - transform: translateX(-50%); - width: 28rpx; - height: 18rpx; - background: #8e8e93; - border-radius: 0 0 4rpx 4rpx; -} - -.icon-active .home-roof { - border-bottom-color: #00CED1; -} - -.icon-active .home-body { - background: #00CED1; -} - -/* ===== 目录图标 ===== */ -.icon-list { - width: 36rpx; - height: 32rpx; - display: flex; - flex-direction: column; - justify-content: space-between; -} - -.list-line { - width: 100%; - height: 6rpx; - background: #8e8e93; - border-radius: 3rpx; -} - -.list-line:nth-child(2) { - width: 75%; -} - -.list-line:nth-child(3) { - width: 50%; -} - -.icon-active .list-line { - background: #00CED1; -} - -/* ===== 我的图标 ===== */ -.icon-user { - position: relative; - width: 36rpx; - height: 40rpx; -} - -.user-head { - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 16rpx; - height: 16rpx; - background: #8e8e93; - border-radius: 50%; -} - -.user-body { - position: absolute; - bottom: 0; - left: 50%; - transform: translateX(-50%); - width: 28rpx; - height: 18rpx; - background: #8e8e93; - border-radius: 14rpx 14rpx 0 0; -} - -.icon-active .user-head, -.icon-active .user-body { - background: #00CED1; -} - -/* ===== 找伙伴 - 中间特殊按钮 ===== */ -.special-item { - position: relative; - margin-top: -32rpx; -} - -.special-button { - width: 112rpx; - height: 112rpx; - border-radius: 50%; - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 8rpx 32rpx rgba(0, 206, 209, 0.4); - margin-bottom: 4rpx; - transition: all 0.2s ease; -} - -.special-button:active { - transform: scale(0.95); -} - -.special-active { - box-shadow: 0 8rpx 40rpx rgba(0, 206, 209, 0.6); -} - -.special-text { - margin-top: 4rpx; -} - -/* ===== 找伙伴图标 (双人) ===== */ -.icon-users { - position: relative; - width: 56rpx; - height: 44rpx; -} - -.user-circle { - position: absolute; - width: 28rpx; - height: 28rpx; - border-radius: 50%; - background: #ffffff; -} - -.user-circle::after { - content: ''; - position: absolute; - bottom: -12rpx; - left: 50%; - transform: translateX(-50%); - width: 22rpx; - height: 14rpx; - background: #ffffff; - border-radius: 11rpx 11rpx 0 0; -} - -.user-1 { - top: 0; - left: 0; -} - -.user-2 { - top: 0; - right: 0; -} +.tab-bar { position: fixed; bottom: 0; left: 0; right: 0; height: 120rpx; background: #1c1c1e; border-top: 2rpx solid rgba(255,255,255,0.05); display: flex; align-items: flex-end; justify-content: space-around; padding-bottom: env(safe-area-inset-bottom); padding-top: 16rpx; } +.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 8rpx 0; } +.tab-icon { font-size: 44rpx; line-height: 1; margin-bottom: 4rpx; opacity: 0.7; } +.tab-item.active .tab-icon { opacity: 1; } +.tab-text { font-size: 20rpx; color: #8e8e93; } +.tab-item.active .tab-text { color: #00CED1; font-weight: 500; } +.tab-item.center .tab-icon { width: 88rpx; height: 88rpx; line-height: 88rpx; text-align: center; font-size: 48rpx; margin-top: -40rpx; margin-bottom: 0; border-radius: 50%; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); box-shadow: 0 8rpx 24rpx rgba(0,206,209,0.3); } +.tab-item.center.active .tab-icon { opacity: 1; box-shadow: 0 8rpx 28rpx rgba(0,206,209,0.4); } diff --git a/miniprogram/pages/about/about.js b/miniprogram/pages/about/about.js index dbbcabb0..cb8f69d6 100644 --- a/miniprogram/pages/about/about.js +++ b/miniprogram/pages/about/about.js @@ -1,81 +1,49 @@ -/** - * Soul创业派对 - 关于作者页 - * 开发: 卡若 - */ const app = getApp() Page({ data: { statusBarHeight: 44, - author: { + navBarHeight: 88, + authorInfo: { name: '卡若', - avatar: 'K', - title: 'Soul派对房主理人 · 私域运营专家', - bio: '每天早上6点到9点,在Soul派对房分享真实的创业故事。专注私域运营与项目变现,用"云阿米巴"模式帮助创业者构建可持续的商业体系。本书记录了62个真实商业案例,涵盖电商、内容、传统行业等多个领域。', - stats: [ - { label: '商业案例', value: '62' }, - { label: '连续直播', value: '365天' }, - { label: '派对分享', value: '1000+' } - ], - // 联系方式已移至后台配置 - contact: null, - highlights: [ - '5年私域运营经验', - '帮助100+品牌从0到1增长', - '连续创业者,擅长商业模式设计' - ] + description: '连续创业者,私域运营专家', + liveTime: '06:00-09:00', + platform: 'Soul派对房' }, - bookInfo: { - title: '一场Soul的创业实验', - totalChapters: 62, - parts: [ - { name: '真实的人', chapters: 10 }, - { name: '真实的行业', chapters: 15 }, - { name: '真实的错误', chapters: 9 }, - { name: '真实的赚钱', chapters: 20 }, - { name: '真实的社会', chapters: 9 } - ], - price: 9.9 - } + authorInitial: '卡', + stats: [ + { value: '55+', label: '真实案例', icon: '📖' }, + { value: '10000+', label: '派对房听众', icon: '👥' }, + { value: '15年', label: '创业经验', icon: '🏆' }, + { value: '3000万', label: '最高年流水', icon: '📈' } + ], + milestones: [ + { year: '2007-2014', event: '游戏电竞创业历程,从魔兽世界代练起步' }, + { year: '2015', event: '转型电商,做天猫虚拟充值' }, + { year: '2016-2019', event: '深耕电商领域,团队扩张到200人,年流水3000万' }, + { year: '2019-2020', event: '公司变故,重整旗鼓' }, + { year: '2020-2025', event: '电竞、地摊、大健康、私域多领域探索' }, + { year: '2025.10.15', event: '在Soul派对房开启每日分享,记录真实商业案例' } + ] }, onLoad() { - this.setData({ - statusBarHeight: app.globalData.statusBarHeight - }) - this.loadBookStats() - }, - - // 加载书籍统计 - async loadBookStats() { - try { - const res = await app.request('/api/book/stats') - if (res && res.success) { - this.setData({ - 'bookInfo.totalChapters': res.data?.totalChapters || 62, - 'author.stats': [ - { label: '商业案例', value: String(res.data?.totalChapters || 62) }, - { label: '连续直播', value: '365天' }, - { label: '派对分享', value: '1000+' } - ] - }) - } - } catch (e) { - console.log('[About] 加载书籍统计失败,使用默认值') - } + const statusBarHeight = app.globalData.statusBarHeight || 44 + const navBarHeight = app.globalData.navBarHeight || (statusBarHeight + 44) + const authorInfo = this.data.authorInfo + const authorInitial = authorInfo.name ? authorInfo.name.charAt(0) : '卡' + this.setData({ statusBarHeight, navBarHeight, authorInitial }) }, - // 联系方式功能已禁用 - copyWechat() { - wx.showToast({ title: '请在派对房联系作者', icon: 'none' }) - }, - - callPhone() { - wx.showToast({ title: '请在派对房联系作者', icon: 'none' }) - }, - - // 返回 goBack() { - wx.navigateBack() + wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/index/index' }) }) + }, + + onJoinParty() { + wx.showToast({ + title: '请关注小程序或联系客服加入派对群', + icon: 'none', + duration: 2500 + }) } }) diff --git a/miniprogram/pages/about/about.json b/miniprogram/pages/about/about.json index e90e9960..7ad45361 100644 --- a/miniprogram/pages/about/about.json +++ b/miniprogram/pages/about/about.json @@ -1,4 +1,4 @@ { - "usingComponents": {}, - "navigationStyle": "custom" + "navigationBarTitleText": "关于作者", + "usingComponents": {} } diff --git a/miniprogram/pages/about/about.wxml b/miniprogram/pages/about/about.wxml index 598e9464..c4197c8c 100644 --- a/miniprogram/pages/about/about.wxml +++ b/miniprogram/pages/about/about.wxml @@ -1,75 +1,54 @@ - - - - 关于作者 - + + + ← 返回 + 关于作者 + - - - - - - {{author.avatar}} - {{author.name}} - {{author.title}} - {{author.bio}} - - - - - {{item.value}} - {{item.label}} - + + + {{authorInitial}} + {{authorInfo.name}} + {{authorInfo.description}} + + 🕐 每日 {{authorInfo.liveTime}} + 💬 {{authorInfo.platform}} - - - - - - {{item}} + + + + {{item.icon}} + {{item.value}} + {{item.label}} + + + + 关于这本书 + + "这不是一本教你成功的鸡汤书。" + 这是我每天早上6点到9点,在Soul派对房和几百个陌生人分享的真实故事。 + "社会不是靠努力,是靠洞察与选择。" + + + + 创业历程 + + + + + + + + {{item.year}} + {{item.event}} + - - - - 📚 {{bookInfo.title}} - - - {{bookInfo.totalChapters}} - 篇章节 - - - 5 - 大篇章 - - - ¥{{bookInfo.price}} - 全书价格 - - - - - {{item.name}} - {{item.chapters}}节 - - - - - - - 联系作者 - - 🎉 - - Soul派对房 - 每天早上6-9点开播 - - - - 在Soul App搜索"创业实验"或"卡若",加入派对房直接交流 - + + 想听更多真实故事? + 每天早上6-9点,卡若在Soul派对房免费分享 + 💬 加入派对群 diff --git a/miniprogram/pages/about/about.wxss b/miniprogram/pages/about/about.wxss index 337aa041..22c728be 100644 --- a/miniprogram/pages/about/about.wxss +++ b/miniprogram/pages/about/about.wxss @@ -1,40 +1,35 @@ -.page { min-height: 100vh; background: #000; } -.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; } -.nav-back { width: 72rpx; height: 72rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: #fff; } -.nav-title { font-size: 36rpx; font-weight: 600; color: #00CED1; } -.nav-placeholder { width: 72rpx; } -.content { padding: 32rpx; } -.author-card { background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%); border-radius: 32rpx; padding: 48rpx; text-align: center; margin-bottom: 24rpx; border: 2rpx solid rgba(0,206,209,0.2); } -.author-avatar { width: 160rpx; height: 160rpx; border-radius: 50%; background: linear-gradient(135deg, #00CED1, #20B2AA); display: flex; align-items: center; justify-content: center; margin: 0 auto 24rpx; font-size: 64rpx; color: #fff; font-weight: 700; border: 4rpx solid rgba(0,206,209,0.3); } -.author-name { font-size: 40rpx; font-weight: 700; color: #fff; display: block; margin-bottom: 8rpx; } -.author-title { font-size: 26rpx; color: #00CED1; display: block; margin-bottom: 24rpx; } -.author-bio { font-size: 26rpx; color: rgba(255,255,255,0.7); line-height: 1.8; display: block; margin-bottom: 32rpx; } -.stats-row { display: flex; justify-content: space-around; padding-top: 32rpx; border-top: 2rpx solid rgba(255,255,255,0.1); } -.stat-item { text-align: center; } -.stat-value { font-size: 36rpx; font-weight: 700; color: #00CED1; display: block; } -.stat-label { font-size: 22rpx; color: rgba(255,255,255,0.5); } -.contact-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; } -.card-title { font-size: 28rpx; font-weight: 600; color: #fff; display: block; margin-bottom: 24rpx; } -.contact-item { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 16rpx; } -.contact-item:last-child { margin-bottom: 0; } -.contact-icon { font-size: 40rpx; } -.contact-info { flex: 1; } -.contact-label { font-size: 22rpx; color: rgba(255,255,255,0.5); display: block; } -.contact-value { font-size: 28rpx; color: #fff; } -.contact-btn { padding: 12rpx 24rpx; background: rgba(0,206,209,0.2); color: #00CED1; font-size: 24rpx; border-radius: 16rpx; } - -/* 亮点标签 */ -.highlights { display: flex; flex-wrap: wrap; gap: 16rpx; margin-top: 32rpx; padding-top: 24rpx; border-top: 2rpx solid rgba(255,255,255,0.1); justify-content: center; } -.highlight-tag { display: flex; align-items: center; gap: 8rpx; padding: 12rpx 24rpx; background: rgba(0,206,209,0.15); border-radius: 24rpx; font-size: 24rpx; color: rgba(255,255,255,0.8); } -.tag-icon { color: #00CED1; font-size: 22rpx; } - -/* 书籍信息卡片 */ -.book-info-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; margin-bottom: 24rpx; } -.book-stats { display: flex; justify-content: space-around; padding: 24rpx 0; margin: 16rpx 0; background: rgba(0,0,0,0.3); border-radius: 16rpx; } -.book-stat { text-align: center; } -.book-stat-value { font-size: 36rpx; font-weight: 700; color: #FFD700; display: block; } -.book-stat-label { font-size: 22rpx; color: rgba(255,255,255,0.5); } -.parts-list { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 16rpx; } -.part-item { display: flex; align-items: center; gap: 8rpx; padding: 12rpx 20rpx; background: rgba(255,255,255,0.05); border-radius: 12rpx; } -.part-name { font-size: 24rpx; color: rgba(255,255,255,0.8); } -.part-chapters { font-size: 22rpx; color: #00CED1; } +.page { min-height: 100vh; background: #000; padding-bottom: 80rpx; } +.nav-bar { background: rgba(0,0,0,0.9); border-bottom: 2rpx solid rgba(255,255,255,0.05); box-sizing: border-box; display: flex; flex-direction: column; justify-content: flex-end; } +.nav-inner { display: flex; align-items: center; padding: 0 24rpx; height: 88rpx; min-height: 44px; flex-shrink: 0; } +.nav-back { font-size: 32rpx; color: #00CED1; padding: 16rpx 0; } +.nav-title { flex: 1; text-align: center; font-size: 34rpx; color: #00CED1; } +.main { padding: 32rpx; } +.card { border-radius: 32rpx; padding: 40rpx; margin-bottom: 24rpx; background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%); border: 2rpx solid rgba(0,206,209,0.2); } +.author-card { display: flex; flex-direction: column; align-items: center; text-align: center; } +.author-avatar { width: 160rpx; height: 160rpx; border-radius: 50%; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); display: flex; align-items: center; justify-content: center; font-size: 60rpx; font-weight: 700; color: #fff; margin-bottom: 24rpx; } +.author-name { font-size: 40rpx; font-weight: 700; color: #fff; display: block; } +.author-desc { font-size: 28rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; display: block; } +.author-tags { display: flex; gap: 16rpx; margin-top: 24rpx; justify-content: center; flex-wrap: wrap; } +.tag { font-size: 22rpx; padding: 12rpx 24rpx; border-radius: 32rpx; background: rgba(255,255,255,0.05); color: rgba(255,255,255,0.5); } +.tag.brand { background: rgba(0,206,209,0.1); color: #00CED1; } +.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16rpx; margin-bottom: 24rpx; } +.stat-item { padding: 24rpx; border-radius: 24rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.05); text-align: center; } +.stat-icon { font-size: 40rpx; display: block; margin-bottom: 12rpx; opacity: 0.9; } +.stat-value { font-size: 32rpx; font-weight: 700; color: #fff; display: block; } +.stat-label { font-size: 20rpx; color: rgba(255,255,255,0.4); } +.section-title { font-size: 32rpx; font-weight: 600; color: #fff; display: block; margin-bottom: 16rpx; } +.section-paras { } +.section-para { font-size: 28rpx; color: rgba(255,255,255,0.8); line-height: 1.7; display: block; margin-bottom: 16rpx; } +.section-para.brand { color: #00CED1; font-weight: 500; } +.timeline { } +.timeline-item { display: flex; gap: 24rpx; } +.timeline-dot-wrap { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; } +.timeline-dot { width: 16rpx; height: 16rpx; border-radius: 50%; background: #00CED1; } +.timeline-line { width: 4rpx; flex: 1; min-height: 24rpx; background: rgba(255,255,255,0.2); margin-top: 8rpx; } +.timeline-content { flex: 1; padding-bottom: 24rpx; } +.milestone-year { font-size: 28rpx; color: #00CED1; font-weight: 600; display: block; margin-bottom: 8rpx; } +.milestone-event { font-size: 26rpx; color: rgba(255,255,255,0.7); display: block; line-height: 1.5; } +.join-card { background: linear-gradient(90deg, rgba(0,206,209,0.1) 0%, rgba(32,178,170,0.05) 100%); border-color: rgba(0,206,209,0.2); text-align: center; } +.join-title { font-size: 32rpx; font-weight: 600; color: #fff; display: block; margin-bottom: 8rpx; } +.join-desc { font-size: 28rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 24rpx; } +.btn-join { width: 100%; padding: 24rpx; border-radius: 24rpx; background: #00CED1; color: #fff; font-size: 30rpx; font-weight: 500; text-align: center; } diff --git a/miniprogram/pages/address-edit/address-edit.js b/miniprogram/pages/address-edit/address-edit.js index de8252c8..8dfcb509 100644 --- a/miniprogram/pages/address-edit/address-edit.js +++ b/miniprogram/pages/address-edit/address-edit.js @@ -1,136 +1,68 @@ -/** - * Soul创业派对 - 收货地址新建/编辑 - */ +// pages/address-edit/address-edit.js const app = getApp() Page({ data: { statusBarHeight: 44, - id: '', - isEdit: false, - name: '', - phone: '', - region: ['广东省', '广州市', '天河区'], - province: '广东省', - city: '广州市', - district: '天河区', - detail: '', - isDefault: false, - loading: false + navBarHeight: 88 }, onLoad(options) { - this.setData({ statusBarHeight: app.globalData.statusBarHeight }) - if (options.id) { - this.setData({ id: options.id, isEdit: true }) - this.loadItem(options.id) - } - }, - - async loadItem(id) { - wx.showLoading({ title: '加载中...' }) - try { - const res = await app.request('/api/user/addresses/' + id) - wx.hideLoading() - if (res.success && res.item) { - const item = res.item - this.setData({ - name: item.name, - phone: item.phone, - province: item.province || '', - city: item.city || '', - district: item.district || '', - region: [item.province || '', item.city || '', item.district || ''], - detail: item.detail, - isDefault: item.isDefault - }) - } - } catch (e) { - wx.hideLoading() - wx.showToast({ title: '加载失败', icon: 'none' }) - } - }, - - onNameInput(e) { - this.setData({ name: e.detail.value.trim() }) - }, - - onPhoneInput(e) { - this.setData({ phone: e.detail.value.replace(/\D/g, '').slice(0, 11) }) - }, - - onRegionChange(e) { - const region = e.detail.value - this.setData({ - region, - province: region[0] || '', - city: region[1] || '', - district: region[2] || '' - }) - }, - - onDetailInput(e) { - this.setData({ detail: e.detail.value.trim() }) - }, - - onDefaultChange(e) { - this.setData({ isDefault: e.detail.value }) - }, - - async save() { - const { id, isEdit, name, phone, province, city, district, detail, isDefault } = this.data - if (!name) { - wx.showToast({ title: '请输入收货人姓名', icon: 'none' }) - return - } - if (!phone || !/^1[3-9]\d{9}$/.test(phone)) { - wx.showToast({ title: '请输入正确的手机号', icon: 'none' }) - return - } - // 省/市/区为选填,不校验 - if (!detail) { - wx.showToast({ title: '请输入详细地址', icon: 'none' }) - return - } - const userId = app.globalData.userInfo?.id - if (!userId) { - wx.showToast({ title: '请先登录', icon: 'none' }) - return - } - this.setData({ loading: true }) - try { - if (isEdit && id) { - const res = await app.request('/api/user/addresses/' + id, { - method: 'PUT', - data: { name, phone, province, city, district, detail, isDefault } - }) - if (res.success) { - wx.showToast({ title: '保存成功', icon: 'success' }) - setTimeout(() => wx.navigateBack(), 1500) - } else { - wx.showToast({ title: res.message || '保存失败', icon: 'none' }) - this.setData({ loading: false }) - } - } else { - const res = await app.request('/api/user/addresses', { - method: 'POST', - data: { userId, name, phone, province, city, district, detail, isDefault } - }) - if (res.success) { - wx.showToast({ title: '添加成功', icon: 'success' }) - setTimeout(() => wx.navigateBack(), 1500) - } else { - wx.showToast({ title: res.message || '添加失败', icon: 'none' }) - this.setData({ loading: false }) - } - } - } catch (e) { - this.setData({ loading: false }) - wx.showToast({ title: '保存失败', icon: 'none' }) - } + const statusBarHeight = app.globalData.statusBarHeight || 44 + const navBarHeight = app.globalData.navBarHeight || (statusBarHeight + 44) + this.setData({ statusBarHeight, navBarHeight }) }, goBack() { - wx.navigateBack() + wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/my/my' }) }) + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + } -}) +}) \ 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 a78dbd8f..e53946ef 100644 --- a/miniprogram/pages/address-edit/address-edit.wxml +++ b/miniprogram/pages/address-edit/address-edit.wxml @@ -1,40 +1,8 @@ - - - - - - {{isEdit ? '编辑地址' : '新增地址'}} - + + + ← 返回 + 地址编辑 - - - - - 收货人 - - - - 手机号码 - - - - 所在地区(选填) - - - {{region[0] && region[1] && region[2] ? region[0] + ' ' + region[1] + ' ' + region[2] : '选填,可不选'}} - - - - - 详细地址 -