|
|
@@ -0,0 +1,783 @@
|
|
|
+<template>
|
|
|
+ <!-- 使用 u-popup 作为弹窗容器 -->
|
|
|
+ <u-popup
|
|
|
+ :show="visible"
|
|
|
+ mode="bottom"
|
|
|
+ round="20"
|
|
|
+ :closeable="false"
|
|
|
+ @close="close"
|
|
|
+ :safeAreaInsetBottom="true"
|
|
|
+ customStyle="height: 80vh;"
|
|
|
+ >
|
|
|
+ <view class="time-picker-container">
|
|
|
+ <!-- 标题栏 -->
|
|
|
+ <view class="picker-header">
|
|
|
+ <text class="picker-title">期望上门时间</text>
|
|
|
+ <text class="close-btn" @tap="close">×</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 主要内容区域:左侧日期 + 右侧时间 -->
|
|
|
+ <view class="main-content">
|
|
|
+ <!-- 左侧日期选择 -->
|
|
|
+ <scroll-view class="date-sidebar" scroll-y>
|
|
|
+ <view
|
|
|
+ v-for="date in dateList"
|
|
|
+ :key="date.value"
|
|
|
+ class="date-item"
|
|
|
+ :class="{
|
|
|
+ active: selectedDate === date.value,
|
|
|
+ today: date.isToday
|
|
|
+ }"
|
|
|
+ @tap="selectDate(date.value)"
|
|
|
+ >
|
|
|
+ <view class="date-content">
|
|
|
+ <text class="date-name">{{ date.label }}</text>
|
|
|
+ <text class="date-info">{{ date.month }}月{{ date.day }}日</text>
|
|
|
+ <text class="date-week">{{ date.week }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+
|
|
|
+ <!-- 右侧时间选择 -->
|
|
|
+ <scroll-view class="time-content" scroll-y>
|
|
|
+ <!-- 推荐时间段(仅今天显示) -->
|
|
|
+ <view v-if="selectedDate === todayDate && withinOneHourSlot" class="recommend-section">
|
|
|
+ <view class="recommend-header">
|
|
|
+ <text class="recommend-title">一小时内</text>
|
|
|
+ <!-- <text class="recommend-desc">推荐时间:{{ withinOneHourSlot.label }}</text> -->
|
|
|
+ </view>
|
|
|
+ <view class="recommend-time"
|
|
|
+ @tap="selectTime(withinOneHourSlot.value, true)">
|
|
|
+ <text class="time-value">{{ withinOneHourSlot.label }}</text>
|
|
|
+ <!-- <text class="time-tag">推荐</text> -->
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 时间段列表 -->
|
|
|
+ <view class="time-section">
|
|
|
+ <view class="section-title">可选时间段</view>
|
|
|
+
|
|
|
+ <view v-if="loading" class="loading">加载中...</view>
|
|
|
+
|
|
|
+ <view v-else class="time-list">
|
|
|
+ <view
|
|
|
+ v-for="timeSlot in timeSlots"
|
|
|
+ :key="timeSlot.value"
|
|
|
+ class="time-item"
|
|
|
+ :class="{
|
|
|
+ active: selectedTime === timeSlot.value,
|
|
|
+ disabled: timeSlot.disabled,
|
|
|
+ night: timeSlot.night
|
|
|
+ }"
|
|
|
+ @tap="!timeSlot.disabled && selectTime(timeSlot.value, false)"
|
|
|
+ >
|
|
|
+ <text class="time-text">{{ timeSlot.label }}</text>
|
|
|
+ <text v-if="timeSlot.tag" class="time-tag">{{ timeSlot.tag }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 提示信息 -->
|
|
|
+ <view class="notice-section">
|
|
|
+ <text class="notice-text">
|
|
|
+ 部分区域支持夜间上门,具体以快递员联系为准
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 操作按钮 -->
|
|
|
+ <view class="action-bar">
|
|
|
+ <button
|
|
|
+ class="confirm-btn"
|
|
|
+ :class="{ active: selectedTime }"
|
|
|
+ @tap="confirm"
|
|
|
+ >
|
|
|
+ {{ buttonText }}
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </u-popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ visible: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ // 显示天数
|
|
|
+ days: {
|
|
|
+ type: Number,
|
|
|
+ default: 3
|
|
|
+ },
|
|
|
+ // 开始时间
|
|
|
+ startTime: {
|
|
|
+ type: Number,
|
|
|
+ default: 9
|
|
|
+ },
|
|
|
+ // 结束时间
|
|
|
+ endTime: {
|
|
|
+ type: Number,
|
|
|
+ default: 21
|
|
|
+ },
|
|
|
+ // 是否自动选择推荐时间
|
|
|
+ autoSelect: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const emit = defineEmits(['update:visible', 'close', 'confirm'])
|
|
|
+
|
|
|
+// 当前时间
|
|
|
+const currentTime = ref(new Date())
|
|
|
+const selectedDate = ref('')
|
|
|
+const selectedTime = ref('')
|
|
|
+const loading = ref(false)
|
|
|
+const isWithinOneHour = ref(false)
|
|
|
+
|
|
|
+// 计算当前时间20分钟后的时间
|
|
|
+const twentyMinutesLater = computed(() => {
|
|
|
+ const now = new Date()
|
|
|
+ now.setMinutes(now.getMinutes() + 10)
|
|
|
+ return now
|
|
|
+})
|
|
|
+
|
|
|
+// 计算一小时后的小时(向上取整)
|
|
|
+const oneHourLaterHour = computed(() => {
|
|
|
+ const now = new Date()
|
|
|
+ now.setHours(now.getHours() + 1)
|
|
|
+ return Math.ceil(now.getHours())
|
|
|
+})
|
|
|
+
|
|
|
+// 获取今天日期字符串
|
|
|
+const todayDate = computed(() => {
|
|
|
+ const today = new Date()
|
|
|
+ return formatDate(today)
|
|
|
+})
|
|
|
+
|
|
|
+// 生成日期列表
|
|
|
+const dateList = computed(() => {
|
|
|
+ const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
|
|
+ const list = []
|
|
|
+ const today = new Date()
|
|
|
+
|
|
|
+ for (let i = 0; i < props.days; i++) {
|
|
|
+ const date = new Date(today)
|
|
|
+ date.setDate(date.getDate() + i)
|
|
|
+
|
|
|
+ const month = date.getMonth() + 1
|
|
|
+ const day = date.getDate()
|
|
|
+ const week = days[date.getDay()]
|
|
|
+ const dateStr = formatDate(date)
|
|
|
+
|
|
|
+ let label = ''
|
|
|
+ if (i === 0) {
|
|
|
+ label = '今天'
|
|
|
+ } else if (i === 1) {
|
|
|
+ label = '明天'
|
|
|
+ } else {
|
|
|
+ label = '后天'
|
|
|
+ }
|
|
|
+
|
|
|
+ list.push({
|
|
|
+ value: dateStr,
|
|
|
+ label: label,
|
|
|
+ week: week,
|
|
|
+ month: month,
|
|
|
+ day: day,
|
|
|
+ fullLabel: `${month}月${day}日 ${week}`,
|
|
|
+ isToday: i === 0,
|
|
|
+ isTomorrow: i === 1,
|
|
|
+ isDayAfterTomorrow: i === 2,
|
|
|
+ displayDate: `${date.getFullYear()}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return list
|
|
|
+})
|
|
|
+
|
|
|
+// 基础时间段配置
|
|
|
+const baseTimeSlots = computed(() => {
|
|
|
+ const slots = []
|
|
|
+ const startHour = props.startTime
|
|
|
+ const endHour = props.endTime
|
|
|
+ const interval = 1 // 小时间隔
|
|
|
+
|
|
|
+ for (let hour = startHour; hour < endHour; hour += interval) {
|
|
|
+ const start = hour
|
|
|
+ const end = hour + interval
|
|
|
+ const startStr = start.toString().padStart(2, '0')
|
|
|
+ const endStr = end.toString().padStart(2, '0')
|
|
|
+
|
|
|
+ slots.push({
|
|
|
+ start: start,
|
|
|
+ end: end,
|
|
|
+ label: `${startStr}:00-${endStr}:00`,
|
|
|
+ value: `${startStr}:00-${endStr}:00`,
|
|
|
+ startHour: start,
|
|
|
+ endHour: end
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return slots
|
|
|
+})
|
|
|
+
|
|
|
+// 计算一小时内时间段
|
|
|
+const withinOneHourSlot = computed(() => {
|
|
|
+ if (!dateList.value.length || !dateList.value[0].isToday) return null
|
|
|
+
|
|
|
+ const now = currentTime.value
|
|
|
+ const later20min = twentyMinutesLater.value
|
|
|
+
|
|
|
+ // 计算开始时间:当前时间 + 20分钟,分钟向上取整到10的倍数
|
|
|
+ const startMinutes = later20min.getMinutes()
|
|
|
+ const roundedMinutes = Math.ceil(startMinutes / 10) * 10
|
|
|
+ let startHour = later20min.getHours()
|
|
|
+ let startMinute = roundedMinutes
|
|
|
+
|
|
|
+ // 如果分钟超过60,小时加1,分钟归零
|
|
|
+ if (startMinute >= 60) {
|
|
|
+ startHour += 1
|
|
|
+ startMinute = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算结束时间:开始时间 + 1小时
|
|
|
+ let endHour = startHour + 1
|
|
|
+ let endMinute = startMinute
|
|
|
+
|
|
|
+ // 如果开始时间已经超过结束时间限制,则不显示
|
|
|
+ if (startHour >= props.endTime) return null
|
|
|
+
|
|
|
+ // 如果结束时间超过结束时间限制,则调整
|
|
|
+ if (endHour > props.endTime) {
|
|
|
+ endHour = props.endTime
|
|
|
+ endMinute = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果开始时间小于开始时间限制,则调整
|
|
|
+ if (startHour < props.startTime) {
|
|
|
+ startHour = props.startTime
|
|
|
+ startMinute = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查时间段是否有效(至少30分钟)
|
|
|
+ const slotDuration = (endHour - startHour) * 60 + (endMinute - startMinute)
|
|
|
+ if (slotDuration < 30) return null
|
|
|
+
|
|
|
+ const startStr = formatTime(startHour, startMinute)
|
|
|
+ const endStr = formatTime(endHour, endMinute)
|
|
|
+
|
|
|
+ return {
|
|
|
+ start: startHour + startMinute / 60,
|
|
|
+ end: endHour + endMinute / 60,
|
|
|
+ label: `(${startStr}-${endStr})`,
|
|
|
+ value: `${startStr}-${endStr}`,
|
|
|
+ isWithinOneHour: true,
|
|
|
+ startHour: startHour,
|
|
|
+ startMinute: startMinute,
|
|
|
+ endHour: endHour,
|
|
|
+ endMinute: endMinute
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 是否是夜间时间段
|
|
|
+const isNightTime = (hour) => {
|
|
|
+ return hour >= 18
|
|
|
+}
|
|
|
+
|
|
|
+// 根据选择的日期获取时间段
|
|
|
+const timeSlots = computed(() => {
|
|
|
+ const slots = []
|
|
|
+ const isToday = selectedDate.value === todayDate.value
|
|
|
+
|
|
|
+ if (isToday) {
|
|
|
+ const oneHourLater = oneHourLaterHour.value
|
|
|
+
|
|
|
+ // 添加普通时间段(开始时间从一小时后开始)
|
|
|
+ baseTimeSlots.value.forEach(slot => {
|
|
|
+ // 时间段开始时间必须在当前时间+1小时之后
|
|
|
+ const isDisabled = slot.start < oneHourLater
|
|
|
+
|
|
|
+ if (!isDisabled) {
|
|
|
+ slots.push({
|
|
|
+ ...slot,
|
|
|
+ disabled: false,
|
|
|
+ night: false,
|
|
|
+ tag: ''
|
|
|
+ })
|
|
|
+ // night: isNightTime(slot.start),
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 明天及以后显示所有时间段
|
|
|
+ baseTimeSlots.value.forEach(slot => {
|
|
|
+ slots.push({
|
|
|
+ ...slot,
|
|
|
+ disabled: false,
|
|
|
+ night: isNightTime(slot.start),
|
|
|
+ tag: ''
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return slots
|
|
|
+})
|
|
|
+
|
|
|
+// 按钮文本
|
|
|
+const buttonText = computed(() => {
|
|
|
+ if (!selectedTime.value) return '请选择上门时间'
|
|
|
+ return '确定'
|
|
|
+})
|
|
|
+
|
|
|
+// 初始化选择
|
|
|
+const initSelection = () => {
|
|
|
+ if (dateList.value.length > 0) {
|
|
|
+ selectedDate.value = dateList.value[0].value
|
|
|
+
|
|
|
+ // 自动选择推荐时间或第一个可用时间
|
|
|
+ if (withinOneHourSlot.value && props.autoSelect) {
|
|
|
+ selectedTime.value = withinOneHourSlot.value.value
|
|
|
+ isWithinOneHour.value = true
|
|
|
+ } else {
|
|
|
+ const firstSlot = timeSlots.value[0]
|
|
|
+ if (firstSlot) {
|
|
|
+ selectedTime.value = firstSlot.value
|
|
|
+ isWithinOneHour.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 选择日期
|
|
|
+const selectDate = (date) => {
|
|
|
+ selectedDate.value = date
|
|
|
+ selectedTime.value = '' // 重置时间选择
|
|
|
+ isWithinOneHour.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 选择时间
|
|
|
+const selectTime = (time, isWithinOneHourFlag = false) => {
|
|
|
+ selectedTime.value = time
|
|
|
+ isWithinOneHour.value = isWithinOneHourFlag
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化日期时间
|
|
|
+const formatDateTime = (dateStr, hour) => {
|
|
|
+ const date = new Date(dateStr)
|
|
|
+ const wholeHour = Math.floor(hour)
|
|
|
+ const minutes = Math.round((hour - wholeHour) * 60)
|
|
|
+ date.setHours(wholeHour, minutes, 0, 0)
|
|
|
+
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0')
|
|
|
+ const mins = String(date.getMinutes()).padStart(2, '0')
|
|
|
+ const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
+
|
|
|
+ return `${year}-${month}-${day} ${hours}:${mins}:${seconds}`
|
|
|
+}
|
|
|
+
|
|
|
+// 获取开始小时
|
|
|
+const getStartHour = (timeLabel) => {
|
|
|
+ if (isWithinOneHour.value) {
|
|
|
+ return withinOneHourSlot.value.startHour + withinOneHourSlot.value.startMinute / 60
|
|
|
+ }
|
|
|
+
|
|
|
+ const match = timeLabel.match(/(\d{2}):/)
|
|
|
+ return match ? parseInt(match[1]) : 0
|
|
|
+}
|
|
|
+
|
|
|
+// 获取结束小时
|
|
|
+const getEndHour = (timeLabel) => {
|
|
|
+ if (isWithinOneHour.value) {
|
|
|
+ return withinOneHourSlot.value.endHour + withinOneHourSlot.value.endMinute / 60
|
|
|
+ }
|
|
|
+
|
|
|
+ const match = timeLabel.match(/-(\d{2}):/)
|
|
|
+ return match ? parseInt(match[1]) : 0
|
|
|
+}
|
|
|
+
|
|
|
+// 确认选择
|
|
|
+const confirm = () => {
|
|
|
+ if (!selectedTime.value) return
|
|
|
+
|
|
|
+ const selectedDateObj = dateList.value.find(d => d.value === selectedDate.value)
|
|
|
+ const selectedTimeSlot = isWithinOneHour.value ? withinOneHourSlot.value :
|
|
|
+ baseTimeSlots.value.find(s => s.value === selectedTime.value)
|
|
|
+
|
|
|
+ if (!selectedDateObj || !selectedTimeSlot) return
|
|
|
+
|
|
|
+ // 计算开始和结束时间
|
|
|
+ const startHour = getStartHour(selectedTime.value)
|
|
|
+ const endHour = getEndHour(selectedTime.value)
|
|
|
+
|
|
|
+ // 计算具体的开始和结束时间字符串
|
|
|
+ const startTimeStr = formatDateTime(selectedDate.value, startHour)
|
|
|
+ const endTimeStr = formatDateTime(selectedDate.value, endHour)
|
|
|
+
|
|
|
+ // 生成显示文本
|
|
|
+ let displayText = ''
|
|
|
+ if (isWithinOneHour.value) {
|
|
|
+ displayText = `${selectedDateObj.label} 一小时内`
|
|
|
+ } else {
|
|
|
+ displayText = `${selectedDateObj.label} ${selectedTime.value}`
|
|
|
+ }
|
|
|
+
|
|
|
+ const timeData = {
|
|
|
+ date: selectedDate.value,
|
|
|
+ time: selectedTime.value,
|
|
|
+ dateLabel: selectedDateObj.label,
|
|
|
+ timeLabel: selectedTime.value,
|
|
|
+ fullLabel: displayText,
|
|
|
+ displayText: displayText,
|
|
|
+ startTime: startTimeStr,
|
|
|
+ endTime: endTimeStr,
|
|
|
+ isToday: selectedDateObj.isToday,
|
|
|
+ isImmediate: isWithinOneHour.value,
|
|
|
+ isNight: isNightTime(selectedTimeSlot.startHour),
|
|
|
+ startHour: startHour,
|
|
|
+ endHour: endHour
|
|
|
+ }
|
|
|
+
|
|
|
+ emit('confirm', timeData)
|
|
|
+ close()
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭弹框
|
|
|
+const close = () => {
|
|
|
+ emit('close')
|
|
|
+ emit('update:visible', false)
|
|
|
+}
|
|
|
+
|
|
|
+// 工具函数
|
|
|
+const formatDate = (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}`
|
|
|
+}
|
|
|
+
|
|
|
+const formatTime = (hour, minute) => {
|
|
|
+ return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
|
|
|
+}
|
|
|
+
|
|
|
+// 监听可见性变化
|
|
|
+watch(() => props.visible, (newVal) => {
|
|
|
+ if (newVal) {
|
|
|
+ currentTime.value = new Date()
|
|
|
+ initSelection()
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 定时更新当前时间
|
|
|
+let timer = null
|
|
|
+onMounted(() => {
|
|
|
+ timer = setInterval(() => {
|
|
|
+ if (props.visible) {
|
|
|
+ currentTime.value = new Date()
|
|
|
+ }
|
|
|
+ }, 60000) // 每分钟更新一次
|
|
|
+})
|
|
|
+
|
|
|
+// 组件销毁时清除定时器
|
|
|
+onUnmounted(() => {
|
|
|
+ if (timer) {
|
|
|
+ clearInterval(timer)
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+/* 弹窗内部容器 */
|
|
|
+.time-picker-container {
|
|
|
+ height: 80vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 32rpx 32rpx 24rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-title {
|
|
|
+ font-size: 36rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.close-btn {
|
|
|
+ font-size: 50rpx;
|
|
|
+ color: #999;
|
|
|
+ line-height: 40rpx;
|
|
|
+ padding: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 主要内容区域 */
|
|
|
+.main-content {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 左侧日期选择 */
|
|
|
+.date-sidebar {
|
|
|
+ width: 220rpx;
|
|
|
+ background-color: #f8f8f8;
|
|
|
+ border-right: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.date-item {
|
|
|
+ padding: 32rpx 24rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.date-item.active {
|
|
|
+ background-color: #fff;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.date-item.active::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 6rpx;
|
|
|
+ height: 40rpx;
|
|
|
+ background-color: #1B64F0;
|
|
|
+ border-radius: 0 4rpx 4rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.date-item.today .date-name {
|
|
|
+ color: #1B64F0;
|
|
|
+}
|
|
|
+
|
|
|
+.date-item.active .date-content {
|
|
|
+ color: #1B64F0;
|
|
|
+}
|
|
|
+
|
|
|
+.date-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.date-name {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.date-info {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.date-week {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧时间选择 */
|
|
|
+.time-content {
|
|
|
+ flex: 1;
|
|
|
+ padding: 0 32rpx;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-section {
|
|
|
+ margin-top: 32rpx;
|
|
|
+ padding-bottom: 24rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-desc {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #1B64F0;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-time {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 28rpx 24rpx;
|
|
|
+ // background: linear-gradient(135deg, #fff2f0 0%, #ffe6e6 100%);
|
|
|
+ border-radius: 16rpx;
|
|
|
+ border: 2rpx solid #1B64F0;
|
|
|
+}
|
|
|
+
|
|
|
+.time-value {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #1B64F0;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-time .time-tag {
|
|
|
+ font-size: 24rpx;
|
|
|
+ padding: 8rpx 16rpx;
|
|
|
+ background: #1B64F0;
|
|
|
+ color: white;
|
|
|
+ border-radius: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 时间段列表 */
|
|
|
+.time-section {
|
|
|
+ margin-top: 32rpx;
|
|
|
+ margin-bottom: 32rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.time-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item {
|
|
|
+ padding: 28rpx 24rpx;
|
|
|
+ border: 2rpx solid #e8e8e8;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item.active {
|
|
|
+ border-color: #1B64F0;
|
|
|
+ // background: #fff2f0;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item.night {
|
|
|
+ border-color: #4a5cff;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item:not(.disabled):active {
|
|
|
+ transform: scale(0.98);
|
|
|
+ opacity: 0.8;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item.disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+}
|
|
|
+
|
|
|
+.time-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item.active .time-text {
|
|
|
+ color: #1B64F0;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item.night .time-text {
|
|
|
+ color: #4a5cff;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item .time-tag {
|
|
|
+ font-size: 20rpx;
|
|
|
+ padding: 4rpx 12rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item.active .time-tag {
|
|
|
+ background: #1B64F0;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item.night .time-tag {
|
|
|
+ background: #4a5cff;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+/* 提示信息 */
|
|
|
+.notice-section {
|
|
|
+ padding: 24rpx;
|
|
|
+ margin-bottom: 32rpx;
|
|
|
+ background: #f8f8f8;
|
|
|
+ border-radius: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.notice-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #666;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+/* 操作按钮 */
|
|
|
+.action-bar {
|
|
|
+ padding: 24rpx 32rpx;
|
|
|
+ padding-top: 0;
|
|
|
+ background: #fff;
|
|
|
+ border-top: 1rpx solid #f0f0f0;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.confirm-btn {
|
|
|
+ width: 100%;
|
|
|
+ height: 88rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #999;
|
|
|
+ border-radius: 44rpx;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border: none;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.confirm-btn.active {
|
|
|
+ background: #1B64F0;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.confirm-btn.active:active {
|
|
|
+ opacity: 0.9;
|
|
|
+ transform: scale(0.99);
|
|
|
+}
|
|
|
+
|
|
|
+.loading {
|
|
|
+ text-align: center;
|
|
|
+ padding: 60rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+</style>
|