diff --git a/.cursor/skills/SKILL-API开发.md b/.cursor/skills/SKILL-API开发.md index b3e855e8..8554532d 100644 --- a/.cursor/skills/SKILL-API开发.md +++ b/.cursor/skills/SKILL-API开发.md @@ -76,6 +76,14 @@ description: Soul 创业派对后端 API 开发规范。在 soul-api/ 下编辑 - **小程序**:只认 `/api/miniprogram/*`;登录、书籍、支付、提现、推荐、用户等均在该组下;返回字段与小程序 `app.request` 解析一致(success、data、error/message)。 - **管理端**:只认 `/api/admin/*`、`/api/db/*` 等;列表接口需包含管理端所需字段(如 user_name/userNickname、userAvatar、status、amount);鉴权用 JWT,与 soul-admin 的 `Authorization: Bearer ` 一致。 + +**列表接口约定**:管理端列表必须支持: +- **分页**:`page`、`pageSize` 查询参数,返回 `total`、`page`、`pageSize`、`totalPages` +- **筛选**:`status`、`matchType`、`search`、`vip` 等按业务 +- **响应格式**:`{ success, records/users/orders/..., total, page, pageSize, totalPages }` +- **错误**:`{ success: false, error: "..." }` + +已实现分页的接口:`/api/db/users`、`/api/orders`、`/api/admin/withdrawals`、`/api/db/distribution`、`/api/db/match-records`。详见 `开发文档/列表标准与角色分工.md`。 - **不要**:在 miniprogram 组挂仅 admin 使用的接口;在 admin 组挂小程序专属逻辑;在 handler 内混用「管理端路径」与「小程序路径」的语义。 --- diff --git a/.cursor/skills/SKILL-管理端开发.md b/.cursor/skills/SKILL-管理端开发.md index 2a37c4f5..faa8de8b 100644 --- a/.cursor/skills/SKILL-管理端开发.md +++ b/.cursor/skills/SKILL-管理端开发.md @@ -42,7 +42,28 @@ description: Soul 创业派对管理端开发规范。在 soul-admin/ 下编辑 --- -## 4. 业务逻辑约定(与 soul-api 对齐) +## 4. 列表页标准(必守) + +新增或修改列表页时,必须包含: + +**必做**: +- **分页**:后端支持时接入 `page`、`pageSize`、`total`,使用 `src/components/ui/Pagination.tsx` +- **搜索**:使用 `useDebounce` 对输入做 300ms 防抖(见 `src/hooks/useDebounce.ts`) +- **筛选**:按业务提供下拉/按钮筛选 +- **刷新**:提供刷新按钮,加载时禁用并显示 loading +- **加载状态**:请求中显示 loading +- **空状态**:无数据时显示友好提示 +- **错误提示**:catch 后设置 error 状态,页面顶部展示可关闭的红底错误条 + +**可选**:导出 CSV、列头排序、批量操作。 + +**禁止**:不得用原生 `alert` 做加载失败提示;不得调用 `/api/miniprogram/*`。 + +**检查清单**:分页、搜索防抖、刷新、loading、空状态、错误条、仅 admin/db 路径。详见 `开发文档/列表标准与角色分工.md`。 + +--- + +## 5. 业务逻辑约定(与 soul-api 对齐) - **鉴权**:登录后 token 存 localStorage(`admin_token`);请求头由 client 自动带 Bearer token;401 时跳转登录页并清除 token。 - **提现**:列表/统计用 `GET /api/admin/withdrawals`;审核/拒绝用 `PUT /api/admin/withdrawals`(如 action: approve/reject);状态与 soul-api 一致(如 pending、processing、success、failed),前端展示可映射为「已完成/已拒绝」等文案。 @@ -52,14 +73,14 @@ description: Soul 创业派对管理端开发规范。在 soul-admin/ 下编辑 --- -## 5. 与 soul-api 的对接要点 +## 6. 与 soul-api 的对接要点 - **响应格式**:成功多为 `{ success: true, data?: ..., withdrawals?: ..., ... }`;失败 `{ success: false, error?: string }` 或 HTTP 4xx/5xx;client 在 `!res.ok` 时 throw,页面 catch 后展示错误。 - **新增需求**:新功能应在 soul-api 的 **admin** 或 **db** 组下新增接口,管理端只调 `/api/admin/...` 或 `/api/db/...`,不得新增对 `/api/miniprogram/...` 的依赖。 --- -## 6. 何时使用本 Skill +## 7. 何时使用本 Skill - 在 **soul-admin/** 下新增或修改页面、组件、API 调用时。 - 在管理端新增任何网络请求时(必须仅使用 admin/db 等管理端路径)。 diff --git a/miniprogram/RESTORE-ANALYSIS.md b/miniprogram/RESTORE-ANALYSIS.md index f8128099..558a31ff 100644 --- a/miniprogram/RESTORE-ANALYSIS.md +++ b/miniprogram/RESTORE-ANALYSIS.md @@ -49,15 +49,16 @@ - **vip 相关接口**:已改为 `/api/miniprogram/vip/*`(members、status、profile),符合项目边界。**后端需在 soul-api 的 miniprogram 组下挂对应路由**,可复用现有 handler。 - **页面结构**:保留 vip、member-detail,未引入 scan、profile-edit -## 四、后端待办 +## 四、后端已补全(soul-api) -soul-api 需在 miniprogram 组下增加以下 VIP 路由(可复用现有 handler): +已在 miniprogram 组下新增以下路由: | 路径 | 方法 | 用途 | |------|------|------| -| `/api/miniprogram/vip/status` | GET | 查询用户 VIP 状态 | -| `/api/miniprogram/vip/profile` | GET/POST | 获取/更新 VIP 资料 | -| `/api/miniprogram/vip/members` | GET | 获取 VIP 会员列表(首页超级个体、会员详情) | +| `/api/miniprogram/vip/status` | GET | 查询用户 VIP 状态(按 fullbook/vip 订单判断) | +| `/api/miniprogram/vip/profile` | GET/POST | 获取/更新 VIP 资料(映射 users 表 nickname/phone) | +| `/api/miniprogram/vip/members` | GET | VIP 会员列表(无 id)或单个(?id=) | +| `/api/miniprogram/users` | GET | 用户列表(?limit=)或单个(?id=),首页超级个体、会员详情回退 | ## 五、后续建议 diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index 9f9a27c1..a19eb487 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -49,6 +49,9 @@ Page({ // 最新新增章节 latestChapters: [], + // 篇章数(从 bookData 计算) + partCount: 0, + // 加载状态 loading: true }, @@ -164,12 +167,16 @@ Page({ .sort((a, b) => new Date(b.updated_at || b.updatedAt || 0) - new Date(a.updated_at || a.updatedAt || 0)) .slice(0, 5) } + const tagMap = ['热门', '推荐', '精选'] + const tagClassMap = ['tag-hot', 'tag-rec', 'tag-rec'] if (featured.length > 0) { this.setData({ - featuredSections: featured.slice(0, 3).map(s => ({ + featuredSections: featured.slice(0, 3).map((s, i) => ({ id: s.id || s.section_id, - title: s.section_title || s.title, - part: (s.cleanPartTitle || s.part_title || '').replace(/[_||]/g, ' ').trim() + title: s.section_title || s.sectionTitle || s.title || s.chapterTitle || '', + part: (s.cleanPartTitle || s.part_title || s.partTitle || '').replace(/[_||]/g, ' ').trim(), + tag: tagMap[i] || '精选', + tagClass: tagClassMap[i] || 'tag-rec' })) }) } @@ -187,8 +194,8 @@ Page({ this.setData({ latestSection: { id: latest.id || latest.section_id, - title: latest.section_title || latest.title, - part: latest.cleanPartTitle || latest.part_title || '' + title: latest.section_title || latest.sectionTitle || latest.title || latest.chapterTitle || '', + part: latest.cleanPartTitle || latest.part_title || latest.partTitle || '' } }) } @@ -202,9 +209,11 @@ Page({ const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true }) if (res && (res.data || res.chapters)) { const chapters = res.data || res.chapters || [] + const partIds = new Set(chapters.map(c => c.partId || c.part_id || '').filter(Boolean)) this.setData({ bookData: chapters, - totalSections: res.total || chapters.length || 62 + totalSections: res.total || chapters.length || 62, + partCount: partIds.size || 5 }) } } catch (e) { @@ -248,6 +257,10 @@ Page({ wx.navigateTo({ url: '/pages/vip/vip' }) }, + goToAbout() { + wx.navigateTo({ url: '/pages/about/about' }) + }, + goToSuperList() { wx.switchTab({ url: '/pages/match/match' }) }, @@ -256,16 +269,35 @@ Page({ try { const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true }) const chapters = (res && res.data) || (res && res.chapters) || [] - const latest = chapters - .filter(c => (c.sectionOrder || c.sort_order || 0) > 62) + const sortOrder = c => (c.sortOrder ?? c.sectionOrder ?? c.sort_order ?? 0) + // 优先取 sort_order > 62 的「新增」章节;若无则取最近更新的前 10 章(排除序言/尾声/附录) + let candidates = chapters.filter(c => sortOrder(c) > 62) + if (candidates.length === 0) { + const id = (c) => (c.id || '').toLowerCase() + const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase() + candidates = chapters.filter(c => + !id(c).includes('preface') && !id(c).includes('epilogue') && !id(c).includes('appendix') + && !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录') + ) + } + const latest = candidates .sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0)) .slice(0, 10) .map(c => { const d = new Date(c.updatedAt || c.updated_at || Date.now()) + const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || '' + const rawContent = (c.content || '').replace(/<[^>]+>/g, '').trim() + // 描述仅用正文摘要,避免 #id 或标题重复;截取 36 字 + let desc = '' + if (rawContent && rawContent.length > 0) { + const clean = rawContent.replace(/^#[\d.]+\s*/, '').trim() + desc = clean.length > 36 ? clean.slice(0, 36) + '...' : clean + } return { id: c.id, - title: c.section_title || c.title || c.sectionTitle, - price: c.price || 1, + title, + desc, + price: c.price ?? 1, dateStr: `${d.getMonth() + 1}/${d.getDate()}` } }) diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index 470c18b5..b04cc314 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -1,5 +1,5 @@ - + @@ -12,9 +12,12 @@ S - - Soul - 创业派对 + + Soul创业派对 + + + + 联系作者 + 来自派对房的真实故事 @@ -26,9 +29,8 @@ - - - + + 🔍 搜索章节标题或内容... @@ -69,53 +71,55 @@ 待读 - 5 + {{partCount}} 篇章 - 11 + {{totalSections}} 章节 - + 超级个体 查看全部 - + - - - - - {{item.name[0] || '会'}} + + + + + + {{item.name[0] || '会'}} + + {{item.name}} - {{item.name}} - + 成为会员,展示你的项目 加入创业派对 → - + 精选推荐 查看全部 - + @@ -129,32 +133,42 @@ {{item.id}} + {{item.tag || '精选'}} {{item.title}} {{item.part}} - + - + - + 最新新增 +{{latestChapters.length}} - - - - NEW - {{item.title}} - - - ¥{{item.price}} - {{item.dateStr}} + + + + + + + + + NEW + {{item.title}} + + + ¥{{item.price}} + {{item.dateStr}} + + + {{item.desc}} + diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss index b3d68715..292cdfe8 100644 --- a/miniprogram/pages/index/index.wxss +++ b/miniprogram/pages/index/index.wxss @@ -45,7 +45,7 @@ } .logo-text { - color: #ffffff; + color: #000000; font-size: 36rpx; font-weight: 700; } @@ -55,6 +55,40 @@ flex-direction: column; } +.logo-title-row { + display: flex; + align-items: center; + gap: 16rpx; + flex-wrap: wrap; +} + +.logo-title-text { + font-size: 36rpx; + font-weight: 700; + color: #ffffff; +} + +.contact-btn { + display: flex; + align-items: center; + gap: 4rpx; + padding: 8rpx 16rpx; + background: rgba(0, 206, 209, 0.1); + border: 2rpx solid rgba(0, 206, 209, 0.2); + border-radius: 32rpx; + font-size: 20rpx; + font-weight: 600; + color: #00CED1; +} + +.contact-icon { + font-size: 20rpx; +} + +.contact-text { + font-size: 20rpx; +} + .logo-title { font-size: 36rpx; font-weight: 700; @@ -99,28 +133,17 @@ border: 2rpx solid rgba(255, 255, 255, 0.05); } -.search-icon { - position: relative; +.search-icon-wrap { width: 32rpx; height: 32rpx; + display: flex; + align-items: center; + justify-content: center; } -.search-circle { - width: 20rpx; - height: 20rpx; - border: 4rpx solid rgba(255, 255, 255, 0.4); - border-radius: 50%; -} - -.search-handle { - position: absolute; - bottom: 0; - right: 0; - width: 12rpx; - height: 4rpx; - background: rgba(255, 255, 255, 0.4); - transform: rotate(45deg); - border-radius: 2rpx; +.search-icon-text { + font-size: 24rpx; + opacity: 0.6; } .search-placeholder { @@ -135,6 +158,14 @@ box-sizing: border-box; } +.main-content > .banner-card { + margin-bottom: 24rpx; +} + +.main-content > .card { + margin-bottom: 24rpx; +} + /* ===== Banner卡片 ===== */ .banner-card { position: relative; @@ -282,7 +313,7 @@ /* ===== 区块标题 ===== */ .section { - margin-bottom: 24rpx; + margin-bottom: 48rpx; } .section-header { @@ -344,12 +375,29 @@ display: flex; align-items: center; gap: 16rpx; - margin-bottom: 16rpx; + margin-bottom: 12rpx; } .featured-id { - font-size: 24rpx; - font-weight: 500; + font-size: 28rpx; + font-weight: 600; +} + +.featured-tag { + font-size: 20rpx; + font-weight: 600; + padding: 4rpx 12rpx; + border-radius: 8rpx; +} + +.tag-hot { + background: rgba(246, 173, 85, 0.15); + color: #F6AD55; +} + +.tag-rec { + background: rgba(0, 206, 209, 0.15); + color: #00CED1; } .tag { @@ -498,12 +546,46 @@ color: rgba(255, 255, 255, 0.6); } -/* ===== 超级个体 ===== */ -.super-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 24rpx 16rpx; +/* ===== 超级个体(横向滚动) ===== */ +.super-scroll { + white-space: nowrap; + width: 100%; + margin: 0 -32rpx; + padding: 0 32rpx; } + +.super-scroll::-webkit-scrollbar { + display: none; +} + +.super-scroll-inner { + display: inline-flex; + gap: 32rpx; + padding-bottom: 16rpx; +} + +.super-item-h { + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 16rpx; + min-width: 140rpx; +} + +.super-scroll .super-avatar { + width: 112rpx; + height: 112rpx; +} + +.super-scroll .super-name { + font-size: 20rpx; + max-width: 120rpx; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .super-item { display: flex; flex-direction: column; @@ -561,72 +643,159 @@ color: #00CED1; } -/* ===== 最新新增 ===== */ +/* ===== 最新新增(时间线样式) ===== */ +.latest-header { + margin-bottom: 32rpx; +} + .daily-badge-wrap { display: inline-flex; align-items: center; } +/* 设计稿 1:1:橙底白字 rounded-full */ .daily-badge { - background: #FF4500; - color: #fff; + background: #F6AD55; + color: #ffffff; font-size: 20rpx; - font-weight: 600; - padding: 4rpx 12rpx; - border-radius: 16rpx; + font-weight: 700; + padding: 8rpx 20rpx; + border-radius: 999rpx; margin-left: 8rpx; + box-shadow: 0 4rpx 16rpx rgba(246, 173, 85, 0.3); } -.latest-list { + +/* 设计稿 1:1:pl-3 竖线 left-3 top-2 bottom-2 w-[1px] bg-gray-800 */ +.timeline-wrap { + position: relative; + padding-left: 24rpx; +} + +.timeline-line { + position: absolute; + left: 23rpx; + top: 16rpx; + bottom: 16rpx; + width: 2rpx; + background: #2c2c2e; + z-index: 0; +} + +.timeline-list { display: flex; flex-direction: column; - gap: 12rpx; + gap: 48rpx; + position: relative; + z-index: 1; } -.latest-item { + +/* 设计稿:pl-6,分隔线在 content 内 */ +.timeline-item { + position: relative; + padding-left: 48rpx; + padding-bottom: 0; +} + +.timeline-item:last-child .timeline-content { + border-bottom: none; + padding-bottom: 0; +} + +/* 设计稿:left-[-4.5px] top-1.5 w-2.5 h-2.5 ring-4 ring-black */ +.timeline-dot { + position: absolute; + left: -9rpx; + top: 12rpx; + width: 20rpx; + height: 20rpx; + border-radius: 50%; + background: #2c2c2e; + box-shadow: 0 0 0 16rpx #000; + z-index: 2; +} + +.timeline-item-first .timeline-dot { + background: #4FD1C5; +} + +.timeline-content { + flex: 1; + padding-bottom: 32rpx; + border-bottom: 2rpx solid #1a1a1a; +} + +/* 设计稿:mb-1 justify-between gap-2 */ +.timeline-row { display: flex; justify-content: space-between; - align-items: center; - padding: 20rpx 24rpx; - background: rgba(255,255,255,0.04); - border-radius: 12rpx; - border-left: 4rpx solid #FF4500; + align-items: flex-start; + gap: 16rpx; + margin-bottom: 8rpx; } -.latest-left { + +.timeline-left { flex: 1; display: flex; align-items: center; - gap: 12rpx; + gap: 16rpx; min-width: 0; } + +.timeline-right { + display: flex; + flex-direction: column; + align-items: flex-end; + flex-shrink: 0; + padding-left: 16rpx; +} + +/* NEW 标签:黑底黄字黄色边框 */ .latest-new-tag { font-size: 18rpx; font-weight: 700; - color: #FF4500; - background: rgba(255,69,0,0.15); - padding: 2rpx 10rpx; - border-radius: 6rpx; + color: #F6AD55; + background: #000000; + padding: 6rpx 12rpx; + border-radius: 8rpx; + border: 2rpx solid #F6AD55; flex-shrink: 0; } -.latest-title { - font-size: 26rpx; - color: rgba(255,255,255,0.9); + +/* 设计稿:text-sm font-medium text-white */ +.timeline-title { + font-size: 28rpx; + color: #ffffff; + font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.latest-right { - display: flex; - align-items: center; - gap: 12rpx; - flex-shrink: 0; - margin-left: 12rpx; -} -.latest-price { + +/* 设计稿 1:1:价格/日期 light grey */ +.timeline-price { font-size: 26rpx; - font-weight: 600; - color: #FFD700; + font-weight: 700; + color: #A0AEC0; } -.latest-date { - font-size: 22rpx; - color: rgba(255,255,255,0.4); + +.timeline-date { + font-size: 20rpx; + color: #A0AEC0; + margin-top: 4rpx; +} + +/* 描述仅单行,超出省略 */ +.timeline-desc { + font-size: 24rpx; + color: #A0AEC0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: block; + line-height: 1; + min-width: 0; + width: 100%; + box-sizing: border-box; + height: 26rpx; } /* ===== 底部留白 ===== */ diff --git a/miniprogram/pages/match/match.js b/miniprogram/pages/match/match.js index 31a5055f..eff8b841 100644 --- a/miniprogram/pages/match/match.js +++ b/miniprogram/pages/match/match.js @@ -96,7 +96,7 @@ Page({ // 加载匹配配置 async loadMatchConfig() { try { - const res = await app.request({ url: '/api/match/config', silent: true, method: 'GET', + const res = await app.request({ url: '/api/miniprogram/match/config', silent: true, method: 'GET', method: 'GET' }) @@ -321,7 +321,7 @@ Page({ // 从数据库获取真实用户匹配 let matchedUser = null try { - const res = await app.request({ url: '/api/match/users', silent: true, + const res = await app.request({ url: '/api/miniprogram/match/users', silent: true, method: 'POST', data: { matchType: this.data.selectedType, @@ -405,7 +405,7 @@ Page({ // 上报匹配行为 async reportMatch(matchedUser) { try { - await app.request({ url: '/api/ckb/match', silent: true, + await app.request({ url: '/api/miniprogram/ckb/match', silent: true, method: 'POST', data: { matchType: this.data.selectedType, diff --git a/miniprogram/pages/my/my.js b/miniprogram/pages/my/my.js index e7c92b46..c857e4d0 100644 --- a/miniprogram/pages/my/my.js +++ b/miniprogram/pages/my/my.js @@ -671,7 +671,7 @@ Page({ app.globalData.userInfo = userInfo wx.setStorageSync('userInfo', userInfo) try { - await app.request('/api/user/update', { method: 'POST', data: { userId: userInfo.id, avatar: tempPath } }) + await app.request('/api/miniprogram/user/update', { method: 'POST', data: { userId: userInfo.id, avatar: tempPath } }) } catch (e) { console.log('头像同步失败', e) } wx.showToast({ title: '头像已更新', icon: 'success' }) } @@ -698,7 +698,7 @@ Page({ wx.showLoading({ title: '提交中...', mask: true }) try { const userId = app.globalData.userInfo?.id - await app.request({ url: '/api/withdraw', method: 'POST', data: { userId, amount } }) + await app.request({ url: '/api/miniprogram/withdraw', method: 'POST', data: { userId, amount } }) wx.hideLoading() wx.showToast({ title: '提现申请已提交', icon: 'success' }) this.loadMyEarnings() diff --git a/miniprogram/pages/purchases/purchases.js b/miniprogram/pages/purchases/purchases.js index ab6b73bb..a332a03c 100644 --- a/miniprogram/pages/purchases/purchases.js +++ b/miniprogram/pages/purchases/purchases.js @@ -20,7 +20,7 @@ Page({ try { const userId = app.globalData.userInfo?.id if (userId) { - const res = await app.request(`/api/orders?userId=${userId}`) + const res = await app.request(`/api/miniprogram/orders?userId=${userId}`) if (res && res.success && res.data) { const orders = (res.data || []).map(item => ({ id: item.id || item.order_sn, diff --git a/miniprogram/project.private.config.json b/miniprogram/project.private.config.json index 9df0293d..4aee70e9 100644 --- a/miniprogram/project.private.config.json +++ b/miniprogram/project.private.config.json @@ -3,7 +3,7 @@ "projectname": "miniprogram", "setting": { "compileHotReLoad": true, - "urlCheck": true, + "urlCheck": false, "coverView": true, "lazyloadPlaceholderEnable": false, "skylineRenderEnable": false, diff --git a/miniprogram2/.gitignore b/miniprogram2/.gitignore deleted file mode 100644 index 14ea590c..00000000 --- a/miniprogram2/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Windows -[Dd]esktop.ini -Thumbs.db -$RECYCLE.BIN/ - -# macOS -.DS_Store -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes - -# Node.js -node_modules/ diff --git a/miniprogram2/README.md b/miniprogram2/README.md deleted file mode 100644 index 2cc11dbf..00000000 --- a/miniprogram2/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/miniprogram2/app.js b/miniprogram2/app.js deleted file mode 100644 index 7d3ab6c9..00000000 --- a/miniprogram2/app.js +++ /dev/null @@ -1,575 +0,0 @@ -/** - * Soul创业派对 - 小程序入口 - * 开发: 卡若 - */ - -const { parseScene } = require('./utils/scene.js') - -App({ - globalData: { - // API基础地址 - 连接真实后端 - baseUrl: 'https://soulapi.quwanzhi.com', - // baseUrl: 'https://souldev.quwanzhi.com', - // baseUrl: 'http://localhost:3006', - // baseUrl: 'http://localhost:8080', - - // 小程序配置 - 真实AppID - appId: 'wxb8bbb2b10dec74aa', - - // 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗 - withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE', - - // 微信支付配置 - mchId: '1318592501', // 商户号 - - // 用户信息 - userInfo: null, - openId: null, // 微信openId,支付必需 - isLoggedIn: false, - - // 书籍数据 - bookData: null, - totalSections: 62, - - // 购买记录 - purchasedSections: [], - sectionMidMap: {}, // id -> mid,来自 purchase-status - hasFullBook: false, - matchCount: 0, - matchQuota: null, - - // 已读章节(仅统计有权限打开过的章节,用于首页「已读/待读」) - readSectionIds: [], - - // 推荐绑定 - pendingReferralCode: null, // 待绑定的推荐码 - - // 主题配置 - theme: { - brandColor: '#00CED1', - brandSecondary: '#20B2AA', - goldColor: '#FFD700', - bgColor: '#000000', - cardBg: '#1c1c1e' - }, - - // 系统信息 - systemInfo: null, - statusBarHeight: 44, - navBarHeight: 88, - - // TabBar相关 - currentTab: 0 - }, - - onLaunch(options) { - this.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || [] - // 获取系统信息 - this.getSystemInfo() - - // 检查登录状态 - this.checkLoginStatus() - - // 加载书籍数据 - this.loadBookData() - - // 检查更新 - this.checkUpdate() - - // 处理分享参数(推荐码绑定) - this.handleReferralCode(options) - }, - - // 小程序显示时也检查分享参数 - onShow(options) { - this.handleReferralCode(options) - }, - - // 处理推荐码绑定:官方以 options.scene 接收扫码参数(可同时带 mid/id + ref),与 utils/scene 解析闭环 - handleReferralCode(options) { - const query = options?.query || {} - let refCode = query.ref || query.referralCode - const sceneStr = (options && (typeof options.scene === 'string' ? options.scene : '')) || '' - if (sceneStr) { - const parsed = parseScene(sceneStr) - if (parsed.mid) this.globalData.initialSectionMid = parsed.mid - if (parsed.id) this.globalData.initialSectionId = parsed.id - if (parsed.ref) refCode = parsed.ref - } - if (refCode) { - console.log('[App] 检测到推荐码:', refCode) - - // 立即记录访问(不需要登录,用于统计"通过链接进的人数") - this.recordReferralVisit(refCode) - - // 保存待绑定的推荐码(不再在前端做"只能绑定一次"的限制,让后端根据30天规则判断续期/抢夺) - this.globalData.pendingReferralCode = refCode - wx.setStorageSync('pendingReferralCode', refCode) - // 同步写入 referral_code,供章节/找伙伴支付时传给后端,订单会记录 referrer_id 与 referral_code - wx.setStorageSync('referral_code', refCode) - - // 如果已登录,立即尝试绑定,由 /api/miniprogram/referral/bind 按 30 天规则决定 new / renew / takeover - if (this.globalData.isLoggedIn && this.globalData.userInfo) { - this.bindReferralCode(refCode) - } - } - }, - - // 记录推荐访问(不需要登录,用于统计) - async recordReferralVisit(refCode) { - try { - // 获取openId(如果有) - const openId = this.globalData.openId || wx.getStorageSync('openId') || '' - const userId = this.globalData.userInfo?.id || '' - - await this.request('/api/miniprogram/referral/visit', { - method: 'POST', - data: { - referralCode: refCode, - visitorOpenId: openId, - visitorId: userId, - source: 'miniprogram', - page: getCurrentPages()[getCurrentPages().length - 1]?.route || '' - }, - silent: true - }) - console.log('[App] 记录推荐访问成功') - } catch (e) { - console.log('[App] 记录推荐访问失败:', e.message) - // 忽略错误,不影响用户体验 - } - }, - - // 绑定推荐码到用户 - async bindReferralCode(refCode) { - try { - const userId = this.globalData.userInfo?.id - if (!userId || !refCode) return - - console.log('[App] 绑定推荐码:', refCode, '到用户:', userId) - - // 调用API绑定推荐关系 - const res = await this.request('/api/miniprogram/referral/bind', { - method: 'POST', - data: { - userId, - referralCode: refCode - }, - silent: true - }) - - if (res.success) { - console.log('[App] 推荐码绑定成功') - // 仅记录当前已绑定的推荐码,用于展示/调试;是否允许更换由后端根据30天规则判断 - wx.setStorageSync('boundReferralCode', refCode) - this.globalData.pendingReferralCode = null - wx.removeStorageSync('pendingReferralCode') - } - } catch (e) { - console.error('[App] 绑定推荐码失败:', e) - } - }, - - // 根据业务 id 从 bookData 查 mid(用于跳转) - getSectionMid(sectionId) { - const list = this.globalData.bookData || [] - const ch = list.find(c => c.id === sectionId) - return ch?.mid || 0 - }, - - // 获取当前用户的邀请码(用于分享带 ref,未登录返回空字符串) - getMyReferralCode() { - const user = this.globalData.userInfo - if (!user) return '' - if (user.referralCode) return user.referralCode - if (user.id) return 'SOUL' + String(user.id).toUpperCase().slice(-6) - return '' - }, - - // 获取系统信息 - getSystemInfo() { - try { - const systemInfo = wx.getSystemInfoSync() - this.globalData.systemInfo = systemInfo - this.globalData.statusBarHeight = systemInfo.statusBarHeight || 44 - - // 计算导航栏高度 - const menuButton = wx.getMenuButtonBoundingClientRect() - if (menuButton) { - this.globalData.navBarHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.height + systemInfo.statusBarHeight - } - } catch (e) { - console.error('获取系统信息失败:', e) - } - }, - - // 检查登录状态 - checkLoginStatus() { - try { - const userInfo = wx.getStorageSync('userInfo') - const token = wx.getStorageSync('token') - - if (userInfo && token) { - this.globalData.userInfo = userInfo - this.globalData.isLoggedIn = true - this.globalData.purchasedSections = userInfo.purchasedSections || [] - this.globalData.hasFullBook = userInfo.hasFullBook || false - } - } catch (e) { - console.error('检查登录状态失败:', e) - } - }, - - // 加载书籍数据 - async loadBookData() { - try { - // 先从缓存加载 - const cachedData = wx.getStorageSync('bookData') - if (cachedData) { - this.globalData.bookData = cachedData - } - - // 从服务器获取最新数据 - const res = await this.request('/api/miniprogram/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')) return - const updateManager = wx.getUpdateManager() - - updateManager.onCheckForUpdate((res) => { - if (res.hasUpdate) { - console.log('[App] 发现新版本,正在后台下载') - wx.showToast({ title: '发现新版本,正在准备…', icon: 'none', duration: 2000 }) - } - }) - - updateManager.onUpdateReady(() => { - wx.showModal({ - title: '发现新版本', - content: '小程序已更新,请立即重启以使用最新版本。', - confirmText: '立即更新', - cancelText: '稍后', - showCancel: true, - success: (res) => { - if (res.confirm) { - updateManager.applyUpdate() - } - } - }) - }) - - updateManager.onUpdateFailed(() => { - wx.showModal({ - title: '更新失败', - content: '新版本下载失败,请稍后重新打开小程序或删除后重新搜索打开。', - showCancel: false - }) - }) - }, - - /** - * 从 soul-api 返回体中取错误提示文案(兼容 message / error 字段) - */ - _getApiErrorMsg(data, defaultMsg = '请求失败') { - if (!data || typeof data !== 'object') return defaultMsg - const msg = data.message || data.error - return (msg && String(msg).trim()) ? String(msg).trim() : defaultMsg - }, - - /** - * 统一请求方法。接口失败时会弹窗提示(与 soul-api 返回的 message/error 一致)。 - * @param {string|object} urlOrOptions - 接口路径,或 { url, method, data, header, silent } - * @param {object} options - { method, data, header, silent } - * @param {boolean} options.silent - 为 true 时不弹窗,仅 reject(用于静默请求如访问统计) - */ - request(urlOrOptions, options = {}) { - let url - if (typeof urlOrOptions === 'string') { - url = urlOrOptions - } else if (urlOrOptions && typeof urlOrOptions === 'object' && urlOrOptions.url) { - url = urlOrOptions.url - options = { ...urlOrOptions, url: undefined } - } else { - url = '' - } - const silent = !!options.silent - const showError = (msg) => { - if (!silent && msg) { - wx.showToast({ title: msg, icon: 'none', duration: 2500 }) - } - } - - return new Promise((resolve, reject) => { - const token = wx.getStorageSync('token') - - wx.request({ - url: this.globalData.baseUrl + url, - method: options.method || 'GET', - data: options.data || {}, - header: { - 'Content-Type': 'application/json', - 'Authorization': token ? `Bearer ${token}` : '', - ...options.header - }, - success: (res) => { - const data = res.data - if (res.statusCode === 200) { - // 业务失败:success === false,soul-api 用 message 或 error 返回原因 - if (data && data.success === false) { - const msg = this._getApiErrorMsg(data, '操作失败') - showError(msg) - reject(new Error(msg)) - return - } - resolve(data) - return - } - if (res.statusCode === 401) { - this.logout() - showError('未授权,请重新登录') - reject(new Error('未授权')) - return - } - // 4xx/5xx:优先用返回体的 message/error - const msg = this._getApiErrorMsg(data, res.statusCode >= 500 ? '服务器异常,请稍后重试' : '请求失败') - showError(msg) - reject(new Error(msg)) - }, - fail: (err) => { - const msg = (err && err.errMsg) ? (err.errMsg.indexOf('timeout') !== -1 ? '请求超时,请重试' : '网络异常,请重试') : '网络异常,请重试' - showError(msg) - reject(new Error(msg)) - } - }) - }) - }, - - // 登录方法 - 获取openId用于支付(加固错误处理,避免审核报“登录报错”) - async login() { - try { - const loginRes = await new Promise((resolve, reject) => { - wx.login({ success: resolve, fail: reject }) - }) - if (!loginRes || !loginRes.code) { - console.warn('[App] wx.login 未返回 code') - wx.showToast({ title: '获取登录态失败,请重试', icon: 'none' }) - return null - } - try { - const res = await this.request('/api/miniprogram/login', { - method: 'POST', - data: { code: loginRes.code } - }) - - if (res.success && res.data) { - // 保存openId - if (res.data.openId) { - this.globalData.openId = res.data.openId - wx.setStorageSync('openId', res.data.openId) - console.log('[App] 获取openId成功') - } - - // 保存用户信息 - if (res.data.user) { - this.globalData.userInfo = res.data.user - this.globalData.isLoggedIn = true - this.globalData.purchasedSections = res.data.user.purchasedSections || [] - this.globalData.hasFullBook = res.data.user.hasFullBook || false - - wx.setStorageSync('userInfo', res.data.user) - wx.setStorageSync('token', res.data.token || '') - - // 登录成功后,检查待绑定的推荐码并执行绑定 - const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode - if (pendingRef) { - console.log('[App] 登录后自动绑定推荐码:', pendingRef) - this.bindReferralCode(pendingRef) - } - } - - return res.data - } - } catch (apiError) { - console.log('[App] API登录失败:', apiError.message) - // 不使用模拟登录,提示用户网络问题 - wx.showToast({ title: '网络异常,请重试', icon: 'none' }) - return null - } - - return null - } catch (e) { - console.error('[App] 登录失败:', e) - wx.showToast({ title: '登录失败,请重试', icon: 'none' }) - return null - } - }, - - // 获取openId (支付必需) - async getOpenId() { - // 先检查缓存 - const cachedOpenId = wx.getStorageSync('openId') - if (cachedOpenId) { - this.globalData.openId = cachedOpenId - return cachedOpenId - } - - // 没有缓存则登录获取 - try { - const loginRes = await new Promise((resolve, reject) => { - wx.login({ success: resolve, fail: reject }) - }) - - const res = await this.request('/api/miniprogram/login', { - method: 'POST', - data: { code: loginRes.code } - }) - - if (res.success && res.data?.openId) { - this.globalData.openId = res.data.openId - wx.setStorageSync('openId', res.data.openId) - // 接口同时返回 user 时视为登录,补全登录态并从登录开始绑定推荐码 - if (res.data.user) { - this.globalData.userInfo = res.data.user - this.globalData.isLoggedIn = true - this.globalData.purchasedSections = res.data.user.purchasedSections || [] - this.globalData.hasFullBook = res.data.user.hasFullBook || false - wx.setStorageSync('userInfo', res.data.user) - wx.setStorageSync('token', res.data.token || '') - const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode - if (pendingRef) { - console.log('[App] getOpenId 登录后自动绑定推荐码:', pendingRef) - this.bindReferralCode(pendingRef) - } - } - return res.data.openId - } - } catch (e) { - console.error('[App] 获取openId失败:', e) - } - - return null - }, - - // 模拟登录已废弃 - 不再使用 - // 现在必须使用真实的微信登录获取openId作为唯一标识 - mockLogin() { - console.warn('[App] mockLogin已废弃,请使用真实登录') - return null - }, - - // 手机号登录:需同时传 wx.login 的 code 与 getPhoneNumber 的 phoneCode - async loginWithPhone(phoneCode) { - try { - const loginRes = await new Promise((resolve, reject) => { - wx.login({ success: resolve, fail: reject }) - }) - if (!loginRes.code) { - wx.showToast({ title: '获取登录态失败', icon: 'none' }) - return null - } - const res = await this.request('/api/miniprogram/phone-login', { - method: 'POST', - data: { code: loginRes.code, phoneCode } - }) - - if (res.success && res.data) { - this.globalData.userInfo = res.data.user - this.globalData.isLoggedIn = true - this.globalData.purchasedSections = res.data.user.purchasedSections || [] - this.globalData.hasFullBook = res.data.user.hasFullBook || false - - wx.setStorageSync('userInfo', res.data.user) - wx.setStorageSync('token', res.data.token) - - // 登录成功后绑定推荐码 - const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode - if (pendingRef) { - console.log('[App] 手机号登录后自动绑定推荐码:', pendingRef) - this.bindReferralCode(pendingRef) - } - - return res.data - } - } catch (e) { - console.log('[App] 手机号登录失败:', e) - wx.showToast({ title: '登录失败,请重试', icon: 'none' }) - } - - return null - }, - - // 退出登录 - logout() { - this.globalData.userInfo = null - this.globalData.isLoggedIn = false - this.globalData.purchasedSections = [] - this.globalData.hasFullBook = false - this.globalData.matchCount = 0 - this.globalData.matchQuota = null - - wx.removeStorageSync('userInfo') - wx.removeStorageSync('token') - }, - - // 检查是否已购买章节 - hasPurchased(sectionId) { - if (this.globalData.hasFullBook) return true - return this.globalData.purchasedSections.includes(sectionId) - }, - - // 标记章节为已读(仅在有权限打开时由阅读页调用,用于首页已读/待读统计) - markSectionAsRead(sectionId) { - if (!sectionId) return - const list = this.globalData.readSectionIds || [] - if (list.includes(sectionId)) return - list.push(sectionId) - this.globalData.readSectionIds = list - wx.setStorageSync('readSectionIds', list) - }, - - // 已读章节数(用于首页展示) - getReadCount() { - return (this.globalData.readSectionIds || []).length - }, - - // 获取章节总数 - getTotalSections() { - return this.globalData.totalSections - }, - - // 切换TabBar - switchTab(index) { - this.globalData.currentTab = index - }, - - // 显示Toast - showToast(title, icon = 'none') { - wx.showToast({ - title, - icon, - duration: 2000 - }) - }, - - // 显示Loading - showLoading(title = '加载中...') { - wx.showLoading({ - title, - mask: true - }) - }, - - // 隐藏Loading - hideLoading() { - wx.hideLoading() - } -}) diff --git a/miniprogram2/app.json b/miniprogram2/app.json deleted file mode 100644 index e70546ce..00000000 --- a/miniprogram2/app.json +++ /dev/null @@ -1 +0,0 @@ -{"pages":["pages/index/index","pages/chapters/chapters","pages/match/match","pages/my/my","pages/read/read","pages/about/about","pages/agreement/agreement","pages/privacy/privacy","pages/referral/referral","pages/purchases/purchases","pages/settings/settings","pages/search/search","pages/addresses/addresses","pages/addresses/edit","pages/withdraw-records/withdraw-records","pages/scan/scan","pages/profile-edit/profile-edit"],"window":{"backgroundTextStyle":"light","navigationBarBackgroundColor":"#000000","navigationBarTitleText":"Soul创业派对","navigationBarTextStyle":"white","backgroundColor":"#000000","navigationStyle":"custom"},"tabBar":{"custom":true,"color":"#8e8e93","selectedColor":"#00CED1","backgroundColor":"#1c1c1e","borderStyle":"black","list":[{"pagePath":"pages/index/index","text":"首页"},{"pagePath":"pages/chapters/chapters","text":"目录"},{"pagePath":"pages/match/match","text":"找伙伴"},{"pagePath":"pages/my/my","text":"我的"}]},"usingComponents":{},"__usePrivacyCheck__":true,"permission":{"scope.userLocation":{"desc":"用于匹配附近的书友"}},"requiredPrivateInfos":["getLocation"],"lazyCodeLoading":"requiredComponents","style":"v2","sitemapLocation":"sitemap.json"} diff --git a/miniprogram2/app.wxss b/miniprogram2/app.wxss deleted file mode 100644 index 9ce22a06..00000000 --- a/miniprogram2/app.wxss +++ /dev/null @@ -1,606 +0,0 @@ -/** - * Soul创业实验 - 全局样式 - * 主题色: #00CED1 (Soul青色) - * 开发: 卡若 - */ - -/* ===== CSS 变量系统 ===== */ -page { - /* 品牌色 */ - --app-brand: #00CED1; - --app-brand-light: rgba(0, 206, 209, 0.1); - --app-brand-dark: #20B2AA; - - /* 背景色 */ - --app-bg-primary: #000000; - --app-bg-secondary: #1c1c1e; - --app-bg-tertiary: #2c2c2e; - - /* 文字色 */ - --app-text-primary: #ffffff; - --app-text-secondary: rgba(255, 255, 255, 0.7); - --app-text-tertiary: rgba(255, 255, 255, 0.4); - - /* 分隔线 */ - --app-separator: rgba(255, 255, 255, 0.05); - - /* iOS 系统色 */ - --ios-indigo: #5856D6; - --ios-green: #30d158; - --ios-red: #FF3B30; - --ios-orange: #FF9500; - --ios-yellow: #FFD700; - - /* 金色 */ - --gold: #FFD700; - --gold-light: #FFA500; - - /* 粉色 */ - --pink: #E91E63; - - /* 紫色 */ - --purple: #7B61FF; -} - -/* ===== 页面基础样式 ===== */ -page { - background-color: var(--app-bg-primary); - color: var(--app-text-primary); - font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', 'Microsoft YaHei', sans-serif; - font-size: 28rpx; - line-height: 1.5; - -webkit-font-smoothing: antialiased; -} - -/* ===== 全局容器 ===== */ -.container { - min-height: 100vh; - padding: 0; - background: #000000; - padding-bottom: env(safe-area-inset-bottom); -} - -/* ===== 品牌色系 ===== */ -.brand-color { - color: #00CED1; -} - -.brand-bg { - background-color: #00CED1; -} - -.brand-gradient { - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); -} - -.gold-color { - color: #FFD700; -} - -.gold-bg { - background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); -} - -/* ===== 文字渐变 ===== */ -.gradient-text { - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.gold-gradient-text { - background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -/* ===== 按钮样式 ===== */ -.btn-primary { - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); - color: #ffffff; - border: none; - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; - font-weight: 600; - box-shadow: 0 8rpx 24rpx rgba(0, 206, 209, 0.3); - display: flex; - align-items: center; - justify-content: center; -} - -.btn-primary::after { - border: none; -} - -.btn-primary:active { - opacity: 0.85; - transform: scale(0.98); -} - -.btn-secondary { - background: rgba(0, 206, 209, 0.1); - color: #00CED1; - border: 2rpx solid rgba(0, 206, 209, 0.3); - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; - font-weight: 500; -} - -.btn-secondary::after { - border: none; -} - -.btn-secondary:active { - background: rgba(0, 206, 209, 0.2); -} - -.btn-ghost { - background: rgba(255, 255, 255, 0.05); - color: #ffffff; - border: 2rpx solid rgba(255, 255, 255, 0.1); - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; -} - -.btn-ghost::after { - border: none; -} - -.btn-ghost:active { - background: rgba(255, 255, 255, 0.1); -} - -.btn-gold { - background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); - color: #000000; - border: none; - border-radius: 48rpx; - padding: 28rpx 48rpx; - font-size: 32rpx; - font-weight: 600; - box-shadow: 0 8rpx 24rpx rgba(255, 215, 0, 0.3); -} - -.btn-gold::after { - border: none; -} - -/* ===== 卡片样式 ===== */ -.card { - background: rgba(28, 28, 30, 0.9); - border-radius: 32rpx; - padding: 32rpx; - margin: 24rpx 32rpx; - border: 2rpx solid rgba(255, 255, 255, 0.05); -} - -.card-light { - background: rgba(44, 44, 46, 0.8); - border-radius: 24rpx; - padding: 24rpx; - border: 2rpx solid rgba(255, 255, 255, 0.08); -} - -.card-gradient { - background: linear-gradient(135deg, rgba(28, 28, 30, 1) 0%, rgba(44, 44, 46, 1) 100%); - border-radius: 32rpx; - padding: 32rpx; - border: 2rpx solid rgba(0, 206, 209, 0.2); -} - -.card-brand { - background: linear-gradient(135deg, rgba(0, 206, 209, 0.1) 0%, rgba(32, 178, 170, 0.05) 100%); - border-radius: 32rpx; - padding: 32rpx; - border: 2rpx solid rgba(0, 206, 209, 0.2); -} - -/* ===== 输入框样式 ===== */ -.input-ios { - background: rgba(0, 0, 0, 0.3); - border: 2rpx solid rgba(255, 255, 255, 0.1); - border-radius: 24rpx; - padding: 28rpx 32rpx; - font-size: 32rpx; - color: #ffffff; -} - -.input-ios:focus { - border-color: rgba(0, 206, 209, 0.5); -} - -.input-ios-placeholder { - color: rgba(255, 255, 255, 0.3); -} - -/* ===== 列表项样式 ===== */ -.list-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 28rpx 32rpx; - background: rgba(28, 28, 30, 0.9); - border-bottom: 1rpx solid rgba(255, 255, 255, 0.05); -} - -.list-item:first-child { - border-radius: 24rpx 24rpx 0 0; -} - -.list-item:last-child { - border-radius: 0 0 24rpx 24rpx; - border-bottom: none; -} - -.list-item:only-child { - border-radius: 24rpx; -} - -.list-item:active { - background: rgba(44, 44, 46, 1); -} - -/* ===== 标签样式 ===== */ -.tag { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 8rpx 20rpx; - min-width: 80rpx; - border-radius: 8rpx; - font-size: 22rpx; - font-weight: 500; - box-sizing: border-box; - text-align: center; -} - -.tag-brand { - background: rgba(0, 206, 209, 0.1); - color: #00CED1; -} - -.tag-gold { - background: rgba(255, 215, 0, 0.1); - color: #FFD700; -} - -.tag-pink { - background: rgba(233, 30, 99, 0.1); - color: #E91E63; -} - -.tag-purple { - background: rgba(123, 97, 255, 0.1); - color: #7B61FF; -} - -.tag-free { - background: rgba(0, 206, 209, 0.1); - color: #00CED1; -} - -/* ===== 分隔线 ===== */ -.divider { - height: 1rpx; - background: rgba(255, 255, 255, 0.05); - margin: 24rpx 0; -} - -.divider-vertical { - width: 2rpx; - height: 48rpx; - background: rgba(255, 255, 255, 0.1); -} - -/* ===== 骨架屏动画 ===== */ -.skeleton { - background: linear-gradient(90deg, - rgba(28, 28, 30, 1) 25%, - rgba(44, 44, 46, 1) 50%, - rgba(28, 28, 30, 1) 75% - ); - background-size: 200% 100%; - animation: skeleton-loading 1.5s ease-in-out infinite; - border-radius: 8rpx; -} - -@keyframes skeleton-loading { - 0% { - background-position: 200% 0; - } - 100% { - background-position: -200% 0; - } -} - -/* ===== 页面过渡动画 ===== */ -.page-transition { - animation: fadeIn 0.3s ease-out; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20rpx); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* ===== 弹窗动画 ===== */ -.modal-overlay { - animation: modalOverlayIn 0.25s ease-out; -} - -.modal-content { - animation: modalContentIn 0.3s cubic-bezier(0.32, 0.72, 0, 1); -} - -@keyframes modalOverlayIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes modalContentIn { - from { - opacity: 0; - transform: scale(0.95) translateY(20rpx); - } - to { - opacity: 1; - transform: scale(1) translateY(0); - } -} - -/* ===== 脉动动画 ===== */ -.pulse { - animation: pulse 2s ease-in-out infinite; -} - -@keyframes pulse { - 0%, 100% { - transform: scale(1); - opacity: 1; - } - 50% { - transform: scale(1.05); - opacity: 0.8; - } -} - -/* ===== 发光效果 ===== */ -.glow { - box-shadow: 0 0 40rpx rgba(0, 206, 209, 0.3); -} - -.glow-gold { - box-shadow: 0 0 40rpx rgba(255, 215, 0, 0.3); -} - -/* ===== 文字样式 ===== */ -.text-xs { - font-size: 22rpx; -} - -.text-sm { - font-size: 26rpx; -} - -.text-base { - font-size: 28rpx; -} - -.text-lg { - font-size: 32rpx; -} - -.text-xl { - font-size: 36rpx; -} - -.text-2xl { - font-size: 44rpx; -} - -.text-3xl { - font-size: 56rpx; -} - -.text-white { - color: #ffffff; -} - -.text-gray { - color: rgba(255, 255, 255, 0.6); -} - -.text-muted { - color: rgba(255, 255, 255, 0.4); -} - -.text-center { - text-align: center; -} - -.font-medium { - font-weight: 500; -} - -.font-semibold { - font-weight: 600; -} - -.font-bold { - font-weight: 700; -} - -/* ===== Flex布局 ===== */ -.flex { - display: flex; -} - -.flex-col { - flex-direction: column; -} - -.items-center { - align-items: center; -} - -.justify-center { - justify-content: center; -} - -.justify-between { - justify-content: space-between; -} - -.justify-around { - justify-content: space-around; -} - -.flex-1 { - flex: 1; -} - -.gap-1 { - gap: 8rpx; -} - -.gap-2 { - gap: 16rpx; -} - -.gap-3 { - gap: 24rpx; -} - -.gap-4 { - gap: 32rpx; -} - -/* ===== 间距 ===== */ -.p-2 { padding: 16rpx; } -.p-3 { padding: 24rpx; } -.p-4 { padding: 32rpx; } -.p-5 { padding: 40rpx; } - -.px-4 { padding-left: 32rpx; padding-right: 32rpx; } -.py-2 { padding-top: 16rpx; padding-bottom: 16rpx; } -.py-3 { padding-top: 24rpx; padding-bottom: 24rpx; } - -.m-4 { margin: 32rpx; } -.mx-4 { margin-left: 32rpx; margin-right: 32rpx; } -.my-3 { margin-top: 24rpx; margin-bottom: 24rpx; } -.mb-2 { margin-bottom: 16rpx; } -.mb-3 { margin-bottom: 24rpx; } -.mb-4 { margin-bottom: 32rpx; } -.mt-4 { margin-top: 32rpx; } - -/* ===== 圆角 ===== */ -.rounded { border-radius: 8rpx; } -.rounded-lg { border-radius: 16rpx; } -.rounded-xl { border-radius: 24rpx; } -.rounded-2xl { border-radius: 32rpx; } -.rounded-full { border-radius: 50%; } - -/* ===== 安全区域 ===== */ -.safe-bottom { - padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx); -} - -.pb-tabbar { - padding-bottom: 200rpx; -} - -/* ===== 头部导航占位 ===== */ -.nav-placeholder { - height: calc(88rpx + env(safe-area-inset-top, 44rpx)); -} - -/* ===== 隐藏滚动条 ===== */ -::-webkit-scrollbar { - display: none; - width: 0; - height: 0; -} - -/* ===== 触摸反馈 ===== */ -.touch-feedback { - transition: all 0.15s ease; -} - -.touch-feedback:active { - opacity: 0.7; - transform: scale(0.98); -} - -/* ===== 进度条 ===== */ -.progress-bar { - height: 8rpx; - background: rgba(44, 44, 46, 1); - border-radius: 4rpx; - overflow: hidden; -} - -.progress-fill { - height: 100%; - background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%); - border-radius: 4rpx; - transition: width 0.3s ease; -} - -/* ===== 头像样式 ===== */ -.avatar { - width: 80rpx; - height: 80rpx; - border-radius: 50%; - background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, rgba(32, 178, 170, 0.1) 100%); - display: flex; - align-items: center; - justify-content: center; - color: #00CED1; - font-weight: 700; - font-size: 32rpx; - border: 4rpx solid rgba(0, 206, 209, 0.3); -} - -.avatar-lg { - width: 120rpx; - height: 120rpx; - font-size: 48rpx; -} - -/* ===== 图标容器 ===== */ -.icon-box { - width: 64rpx; - height: 64rpx; - border-radius: 16rpx; - display: flex; - align-items: center; - justify-content: center; -} - -.icon-box-brand { - background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, rgba(32, 178, 170, 0.1) 100%); -} - -.icon-box-gold { - background: linear-gradient(135deg, rgba(255, 215, 0, 0.2) 0%, rgba(255, 165, 0, 0.1) 100%); -} - -/* ===== 渐变背景 ===== */ -.bg-gradient-dark { - background: linear-gradient(180deg, #000000 0%, #1a1a1a 100%); -} - -.bg-gradient-brand { - background: linear-gradient(135deg, rgba(0, 206, 209, 0.1) 0%, transparent 100%); -} diff --git a/miniprogram2/assets/icons/alert-circle.svg b/miniprogram2/assets/icons/alert-circle.svg deleted file mode 100644 index f5a441f3..00000000 --- a/miniprogram2/assets/icons/alert-circle.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/miniprogram2/assets/icons/arrow-right.svg b/miniprogram2/assets/icons/arrow-right.svg deleted file mode 100644 index 1dc64d3f..00000000 --- a/miniprogram2/assets/icons/arrow-right.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/assets/icons/bell.svg b/miniprogram2/assets/icons/bell.svg deleted file mode 100644 index 0e7e405b..00000000 --- a/miniprogram2/assets/icons/bell.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/assets/icons/book-open.svg b/miniprogram2/assets/icons/book-open.svg deleted file mode 100644 index d833e86b..00000000 --- a/miniprogram2/assets/icons/book-open.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/assets/icons/book.svg b/miniprogram2/assets/icons/book.svg deleted file mode 100644 index 93579576..00000000 --- a/miniprogram2/assets/icons/book.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/assets/icons/chevron-left.svg b/miniprogram2/assets/icons/chevron-left.svg deleted file mode 100644 index e406b2b9..00000000 --- a/miniprogram2/assets/icons/chevron-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram2/assets/icons/gift.svg b/miniprogram2/assets/icons/gift.svg deleted file mode 100644 index 66ac806c..00000000 --- a/miniprogram2/assets/icons/gift.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/miniprogram2/assets/icons/home-active.png b/miniprogram2/assets/icons/home-active.png deleted file mode 100644 index b6090d87..00000000 Binary files a/miniprogram2/assets/icons/home-active.png and /dev/null differ diff --git a/miniprogram2/assets/icons/home.png b/miniprogram2/assets/icons/home.png deleted file mode 100644 index 0ffba614..00000000 Binary files a/miniprogram2/assets/icons/home.png and /dev/null differ diff --git a/miniprogram2/assets/icons/home.svg b/miniprogram2/assets/icons/home.svg deleted file mode 100644 index 76244091..00000000 --- a/miniprogram2/assets/icons/home.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/assets/icons/image.svg b/miniprogram2/assets/icons/image.svg deleted file mode 100644 index 50ed9e6d..00000000 --- a/miniprogram2/assets/icons/image.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/miniprogram2/assets/icons/list.svg b/miniprogram2/assets/icons/list.svg deleted file mode 100644 index 688326aa..00000000 --- a/miniprogram2/assets/icons/list.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/miniprogram2/assets/icons/match-active.png b/miniprogram2/assets/icons/match-active.png deleted file mode 100644 index da62b436..00000000 Binary files a/miniprogram2/assets/icons/match-active.png and /dev/null differ diff --git a/miniprogram2/assets/icons/match.png b/miniprogram2/assets/icons/match.png deleted file mode 100644 index b15582e3..00000000 Binary files a/miniprogram2/assets/icons/match.png and /dev/null differ diff --git a/miniprogram2/assets/icons/message-circle.svg b/miniprogram2/assets/icons/message-circle.svg deleted file mode 100644 index 037560e9..00000000 --- a/miniprogram2/assets/icons/message-circle.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram2/assets/icons/my-active.png b/miniprogram2/assets/icons/my-active.png deleted file mode 100644 index da62b436..00000000 Binary files a/miniprogram2/assets/icons/my-active.png and /dev/null differ diff --git a/miniprogram2/assets/icons/my.png b/miniprogram2/assets/icons/my.png deleted file mode 100644 index b15582e3..00000000 Binary files a/miniprogram2/assets/icons/my.png and /dev/null differ diff --git a/miniprogram2/assets/icons/partners.svg b/miniprogram2/assets/icons/partners.svg deleted file mode 100644 index 80668312..00000000 --- a/miniprogram2/assets/icons/partners.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - diff --git a/miniprogram2/assets/icons/settings.svg b/miniprogram2/assets/icons/settings.svg deleted file mode 100644 index c7006ea8..00000000 --- a/miniprogram2/assets/icons/settings.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/assets/icons/share.svg b/miniprogram2/assets/icons/share.svg deleted file mode 100644 index 93179fc2..00000000 --- a/miniprogram2/assets/icons/share.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/miniprogram2/assets/icons/sparkles.svg b/miniprogram2/assets/icons/sparkles.svg deleted file mode 100644 index e2a4461f..00000000 --- a/miniprogram2/assets/icons/sparkles.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/miniprogram2/assets/icons/user.svg b/miniprogram2/assets/icons/user.svg deleted file mode 100644 index 8b190427..00000000 --- a/miniprogram2/assets/icons/user.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/assets/icons/users.svg b/miniprogram2/assets/icons/users.svg deleted file mode 100644 index 4816094b..00000000 --- a/miniprogram2/assets/icons/users.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/miniprogram2/assets/icons/wallet.svg b/miniprogram2/assets/icons/wallet.svg deleted file mode 100644 index 6d431e54..00000000 --- a/miniprogram2/assets/icons/wallet.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram2/components/icon/README.md b/miniprogram2/components/icon/README.md deleted file mode 100644 index 34e394c8..00000000 --- a/miniprogram2/components/icon/README.md +++ /dev/null @@ -1,175 +0,0 @@ -# Icon 图标组件 - -SVG 图标组件,参考 lucide-react 实现,用于在小程序中使用矢量图标。 - -**技术实现**: 使用 Base64 编码的 SVG + image 组件(小程序不支持直接使用 SVG 标签) - ---- - -## 使用方法 - -### 1. 在页面 JSON 中引入组件 - -```json -{ - "usingComponents": { - "icon": "/components/icon/icon" - } -} -``` - -### 2. 在 WXML 中使用 - -```xml - - - - - - - - - - - - - - - - - -``` - ---- - -## 属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -|-----|------|--------|-----| -| name | String | 'share' | 图标名称 | -| size | Number | 48 | 图标大小(rpx) | -| color | String | 'currentColor' | 图标颜色 | -| customClass | String | '' | 自定义类名 | -| customStyle | String | '' | 自定义样式 | - ---- - -## 可用图标 - -| 图标名称 | 说明 | 对应 lucide-react | -|---------|------|-------------------| -| `share` | 分享 | `` | -| `arrow-up-right` | 右上箭头 | `` | -| `chevron-left` | 左箭头 | `` | -| `search` | 搜索 | `` | -| `heart` | 心形 | `` | - ---- - -## 添加新图标 - -在 `icon.js` 的 `getSvgPath` 方法中添加新图标: - -```javascript -getSvgPath(name) { - const svgMap = { - 'new-icon': '', - // ... 其他图标 - } - return svgMap[name] || '' -} -``` - -**获取 SVG 代码**: 访问 [lucide.dev](https://lucide.dev) 搜索图标,复制 SVG 内容。 -**注意**: 颜色使用 `COLOR` 占位符,组件会自动替换。 - ---- - -## 样式定制 - -### 1. 使用 customClass - -```xml - -``` - -```css -.my-icon-class { - opacity: 0.8; -} -``` - -### 2. 使用 customStyle - -```xml - -``` - ---- - -## 技术说明 - -### 为什么使用 Base64 + image? - -1. **矢量图标**:任意缩放不失真 -2. **灵活着色**:通过 `COLOR` 占位符动态改变颜色 -3. **轻量级**:无需加载字体文件或外部图片 -4. **兼容性**:小程序不支持直接使用 SVG 标签,image 组件支持 Base64 SVG - -### 为什么不用字体图标? - -小程序对字体文件有限制,Base64 编码字体文件会增加包体积,SVG 图标更轻量。 - -### 与 lucide-react 的对应关系 - -- **lucide-react**: React 组件库,使用 SVG -- **本组件**: 小程序自定义组件,也使用 SVG -- **SVG path 数据**: 完全相同,从 lucide 官网复制 - ---- - -## 示例 - -### 悬浮分享按钮 - -```xml - -``` - -```css -.fab-share { - position: fixed; - right: 32rpx; - bottom: calc(120rpx + env(safe-area-inset-bottom)); - width: 96rpx; - height: 96rpx; - border-radius: 50%; - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); - display: flex; - align-items: center; - justify-content: center; -} -``` - ---- - -## 扩展图标库 - -可以继续添加更多 lucide-react 图标: - -- `star` - 星星 -- `wallet` - 钱包 -- `gift` - 礼物 -- `info` - 信息 -- `settings` - 设置 -- `user` - 用户 -- `book-open` - 打开的书 -- `eye` - 眼睛 -- `clock` - 时钟 -- `users` - 用户组 - ---- - -**图标组件创建完成!** 🎉 diff --git a/miniprogram2/components/icon/icon.js b/miniprogram2/components/icon/icon.js deleted file mode 100644 index b2dec23f..00000000 --- a/miniprogram2/components/icon/icon.js +++ /dev/null @@ -1,83 +0,0 @@ -// components/icon/icon.js -Component({ - properties: { - // 图标名称 - name: { - type: String, - value: 'share', - observer: 'updateIcon' - }, - // 图标大小(rpx) - size: { - type: Number, - value: 48 - }, - // 图标颜色 - color: { - type: String, - value: '#ffffff', - observer: 'updateIcon' - }, - // 自定义类名 - customClass: { - type: String, - value: '' - }, - // 自定义样式 - customStyle: { - type: String, - value: '' - } - }, - - data: { - svgData: '' - }, - - lifetimes: { - attached() { - this.updateIcon() - } - }, - - methods: { - // SVG 图标数据映射 - getSvgPath(name) { - const svgMap = { - 'share': '', - - 'arrow-up-right': '', - - 'chevron-left': '', - - 'search': '', - - 'heart': '' - } - - return svgMap[name] || '' - }, - - // 更新图标 - updateIcon() { - const { name, color } = this.data - let svgString = this.getSvgPath(name) - - if (svgString) { - // 替换颜色占位符 - svgString = svgString.replace(/COLOR/g, color) - - // 转换为 Base64 Data URL - const svgData = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}` - - this.setData({ - svgData: svgData - }) - } else { - this.setData({ - svgData: '' - }) - } - } - } -}) diff --git a/miniprogram2/components/icon/icon.json b/miniprogram2/components/icon/icon.json deleted file mode 100644 index a89ef4db..00000000 --- a/miniprogram2/components/icon/icon.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "component": true, - "usingComponents": {} -} diff --git a/miniprogram2/components/icon/icon.wxml b/miniprogram2/components/icon/icon.wxml deleted file mode 100644 index b1c29a25..00000000 --- a/miniprogram2/components/icon/icon.wxml +++ /dev/null @@ -1,5 +0,0 @@ - - - - {{name}} - diff --git a/miniprogram2/components/icon/icon.wxss b/miniprogram2/components/icon/icon.wxss deleted file mode 100644 index d12d2a0a..00000000 --- a/miniprogram2/components/icon/icon.wxss +++ /dev/null @@ -1,18 +0,0 @@ -/* components/icon/icon.wxss */ -.icon { - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.icon-image { - display: block; - width: 100%; - height: 100%; -} - -.icon-text { - font-size: 24rpx; - color: currentColor; -} diff --git a/miniprogram2/custom-tab-bar/index.js b/miniprogram2/custom-tab-bar/index.js deleted file mode 100644 index 4acd9546..00000000 --- a/miniprogram2/custom-tab-bar/index.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Soul创业实验 - 自定义TabBar组件 - * 根据后台配置动态显示/隐藏"找伙伴"按钮 - */ - -console.log('[TabBar] ===== 组件文件开始加载 =====') - -const app = getApp() -console.log('[TabBar] App 对象:', app) - -Component({ - data: { - selected: 0, - color: '#8e8e93', - selectedColor: '#00CED1', - matchEnabled: false, // 找伙伴功能开关,默认关闭 - list: [ - { - pagePath: '/pages/index/index', - text: '首页', - iconType: 'home' - }, - { - pagePath: '/pages/chapters/chapters', - text: '目录', - iconType: 'list' - }, - { - pagePath: '/pages/match/match', - text: '找伙伴', - iconType: 'match', - isSpecial: true - }, - { - pagePath: '/pages/my/my', - text: '我的', - iconType: 'user' - } - ] - }, - - lifetimes: { - attached() { - console.log('[TabBar] Component attached 生命周期触发') - this.loadFeatureConfig() - }, - ready() { - console.log('[TabBar] Component ready 生命周期触发') - // 如果 attached 中没有成功加载,在 ready 中再次尝试 - if (this.data.matchEnabled === undefined || this.data.matchEnabled === null) { - console.log('[TabBar] 在 ready 中重新加载配置') - this.loadFeatureConfig() - } - } - }, - - // 页面加载时也调用(兼容性更好) - attached() { - console.log('[TabBar] attached() 方法触发') - this.loadFeatureConfig() - }, - - methods: { - // 加载功能配置 - async loadFeatureConfig() { - try { - console.log('[TabBar] 开始加载功能配置...') - console.log('[TabBar] API地址:', app.globalData.baseUrl + '/api/miniprogram/config') - - // app.request 的第一个参数是 url 字符串,第二个参数是 options 对象 - const res = await app.request('/api/miniprogram/config', { - method: 'GET' - }) - - - // 兼容两种返回格式 - let matchEnabled = false - - if (res && res.success && res.features) { - console.log('[TabBar] features配置:', JSON.stringify(res.features)) - matchEnabled = res.features.matchEnabled === true - console.log('[TabBar] matchEnabled值:', matchEnabled) - } else if (res && res.configs && res.configs.feature_config) { - // 备用格式:从 configs.feature_config 读取 - console.log('[TabBar] 使用备用格式,从configs读取') - matchEnabled = res.configs.feature_config.matchEnabled === true - console.log('[TabBar] matchEnabled值:', matchEnabled) - } else { - console.log('[TabBar] ⚠️ 未找到features配置,使用默认值false') - console.log('[TabBar] res对象keys:', Object.keys(res || {})) - } - - this.setData({ matchEnabled }, () => { - console.log('[TabBar] ✅ matchEnabled已设置为:', this.data.matchEnabled) - // 配置加载完成后,根据当前路由设置选中状态 - this.updateSelected() - }) - - // 如果当前在找伙伴页面,但功能已关闭,跳转到首页 - if (!matchEnabled) { - const pages = getCurrentPages() - const currentPage = pages[pages.length - 1] - if (currentPage && currentPage.route === 'pages/match/match') { - console.log('[TabBar] 找伙伴功能已关闭,从match页面跳转到首页') - wx.switchTab({ url: '/pages/index/index' }) - } - } - } catch (error) { - console.log('[TabBar] ❌ 加载功能配置失败:', error) - console.log('[TabBar] 错误详情:', error.message || error) - // 默认关闭找伙伴功能 - this.setData({ matchEnabled: false }, () => { - this.updateSelected() - }) - } - }, - - // 根据当前路由更新选中状态 - updateSelected() { - const pages = getCurrentPages() - if (pages.length === 0) return - - const currentPage = pages[pages.length - 1] - const route = currentPage.route - - let selected = 0 - const { matchEnabled } = this.data - - // 根据路由匹配对应的索引 - if (route === 'pages/index/index') { - selected = 0 - } else if (route === 'pages/chapters/chapters') { - selected = 1 - } else if (route === 'pages/match/match') { - selected = 2 - } else if (route === 'pages/my/my') { - selected = matchEnabled ? 3 : 2 - } - - this.setData({ selected }) - }, - - switchTab(e) { - const data = e.currentTarget.dataset - const url = data.path - const index = data.index - - if (this.data.selected === index) return - - wx.switchTab({ url }) - } - } -}) diff --git a/miniprogram2/custom-tab-bar/index.json b/miniprogram2/custom-tab-bar/index.json deleted file mode 100644 index 467ce294..00000000 --- a/miniprogram2/custom-tab-bar/index.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "component": true -} diff --git a/miniprogram2/custom-tab-bar/index.wxml b/miniprogram2/custom-tab-bar/index.wxml deleted file mode 100644 index 73369b2a..00000000 --- a/miniprogram2/custom-tab-bar/index.wxml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - {{list[0].text}} - - - - - - - - {{list[1].text}} - - - - - - - - {{list[2].text}} - - - - - - - - {{list[3].text}} - - diff --git a/miniprogram2/custom-tab-bar/index.wxss b/miniprogram2/custom-tab-bar/index.wxss deleted file mode 100644 index 98036655..00000000 --- a/miniprogram2/custom-tab-bar/index.wxss +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Soul创业实验 - 自定义TabBar样式 - * 实现中间突出的"找伙伴"按钮 - */ - -.tab-bar { - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: 100rpx; - background: rgba(28, 28, 30, 0.95); - backdrop-filter: blur(40rpx); - -webkit-backdrop-filter: blur(40rpx); - display: flex; - align-items: flex-end; - padding-bottom: env(safe-area-inset-bottom); - z-index: 999; -} - -/* 三个tab布局(找伙伴功能关闭时) */ -.tab-bar-three .tab-bar-item { - flex: 1; -} - -/* 四个tab布局(找伙伴功能开启时) */ -.tab-bar-four .tab-bar-item { - flex: 1; -} - -.tab-bar-border { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 1rpx; - background: rgba(255, 255, 255, 0.05); -} - -.tab-bar-item { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 10rpx 0 16rpx; -} - -.icon-wrapper { - width: 48rpx; - height: 48rpx; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 4rpx; -} - -.icon { - width: 44rpx; - height: 44rpx; - display: flex; - align-items: center; - justify-content: center; -} - -.tab-bar-text { - font-size: 22rpx; - line-height: 1; -} - -/* ===== SVG 图标样式 ===== */ -.tab-icon { - width: 48rpx; - height: 48rpx; - display: block; - filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); -} - -.tab-icon.icon-active { - filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); -} - - -/* ===== 找伙伴 - 中间特殊按钮 ===== */ -.special-item { - position: relative; - margin-top: -32rpx; -} - -.special-button { - width: 112rpx; - height: 112rpx; - border-radius: 50%; - background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 8rpx 32rpx rgba(0, 206, 209, 0.4); - margin-bottom: 4rpx; - transition: all 0.2s ease; -} - -.special-button:active { - transform: scale(0.95); -} - -.special-active { - box-shadow: 0 8rpx 40rpx rgba(0, 206, 209, 0.6); -} - -.special-text { - margin-top: 4rpx; -} - -/* ===== 找伙伴特殊按钮图标 ===== */ -.special-icon { - width: 80rpx; - height: 80rpx; - display: block; - filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%); -} diff --git a/miniprogram2/pages/about/about.js b/miniprogram2/pages/about/about.js deleted file mode 100644 index c4f6b68e..00000000 --- a/miniprogram2/pages/about/about.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Soul创业派对 - 关于作者页 - * 开发: 卡若 - */ -const app = getApp() - -Page({ - data: { - statusBarHeight: 44, - author: { - name: '卡若', - avatar: 'K', - title: 'Soul派对房主理人 · 私域运营专家', - bio: '每天早上6点到9点,在Soul派对房分享真实的创业故事。专注私域运营与项目变现,用"云阿米巴"模式帮助创业者构建可持续的商业体系。本书记录了62个真实商业案例,涵盖电商、内容、传统行业等多个领域。', - stats: [ - { label: '商业案例', value: '62' }, - { label: '连续直播', value: '365天' }, - { label: '派对分享', value: '1000+' } - ], - // 联系方式已移至后台配置 - contact: null, - highlights: [ - '5年私域运营经验', - '帮助100+品牌从0到1增长', - '连续创业者,擅长商业模式设计' - ] - }, - bookInfo: { - title: '一场Soul的创业实验', - totalChapters: 62, - parts: [ - { name: '真实的人', chapters: 10 }, - { name: '真实的行业', chapters: 15 }, - { name: '真实的错误', chapters: 9 }, - { name: '真实的赚钱', chapters: 20 }, - { name: '真实的社会', chapters: 9 } - ], - price: 9.9 - } - }, - - onLoad() { - this.setData({ - statusBarHeight: app.globalData.statusBarHeight - }) - this.loadBookStats() - }, - - // 加载书籍统计 - async loadBookStats() { - try { - const res = await app.request('/api/miniprogram/book/stats') - if (res && res.success) { - this.setData({ - 'bookInfo.totalChapters': res.data?.totalChapters || 62, - 'author.stats': [ - { label: '商业案例', value: String(res.data?.totalChapters || 62) }, - { label: '连续直播', value: '365天' }, - { label: '派对分享', value: '1000+' } - ] - }) - } - } catch (e) { - console.log('[About] 加载书籍统计失败,使用默认值') - } - }, - - // 联系方式功能已禁用 - copyWechat() { - wx.showToast({ title: '请在派对房联系作者', icon: 'none' }) - }, - - callPhone() { - wx.showToast({ title: '请在派对房联系作者', icon: 'none' }) - }, - - // 返回 - goBack() { - wx.navigateBack() - }, - - onShareAppMessage() { - const ref = app.getMyReferralCode() - return { - title: 'Soul创业派对 - 关于作者', - path: ref ? `/pages/about/about?ref=${ref}` : '/pages/about/about' - } - } -}) diff --git a/miniprogram2/pages/about/about.json b/miniprogram2/pages/about/about.json deleted file mode 100644 index e90e9960..00000000 --- a/miniprogram2/pages/about/about.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "usingComponents": {}, - "navigationStyle": "custom" -} diff --git a/miniprogram2/pages/about/about.wxml b/miniprogram2/pages/about/about.wxml deleted file mode 100644 index 598e9464..00000000 --- a/miniprogram2/pages/about/about.wxml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - 关于作者 - - - - - - - - {{author.avatar}} - {{author.name}} - {{author.title}} - {{author.bio}} - - - - - {{item.value}} - {{item.label}} - - - - - - - - {{item}} - - - - - - - 📚 {{bookInfo.title}} - - - {{bookInfo.totalChapters}} - 篇章节 - - - 5 - 大篇章 - - - ¥{{bookInfo.price}} - 全书价格 - - - - - {{item.name}} - {{item.chapters}}节 - - - - - - - 联系作者 - - 🎉 - - Soul派对房 - 每天早上6-9点开播 - - - - 在Soul App搜索"创业实验"或"卡若",加入派对房直接交流 - - - - diff --git a/miniprogram2/pages/about/about.wxss b/miniprogram2/pages/about/about.wxss deleted file mode 100644 index 337aa041..00000000 --- a/miniprogram2/pages/about/about.wxss +++ /dev/null @@ -1,40 +0,0 @@ -.page { min-height: 100vh; background: #000; } -.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; } -.nav-back { width: 72rpx; height: 72rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: #fff; } -.nav-title { font-size: 36rpx; font-weight: 600; color: #00CED1; } -.nav-placeholder { width: 72rpx; } -.content { padding: 32rpx; } -.author-card { background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%); border-radius: 32rpx; padding: 48rpx; text-align: center; margin-bottom: 24rpx; border: 2rpx solid rgba(0,206,209,0.2); } -.author-avatar { width: 160rpx; height: 160rpx; border-radius: 50%; background: linear-gradient(135deg, #00CED1, #20B2AA); display: flex; align-items: center; justify-content: center; margin: 0 auto 24rpx; font-size: 64rpx; color: #fff; font-weight: 700; border: 4rpx solid rgba(0,206,209,0.3); } -.author-name { font-size: 40rpx; font-weight: 700; color: #fff; display: block; margin-bottom: 8rpx; } -.author-title { font-size: 26rpx; color: #00CED1; display: block; margin-bottom: 24rpx; } -.author-bio { font-size: 26rpx; color: rgba(255,255,255,0.7); line-height: 1.8; display: block; margin-bottom: 32rpx; } -.stats-row { display: flex; justify-content: space-around; padding-top: 32rpx; border-top: 2rpx solid rgba(255,255,255,0.1); } -.stat-item { text-align: center; } -.stat-value { font-size: 36rpx; font-weight: 700; color: #00CED1; display: block; } -.stat-label { font-size: 22rpx; color: rgba(255,255,255,0.5); } -.contact-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; } -.card-title { font-size: 28rpx; font-weight: 600; color: #fff; display: block; margin-bottom: 24rpx; } -.contact-item { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 16rpx; } -.contact-item:last-child { margin-bottom: 0; } -.contact-icon { font-size: 40rpx; } -.contact-info { flex: 1; } -.contact-label { font-size: 22rpx; color: rgba(255,255,255,0.5); display: block; } -.contact-value { font-size: 28rpx; color: #fff; } -.contact-btn { padding: 12rpx 24rpx; background: rgba(0,206,209,0.2); color: #00CED1; font-size: 24rpx; border-radius: 16rpx; } - -/* 亮点标签 */ -.highlights { display: flex; flex-wrap: wrap; gap: 16rpx; margin-top: 32rpx; padding-top: 24rpx; border-top: 2rpx solid rgba(255,255,255,0.1); justify-content: center; } -.highlight-tag { display: flex; align-items: center; gap: 8rpx; padding: 12rpx 24rpx; background: rgba(0,206,209,0.15); border-radius: 24rpx; font-size: 24rpx; color: rgba(255,255,255,0.8); } -.tag-icon { color: #00CED1; font-size: 22rpx; } - -/* 书籍信息卡片 */ -.book-info-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; margin-bottom: 24rpx; } -.book-stats { display: flex; justify-content: space-around; padding: 24rpx 0; margin: 16rpx 0; background: rgba(0,0,0,0.3); border-radius: 16rpx; } -.book-stat { text-align: center; } -.book-stat-value { font-size: 36rpx; font-weight: 700; color: #FFD700; display: block; } -.book-stat-label { font-size: 22rpx; color: rgba(255,255,255,0.5); } -.parts-list { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 16rpx; } -.part-item { display: flex; align-items: center; gap: 8rpx; padding: 12rpx 20rpx; background: rgba(255,255,255,0.05); border-radius: 12rpx; } -.part-name { font-size: 24rpx; color: rgba(255,255,255,0.8); } -.part-chapters { font-size: 22rpx; color: #00CED1; } diff --git a/miniprogram2/pages/addresses/addresses.js b/miniprogram2/pages/addresses/addresses.js deleted file mode 100644 index cd13fe90..00000000 --- a/miniprogram2/pages/addresses/addresses.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * 收货地址列表页 - * 参考 Next.js: app/view/my/addresses/page.tsx - */ - -const app = getApp() - -Page({ - data: { - statusBarHeight: 44, - isLoggedIn: false, - addressList: [], - loading: true - }, - - onLoad() { - this.setData({ - statusBarHeight: app.globalData.statusBarHeight || 44 - }) - this.checkLogin() - }, - - onShow() { - if (this.data.isLoggedIn) { - this.loadAddresses() - } - }, - - // 检查登录状态 - checkLogin() { - const isLoggedIn = app.globalData.isLoggedIn - const userId = app.globalData.userInfo?.id - - if (!isLoggedIn || !userId) { - wx.showModal({ - title: '需要登录', - content: '请先登录后再管理收货地址', - confirmText: '去登录', - success: (res) => { - if (res.confirm) { - wx.switchTab({ url: '/pages/my/my' }) - } else { - wx.navigateBack() - } - } - }) - return - } - - this.setData({ isLoggedIn: true }) - this.loadAddresses() - }, - - // 加载地址列表 - async loadAddresses() { - const userId = app.globalData.userInfo?.id - if (!userId) return - - this.setData({ loading: true }) - - try { - const res = await app.request(`/api/miniprogram/user/addresses?userId=${userId}`) - if (res.success && res.list) { - this.setData({ - addressList: res.list, - loading: false - }) - } else { - this.setData({ addressList: [], loading: false }) - } - } catch (e) { - console.error('加载地址列表失败:', e) - this.setData({ loading: false }) - wx.showToast({ title: '加载失败', icon: 'none' }) - } - }, - - // 编辑地址 - editAddress(e) { - const id = e.currentTarget.dataset.id - wx.navigateTo({ url: `/pages/addresses/edit?id=${id}` }) - }, - - // 删除地址 - deleteAddress(e) { - const id = e.currentTarget.dataset.id - - wx.showModal({ - title: '确认删除', - content: '确定要删除该收货地址吗?', - confirmColor: '#FF3B30', - success: async (res) => { - if (res.confirm) { - try { - const result = await app.request(`/api/miniprogram/user/addresses/${id}`, { - method: 'DELETE' - }) - - if (result.success) { - wx.showToast({ title: '删除成功', icon: 'success' }) - this.loadAddresses() - } else { - wx.showToast({ title: result.message || '删除失败', icon: 'none' }) - } - } catch (e) { - console.error('删除地址失败:', e) - wx.showToast({ title: '删除失败', icon: 'none' }) - } - } - } - }) - }, - - // 新增地址 - addAddress() { - wx.navigateTo({ url: '/pages/addresses/edit' }) - }, - - // 返回 - goBack() { - wx.navigateBack() - }, - - onShareAppMessage() { - const ref = app.getMyReferralCode() - return { - title: 'Soul创业派对 - 收货地址', - path: ref ? `/pages/addresses/addresses?ref=${ref}` : '/pages/addresses/addresses' - } - } -}) diff --git a/miniprogram2/pages/addresses/addresses.json b/miniprogram2/pages/addresses/addresses.json deleted file mode 100644 index 2e45b65e..00000000 --- a/miniprogram2/pages/addresses/addresses.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "usingComponents": {}, - "navigationStyle": "custom", - "enablePullDownRefresh": false -} diff --git a/miniprogram2/pages/addresses/addresses.wxml b/miniprogram2/pages/addresses/addresses.wxml deleted file mode 100644 index cec2ef6e..00000000 --- a/miniprogram2/pages/addresses/addresses.wxml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - 收货地址 - - - - - - - - 加载中... - - - - - 📍 - 暂无收货地址 - 点击下方按钮添加 - - - - - - - {{item.name}} - {{item.phone}} - 默认 - - {{item.fullAddress}} - - - ✏️ - 编辑 - - - 🗑️ - 删除 - - - - - - - - - 新增收货地址 - - - diff --git a/miniprogram2/pages/addresses/addresses.wxss b/miniprogram2/pages/addresses/addresses.wxss deleted file mode 100644 index 9ff21637..00000000 --- a/miniprogram2/pages/addresses/addresses.wxss +++ /dev/null @@ -1,217 +0,0 @@ -/** - * 收货地址列表页样式 - */ - -.page { - min-height: 100vh; - background: #000000; - padding-bottom: 200rpx; -} - -/* ===== 导航栏 ===== */ -.nav-bar { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 100; - background: rgba(0, 0, 0, 0.9); - backdrop-filter: blur(40rpx); - border-bottom: 1rpx solid rgba(255, 255, 255, 0.05); -} - -.nav-bar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 32rpx; - height: 88rpx; -} - -.nav-back { - width: 64rpx; - height: 64rpx; - border-radius: 50%; - background: rgba(255, 255, 255, 0.1); - display: flex; - align-items: center; - justify-content: center; -} - -.nav-back:active { - background: rgba(255, 255, 255, 0.15); -} - -.back-icon { - font-size: 48rpx; - color: #ffffff; - line-height: 1; -} - -.nav-title { - flex: 1; - text-align: center; - font-size: 36rpx; - font-weight: 600; - color: #ffffff; -} - -.nav-placeholder { - width: 64rpx; -} - -/* ===== 内容区 ===== */ -.content { - padding: 32rpx; -} - -/* ===== 加载状态 ===== */ -.loading-state { - padding: 240rpx 0; - text-align: center; -} - -.loading-text { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.4); -} - -/* ===== 空状态 ===== */ -.empty-state { - padding: 240rpx 0; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; -} - -.empty-icon { - font-size: 96rpx; - margin-bottom: 24rpx; - opacity: 0.3; -} - -.empty-text { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.6); - margin-bottom: 16rpx; -} - -.empty-tip { - font-size: 24rpx; - color: rgba(255, 255, 255, 0.4); -} - -/* ===== 地址列表 ===== */ -.address-list { - margin-bottom: 24rpx; -} - -.address-card { - background: #1c1c1e; - border-radius: 24rpx; - border: 2rpx solid rgba(255, 255, 255, 0.05); - padding: 32rpx; - margin-bottom: 24rpx; -} - -/* 地址头部 */ -.address-header { - display: flex; - align-items: center; - gap: 16rpx; - margin-bottom: 16rpx; -} - -.receiver-name { - font-size: 32rpx; - font-weight: 600; - color: #ffffff; -} - -.receiver-phone { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.5); -} - -.default-tag { - font-size: 22rpx; - color: #00CED1; - background: rgba(0, 206, 209, 0.2); - padding: 6rpx 16rpx; - border-radius: 8rpx; - margin-left: auto; -} - -/* 地址文本 */ -.address-text { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.6); - line-height: 1.6; - display: block; - margin-bottom: 24rpx; - padding-bottom: 24rpx; - border-bottom: 2rpx solid rgba(255, 255, 255, 0.05); -} - -/* 操作按钮 */ -.address-actions { - display: flex; - justify-content: flex-end; - gap: 32rpx; -} - -.action-btn { - display: flex; - align-items: center; - gap: 8rpx; - padding: 8rpx 0; -} - -.action-btn:active { - opacity: 0.6; -} - -.edit-btn { - color: #00CED1; -} - -.delete-btn { - color: #FF3B30; -} - -.action-icon { - font-size: 28rpx; -} - -.action-text { - font-size: 28rpx; -} - -/* ===== 新增按钮 ===== */ -.add-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 16rpx; - padding: 32rpx; - background: #00CED1; - border-radius: 24rpx; - font-weight: 600; - margin-top: 48rpx; -} - -.add-btn:active { - opacity: 0.8; - transform: scale(0.98); -} - -.add-icon { - font-size: 36rpx; - color: #000000; -} - -.add-text { - font-size: 32rpx; - color: #000000; -} diff --git a/miniprogram2/pages/addresses/edit.js b/miniprogram2/pages/addresses/edit.js deleted file mode 100644 index 4f45893c..00000000 --- a/miniprogram2/pages/addresses/edit.js +++ /dev/null @@ -1,209 +0,0 @@ -/** - * 地址编辑页(新增/编辑) - * 参考 Next.js: app/view/my/addresses/[id]/page.tsx - */ - -const app = getApp() - -Page({ - data: { - statusBarHeight: 44, - isEdit: false, // 是否为编辑模式 - addressId: null, - - // 表单数据 - name: '', - phone: '', - province: '', - city: '', - district: '', - detail: '', - isDefault: false, - - // 地区选择器 - region: [], - - saving: false - }, - - onLoad(options) { - this.setData({ - statusBarHeight: app.globalData.statusBarHeight || 44 - }) - - // 如果有 id 参数,则为编辑模式 - if (options.id) { - this.setData({ - isEdit: true, - addressId: options.id - }) - this.loadAddress(options.id) - } - }, - - // 加载地址详情(编辑模式) - async loadAddress(id) { - wx.showLoading({ title: '加载中...', mask: true }) - - try { - const res = await app.request(`/api/miniprogram/user/addresses/${id}`) - if (res.success && res.data) { - const addr = res.data - this.setData({ - name: addr.name || '', - phone: addr.phone || '', - province: addr.province || '', - city: addr.city || '', - district: addr.district || '', - detail: addr.detail || '', - isDefault: addr.isDefault || false, - region: [addr.province, addr.city, addr.district] - }) - } else { - wx.showToast({ title: '加载失败', icon: 'none' }) - } - } catch (e) { - console.error('加载地址详情失败:', e) - wx.showToast({ title: '加载失败', icon: 'none' }) - } finally { - wx.hideLoading() - } - }, - - // 表单输入 - onNameInput(e) { - this.setData({ name: e.detail.value }) - }, - - onPhoneInput(e) { - this.setData({ phone: e.detail.value.replace(/\D/g, '').slice(0, 11) }) - }, - - onDetailInput(e) { - this.setData({ detail: e.detail.value }) - }, - - // 地区选择 - onRegionChange(e) { - const region = e.detail.value - this.setData({ - region, - province: region[0], - city: region[1], - district: region[2] - }) - }, - - // 切换默认地址 - onDefaultChange(e) { - this.setData({ isDefault: e.detail.value }) - }, - - // 表单验证 - validateForm() { - const { name, phone, province, city, district, detail } = this.data - - if (!name || name.trim().length === 0) { - wx.showToast({ title: '请输入收货人姓名', icon: 'none' }) - return false - } - - if (!phone || phone.length !== 11) { - wx.showToast({ title: '请输入正确的手机号', icon: 'none' }) - return false - } - - if (!province || !city || !district) { - wx.showToast({ title: '请选择省市区', icon: 'none' }) - return false - } - - if (!detail || detail.trim().length === 0) { - wx.showToast({ title: '请输入详细地址', icon: 'none' }) - return false - } - - return true - }, - - // 保存地址 - async saveAddress() { - if (!this.validateForm()) return - if (this.data.saving) return - - this.setData({ saving: true }) - wx.showLoading({ title: '保存中...', mask: true }) - - const { isEdit, addressId, name, phone, province, city, district, detail, isDefault } = this.data - const userId = app.globalData.userInfo?.id - - if (!userId) { - wx.hideLoading() - wx.showToast({ title: '请先登录', icon: 'none' }) - this.setData({ saving: false }) - return - } - - const addressData = { - userId, - name, - phone, - province, - city, - district, - detail, - fullAddress: `${province}${city}${district}${detail}`, - isDefault - } - - try { - let res - if (isEdit) { - // 编辑模式 - PUT 请求 - res = await app.request(`/api/miniprogram/user/addresses/${addressId}`, { - method: 'PUT', - data: addressData - }) - } else { - // 新增模式 - POST 请求 - res = await app.request('/api/miniprogram/user/addresses', { - method: 'POST', - data: addressData - }) - } - - if (res.success) { - wx.hideLoading() - wx.showToast({ - title: isEdit ? '保存成功' : '添加成功', - icon: 'success' - }) - setTimeout(() => { - wx.navigateBack() - }, 1500) - } else { - wx.hideLoading() - wx.showToast({ title: res.message || '保存失败', icon: 'none' }) - this.setData({ saving: false }) - } - } catch (e) { - console.error('保存地址失败:', e) - wx.hideLoading() - wx.showToast({ title: '保存失败', icon: 'none' }) - this.setData({ saving: false }) - } - }, - - // 返回 - goBack() { - wx.navigateBack() - }, - - onShareAppMessage() { - const ref = app.getMyReferralCode() - return { - title: 'Soul创业派对 - 编辑地址', - path: ref ? `/pages/addresses/edit?ref=${ref}` : '/pages/addresses/edit' - } - } -}) diff --git a/miniprogram2/pages/addresses/edit.json b/miniprogram2/pages/addresses/edit.json deleted file mode 100644 index 2e45b65e..00000000 --- a/miniprogram2/pages/addresses/edit.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "usingComponents": {}, - "navigationStyle": "custom", - "enablePullDownRefresh": false -} diff --git a/miniprogram2/pages/addresses/edit.wxml b/miniprogram2/pages/addresses/edit.wxml deleted file mode 100644 index c5429207..00000000 --- a/miniprogram2/pages/addresses/edit.wxml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - {{isEdit ? '编辑地址' : '新增地址'}} - - - - - - - - - - 👤 - 收货人 - - - - - - - - 📱 - 手机号 - - - - - - - - 📍 - 所在地区 - - - - {{province || city || district ? province + ' ' + city + ' ' + district : '请选择省市区'}} - - - - - - - - 🏠 - 详细地址 - - + + + + +
+

+handshake + 互助需求 +

+ + +
+
+

+rocket_launch + 项目介绍 +

+ +
+
+ +
+ + + + + \ No newline at end of file diff --git a/临时需求池/资料页+编辑资料/编辑资料页/screen.png b/临时需求池/资料页+编辑资料/编辑资料页/screen.png new file mode 100644 index 00000000..47bec69f Binary files /dev/null and b/临时需求池/资料页+编辑资料/编辑资料页/screen.png differ diff --git a/临时需求池/资料页+编辑资料/超级个体点进去的/code.html b/临时需求池/资料页+编辑资料/超级个体点进去的/code.html new file mode 100644 index 00000000..d5ed59fe --- /dev/null +++ b/临时需求池/资料页+编辑资料/超级个体点进去的/code.html @@ -0,0 +1,211 @@ + + + + +Profile with Paid Unlock Options + + + + + + + + +
+ +

超级个体

+
+ + +
+
+
+
+
+
+
+Profile Picture +
+

乘风

+
+INFP + +push_pin 杭州 + +
+
+
+
+
+person +

基本信息

+
+
+
+

行业

+

新媒体 / 电商

+
+
+

职位

+

创始人

+
+
+

业务体量

+

年GMV 5000万+

+
+
+
+

我擅长

+

+ 短视频制作、IP打造、私域运营 +

+
+
+

联系方式

+
+139****02 + +
+
+
+

微信号

+
+wxid_****abc + +
+
+
+
+
+
+lightbulb +

个人故事

+
+
+
+
+🏆 +

最赚钱的一个月做的是什么

+
+

+ 旅游账号30天涨粉10万,带货佣金收入12万 +

+
+
+
+
+⭐️ +

最有成就感的一件事

+
+

+ 帮助3个素人打造个人IP,每个月稳定变现5万+ +

+
+
+
+
+restart_alt +

人生的转折点

+
+

+ 辞去互联网大厂工作开始做自媒体,第三个月就超过原薪资 +

+
+
+
+
+
+🤝 +

互助需求

+
+
+
+ + 我能帮你 + +

+ 短视频脚本、账号冷启动、私域转化设计 +

+
+
+ + 我需要帮助 + +

+ 寻找供应链资源和线下活动合作 +

+
+
+
+
+
+rocket_launch +

项目介绍

+
+

+ 旅游+生活方式自媒体矩阵,全网粉丝50万+ +

+
+
+
+
+ + +
+
+ + \ No newline at end of file diff --git a/临时需求池/资料页+编辑资料/超级个体点进去的/screen.png b/临时需求池/资料页+编辑资料/超级个体点进去的/screen.png new file mode 100644 index 00000000..a3060d0c Binary files /dev/null and b/临时需求池/资料页+编辑资料/超级个体点进去的/screen.png differ diff --git a/临时需求池/资料页+编辑资料/超级个体解锁完的/code.html b/临时需求池/资料页+编辑资料/超级个体解锁完的/code.html new file mode 100644 index 00000000..8c31f4ae --- /dev/null +++ b/临时需求池/资料页+编辑资料/超级个体解锁完的/code.html @@ -0,0 +1,210 @@ + + + + +Unlocked Professional Profile View + + + + + + + + + +
+ +

超级个体

+
+ + +
+
+
+
+
+
+
+Profile Picture +
+

乘风

+
+INFP + +push_pin 杭州 + +
+
+
+
+
+person +

基本信息

+
+
+
+

行业

+

新媒体 / 电商

+
+
+

职位

+

创始人

+
+
+

业务体量

+

年GMV 5000万+

+
+
+
+

我擅长

+

+ 短视频制作、IP打造、私域运营 +

+
+
+

联系方式

+
+13912345602 +
+
+
+

微信号

+
+wxid_myid123456 +
+
+
+
+
+
+lightbulb +

个人故事

+
+
+
+
+🏆 +

最赚钱的一个月做的是什么

+
+

+ 旅游账号30天涨粉10万,带货佣金收入12万 +

+
+
+
+
+⭐️ +

最有成就感的一件事

+
+

+ 帮助3个素人打造个人IP,每个月稳定变现5万+ +

+
+
+
+
+restart_alt +

人生的转折点

+
+

+ 辞去互联网大厂工作开始做自媒体,第三个月就超过原薪资 +

+
+
+
+
+
+🤝 +

互助需求

+
+
+
+ + 我能帮你 + +

+ 短视频脚本、账号冷启动、私域转化设计 +

+
+
+ + 我需要帮助 + +

+ 寻找供应链资源和线下活动合作 +

+
+
+
+
+
+rocket_launch +

项目介绍

+
+

+ 旅游+生活方式自媒体矩阵,全网粉丝50万+ +

+
+
+
+
+ + +
+
+ + \ No newline at end of file diff --git a/临时需求池/资料页+编辑资料/超级个体解锁完的/screen.png b/临时需求池/资料页+编辑资料/超级个体解锁完的/screen.png new file mode 100644 index 00000000..e2b3c13a Binary files /dev/null and b/临时需求池/资料页+编辑资料/超级个体解锁完的/screen.png differ diff --git a/临时需求池/首页页面/code.html b/临时需求池/首页页面/code.html new file mode 100644 index 00000000..456b40f6 --- /dev/null +++ b/临时需求池/首页页面/code.html @@ -0,0 +1,263 @@ + + + +Clean Home with Contact Author Link + + + + + + + + +
+
+
+
+ S +
+
+
+

Soul创业派对

+ +
+

来自派对房的真实故事

+
+
+
+ 73章 +
+
+
+
+ +search + + +
+
+
+
+
+最新更新 +

流量红利的终局

+

第五篇 真实的社会

+ + 开始阅读 arrow_forward + +
+
+
+
+
+

我的阅读

+2/73章 +
+
+
+
+
+
+
2
+
已读
+
+
+
71
+
待读
+
+
+
5
+
篇章
+
+
+
11
+
章节
+
+
+
+
+
+
+

超级个体

+查看全部 chevron_right +
+
+
+
+person +
+微信用户RBEY +
+
+
+person +
+微信用户HciA +
+
+
+ 版 +
+好版登小程序顾问 +
+
+
+Profile of user +
+乘风 +
+
+
+
+
+

精选推荐

+查看全部 chevron_right +
+
+
+
+
+1.1 +热门 +
+

荷包:电动车出租的被动收入模式

+

第一篇 真实的人

+
+chevron_right +
+
+
+
+1.2 +推荐 +
+

老墨:资源整合高手的社交方法

+

第一篇 真实的人

+
+chevron_right +
+
+
+
+9.17 +精选 +
+

第104场 | 婚恋、AI客服与一个微信

+

第四篇 真实的赚钱

+
+chevron_right +
+
+
+
+
+

最新新增

++10 +
+
+
+
+
+
+
+NEW +

流量红利的终局

+
+
+¥1 +2/24 +
+
+

深入分析当前市场的流量趋势与未来走向...

+
+
+
+
+
+
+
+NEW +

9.18 第105场 | 创业社群

+
+
+¥1 +2/24 +
+
+

社群运营的核心逻辑与直播实战技巧分享...

+
+
+
+
+
+
+ + +
+ +找伙伴 +
+ +
+
+
+
+ + \ No newline at end of file diff --git a/临时需求池/首页页面/screen.png b/临时需求池/首页页面/screen.png new file mode 100644 index 00000000..71342cf2 Binary files /dev/null and b/临时需求池/首页页面/screen.png differ diff --git a/开发文档/三端需求业务对齐-小程序与API.md b/开发文档/三端需求业务对齐-小程序与API.md new file mode 100644 index 00000000..0ac9eb43 --- /dev/null +++ b/开发文档/三端需求业务对齐-小程序与API.md @@ -0,0 +1,162 @@ +# Soul 创业派对 - 三端需求业务对齐(小程序 ↔ API) + +> 供小程序、后端 API、管理端工程师需求与业务对齐使用。 +> 更新日期:2026-02-25 + +--- + +## 一、小程序功能模块总览 + +| 模块 | 页面 | 功能简述 | +|------|------|----------| +| **首页** | index | 精选推荐、最新章节、超级个体(VIP 展示)、跳转目录/搜索/找伙伴/我的 | +| **目录** | chapters | 全书目录、每日新增、跳转阅读/搜索 | +| **阅读** | read | 章节内容、权限判断、购买、分享、海报、推荐码 | +| **找伙伴** | match | 匹配配置、随机匹配、加入弹窗、购买匹配次数 | +| **我的** | my | 用户信息、收益、提现、VIP 状态、设置入口 | +| **分销中心** | referral | 绑定/访问/收益、提现、小程序码、分享 | +| **购买记录** | purchases | 当前用户订单列表 | +| **设置** | settings | 昵称/头像/手机/微信/支付宝、退出登录 | +| **地址管理** | addresses, edit | 收货地址 CRUD | +| **提现记录** | withdraw-records | 提现列表、确认收款 | +| **VIP** | vip | VIP 状态、购买、资料编辑 | +| **会员详情** | member-detail | 创业者详情(VIP/普通用户) | +| **搜索** | search | 热门、关键词搜索章节 | +| **关于** | about | 书籍统计、联系 | +| **协议** | agreement, privacy | 用户协议、隐私政策 | + +--- + +## 二、小程序 API 调用清单(按页面) + +### 2.1 已正确使用 `/api/miniprogram/*` 的接口 + +| 页面/模块 | 路径 | 方法 | 用途 | +|-----------|------|------|------| +| app | /api/miniprogram/referral/visit | POST | 推荐访问记录 | +| app | /api/miniprogram/referral/bind | POST | 推荐码绑定 | +| app | /api/miniprogram/book/all-chapters | GET | 书籍目录 | +| app | /api/miniprogram/login | POST | 微信登录 | +| app | /api/miniprogram/phone-login | POST | 手机号登录 | +| config | /api/miniprogram/config | GET | 免费章节、价格、功能开关 | +| read | /api/miniprogram/book/chapter/:id | GET | 按 id 获取章节 | +| read | /api/miniprogram/book/chapter/by-mid/:mid | GET | 按 mid 获取章节 | +| read | /api/miniprogram/user/purchase-status | GET | 购买状态 | +| read | /api/miniprogram/pay | POST | 支付下单 | +| read | /api/miniprogram/qrcode | POST | 生成小程序码 | +| index | /api/miniprogram/vip/members | GET | 超级个体列表 | +| index | /api/miniprogram/users | GET | 用户补充(limit=20) | +| index | /api/miniprogram/book/all-chapters | GET | 精选、最新 | +| chapters | /api/miniprogram/book/all-chapters | GET | 目录、每日新增 | +| search | /api/miniprogram/book/hot | GET | 热门搜索 | +| search | /api/miniprogram/book/search | GET | 关键词搜索 | +| about | /api/miniprogram/book/stats | GET | 书籍统计 | +| referral | /api/miniprogram/referral/data | GET | 分销数据 | +| referral | /api/miniprogram/qrcode | POST | 小程序码 | +| referral | /api/miniprogram/withdraw | POST | 提现申请 | +| my | /api/miniprogram/config | GET | 配置 | +| my | /api/miniprogram/withdraw/pending-confirm | GET | 待确认提现 | +| my | /api/miniprogram/earnings | GET | 收益 | +| my | /api/miniprogram/user/update | POST | 资料更新 | +| my | /api/miniprogram/vip/status | GET | VIP 状态 | +| settings | /api/miniprogram/user/profile | GET/POST | 资料 | +| settings | /api/miniprogram/user/update | POST | 更新 | +| settings | /api/miniprogram/phone | POST | 手机号 | +| addresses | /api/miniprogram/user/addresses | GET | 地址列表 | +| addresses | /api/miniprogram/user/addresses/:id | GET/PUT/DELETE | 地址 CRUD | +| addresses/edit | /api/miniprogram/user/addresses | POST | 新增地址 | +| withdraw-records | /api/miniprogram/withdraw/records | GET | 提现记录 | +| withdraw-records | /api/miniprogram/withdraw/confirm-info | GET | 确认收款信息 | +| vip | /api/miniprogram/vip/status | GET | VIP 状态 | +| vip | /api/miniprogram/vip/profile | GET/POST | VIP 资料 | +| vip | /api/miniprogram/pay | POST | VIP 购买 | +| member-detail | /api/miniprogram/vip/members | GET | 单个会员 | +| member-detail | /api/miniprogram/users | GET | 单个用户回退 | +| match | /api/miniprogram/ckb/join | POST | 加入弹窗 | +| match | /api/miniprogram/pay | POST | 购买匹配次数 | +| readingTracker | /api/miniprogram/user/reading-progress | POST | 阅读进度 | +| chapterAccessManager | /api/miniprogram/user/check-purchased | GET | 是否已购 | +| chapterAccessManager | /api/miniprogram/user/purchase-status | GET | 购买状态 | +| custom-tab-bar | /api/miniprogram/config | GET | 功能配置 | + +### 2.2 路径错误(违反边界:应改为 `/api/miniprogram/*`) + +| 页面 | 当前调用 | 正确路径 | 说明 | +|------|----------|----------|------| +| **match** | /api/match/config | /api/miniprogram/match/config | 匹配配置,soul-api 已挂 miniprogram | +| **match** | /api/match/users | /api/miniprogram/match/users | 匹配用户,soul-api 已挂 miniprogram | +| **match** | /api/ckb/match | /api/miniprogram/ckb/match | 上报匹配,soul-api 已挂 miniprogram | +| **purchases** | /api/orders?userId= | /api/miniprogram/orders?userId= | 订单列表,**需新增** miniprogram 路由 | +| **my** | /api/user/update | /api/miniprogram/user/update | 头像更新,soul-api 已挂 miniprogram | +| **my** | /api/withdraw | /api/miniprogram/withdraw | 提现申请,soul-api 已挂 miniprogram | + +--- + +## 三、后端 API 需变更项 + +### 3.1 小程序端需修正的调用(前端改) + +| 文件 | 当前 | 改为 | +|------|------|------| +| match.js | `/api/match/config` | `/api/miniprogram/match/config` | +| match.js | `/api/match/users` | `/api/miniprogram/match/users` | +| match.js | `/api/ckb/match` | `/api/miniprogram/ckb/match` | +| my.js | `/api/user/update` | `/api/miniprogram/user/update` | +| my.js | `/api/withdraw` | `/api/miniprogram/withdraw` | + +### 3.2 后端需新增/调整的接口 + +| 接口 | 变更类型 | 说明 | +|------|----------|------| +| **GET /api/miniprogram/orders** | **新增** | 购买记录页专用。当前 `/api/orders` 无 userId 过滤且返回 `orders`;小程序需 `?userId=` 过滤且期望 `data`。建议在 miniprogram 组新增 `MiniprogramOrders`:按 userId 过滤、返回 `{ success, data: [...] }`,字段含 id/order_sn、product_id、product_name、amount、status、created_at | + +### 3.3 响应格式对齐 + +| 接口 | 当前返回 | 小程序期望 | 建议 | +|------|----------|------------|------| +| /api/orders | `{ success, orders }` | `res.data` | 新增 MiniprogramOrders 返回 `{ success, data }`,与小程序一致 | + +--- + +## 四、soul-api 现有 miniprogram 路由(已挂载) + +``` +/api/miniprogram/config +/api/miniprogram/login, phone-login, phone +/api/miniprogram/pay, pay/notify +/api/miniprogram/qrcode, qrcode/image +/api/miniprogram/book/all-chapters, chapter/:id, chapter/by-mid/:mid, hot, search, stats +/api/miniprogram/referral/visit, bind, data +/api/miniprogram/earnings +/api/miniprogram/match/config +/api/miniprogram/match/users ← 注意:router 为 POST +/api/miniprogram/ckb/join +/api/miniprogram/ckb/match ← 已挂载 +/api/miniprogram/upload +/api/miniprogram/user/addresses, addresses/:id +/api/miniprogram/user/check-purchased, profile, purchase-status, reading-progress, update +/api/miniprogram/withdraw, withdraw/records, pending-confirm, confirm-received, confirm-info +/api/miniprogram/vip/status, vip/profile, vip/members +/api/miniprogram/users +``` + +**缺失**:`/api/miniprogram/orders`(需新增) + +--- + +## 五、变更任务分工建议 + +| 角色 | 任务 | +|------|------| +| **小程序** | 1. match.js:3 处路径改为 /api/miniprogram/*
2. my.js:2 处路径改为 /api/miniprogram/*
3. purchases.js:路径改为 /api/miniprogram/orders(待后端提供后) | +| **后端 API** | 1. 新增 MiniprogramOrders handler:GET,支持 ?userId=,返回 { success, data }
2. router 挂载 miniprogram.GET("/orders", handler.MiniprogramOrders)
3. ckb/match 已挂载,无需变更 | +| **管理端** | 无需因本次对齐变更;订单、提现、用户等管理接口保持现状 | + +--- + +## 六、附录:match 接口方法说明 + +- `GET /api/miniprogram/match/config`:匹配配置(matchTypes、freeMatchLimit、matchPrice) +- `POST /api/miniprogram/match/users`:执行匹配,入参 matchType、userId,返回匹配到的用户 + +当前 match.js 对 config 使用 `method: 'GET'`,对 users 使用 `method: 'POST'`,与后端一致。仅需将路径从 `/api/match/*` 改为 `/api/miniprogram/match/*`。 diff --git a/开发文档/列表标准与角色分工.md b/开发文档/列表标准与角色分工.md new file mode 100644 index 00000000..bd419bf6 --- /dev/null +++ b/开发文档/列表标准与角色分工.md @@ -0,0 +1,111 @@ +# Soul 创业派对 - 列表标准与角色分工 + +> 供管理端开发者、API 开发者参考。基于 2026-02 列表缺陷排查经验归纳。 +> 更新日期:2026-02-25 + +--- + +## 一、标准列表应具备的能力 + +| 能力 | 说明 | 优先级 | +|------|------|--------| +| **搜索** | 关键词模糊搜索,建议 300ms 防抖 | 高 | +| **筛选** | 状态/类型/时间范围等 | 高 | +| **刷新** | 手动重新加载 | 高 | +| **分页** | 上一页/下一页、页码、每页条数(后端支持时) | 高 | +| **加载状态** | loading 或骨架屏 | 高 | +| **空状态** | 无数据时的提示 | 高 | +| **错误提示** | 加载失败时展示可关闭的提示条 | 高 | +| **排序** | 列头点击排序(可选) | 中 | +| **导出** | CSV/Excel(可选) | 中 | +| **批量操作** | 勾选多行后批量处理(可选) | 低 | + +--- + +## 二、角色分工 + +### 2.1 管理端开发者(soul-admin) + +**职责**:实现列表页面的交互与展示,对接 soul-api 的管理端接口。 + +**必做**: +- 搜索:使用 `useDebounce` 对输入做 300ms 防抖 +- 筛选:按业务提供下拉/按钮筛选 +- 刷新:提供刷新按钮,加载时禁用并显示 loading +- 加载状态:请求中显示 loading +- 空状态:无数据时显示友好提示 +- 错误提示:catch 后设置 error 状态,页面顶部展示可关闭的错误条(红底) + +**可选**: +- 导出:前端基于当前筛选结果生成 CSV(无需后端支持) +- 排序:前端内存排序或后端支持时传 sort 参数 + +**禁止**: +- 不得调用 `/api/miniprogram/*` +- 不得用原生 `alert`/`confirm` 替代错误提示(应使用页面内错误条或 Dialog) + +**参考**:`.cursor/skills/SKILL-管理端开发.md`、`soul-admin-boundary.mdc` + +--- + +### 2.2 API 开发者(soul-api) + +**职责**:为管理端列表提供分页、筛选、排序等能力。 + +**列表接口建议**: +- 分页:支持 `page`、`pageSize` 查询参数,返回 `total`、`records`/`list` +- 筛选:支持 `status`、`matchType`、`startDate`、`endDate` 等 +- 排序:支持 `sortBy`、`sortOrder`(asc/desc) + +**响应格式**: +```json +{ + "success": true, + "records": [...], + "total": 100, + "page": 1, + "pageSize": 10 +} +``` + +**错误**:失败时返回 `{ "success": false, "error": "..." }`,管理端据此展示错误条。 + +**参考**:`.cursor/skills/SKILL-API开发.md`、`soul-api-boundary.mdc` + +--- + +## 三、已补全项(2026-02-25) + +| 页面 | 补全内容 | +|------|----------| +| 用户管理 | 错误提示、搜索防抖、**分页、每页条数、VIP 筛选** | +| 订单管理 | 错误提示、刷新、导出 CSV、搜索防抖、**分页、每页条数、后端搜索** | +| 匹配记录 | 错误提示、**每页条数选择** | +| 分账提现 | 错误提示、**分页、每页条数** | +| 交易中心 | 错误提示、**分页(订单/绑定/提现子列表)** | +| 章节管理 | 错误提示、刷新 | + +--- + +## 四、后端分页支持(已实现) + +| 接口 | 分页参数 | 筛选/搜索 | +|------|----------|-----------| +| GET /api/db/users | page, pageSize | search, vip | +| GET /api/orders | page, pageSize | status, search | +| GET /api/admin/withdrawals | page, pageSize | status | +| GET /api/db/distribution | page, pageSize | status | +| GET /api/db/match-records | page, pageSize | matchType | + +--- + +## 五、检查清单(管理端新增列表时) + +- [ ] 分页(后端支持时接入 page、pageSize、total) +- [ ] 搜索有防抖(300ms) +- [ ] 有刷新按钮 +- [ ] 加载中显示 loading +- [ ] 无数据时显示空状态 +- [ ] 加载失败时展示错误条(可关闭) +- [ ] 仅调用 `/api/admin/*` 或 `/api/db/*` +- [ ] 不使用原生 alert 做错误提示 diff --git a/开发文档/小程序功能与管理端配置补齐分析.md b/开发文档/小程序功能与管理端配置补齐分析.md new file mode 100644 index 00000000..0e5f6786 --- /dev/null +++ b/开发文档/小程序功能与管理端配置补齐分析.md @@ -0,0 +1,168 @@ +# 小程序功能与管理端配置补齐分析 + +> 基于 miniprogram 功能分析,梳理需管理端补齐的配置与功能。 +> 更新日期:2026-02-25 +> **2026-02-25 已补齐**:mp_config 管理端、网站配置持久化、支付/二维码 POST、小程序 config 读取 + +--- + +## 一、小程序配置来源总览 + +| 配置类型 | 来源 | 管理端入口 | 状态 | +|----------|------|------------|------| +| 免费章节 | system_config.free_chapters | 系统设置 | ✅ 已有 | +| 价格 (section/fullbook) | chapter_config / site_settings | 系统设置 | ✅ 已有 | +| 功能开关 (match/referral/search/about) | feature_config | 系统设置 | ✅ 已有 | +| 找伙伴配置 | match_config | 找伙伴配置页 | ✅ 已有 | +| 推广/分销 | referral_config | 推广设置 | ✅ 已有 | +| 小程序专用 (mp_config) | mp_config | 系统设置 → 小程序配置 | ✅ 已补齐 | +| 订阅消息模板 ID | mp_config 或 app.js 兜底 | 系统设置 → 小程序配置 | ✅ 已补齐 | +| 微信支付商户号 | mp_config 或 app.js 兜底 | 系统设置 → 小程序配置 | ✅ 已补齐 | +| API 地址 (baseUrl) | app.js 硬编码 | 发版时改 baseUrl | 已移除配置 | +| 网站/站点配置 | site_config, page_config | 网站配置 | ✅ 已持久化 | + +--- + +## 二、小程序功能模块与配置依赖 + +### 2.1 核心配置接口:`GET /api/miniprogram/config` + +**返回字段**(来自 GetPublicDBConfig): + +| 字段 | 说明 | 管理端对应 | +|------|------|------------| +| freeChapters | 免费章节 ID 列表 | 系统设置 → 免费章节 | +| prices | { section, fullbook } | 系统设置 → 价格设置 | +| features | matchEnabled, referralEnabled, searchEnabled, aboutEnabled | 系统设置 → 功能开关 | +| mpConfig | appId, apiDomain, buyerDiscount, referralBindDays, minWithdraw | **无管理端** | +| userDiscount | 好友购买优惠 % | 推广设置 | + +### 2.2 找伙伴配置:`GET /api/miniprogram/match/config` + +| 字段 | 说明 | 管理端对应 | +|------|------|------------| +| matchTypes | 匹配类型列表 | 找伙伴配置页 | +| freeMatchLimit | 每日免费匹配次数 | 找伙伴配置页 | +| matchPrice | 单次匹配价格(元) | 找伙伴配置页 | +| settings | enableFreeMatches, enablePaidMatches, maxMatchesPerDay | 找伙伴配置页 | + +### 2.3 小程序 app.js 硬编码项 + +```javascript +// 当前硬编码,无法通过管理端修改 +baseUrl: 'http://localhost:8080', // 开发/生产需改代码 +appId: 'wxb8bbb2b10dec74aa', +withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-...', // 提现订阅消息模板 +mchId: '1318592501', // 微信支付商户号 +``` + +--- + +## 三、管理端需补齐项(按优先级) + +### P0 - 必须补齐 + +| 项 | 说明 | 建议方案 | +|----|------|----------| +| **小程序专用配置 (mp_config)** | appId、apiDomain、minWithdraw 等,小程序从 config 读取 | 在「系统设置」或新建「小程序配置」卡片,支持编辑并写入 system_config.mp_config | +| **订阅消息模板 ID** | 提现申请需用户授权订阅,模板 ID 现硬编码 | 管理端增加「提现订阅模板 ID」配置,写入 mp_config 或单独 key;小程序启动时从 config 拉取 | +| **API 地址 (baseUrl)** | 开发/生产切换需改 app.js | 方案 A:从 mp_config.apiDomain 下发,小程序优先用接口返回值;方案 B:保留硬编码,仅文档说明切换方式 | + +### P1 - 建议补齐 + +| 项 | 说明 | 建议方案 | +|----|------|----------| +| **微信支付商户号** | 支付回调、对账依赖 mchId,现硬编码 | 管理端「支付配置」或「小程序配置」增加 mchId 字段;后端从配置读取,小程序可不改(支付由后端发起) | +| **网站配置持久化** | SitePage 当前保存仅前端状态,未调用后端 | 对接 POST /api/db/config,按 key 保存 site_config、page_config、menu_config | +| **支付/二维码配置持久化** | PaymentPage、QRCodesPage 使用 POST /api/config | soul-api 无 POST /api/config,需新增或改为 POST /api/db/config { key, value } | + +### P2 - 可选优化 + +| 项 | 说明 | 建议方案 | +|----|------|----------| +| **referral_config 的 withdrawFee** | 文档有提现手续费,ReferralSettingsPage 未展示 | 若业务需要,在推广设置页增加「提现手续费」字段 | +| **主题色/品牌色** | app.js theme 硬编码 | 若需多端统一,可从 site_config 或 mp_config 下发 | + +--- + +## 四、配置与 system_config 键名映射 + +| system_config.config_key | 管理端页面 | 说明 | +|--------------------------|------------|------| +| free_chapters | 系统设置 | 免费章节 ID 数组 | +| feature_config | 系统设置 | 功能开关对象 | +| site_settings | 系统设置 | 价格、作者信息 | +| chapter_config | (可选) | 合并 freeChapters + prices,GetPublicDBConfig 优先用 | +| match_config | 找伙伴配置 | 匹配类型、免费次数、价格 | +| referral_config | 推广设置 | 分销比例、提现门槛、绑定期等 | +| mp_config | **待新增** | 小程序专用:appId, apiDomain, withdrawSubscribeTmplId, mchId 等 | +| site_config | 网站配置 | 站点名称、logo 等(需持久化) | +| page_config | 网站配置 | 页面标题(需持久化) | +| menu_config | 网站配置 | 菜单开关(需持久化) | +| payment_methods | 支付配置 | 微信/支付宝活码等(需确认 POST 接口) | +| live_qr_codes | 二维码管理 | 群活码(需确认 POST 接口) | + +--- + +## 五、接口与数据流检查 + +### 5.1 管理端调用与后端支持 + +| 管理端页面 | 调用 | 后端支持 | 备注 | +|------------|------|----------|------| +| 系统设置 | GET/POST /api/admin/settings | ✅ | 写入 free_chapters, feature_config, site_settings | +| 推广设置 | GET/POST /api/admin/referral-settings | ✅ | 写入 referral_config | +| 找伙伴配置 | GET /api/db/config/full?key=match_config | ✅ | 需 AdminAuth | +| 找伙伴配置 | POST /api/db/config | ✅ | body: { key: 'match_config', value: {...} } | +| 支付配置 | GET/POST /api/config | ⚠️ | GET 有,POST 无;需新增或改用 db/config | +| 网站配置 | GET /api/config | ⚠️ | GET 有,保存未对接 | +| 二维码管理 | GET/POST /api/config | ⚠️ | 同上 | + +### 5.2 小程序读取链 + +``` +小程序 onLoad / custom-tab-bar + → GET /api/miniprogram/config + → GetPublicDBConfig + → 读取 system_config: chapter_config, free_chapters, feature_config, mp_config, referral_config + → 返回 freeChapters, prices, features, mpConfig, userDiscount +``` + +--- + +## 六、实施建议(管理端开发任务) + +### 任务 1:新增「小程序配置」区块(P0) + +- **位置**:系统设置页新增卡片,或独立「小程序配置」页 +- **字段**: + - API 域名 (apiDomain):如 `https://soulapi.quwanzhi.com` + - 小程序 AppID (appId):如 `wxb8bbb2b10dec74aa` + - 提现订阅模板 ID (withdrawSubscribeTmplId) + - 微信支付商户号 (mchId)(可选,后端也可用 env) + - 最低提现金额 (minWithdraw)(可与 referral_config 同步) +- **存储**:POST /api/admin/settings 扩展,或 POST /api/db/config { key: 'mp_config', value: {...} } +- **小程序**:app.js 启动时请求 config,若 mp_config 存在则覆盖 baseUrl;withdrawSubscribeTmplId 从 config 取 + +### 任务 2:网站配置持久化(P1) + +- SitePage 保存时调用 POST /api/db/config,按 key 分别保存 site_config、page_config、menu_config +- 或扩展 AdminSettingsPost 支持 site_config 等 + +### 任务 3:支付/二维码配置接口(P1) + +- 方案 A:新增 POST /api/admin/config,支持 payment_methods、live_qr_codes 等 key +- 方案 B:PaymentPage、QRCodesPage 改为调用 POST /api/db/config,body: { key, value } + +--- + +## 七、附录:小程序页面与配置使用 + +| 页面 | 使用的配置 | 接口 | +|------|------------|------| +| custom-tab-bar | features.matchEnabled | GET /api/miniprogram/config | +| 阅读 read | freeChapters, prices | GET /api/miniprogram/config (chapterAccessManager) | +| 找伙伴 match | matchTypes, freeMatchLimit, matchPrice | GET /api/miniprogram/match/config | +| 我的 my | features, 收益/提现规则 | GET /api/miniprogram/config, referral/data | +| 分销 referral | shareRate, minWithdraw, bindingDays | GET /api/miniprogram/referral/data | +| 支付流程 | mchId (后端), openId | app.js 硬编码 + 后端 env |