3 changed files with 679 additions and 140 deletions
-
454subpkg/pages/seat-booking/seat-booking-1.vue
-
0subpkg/pages/seat-booking/seat-booking-chang.vue
-
365subpkg/pages/seat-booking/seat-booking.vue
@ -0,0 +1,454 @@ |
|||||
|
<template> |
||||
|
<view class="booking-detail"> |
||||
|
<view class="section"> |
||||
|
<view class="section-title">日期选择</view> |
||||
|
<view class="calendar"> |
||||
|
<view class="calendar-header"> |
||||
|
<text class="month">{{ currentYear }}年{{ currentMonth }}月</text> |
||||
|
</view> |
||||
|
<view class="calendar-weekdays"> |
||||
|
<text v-for="day in weekdays" :key="day" class="weekday">{{ day }}</text> |
||||
|
</view> |
||||
|
<view class="calendar-days"> |
||||
|
<view |
||||
|
v-for="(day, index) in calendarDays" |
||||
|
:key="index" |
||||
|
class="day-item" |
||||
|
:class="{ |
||||
|
'other-month': !day.currentMonth, |
||||
|
'selected': day.date === selectedDate, |
||||
|
'disabled': !day.available, |
||||
|
'today': day.isToday |
||||
|
}" |
||||
|
@click="selectDate(day)" |
||||
|
> |
||||
|
<text class="day-num">{{ day.day }}</text> |
||||
|
<text v-if="day.available && !day.isToday" class="available-tag">可预约</text> |
||||
|
<text v-if="day.isToday" class="today-tag">今天</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="section"> |
||||
|
<view class="section-title">时间选择</view> |
||||
|
<view class="time-slots"> |
||||
|
<view |
||||
|
v-for="slot in timeSlots" |
||||
|
:key="slot.id" |
||||
|
class="time-slot" |
||||
|
:class="{ 'selected': selectedTime === slot.time }" |
||||
|
@click="selectTime(slot)" |
||||
|
> |
||||
|
<text class="time-text">{{ slot.time }}</text> |
||||
|
<text v-if="slot.available" class="available-badge">可选</text> |
||||
|
<text v-else class="unavailable-badge">已满</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="bottom-bar"> |
||||
|
<button class="btn-primary" @click="confirmBooking">确认预约</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
currentYear: new Date().getFullYear(), |
||||
|
currentMonth: new Date().getMonth() + 1, |
||||
|
selectedDate: '', |
||||
|
selectedTime: '', |
||||
|
weekdays: ['日', '一', '二', '三', '四', '五', '六'], |
||||
|
calendarDays: [], |
||||
|
timeSlots: [], |
||||
|
// 后台预约配置 |
||||
|
reserveConfig: null |
||||
|
}; |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
// 模拟后台返回数据,实际替换成接口请求 |
||||
|
this.reserveConfig = { |
||||
|
"frequency": "30", |
||||
|
"weekDays": [ |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"name": "周一", |
||||
|
"checked": true, |
||||
|
"times": [ |
||||
|
"00:00 - 23:59" |
||||
|
], |
||||
|
"showPicker": false, |
||||
|
"newTime": "" |
||||
|
}, |
||||
|
{ |
||||
|
"id": 2, |
||||
|
"name": "周二", |
||||
|
"checked": false, |
||||
|
"times": [], |
||||
|
"showPicker": false, |
||||
|
"newTime": "" |
||||
|
}, |
||||
|
{ |
||||
|
"id": 3, |
||||
|
"name": "周三", |
||||
|
"checked": false, |
||||
|
"times": [], |
||||
|
"showPicker": false, |
||||
|
"newTime": "" |
||||
|
}, |
||||
|
{ |
||||
|
"id": 4, |
||||
|
"name": "周四", |
||||
|
"checked": false, |
||||
|
"times": [], |
||||
|
"showPicker": false, |
||||
|
"newTime": "" |
||||
|
}, |
||||
|
{ |
||||
|
"id": 5, |
||||
|
"name": "周五", |
||||
|
"checked": false, |
||||
|
"times": [], |
||||
|
"showPicker": false, |
||||
|
"newTime": "" |
||||
|
}, |
||||
|
{ |
||||
|
"id": 6, |
||||
|
"name": "周六", |
||||
|
"checked": false, |
||||
|
"times": [], |
||||
|
"showPicker": false, |
||||
|
"newTime": "" |
||||
|
}, |
||||
|
{ |
||||
|
"id": 7, |
||||
|
"name": "周日", |
||||
|
"checked": false, |
||||
|
"showPicker": false, |
||||
|
"newTime": "" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
this.generateCalendar(); |
||||
|
}, |
||||
|
methods: { |
||||
|
// 根据日期字符串获取星期 1~7 (周一=1,周日=7) |
||||
|
getWeekNum(dateStr) { |
||||
|
const d = new Date(dateStr); |
||||
|
let day = d.getDay(); // 0周日,1周一...6周六 |
||||
|
if(day === 0) return 7 |
||||
|
return day |
||||
|
}, |
||||
|
|
||||
|
generateCalendar() { |
||||
|
const year = this.currentYear; |
||||
|
const month = this.currentMonth; |
||||
|
const firstDay = new Date(year, month - 1, 1); |
||||
|
const lastDay = new Date(year, month, 0); |
||||
|
const daysInMonth = lastDay.getDate(); |
||||
|
const startWeekday = firstDay.getDay(); |
||||
|
const today = new Date(); |
||||
|
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; |
||||
|
|
||||
|
// 1. 根据 frequency 生成允许的日期范围 |
||||
|
let allowDateList = []; |
||||
|
const freq = Number(this.reserveConfig.frequency); |
||||
|
for(let i = 0; i < freq; i++){ |
||||
|
const temp = new Date(today); |
||||
|
temp.setDate(today.getDate() + i); |
||||
|
const ds = `${temp.getFullYear()}-${String(temp.getMonth()+1).padStart(2,'0')}-${String(temp.getDate()).padStart(2,'0')}` |
||||
|
allowDateList.push(ds) |
||||
|
} |
||||
|
|
||||
|
const days = []; |
||||
|
const prevMonthLastDay = new Date(year, month - 1, 0).getDate(); |
||||
|
for (let i = startWeekday - 1; i >= 0; i--) { |
||||
|
days.push({ |
||||
|
day: prevMonthLastDay - i, |
||||
|
date: '', |
||||
|
currentMonth: false, |
||||
|
available: false, |
||||
|
isToday: false |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 生成当月每一天 |
||||
|
for (let i = 1; i <= daysInMonth; i++) { |
||||
|
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}`; |
||||
|
const isToday = dateStr === todayStr; |
||||
|
|
||||
|
// 是否在frequency时间范围内 |
||||
|
const inFreqRange = allowDateList.includes(dateStr); |
||||
|
|
||||
|
let available = false; |
||||
|
if(inFreqRange){ |
||||
|
// 获取当前是周几 1~7 |
||||
|
const weekNo = this.getWeekNum(dateStr); |
||||
|
const weekCfg = this.reserveConfig.weekDays.find(w => w.id === weekNo) |
||||
|
if(weekCfg && weekCfg.checked){ |
||||
|
available = true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
days.push({ |
||||
|
day: i, |
||||
|
date: dateStr, |
||||
|
currentMonth: true, |
||||
|
available: available, |
||||
|
isToday: isToday |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const remainingDays = 42 - days.length; |
||||
|
for (let i = 1; i <= remainingDays; i++) { |
||||
|
days.push({ |
||||
|
day: i, |
||||
|
date: '', |
||||
|
currentMonth: false, |
||||
|
available: false, |
||||
|
isToday: false |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
this.calendarDays = days; |
||||
|
|
||||
|
// 默认选中第一个可用日期 |
||||
|
const firstAvail = this.calendarDays.find(d => d.available); |
||||
|
if(firstAvail){ |
||||
|
this.selectedDate = firstAvail.date |
||||
|
this.refreshTimeSlots(this.selectedDate) |
||||
|
}else{ |
||||
|
this.selectedDate = '' |
||||
|
this.timeSlots = [] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 根据选中日期刷新时间段 |
||||
|
refreshTimeSlots(dateStr){ |
||||
|
this.selectedTime = '' |
||||
|
if(!dateStr) { |
||||
|
this.timeSlots = [] |
||||
|
return |
||||
|
} |
||||
|
const weekNo = this.getWeekNum(dateStr) |
||||
|
const weekCfg = this.reserveConfig.weekDays.find(w => w.id === weekNo) |
||||
|
if(!weekCfg || !weekCfg.checked){ |
||||
|
this.timeSlots = [] |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// times数组转为时间槽 |
||||
|
this.timeSlots = weekCfg.times.map((t, idx)=>{ |
||||
|
return { |
||||
|
id: idx + 1, |
||||
|
time: t, |
||||
|
available: true |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
selectDate(day) { |
||||
|
if (!day.available || !day.currentMonth) return; |
||||
|
this.selectedDate = day.date; |
||||
|
// 切换日期,重新加载时间段 |
||||
|
this.refreshTimeSlots(day.date) |
||||
|
}, |
||||
|
selectTime(slot) { |
||||
|
if (!slot.available) return; |
||||
|
this.selectedTime = slot.time; |
||||
|
}, |
||||
|
confirmBooking() { |
||||
|
if (!this.selectedDate) { |
||||
|
uni.showToast({ title: '请选择日期', icon: 'none' }); |
||||
|
return; |
||||
|
} |
||||
|
if (!this.selectedTime) { |
||||
|
uni.showToast({ title: '请选择时间段', icon: 'none' }); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uni.showLoading({ title: '预约中...' }); |
||||
|
setTimeout(() => { |
||||
|
uni.hideLoading(); |
||||
|
uni.showModal({ |
||||
|
title: '预约成功', |
||||
|
content: `您已成功预约\n日期:${this.selectedDate}\n时间:${this.selectedTime}`, |
||||
|
showCancel: false, |
||||
|
confirmText: '确定', |
||||
|
success: () => { |
||||
|
uni.navigateBack({ delta: 2 }); |
||||
|
} |
||||
|
}); |
||||
|
}, 1500); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.booking-detail { |
||||
|
min-height: 100vh; |
||||
|
background-color: #f5f5f5; |
||||
|
padding-bottom: 70px; |
||||
|
} |
||||
|
|
||||
|
.section { |
||||
|
background-color: #fff; |
||||
|
margin: 10px; |
||||
|
border-radius: 12px; |
||||
|
padding: 16px; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
/* 日历 */ |
||||
|
.calendar-header { |
||||
|
text-align: center; |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
.month { |
||||
|
font-size: 16px; |
||||
|
color: #333; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.calendar-weekdays { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
.weekday { |
||||
|
width: 14.28%; |
||||
|
text-align: center; |
||||
|
font-size: 13px; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.calendar-days { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
.day-item { |
||||
|
width: 14.28%; |
||||
|
aspect-ratio: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-bottom: 6px; |
||||
|
border-radius: 6px; |
||||
|
position: relative; |
||||
|
} |
||||
|
.day-item.other-month .day-num { |
||||
|
color: #ccc; |
||||
|
} |
||||
|
.day-item.disabled .day-num { |
||||
|
color: #ccc; |
||||
|
} |
||||
|
.day-item.selected { |
||||
|
background-color: #01a4fe; |
||||
|
} |
||||
|
.day-item.selected .day-num { |
||||
|
color: #fff; |
||||
|
font-size: 15px; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
.day-item.selected .available-tag, |
||||
|
.day-item.selected .today-tag { |
||||
|
color: rgba(255, 255, 255, 0.9); |
||||
|
font-size: 10px; |
||||
|
} |
||||
|
.day-item.today:not(.selected) { |
||||
|
background-color: #fff3cd; |
||||
|
} |
||||
|
.day-item.today:not(.selected) .day-num { |
||||
|
color: #856404; |
||||
|
} |
||||
|
|
||||
|
.day-num { |
||||
|
font-size: 14px; |
||||
|
color: #333; |
||||
|
} |
||||
|
.available-tag { |
||||
|
font-size: 10px; |
||||
|
color: #01a4fe; |
||||
|
margin-top: 2px; |
||||
|
} |
||||
|
.today-tag { |
||||
|
font-size: 10px; |
||||
|
color: #856404; |
||||
|
margin-top: 2px; |
||||
|
} |
||||
|
|
||||
|
/* 时间选择 */ |
||||
|
.time-slots { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
.time-slot { |
||||
|
width: calc(50% - 5px); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 12px; |
||||
|
background-color: #f8f8f8; |
||||
|
border-radius: 8px; |
||||
|
border: 1px solid transparent; |
||||
|
} |
||||
|
.time-slot.selected { |
||||
|
background-color: #e8f4fd; |
||||
|
border-color: #01a4fe; |
||||
|
} |
||||
|
.time-text { |
||||
|
font-size: 14px; |
||||
|
color: #333; |
||||
|
} |
||||
|
.available-badge { |
||||
|
font-size: 12px; |
||||
|
color: #01a4fe; |
||||
|
background-color: #e8f4fd; |
||||
|
padding: 2px 8px; |
||||
|
border-radius: 10px; |
||||
|
} |
||||
|
.unavailable-badge { |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
background-color: #f0f0f0; |
||||
|
padding: 2px 8px; |
||||
|
border-radius: 10px; |
||||
|
} |
||||
|
|
||||
|
/* 底部按钮 */ |
||||
|
.bottom-bar { |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
padding: 10px; |
||||
|
background-color: #fff; |
||||
|
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
.btn-primary { |
||||
|
width: 100%; |
||||
|
height: 44px; |
||||
|
line-height: 44px; |
||||
|
background-color: #01a4fe; |
||||
|
color: #fff; |
||||
|
border-radius: 22px; |
||||
|
font-size: 15px; |
||||
|
border: none; |
||||
|
} |
||||
|
.btn-primary::after { |
||||
|
border: none; |
||||
|
} |
||||
|
</style> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue