|
|
<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>
|