图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

336 lines
8.2 KiB

<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: [
{ id: 1, time: '09:15-12:45', available: true },
{ id: 2, time: '13:00-16:30', available: false },
{ id: 3, time: '16:45-20:15', available: true }
]
};
},
onLoad(options) {
this.generateCalendar();
},
methods: {
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')}`;
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
});
}
const todayDate = new Date();
const availableDates = [];
for (let i = 0; i < 7; i++) {
const date = new Date(todayDate);
date.setDate(todayDate.getDate() + i);
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
availableDates.push(dateStr);
}
for (let i = 1; i <= daysInMonth; i++) {
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const isToday = dateStr === todayStr;
days.push({
day: i,
date: dateStr,
currentMonth: true,
available: isAvailable,
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;
if (availableDates.length > 0) {
this.selectedDate = availableDates[0];
}
},
selectDate(day) {
if (!day.available || !day.currentMonth) return;
this.selectedDate = 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>