571 lines
16 KiB
Vue
571 lines
16 KiB
Vue
<template>
|
||
<com-page-container-base ref="_containerBase">
|
||
<view class="content column">
|
||
|
||
<text class="page-title">发起预约</text>
|
||
|
||
<card-container marginTop="30rpx">
|
||
<label-field label="预定时长">
|
||
<tag-select :options="timeRange" v-model="timeRangeValue" @change="onTimeRangeChange" />
|
||
</label-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<time-select-cell label="开始时间" icon="@@:app/static/time_start.png" @select="openUpDatesTimePicker">
|
||
{{ getDayDescription(startTimeStr) }}
|
||
</time-select-cell>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<time-select-cell label="结束时间" icon="@@:app/static/time_end.png" @select="openUpDatesTimePickerEnd">
|
||
{{ getDayDescription(endTimeStr) }}
|
||
</time-select-cell>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<time-select-cell label="房间" @select="openRoomPicker">
|
||
{{ getRoomPickerName() }}
|
||
</time-select-cell>
|
||
</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="是否禁烟">
|
||
<com-appointment-radio-select :options="smokingOptions" v-model="reservationInfo.is_smoking" />
|
||
</label-slect-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-slect-field label="性别">
|
||
<com-appointment-radio-select :options="genderOptions" v-model="reservationInfo.gender_limit" />
|
||
</label-slect-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-slect-field label="年龄范围">
|
||
<view @click="openAgePicker" class="clickable-row">
|
||
{{ getAgeRangeText() }}
|
||
</view>
|
||
</label-slect-field>
|
||
<view :style="{ height: lineHeight }"></view>
|
||
<label-slect-field label="信誉">
|
||
<view class="flex-center-row">
|
||
<text class="inline-label">大于等于</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 :style="{ height: lineHeight }"></view>
|
||
<text class="note-text">鸽子费(保证金),参与人需缴纳鸽子费。若有参与者在预约后没有赴约,其鸽子费由在场的所有人平分。组局成功或失败后鸽子费将全额返还。</text>
|
||
</card-container>
|
||
|
||
<view class="center submit-button">
|
||
<text style="margin: 20rpx; color: white;">发起预约</text>
|
||
</view>
|
||
|
||
<view class="center note-container">
|
||
<text class="muted-text">组局成功后,发起者可通过店员领取线下红包</text>
|
||
</view>
|
||
|
||
|
||
</view>
|
||
<com-appointment-picker-data ref="roomPickerRef" />
|
||
<up-picker title="年龄范围选择" :show="agePickerVisible" :columns="agePickerColumns" :keyName="'text'"
|
||
:defaultIndex="agePickerDefaultIndex" @confirm="onAgePickerConfirm" @cancel="() => agePickerVisible = false"
|
||
@close="() => agePickerVisible = false"></up-picker>
|
||
</com-page-container-base>
|
||
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
watch
|
||
} from 'vue';
|
||
import {
|
||
getConfigData
|
||
} from '@/common/server/config'
|
||
import {
|
||
getDayDescription,
|
||
ceilMinuteToNext5
|
||
} from '@/common/system/timeUtile';
|
||
import {
|
||
forEach,
|
||
union
|
||
} from 'lodash';
|
||
import TimeSelectCell from '@/components/com/appointment/time-select-cell.vue'
|
||
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 TagSelect from '@/components/com/appointment/tag-select.vue'
|
||
import ComAppointmentPickerData from '@/components/com/appointment/picker-data.vue'
|
||
import ComAppointmentRadioSelect from '@/components/com/appointment/radio-select.vue'
|
||
import {
|
||
getReservationRoomList
|
||
} from '@/common/server/interface/sq'
|
||
const _containerBase = ref(null)
|
||
// 年龄选择器状态
|
||
const agePickerVisible = ref(false)
|
||
const agePickerColumns = ref([[], []])
|
||
const agePickerDefaultIndex = ref([0, 0])
|
||
const startTimeStr = ref(0)
|
||
const endTimeStr = ref(0)
|
||
|
||
const lineHeight = ref("15rpx")
|
||
const timeRange = ref(["2小时", "3小时", "4小时", "自定义"])
|
||
const timeRangeValue = ref("")
|
||
// 房间选项
|
||
const roomOptions = ref([])
|
||
const roomPickerRef = ref(null)
|
||
//提交表单数据
|
||
const reservationInfo = ref({
|
||
room_id: 0, //房间id
|
||
room_name: '请选择房间', //房间名称
|
||
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, //人数
|
||
});
|
||
//自动计算结束时间
|
||
const onTimeRangeChange = async (val) => {
|
||
timeRangeValue.value = val;
|
||
console.log('timeRange change:', val)
|
||
if (val != "") {
|
||
await openUpDatesTimePicker(false);
|
||
if (startTimeStr.value > 0) {
|
||
var str = val;
|
||
if (str == "2小时") {
|
||
endTimeStr.value = startTimeStr.value + 1000 * 60 * 60 * 2;
|
||
} else if (str == "3小时") {
|
||
endTimeStr.value = startTimeStr.value + 1000 * 60 * 60 * 3;
|
||
} else if (str == "4小时") {
|
||
endTimeStr.value = startTimeStr.value + 1000 * 60 * 60 * 4;
|
||
} else {
|
||
await openUpDatesTimePickerEnd(false);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
const getRoomPickerName = () => {
|
||
return reservationInfo.value.room_name;
|
||
}
|
||
|
||
const openUpDatesTimePicker = async (isManual = true) => {
|
||
var now = startTimeStr.value;
|
||
var min = Date.now();
|
||
min += 1000 * 60 * 30;
|
||
min = ceilMinuteToNext5(min).valueOf();
|
||
if (startTimeStr.value == 0) {
|
||
now = Date.now();
|
||
now += 1000 * 60 * 30;
|
||
now = ceilMinuteToNext5(now).valueOf();
|
||
}
|
||
const startTime = await _containerBase.value.openUpDatesTimePicker(now, min, "预约开始时间")
|
||
startTimeStr.value = startTime
|
||
// 同步到表单对象,毫秒转秒
|
||
reservationInfo.value.start_time = Math.floor(startTimeStr.value / 1000)
|
||
// 手动选择时间则切换为“自定义”
|
||
if (isManual) {
|
||
timeRangeValue.value = "自定义"
|
||
}
|
||
}
|
||
const openUpDatesTimePickerEnd = async (isManual = true) => {
|
||
if (startTimeStr.value == 0) {
|
||
uni.showToast({
|
||
title: '请先选择开始时间',
|
||
icon: 'none'
|
||
})
|
||
return;
|
||
}
|
||
var now = endTimeStr.value;
|
||
var min = (startTimeStr.value + 1000 * 60 * 30);
|
||
if (now == 0) {
|
||
now = (startTimeStr.value + 1000 * 60 * 30);
|
||
}
|
||
//minDate+1000*60*30 最小间隔30分钟
|
||
const endTime = await _containerBase.value.openUpDatesTimePicker(now, min, "预约结束时间")
|
||
endTimeStr.value = endTime
|
||
// 同步到表单对象,毫秒转秒
|
||
reservationInfo.value.end_time = Math.floor(endTimeStr.value / 1000)
|
||
// 手动选择时间则切换为“自定义”
|
||
if (isManual) {
|
||
timeRangeValue.value = "自定义"
|
||
}
|
||
}
|
||
const maxPlayerCount = ref(0)
|
||
const openRoomPicker = async () => {
|
||
// 需先选择有效的开始/结束时间
|
||
if (!reservationInfo.value.start_time || !reservationInfo.value.end_time) {
|
||
uni.showToast({
|
||
title: '请先选择开始和结束时间',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if (reservationInfo.value.end_time <= reservationInfo.value.start_time) {
|
||
uni.showToast({
|
||
title: '结束时间需晚于开始时间',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
// 无数据则尝试拉取
|
||
if (!roomOptions.value || roomOptions.value.length === 0) {
|
||
await tryLoadRooms()
|
||
}
|
||
if (!roomOptions.value || roomOptions.value.length === 0) {
|
||
uni.showToast({
|
||
title: '暂无可预约房间',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const selected = await roomPickerRef.value.show({
|
||
options: roomOptions.value,
|
||
valueKey: 'id',
|
||
labelKey: 'name',
|
||
modelValue: reservationInfo.value.room_id
|
||
})
|
||
if (selected) {
|
||
reservationInfo.value.room_id = selected.id
|
||
reservationInfo.value.room_name = selected.name
|
||
maxPlayerCount.value = selected.capacity
|
||
|
||
}
|
||
}
|
||
|
||
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 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元' },
|
||
])
|
||
|
||
const currentValue = ref(0)
|
||
|
||
const increment = () => {
|
||
if (currentValue.value < 5) {
|
||
currentValue.value += 0.5
|
||
if (currentValue.value > 5) {
|
||
currentValue.value = 5
|
||
}
|
||
}
|
||
}
|
||
|
||
const decrement = () => {
|
||
if (currentValue.value > 0) {
|
||
currentValue.value -= 0.5
|
||
if (currentValue.value < 0) {
|
||
currentValue.value = 0
|
||
}
|
||
}
|
||
}
|
||
|
||
const changeLog = (e) => {
|
||
console.log('change事件:', e)
|
||
|
||
}
|
||
|
||
// 当开始或结束时间变化(通过其它方式)时也自动刷新房间
|
||
watch([startTimeStr, endTimeStr], async ([s, e]) => {
|
||
reservationInfo.value.start_time = s ? Math.floor(s / 1000) : 0
|
||
reservationInfo.value.end_time = e ? Math.floor(e / 1000) : 0
|
||
await tryLoadRooms()
|
||
})
|
||
|
||
watch(() => reservationInfo.value.room_id, async (newId, oldId) => {
|
||
console.log('room_id changed:', oldId, '->', newId);
|
||
if (newId == 0) {
|
||
reservationInfo.value.player_count = 0;
|
||
peopleRange.value = [];
|
||
peopleText.value = "请先选择房间";
|
||
} else {
|
||
var t = [];
|
||
peopleText.value = "请选择游玩人数";
|
||
for (let i = 2; i <= maxPlayerCount.value; i++) {
|
||
t.push({
|
||
value: i,
|
||
text: i + '人'
|
||
});
|
||
}
|
||
console.log("peopleRange.value ", maxPlayerCount.value, t);
|
||
if (reservationInfo.value.player_count > maxPlayerCount.value) {
|
||
reservationInfo.value.player_count = 0;
|
||
}
|
||
peopleRange.value = t;
|
||
}
|
||
});
|
||
|
||
// 拉取可预约房间
|
||
const tryLoadRooms = async () => {
|
||
if (!reservationInfo.value.start_time || !reservationInfo.value.end_time) return
|
||
if (reservationInfo.value.end_time <= reservationInfo.value.start_time) return
|
||
const list = await getReservationRoomList(reservationInfo.value.start_time, reservationInfo.value.end_time)
|
||
// list.push()
|
||
if (Array.isArray(list)) {
|
||
roomOptions.value = Array.isArray(list) ? list : []
|
||
} else {
|
||
roomOptions.value = []
|
||
}
|
||
// 若当前选中房间不在可选列表中,则清空
|
||
console.log("房间id==>", reservationInfo.value.room_id, "房间列表==>", roomOptions.value);
|
||
|
||
if (!roomOptions.value.some(it => it.id === reservationInfo.value.room_id)) {
|
||
reservationInfo.value.room_id = 0;
|
||
reservationInfo.value.room_name = '请选择房间';
|
||
}
|
||
}
|
||
onLoad(async () => {
|
||
const config = await getConfigData();
|
||
console.log('config', config);
|
||
if (config != null && config.config != null) {
|
||
gameTypeRange.value = [...config.config.playingMethodOptions];
|
||
}
|
||
})
|
||
// 年龄列构建与显示/文案
|
||
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;
|
||
overflow-y: auto;
|
||
background-color: #F7F7F7;
|
||
}
|
||
|
||
/* 页面标题 */
|
||
.page-title {
|
||
margin-top: 100rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 输入外层统一样式 */
|
||
.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;
|
||
}
|
||
|
||
/* 备注容器及文本 */
|
||
.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> |