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
-
347subpkg/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