1525 lines
44 KiB
Vue
1525 lines
44 KiB
Vue
<template>
|
||
<com-page-container-base ref="_containerBase">
|
||
<view class="content column">
|
||
<view class="header-row">
|
||
<image src="/static/back.png" class="back-icon" @click="goBack" mode="aspectFit"></image>
|
||
<text class="page-title">发起预约</text>
|
||
<view class="spacer-40"></view>
|
||
</view>
|
||
<view class="column" style="overflow-y: auto;">
|
||
<card-container marginTop="30rpx">
|
||
<label-field label="日期">
|
||
<view class="input-wrapper" style="padding: 15rpx 20rpx;" @click="openDatePicker">
|
||
<text>{{ getDateDisplayText() }}</text>
|
||
</view>
|
||
</label-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-field label="时间段">
|
||
<view v-if="roomDetailLoading" class="center" style="padding: 30rpx 0;">
|
||
<text style="color: #999; font-size: 24rpx;">加载时段信息中...</text>
|
||
</view>
|
||
<uni-data-select v-else v-model="selectedTimeSlot" placeholder="请选择时间段"
|
||
:localdata="timeSlotOptions" @change="onTimeSlotChange"></uni-data-select>
|
||
<view v-if="!roomDetailLoading && timeSlotOptions.length === 0" style="padding: 20rpx 0;">
|
||
<text style="color: #FF0000; font-size: 24rpx;">当前日期该房间暂无可预约时段</text>
|
||
</view>
|
||
</label-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-field label="最晚到店时间">
|
||
<view class="input-wrapper" style="padding: 15rpx 20rpx;" @click="openLatestDateTimePicker">
|
||
<text>{{ getLatestDateTimeDisplayText() }}</text>
|
||
</view>
|
||
</label-field>
|
||
</card-container>
|
||
|
||
<card-container marginTop="30rpx">
|
||
|
||
<label-field label="组局名称">
|
||
<view class="input-wrapper">
|
||
<up-input placeholder="请输入内容" border="surround" v-model="reservationInfo.title"></up-input>
|
||
</view>
|
||
</label-field>
|
||
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-field label="人数">
|
||
<uni-data-select v-model="reservationInfo.player_count" :placeholder="peopleText"
|
||
:localdata="peopleRange"></uni-data-select>
|
||
</label-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-field label="玩法类型">
|
||
<uni-data-select v-model="reservationInfo.game_type" placeholder="请选择玩法类型"
|
||
:localdata="gameTypeRange" @change="gameTypeRangeChange"></uni-data-select>
|
||
</label-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-field label="具体规则">
|
||
<uni-data-select v-model="reservationInfo.game_rule" :placeholder="gameRuleText"
|
||
:localdata="gameRuleRange" @change="gameRuleRangeChange"></uni-data-select>
|
||
</label-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-field label="其他补充">
|
||
<view class="input-wrapper">
|
||
<up-input placeholder="请输入其他补充内容" v-model="reservationInfo.extra_info"></up-input>
|
||
</view>
|
||
</label-field>
|
||
</card-container>
|
||
|
||
|
||
<card-container marginTop="30rpx">
|
||
<label-slect-field label="是否禁烟">
|
||
<view style="height: 61rpx;display: flex;justify-content: left;align-items: center;">
|
||
<com-appointment-radio-select :options="smokingOptions"
|
||
v-model="reservationInfo.is_smoking" />
|
||
</view>
|
||
</label-slect-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-slect-field label="性别">
|
||
<view style="height: 61rpx;display: flex;justify-content: left;align-items: center;">
|
||
<com-appointment-radio-select :options="genderOptions"
|
||
v-model="reservationInfo.gender_limit" />
|
||
</view>
|
||
</label-slect-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-slect-field label="年龄范围">
|
||
<view @click="openAgePicker"
|
||
style="height: 61rpx;display: flex;justify-content: left;align-items: center;"
|
||
class="clickable-row">
|
||
{{ getAgeRangeText() }}
|
||
</view>
|
||
</label-slect-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-slect-field label="信誉">
|
||
<view class="flex-center-row" style="height: 61rpx;">
|
||
<text class="inline-label" style="margin-top: 12rpx;">大于等于</text>
|
||
<view class="counter-container">
|
||
<view @click="decrement" :disabled="currentValue <= 0">
|
||
<uni-icons type="minus" size="24" color="#00AC4E" />
|
||
</view>
|
||
<view class="counter-value">
|
||
{{ currentValue.toFixed(1) }}
|
||
</view>
|
||
<view @click="increment" :disabled="currentValue >= 5">
|
||
<uni-icons type="plus" size="24" color="#00AC4E" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</label-slect-field>
|
||
</card-container>
|
||
|
||
<card-container marginTop="30rpx">
|
||
|
||
<label-slect-field label="鸽子费">
|
||
<com-appointment-radio-select :options="depositOptions" v-model="reservationInfo.deposit_fee" />
|
||
</label-slect-field>
|
||
<view v-if="reservationInfo.deposit_fee === -1" :style="{ height: lineHeight }"></view>
|
||
<label-slect-field v-if="reservationInfo.deposit_fee === -1" label="金额">
|
||
<view class="input-wrapper">
|
||
<up-input type="number" placeholder="请输入0-50" v-model="customDeposit" @input="onCustomDepositInput"></up-input>
|
||
</view>
|
||
</label-slect-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<text class="note-text">鸽子费(保证金),参与人需缴纳鸽子费。若有参与者在预约后没有赴约,其鸽子费由在场的所有人平分。组局成功或失败后鸽子费将全额返还。</text>
|
||
</card-container>
|
||
|
||
<view class="action-row">
|
||
<view class="center reset-button" @click="resetForm">
|
||
<text style="margin: 20rpx;">重置</text>
|
||
</view>
|
||
<view class="center submit-button action-submit" @click="submitReservation">
|
||
<text style="margin: 20rpx; color: white;">发起预约</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="center note-container" @click="tipsShow">
|
||
|
||
<!-- <text class="muted-text">组局成功后,发起者可通过店员领取线下红包</text> -->
|
||
</view>
|
||
|
||
|
||
</view>
|
||
</view>
|
||
<up-datetime-picker :show="datePickerVisible" v-model="datePickerValue" mode="date"
|
||
:minDate="datePickerMinDate" :maxDate="datePickerMaxDate" title="选择日期" @confirm="onDatePickerConfirm"
|
||
@cancel="() => datePickerVisible = false" @close="() => datePickerVisible = false"></up-datetime-picker>
|
||
<up-datetime-picker :show="latestDateTimePickerVisible" v-model="latestDateTimePickerValue" mode="time"
|
||
:minHour="latestDateTimeMinHour" :maxHour="latestDateTimeMaxHour"
|
||
:minMinute="latestDateTimeMinMinute" :maxMinute="latestDateTimeMaxMinute"
|
||
title="选择最晚到店时间" @confirm="onLatestDateTimePickerConfirm"
|
||
@cancel="() => latestDateTimePickerVisible = false" @close="() => latestDateTimePickerVisible = false"></up-datetime-picker>
|
||
<up-picker title="年龄范围选择" :show="agePickerVisible" :columns="agePickerColumns" :keyName="'text'"
|
||
:defaultIndex="agePickerDefaultIndex" @confirm="onAgePickerConfirm" @cancel="() => agePickerVisible = false"
|
||
@close="() => agePickerVisible = false"></up-picker>
|
||
|
||
<uni-popup ref="submitPopupRef" type="center">
|
||
|
||
<view style="width: 90vw;height:300rpx;background-color: #fff;border-radius: 20rpx;">
|
||
<view>
|
||
<view style="height: 80rpx;"></view>
|
||
<view style="font-size: 32rpx;font-weight: 500;color: #000;text-align: center;">发起预约成功!</view>
|
||
<view style="height:100rpx;"></view>
|
||
<view style="display: flex;width: 100%;height:100rpx;">
|
||
<button @click.stop open-type="share"
|
||
style=" background-color:#00AC4E;color: #fff;width: 50%;height: 100%; background-color:#00AC4E;"
|
||
class="center evaluate-btn" :data-item="reservation">
|
||
<text class="evaluate-btn-text">分享给好友</text>
|
||
</button>
|
||
|
||
<view
|
||
style="width: 50%;height: 100%;background-color: #f7f7f7;display: flex;align-items: center;justify-content: center;"
|
||
@click="submitPopupRef.close();">
|
||
<text>关闭</text>
|
||
</view>
|
||
</view>
|
||
|
||
</view>
|
||
</view>
|
||
</uni-popup>
|
||
|
||
</com-page-container-base>
|
||
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
watch,
|
||
nextTick
|
||
} from 'vue';
|
||
import {
|
||
getConfigData,
|
||
getSubscribeMessage
|
||
} from '@/common/server/config'
|
||
import {
|
||
usePay
|
||
} from '@/common/server/interface/user'
|
||
import {
|
||
requestSubscribeMessage,
|
||
requestPayment
|
||
} from '@/common/utils'
|
||
import {
|
||
isLogin
|
||
} from '@/common/server/user'
|
||
import {
|
||
getDetail
|
||
} from '@/common/server/index'
|
||
|
||
import LabelField from '@/components/com/appointment/label-field.vue'
|
||
import LabelSlectField from '@/components/com/appointment/label-slect-field.vue'
|
||
import CardContainer from '@/components/com/appointment/card-container.vue'
|
||
import ComAppointmentRadioSelect from '@/components/com/appointment/radio-select.vue'
|
||
import {
|
||
addSQReservation,
|
||
cancelReservation,
|
||
canCreateSQReservation,
|
||
getRoomDetail
|
||
} from '@/common/server/interface/sq'
|
||
const submitPopupRef = ref(null)
|
||
// 年龄选择器状态
|
||
const agePickerVisible = ref(false)
|
||
const agePickerColumns = ref([
|
||
[],
|
||
[]
|
||
])
|
||
const agePickerDefaultIndex = ref([0, 0])
|
||
const reservationData = ref(null)
|
||
const lineHeight = ref("15rpx")
|
||
// 房间页跳转相关
|
||
const selectedDate = ref(null) // 选中的日期时间戳(秒级)
|
||
const selectedTimeSlot = ref(null) // 选中的时段类型 0-3,null表示未选择
|
||
const roomDetail = ref(null) // 房间详情数据
|
||
const roomDetailLoading = ref(false) // 房间详情加载状态
|
||
const timeSlotOptions = ref([]) // 时间段选项,从房间详情动态生成
|
||
// 日期选择器状态
|
||
const datePickerVisible = ref(false)
|
||
const datePickerValue = ref(Date.now())
|
||
const datePickerMinDate = ref(Date.now()) // 最小日期为今天
|
||
// 最大日期为未来7天(今天+未来6天)
|
||
const datePickerMaxDate = ref(new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).getTime())
|
||
// 最晚到店时间选择器状态
|
||
const latestDateTimePickerVisible = ref(false)
|
||
const latestDateTimePickerValue = ref('00:00') // 时间选择器值(HH:MM格式字符串)
|
||
const latestDateTime = ref(null) // 最晚到店时间(秒级时间戳)
|
||
// 时间选择器范围限制
|
||
const latestDateTimeMinHour = ref(0)
|
||
const latestDateTimeMaxHour = ref(23)
|
||
const latestDateTimeMinMinute = ref(0)
|
||
const latestDateTimeMaxMinute = ref(59)
|
||
//提交表单数据
|
||
const reservationInfo = ref({
|
||
room_id: 0, //房间id 非空
|
||
room_name: '请选择房间', //房间名称
|
||
start_time: 0, //开始时间 时间戳 非空
|
||
end_time: 0, //结束时间 时间戳 非空
|
||
max_age: 0, //最大年龄 默认0
|
||
min_age: 0, //最小年龄 非空
|
||
title: '', //组局名称 非空
|
||
extra_info: '', //其它说明 可为空
|
||
game_rule: '', //具体规则 非空
|
||
game_type: '', //玩法类型 非空
|
||
gender_limit: 0, //性别限制
|
||
is_smoking: 2, //是否禁烟
|
||
credit_limit: 0, //信誉限制
|
||
deposit_fee: 0, //鸽子费
|
||
player_count: 0, //人数
|
||
});
|
||
const getRoomPickerName = () => {
|
||
return reservationInfo.value.room_name;
|
||
}
|
||
/**
|
||
* 获取日期显示文本
|
||
*/
|
||
const getDateDisplayText = () => {
|
||
if (!selectedDate.value) {
|
||
return '请选择日期';
|
||
}
|
||
const date = new Date(selectedDate.value * 1000);
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||
const tomorrow = new Date(today);
|
||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||
const dayAfterTomorrow = new Date(today);
|
||
dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 2);
|
||
|
||
let dateText = '';
|
||
if (targetDate.getTime() === today.getTime()) {
|
||
dateText = '今天';
|
||
} else if (targetDate.getTime() === tomorrow.getTime()) {
|
||
dateText = '明天';
|
||
} else if (targetDate.getTime() === dayAfterTomorrow.getTime()) {
|
||
dateText = '后天';
|
||
} else {
|
||
const month = date.getMonth() + 1;
|
||
const day = date.getDate();
|
||
dateText = `${month}月${day}日`;
|
||
}
|
||
|
||
// 获取星期
|
||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||
const weekday = weekdays[date.getDay()];
|
||
|
||
return `${dateText} ${weekday}`;
|
||
}
|
||
/**
|
||
* 打开日期选择器
|
||
*/
|
||
const openDatePicker = () => {
|
||
if (selectedDate.value) {
|
||
datePickerValue.value = selectedDate.value * 1000; // 转换为毫秒
|
||
} else {
|
||
datePickerValue.value = Date.now();
|
||
}
|
||
datePickerVisible.value = true;
|
||
}
|
||
/**
|
||
* 日期选择器确认
|
||
*/
|
||
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) {
|
||
timestampMs = e.value;
|
||
} else if (typeof e === 'number' && !isNaN(e) && e > 0) {
|
||
timestampMs = e;
|
||
}
|
||
}
|
||
|
||
console.log('提取的时间戳(毫秒):', timestampMs);
|
||
|
||
// 确保是数字类型且有效
|
||
if (typeof timestampMs !== 'number' || isNaN(timestampMs) || timestampMs <= 0) {
|
||
console.error('日期选择器返回的时间戳无效:', e, 'datePickerValue:', datePickerValue.value);
|
||
uni.showToast({
|
||
title: '日期选择失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
datePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
// 将日期转换为当天的 0 点(只取日期,不取时间)
|
||
const date = new Date(timestampMs);
|
||
if (isNaN(date.getTime())) {
|
||
console.error('日期转换失败:', timestampMs);
|
||
uni.showToast({
|
||
title: '日期格式错误',
|
||
icon: 'none'
|
||
});
|
||
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);
|
||
uni.showToast({
|
||
title: '日期处理失败',
|
||
icon: 'none'
|
||
});
|
||
datePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
// 如果是同一天,不需要重新加载
|
||
if (selectedDate.value && selectedDate.value === selectedTimestamp) {
|
||
datePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
// 更新选中的日期
|
||
selectedDate.value = selectedTimestamp;
|
||
|
||
// 重置时段选择
|
||
selectedTimeSlot.value = null;
|
||
reservationInfo.value.start_time = 0;
|
||
reservationInfo.value.end_time = 0;
|
||
// 重置最晚到店时间
|
||
latestDateTime.value = null;
|
||
|
||
// 重新加载房间详情
|
||
if (reservationInfo.value.room_id) {
|
||
console.log('重新加载房间详情, roomId:', reservationInfo.value.room_id, 'date:', selectedTimestamp);
|
||
await loadRoomDetailForReservation(reservationInfo.value.room_id, selectedTimestamp);
|
||
}
|
||
|
||
datePickerVisible.value = false;
|
||
}
|
||
/**
|
||
* 获取最晚到店时间显示文本
|
||
*/
|
||
const getLatestDateTimeDisplayText = () => {
|
||
if (!latestDateTime.value) {
|
||
return '请选择最晚到店时间';
|
||
}
|
||
const date = new Date(latestDateTime.value * 1000);
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const hours = String(date.getHours()).padStart(2, '0');
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||
}
|
||
/**
|
||
* 获取时间段的时间范围限制
|
||
*/
|
||
const getTimeSlotRange = () => {
|
||
if (!selectedTimeSlot.value || selectedTimeSlot.value === null || selectedTimeSlot.value === undefined) {
|
||
return { minHour: 0, maxHour: 23, minMinute: 0, maxMinute: 59 };
|
||
}
|
||
|
||
let minHour = 0, maxHour = 23, minMinute = 0, maxMinute = 59;
|
||
|
||
switch (selectedTimeSlot.value) {
|
||
case 0: // 凌晨 00:00-05:59
|
||
minHour = 0;
|
||
maxHour = 5;
|
||
minMinute = 0;
|
||
maxMinute = 59;
|
||
break;
|
||
case 1: // 上午 06:00-11:59
|
||
minHour = 6;
|
||
maxHour = 11;
|
||
minMinute = 0;
|
||
maxMinute = 59;
|
||
break;
|
||
case 2: // 下午 12:00-17:59
|
||
minHour = 12;
|
||
maxHour = 17;
|
||
minMinute = 0;
|
||
maxMinute = 59;
|
||
break;
|
||
case 3: // 晚上 18:00-23:59
|
||
minHour = 18;
|
||
maxHour = 23;
|
||
minMinute = 0;
|
||
maxMinute = 59;
|
||
break;
|
||
}
|
||
|
||
return { minHour, maxHour, minMinute, maxMinute };
|
||
}
|
||
|
||
/**
|
||
* 打开最晚到店时间选择器
|
||
*/
|
||
const openLatestDateTimePicker = () => {
|
||
if (!selectedDate.value) {
|
||
uni.showToast({
|
||
title: '请先选择日期',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!selectedTimeSlot.value || selectedTimeSlot.value === null || selectedTimeSlot.value === undefined) {
|
||
uni.showToast({
|
||
title: '请先选择时间段',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 设置时间段范围限制
|
||
const timeRange = getTimeSlotRange();
|
||
latestDateTimeMinHour.value = timeRange.minHour;
|
||
latestDateTimeMaxHour.value = timeRange.maxHour;
|
||
latestDateTimeMinMinute.value = timeRange.minMinute;
|
||
latestDateTimeMaxMinute.value = timeRange.maxMinute;
|
||
|
||
// 如果有选择时间段,使用自动计算的时间
|
||
updateLatestDateTimeFromSlot();
|
||
|
||
// 将时间戳转换为 HH:MM 格式字符串
|
||
let timeString = null;
|
||
if (latestDateTime.value) {
|
||
const date = new Date(latestDateTime.value * 1000);
|
||
const hours = String(date.getHours()).padStart(2, '0');
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
timeString = `${hours}:${minutes}`;
|
||
} else {
|
||
// 默认使用时间段开始时间+30分钟
|
||
const timeRangeForDefault = getTimeSlotRange();
|
||
let defaultHour = timeRangeForDefault.minHour;
|
||
let defaultMinute = 30;
|
||
if (defaultMinute > timeRangeForDefault.maxMinute) {
|
||
defaultMinute = timeRangeForDefault.maxMinute;
|
||
}
|
||
timeString = `${String(defaultHour).padStart(2, '0')}:${String(defaultMinute).padStart(2, '0')}`;
|
||
}
|
||
|
||
// 确保时间字符串格式正确(必须是 "HH:MM" 格式)
|
||
if (timeString && typeof timeString === 'string' && timeString.match(/^\d{2}:\d{2}$/)) {
|
||
latestDateTimePickerValue.value = timeString;
|
||
} else {
|
||
// 如果格式不正确,使用时间段开始时间作为默认值
|
||
const timeRangeForFallback = getTimeSlotRange();
|
||
latestDateTimePickerValue.value = `${String(timeRangeForFallback.minHour).padStart(2, '0')}:00`;
|
||
}
|
||
|
||
// 确保值设置完成后再打开选择器
|
||
nextTick(() => {
|
||
latestDateTimePickerVisible.value = true;
|
||
});
|
||
}
|
||
/**
|
||
* 最晚到店时间选择器确认
|
||
*/
|
||
const onLatestDateTimePickerConfirm = async (e) => {
|
||
// up-datetime-picker mode="time" 时,v-model 绑定的值应该是 "HH:MM" 格式字符串
|
||
// 事件参数 e 可能是对象 { value: "HH:MM", mode: 'time' } 或直接是字符串 "HH:MM"
|
||
let timeString = latestDateTimePickerValue.value;
|
||
|
||
// 如果 v-model 的值无效,尝试从事件参数获取
|
||
if (!timeString || typeof timeString !== 'string') {
|
||
if (e && typeof e === 'object' && e.value !== undefined) {
|
||
timeString = e.value;
|
||
} else if (typeof e === 'string') {
|
||
timeString = e;
|
||
}
|
||
}
|
||
|
||
// 验证时间字符串格式
|
||
if (!timeString || typeof timeString !== 'string' || !timeString.includes(':')) {
|
||
console.error('时间选择器返回的值格式无效:', e, 'latestDateTimePickerValue:', latestDateTimePickerValue.value);
|
||
uni.showToast({
|
||
title: '时间选择失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
// 解析时间字符串 "HH:MM"
|
||
const timeParts = timeString.split(':');
|
||
if (timeParts.length !== 2) {
|
||
console.error('时间格式错误:', timeString);
|
||
uni.showToast({
|
||
title: '时间格式错误',
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
const hours = parseInt(timeParts[0], 10);
|
||
const minutes = parseInt(timeParts[1], 10);
|
||
|
||
if (isNaN(hours) || isNaN(minutes) || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||
console.error('时间值无效:', hours, minutes);
|
||
uni.showToast({
|
||
title: '时间值无效',
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
// 验证选择的时间是否在时间段范围内
|
||
if (selectedTimeSlot.value !== null && selectedTimeSlot.value !== undefined) {
|
||
const timeRange = getTimeSlotRange();
|
||
if (hours < timeRange.minHour || hours > timeRange.maxHour) {
|
||
uni.showToast({
|
||
title: `时间必须在 ${timeRange.minHour}:00-${timeRange.maxHour}:59 范围内`,
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
if (hours === timeRange.minHour && minutes < timeRange.minMinute) {
|
||
uni.showToast({
|
||
title: `时间必须在 ${timeRange.minHour}:${String(timeRange.minMinute).padStart(2, '0')}-${timeRange.maxHour}:59 范围内`,
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
if (hours === timeRange.maxHour && minutes > timeRange.maxMinute) {
|
||
uni.showToast({
|
||
title: `时间必须在 ${timeRange.minHour}:00-${timeRange.maxHour}:${String(timeRange.maxMinute).padStart(2, '0')} 范围内`,
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果没有选择日期,使用当前日期
|
||
if (!selectedDate.value) {
|
||
uni.showToast({
|
||
title: '请先选择日期',
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
// 将选择的日期和时间组合
|
||
const date = new Date(selectedDate.value * 1000);
|
||
const year = date.getFullYear();
|
||
const month = date.getMonth();
|
||
const day = date.getDate();
|
||
const dateTime = new Date(year, month, day, hours, minutes, 0);
|
||
|
||
// 转换为秒级时间戳
|
||
const selectedTimestamp = Math.floor(dateTime.getTime() / 1000);
|
||
|
||
// 验证转换后的时间戳是否有效
|
||
if (isNaN(selectedTimestamp) || selectedTimestamp <= 0) {
|
||
console.error('时间戳转换失败:', dateTime, selectedTimestamp);
|
||
uni.showToast({
|
||
title: '时间处理失败',
|
||
icon: 'none'
|
||
});
|
||
latestDateTimePickerVisible.value = false;
|
||
return;
|
||
}
|
||
|
||
// 更新最晚到店时间
|
||
latestDateTime.value = selectedTimestamp;
|
||
|
||
latestDateTimePickerVisible.value = false;
|
||
}
|
||
/**
|
||
* 根据时间段自动更新最晚到店时间(时间段开始时间+30分钟)
|
||
*/
|
||
const updateLatestDateTimeFromSlot = () => {
|
||
if (!selectedDate.value || selectedTimeSlot.value === null || selectedTimeSlot.value === undefined) {
|
||
return;
|
||
}
|
||
|
||
// 将日期时间戳转换为日期对象
|
||
const date = new Date(selectedDate.value * 1000);
|
||
const year = date.getFullYear();
|
||
const month = date.getMonth();
|
||
const day = date.getDate();
|
||
|
||
// 根据时段类型设置开始时间
|
||
let startHour = 0;
|
||
let startMinute = 30; // 默认加30分钟
|
||
|
||
switch (selectedTimeSlot.value) {
|
||
case 0: // 凌晨 00:00-05:59
|
||
startHour = 0;
|
||
startMinute = 30;
|
||
break;
|
||
case 1: // 上午 06:00-11:59
|
||
startHour = 6;
|
||
startMinute = 30;
|
||
break;
|
||
case 2: // 下午 12:00-17:59
|
||
startHour = 12;
|
||
startMinute = 30;
|
||
break;
|
||
case 3: // 晚上 18:00-23:59
|
||
startHour = 18;
|
||
startMinute = 30;
|
||
break;
|
||
}
|
||
|
||
// 创建最晚到店时间(开始时间+30分钟)
|
||
const latestDateTimeObj = new Date(year, month, day, startHour, startMinute, 0);
|
||
|
||
// 转换为秒级时间戳
|
||
latestDateTime.value = Math.floor(latestDateTimeObj.getTime() / 1000);
|
||
}
|
||
const tipsShow = () => {
|
||
submitPopupRef.value.open()
|
||
}
|
||
const maxPlayerCount = ref(0)
|
||
|
||
const peopleRange = ref([])
|
||
const peopleText = ref("请先选择房间");
|
||
const gameTypeRange = ref([])
|
||
const gameRuleRange = ref([])
|
||
const gameRuleText = ref("请先选择玩法类型");
|
||
const gameTypeRangeChange = (e) => {
|
||
console.log('gameTypeRangeChange:', e)
|
||
// gameRuleRange.value
|
||
if (e == 0) {
|
||
gameRuleRange.value = [];
|
||
reservationInfo.value.game_rule = 0;
|
||
gameRuleText.value = "请先选择玩法类型";
|
||
} else {
|
||
var f = gameTypeRange.value.find(it => it.value == e);
|
||
console.log('f', f);
|
||
gameRuleText.value = "请选择具体规则";
|
||
var temp = [];
|
||
|
||
if (f.children != null && f.children.length > 0) {
|
||
|
||
f.children.forEach(e => {
|
||
temp.push({
|
||
value: e.id,
|
||
text: e.name
|
||
})
|
||
})
|
||
}
|
||
gameRuleRange.value = temp;
|
||
|
||
}
|
||
}
|
||
const gameRuleRangeChange = (e) => {
|
||
console.log('gameRuleRangeChange:', e)
|
||
}
|
||
|
||
// 获取玩法类型文本
|
||
const getGameTypeText = (gameTypeValue) => {
|
||
if (!gameTypeValue || gameTypeValue === 0) {
|
||
return '';
|
||
}
|
||
const gameType = gameTypeRange.value.find(item => item.value === gameTypeValue);
|
||
return gameType ? gameType.text : '';
|
||
}
|
||
|
||
// 获取具体规则文本
|
||
const getGameRuleText = (gameRuleValue) => {
|
||
if (!gameRuleValue || gameRuleValue === 0) {
|
||
return '';
|
||
}
|
||
const gameRule = gameRuleRange.value.find(item => item.value === gameRuleValue);
|
||
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 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 increment = () => {
|
||
if (currentValue.value < 5) {
|
||
currentValue.value += 0.5
|
||
if (currentValue.value > 5) {
|
||
currentValue.value = 5
|
||
}
|
||
// 同步到表单对象
|
||
reservationInfo.value.credit_limit = currentValue.value
|
||
}
|
||
}
|
||
|
||
const decrement = () => {
|
||
if (currentValue.value > 0) {
|
||
currentValue.value -= 0.5
|
||
if (currentValue.value < 0) {
|
||
currentValue.value = 0
|
||
}
|
||
// 同步到表单对象
|
||
reservationInfo.value.credit_limit = currentValue.value
|
||
}
|
||
}
|
||
|
||
// 返回上一页
|
||
const goBack = () => {
|
||
uni.navigateBack({
|
||
delta: 1
|
||
})
|
||
}
|
||
|
||
// 表单验证方法
|
||
const validateForm = async () => {
|
||
const info = reservationInfo.value
|
||
|
||
// 必填字段验证
|
||
if (!info.room_id || info.room_id === 0) {
|
||
uni.showToast({
|
||
title: '请选择房间',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
// 检查是否选择了时段
|
||
if (selectedTimeSlot.value === null || selectedTimeSlot.value === undefined) {
|
||
uni.showToast({
|
||
title: '请选择时间段',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
// 验证选择的时段是否仍然可预约
|
||
if (roomDetail.value && roomDetail.value.time_slots) {
|
||
const selectedSlot = roomDetail.value.time_slots.find(
|
||
slot => slot.slot_type === selectedTimeSlot.value
|
||
);
|
||
|
||
if (!selectedSlot || selectedSlot.status !== 'available') {
|
||
uni.showToast({
|
||
title: '该时段已不可预约,请重新选择',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
// 重新加载房间详情,更新可预约时段
|
||
if (reservationInfo.value.room_id && selectedDate.value) {
|
||
await loadRoomDetailForReservation(reservationInfo.value.room_id, selectedDate.value);
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (!info.start_time || info.start_time === 0) {
|
||
uni.showToast({
|
||
title: '请选择时间段',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!info.end_time || info.end_time === 0) {
|
||
uni.showToast({
|
||
title: '请选择时间段',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (info.end_time <= info.start_time) {
|
||
uni.showToast({
|
||
title: '结束时间需晚于开始时间',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!info.title || info.title.trim() === '') {
|
||
uni.showToast({
|
||
title: '请输入组局名称',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!info.player_count || info.player_count === 0) {
|
||
uni.showToast({
|
||
title: '请选择游玩人数',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!info.game_type || info.game_type === 0) {
|
||
uni.showToast({
|
||
title: '请选择玩法类型',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!info.game_rule || info.game_rule === 0) {
|
||
uni.showToast({
|
||
title: '请选择具体规则',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
// 年龄范围验证(如果设置了年龄限制)
|
||
if (info.min_age > 0 && info.max_age > 0 && info.min_age > info.max_age) {
|
||
uni.showToast({
|
||
title: '最小年龄不能大于最大年龄',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// 提交预约方法
|
||
const submitReservation = async () => {
|
||
var isLoginSucces = await isLogin();
|
||
if (!isLoginSucces) {
|
||
uni.navigateTo({
|
||
url: '/pages/me/login'
|
||
});
|
||
return;
|
||
}
|
||
// 验证表单
|
||
if (!(await validateForm())) {
|
||
return
|
||
}
|
||
|
||
|
||
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: '提交中...'
|
||
})
|
||
|
||
// 处理自定义鸽子费
|
||
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 = {
|
||
...reservationInfo.value,
|
||
// 确保信誉限制同步
|
||
credit_limit: currentValue.value,
|
||
deposit_fee: finalDeposit,
|
||
important_data: {},
|
||
// 添加最晚到店时间参数
|
||
latestDateTime: latestDateTime.value || null
|
||
}
|
||
|
||
// 将玩法类型和具体规则从数字转换为字符串
|
||
submitData.game_type = getGameTypeText(submitData.game_type);
|
||
submitData.game_rule = getGameRuleText(submitData.game_rule);
|
||
var important_data = "";
|
||
if (subscribeMessage.result != null) {
|
||
important_data = JSON.stringify(subscribeMessage.result);
|
||
}
|
||
submitData.important_data = important_data;
|
||
// 检测是否能创建预约 ,如果不能创建则提示并中断
|
||
const canCreateRes = await canCreateSQReservation(submitData)
|
||
if (!canCreateRes || canCreateRes.canCreate !== true) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: (canCreateRes && canCreateRes.message) ? canCreateRes.message : '当前条件不可创建预约',
|
||
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;
|
||
}
|
||
|
||
// TODO: 调用后端API提交预约
|
||
// 调用后端API提交预约
|
||
const result = await addSQReservation(submitData)
|
||
uni.hideLoading()
|
||
console.log("result", result);
|
||
if (!result.success) {
|
||
uni.showToast({
|
||
title: result.message,
|
||
icon: 'none'
|
||
})
|
||
return;
|
||
}
|
||
if (submitData.deposit_fee > 0 && resPay != null) {
|
||
var payRes = await requestPayment(resPay);
|
||
if (payRes.success) {
|
||
uni.showToast({
|
||
title: '鸽子费支付成功',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
await cancelReservation(result.data, "鸽子费支付失败,请重新预约!");
|
||
uni.showToast({
|
||
title: '鸽子费支付失败,请重新预约!',
|
||
icon: 'none'
|
||
})
|
||
return;
|
||
}
|
||
console.log("payRes", payRes);
|
||
}
|
||
var share_id = result.data;
|
||
var detailData = await getDetail(share_id);
|
||
if (detailData != null) {
|
||
reservationData.value = detailData;
|
||
tipsShow();
|
||
} else {
|
||
|
||
// 提交成功
|
||
uni.showToast({
|
||
title: '预约提交成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
// 可以跳转到其他页面或重置表单
|
||
// uni.navigateBack() // 返回上一页
|
||
// 或者重置表单
|
||
resetForm()
|
||
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('提交预约失败:', error)
|
||
uni.showToast({
|
||
title: '提交失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 重置表单方法
|
||
const resetForm = () => {
|
||
// 保存房间信息(不重置房间号)
|
||
const savedRoomId = reservationInfo.value.room_id;
|
||
const savedRoomName = reservationInfo.value.room_name;
|
||
const savedMaxPlayerCount = maxPlayerCount.value;
|
||
const savedPeopleRange = [...peopleRange.value];
|
||
const savedPeopleText = peopleText.value;
|
||
|
||
reservationInfo.value = {
|
||
room_id: savedRoomId,
|
||
room_name: savedRoomName,
|
||
start_time: 0,
|
||
end_time: 0,
|
||
max_age: 0,
|
||
min_age: 0,
|
||
title: '',
|
||
extra_info: '',
|
||
game_rule: '',
|
||
game_type: '',
|
||
gender_limit: 0,
|
||
is_smoking: 2,
|
||
credit_limit: 0,
|
||
deposit_fee: 0,
|
||
player_count: 0,
|
||
}
|
||
currentValue.value = 0
|
||
gameRuleRange.value = []
|
||
customDeposit.value = '' // 重置自定义鸽子费
|
||
|
||
// 保留人数范围
|
||
maxPlayerCount.value = savedMaxPlayerCount;
|
||
peopleRange.value = savedPeopleRange;
|
||
peopleText.value = savedPeopleText;
|
||
|
||
// 重置时段选择
|
||
selectedTimeSlot.value = null;
|
||
reservationInfo.value.start_time = 0;
|
||
reservationInfo.value.end_time = 0;
|
||
// 重置最晚到店时间
|
||
latestDateTime.value = null;
|
||
|
||
gameRuleText.value = "请先选择玩法类型"
|
||
}
|
||
|
||
/**
|
||
* 加载房间详情,用于预约页面
|
||
*/
|
||
const loadRoomDetailForReservation = async (roomId, date) => {
|
||
console.log('loadRoomDetailForReservation 调用参数:', { roomId, date, dateType: typeof date });
|
||
|
||
// 验证参数
|
||
if (!roomId || !date) {
|
||
console.error('loadRoomDetailForReservation 参数无效:', { roomId, date });
|
||
uni.showToast({
|
||
title: '参数错误',
|
||
icon: 'none'
|
||
});
|
||
roomDetailLoading.value = false;
|
||
return;
|
||
}
|
||
|
||
// 确保 date 是数字类型
|
||
const dateTimestamp = Number(date);
|
||
if (isNaN(dateTimestamp) || dateTimestamp <= 0) {
|
||
console.error('日期时间戳无效:', date, '转换后:', dateTimestamp);
|
||
uni.showToast({
|
||
title: '日期格式错误',
|
||
icon: 'none'
|
||
});
|
||
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;
|
||
const t = [];
|
||
peopleText.value = "请选择游玩人数";
|
||
for (let i = 2; i <= detail.capacity; i++) {
|
||
t.push({
|
||
value: i,
|
||
text: i + '人'
|
||
});
|
||
}
|
||
peopleRange.value = t;
|
||
} else {
|
||
peopleText.value = "请选择游玩人数";
|
||
}
|
||
|
||
// 从 time_slots 中过滤出可预约的时段,动态生成时间段选项
|
||
generateTimeSlotOptions(detail.time_slots);
|
||
} else {
|
||
uni.showToast({
|
||
title: '获取房间详情失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('加载房间详情失败', error);
|
||
uni.showToast({
|
||
title: '加载房间详情失败',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
roomDetailLoading.value = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据房间的 time_slots 动态生成时间段选项
|
||
* 只显示可预约的时段(status === 'available')
|
||
*/
|
||
const generateTimeSlotOptions = (timeSlots) => {
|
||
if (!timeSlots || !Array.isArray(timeSlots)) {
|
||
timeSlotOptions.value = [];
|
||
return;
|
||
}
|
||
|
||
// 时段时间范围映射
|
||
const timeRangeMap = {
|
||
0: { text: '凌晨', range: '00:00-05:59' },
|
||
1: { text: '上午', range: '06:00-11:59' },
|
||
2: { text: '下午', range: '12:00-17:59' },
|
||
3: { text: '晚上', range: '18:00-23:59' }
|
||
};
|
||
|
||
// 过滤出可预约的时段并生成选项
|
||
const availableSlots = timeSlots.filter(slot => slot.status === 'available');
|
||
|
||
timeSlotOptions.value = availableSlots.map(slot => {
|
||
const slotInfo = timeRangeMap[slot.slot_type] || { text: slot.slot_name, range: '' };
|
||
// 生成显示文本
|
||
let displayText = slotInfo.text;
|
||
if (slotInfo.range) {
|
||
displayText += ` (${slotInfo.range})`;
|
||
}
|
||
// 如果有价格信息,可以追加显示
|
||
if (slot.price_desc_standard) {
|
||
displayText += ` ${slot.price_desc_standard}`;
|
||
}
|
||
|
||
return {
|
||
value: slot.slot_type,
|
||
text: displayText
|
||
};
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 时段选择改变事件
|
||
*/
|
||
const onTimeSlotChange = (val) => {
|
||
if (!selectedDate.value) return;
|
||
selectedTimeSlot.value = val;
|
||
// 根据日期和时段计算开始时间和结束时间
|
||
calculateTimeFromSlot();
|
||
// 自动更新最晚到店时间(时间段开始时间+30分钟)
|
||
updateLatestDateTimeFromSlot();
|
||
}
|
||
|
||
/**
|
||
* 根据选择的日期和时段计算开始时间和结束时间
|
||
*/
|
||
const calculateTimeFromSlot = () => {
|
||
if (!selectedDate.value || selectedTimeSlot.value === null || selectedTimeSlot.value === undefined) return;
|
||
|
||
// 将日期时间戳转换为日期对象
|
||
const date = new Date(selectedDate.value * 1000);
|
||
const year = date.getFullYear();
|
||
const month = date.getMonth();
|
||
const day = date.getDate();
|
||
|
||
// 根据时段类型设置开始时间
|
||
let startHour = 0;
|
||
let endHour = 0;
|
||
|
||
switch (selectedTimeSlot.value) {
|
||
case 0: // 凌晨 00:00-05:59
|
||
startHour = 0;
|
||
endHour = 6;
|
||
break;
|
||
case 1: // 上午 06:00-11:59
|
||
startHour = 6;
|
||
endHour = 12;
|
||
break;
|
||
case 2: // 下午 12:00-17:59
|
||
startHour = 12;
|
||
endHour = 18;
|
||
break;
|
||
case 3: // 晚上 18:00-23:59
|
||
startHour = 18;
|
||
endHour = 24;
|
||
break;
|
||
}
|
||
|
||
// 创建开始时间和结束时间
|
||
const startTime = new Date(year, month, day, startHour, 0, 0);
|
||
const endTime = new Date(year, month, day, endHour, 0, 0);
|
||
|
||
// 转换为秒级时间戳
|
||
reservationInfo.value.start_time = Math.floor(startTime.getTime() / 1000);
|
||
reservationInfo.value.end_time = Math.floor(endTime.getTime() / 1000);
|
||
}
|
||
|
||
onLoad(async (options) => {
|
||
const config = await getConfigData();
|
||
console.log('config', config);
|
||
if (config != null && config.config != null) {
|
||
gameTypeRange.value = [...config.config.playingMethodOptions];
|
||
}
|
||
|
||
// 必须从房间页跳转,需要传入房间信息
|
||
if (!options || !options.roomId) {
|
||
uni.showToast({
|
||
title: '参数错误,请从房间页进入',
|
||
icon: 'none'
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
return;
|
||
}
|
||
|
||
// 接收房间信息
|
||
const roomId = Number(options.roomId);
|
||
const roomName = decodeURIComponent(options.roomName || '未知房间');
|
||
const date = Number(options.date);
|
||
|
||
if (!roomId || !date) {
|
||
uni.showToast({
|
||
title: '房间信息不完整',
|
||
icon: 'none'
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
return;
|
||
}
|
||
|
||
// 自动填充房间信息
|
||
reservationInfo.value.room_id = roomId;
|
||
reservationInfo.value.room_name = roomName;
|
||
selectedDate.value = date;
|
||
|
||
// 加载房间详情,获取可预约时段和容量信息
|
||
await loadRoomDetailForReservation(roomId, date);
|
||
})
|
||
onShow(async () => {
|
||
// resetForm();
|
||
})
|
||
// 年龄列构建与显示/文案
|
||
const buildAgeColumns = () => {
|
||
const minList = [{
|
||
value: 0,
|
||
text: '不限'
|
||
}]
|
||
for (let i = 18; i <= 80; i++) minList.push({
|
||
value: i,
|
||
text: String(i)
|
||
})
|
||
const maxList = [{
|
||
value: 0,
|
||
text: '不限'
|
||
}]
|
||
for (let i = 18; i <= 80; i++) maxList.push({
|
||
value: i,
|
||
text: String(i)
|
||
})
|
||
agePickerColumns.value = [minList, maxList]
|
||
}
|
||
const getAgeRangeText = () => {
|
||
const {
|
||
min_age,
|
||
max_age
|
||
} = reservationInfo.value
|
||
if (min_age == 0 && max_age == 0) {
|
||
return '不限'
|
||
}
|
||
const minText = min_age === 0 ? '不限' : min_age + '岁'
|
||
const maxText = max_age === 0 ? '不限' : max_age + '岁'
|
||
return minText + ' - ' + maxText
|
||
}
|
||
const openAgePicker = () => {
|
||
buildAgeColumns()
|
||
// 计算默认索引
|
||
const {
|
||
min_age,
|
||
max_age
|
||
} = reservationInfo.value
|
||
const [mins, maxs] = agePickerColumns.value
|
||
const findIndexByValue = (arr, val) => {
|
||
const idx = arr.findIndex(it => Number(it.value) === Number(val))
|
||
return idx >= 0 ? idx : 0
|
||
}
|
||
agePickerDefaultIndex.value = [
|
||
findIndexByValue(mins, min_age ?? 0),
|
||
findIndexByValue(maxs, max_age ?? 0),
|
||
]
|
||
agePickerVisible.value = true
|
||
}
|
||
const onAgePickerConfirm = (e) => {
|
||
// uview-plus up-picker confirm 回调 e.value 为两列所选对象数组
|
||
const selected = e && e.value ? e.value : []
|
||
const min = selected[0] ? Number(selected[0].value) : 0
|
||
const max = selected[1] ? Number(selected[1].value) : 0
|
||
// 若最小值大于最大值,自动交换或归零“不限”优先
|
||
let minAge = min
|
||
let maxAge = max
|
||
if (minAge !== 0 && maxAge !== 0 && minAge > maxAge) {
|
||
[minAge, maxAge] = [maxAge, minAge]
|
||
}
|
||
reservationInfo.value.min_age = minAge
|
||
reservationInfo.value.max_age = maxAge
|
||
agePickerVisible.value = false
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.content {
|
||
width: 100%;
|
||
height: 100vh;
|
||
background-color: #F7F7F7;
|
||
}
|
||
|
||
/* 标题行布局 */
|
||
.header-row {
|
||
width: 90%;
|
||
margin: 100rpx auto 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
/* 返回图标 */
|
||
.back-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
/* 页面标题 */
|
||
.page-title {
|
||
font-size: 30rpx;
|
||
text-align: center;
|
||
flex: 1;
|
||
}
|
||
|
||
/* 占位元素,保持标题居中 */
|
||
.spacer-40 {
|
||
width: 40rpx;
|
||
}
|
||
|
||
/* 输入外层统一样式 */
|
||
.input-wrapper {
|
||
border: 1px solid #515151;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* 可点击的行(年龄范围显示) */
|
||
.clickable-row {
|
||
font-size: 28rpx;
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
height: 60rpx;
|
||
}
|
||
|
||
/* 内联标签(信誉左侧“⼤于等于”) */
|
||
.inline-label {
|
||
font-size: 25.86rpx;
|
||
width: 120rpx;
|
||
}
|
||
|
||
/* 通用居中容器 */
|
||
.center {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 提交按钮容器样式 */
|
||
.submit-button {
|
||
width: 90%;
|
||
border-radius: 10rpx;
|
||
margin: 30rpx auto 0;
|
||
background-color: #00AC4E;
|
||
}
|
||
|
||
/* 操作区一行布局:重置 + 发起 */
|
||
.action-row {
|
||
width: 90%;
|
||
margin: 30rpx auto 0;
|
||
display: flex;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.reset-button {
|
||
flex: 0 0 30%;
|
||
height: 88rpx;
|
||
border-radius: 10rpx;
|
||
background-color: #FFFFFF;
|
||
color: #333;
|
||
}
|
||
|
||
.action-submit {
|
||
flex: 1 1 auto;
|
||
height: 88rpx;
|
||
margin: 0;
|
||
/* 覆盖原有 submit-button 的外边距,使其与 reset 同行 */
|
||
}
|
||
|
||
/* 备注容器及文本 */
|
||
.note-container {
|
||
width: 90%;
|
||
margin: 20rpx auto 20rpx;
|
||
}
|
||
|
||
.muted-text {
|
||
color: #979797;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.note-text {
|
||
font-size: 24rpx;
|
||
margin-left: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.counter-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.counter-btn {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
padding: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
.minus-btn {
|
||
background-color: #f5f5f5;
|
||
border: 1px solid #eee;
|
||
}
|
||
|
||
|
||
.counter-value {
|
||
width: 90rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
|
||
.custom-select ::v-deep .uni-input-input {
|
||
/* 正常状态下的边框颜色 */
|
||
border: 1px solid #007aff !important;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.flex-center-row {
|
||
display: flex;
|
||
}
|
||
</style> |