From 4146309ff2164209b33f86ec49de539b2519386a Mon Sep 17 00:00:00 2001 From: zhiyun Date: Thu, 5 Feb 2026 01:02:05 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master --- pages/city-activities/city-activities.js | 29 +- pages/commission/commission.js | 79 +++- pages/eldercare/eldercare.js | 22 + pages/entertainment/entertainment.js | 22 + pages/gift-shop/gift-shop.js | 12 +- pages/happy-school/happy-school.js | 22 + .../housekeeping-apply/housekeeping-apply.js | 24 ++ pages/index/index.js | 21 + pages/interest-partner/interest-partner.js | 22 + pages/medical-apply/medical-apply.js | 24 ++ pages/orders/orders.js | 12 +- pages/orders/orders.wxml | 30 +- pages/orders/orders.wxss | 140 ++++--- .../outdoor-activities/outdoor-activities.js | 22 + pages/profile/profile.js | 38 +- pages/profile/profile.wxml | 2 +- pages/service/service.js | 22 + pages/singles-party/singles-party.js | 24 +- pages/support/support.js | 304 +++++++++++++- pages/support/support.wxml | 129 +++++- pages/support/support.wxss | 379 ++++++++++++++++-- pages/team/team.js | 27 +- pages/team/team.wxml | 2 +- pages/theme-travel/theme-travel.js | 24 +- 24 files changed, 1261 insertions(+), 171 deletions(-) diff --git a/pages/city-activities/city-activities.js b/pages/city-activities/city-activities.js index 4ee0796..400e697 100644 --- a/pages/city-activities/city-activities.js +++ b/pages/city-activities/city-activities.js @@ -233,14 +233,35 @@ Page({ }, fail: () => { wx.hideLoading() - wx.showToast({ - title: '下载失败', - icon: 'none' - }) + wx.showToast({ title: '下载失败', icon: 'none' }) } }) }, + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + const city = this.data.selectedCity || '深圳市' + return { + title: `${city}同城活动 - 发现身边的精彩`, + path: `/pages/city-activities/city-activities?city=${encodeURIComponent(city)}&referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + const city = this.data.selectedCity || '深圳市' + return { + title: `${city}同城活动 - 发现身边的精彩`, + query: `city=${encodeURIComponent(city)}&referralCode=${referralCode}` + } + }, + /** * 点击活动卡片 */ diff --git a/pages/commission/commission.js b/pages/commission/commission.js index 249af31..1d0bc00 100644 --- a/pages/commission/commission.js +++ b/pages/commission/commission.js @@ -3,6 +3,7 @@ const api = require('../../utils/api') const errorHandler = require('../../utils/errorHandler') +const imageUrl = require('../../utils/imageUrl') // 缓存配置 const CACHE_CONFIG = { @@ -45,7 +46,7 @@ Page({ // 缓存状态 cacheExpired: false, lastUpdateTime: '', - defaultAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=500&auto=format&fit=crop&q=60', + defaultAvatar: '/images/default-avatar.svg', cardTitle: '守护会员', pickerDate: '', // YYYY-MM pickerDateDisplay: '' // YYYY年MM月 @@ -251,6 +252,11 @@ Page({ lastUpdateTime: this.formatCacheTime(Date.now()) }) + // 关键修复:保存推荐码到本地存储,供全局分享使用 + if (statsData.referralCode) { + wx.setStorageSync('referralCode', statsData.referralCode) + } + // 保存到缓存 this.saveStatsToCache(statsData) @@ -372,18 +378,36 @@ Page({ * 转换记录数据格式 */ transformRecord(record) { - let titleText = record.fromUserName || record.userName || '用户'; - let descText = 'VIP月卡'; - if (record.amount > 100) descText = 'SVIP年卡'; - - if (record.fromUserLevel) { - descText = this.getUserLevelText(record.fromUserLevel); - } else if (record.orderType === 'vip' || record.orderType === 'svip') { - descText = record.orderType.toUpperCase() === 'SVIP' ? 'SVIP会员' : 'VIP会员'; - } else if (record.orderType === 'identity_card') { - descText = '身份会员'; - } else if (record.orderType === 'companion_chat') { - descText = '陪伴聊天'; + const fromUser = record.fromUser || record.from_user || record.user || record.fromUserInfo || null + const titleText = + record.fromUserName || + record.from_user_name || + record.userName || + record.user_name || + fromUser?.name || + fromUser?.nickname || + '用户' + + const levelRaw = + record.fromUserLevel ?? + record.from_user_level ?? + record.fromUserRole ?? + record.from_user_role ?? + record.fromUserDistributorRole ?? + record.distributorRole ?? + record.role ?? + record.userLevel ?? + record.user_level ?? + record.level ?? + fromUser?.level ?? + fromUser?.role + + let descText = this.getUserLevelText(levelRaw) + if (descText === '普通用户') { + if (record.orderType === 'vip') descText = 'VIP月卡' + else if (record.orderType === 'svip') descText = 'SVIP年卡' + else if (record.orderType === 'identity_card') descText = '身份会员' + else if (record.orderType === 'companion_chat') descText = '陪伴聊天' } const dateObj = new Date(record.created_at || record.createdAt); @@ -403,7 +427,14 @@ Page({ statusText: record.status === 'pending' ? '待结算' : '已结算', time: this.formatTime(record.created_at || record.createdAt), orderNo: record.orderId ? `ORD${record.orderId.substring(0, 12)}` : 'ORD2024012401', - userAvatar: record.userAvatar || record.fromUserAvatar || record.avatar || '', + userAvatar: imageUrl.getAvatarUrl( + record.userAvatar || + record.user_avatar || + record.fromUserAvatar || + record.from_user_avatar || + record.avatar || + fromUser?.avatar + ), listTitle: titleText, fmtTime: fmtTime, } @@ -411,19 +442,29 @@ Page({ getUserLevelText(level) { const levelMap = { - 'vip': 'VIP会员', - 'svip': 'SVIP会员', + 'vip_month': 'VIP月卡', + 'vip_monthly': 'VIP月卡', + 'vip_month_card': 'VIP月卡', + 'vip': 'VIP月卡', + 'svip_year': 'SVIP年卡', + 'svip_yearly': 'SVIP年卡', + 'svip_year_card': 'SVIP年卡', + 'svip': 'SVIP年卡', 'guardian': '守护会员', 'companion': '陪伴会员', 'partner': '城市合伙人', '1': '普通用户', - '2': 'VIP会员', - '3': 'SVIP会员', + '2': 'VIP月卡', + '3': 'SVIP年卡', '4': '守护会员', '5': '陪伴会员', '6': '城市合伙人' }; - return levelMap[level] || levelMap['1']; + if (level === null || level === undefined || level === '') return levelMap['1'] + const normalized = typeof level === 'number' ? String(level) : String(level).trim() + if (levelMap[normalized]) return levelMap[normalized] + if (/(会员|月卡|年卡|合伙人)/.test(normalized)) return normalized + return levelMap['1'] }, getCardTitle(type) { diff --git a/pages/eldercare/eldercare.js b/pages/eldercare/eldercare.js index 7f5debf..bd3bd2e 100644 --- a/pages/eldercare/eldercare.js +++ b/pages/eldercare/eldercare.js @@ -42,5 +42,27 @@ Page({ } finally { this.setData({ loading: false }) } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '智慧康养 - 守护您的健康生活', + path: `/pages/eldercare/eldercare?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '智慧康养 - 守护您的健康生活', + query: `referralCode=${referralCode}` + } } }) diff --git a/pages/entertainment/entertainment.js b/pages/entertainment/entertainment.js index f722f06..d0aa799 100644 --- a/pages/entertainment/entertainment.js +++ b/pages/entertainment/entertainment.js @@ -825,5 +825,27 @@ Page({ } } wx.switchTab({ url: path }) + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '休闲文娱 - 精彩活动等你来', + path: `/pages/entertainment/entertainment?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '休闲文娱 - 精彩活动等你来', + query: `referralCode=${referralCode}` + } } }) diff --git a/pages/gift-shop/gift-shop.js b/pages/gift-shop/gift-shop.js index f28a9db..07a4c46 100644 --- a/pages/gift-shop/gift-shop.js +++ b/pages/gift-shop/gift-shop.js @@ -129,7 +129,17 @@ Page({ title: '提示', content: '请联系在线客服', showCancel: false, - confirmText: '知道了' + confirmText: '知道了', + success: (res) => { + if (res.confirm) { + wx.setClipboardData({ + data: 'mmj20259999', + success: () => { + //wx.showToast({ title: '已复制', icon: 'success' }) + } + }) + } + } }) }, diff --git a/pages/happy-school/happy-school.js b/pages/happy-school/happy-school.js index d65e0b7..7ed9eb1 100644 --- a/pages/happy-school/happy-school.js +++ b/pages/happy-school/happy-school.js @@ -343,5 +343,27 @@ Page({ wx.showToast({ title: '保存失败', icon: 'none' }) } } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '快乐学堂 - 活到老 学到老', + path: `/pages/happy-school/happy-school?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '快乐学堂 - 活到老 学到老', + query: `referralCode=${referralCode}` + } } }) diff --git a/pages/housekeeping-apply/housekeeping-apply.js b/pages/housekeeping-apply/housekeeping-apply.js index ae4ce25..edad480 100644 --- a/pages/housekeeping-apply/housekeeping-apply.js +++ b/pages/housekeeping-apply/housekeeping-apply.js @@ -256,5 +256,29 @@ Page({ } finally { wx.hideLoading() } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + const { isReapply } = this.data + return { + title: '家政保洁服务师 - 诚邀您的加入', + path: `/pages/housekeeping-apply/housekeeping-apply?isReapply=${isReapply}&referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + const { isReapply } = this.data + return { + title: '家政保洁服务师 - 诚邀您的加入', + query: `isReapply=${isReapply}&referralCode=${referralCode}` + } } }) diff --git a/pages/index/index.js b/pages/index/index.js index 391bc3c..5323502 100644 --- a/pages/index/index.js +++ b/pages/index/index.js @@ -101,6 +101,27 @@ Page({ this.loadCharacters() this.loadHeartBalance() this.loadUnlockConfig() + this.ensureReferralCode() + }, + + /** + * 确保本地有推荐码(用于分享) + */ + async ensureReferralCode() { + if (!app.globalData.isLoggedIn) return + + // 如果本地已有,暂不刷新 + if (wx.getStorageSync('referralCode')) return + + try { + const res = await api.commission.getStats() + if (res.success && res.data && res.data.referralCode) { + wx.setStorageSync('referralCode', res.data.referralCode) + console.log('[index] 已同步推荐码:', res.data.referralCode) + } + } catch (err) { + console.log('[index] 同步推荐码失败(非阻断)', err) + } }, /** diff --git a/pages/interest-partner/interest-partner.js b/pages/interest-partner/interest-partner.js index 00b1dcc..7309346 100644 --- a/pages/interest-partner/interest-partner.js +++ b/pages/interest-partner/interest-partner.js @@ -330,5 +330,27 @@ Page({ }) } }) + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '兴趣搭子 - 寻找志同道合的伙伴', + path: `/pages/interest-partner/interest-partner?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '兴趣搭子 - 寻找志同道合的伙伴', + query: `referralCode=${referralCode}` + } } }) diff --git a/pages/medical-apply/medical-apply.js b/pages/medical-apply/medical-apply.js index 12c6a57..43b9296 100644 --- a/pages/medical-apply/medical-apply.js +++ b/pages/medical-apply/medical-apply.js @@ -311,5 +311,29 @@ Page({ } finally { wx.hideLoading() } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + const { isReapply } = this.data + return { + title: '陪诊师招募 - 诚邀您的加入', + path: `/pages/medical-apply/medical-apply?isReapply=${isReapply}&referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + const { isReapply } = this.data + return { + title: '陪诊师招募 - 诚邀您的加入', + query: `isReapply=${isReapply}&referralCode=${referralCode}` + } } }) diff --git a/pages/orders/orders.js b/pages/orders/orders.js index 00dda03..ee3fd22 100644 --- a/pages/orders/orders.js +++ b/pages/orders/orders.js @@ -48,13 +48,17 @@ Page({ const list = orders.map((o) => ({ id: o.id || o.orderNo, - remark: this.formatOrderType(o.orderType || 'order'), + // Use title/desc from API, fallback to existing logic + title: o.title || this.formatOrderType(o.orderType || 'order'), + desc: o.desc || '', amountText: this.formatAmount(o.amount), - status: this.formatStatus(o.status), + // Use statusText from API, fallback to local formatting + status: o.statusText || this.formatStatus(o.status), statusClass: `status-${o.status}`, + // Handle createdAt createdAtText: this.formatDateTime(new Date(o.createdAt || o.created_at || Date.now())), - // visual adjustment: ensure it looks like income/recharge style - transactionType: o.orderType || 'recharge' + transactionType: o.orderType || 'recharge', + orderNo: o.orderNo })); this.setData({ list }); diff --git a/pages/orders/orders.wxml b/pages/orders/orders.wxml index 684e963..d27e92a 100644 --- a/pages/orders/orders.wxml +++ b/pages/orders/orders.wxml @@ -17,24 +17,24 @@ - - - 加载中... - 暂无数据 - - - - {{item.remark || item.transactionType}} - {{item.createdAtText}} - - - {{item.amountText}} - {{item.status}} - + 加载中... + 暂无数据 + + + + {{item.createdAtText}} + {{item.status}} + + + + {{item.title}} + {{item.desc}} + + + {{item.amountText}} - diff --git a/pages/orders/orders.wxss b/pages/orders/orders.wxss index 6c279fc..4ad5c00 100644 --- a/pages/orders/orders.wxss +++ b/pages/orders/orders.wxss @@ -13,27 +13,27 @@ position: fixed; left: 0; right: 0; - height: 120rpx; + height: 100rpx; background: #ffffff; display: flex; align-items: center; justify-content: space-around; z-index: 100; - border-bottom: 2rpx solid #f3f4f6; + box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.02); } .tab-item { - font-size: 28rpx; + font-size: 32rpx; color: #6b7280; - font-weight: 600; + font-weight: 500; position: relative; - height: 80rpx; + height: 100rpx; line-height: 100rpx; } .tab-item.active { color: #b06ab3; - font-weight: 900; + font-weight: 700; } .tab-item.active::after { @@ -43,94 +43,92 @@ left: 50%; transform: translateX(-50%); width: 40rpx; - height: 6rpx; + height: 10rpx; background: #b06ab3; border-radius: 3rpx; } -.card { - background: #ffffff; - border-radius: 40rpx; - padding: 24rpx; - box-shadow: 0 10rpx 20rpx rgba(17, 24, 39, 0.04); -} - .loading, .empty { text-align: center; color: #9ca3af; - font-weight: 800; + font-weight: 500; padding: 80rpx 0; + font-size: 28rpx; } -.row { - padding: 24rpx 8rpx; +/* Order List Styles */ +.order-list { + padding-bottom: 40rpx; +} + +.order-card { + background: #ffffff; + border-radius: 24rpx; + padding: 24rpx; + margin-bottom: 24rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); +} + +.card-header { display: flex; - align-items: center; justify-content: space-between; - border-bottom: 2rpx solid #f3f4f6; + align-items: center; + margin-bottom: 20rpx; + padding-bottom: 20rpx; + border-bottom: 1rpx solid #f3f4f6; } -.row:last-child { - border-bottom: 0; +.order-time { + font-size: 32rpx; + color: #353434ff; } -.row-left { - flex: 1; - min-width: 0; -} - -.row-title { - display: block; - font-size: 34rpx; - font-weight: 900; - color: #111827; -} - -.row-sub { - display: block; - margin-top: 10rpx; - font-size: 26rpx; - color: #9ca3af; +.order-status { + font-size: 30rpx; font-weight: 600; } -.row-right { - text-align: right; +.card-body { + display: flex; + justify-content: space-between; + align-items: center; } -.row-amount { - display: block; +.body-left { + flex: 1; + margin-right: 20rpx; + display: flex; + flex-direction: column; +} + +.order-title { font-size: 36rpx; - font-weight: 900; + font-weight: 700; + color: #111827; + margin-bottom: 8rpx; + line-height: 1.4; +} + +.order-desc { + font-size: 30rpx; + color: #6b7280; + line-height: 1.4; +} + +.body-right { + flex-shrink: 0; +} + +.order-amount { + font-size: 38rpx; + font-weight: 800; color: #b06ab3; } -.row-status { - display: block; - margin-top: 10rpx; - font-size: 26rpx; - color: #6b7280; - font-weight: 700; -} - -.status-completed { - color: #10B981; -} - -.status-paid { - color: #3B82F6; -} - -.status-pending { - color: #F59E0B; -} - -.status-cancelled { - color: #9CA3AF; -} - -.status-refunded { - color: #EF4444; -} - +/* Status Colors */ +.status-completed { color: #10B981; } +.status-paid { color: #3B82F6; } +.status-pending { color: #F59E0B; } +.status-cancelled { color: #9CA3AF; } +.status-refunded { color: #EF4444; } diff --git a/pages/outdoor-activities/outdoor-activities.js b/pages/outdoor-activities/outdoor-activities.js index 1b0369c..98106cb 100644 --- a/pages/outdoor-activities/outdoor-activities.js +++ b/pages/outdoor-activities/outdoor-activities.js @@ -325,5 +325,27 @@ Page({ console.error('保存失败', err) wx.showToast({ title: '保存失败', icon: 'none' }) } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '户外郊游 - 结伴同行 领略自然', + path: `/pages/outdoor-activities/outdoor-activities?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '户外郊游 - 结伴同行 领略自然', + query: `referralCode=${referralCode}` + } } }) diff --git a/pages/profile/profile.js b/pages/profile/profile.js index 0d73a14..bf870ce 100644 --- a/pages/profile/profile.js +++ b/pages/profile/profile.js @@ -52,6 +52,14 @@ Page({ auditStatus: app.globalData.auditStatus }); wx.hideTabBar({ animation: false }); + + // Throttle loadAll to prevent frequent calls (e.g. switching tabs quickly) + const now = Date.now(); + if (this.lastLoadTime && now - this.lastLoadTime < 2000) { + return; + } + this.lastLoadTime = now; + this.loadAll(); }, @@ -62,15 +70,27 @@ Page({ wx.showNavigationBarLoading(); try { if (isLoggedIn) { + // Stage 1: Critical user info and unread counts (Visual priority) await Promise.all([ this.loadMe(), this.loadBalance(), - this.loadCommission(), - this.loadCounts(), this.loadUnreadCount() ]); - this.checkRegistrationReward(); - this.checkGf100Popup(); + + // Stage 2: Financial stats (Slight delay to avoid 429) + setTimeout(async () => { + if (!this.data.isLoggedIn) return; + await this.loadCommission(); + // Run loadCounts separately as it triggers multiple sub-requests + await this.loadCounts(); + }, 300); + + // Stage 3: Popups and Rewards (Lowest priority) + setTimeout(() => { + if (!this.data.isLoggedIn) return; + this.checkRegistrationReward(); + this.checkGf100Popup(); + }, 800); } else { this.setData({ me: { nickname: '未登录', avatar: this.data.defaultAvatar }, @@ -141,7 +161,7 @@ Page({ const roleMap = { 'soulmate': { text: '心伴会员', class: 'vip-soulmate' }, 'guardian': { text: '守护会员', class: 'vip-guardian' }, - 'companion': { text: '陪伴会员', class: 'vip-companion' }, + 'companion': { text: '心伴会员', class: 'vip-companion' }, 'listener': { text: '倾听会员', class: 'vip-listener' }, 'partner': { text: '城市合伙人', class: 'vip-partner' } }; @@ -598,7 +618,13 @@ Page({ try { const res = await api.commission.getStats() if (res.success && res.data) { - this.setData({ referralCode: res.data.referralCode || '' }) + const code = res.data.referralCode || '' + this.setData({ referralCode: code }) + + // 关键修复:保存推荐码到本地存储,供全局分享使用 + if (code) { + wx.setStorageSync('referralCode', code) + } } } catch (err) { console.error('加载推荐码失败:', err) diff --git a/pages/profile/profile.wxml b/pages/profile/profile.wxml index 32429bf..c2f0b85 100644 --- a/pages/profile/profile.wxml +++ b/pages/profile/profile.wxml @@ -43,7 +43,7 @@ 我的会员 - 心伴会员 + {{vip.levelText || '会员'}} diff --git a/pages/service/service.js b/pages/service/service.js index 6354431..957aef6 100644 --- a/pages/service/service.js +++ b/pages/service/service.js @@ -289,5 +289,27 @@ Page({ } wx.switchTab({ url: path }) } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '综合服务 - 优质生活服务平台', + path: `/pages/service/service?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '综合服务 - 优质生活服务平台', + query: `referralCode=${referralCode}` + } } }) diff --git a/pages/singles-party/singles-party.js b/pages/singles-party/singles-party.js index c9ac018..4873b11 100644 --- a/pages/singles-party/singles-party.js +++ b/pages/singles-party/singles-party.js @@ -64,7 +64,7 @@ Page({ try { const { activeTab } = this.data const params = { - category: 'city', // 单身聚会通常属于同城活动 + category: 'singles-party', limit: 100 } @@ -276,5 +276,27 @@ Page({ console.error('保存失败', err) wx.showToast({ title: '保存失败', icon: 'none' }) } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '单身聚会 - 遇见心动的TA', + path: `/pages/singles-party/singles-party?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '单身聚会 - 遇见心动的TA', + query: `referralCode=${referralCode}` + } } }) \ No newline at end of file diff --git a/pages/support/support.js b/pages/support/support.js index ec1d2f9..c3561b8 100644 --- a/pages/support/support.js +++ b/pages/support/support.js @@ -4,6 +4,16 @@ const api = require('../../utils/api') const util = require('../../utils/util') const imageUrl = require('../../utils/imageUrl') +// 常用表情 +const EMOJIS = [ + "😊", "😀", "😁", "😃", "😂", "🤣", "😅", "😆", "😉", "😋", "😎", "😍", "😘", "🥰", "😗", "😙", + "🙂", "🤗", "🤩", "🤔", "😐", "😑", "😶", "🙄", "😏", "😣", "😥", "😮", "😯", "😪", "😫", "😴", + "😌", "😛", "😜", "😝", "😒", "😓", "😔", "😕", "🙃", "😲", "😖", "😞", "😟", "😤", "😢", "😭", + "😨", "😩", "😬", "😰", "😱", "😳", "😵", "😡", "😠", "😷", "🤒", "🤕", "😇", "🥳", "🥺", + "👋", "👌", "✌️", "🤞", "👍", "👎", "👏", "🙌", "🤝", "🙏", "💪", "❤️", "🧡", "💛", "💚", "💙", + "💜", "🖤", "💔", "💕", "💖", "💗", "💘", "💝", "🌹", "🌺", "🌻", "🌼", "🌷", "🎉", "🎊", "🎁" +] + Page({ data: { statusBarHeight: 44, @@ -16,7 +26,17 @@ Page({ ticketId: '', scrollIntoView: '', scrollTop: 0, - pollingTimer: null + pollingTimer: null, + + // 新增状态 + isVoiceMode: false, + isRecording: false, + showEmoji: false, + showMorePanel: false, + voiceCancelHint: false, + recordingDuration: 0, + emojis: EMOJIS, + playingVoiceId: null }, onLoad() { @@ -74,7 +94,6 @@ Page({ this.setData({ ticketId: latestTicket.id }) await this.loadMessages(latestTicket.id) } else { - // 如果没有工单,可以在首次发送消息时创建 console.log('[support] No existing tickets found.') } } catch (err) { @@ -95,7 +114,11 @@ Page({ const messages = res.data.messages.map(msg => ({ id: msg.id, isMe: msg.senderType === 'user', - text: msg.content, + text: msg.type === 'text' ? msg.content : (msg.type === 'image' ? '[图片]' : (msg.type === 'voice' ? '[语音]' : msg.content)), + type: msg.type || 'text', + imageUrl: msg.type === 'image' ? msg.content : '', + audioUrl: msg.type === 'voice' ? msg.content : '', + duration: msg.duration || 0, time: util.formatTime(new Date(msg.createdAt), 'HH:mm'), senderName: msg.senderName })) @@ -119,7 +142,25 @@ Page({ const content = this.data.inputText.trim() if (!content || this.isSending) return + // 收起键盘和面板 + this.setData({ + showEmoji: false, + showMorePanel: false + }) + + await this.sendMessage(content, 'text') + }, + + /** + * 统一发送消息方法 + * @param {string} content - 消息内容(文本或URL) + * @param {string} type - 消息类型 (text/image/voice) + * @param {number} duration - 语音时长 + */ + async sendMessage(content, type = 'text', duration = 0) { + if (this.isSending) return this.isSending = true + const tempId = util.generateId() const now = new Date() @@ -127,39 +168,59 @@ Page({ const userMsg = { id: tempId, isMe: true, - text: content, - time: util.formatTime(now, 'HH:mm') + text: type === 'text' ? content : (type === 'image' ? '[图片]' : '[语音]'), + type: type, + imageUrl: type === 'image' ? content : '', + audioUrl: type === 'voice' ? content : '', + duration: duration, + time: util.formatTime(now, 'HH:mm'), + uploading: true } this.setData({ messages: [...this.data.messages, userMsg], inputText: '', - inputFocus: true + inputFocus: type === 'text' // 仅文本发送后聚焦 }, () => { this.scrollToBottom() }) try { + const payload = { + content: content, + type: type, + duration: duration, + userName: app.globalData.userInfo?.nickname || '访客' + } + if (this.data.ticketId) { // 回复已有工单 await api.customerService.reply({ ticketId: this.data.ticketId, - content: content, - userName: app.globalData.userInfo?.nickname || '访客' + ...payload }) } else { // 创建新工单 const guestId = wx.getStorageSync('guestId') const res = await api.customerService.create({ category: 'other', - content: content, - userName: app.globalData.userInfo?.nickname || '访客', - guestId: guestId + guestId: guestId, + ...payload }) if (res.success && res.data) { this.setData({ ticketId: res.data.ticketId }) } } + + // 更新本地消息状态 + const messages = this.data.messages.map(msg => { + if (msg.id === tempId) { + return { ...msg, uploading: false } + } + return msg + }) + this.setData({ messages }) + // 发送后立即拉取一次 if (this.data.ticketId) { await this.loadMessages(this.data.ticketId) @@ -167,6 +228,15 @@ Page({ } catch (err) { console.error('[support] send message error:', err) wx.showToast({ title: '发送失败', icon: 'none' }) + + // 更新失败状态 + const messages = this.data.messages.map(msg => { + if (msg.id === tempId) { + return { ...msg, uploading: false, error: true } + } + return msg + }) + this.setData({ messages }) } finally { this.isSending = false } @@ -203,12 +273,222 @@ Page({ }, onTapChatArea() { - this.setData({ inputFocus: false }) + this.setData({ + inputFocus: false, + showEmoji: false, + showMorePanel: false + }) }, scrollToBottom() { this.setData({ scrollIntoView: 'chat-bottom-anchor' }) + }, + + // ==================== 底部功能区逻辑 ==================== + + onVoiceMode() { + this.setData({ + isVoiceMode: !this.data.isVoiceMode, + showEmoji: false, + showMorePanel: false, + inputFocus: false + }) + }, + + onEmojiToggle() { + this.setData({ + inputFocus: false + }) + + setTimeout(() => { + this.setData({ + showEmoji: !this.data.showEmoji, + showMorePanel: false, + isVoiceMode: false + }) + }, 50) + }, + + onAddMore() { + this.setData({ + inputFocus: false + }) + + setTimeout(() => { + this.setData({ + showMorePanel: !this.data.showMorePanel, + showEmoji: false, + isVoiceMode: false + }) + }, 50) + }, + + onClosePanels() { + this.setData({ + showEmoji: false, + showMorePanel: false + }) + }, + + onEmojiSelect(e) { + const emoji = e.currentTarget.dataset.emoji + this.setData({ + inputText: this.data.inputText + emoji + }) + }, + + // ==================== 语音录制 ==================== + + onVoiceTouchStart(e) { + this.touchStartY = e.touches[0].clientY + this.setData({ + isRecording: true, + voiceCancelHint: false, + recordingDuration: 0 + }) + + const recorderManager = wx.getRecorderManager() + recorderManager.start({ + duration: 60000, + format: 'mp3' + }) + + this.recorderManager = recorderManager + + this.recordingTimer = setInterval(() => { + this.setData({ + recordingDuration: this.data.recordingDuration + 1 + }) + }, 1000) + }, + + onVoiceTouchMove(e) { + const moveY = e.touches[0].clientY + const diff = this.touchStartY - moveY + this.setData({ + voiceCancelHint: diff > 50 + }) + }, + + onVoiceTouchEnd() { + clearInterval(this.recordingTimer) + const { voiceCancelHint, recordingDuration } = this.data + + this.setData({ isRecording: false }) + + if (this.recorderManager) { + this.recorderManager.stop() + } + + if (voiceCancelHint) { + util.showToast('已取消') + return + } + + if (recordingDuration < 1) { + util.showError('录音时间太短') + return + } + + this.recorderManager.onStop(async (res) => { + const tempFilePath = res.tempFilePath + + // 上传语音 + try { + const uploadRes = await api.uploadFile(tempFilePath, 'audio') + if (uploadRes.success && uploadRes.data && uploadRes.data.url) { + await this.sendMessage(uploadRes.data.url, 'voice', recordingDuration) + } else { + throw new Error('Upload failed') + } + } catch (err) { + console.error('语音上传失败', err) + util.showError('语音发送失败') + } + }) + }, + + onVoiceTouchCancel() { + clearInterval(this.recordingTimer) + this.setData({ isRecording: false }) + if (this.recorderManager) { + this.recorderManager.stop() + } + }, + + // ==================== 图片/拍照 ==================== + + onChooseImage() { + this.setData({ showMorePanel: false }) + wx.chooseMedia({ + count: 9, + mediaType: ['image'], + sourceType: ['album'], + success: (res) => { + res.tempFiles.forEach(file => { + this.uploadAndSendImage(file.tempFilePath) + }) + } + }) + }, + + onTakePhoto() { + this.setData({ showMorePanel: false }) + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['camera'], + camera: 'back', + success: (res) => { + this.uploadAndSendImage(res.tempFiles[0].tempFilePath) + } + }) + }, + + async uploadAndSendImage(filePath) { + try { + const uploadRes = await api.uploadFile(filePath, 'uploads') + if (uploadRes.success && uploadRes.data && uploadRes.data.url) { + await this.sendMessage(uploadRes.data.url, 'image') + } else { + throw new Error('Upload failed') + } + } catch (err) { + console.error('图片上传失败', err) + util.showError('图片发送失败') + } + }, + + // ==================== 预览与播放 ==================== + + onPreviewImage(e) { + const url = e.currentTarget.dataset.url + wx.previewImage({ + current: url, + urls: [url] + }) + }, + + onPlayVoice(e) { + const { id, url } = e.currentTarget.dataset + if (!url) return + + const innerAudioContext = wx.createInnerAudioContext() + innerAudioContext.src = url + innerAudioContext.play() + + this.setData({ playingVoiceId: id }) + + innerAudioContext.onEnded(() => { + this.setData({ playingVoiceId: null }) + innerAudioContext.destroy() + }) + + innerAudioContext.onError((res) => { + console.error(res.errMsg) + this.setData({ playingVoiceId: null }) + }) } }) diff --git a/pages/support/support.wxml b/pages/support/support.wxml index 5ed94f1..b64ca24 100644 --- a/pages/support/support.wxml +++ b/pages/support/support.wxml @@ -58,9 +58,23 @@ - + + {{item.text}} + + + + + + + + + + + + {{item.duration || 1}}″ + {{item.time}} @@ -70,9 +84,23 @@ - + + {{item.text}} + + + + + + + {{item.duration || 1}}″ + + + + + + {{item.time}} @@ -101,10 +129,31 @@ + + + - + - + + + + + + + + {{isRecording ? (voiceCancelHint ? '松开 取消' : '松开 发送') : '按住 说话'}} + + + + - - - 发送 + + + + + + + + + + + + + + + + + + + {{item}} + + + + + + + + + + + + + + + 照片 + + + + + + + 拍摄 + + + + + + + + + + + + + + + + + + + + {{voiceCancelHint ? '松开手指,取消发送' : '手指上划,取消发送'}} + {{recordingDuration}}″ diff --git a/pages/support/support.wxss b/pages/support/support.wxss index c00cc9c..89f1773 100644 --- a/pages/support/support.wxss +++ b/pages/support/support.wxss @@ -1,4 +1,5 @@ /* pages/support/support.wxss */ +/* 样式复用自 AI聊天详情页 (pages/chat-detail/chat-detail.wxss) 以保持一致体验 */ /* 页面容器 */ .page-container { @@ -9,7 +10,7 @@ position: relative; } -/* 聊天区域包装器 */ +/* 聊天区域包装器 - 使用固定定位确保正确布局 */ .chat-area-wrapper { position: fixed; left: 0; @@ -20,6 +21,17 @@ overflow: hidden; } +/* 面板打开时的透明遮罩层 */ +.panel-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: transparent; + z-index: 98; +} + /* 状态栏区域 */ .status-bar-area { position: fixed; @@ -267,21 +279,46 @@ padding-bottom: env(safe-area-inset-bottom); } -.figma-input-container { +.input-container { display: flex; align-items: center; gap: 16rpx; padding: 24rpx 32rpx; - padding-bottom: 24rpx; + padding-bottom: 16rpx; +} + +/* Figma设计样式 - 底部输入区域 */ +.figma-input-container { + display: flex; + align-items: center; + gap: 16rpx; + padding: 24rpx 20rpx; + padding-bottom: 20rpx; +} + +.figma-voice-btn { + width: 80rpx; + height: 80rpx; + background: #F3F4F6; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.figma-btn-icon { + width: 80rpx; + height: 80rpx; } .figma-input-wrap { flex: 1; background: #F9FAFB; border: 2rpx solid #F3F4F6; - border-radius: 40rpx; + border-radius: 32rpx; padding: 0 32rpx; - height: 120rpx; + height: 96rpx; display: flex; align-items: center; } @@ -289,43 +326,337 @@ .figma-text-input { width: 100%; height: 100%; - font-size: 38rpx; + font-size: 36rpx; color: #101828; + font-family: Arial, sans-serif; } -.figma-send-btn { - width: 180rpx; - height: 88rpx; - background: #F3F4F6; - border-radius: 44rpx; +.figma-emoji-btn { + width: 80rpx; + height: 80rpx; + background: transparent; + border-radius: 50%; display: flex; align-items: center; justify-content: center; - gap: 8rpx; flex-shrink: 0; - transition: all 0.3s ease; } -.figma-send-btn.active { +.figma-emoji-btn.active { + background: #E9D5FF; +} + +.figma-send-btn { + width: 80rpx; + height: 80rpx; background: #914584; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; } -.figma-btn-icon { - width: 36rpx; - height: 36rpx; - filter: grayscale(1) opacity(0.5); +.figma-send-btn .figma-btn-icon { + width: 44rpx; + height: 44rpx; } -.figma-send-btn.active .figma-btn-icon { +.figma-add-btn { + width: 80rpx; + height: 80rpx; + background: transparent; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.figma-add-btn.active { + background: #FCE7F3; +} + +/* 面板面板显示时,移除底部安全区域 */ +.bottom-input-area.panel-open { + padding-bottom: 0; +} + +/* 语音录制按钮 */ +.voice-record-btn { + flex: 1; + background: #F3F4F6; + border: 2rpx solid #E5E7EB; + border-radius: 32rpx; + height: 96rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + font-weight: 500; + color: #374151; + transition: all 0.15s; +} + +.voice-record-btn:active, +.voice-record-btn.recording { + background: #E5E7EB; + transform: scale(0.98); +} + +/* 录音提示浮层 */ +.voice-recording-mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + display: flex; + align-items: center; + justify-content: center; + z-index: 200; +} + +.voice-recording-popup { + width: 320rpx; + height: 320rpx; + background: rgba(0, 0, 0, 0.8); + border-radius: 32rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 24rpx; +} + +.voice-recording-popup.cancel { + background: rgba(220, 38, 38, 0.9); +} + +.cancel-icon { + width: 80rpx; + height: 80rpx; filter: brightness(0) invert(1); } -.send-text { - font-size: 28rpx; - font-weight: 700; - color: #9CA3AF; +.voice-wave { + display: flex; + align-items: center; + justify-content: center; + gap: 12rpx; + height: 100rpx; } -.figma-send-btn.active .send-text { +.wave-bar { + width: 12rpx; + height: 40rpx; + background: #22C55E; + border-radius: 6rpx; + animation: wave 0.8s ease-in-out infinite; +} + +.wave-bar:nth-child(1) { animation-delay: 0s; height: 40rpx; } +.wave-bar:nth-child(2) { animation-delay: 0.1s; height: 60rpx; } +.wave-bar:nth-child(3) { animation-delay: 0.2s; height: 80rpx; } +.wave-bar:nth-child(4) { animation-delay: 0.3s; height: 60rpx; } +.wave-bar:nth-child(5) { animation-delay: 0.4s; height: 40rpx; } + +@keyframes wave { + 0%, 100% { transform: scaleY(1); } + 50% { transform: scaleY(1.5); } +} + +.voice-tip { + font-size: 28rpx; color: #FFFFFF; } + +.voice-duration-tip { + font-size: 48rpx; + font-weight: 700; + color: #FFFFFF; +} + +/* 表情面板 */ +.emoji-panel { + background: #FFFFFF; + border-top: 2rpx solid #F3F4F6; +} + +.emoji-scroll { + height: 480rpx; + padding: 24rpx; + box-sizing: border-box; +} + +.emoji-grid { + display: flex; + flex-wrap: wrap; +} + +.emoji-item { + width: 12.5%; + height: 88rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.emoji-text { + font-size: 56rpx; + line-height: 1; +} + +.emoji-item:active { + background: #F3F4F6; + border-radius: 16rpx; +} + +/* 更多功能面板 */ +.more-panel { + background: #F5F5F5; + border-top: 2rpx solid #E5E7EB; +} + +.more-panel-content { + padding: 40rpx 32rpx 24rpx; +} + +.more-grid { + display: flex; + justify-content: space-between; +} + +.more-grid.ai-chat-grid { + justify-content: center; + gap: 90rpx; + padding: 0 48rpx; +} + +.more-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 16rpx; + width: 25%; +} + +.more-icon-wrap { + width: 112rpx; + height: 112rpx; + background: #FFFFFF; + border-radius: 24rpx; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.more-icon-wrap.figma-style { + width: 128rpx; + height: 128rpx; + background: transparent; + border-radius: 0; + box-shadow: none; +} + +.figma-action-icon { + width: 128rpx; + height: 128rpx; +} + +.more-text { + font-size: 28rpx; + font-weight: 700; + color: #4A5565; +} + +.more-panel-safe { + height: env(safe-area-inset-bottom); + background: #F5F5F5; +} + +/* 图片消息气泡 */ +.chat-bubble-image { + max-width: 400rpx; + border-radius: 24rpx; + overflow: hidden; + box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); +} + +.chat-bubble-image.other { + border-radius: 12rpx 24rpx 24rpx 24rpx; +} + +.chat-bubble-image.me { + border-radius: 24rpx 12rpx 24rpx 24rpx; +} + +.message-image { + width: 100%; + min-width: 200rpx; + max-width: 400rpx; + display: block; +} + +/* 语音消息气泡 */ +.chat-bubble.voice { + display: flex; + align-items: center; + gap: 16rpx; + min-width: 160rpx; + padding: 24rpx 32rpx; +} + +.chat-bubble.voice.other { + flex-direction: row; +} + +.chat-bubble.voice.me { + flex-direction: row-reverse; +} + +.voice-waves { + display: flex; + align-items: center; + gap: 6rpx; + height: 40rpx; +} + +.voice-wave-bar { + width: 6rpx; + height: 20rpx; + border-radius: 3rpx; + background: #9CA3AF; +} + +.chat-bubble.voice.me .voice-wave-bar { + background: rgba(255, 255, 255, 0.7); +} + +.voice-wave-bar:nth-child(1) { height: 16rpx; } +.voice-wave-bar:nth-child(2) { height: 28rpx; } +.voice-wave-bar:nth-child(3) { height: 20rpx; } + +.chat-bubble.voice.playing .voice-wave-bar { + animation: voiceWave 0.8s ease-in-out infinite; +} + +.chat-bubble.voice.playing .voice-wave-bar:nth-child(1) { animation-delay: 0s; } +.chat-bubble.voice.playing .voice-wave-bar:nth-child(2) { animation-delay: 0.2s; } +.chat-bubble.voice.playing .voice-wave-bar:nth-child(3) { animation-delay: 0.4s; } + +@keyframes voiceWave { + 0%, 100% { transform: scaleY(1); } + 50% { transform: scaleY(1.8); } +} + +.voice-duration { + font-size: 28rpx; + color: #6B7280; +} + +.chat-bubble.voice.me .voice-duration { + color: rgba(255, 255, 255, 0.9); +} diff --git a/pages/team/team.js b/pages/team/team.js index 99c2f36..3e5492e 100644 --- a/pages/team/team.js +++ b/pages/team/team.js @@ -75,7 +75,10 @@ Page({ this.setData({ stats: { todayReferrals: Number(d.todayReferrals || d.today_referrals || 0), - totalReferrals: Number(d.totalReferrals || d.total_referrals || 0), + // 团队总人数:优先使用 teamMembers (新字段),兼容 totalReferrals + totalReferrals: Number(d.teamMembers || d.team_members || d.totalReferrals || d.total_referrals || 0), + // 直推人数:优先使用 directReferrals (新字段) + directReferrals: Number(d.directReferrals || d.direct_referrals || 0), totalContribution: Number(d.totalContribution || d.total_contribution || 0).toFixed(2) }, cardTitle: currentRoleText @@ -92,12 +95,17 @@ Page({ // Flexible data extraction let rawList = []; + let totalDirects = 0; // Initialize total count + if (Array.isArray(body.data)) { rawList = body.data; + totalDirects = rawList.length; } else if (body.data && Array.isArray(body.data.list)) { rawList = body.data.list; + totalDirects = body.data.total || rawList.length; // Use total from API if available } else if (body.list && Array.isArray(body.list)) { rawList = body.list; + totalDirects = body.total || rawList.length; } console.log('[团队页面] rawList:', JSON.stringify(rawList.slice(0, 2), null, 2)); @@ -111,6 +119,7 @@ Page({ }; const list = rawList.map((x) => { + // ... (mapping logic) ... const user = x.user || {}; // Map fields robustly let avatar = x.avatarUrl || x.avatar_url || x.userAvatar || user.avatarUrl || user.avatar_url || ''; @@ -140,7 +149,17 @@ Page({ }; }); - this.setData({ list }); + // 优先使用 stats API 返回的 directReferrals + // 如果 stats API 未返回有效值 (<=0),则回退使用列表接口的 total 或长度 + const currentStatsDirects = this.data.stats.directReferrals; + const finalDirects = (currentStatsDirects && currentStatsDirects > 0) + ? currentStatsDirects + : totalDirects; + + this.setData({ + list, + 'stats.directReferrals': finalDirects + }); } catch (err) { console.log('API failed, using mock data', err); this.setData({ @@ -176,11 +195,11 @@ Page({ getCardTitle(type) { const map = { 'guardian_card': '守护会员', - 'companion_card': '陪伴会员', + 'companion_card': '心伴会员', 'soulmate_card': '心伴会员', 'listener_card': '倾听会员', 'guardian': '守护会员', - 'companion': '陪伴会员', + 'companion': '心伴会员', 'soulmate': '心伴会员', 'listener': '倾听会员', 'identity_card': '身份会员', diff --git a/pages/team/team.wxml b/pages/team/team.wxml index 1df1d8b..436436e 100644 --- a/pages/team/team.wxml +++ b/pages/team/team.wxml @@ -22,7 +22,7 @@ 直推人数 - {{list.length}} + {{stats.directReferrals || list.length}} 团队总计 diff --git a/pages/theme-travel/theme-travel.js b/pages/theme-travel/theme-travel.js index 113ba6f..ea9122d 100644 --- a/pages/theme-travel/theme-travel.js +++ b/pages/theme-travel/theme-travel.js @@ -279,7 +279,29 @@ Page({ } } catch (err) { console.error('点赞失败', err) - wx.showToast({ title: '操作失败', icon: 'none' }) + wx.showToast({ title: '保存失败', icon: 'none' }) + } + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '高端定制 - 专属主题旅行线路', + path: `/pages/theme-travel/theme-travel?referralCode=${referralCode}` + } + }, + + /** + * 分享到朋友圈 + */ + onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + return { + title: '高端定制 - 专属主题旅行线路', + query: `referralCode=${referralCode}` } },