diff --git a/common/system/timeUtile.js b/common/system/timeUtile.js index 6667bc3..c301cbf 100644 --- a/common/system/timeUtile.js +++ b/common/system/timeUtile.js @@ -14,7 +14,8 @@ function parseTimeString(timeStr) { /** * 将时间对象格式化为指定格式的字符串 * @param {Date} date - 时间对象 - * @param {string} format - 格式字符串,如:yyyy-mm-dd HH:MM:ss + * @param {string} format - 格式字符串,如:yyyy-MM-dd HH:MM:ss + * 注意:MM 在日期部分(yyyy-MM-dd)表示月份,在时间部分(HH:MM)表示分钟 * @returns {string} 格式化后的时间字符串 */ function formatTime(date, format) { @@ -25,13 +26,28 @@ function formatTime(date, format) { const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); - return format - .replace('yyyy', year) - .replace('mm', month) - .replace('dd', day) - .replace('HH', hours) - .replace('MM', minutes) - .replace('ss', seconds); + // 使用临时占位符避免 MM 冲突 + // 先处理日期部分的 MM(在 yyyy 和 dd 之间的 MM 表示月份) + let result = format.replace(/(yyyy-)(MM)(-dd)/g, '$1__MONTH__$3'); + + // 再处理时间部分的 MM(在 HH 之后的 MM 表示分钟) + result = result.replace(/(HH:)(MM)/g, '$1__MINUTE__'); + + // 替换所有其他标记 + result = result.replace(/yyyy/g, year); + result = result.replace(/dd/g, day); + result = result.replace(/HH/g, hours); + result = result.replace(/mm/g, month); // 小写 mm 始终表示月份 + result = result.replace(/ss/g, seconds); + + // 最后替换临时占位符 + result = result.replace(/__MONTH__/g, month); + result = result.replace(/__MINUTE__/g, minutes); + + // 处理剩余的 MM(如果还有,默认表示月份) + result = result.replace(/MM/g, month); + + return result; } /** diff --git a/components/index/MahjongCard.vue b/components/index/MahjongCard.vue index 607a903..ff24a24 100644 --- a/components/index/MahjongCard.vue +++ b/components/index/MahjongCard.vue @@ -12,28 +12,28 @@ - + - + - + - + @@ -97,7 +97,13 @@ const playerPositions = computed(() => { const joinedCount = joinPerson.length // 根据总人数确定布局 - if (personCount === 2) { + if (personCount === 1) { + // 1人局:只显示自己,头像放在左侧位置(第二行左) + return { + positions: [null, joinPerson[0] || null, null, null], + joinPositions: [false, false, false, false] + } + } else if (personCount === 2) { // 2人局:第二行左边和右边 return { positions: [null, joinPerson[0] || null, joinPerson[1] || null, null], @@ -169,7 +175,10 @@ const getPlayerAtPosition = (position) => { const getJoinPlayerAtPosition = (index) => { const personCount = props.item.personCount - if (personCount === 2) { + if (personCount === 1) { + // 1人局:仅展示左侧位置(索引2) + return index === 2 + } else if (personCount === 2) { // 2人局:第二行左边和右边 return [2, 3].includes(index) } else if (personCount === 3) { @@ -320,7 +329,7 @@ const handleJoin = () => { .info-text { font-family: PingFang SC, PingFang SC; font-weight: 400; - font-size:18rpx; + font-size:24rpx; color: #575757; text-align: left; display: block; diff --git a/pages/appointment/appointment-page.vue b/pages/appointment/appointment-page.vue index dc1dce4..0172073 100644 --- a/pages/appointment/appointment-page.vue +++ b/pages/appointment/appointment-page.vue @@ -124,18 +124,19 @@ - - - - - - - - - - - 鸽子费(保证金),参与人需缴纳鸽子费。若有参与者在预约后没有赴约,其鸽子费由在场的所有人平分。组局成功或失败后鸽子费将全额返还。 - + + + + + + + + + + + 鸽子费(保证金),参与人需缴纳鸽子费。若有参与者在预约后没有赴约,其鸽子费由在场的所有人平分。组局成功或失败后鸽子费将全额返还。 + @@ -161,23 +162,23 @@ :defaultIndex="agePickerDefaultIndex" @confirm="onAgePickerConfirm" @cancel="() => agePickerVisible = false" @close="() => agePickerVisible = false"> - + - + 发起预约成功! - + + style="width: 45%; height: 100%;background-color: #f7f7f7;display: flex;align-items: center;justify-content: center; border-radius: 10rpx;" + @click="handleClosePopup"> 关闭 @@ -198,8 +199,12 @@ } from 'vue'; import { getConfigData, - getSubscribeMessage + getSubscribeMessage, + configData } from '@/common/server/config' + import { + onShareAppMessage + } from '@dcloudio/uni-app'; import { usePay } from '@/common/server/interface/user' @@ -285,7 +290,7 @@ tomorrow.setDate(tomorrow.getDate() + 1); const dayAfterTomorrow = new Date(today); dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 2); - + let dateText = ''; if (targetDate.getTime() === today.getTime()) { dateText = '今天'; @@ -298,11 +303,11 @@ const day = date.getDate(); dateText = `${month}月${day}日`; } - + // 获取星期 const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; const weekday = weekdays[date.getDay()]; - + return `${dateText} ${weekday}`; } /** @@ -322,11 +327,11 @@ const onDatePickerConfirm = async (e) => { console.log('日期选择器确认事件:', e); console.log('datePickerValue.value:', datePickerValue.value); - + // up-datetime-picker 的 confirm 事件返回的是对象 { value: 毫秒级时间戳, mode: 'date' } // 优先使用 v-model 绑定的 datePickerValue.value,因为它会在用户选择后自动更新 let timestampMs = datePickerValue.value; - + // 如果 v-model 的值无效,尝试从事件参数获取 if (!timestampMs || isNaN(timestampMs) || timestampMs <= 0) { if (e && typeof e === 'object' && e.value !== undefined) { @@ -335,9 +340,9 @@ timestampMs = e; } } - + console.log('提取的时间戳(毫秒):', timestampMs); - + // 确保是数字类型且有效 if (typeof timestampMs !== 'number' || isNaN(timestampMs) || timestampMs <= 0) { console.error('日期选择器返回的时间戳无效:', e, 'datePickerValue:', datePickerValue.value); @@ -348,7 +353,7 @@ datePickerVisible.value = false; return; } - + // 将日期转换为当天的 0 点(只取日期,不取时间) const date = new Date(timestampMs); if (isNaN(date.getTime())) { @@ -360,17 +365,17 @@ datePickerVisible.value = false; return; } - + const year = date.getFullYear(); const month = date.getMonth(); const day = date.getDate(); const dateAtMidnight = new Date(year, month, day, 0, 0, 0); - + // 转换为秒级时间戳 const selectedTimestamp = Math.floor(dateAtMidnight.getTime() / 1000); - + console.log('转换后的秒级时间戳:', selectedTimestamp); - + // 验证转换后的时间戳是否有效 if (isNaN(selectedTimestamp) || selectedTimestamp <= 0) { console.error('时间戳转换失败:', dateAtMidnight, selectedTimestamp); @@ -381,13 +386,13 @@ datePickerVisible.value = false; return; } - + // 如果是同一天,不需要重新加载 if (selectedDate.value && selectedDate.value === selectedTimestamp) { datePickerVisible.value = false; return; } - + // 更新选中的日期 selectedDate.value = selectedTimestamp; @@ -404,7 +409,7 @@ console.log('重新加载房间详情, roomId:', reservationInfo.value.room_id, 'date:', selectedTimestamp); await loadRoomDetailForReservation(reservationInfo.value.room_id, selectedTimestamp); } - + datePickerVisible.value = false; } @@ -608,6 +613,28 @@ const tipsShow = () => { submitPopupRef.value.open() } + + // 处理关闭弹窗,返回上一页 + const handleClosePopup = () => { + submitPopupRef.value.close() + refreshPrevPageRoomList() + goBack() + } + + // 刷新上一页的房间列表(发起预约入口页) + const refreshPrevPageRoomList = () => { + const pages = getCurrentPages() + if (!pages || pages.length < 2) return + const prevPage = pages[pages.length - 2] + const vm = prevPage?.$vm || prevPage + if (vm && typeof vm.loadRoomList === 'function') { + vm.loadRoomList() + return + } + if (vm && typeof vm.loadDates === 'function') { + vm.loadDates() + } + } const maxPlayerCount = ref(0) const peopleRange = ref([]) @@ -663,34 +690,58 @@ return gameRule ? gameRule.text : ''; } -const smokingOptions = ref([ - { value: 2, text: '不禁烟' }, - { value: 1, text: '禁烟' }, -]) -const genderOptions = ref([ - { value: 0, text: '不限' }, - { value: 1, text: '男' }, - { value: 2, text: '女' }, -]) -const depositOptions = ref([ - { value: 0, text: '0元' }, - { value: 5, text: '5元' }, - { value: 10, text: '10元' }, - { value: -1, text: '自定义' }, -]) + const smokingOptions = ref([{ + value: 2, + text: '不禁烟' + }, + { + value: 1, + text: '禁烟' + }, + ]) + const genderOptions = ref([{ + value: 0, + text: '不限' + }, + { + value: 1, + text: '男' + }, + { + value: 2, + text: '女' + }, + ]) + const depositOptions = ref([{ + value: 0, + text: '0元' + }, + { + value: 5, + text: '5元' + }, + { + value: 10, + text: '10元' + }, + { + value: -1, + text: '自定义' + }, + ]) -const currentValue = ref(0) -const customDeposit = ref('') + const currentValue = ref(0) + const customDeposit = ref('') -const onCustomDepositInput = (val) => { - // 仅保留数字与小数点,但需求是整数金额,按只允许数字处理 - let v = String(val).replace(/[^0-9]/g, '') - if (v === '') v = '0' - let n = Number(v) - if (n > 50) n = 50 - if (n < 0) n = 0 - customDeposit.value = String(n) -} + const onCustomDepositInput = (val) => { + // 仅保留数字与小数点,但需求是整数金额,按只允许数字处理 + let v = String(val).replace(/[^0-9]/g, '') + if (v === '') v = '0' + let n = Number(v) + if (n > 50) n = 50 + if (n < 0) n = 0 + customDeposit.value = String(n) + } const increment = () => { if (currentValue.value < 5) { @@ -752,6 +803,7 @@ const onCustomDepositInput = (val) => { return false } + if (!info.start_time || info.start_time === 0) { uni.showToast({ title: '请选择开始时间', @@ -835,27 +887,30 @@ const onCustomDepositInput = (val) => { } - try { - var messageId = await getSubscribeMessage(reservationInfo.value.deposit_fee); - console.log("messageId", messageId); - var subscribeMessage = await requestSubscribeMessage(messageId); - console.log("message", subscribeMessage); + try { + var messageId = await getSubscribeMessage(reservationInfo.value.deposit_fee); + console.log("messageId", messageId); + var subscribeMessage = await requestSubscribeMessage(messageId); + console.log("message", subscribeMessage); - // 显示加载状态 - uni.showLoading({ - title: '提交中...' - }) + // 显示加载状态 + uni.showLoading({ + title: '提交中...' + }) - // 处理自定义鸽子费 - let finalDeposit = reservationInfo.value.deposit_fee - if (finalDeposit === -1) { - const n = Number(customDeposit.value || 0) - if (isNaN(n) || n < 0 || n > 50) { - uni.showToast({ title: '自定义金额需在0-50之间', icon: 'none' }) - return - } - finalDeposit = n - } + // 处理自定义鸽子费 + let finalDeposit = reservationInfo.value.deposit_fee + if (finalDeposit === -1) { + const n = Number(customDeposit.value || 0) + if (isNaN(n) || n < 0 || n > 50) { + uni.showToast({ + title: '自定义金额需在0-50之间', + icon: 'none' + }) + return + } + finalDeposit = n + } // 准备提交数据 const submitData = { @@ -885,22 +940,22 @@ const onCustomDepositInput = (val) => { return } - var resPay = null; - if (submitData.deposit_fee > 0) { - resPay = await usePay(submitData.deposit_fee); - if (resPay == null) { - uni.showToast({ - title: '鸽子费订单创建失败,请重新提交预约数据!', - icon: 'none' - }) - return; + var resPay = null; + if (submitData.deposit_fee > 0) { + resPay = await usePay(submitData.deposit_fee); + if (resPay == null) { + uni.showToast({ + title: '鸽子费订单创建失败,请重新提交预约数据!', + icon: 'none' + }) + return; + } + subscribeMessage.result.paymentId = resPay.paymentId; + if (subscribeMessage.result != null) { + important_data = JSON.stringify(subscribeMessage.result); + } + submitData.important_data = important_data; } - subscribeMessage.result.paymentId = resPay.paymentId; - if (subscribeMessage.result != null) { - important_data = JSON.stringify(subscribeMessage.result); - } - submitData.important_data = important_data; - } // TODO: 调用后端API提交预约 // 调用后端API提交预约 @@ -967,7 +1022,7 @@ const onCustomDepositInput = (val) => { const savedMaxPlayerCount = maxPlayerCount.value; const savedPeopleRange = [...peopleRange.value]; const savedPeopleText = peopleText.value; - + reservationInfo.value = { room_id: savedRoomId, room_name: savedRoomName, @@ -988,7 +1043,7 @@ const onCustomDepositInput = (val) => { currentValue.value = 0 gameRuleRange.value = [] customDeposit.value = '' // 重置自定义鸽子费 - + // 保留人数范围 maxPlayerCount.value = savedMaxPlayerCount; peopleRange.value = savedPeopleRange; @@ -1009,11 +1064,18 @@ const onCustomDepositInput = (val) => { * 加载房间详情,用于预约页面 */ const loadRoomDetailForReservation = async (roomId, date) => { - console.log('loadRoomDetailForReservation 调用参数:', { roomId, date, dateType: typeof date }); - + console.log('loadRoomDetailForReservation 调用参数:', { + roomId, + date, + dateType: typeof date + }); + // 验证参数 if (!roomId || !date) { - console.error('loadRoomDetailForReservation 参数无效:', { roomId, date }); + console.error('loadRoomDetailForReservation 参数无效:', { + roomId, + date + }); uni.showToast({ title: '参数错误', icon: 'none' @@ -1021,7 +1083,7 @@ const onCustomDepositInput = (val) => { roomDetailLoading.value = false; return; } - + // 确保 date 是数字类型 const dateTimestamp = Number(date); if (isNaN(dateTimestamp) || dateTimestamp <= 0) { @@ -1033,14 +1095,14 @@ const onCustomDepositInput = (val) => { roomDetailLoading.value = false; return; } - + roomDetailLoading.value = true; try { console.log('调用 getRoomDetail, roomId:', roomId, 'date:', dateTimestamp); const detail = await getRoomDetail(roomId, dateTimestamp); if (detail) { roomDetail.value = detail; - + // 设置容量和人数范围 if (detail.capacity && detail.capacity > 0) { maxPlayerCount.value = detail.capacity; @@ -1048,7 +1110,7 @@ const onCustomDepositInput = (val) => { peopleText.value = "请选择游玩人数"; t.push({ value: 1, - text:'无需组局' + text: '无需组局' }); for (let i = 2; i <= detail.capacity; i++) { t.push({ @@ -1083,7 +1145,7 @@ const onCustomDepositInput = (val) => { if (config != null && config.config != null) { gameTypeRange.value = [...config.config.playingMethodOptions]; } - + // 必须从房间页跳转,需要传入房间信息 if (!options || !options.roomId) { uni.showToast({ @@ -1095,12 +1157,12 @@ const onCustomDepositInput = (val) => { }, 1500); return; } - + // 接收房间信息 const roomId = Number(options.roomId); const roomName = decodeURIComponent(options.roomName || '未知房间'); const date = Number(options.date); - + if (!roomId || !date) { uni.showToast({ title: '房间信息不完整', @@ -1111,12 +1173,12 @@ const onCustomDepositInput = (val) => { }, 1500); return; } - + // 自动填充房间信息 reservationInfo.value.room_id = roomId; reservationInfo.value.room_name = roomName; selectedDate.value = date; - + // 加载房间详情,获取可预约时段和容量信息 await loadRoomDetailForReservation(roomId, date); }) @@ -1188,6 +1250,53 @@ const onCustomDepositInput = (val) => { reservationInfo.value.max_age = maxAge agePickerVisible.value = false } + + // 分享处理函数 + onShareAppMessage(({ + from, + target, + webViewUrl + }) => { + console.log('onShareAppMessage', from, target, webViewUrl); + var resid = 0; + if (target != null && target.dataset && target.dataset.item) { + var item = target.dataset.item; + console.log('item', item); + // reservationData 的结构包含 id 字段 + if (item && item.id) { + resid = item.id; + } else if (item && item.data && item.data.id) { + // 兼容数据结构 + resid = item.data.id; + } + } + // 如果从 reservationData 获取不到,尝试从当前值获取 + if (resid === 0 && reservationData.value) { + if (reservationData.value.id) { + resid = reservationData.value.id; + } else if (reservationData.value.data && reservationData.value.data.id) { + resid = reservationData.value.data.id; + } + } + console.log('分享的预约ID:', resid); + + // 同步获取分享配置(因为 onShareAppMessage 需要同步返回) + const path = "pages/index/loading?id=" + resid; + // 直接访问 configData.value,避免异步调用 + if (configData.value && configData.value.config) { + return { + title: configData.value.config.shareTitle || '山雀搭子 - 麻将组局', + path: path, + imageUrl: configData.value.config.shareImage || '' + }; + } + // 默认配置 + return { + title: '山雀搭子 - 麻将组局', + path: path, + imageUrl: '' + }; + }); \ No newline at end of file diff --git a/pages/index/index.vue b/pages/index/index.vue index 8659476..a4bb52e 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -55,7 +55,7 @@ - + diff --git a/pages/index/loading.vue b/pages/index/loading.vue index 0f73316..f1e5785 100644 --- a/pages/index/loading.vue +++ b/pages/index/loading.vue @@ -1,6 +1,6 @@