// pages/theme-travel/theme-travel.js - 高端定制页面 const api = require('../../utils/api') const app = getApp() Page({ data: { statusBarHeight: 44, navBarHeight: 44, totalNavHeight: 88, loading: false, loadingMore: false, activeTab: 'featured', // 活动列表 activityList: [], // 分页相关 page: 1, limit: 20, hasMore: true, total: 0, // 二维码引导弹窗 showQrcodeModal: false, qrcodeImageUrl: 'https://ai-c.maimanji.com/api/common/qrcode?type=theme-travel' }, onLoad(options) { // 计算导航栏高度 const systemInfo = wx.getSystemInfoSync() const statusBarHeight = systemInfo.statusBarHeight || 44 const menuButton = wx.getMenuButtonBoundingClientRect() const navBarHeight = menuButton.height + (menuButton.top - statusBarHeight) * 2 const totalNavHeight = statusBarHeight + navBarHeight this.setData({ statusBarHeight, navBarHeight, totalNavHeight }) this.loadActivityList() }, /** * 返回上一页 */ onBack() { wx.navigateBack() }, /** * 加载活动列表(支持分页) */ async loadActivityList(isLoadMore = false) { if (isLoadMore) { this.setData({ loadingMore: true }) } else { this.setData({ loading: true, page: 1, hasMore: true, activityList: [] }) } try { const { activeTab, page, limit } = this.data const params = { category: 'travel', limit: limit, page: page } if (activeTab === 'featured') { params.tab = 'featured' } else if (activeTab === 'free') { params.priceType = 'free' } else if (activeTab === 'vip') { params.is_vip = true } else if (activeTab === 'svip') { params.is_svip = true } const res = await api.activity.getList(params) if (res.success && res.data && res.data.list) { const total = res.data.total || 0 const allActivities = res.data.list const travelActivities = allActivities.filter(item => item.categoryName === '高端定制') let clubQrcode = '' const firstWithQrcode = travelActivities.find(item => item.activityGuideQrcode || item.activity_guide_qrcode) if (firstWithQrcode && !isLoadMore) { clubQrcode = firstWithQrcode.activityGuideQrcode || firstWithQrcode.activity_guide_qrcode } const newActivityList = travelActivities.map(item => { const heat = item.heat || (item.likes * 2 + (item.views || 0) + (item.current_participants || 0) * 3) return { id: item.id, title: item.title, date: this.formatDate(item.start_date || item.activityDate, item.end_date || item.endDate), location: item.location || '', venue: item.venue || '', image: item.coverImage || item.cover_image || '', heat: Math.floor(heat), price: item.price_text || item.priceText || '免费', priceType: item.is_free || item.priceType === 'free' ? 'free' : 'paid', likes: item.likes || item.likesCount || 0, participants: item.current_participants || item.currentParticipants || 0, isLiked: item.is_liked || item.isLiked || false, isSignedUp: item.is_registered || item.isSignedUp || false, status: item.status || (item.currentParticipants >= item.maxParticipants && item.maxParticipants > 0 ? 'full' : 'upcoming'), activityGuideQrcode: item.activityGuideQrcode || item.activity_guide_qrcode || '' } }) const hasMore = newActivityList.length >= limit && (this.data.activityList.length + newActivityList.length) < total if (isLoadMore) { this.setData({ activityList: [...this.data.activityList, ...newActivityList], loadingMore: false, hasMore, page: this.data.page + 1, total }) } else { this.setData({ activityList: newActivityList, hasMore, total, qrcodeImageUrl: clubQrcode || this.data.qrcodeImageUrl }) } console.log('[theme-travel] 加载成功,总数:', total, '当前:', this.data.activityList.length, 'hasMore:', hasMore) } else { if (isLoadMore) { this.setData({ loadingMore: false, hasMore: false }) } else { this.setData({ activityList: [], hasMore: false }) } } } catch (err) { console.error('加载活动列表失败', err) if (isLoadMore) { this.setData({ loadingMore: false }) } else { this.setData({ activityList: [], loading: false }) } } finally { if (!isLoadMore) { this.setData({ loading: false }) } } }, /** * 标签切换 */ onTabChange(e) { const tab = e.currentTarget.dataset.tab if (tab === this.data.activeTab) return this.setData({ activeTab: tab, activityList: [], page: 1, hasMore: true }) this.loadActivityList() }, /** * 下拉刷新 */ onPullDownRefresh() { this.loadActivityList(false).finally(() => { wx.stopPullDownRefresh() }) }, /** * 上拉加载更多 */ onReachBottom() { if (this.data.hasMore && !this.data.loadingMore && !this.data.loading) { this.loadActivityList(true) } }, /** * 加载模拟数据(降级方案) */ loadMockActivities() { // 使用空数据,等待后端API返回真实数据 const mockActivities = [] this.setData({ activityList: mockActivities }) }, /** * 格式化日期 */ formatDate(startDate, endDate) { if (!startDate) return '' const formatSingle = (dateStr) => { const date = new Date(dateStr) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}年${month}月${day}日` } // 如果有结束日期且不同于开始日期,显示日期范围 if (endDate && endDate !== startDate) { const startFormatted = formatSingle(startDate) const endFormatted = formatSingle(endDate) // 如果是同一年,省略结束日期的年份 if (startFormatted.split('年')[0] === endFormatted.split('年')[0]) { return `${startFormatted}-${endFormatted.split('年')[1]}` } return `${startFormatted}-${endFormatted}` } return formatSingle(startDate) }, /** * 立即报名 */ async onSignUp(e) { const id = e.currentTarget.dataset.id const index = e.currentTarget.dataset.index if (!app.globalData.isLoggedIn) { wx.navigateTo({ url: '/pages/login/login' }) return } const activity = this.data.activityList[index] // 检查活动状态 if (activity.status === 'full' || activity.status === 'ended') { const qrCode = activity.activityGuideQrcode || activity.activity_guide_qrcode || this.data.qrcodeImageUrl this.setData({ qrcodeImageUrl: qrCode, showQrcodeModal: true }) return } try { if (activity.isSignedUp) { // 取消报名 const res = await api.activity.cancelSignup(id) if (res.success) { wx.showToast({ title: '已取消报名', icon: 'success' }) this.loadActivityList() } } else { // 报名 const res = await api.activity.signup(id) if (res.success) { wx.showToast({ title: '报名成功', icon: 'success' }) this.loadActivityList() } else { // 检查是否需要显示二维码(后端开关关闭或活动已结束) if (res.code === 'QR_CODE_REQUIRED' || res.error === 'QR_CODE_REQUIRED' || res.code === 'ACTIVITY_ENDED' || res.error === '活动已结束') { if (activity.activityGuideQrcode || activity.activity_guide_qrcode) { this.setData({ qrcodeImageUrl: activity.activityGuideQrcode || activity.activity_guide_qrcode }) } this.setData({ showQrcodeModal: true }) if (res.code === 'ACTIVITY_ENDED' || res.error === '活动已结束') { wx.showToast({ title: '活动已结束,进群查看更多', icon: 'none' }) } } else { wx.showToast({ title: res.error || '报名失败', icon: 'none' }) } } } } catch (err) { console.error('报名操作失败', err) // 捕获特定错误码以显示二维码 const isQrRequired = err && (err.code === 'QR_CODE_REQUIRED' || (err.data && err.data.code === 'QR_CODE_REQUIRED')) const isActivityEnded = err && (err.code === 'ACTIVITY_ENDED' || (err.data && err.data.code === 'ACTIVITY_ENDED') || err.error === '活动已结束') if (isQrRequired || isActivityEnded) { if (activity.activityGuideQrcode || activity.activity_guide_qrcode) { this.setData({ qrcodeImageUrl: activity.activityGuideQrcode || activity.activity_guide_qrcode }) } this.setData({ showQrcodeModal: true }) if (isActivityEnded) { wx.showToast({ title: '活动已结束,进群查看更多', icon: 'none' }) } } else { wx.showToast({ title: err.error || err.message || '操作失败', icon: 'none' }) } } }, /** * 点赞/取消点赞 */ async onLike(e) { const id = e.currentTarget.dataset.id const index = e.currentTarget.dataset.index if (!app.globalData.isLoggedIn) { wx.navigateTo({ url: '/pages/login/login' }) return } try { const res = await api.activity.toggleLike(id) if (res.success) { this.setData({ [`activityList[${index}].isLiked`]: res.data.isLiked, [`activityList[${index}].likes`]: res.data.likesCount }) } } catch (err) { console.error('点赞失败', err) wx.showToast({ title: '操作失败', icon: 'none' }) } }, /** * 点击活动卡片 */ onActivityTap(e) { const id = e.currentTarget.dataset.id wx.navigateTo({ url: `/pages/activity-detail/activity-detail?id=${id}` }) }, /** * 加入高端定制群 */ onJoinGroup() { let qrcodeUrl = this.data.qrcodeImageUrl if (!qrcodeUrl && this.data.activityList && this.data.activityList.length > 0) { const firstWithQrcode = this.data.activityList.find(item => item.activityGuideQrcode || item.activity_guide_qrcode) if (firstWithQrcode) { qrcodeUrl = firstWithQrcode.activityGuideQrcode || firstWithQrcode.activity_guide_qrcode } } if (!qrcodeUrl) { wx.showToast({ title: '暂无二维码', icon: 'none' }) return } this.setData({ showQrcodeModal: true, qrcodeImageUrl: qrcodeUrl }) }, /** * 关闭二维码弹窗 */ onCloseQrcodeModal() { this.setData({ showQrcodeModal: false }) }, /** * 保存二维码 */ async onSaveQrcode() { try { const { qrcodeImageUrl } = this.data if (!qrcodeImageUrl) { wx.showToast({ title: '二维码链接不存在', icon: 'none' }) return } wx.showLoading({ title: '保存中...' }) let filePath = '' // 判断是否是 Base64 格式 if (qrcodeImageUrl.startsWith('data:image')) { const fs = wx.getFileSystemManager() const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(qrcodeImageUrl) || [] if (!format || !bodyData) { throw new Error('Base64 格式错误') } filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.${format}` fs.writeFileSync(filePath, bodyData, 'base64') } else { // 远程 URL 格式 const downloadRes = await new Promise((resolve, reject) => { wx.downloadFile({ url: qrcodeImageUrl, success: resolve, fail: reject }) }) if (downloadRes.statusCode !== 200) { throw new Error('下载图片失败') } filePath = downloadRes.tempFilePath } // 保存到相册 await new Promise((resolve, reject) => { wx.saveImageToPhotosAlbum({ filePath: filePath, success: resolve, fail: reject }) }) wx.hideLoading() wx.showToast({ title: '保存成功', icon: 'success' }) this.onCloseQrcodeModal() } catch (err) { wx.hideLoading() console.error('保存二维码失败', err) if (err.errMsg && (err.errMsg.includes('auth deny') || err.errMsg.includes('auth denied'))) { wx.showModal({ title: '需要授权', content: '请允许访问相册以保存二维码', confirmText: '去设置', success: (res) => { if (res.confirm) { wx.openSetting() } } }) } else { wx.showToast({ title: err.message || '保存失败', icon: 'none' }) } } }, /** * 阻止冒泡 */ preventBubble() { return } })