功能需求:活动分享与嘉宾匹配系统 1. 活动分享入口 每个活动配置独立的分享链接和二维码,支持扫码或点击链接进入小程序。 2. 用户绑定流程 用户通过分享链接/二维码打开小程序,填写个人信息并绑定至对应活动。 若用户曾参与过历史活动,系统自动复用其历史信息并完成绑定,无需重复填写。 绑定成功后,在活动正式开始前,用户无法查看其他参与者的信息。 3. 信息可见性规则 活动开始前:仅允许查看自己的信息,其他用户信息不可见。 活动开始后:开放本活动内异性嘉宾资料查看与匹配,其他活动的用户信息完全隔离,不可见。 资料展示:男嘉宾、女嘉宾资料分开展示,用户仅可浏览异性嘉宾信息。 4. 嘉宾选择规则 每位用户最多可选择 3 位心仪嘉宾。 确认提交后不可再新增选择,但支持修改或撤销已选嘉宾。 管理员可设置匹配截止时间,到达截止时间后系统自动锁定,禁止任何修改。 5. 匹配判定规则 双向互选:若双方恰好互选,则判定为匹配成功,双方均可查看匹配结果。 多向匹配:用户同时与多位嘉宾匹配成功,全部同时展示。 单向选择:A选择B但B未选择A,A可在"谁选了我"页面看到B。 6. 用户页面说明 异性嘉宾列表:展示所有异性嘉宾资料,支持选择/取消选择。 我的选择:展示当前已选的3位嘉宾,支持修改/撤销(截止前)。 谁选了我:展示所有选择了自己的嘉宾(无论自己是否选择了对方)。 匹配成功:展示所有双向互选的匹配对象,支持查看对方完整资料。
125 lines
4.1 KiB
JavaScript
125 lines
4.1 KiB
JavaScript
const request = require('../../utils/request')
|
|
|
|
Page({
|
|
data: {
|
|
activityId: null,
|
|
activeTab: 'my_choices',
|
|
activityTitle: '',
|
|
tabs: [
|
|
{ key: 'my_choices', label: '我的选择' },
|
|
{ key: 'liked_me', label: '谁选了我' },
|
|
{ key: 'mutual_matches', label: '匹配成功' }
|
|
],
|
|
matches: [],
|
|
loading: false
|
|
},
|
|
|
|
onLoad(options) {
|
|
this.setData({
|
|
activityId: options.activity_id ? Number(options.activity_id) : null
|
|
})
|
|
},
|
|
|
|
onShow() {
|
|
this.loadMatches()
|
|
},
|
|
|
|
switchTab(e) {
|
|
const tab = e.currentTarget.dataset.tab
|
|
if (!tab || tab === this.data.activeTab) {
|
|
return
|
|
}
|
|
this.setData({ activeTab: tab })
|
|
this.loadMatches()
|
|
},
|
|
|
|
normalizeMatch(item) {
|
|
const hobbies = Array.isArray(item.hobbies) ? item.hobbies : Array.isArray(item.other_user && item.other_user.hobbies) ? item.other_user.hobbies : []
|
|
const nickname = item.nickname || (item.other_user && item.other_user.nickname) || ''
|
|
const avatarUrl = item.avatar_url || (item.other_user && item.other_user.avatar_url) || ''
|
|
const city = item.city || (item.other_user && item.other_user.city) || ''
|
|
const education = item.education || (item.other_user && item.other_user.education) || ''
|
|
const statusMap = {
|
|
success: { text: '匹配成功', statusClass: 'success-badge', itemClass: 'success-item' },
|
|
pending: { text: '待回应', statusClass: 'pending-badge', itemClass: 'pending-item' }
|
|
}
|
|
const matchStatus = item.match_status || 'pending'
|
|
const statusConfig = statusMap[matchStatus] || statusMap.pending
|
|
return {
|
|
...item,
|
|
match_status: matchStatus,
|
|
match_status_text: item.match_status_text || statusConfig.text,
|
|
match_status_class: statusConfig.statusClass,
|
|
match_item_class: statusConfig.itemClass,
|
|
nicknameText: nickname || '匿名用户',
|
|
avatar_url: avatarUrl,
|
|
cityText: city || '城市未知',
|
|
educationText: education || '学历未知',
|
|
hobbiesText: hobbies.length ? hobbies.join('、') : '未填写爱好',
|
|
timeText: item.matched_at || item.selected_at || ''
|
|
}
|
|
},
|
|
|
|
async loadMatches() {
|
|
this.setData({ loading: true })
|
|
try {
|
|
let remoteMatches = []
|
|
if (this.data.activityId) {
|
|
const endpointMap = {
|
|
my_choices: 'my-choices',
|
|
liked_me: 'liked-me',
|
|
mutual_matches: 'mutual-matches'
|
|
}
|
|
remoteMatches = await request({ url: `/activities/${this.data.activityId}/${endpointMap[this.data.activeTab]}` })
|
|
} else {
|
|
remoteMatches = await request({ url: '/matches/my' })
|
|
}
|
|
const normalizedMatches = Array.isArray(remoteMatches) ? remoteMatches.map((item) => this.normalizeMatch(item)) : []
|
|
this.setData({ matches: normalizedMatches })
|
|
} catch (err) {
|
|
console.error('Load my matches failed:', err)
|
|
wx.showToast({ title: err.message || '加载失败', icon: 'none' })
|
|
this.setData({ matches: [] })
|
|
} finally {
|
|
this.setData({ loading: false })
|
|
}
|
|
},
|
|
|
|
viewDetail(e) {
|
|
const userId = e.currentTarget.dataset.userId
|
|
const status = e.currentTarget.dataset.status
|
|
if (!userId) {
|
|
return
|
|
}
|
|
if (status !== 'success') {
|
|
wx.showToast({ title: '互选成功后才可查看完整资料', icon: 'none' })
|
|
return
|
|
}
|
|
if (this.data.activityId) {
|
|
wx.navigateTo({
|
|
url: `/pages/match-detail/match-detail?activity_id=${this.data.activityId}&target_user_id=${userId}`
|
|
})
|
|
return
|
|
}
|
|
wx.showToast({ title: '当前入口仅支持活动内详情', icon: 'none' })
|
|
},
|
|
|
|
async cancelChoice(e) {
|
|
const userId = e.currentTarget.dataset.userId
|
|
if (!this.data.activityId || !userId) {
|
|
return
|
|
}
|
|
try {
|
|
await request({
|
|
url: `/activities/${this.data.activityId}/choices/${userId}`,
|
|
method: 'DELETE'
|
|
})
|
|
wx.showToast({ title: '已撤销选择', icon: 'success' })
|
|
this.loadMatches()
|
|
} catch (err) {
|
|
console.error('Cancel choice failed:', err)
|
|
wx.showToast({ title: err.message || '撤销失败', icon: 'none' })
|
|
}
|
|
}
|
|
})
|