// pages/entertainment/entertainment.js - 休闲文娱页面 // 根据Figma设计实现 const api = require('../../utils/api') const app = getApp() Page({ data: { statusBarHeight: 44, navBarHeight: 44, totalNavHeight: 88, loading: false, loadingMore: false, // 顶部轮播数据 - 从后台素材管理API加载 bannerList: [], swiperHeight: 400, currentBannerIndex: 0, // 功能入口 - 使用正式环境图片URL categoryList: [ { id: 1, name: '兴趣搭子', icon: '/images/icon-interest.png' }, { id: 2, name: '同城活动', icon: '/images/icon-city.png' }, { id: 3, name: '户外郊游', icon: '/images/icon-outdoor.png' }, { id: 4, name: '高端定制', icon: '/images/icon-travel.png' }, { id: 5, name: '快乐学堂', icon: '/images/icon-checkin.png' }, { id: 6, name: '单身聚会', icon: '/images/icon-love.png' } ], // 滚动公告 noticeList: [], currentNoticeIndex: 0, // 活动标签 activeTab: 'featured', // featured: 精选活动, free: 免费活动, vip: VIP活动, svip: SVIP活动 // 活动列表 activityList: [], // 分页相关 page: 1, limit: 20, hasMore: true, total: 0, // 二维码引导弹窗 showQrcodeModal: false, qrcodeImageUrl: '', // 未读消息数 totalUnread: 0, // 审核状态 auditStatus: 0 }, onLoad() { 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.loadPageData() this.loadNotices() this.startNoticeScroll() }, /** * 加载页面数据 */ async loadPageData() { // 并行加载Banner、功能入口和活动列表 await Promise.all([ this.loadBanners(), this.loadEntries(), this.loadActivityList() ]) }, /** * 处理图片URL,如果是相对路径则拼接域名,并设置清晰度为85 */ processImageUrl(url) { if (!url) return '' let fullUrl = url if (!url.startsWith('http://') && !url.startsWith('https://')) { const baseUrl = 'https://ai-c.maimanji.com' fullUrl = baseUrl + (url.startsWith('/') ? '' : '/') + url } // 添加清晰度参数 q=85 if (fullUrl.includes('?')) { if (!fullUrl.includes('q=')) { fullUrl += '&q=85' } } else { fullUrl += '?q=85' } return fullUrl }, /** * 轮播图图片加载完成,自适应高度 */ onBannerLoad(e) { if (this.data.swiperHeight !== 300) return; // 只计算一次 const { width, height } = e.detail; const sysInfo = wx.getSystemInfoSync(); // 减去左右padding (32rpx * 2) const swiperWidth = sysInfo.windowWidth - (32 * 2 / 750 * sysInfo.windowWidth); const ratio = width / height; const swiperHeight = swiperWidth / ratio; const swiperHeightRpx = swiperHeight * (750 / sysInfo.windowWidth); this.setData({ swiperHeight: swiperHeightRpx }); }, /** * 加载功能入口图标 * 从后台素材管理API加载 (group=entries) */ async loadEntries() { try { const res = await api.pageAssets.getAssets('entries') console.log('功能入口 API响应:', res) if (res.success && res.data) { const icons = res.data const { categoryList } = this.data // 映射图标:搭子(id=1), 同城(id=2), 户外(id=3), 定制(id=4), 学堂(id=5), 传递(id=6) const idMap = { 1: 'entry_1', // 兴趣搭子 2: 'entry_2', // 同城活动 3: 'entry_3', // 户外郊游 4: 'entry_4', // 定制主题 5: 'entry_5', // 快乐学堂 6: 'entry_6' // 爱心传递 } const updatedCategoryList = categoryList.map(item => { const assetKey = idMap[item.id] const iconUrl = icons[assetKey] if (iconUrl) { return { ...item, icon: this.processImageUrl(iconUrl) } } return item }) this.setData({ categoryList: updatedCategoryList }) console.log('已更新娱乐页功能入口图标') } } catch (err) { console.error('加载功能入口失败', err) } }, /** * 加载娱乐页Banner * 调用专用API:/api/page-assets/entertainment-banners */ async loadBanners() { try { const res = await api.pageAssets.getEntertainmentBanners() console.log('娱乐页Banner API响应:', res) if (res.success && res.data) { // 处理相对路径,拼接完整URL // 兼容不同可能的字段名:asset_url, url, imageUrl const bannerList = res.data.map(item => { const rawUrl = item.asset_url || item.url || item.imageUrl || item.image_url || '' return { id: item.id, imageUrl: this.processImageUrl(rawUrl), // 只保留标签,不显示标题和副标题 tag: item.description || item.tag || '热门推荐', title: '', subtitle: '', bgColor: item.bg_color || item.bgColor || 'linear-gradient(135deg, #E8D5F0 0%, #F5E6D3 100%)' } }).filter(item => item.imageUrl) // 只保留有图片的 if (bannerList.length > 0) { this.setData({ bannerList }) console.log(`加载了 ${bannerList.length} 个娱乐页Banner`) } else { console.log('娱乐页Banner数据为空或解析失败,使用默认配置') this.setDefaultBanners() } } else { console.log('娱乐页Banner API返回失败,使用默认配置') this.setDefaultBanners() } } catch (err) { console.error('加载娱乐页Banner失败', err) this.setDefaultBanners() } }, /** * 设置默认Banner(降级方案 - 使用CDN URL) */ setDefaultBanners() { const cdnBase = 'https://ai-c.maimanji.com/images' this.setData({ bannerList: [ { id: 1, imageUrl: `${cdnBase}/service-banner-1.png`, tag: '热门', title: '', subtitle: '', bgColor: 'linear-gradient(135deg, #E8D5F0 0%, #F5E6D3 100%)' }, { id: 2, imageUrl: `${cdnBase}/service-banner-2.png`, tag: '活动', title: '', subtitle: '', bgColor: 'linear-gradient(135deg, #D5E8F0 0%, #E6F5D3 100%)' }, { id: 3, imageUrl: `${cdnBase}/service-banner-3.png`, tag: '推荐', title: '', subtitle: '', bgColor: 'linear-gradient(135deg, #F0E8D5 0%, #F5D3E6 100%)' } ] }) console.log('使用默认娱乐页Banner配置') }, /** * 加载公告 */ async loadNotices() { try { const res = await api.common.getNotices() console.log('[notice] 公告API响应:', res) if (res.success && res.data && res.data.length > 0) { const noticeList = res.data.map(item => ({ id: item.id, content: item.content, linkType: item.linkType || 'none', linkValue: item.linkValue || '' })) this.setData({ noticeList }) } } catch (err) { console.error('[notice] 加载公告失败', err) } }, /** * 点击公告栏 */ onNoticeTap() { wx.navigateTo({ url: '/pages/notices/notices' }) }, onShow() { if (typeof this.getTabBar === 'function' && this.getTabBar()) { this.getTabBar().setData({ selected: 1 }) } wx.hideTabBar({ animation: false }) const app = getApp() this.setData({ auditStatus: app.globalData.auditStatus }) this.loadUnreadCount() }, onUnload() { if (this.noticeTimer) { clearInterval(this.noticeTimer) } }, /** * 开始公告滚动 */ startNoticeScroll() { this.noticeTimer = setInterval(() => { const { noticeList, currentNoticeIndex } = this.data const nextIndex = (currentNoticeIndex + 1) % noticeList.length this.setData({ currentNoticeIndex: nextIndex }) }, 3000) }, /** * 加载活动列表 - 根据activeTab加载不同的活动(支持分页) */ async loadActivityList(isLoadMore = false) { console.log('========== 加载活动列表 ==========') console.log('[6] activeTab:', this.data.activeTab) console.log('[6.1] isLoadMore:', isLoadMore) if (isLoadMore) { this.setData({ loadingMore: true }) } else { this.setData({ loading: true, page: 1, hasMore: true }) } try { const config = require('../../config/index') const { activeTab, page, limit } = this.data console.log('[8] 请求URL:', `${config.API_BASE_URL}/entertainment/home`) console.log('[9] 请求参数:', { type: activeTab, page, limit }) const res = await new Promise((resolve, reject) => { wx.request({ url: `${config.API_BASE_URL}/entertainment/home`, method: 'GET', data: { type: activeTab, page, limit }, timeout: 10000, success: (res) => resolve(res), fail: (err) => reject(err) }) }) console.log('[10] API响应状态:', res.statusCode) console.log('[11] API响应数据:', JSON.stringify(res.data, null, 2)) if (res.statusCode === 200 && res.data.success && res.data.data) { const homeData = res.data.data const total = res.data.data.total || 0 let activities = [] if (activeTab === 'featured') { activities = homeData.featuredActivities || [] console.log('[12] 精选活动原始数量:', activities.length) } else if (activeTab === 'free') { activities = homeData.freeActivities || [] console.log('[12] 免费活动原始数量:', activities.length) } else if (activeTab === 'vip') { activities = homeData.vipActivities || [] console.log('[12] VIP活动原始数量:', activities.length) } else if (activeTab === 'svip') { activities = homeData.svipActivities || [] console.log('[12] SVIP活动原始数量:', activities.length) } const newActivityList = activities.map(item => { const heat = item.heat !== undefined && item.heat !== null ? item.heat : (item.likesCount || 0) * 2 + (item.viewsCount || 0) + ((item.virtualParticipants || 0) + (item.currentParticipants || 0)) * 3 return { id: item.id, title: item.title, date: this.formatDate(item.activityDate), location: item.location || '', venue: item.venue || '', image: item.coverImage || '/images/activity-default.jpg', bgColor: this.getRandomGradient(), price: item.priceText || '免费', priceType: item.priceType || 'free', likes: item.likesCount || 0, participants: item.currentParticipants || 0, maxParticipants: item.maxParticipants || 0, isLiked: item.isLiked || false, isSignedUp: item.isSignedUp || false, signupEnabled: item.signupEnabled !== undefined ? item.signupEnabled : true, activityGuideQrcode: item.activityGuideQrcode || '', categoryName: item.categoryName || '', heat: Math.floor(heat), participantAvatars: item.participantAvatars || [ 'https://i.pravatar.cc/100?u=1', 'https://i.pravatar.cc/100?u=2', 'https://i.pravatar.cc/100?u=3' ] } }) console.log('[13] 转换后活动数量:', newActivityList.length) const hasMore = activities.length >= limit && (this.data.activityList.length + activities.length) < total console.log('[14] hasMore:', hasMore, 'current:', this.data.activityList.length + activities.length, 'total:', 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 }) } console.log('[15] setData完成,当前页面活动数量:', this.data.activityList.length) } else { console.log('[ERROR] API返回失败') if (isLoadMore) { this.setData({ loadingMore: false }) } else { this.setData({ activityList: [], hasMore: false }) } } } catch (err) { console.error('[ERROR] 加载活动列表失败:', err) if (isLoadMore) { this.setData({ loadingMore: false }) } else { this.setData({ activityList: [], loading: false, hasMore: false }) } } finally { if (!isLoadMore) { this.setData({ loading: false }) } console.log('========== 加载完成 ==========') } }, /** * 加载模拟数据(降级方案) */ loadMockActivities() { // 使用空数据,等待后端API返回真实数据 const mockActivities = [] this.setData({ activityList: mockActivities }) }, /** * 格式化日期 */ formatDate(dateStr) { if (!dateStr) return '' 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}日` }, /** * 获取随机渐变色 */ getRandomGradient() { const gradients = [ 'linear-gradient(135deg, #E8D5F0 0%, #F5E6D3 100%)', 'linear-gradient(135deg, #D5F0E8 0%, #E6D3F5 100%)', 'linear-gradient(135deg, #F0D5E8 0%, #D3F5E6 100%)', 'linear-gradient(135deg, #F5E6D3 0%, #E8D5F0 100%)' ] return gradients[Math.floor(Math.random() * gradients.length)] }, /** * 加载未读消息数 */ async loadUnreadCount() { if (!app.globalData.isLoggedIn) { this.setData({ totalUnread: 0 }) return } try { const res = await api.chat.getConversations() if (res.success && res.data) { const totalUnread = res.data.reduce((sum, conv) => sum + (conv.unread_count || 0), 0) this.setData({ totalUnread }) } } catch (err) { console.log('获取未读消息数失败', err) } }, /** * 轮播图切换 */ onBannerChange(e) { // 只在用户手动滑动或自动播放时更新索引 if (e.detail.source === 'autoplay' || e.detail.source === 'touch') { this.setData({ currentBannerIndex: e.detail.current }) } }, /** * 轮播指示器点击 */ onDotTap(e) { const index = e.currentTarget.dataset.index // 避免重复设置相同索引 if (index !== this.data.currentBannerIndex) { this.setData({ currentBannerIndex: index }) } }, /** * 分类点击 */ onCategoryTap(e) { const { id, name } = e.currentTarget.dataset // 兴趣搭子跳转到专门页面 if (id === 1) { wx.navigateTo({ url: '/pages/interest-partner/interest-partner' }) return } // 同城活动跳转到专门页面 if (id === 2) { wx.navigateTo({ url: '/pages/city-activities/city-activities' }) return } // 户外郊游跳转到专门页面 if (id === 3) { wx.navigateTo({ url: '/pages/outdoor-activities/outdoor-activities' }) return } // 定制主题跳转到专门页面 if (id === 4) { wx.navigateTo({ url: '/pages/theme-travel/theme-travel' }) return } // 快乐学堂跳转到专门页面 if (id === 5) { wx.navigateTo({ url: '/pages/happy-school/happy-school' }) return } // 单身聚会跳转到专门页面 if (id === 6) { wx.navigateTo({ url: '/pages/singles-party/singles-party' }) return } wx.showToast({ title: `${name}功能开发中`, icon: 'none' }) // TODO: 跳转到对应分类页面 }, /** * 切换活动标签 */ onTabChange(e) { const tab = e.currentTarget.dataset.tab console.log('========== Tab切换开始 ==========') console.log('[1] 点击的Tab:', tab) console.log('[2] 当前activeTab:', this.data.activeTab) if (tab === this.data.activeTab) { console.log('[3] Tab未变化,跳过') return } console.log('[4] 更新activeTab为:', tab) this.setData({ activeTab: tab, activityList: [], page: 1, hasMore: true }) console.log('[5] 调用loadActivityList()') this.loadActivityList() }, /** * 下拉刷新 */ onPullDownRefresh() { this.loadActivityList(false).finally(() => { wx.stopPullDownRefresh() }) }, /** * 上拉加载更多 */ onReachBottom() { if (this.data.hasMore && !this.data.loadingMore && !this.data.loading) { console.log('[100] 触发上拉加载更多') this.loadActivityList(true) } else { console.log('[101] 不满足加载条件:', { hasMore: this.data.hasMore, loadingMore: this.data.loadingMore, loading: this.data.loading }) } }, /** * 活动卡片点击 */ onActivityTap(e) { const id = e.currentTarget.dataset.id wx.navigateTo({ url: `/pages/activity-detail/activity-detail?id=${id}` }) }, /** * 报名按钮点击 */ 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] // 检查活动状态:满员或结束时弹出二维码 const isFull = activity.participants >= activity.maxParticipants && activity.maxParticipants > 0 const isEnded = activity.status === 'ended' || (activity.endDate && new Date(activity.endDate) < new Date()) if (isFull || isEnded) { const qrCode = activity.activityGuideQrcode || activity.activity_guide_qrcode || this.data.qrcodeImageUrl || 'https://ai-c.maimanji.com/api/common/qrcode?type=group' this.setData({ qrcodeImageUrl: qrCode, showQrcodeModal: true }) return } // 如果报名功能已关闭,直接显示二维码 if (activity.signupEnabled === false) { if (activity.activityGuideQrcode) { this.setData({ qrcodeImageUrl: activity.activityGuideQrcode }) } this.setData({ showQrcodeModal: true }) return } try { if (activity.isSignedUp) { // 取消报名 const res = await api.activity.cancelSignup(id) if (res.success) { wx.showToast({ title: '已取消报名', icon: 'success' }) this.setData({ [`activityList[${index}].isSignedUp`]: false, [`activityList[${index}].participants`]: res.data.currentParticipants }) } } else { // 报名 const userInfo = app.globalData.userInfo || {} const res = await api.activity.signup(id, { remark: userInfo.nickname || '', contactPhone: userInfo.phone || '' }) if (res.success) { wx.showToast({ title: '报名成功', icon: 'success' }) this.setData({ [`activityList[${index}].isSignedUp`]: true, [`activityList[${index}].participants`]: res.data.currentParticipants }) } else { // 检查是否需要显示二维码(后端开关关闭) if (res.code === 'QR_CODE_REQUIRED') { if (activity.activityGuideQrcode) { this.setData({ qrcodeImageUrl: activity.activityGuideQrcode }) } this.setData({ showQrcodeModal: true }) } else if (res.code === 'ACTIVITY_ENDED' || res.error === '活动已结束') { if (activity.activityGuideQrcode) { this.setData({ qrcodeImageUrl: activity.activityGuideQrcode }) } this.setData({ showQrcodeModal: true }) 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) { this.setData({ qrcodeImageUrl: activity.activityGuideQrcode }) } this.setData({ showQrcodeModal: true }) if (isActivityEnded) { wx.showToast({ title: '活动已结束,进群查看更多', icon: 'none' }) } } else { wx.showToast({ title: err.error || err.message || '操作失败', icon: 'none' }) } } }, /** * 关闭二维码弹窗 */ 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 }, /** * 点赞 */ 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' }) } }, /** * Tab bar 导航 */ switchTab(e) { const path = e.currentTarget.dataset.path if (path === '/pages/chat/chat') { if (!app.globalData.isLoggedIn) { wx.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent(path) }) return } } wx.switchTab({ url: path }) } })