const request = require('../../utils/request') const { resolveImageUrl } = require('../../utils/media') function normalizeMediaUrlForHtml(url) { const trimmedUrl = String(url || '').trim() if (!trimmedUrl) { return '' } if (/^https?:\/\/[^/]+\/api\/v1\/https?:\/\//i.test(trimmedUrl)) { return trimmedUrl.replace(/^https?:\/\/[^/]+\/api\/v1\//i, '') } if (/^\/api\/v1\/https?:\/\//i.test(trimmedUrl)) { return trimmedUrl.replace(/^\/api\/v1\//i, '') } if (/^https?:\/\/[^/]+\/v1\/static\//i.test(trimmedUrl)) { return trimmedUrl.replace(/^https?:\/\/[^/]+\/v1\//i, '/api/v1/') } if (/^\/v1\/static\//i.test(trimmedUrl)) { return trimmedUrl.replace(/^\/v1\//i, '/api/v1/') } if (/^https?:\/\/[^/]+\/static\//i.test(trimmedUrl)) { return trimmedUrl.replace(/^https?:\/\/[^/]+/, '') } return trimmedUrl } function resolveHtmlImageSources(html) { if (!html) { return '' } const text = String(html) if (!text.includes(' { const trimmedUrl = normalizeMediaUrlForHtml(url) if (!trimmedUrl) { return match } if (/^https?:\/\/[^\s]+\/api\/v1\/data:/i.test(trimmedUrl)) { return `${attr}=${quote}${trimmedUrl.replace(/^https?:\/\/[^\s]+\/api\/v1\//i, '')}${quote}` } const resolved = resolveImageUrl(trimmedUrl) return resolved ? `${attr}=${quote}${resolved}${quote}` : match }) } Page({ data: { id: null, shareToken: '', activity: null, loading: false, submitting: false }, parseLaunchOptions(options = {}) { const normalized = { ...options } if (normalized.scene) { const sceneText = decodeURIComponent(normalized.scene) sceneText.split('&').forEach((segment) => { const [rawKey, ...rest] = segment.split('=') const key = rawKey || '' if (!key) { return } normalized[key] = rest.join('=') }) } const rawId = normalized.id || normalized.activity_id || '' const parsedId = rawId !== '' && !Number.isNaN(Number(rawId)) ? Number(rawId) : null const shareToken = normalized.share_token || '' return { id: parsedId, shareToken } }, onLoad(options) { const { id, shareToken } = this.parseLaunchOptions(options) this.setData({ id, shareToken }) if (id || shareToken) { this.loadDetail() return } wx.showToast({ title: '活动链接无效', icon: 'none' }) }, onShow() { const pendingBind = wx.getStorageSync('pending_activity_bind') if (pendingBind && Number(pendingBind.activity_id) === Number(this.data.id || 0)) { wx.removeStorageSync('pending_activity_bind') this.handleRegister() return } if (this.data.id || this.data.shareToken) { this.loadDetail() } }, onShareAppMessage() { const activity = this.data.activity || {} const shareToken = activity.share_token || this.data.shareToken || '' const shareImage = activity.cover_image ? resolveImageUrl(activity.cover_image) : '' return { title: activity.title || '邀请你参加这个活动', path: shareToken ? `/pages/activity-detail/activity-detail?share_token=${shareToken}` : `/pages/activity-detail/activity-detail?id=${this.data.id}`, imageUrl: shareImage } }, normalizeActivity(activity) { const stageTextMap = { before_start: '活动未开始,暂不可查看嘉宾信息', matching_open: '活动已开始,可进入活动', matching_closed: '匹配已截止,可查看选择结果', ended: '活动已结束' } const descriptionHtml = resolveHtmlImageSources(activity.description || '暂无活动详情') return { ...activity, cover_image: resolveImageUrl(activity.cover_image || ''), locationText: activity.location || '待定', signupDeadlineText: activity.signup_deadline || '未设置', matchDeadlineText: activity.match_deadline || '未设置', descriptionText: descriptionHtml, descriptionHtml, stageText: stageTextMap[activity.stage] || '活动状态待更新', actionText: activity.is_bound || activity.is_registered ? '已报名' : '立即报名', selectionSummaryText: `已选 ${activity.selected_count || 0}/${activity.selection_limit || 3},剩余 ${activity.remaining_count || 0}`, registered_preview: Array.isArray(activity.registered_preview) ? activity.registered_preview.map((item) => ({ ...item, avatar_blur_url: resolveImageUrl(item.avatar_blur_url || ''), nicknameText: item.nickname || '用户' })) : [] } }, async loadDetail() { this.setData({ loading: true }) try { const activity = this.data.shareToken ? await request({ url: `/activities/share/${this.data.shareToken}` }) : await request({ url: `/activities/${this.data.id}` }) this.setData({ id: activity.id || this.data.id, activity: this.normalizeActivity(activity) }) } catch (err) { console.error('Load activity detail failed:', err) wx.showToast({ title: '活动详情加载失败', icon: 'none' }) this.setData({ activity: null }) } finally { this.setData({ loading: false }) } }, goActivityMatch() { if (!this.data.id || !this.data.activity) { wx.showToast({ title: '活动信息加载中,请稍后重试', icon: 'none' }) return } wx.setStorageSync('match_entry_context', { source: 'activity', activity_id: this.data.id, activity_title: this.data.activity.title || '' }) wx.navigateTo({ url: '/pages/match/match' }) }, goMatchResults() { if (!this.data.id) { return } wx.navigateTo({ url: `/pages/my-matches/my-matches?activity_id=${this.data.id}` }) }, async handleRegister() { if (!this.data.activity) { return } if (this.data.activity.is_bound || this.data.activity.is_registered) { wx.showToast({ title: '你已绑定该活动', icon: 'none' }) return } if (this.data.submitting) { return } this.setData({ submitting: true }) const timer = setTimeout(() => { if (this.data.submitting) { this.setData({ submitting: false }) wx.showToast({ title: '网络超时,请重试', icon: 'none' }) } }, 15000) try { await request({ url: `/activities/${this.data.id}/bind`, method: 'POST', timeout: 15000 }) wx.showToast({ title: '报名成功', icon: 'success' }) this.setData({ 'activity.is_bound': true, 'activity.is_registered': true, 'activity.actionText': '已报名' }) this.loadDetail() } catch (err) { console.error('Bind activity failed:', err) if (err && err.message && err.message.includes('资料')) { wx.setStorageSync('pending_activity_bind', { activity_id: this.data.id, share_token: this.data.shareToken || '' }) wx.showModal({ title: '请先完善资料', content: err.message, success: (res) => { if (res.confirm) { wx.navigateTo({ url: '/pages/profile-edit/profile-edit' }) } } }) return } wx.showToast({ title: err.message || '报名失败,请重试', icon: 'none' }) } finally { clearTimeout(timer) this.setData({ submitting: false }) } } })