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.
1023 lines
22 KiB
1023 lines
22 KiB
<!--学员课程预约页面-->
|
|
<template>
|
|
<view class="main_box">
|
|
<!-- 自定义导航栏 -->
|
|
<view class="navbar_section">
|
|
<view class="navbar_back" @click="goBack">
|
|
<text class="back_icon">‹</text>
|
|
</view>
|
|
<view class="navbar_title">课程预约</view>
|
|
<view class="navbar_action"></view>
|
|
</view>
|
|
|
|
<!-- 学员信息 -->
|
|
<view class="student_info_section" v-if="studentInfo">
|
|
<view class="student_name">{{ studentInfo.name }}</view>
|
|
<view class="course_info">
|
|
<text class="info_item">剩余课时:{{ studentInfo.remaining_courses || 0 }}节</text>
|
|
<text class="info_item">可预约:{{ availableBookings }}节</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 日期选择器 -->
|
|
<view class="date_selector_section">
|
|
<view class="date_header">
|
|
<view class="month_info">{{ currentMonth }}</view>
|
|
<view class="date_controls">
|
|
<view class="control_button" @click="prevWeek">‹</view>
|
|
<view class="control_button" @click="nextWeek">›</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="date_tabs">
|
|
<view
|
|
v-for="date in dateList"
|
|
:key="date.date"
|
|
:class="['date_tab', selectedDate === date.date ? 'active' : '', date.disabled ? 'disabled' : '']"
|
|
@click="selectDate(date)"
|
|
>
|
|
<view class="tab_weekday">{{ date.weekday }}</view>
|
|
<view class="tab_date">{{ date.day }}</view>
|
|
<view class="tab_dot" v-if="date.hasBooking"></view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 时段选择 -->
|
|
<view class="time_slots_section">
|
|
<view class="section_title">可预约时段</view>
|
|
|
|
<view v-if="loading" class="loading_section">
|
|
<view class="loading_text">加载中...</view>
|
|
</view>
|
|
|
|
<view v-else-if="timeSlots.length === 0" class="empty_section">
|
|
<view class="empty_icon">📅</view>
|
|
<view class="empty_text">当日暂无可预约时段</view>
|
|
<view class="empty_hint">请选择其他日期</view>
|
|
</view>
|
|
|
|
<view v-else class="time_slots_list">
|
|
<view
|
|
v-for="slot in timeSlots"
|
|
:key="slot.id"
|
|
:class="['time_slot_item', slot.status]"
|
|
@click="selectTimeSlot(slot)"
|
|
>
|
|
<view class="slot_time">
|
|
<view class="time_range">{{ slot.start_time }} - {{ slot.end_time }}</view>
|
|
<view class="time_duration">{{ slot.duration }}分钟</view>
|
|
</view>
|
|
|
|
<view class="slot_info">
|
|
<view class="coach_name">教练:{{ slot.coach_name }}</view>
|
|
<view class="course_type">{{ slot.course_type }}</view>
|
|
<view class="venue_info">{{ slot.venue_name }}</view>
|
|
</view>
|
|
|
|
<view class="slot_status">
|
|
<view v-if="slot.status === 'available'" class="status_text available">可预约</view>
|
|
<view v-else-if="slot.status === 'booked'" class="status_text booked">已预约</view>
|
|
<view v-else-if="slot.status === 'full'" class="status_text full">已满员</view>
|
|
<view v-else-if="slot.status === 'closed'" class="status_text closed">已结束</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 我的预约 -->
|
|
<view class="my_bookings_section">
|
|
<view class="section_title">我的预约</view>
|
|
|
|
<view v-if="myBookings.length === 0" class="empty_bookings">
|
|
<view class="empty_text">暂无预约</view>
|
|
</view>
|
|
|
|
<view v-else class="bookings_list">
|
|
<view
|
|
v-for="booking in myBookings"
|
|
:key="booking.id"
|
|
class="booking_item"
|
|
>
|
|
<view class="booking_header">
|
|
<view class="booking_date">{{ formatBookingDate(booking.booking_date) }}</view>
|
|
<view class="booking_status" :class="booking.status">
|
|
{{ getBookingStatusText(booking.status) }}
|
|
</view>
|
|
</view>
|
|
|
|
<view class="booking_details">
|
|
<view class="detail_row">
|
|
<text class="detail_label">时间:</text>
|
|
<text class="detail_value">{{ booking.start_time }} - {{ booking.end_time }}</text>
|
|
</view>
|
|
<view class="detail_row">
|
|
<text class="detail_label">教练:</text>
|
|
<text class="detail_value">{{ booking.coach_name }}</text>
|
|
</view>
|
|
<view class="detail_row">
|
|
<text class="detail_label">课程:</text>
|
|
<text class="detail_value">{{ booking.course_type }}</text>
|
|
</view>
|
|
<view class="detail_row">
|
|
<text class="detail_label">场地:</text>
|
|
<text class="detail_value">{{ booking.venue_name }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="booking_actions" v-if="booking.status === 'pending'">
|
|
<fui-button
|
|
background="transparent"
|
|
color="#e74c3c"
|
|
size="small"
|
|
@click="cancelBooking(booking)"
|
|
>
|
|
取消预约
|
|
</fui-button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 预约确认弹窗 -->
|
|
<view class="booking_popup" v-if="showBookingPopup" @click="closeBookingPopup">
|
|
<view class="popup_content" @click.stop>
|
|
<view class="popup_header">
|
|
<view class="popup_title">确认预约</view>
|
|
<view class="popup_close" @click="closeBookingPopup">×</view>
|
|
</view>
|
|
|
|
<view class="popup_booking_info" v-if="selectedSlot">
|
|
<view class="info_row">
|
|
<text class="info_label">日期:</text>
|
|
<text class="info_value">{{ formatDate(selectedDate) }}</text>
|
|
</view>
|
|
<view class="info_row">
|
|
<text class="info_label">时间:</text>
|
|
<text class="info_value">{{ selectedSlot.start_time }} - {{ selectedSlot.end_time }}</text>
|
|
</view>
|
|
<view class="info_row">
|
|
<text class="info_label">教练:</text>
|
|
<text class="info_value">{{ selectedSlot.coach_name }}</text>
|
|
</view>
|
|
<view class="info_row">
|
|
<text class="info_label">课程:</text>
|
|
<text class="info_value">{{ selectedSlot.course_type }}</text>
|
|
</view>
|
|
<view class="info_row">
|
|
<text class="info_label">场地:</text>
|
|
<text class="info_value">{{ selectedSlot.venue_name }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="popup_actions">
|
|
<fui-button
|
|
background="#f8f9fa"
|
|
color="#666"
|
|
@click="closeBookingPopup"
|
|
>
|
|
取消
|
|
</fui-button>
|
|
<fui-button
|
|
background="#29d3b4"
|
|
:loading="booking"
|
|
@click="confirmBooking"
|
|
>
|
|
{{ booking ? '预约中...' : '确认预约' }}
|
|
</fui-button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import apiRoute from '@/api/apiRoute.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
studentId: 0,
|
|
studentInfo: {},
|
|
selectedDate: '',
|
|
dateList: [],
|
|
timeSlots: [],
|
|
myBookings: [],
|
|
loading: false,
|
|
booking: false,
|
|
showBookingPopup: false,
|
|
selectedSlot: null,
|
|
currentWeekStart: new Date()
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
currentMonth() {
|
|
const date = new Date(this.selectedDate || new Date())
|
|
return `${date.getFullYear()}年${date.getMonth() + 1}月`
|
|
},
|
|
|
|
availableBookings() {
|
|
// 根据业务规则计算可预约课时数
|
|
const remaining = this.studentInfo.remaining_courses || 0
|
|
const pending = this.myBookings.filter(b => b.status === 'pending').length
|
|
return Math.max(0, remaining - pending)
|
|
}
|
|
},
|
|
|
|
onLoad(options) {
|
|
this.studentId = parseInt(options.student_id) || 0
|
|
if (this.studentId) {
|
|
this.initPage()
|
|
} else {
|
|
uni.showToast({
|
|
title: '参数错误',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
goBack() {
|
|
uni.navigateBack()
|
|
},
|
|
|
|
async initPage() {
|
|
await this.loadStudentInfo()
|
|
this.generateDateList()
|
|
await this.loadMyBookings()
|
|
|
|
// 默认选择今天
|
|
const today = new Date()
|
|
const todayStr = this.formatDateString(today)
|
|
this.selectedDate = todayStr
|
|
await this.loadTimeSlots()
|
|
},
|
|
|
|
async loadStudentInfo() {
|
|
try {
|
|
// 模拟获取学员信息
|
|
const mockStudentInfo = {
|
|
id: this.studentId,
|
|
name: '小明',
|
|
remaining_courses: 24,
|
|
total_courses: 48
|
|
}
|
|
this.studentInfo = mockStudentInfo
|
|
} catch (error) {
|
|
console.error('获取学员信息失败:', error)
|
|
}
|
|
},
|
|
|
|
generateDateList() {
|
|
const dates = []
|
|
const startDate = new Date(this.currentWeekStart)
|
|
|
|
for (let i = 0; i < 7; i++) {
|
|
const date = new Date(startDate)
|
|
date.setDate(startDate.getDate() + i)
|
|
|
|
const dateStr = this.formatDateString(date)
|
|
const today = new Date()
|
|
const isDisabled = date < today
|
|
|
|
dates.push({
|
|
date: dateStr,
|
|
day: date.getDate(),
|
|
weekday: this.getWeekday(date.getDay()),
|
|
disabled: isDisabled,
|
|
hasBooking: this.myBookings.some(b => b.booking_date === dateStr)
|
|
})
|
|
}
|
|
|
|
this.dateList = dates
|
|
},
|
|
|
|
prevWeek() {
|
|
this.currentWeekStart.setDate(this.currentWeekStart.getDate() - 7)
|
|
this.generateDateList()
|
|
},
|
|
|
|
nextWeek() {
|
|
this.currentWeekStart.setDate(this.currentWeekStart.getDate() + 7)
|
|
this.generateDateList()
|
|
},
|
|
|
|
selectDate(dateObj) {
|
|
if (dateObj.disabled) {
|
|
uni.showToast({
|
|
title: '不能选择过去的日期',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
|
|
this.selectedDate = dateObj.date
|
|
this.loadTimeSlots()
|
|
},
|
|
|
|
async loadTimeSlots() {
|
|
if (!this.selectedDate) return
|
|
|
|
this.loading = true
|
|
try {
|
|
console.log('加载时段:', this.selectedDate)
|
|
|
|
// 调用真实API
|
|
const response = await apiRoute.getAvailableCourses({
|
|
student_id: this.studentId,
|
|
date: this.selectedDate
|
|
})
|
|
|
|
if (response.code === 1) {
|
|
// 处理响应数据
|
|
this.timeSlots = response.data.list.map(course => ({
|
|
id: course.id,
|
|
start_time: course.start_time,
|
|
end_time: course.end_time,
|
|
duration: course.duration || 60,
|
|
coach_name: course.coach_name,
|
|
course_name: course.course_name,
|
|
course_type: course.course_type || course.course_name,
|
|
venue_name: course.venue_name,
|
|
status: course.booking_status,
|
|
max_students: course.max_students,
|
|
current_students: course.current_students
|
|
}))
|
|
|
|
console.log('时段数据加载成功:', this.timeSlots)
|
|
} else {
|
|
uni.showToast({
|
|
title: response.msg || '获取时段失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('获取时段失败:', error)
|
|
uni.showToast({
|
|
title: '获取时段失败',
|
|
icon: 'none'
|
|
})
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async loadMyBookings() {
|
|
try {
|
|
console.log('加载我的预约')
|
|
|
|
// 调用真实API
|
|
const response = await apiRoute.getMyBookingList({
|
|
student_id: this.studentId
|
|
})
|
|
|
|
if (response.code === 1) {
|
|
// 处理响应数据
|
|
this.myBookings = response.data.list.map(booking => ({
|
|
id: booking.id,
|
|
booking_date: booking.booking_date,
|
|
start_time: booking.start_time,
|
|
end_time: booking.end_time,
|
|
coach_name: booking.coach_name,
|
|
course_type: booking.course_type,
|
|
venue_name: booking.venue_name,
|
|
status: this.mapBookingStatus(booking.status),
|
|
time_slot_id: booking.schedule_id
|
|
}))
|
|
|
|
console.log('我的预约加载成功:', this.myBookings)
|
|
}
|
|
} catch (error) {
|
|
console.error('获取我的预约失败:', error)
|
|
}
|
|
},
|
|
|
|
selectTimeSlot(slot) {
|
|
if (slot.status !== 'available') {
|
|
if (slot.status === 'booked') {
|
|
uni.showToast({
|
|
title: '您已预约此时段',
|
|
icon: 'none'
|
|
})
|
|
} else if (slot.status === 'full') {
|
|
uni.showToast({
|
|
title: '此时段已满员',
|
|
icon: 'none'
|
|
})
|
|
} else if (slot.status === 'closed') {
|
|
uni.showToast({
|
|
title: '此时段已结束',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
if (this.availableBookings <= 0) {
|
|
uni.showToast({
|
|
title: '剩余课时不足',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
|
|
this.selectedSlot = slot
|
|
this.showBookingPopup = true
|
|
},
|
|
|
|
closeBookingPopup() {
|
|
this.showBookingPopup = false
|
|
this.selectedSlot = null
|
|
},
|
|
|
|
async confirmBooking() {
|
|
if (!this.selectedSlot || this.booking) return
|
|
|
|
this.booking = true
|
|
try {
|
|
console.log('确认预约:', {
|
|
student_id: this.studentId,
|
|
schedule_id: this.selectedSlot.id,
|
|
booking_date: this.selectedDate,
|
|
time_slot: `${this.selectedSlot.start_time}-${this.selectedSlot.end_time}`
|
|
})
|
|
|
|
// 调用真实API
|
|
const response = await apiRoute.createBooking({
|
|
student_id: this.studentId,
|
|
schedule_id: this.selectedSlot.id,
|
|
booking_date: this.selectedDate,
|
|
time_slot: `${this.selectedSlot.start_time}-${this.selectedSlot.end_time}`
|
|
})
|
|
|
|
if (response.code === 1) {
|
|
uni.showToast({
|
|
title: '预约成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
this.closeBookingPopup()
|
|
|
|
// 重新加载数据
|
|
await Promise.all([
|
|
this.loadMyBookings(),
|
|
this.loadTimeSlots()
|
|
])
|
|
} else {
|
|
uni.showToast({
|
|
title: response.msg || '预约失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('预约失败:', error)
|
|
uni.showToast({
|
|
title: '预约失败',
|
|
icon: 'none'
|
|
})
|
|
} finally {
|
|
this.booking = false
|
|
}
|
|
},
|
|
|
|
async cancelBooking(booking) {
|
|
uni.showModal({
|
|
title: '确认取消',
|
|
content: '确定要取消此预约吗?',
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
try {
|
|
console.log('取消预约:', booking.id)
|
|
|
|
// 调用真实API
|
|
const response = await apiRoute.cancelBooking({
|
|
booking_id: booking.id,
|
|
cancel_reason: '用户主动取消'
|
|
})
|
|
|
|
if (response.code === 1) {
|
|
uni.showToast({
|
|
title: '取消成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
// 重新加载数据
|
|
await Promise.all([
|
|
this.loadMyBookings(),
|
|
this.loadTimeSlots()
|
|
])
|
|
} else {
|
|
uni.showToast({
|
|
title: response.msg || '取消失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('取消预约失败:', error)
|
|
uni.showToast({
|
|
title: '取消失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
},
|
|
|
|
// 工具方法
|
|
formatDateString(date) {
|
|
const year = date.getFullYear()
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
return `${year}-${month}-${day}`
|
|
},
|
|
|
|
formatDate(dateString) {
|
|
const date = new Date(dateString)
|
|
const month = date.getMonth() + 1
|
|
const day = date.getDate()
|
|
const weekday = this.getWeekday(date.getDay())
|
|
return `${month}月${day}日 ${weekday}`
|
|
},
|
|
|
|
formatBookingDate(dateString) {
|
|
const date = new Date(dateString)
|
|
const month = date.getMonth() + 1
|
|
const day = date.getDate()
|
|
const weekday = this.getWeekday(date.getDay())
|
|
return `${month}月${day}日 ${weekday}`
|
|
},
|
|
|
|
getWeekday(dayIndex) {
|
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
|
return weekdays[dayIndex]
|
|
},
|
|
|
|
getBookingStatusText(status) {
|
|
const statusMap = {
|
|
'pending': '待上课',
|
|
'completed': '已完成',
|
|
'cancelled': '已取消'
|
|
}
|
|
return statusMap[status] || status
|
|
},
|
|
|
|
// 将后端状态码映射为前端状态
|
|
mapBookingStatus(status) {
|
|
const statusMap = {
|
|
0: 'pending', // 待上课
|
|
1: 'completed', // 已完成
|
|
2: 'leave', // 请假
|
|
3: 'cancelled' // 已取消
|
|
}
|
|
return statusMap[status] || 'pending'
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.main_box {
|
|
background: #f8f9fa;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
// 自定义导航栏
|
|
.navbar_section {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: #29D3B4;
|
|
padding: 40rpx 32rpx 20rpx;
|
|
|
|
// 小程序端适配状态栏
|
|
// #ifdef MP-WEIXIN
|
|
padding-top: 80rpx;
|
|
// #endif
|
|
|
|
.navbar_back {
|
|
width: 60rpx;
|
|
|
|
.back_icon {
|
|
color: #fff;
|
|
font-size: 40rpx;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.navbar_title {
|
|
color: #fff;
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.navbar_action {
|
|
width: 60rpx;
|
|
}
|
|
}
|
|
|
|
// 学员信息
|
|
.student_info_section {
|
|
background: #fff;
|
|
padding: 24rpx 32rpx;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
.student_name {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.course_info {
|
|
display: flex;
|
|
gap: 24rpx;
|
|
|
|
.info_item {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 日期选择器
|
|
.date_selector_section {
|
|
background: #fff;
|
|
margin: 20rpx;
|
|
border-radius: 16rpx;
|
|
padding: 24rpx 32rpx;
|
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
|
|
|
.date_header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24rpx;
|
|
|
|
.month_info {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.date_controls {
|
|
display: flex;
|
|
gap: 16rpx;
|
|
|
|
.control_button {
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
background: #f8f9fa;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
}
|
|
}
|
|
}
|
|
|
|
.date_tabs {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
|
|
.date_tab {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 16rpx 8rpx;
|
|
border-radius: 12rpx;
|
|
position: relative;
|
|
|
|
&.active {
|
|
background: #29D3B4;
|
|
color: #fff;
|
|
}
|
|
|
|
&.disabled {
|
|
opacity: 0.4;
|
|
}
|
|
|
|
.tab_weekday {
|
|
font-size: 22rpx;
|
|
margin-bottom: 4rpx;
|
|
}
|
|
|
|
.tab_date {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tab_dot {
|
|
position: absolute;
|
|
top: 8rpx;
|
|
right: 12rpx;
|
|
width: 8rpx;
|
|
height: 8rpx;
|
|
background: #ff4757;
|
|
border-radius: 50%;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 时段列表
|
|
.time_slots_section {
|
|
margin: 0 20rpx 20rpx;
|
|
|
|
.section_title {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 16rpx;
|
|
padding-left: 16rpx;
|
|
}
|
|
|
|
.loading_section, .empty_section {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 60rpx 32rpx;
|
|
text-align: center;
|
|
|
|
.loading_text, .empty_text {
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.empty_icon {
|
|
font-size: 60rpx;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.empty_hint {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.time_slots_list {
|
|
.time_slot_item {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 24rpx;
|
|
margin-bottom: 16rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
|
|
|
&.available {
|
|
border-left: 4rpx solid #29D3B4;
|
|
}
|
|
|
|
&.booked {
|
|
background: #f8f9fa;
|
|
border-left: 4rpx solid #3498db;
|
|
}
|
|
|
|
&.full {
|
|
background: #f8f9fa;
|
|
border-left: 4rpx solid #f39c12;
|
|
}
|
|
|
|
&.closed {
|
|
background: #f8f9fa;
|
|
border-left: 4rpx solid #95a5a6;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.slot_time {
|
|
min-width: 120rpx;
|
|
|
|
.time_range {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 4rpx;
|
|
}
|
|
|
|
.time_duration {
|
|
font-size: 22rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.slot_info {
|
|
flex: 1;
|
|
|
|
.coach_name {
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
margin-bottom: 6rpx;
|
|
}
|
|
|
|
.course_type {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
margin-bottom: 4rpx;
|
|
}
|
|
|
|
.venue_info {
|
|
font-size: 22rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.slot_status {
|
|
.status_text {
|
|
font-size: 22rpx;
|
|
padding: 6rpx 12rpx;
|
|
border-radius: 12rpx;
|
|
|
|
&.available {
|
|
color: #27ae60;
|
|
background: rgba(39, 174, 96, 0.1);
|
|
}
|
|
|
|
&.booked {
|
|
color: #3498db;
|
|
background: rgba(52, 152, 219, 0.1);
|
|
}
|
|
|
|
&.full {
|
|
color: #f39c12;
|
|
background: rgba(243, 156, 18, 0.1);
|
|
}
|
|
|
|
&.closed {
|
|
color: #95a5a6;
|
|
background: rgba(149, 165, 166, 0.1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 我的预约
|
|
.my_bookings_section {
|
|
margin: 0 20rpx 20rpx;
|
|
|
|
.section_title {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 16rpx;
|
|
padding-left: 16rpx;
|
|
}
|
|
|
|
.empty_bookings {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 40rpx;
|
|
text-align: center;
|
|
|
|
.empty_text {
|
|
font-size: 26rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.bookings_list {
|
|
.booking_item {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 24rpx;
|
|
margin-bottom: 16rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
|
|
|
.booking_header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 16rpx;
|
|
|
|
.booking_date {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.booking_status {
|
|
font-size: 22rpx;
|
|
padding: 6rpx 12rpx;
|
|
border-radius: 12rpx;
|
|
|
|
&.pending {
|
|
color: #f39c12;
|
|
background: rgba(243, 156, 18, 0.1);
|
|
}
|
|
|
|
&.completed {
|
|
color: #27ae60;
|
|
background: rgba(39, 174, 96, 0.1);
|
|
}
|
|
|
|
&.cancelled {
|
|
color: #e74c3c;
|
|
background: rgba(231, 76, 60, 0.1);
|
|
}
|
|
}
|
|
}
|
|
|
|
.booking_details {
|
|
.detail_row {
|
|
display: flex;
|
|
margin-bottom: 8rpx;
|
|
|
|
.detail_label {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
min-width: 80rpx;
|
|
}
|
|
|
|
.detail_value {
|
|
font-size: 24rpx;
|
|
color: #333;
|
|
}
|
|
}
|
|
}
|
|
|
|
.booking_actions {
|
|
margin-top: 16rpx;
|
|
text-align: right;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 预约弹窗
|
|
.booking_popup {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 1000;
|
|
|
|
.popup_content {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
width: 85%;
|
|
max-height: 70vh;
|
|
overflow: hidden;
|
|
|
|
.popup_header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 32rpx;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
.popup_title {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.popup_close {
|
|
font-size: 48rpx;
|
|
color: #999;
|
|
font-weight: 300;
|
|
}
|
|
}
|
|
|
|
.popup_booking_info {
|
|
padding: 32rpx;
|
|
|
|
.info_row {
|
|
display: flex;
|
|
margin-bottom: 16rpx;
|
|
|
|
.info_label {
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
min-width: 100rpx;
|
|
}
|
|
|
|
.info_value {
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
|
|
.popup_actions {
|
|
padding: 24rpx 32rpx;
|
|
display: flex;
|
|
gap: 16rpx;
|
|
|
|
fui-button {
|
|
flex: 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|