first commit

This commit is contained in:
ytc1012
2026-02-04 16:11:55 +08:00
commit 0f3ee050dc
165 changed files with 25795 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
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' })
}
})
}
})