Files
WoMenQuNaJu/miniprogram/pages/room/room.js
2026-02-04 16:11:55 +08:00

611 lines
20 KiB
JavaScript
Raw 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.
const app = getApp()
const db = wx.cloud.database()
Page({
data: {
roomId: '',
roomName: '',
roomTime: '',
keywords: '',
keywordOptions: ['咖啡馆', '餐厅', '公园', '商场', '电影院', 'KTV', '酒吧', '火锅', '烧烤', '其他'],
keywordIndex: 0,
requirements: '',
members: [],
readyCount: 0,
hasResult: false,
currentUserOpenId: '',
isCreator: false,
calculating: false,
editingRequirements: false,
meetDate: '',
meetTimeOnly: '',
hasJoined: false,
userInfo: {
avatarUrl: '',
nickName: ''
},
showUserInfoModal: false,
previousStatus: '' // 新增:记录上一次的状态,用于判断是否刚完成计算
},
onLoad: function (options) {
const roomId = options.roomId
const isCreator = options.isCreator === 'true'
this.setData({
roomId,
isCreator,
// 如果是创建者,已经在创建时加入了房间
hasJoined: isCreator
})
// 直接开始监听房间,不需要先加入
this.startWatch()
},
onUnload: function () {
if (this.watcher) {
this.watcher.close()
}
},
async joinGroup() {
// 显示用户信息填写弹窗
this.setData({ showUserInfoModal: true })
},
onChooseAvatar(e) {
const { avatarUrl } = e.detail
this.setData({
'userInfo.avatarUrl': avatarUrl
})
},
onNicknameChange(e) {
const nickName = e.detail.value
this.setData({
'userInfo.nickName': nickName
})
},
onCancelUserInfo() {
this.setData({ showUserInfoModal: false })
},
async onConfirmUserInfo() {
const { userInfo } = this.data
// 检查是否填写了昵称
if (!userInfo.nickName || !userInfo.nickName.trim()) {
wx.showToast({
title: '请输入你的昵称',
icon: 'none'
})
return
}
// 关闭弹窗
this.setData({ showUserInfoModal: false })
// 调用云函数加入
wx.showLoading({ title: '加入房间...' })
try {
const res = await wx.cloud.callFunction({
name: 'joinRoom',
data: {
roomId: this.data.roomId,
userInfo: userInfo
}
})
wx.hideLoading()
if (!res.result.success) {
wx.showModal({ title: '错误', content: res.result.msg, showCancel: false })
return false
}
this.setData({ hasJoined: true })
// 加入成功后,继续添加位置
if (this.pendingAddLocation) {
this.pendingAddLocation()
this.pendingAddLocation = null
}
return true
} catch (err) {
console.error(err)
wx.hideLoading()
wx.showToast({ title: '加入失败', icon: 'none' })
return false
}
},
startWatch() {
// 先获取当前用户的 openid
this.getCurrentUserOpenId()
this.watcher = db.collection('rooms').doc(this.data.roomId).watch({
onChange: snapshot => {
if (snapshot.docs && snapshot.docs.length > 0) {
const roomData = snapshot.docs[0]
const members = roomData.members || []
const readyCount = members.filter(m => m.location).length
const hasResult = roomData.result && roomData.result.success && roomData.status === 'calculated'
const isCalculating = roomData.status === 'calculating'
const currentStatus = roomData.status
const roomTime = roomData.meetTime || ''
const [date, time] = roomTime.includes(' ') ? roomTime.split(' ') : [roomTime, '']
const keyword = roomData.keywords || '咖啡馆'
const keywordIndex = this.data.keywordOptions.indexOf(keyword)
// 检查当前用户是否已经在成员列表中
const currentUserOpenId = this.data.currentUserOpenId
const hasJoined = currentUserOpenId && members.some(m => m.openid === currentUserOpenId)
// 判断是否刚完成计算(从 calculating 变为 calculated
const justFinishedCalculating = this.data.previousStatus === 'calculating' && currentStatus === 'calculated'
this.setData({
members,
readyCount,
hasResult,
calculating: isCalculating, // 同步云端计算状态
roomName: roomData.name || '未命名聚会',
roomTime: roomTime,
meetDate: date,
meetTimeOnly: time,
keywords: keyword,
keywordIndex: keywordIndex >= 0 ? keywordIndex : 0,
requirements: roomData.requirements || '',
hasJoined: hasJoined || this.data.hasJoined,
previousStatus: currentStatus // 更新状态记录
})
// 只有刚完成计算时才自动跳转到结果页
if (justFinishedCalculating && hasResult) {
wx.navigateTo({
url: `/pages/result/result?roomId=${this.data.roomId}`
})
}
}
},
onError: err => {
console.error('Watch Error', err)
}
})
},
async getCurrentUserOpenId() {
try {
const res = await wx.cloud.callFunction({
name: 'getOpenId'
})
if (res.result && res.result.openid) {
this.setData({ currentUserOpenId: res.result.openid })
}
} catch (err) {
console.error('获取openid失败', err)
}
},
onAddLocation() {
if (!this.data.roomId) return
// 定义添加位置的函数
const doAddLocation = () => {
wx.chooseLocation({
success: (res) => {
const { latitude, longitude, address, name } = res
wx.showLoading({ title: '提交位置...' })
wx.cloud.callFunction({
name: 'updateLocation',
data: {
roomId: this.data.roomId,
location: {
lng: longitude,
lat: latitude,
address: address || '地图选点',
name: name || '我的位置'
}
},
success: res => {
wx.hideLoading()
if (res.result.success) {
wx.showToast({ title: '已更新', icon: 'success' })
} else {
wx.showToast({ title: res.result.msg || '更新失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error(err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
},
fail: (err) => {
if (err.errMsg.indexOf('cancel') === -1) {
wx.showToast({ title: '选择位置失败', icon: 'none' })
}
}
})
}
// 如果还没加入房间,先加入
if (!this.data.hasJoined) {
// 保存待执行的操作
this.pendingAddLocation = doAddLocation
this.joinGroup()
} else {
doAddLocation()
}
},
onShareAppMessage() {
return {
title: '快来填位置,我们去找个中间点聚会!',
path: `/pages/room/room?roomId=${this.data.roomId}`
}
},
// 添加测试成员(仅用于开发测试)
onAddTestMember() {
wx.showModal({
title: '添加测试成员',
content: '请输入成员名称',
editable: true,
placeholderText: '测试成员',
success: (res) => {
if (res.confirm && res.content) {
const nickName = res.content.trim() || '测试成员'
wx.showLoading({ title: '添加中...' })
wx.cloud.callFunction({
name: 'addTestMember',
data: {
roomId: this.data.roomId,
nickName: nickName
},
success: res => {
wx.hideLoading()
if (res.result.success) {
wx.showToast({ title: '添加成功', icon: 'success' })
} else {
wx.showToast({ title: res.result.msg || '添加失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error(err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
}
}
})
},
// 为测试成员添加/修改位置
onAddMemberLocation(e) {
const { openid, nickname, istest } = e.currentTarget.dataset
// 检查是否是测试成员,如果不是则不处理
if (!istest) {
return
}
wx.chooseLocation({
success: (res) => {
const { latitude, longitude, address, name } = res
wx.showLoading({ title: '提交中...' })
wx.cloud.callFunction({
name: 'updateMemberLocation',
data: {
roomId: this.data.roomId,
memberOpenid: openid,
location: {
lng: longitude,
lat: latitude,
address: address || '地图选点',
name: name || '选定位置'
}
},
success: res => {
wx.hideLoading()
if (res.result.success) {
wx.showToast({ title: `${nickname}位置已更新`, icon: 'success' })
} else {
wx.showToast({ title: res.result.msg || '更新失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error(err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
},
fail: (err) => {
if (err.errMsg.indexOf('cancel') === -1) {
wx.showToast({ title: '选择位置失败', icon: 'none' })
}
}
})
},
onCalculate() {
// 前端防抖:检查是否正在计算
if (this.data.calculating) {
wx.showToast({
title: '正在计算中,请稍候...',
icon: 'none',
duration: 2000
})
return
}
const membersWithLocation = this.data.members.filter(m => m.location).length
if (membersWithLocation < 2) {
wx.showToast({
title: '至少需要2人添加位置才能计算',
icon: 'none',
duration: 2000
})
return
}
// 设置计算状态,禁用按钮
this.setData({ calculating: true })
wx.showLoading({ title: '计算最佳地点...' })
wx.cloud.callFunction({
name: 'calculateMeetSpot',
data: { roomId: this.data.roomId },
success: res => {
wx.hideLoading()
this.setData({ calculating: false })
// 处理后端返回的状态锁信息
if (res.result.isCalculating) {
wx.showToast({
title: res.result.msg || '正在计算中',
icon: 'none',
duration: 2000
})
return
}
// 处理重复计算拦截
if (res.result.isDuplicate) {
wx.showModal({
title: '提示',
content: res.result.msg || '成员位置未变化,无需重复计算',
confirmText: '查看结果',
cancelText: '知道了',
success: (modalRes) => {
if (modalRes.confirm) {
// 跳转到结果页
wx.navigateTo({
url: `/pages/result/result?roomId=${this.data.roomId}`
})
}
}
})
return
}
if (!res.result.success) {
wx.showModal({
title: '计算失败',
content: res.result.msg,
showCancel: false
})
}
// 成功后依靠 watch 自动跳转,或者这里手动跳转也可以
},
fail: err => {
wx.hideLoading()
this.setData({ calculating: false })
console.error(err)
wx.showToast({ title: '调用失败', icon: 'none' })
}
})
},
onViewResult() {
if (!this.data.hasResult) {
wx.showToast({ title: '暂无计算结果', icon: 'none' })
return
}
wx.navigateTo({
url: `/pages/result/result?roomId=${this.data.roomId}`
})
},
onEditRoomName() {
wx.showModal({
title: '修改聚会名称',
content: '请输入新的聚会名称',
editable: true,
placeholderText: this.data.roomName,
success: (res) => {
if (res.confirm && res.content.trim()) {
const newName = res.content.trim()
this.updateRoomInfo({ name: newName })
}
}
})
},
onEditRoomTime() {
// 不需要做任何事,交给 picker 组件处理
},
onDateChange(e) {
const date = e.detail.value
this.setData({ meetDate: date })
this.updateMeetTime()
},
onTimeChange(e) {
const time = e.detail.value
this.setData({ meetTimeOnly: time })
this.updateMeetTime()
},
updateMeetTime() {
const { meetDate, meetTimeOnly } = this.data
let newTime = ''
if (meetDate && meetTimeOnly) {
newTime = `${meetDate} ${meetTimeOnly}`
} else if (meetDate) {
newTime = meetDate
}
if (newTime) {
this.setData({ roomTime: newTime })
this.updateRoomInfo({ meetTime: newTime })
}
},
onKeywordChange(e) {
const index = e.detail.value
const keyword = this.data.keywordOptions[index]
this.setData({
keywordIndex: index,
keywords: keyword
})
this.updateRoomInfo({ keywords: keyword })
},
onEditKeywords() {
// 已改用 picker此方法不再需要
},
onEditRequirements() {
this.setData({ editingRequirements: true })
},
onRequirementsInput(e) {
this.setData({ requirements: e.detail.value })
},
onRequirementsBlur() {
this.setData({ editingRequirements: false })
this.updateRoomInfo({ requirements: this.data.requirements })
},
onRequirementsConfirm() {
this.setData({ editingRequirements: false })
this.updateRoomInfo({ requirements: this.data.requirements })
},
updateRoomInfo(updateData) {
wx.showLoading({ title: '更新中...' })
wx.cloud.callFunction({
name: 'updateRoomInfo',
data: {
roomId: this.data.roomId,
...updateData
},
success: res => {
wx.hideLoading()
if (res.result && res.result.success) {
wx.showToast({ title: '更新成功', icon: 'success' })
} else {
wx.showToast({ title: res.result?.msg || '更新失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error('更新失败', err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
},
// 退出聚会(普通成员)
onLeaveRoom() {
wx.showModal({
title: '确认退出',
content: '确定要退出这个聚会吗?',
success: (res) => {
if (res.confirm) {
this.removeMember()
}
}
})
},
// 移除成员(创建者权限)
onRemoveMember(e) {
const { openid, nickname } = e.currentTarget.dataset
// 不能移除测试成员(测试成员应该用专门的删除逻辑)
const member = this.data.members.find(m => m.openid === openid)
if (member && member.isTestMember) {
wx.showToast({ title: '请使用删除功能移除测试成员', icon: 'none' })
return
}
wx.showModal({
title: '移除成员',
content: `确定要移除 ${nickname} 吗?`,
success: (res) => {
if (res.confirm) {
this.removeMember(openid, nickname)
}
}
})
},
// 执行移除操作
removeMember(memberOpenid = null, nickName = null) {
wx.showLoading({ title: '处理中...' })
wx.cloud.callFunction({
name: 'removeMember',
data: {
roomId: this.data.roomId,
memberOpenid: memberOpenid // 不传则退出自己
},
success: res => {
wx.hideLoading()
if (res.result.success) {
const msg = memberOpenid ? `已移除 ${nickName}` : res.result.msg
wx.showToast({
title: msg,
icon: 'success',
duration: 2000
})
// 如果是退出自己,返回首页
if (!memberOpenid) {
setTimeout(() => {
wx.navigateBack({ delta: 1 })
}, 2000)
}
} else {
wx.showModal({
title: '操作失败',
content: res.result.msg,
showCancel: false
})
}
},
fail: err => {
wx.hideLoading()
console.error('移除成员失败', err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
}
})