ai-c/pages/entertainment/entertainment.js
2026-02-02 18:21:32 +08:00

895 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 })
}
})