336 lines
8.5 KiB
Vue
336 lines
8.5 KiB
Vue
|
|
<template>
|
||
|
|
<view class="appointment-page">
|
||
|
|
<!-- 服务选择 -->
|
||
|
|
<view class="section">
|
||
|
|
<text class="section-title">选择服务</text>
|
||
|
|
<radio-group @change="handleServiceChange">
|
||
|
|
<label v-for="item in services" :key="item.id" class="radio-item">
|
||
|
|
<radio :value="item.name" :checked="selectedService === item.name" />
|
||
|
|
<text>{{ item.name }} ({{ item.duration }}分钟)</text>
|
||
|
|
</label>
|
||
|
|
</radio-group>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- 日期选择 -->
|
||
|
|
<view class="section">
|
||
|
|
<text class="section-title">选择日期</text>
|
||
|
|
<picker
|
||
|
|
mode="date"
|
||
|
|
:value="selectedDate"
|
||
|
|
:start="startDate"
|
||
|
|
:end="endDate"
|
||
|
|
@change="handleDateChange"
|
||
|
|
>
|
||
|
|
<view class="picker">
|
||
|
|
{{ selectedDate }} ({{ selectedDay }})
|
||
|
|
</view>
|
||
|
|
</picker>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- 时间选择 -->
|
||
|
|
<view class="section" v-if="timeSlots.length > 0">
|
||
|
|
<text class="section-title">选择时间</text>
|
||
|
|
<view class="time-slots">
|
||
|
|
<view
|
||
|
|
v-for="(slot, index) in timeSlots"
|
||
|
|
:key="index"
|
||
|
|
class="time-slot"
|
||
|
|
:class="{ selected: selectedTime === slot[0] }"
|
||
|
|
@click="selectTime(slot)"
|
||
|
|
>
|
||
|
|
{{ slot[0] }} - {{ slot[1] }}
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<view v-else class="no-slots">
|
||
|
|
当前没有可预约时段
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- 提交按钮 -->
|
||
|
|
<button
|
||
|
|
type="primary"
|
||
|
|
class="submit-btn"
|
||
|
|
:disabled="!selectedService || !selectedTime"
|
||
|
|
@click="submitAppointment"
|
||
|
|
>
|
||
|
|
确认预约
|
||
|
|
</button>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import { ServiceManager } from '../../utils/service'
|
||
|
|
|
||
|
|
export default {
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
serviceManager: new ServiceManager(),
|
||
|
|
services: [
|
||
|
|
{ id: 1, name: '剪发', duration: 30 },
|
||
|
|
{ id: 2, name: '染发', duration: 90 },
|
||
|
|
{ id: 3, name: '护理', duration: 60 }
|
||
|
|
],
|
||
|
|
selectedService: '',
|
||
|
|
selectedDate: '',
|
||
|
|
selectedDay: '',
|
||
|
|
selectedTime: '',
|
||
|
|
timeSlots: [],
|
||
|
|
existingAppointments: {
|
||
|
|
// 示例数据: 已有预约
|
||
|
|
'2023-06-15': [['10:00', '10:30'], ['14:00', '15:00']],
|
||
|
|
'2023-06-16': [['09:30', '10:30']]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
computed: {
|
||
|
|
startDate() {
|
||
|
|
return this.getDateStr(0)
|
||
|
|
},
|
||
|
|
endDate() {
|
||
|
|
return this.getDateStr(30) // 可预约未来30天
|
||
|
|
}
|
||
|
|
},
|
||
|
|
created() {
|
||
|
|
this.initServiceManager()
|
||
|
|
this.selectedDate = this.getDateStr(0)
|
||
|
|
this.updateDayName()
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
// 生成时间段选项
|
||
|
|
generateTimeSlots(date) {
|
||
|
|
if (!this.selected_date) {
|
||
|
|
this.timeOptions = [];
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const slots = [];
|
||
|
|
let workTime;
|
||
|
|
let startWorkTime;
|
||
|
|
let endWorkTime;
|
||
|
|
if(this.service.server_kind == 1) {
|
||
|
|
workTime = this.syrorsjInfo.default_times.split('-');
|
||
|
|
startWorkTime = workTime[0].split(':');
|
||
|
|
endWorkTime = workTime[1].split(':');
|
||
|
|
}else if(this.service.server_kind == 2) {
|
||
|
|
workTime = this.syrorsjInfo.business_time.split('-');
|
||
|
|
startWorkTime = workTime[0].split(':');
|
||
|
|
endWorkTime = workTime[1].split(':');
|
||
|
|
}
|
||
|
|
const now = new Date();
|
||
|
|
let startHour;
|
||
|
|
if(this.selectedDateIndex == 0) {
|
||
|
|
startHour = now.getHours();
|
||
|
|
if(startHour > parseInt(startWorkTime[0])) {
|
||
|
|
const startMinute = now.getMinutes();
|
||
|
|
let beginTime = new Date();
|
||
|
|
if (startMinute >= 30) {
|
||
|
|
startHour = startHour+1;
|
||
|
|
}
|
||
|
|
}else {
|
||
|
|
startHour = parseInt(startWorkTime[0]);
|
||
|
|
}
|
||
|
|
|
||
|
|
}else {
|
||
|
|
startHour = parseInt(startWorkTime[0]);
|
||
|
|
}
|
||
|
|
const endHour = parseInt(endWorkTime[0]); // 结束时间 18:00
|
||
|
|
// const bookedTimes = this.bookedSlots[this.date] || [];
|
||
|
|
for (let hour = startHour; hour < endHour; hour++) {
|
||
|
|
for (let minute = 0; minute < 60; minute += 30) {
|
||
|
|
const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||
|
|
|
||
|
|
// 跳过已预约的时间
|
||
|
|
// if (!bookedTimes.includes(timeStr)) {
|
||
|
|
slots.push({
|
||
|
|
label: timeStr,
|
||
|
|
value: timeStr
|
||
|
|
});
|
||
|
|
// }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
this.timeOptions = slots;
|
||
|
|
// 如果没有可用时间段,显示提示
|
||
|
|
if (slots.length === 0) {
|
||
|
|
this.timeOptions = [{ label: '该日期无可用时间段', value: '' }];
|
||
|
|
}
|
||
|
|
},
|
||
|
|
// 初始化服务管理器
|
||
|
|
initServiceManager() {
|
||
|
|
const sm = this.serviceManager
|
||
|
|
|
||
|
|
// 设置工作日时间 (9:00-17:00)
|
||
|
|
const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
|
||
|
|
weekdays.forEach(day => {
|
||
|
|
|
||
|
|
sm.setWorkingHours(day, '09:00', '12:00')
|
||
|
|
sm.setWorkingHours(day, '13:00', '17:00')
|
||
|
|
})
|
||
|
|
|
||
|
|
// 周末时间 (10:00-16:00)
|
||
|
|
sm.setWorkingHours('Saturday', '10:00', '16:00')
|
||
|
|
sm.setWorkingHours('Sunday', '10:00', '16:00')
|
||
|
|
|
||
|
|
// 设置午休时间
|
||
|
|
weekdays.forEach(day => {
|
||
|
|
sm.addBlockedTime(day, '12:00', '13:00')
|
||
|
|
})
|
||
|
|
|
||
|
|
// 添加服务
|
||
|
|
this.services.forEach(service => {
|
||
|
|
sm.addService(service.name, service.duration)
|
||
|
|
})
|
||
|
|
},
|
||
|
|
|
||
|
|
// 获取日期字符串
|
||
|
|
getDateStr(daysAfterToday) {
|
||
|
|
const date = new Date()
|
||
|
|
date.setDate(date.getDate() + daysAfterToday)
|
||
|
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
|
||
|
|
},
|
||
|
|
|
||
|
|
// 更新星期几显示
|
||
|
|
updateDayName() {
|
||
|
|
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
||
|
|
const date = new Date(this.selectedDate)
|
||
|
|
this.selectedDay = days[date.getDay()]
|
||
|
|
},
|
||
|
|
|
||
|
|
// 服务选择变化
|
||
|
|
handleServiceChange(e) {
|
||
|
|
this.selectedService = e.detail.value
|
||
|
|
this.updateTimeSlots()
|
||
|
|
},
|
||
|
|
|
||
|
|
// 日期选择变化
|
||
|
|
handleDateChange(e) {
|
||
|
|
this.selectedDate = e.detail.value
|
||
|
|
this.updateDayName()
|
||
|
|
this.updateTimeSlots()
|
||
|
|
},
|
||
|
|
|
||
|
|
// 更新时间段列表
|
||
|
|
updateTimeSlots() {
|
||
|
|
if (!this.selectedService) return
|
||
|
|
|
||
|
|
const day = this.selectedDay
|
||
|
|
const appointments = this.existingAppointments[this.selectedDate] || []
|
||
|
|
|
||
|
|
try {
|
||
|
|
this.timeSlots = this.serviceManager.getAvailableSlots(day, this.selectedService, appointments)
|
||
|
|
this.selectedTime = ''
|
||
|
|
} catch (e) {
|
||
|
|
uni.showToast({
|
||
|
|
title: e.message,
|
||
|
|
icon: 'none'
|
||
|
|
})
|
||
|
|
this.timeSlots = []
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// 选择时间段
|
||
|
|
selectTime(slot) {
|
||
|
|
this.selectedTime = slot[0]
|
||
|
|
},
|
||
|
|
|
||
|
|
// 提交预约
|
||
|
|
submitAppointment() {
|
||
|
|
if (!this.selectedService || !this.selectedTime) return
|
||
|
|
|
||
|
|
const duration = this.services.find(s => s.name === this.selectedService).duration
|
||
|
|
const endTime = this.addMinutes(this.selectedTime, duration)
|
||
|
|
|
||
|
|
uni.showModal({
|
||
|
|
title: '确认预约',
|
||
|
|
content: `确认预约 ${this.selectedService} 服务\n时间: ${this.selectedDate} ${this.selectedTime}-${endTime}`,
|
||
|
|
success: (res) => {
|
||
|
|
if (res.confirm) {
|
||
|
|
// 这里应该调用API保存预约
|
||
|
|
uni.showToast({
|
||
|
|
title: '预约成功',
|
||
|
|
icon: 'success'
|
||
|
|
})
|
||
|
|
|
||
|
|
// 添加到已有预约
|
||
|
|
if (!this.existingAppointments[this.selectedDate]) {
|
||
|
|
this.existingAppointments[this.selectedDate] = []
|
||
|
|
}
|
||
|
|
this.existingAppointments[this.selectedDate].push([this.selectedTime, endTime])
|
||
|
|
|
||
|
|
// 重置选择
|
||
|
|
this.selectedTime = ''
|
||
|
|
this.updateTimeSlots()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
},
|
||
|
|
|
||
|
|
// 时间加减
|
||
|
|
addMinutes(time, minutes) {
|
||
|
|
const [hours, mins] = time.split(':').map(Number)
|
||
|
|
let totalMins = hours * 60 + mins + minutes
|
||
|
|
const newHours = Math.floor(totalMins / 60)
|
||
|
|
const newMins = totalMins % 60
|
||
|
|
return `${newHours.toString().padStart(2, '0')}:${newMins.toString().padStart(2, '0')}`
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.appointment-page {
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.section {
|
||
|
|
margin-bottom: 30px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-title {
|
||
|
|
display: block;
|
||
|
|
font-size: 16px;
|
||
|
|
font-weight: bold;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.radio-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
margin: 10px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.picker {
|
||
|
|
padding: 10px;
|
||
|
|
border: 1px solid #eee;
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.time-slots {
|
||
|
|
display: flex;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
gap: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.time-slot {
|
||
|
|
padding: 8px 12px;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
border-radius: 4px;
|
||
|
|
background-color: #f9f9f9;
|
||
|
|
}
|
||
|
|
|
||
|
|
.time-slot.selected {
|
||
|
|
background-color: #007aff;
|
||
|
|
color: white;
|
||
|
|
border-color: #007aff;
|
||
|
|
}
|
||
|
|
|
||
|
|
.no-slots {
|
||
|
|
color: #999;
|
||
|
|
text-align: center;
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.submit-btn {
|
||
|
|
margin-top: 30px;
|
||
|
|
}
|
||
|
|
</style>
|