|
|
@@ -0,0 +1,769 @@
|
|
|
+<template>
|
|
|
+ <div class="pickup-time-cascader">
|
|
|
+ <el-cascader
|
|
|
+ ref="cascaderRef"
|
|
|
+ v-model="selectedValue"
|
|
|
+ :options="cascaderOptions"
|
|
|
+ :props="cascaderProps"
|
|
|
+ :show-all-levels="false"
|
|
|
+ :placeholder="placeholder"
|
|
|
+ :clearable="clearable"
|
|
|
+ :filterable="filterable"
|
|
|
+ :disabled="disabled"
|
|
|
+ :size="size"
|
|
|
+ @change="handleChange"
|
|
|
+ @visible-change="handleVisibleChange"
|
|
|
+ @expand-change="handleExpandChange"
|
|
|
+ >
|
|
|
+ <template #default="{ node, data }">
|
|
|
+ <div class="cascader-item" :class="{'is-date': !data.timeLabel, 'is-time': data.timeLabel}">
|
|
|
+ <span class="item-label">{{ data.label }}</span>
|
|
|
+ <span v-if="data.tag" class="item-tag" :class="data.tagClass">
|
|
|
+ {{ data.tag }}
|
|
|
+ </span>
|
|
|
+ <span v-if="data.recommend" class="item-recommend">推荐</span>
|
|
|
+ <span v-if="data.night" class="item-night">夜间</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #empty>
|
|
|
+ <div class="empty-content">
|
|
|
+ <span class="empty-text">无可用时间段</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-cascader>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ placeholder: {
|
|
|
+ type: String,
|
|
|
+ default: '请选择上门时间'
|
|
|
+ },
|
|
|
+ clearable: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ filterable: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ disabled: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ size: {
|
|
|
+ type: String,
|
|
|
+ default: 'large',
|
|
|
+ validator: (value) => ['large', 'default', 'small'].includes(value)
|
|
|
+ },
|
|
|
+ // 显示天数
|
|
|
+ days: {
|
|
|
+ type: Number,
|
|
|
+ default: 3
|
|
|
+ },
|
|
|
+ // 时间间隔(分钟)
|
|
|
+ timeInterval: {
|
|
|
+ type: Number,
|
|
|
+ default: 60
|
|
|
+ },
|
|
|
+ // 开始时间
|
|
|
+ startTime: {
|
|
|
+ type: Number,
|
|
|
+ default: 9
|
|
|
+ },
|
|
|
+ // 结束时间
|
|
|
+ endTime: {
|
|
|
+ type: Number,
|
|
|
+ default: 21
|
|
|
+ },
|
|
|
+ // 当前时间(用于测试)
|
|
|
+ currentTime: {
|
|
|
+ type: Date,
|
|
|
+ default: () => new Date()
|
|
|
+ },
|
|
|
+ // 是否自动选择推荐时间
|
|
|
+ autoSelect: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const emit = defineEmits(['update:modelValue', 'change', 'select', 'clear'])
|
|
|
+
|
|
|
+const selectedValue = ref([])
|
|
|
+const cascaderRef = ref(null)
|
|
|
+const currentTime = ref(new Date())
|
|
|
+const timer = ref(null)
|
|
|
+
|
|
|
+// 级联选择器配置
|
|
|
+const cascaderProps = {
|
|
|
+ expandTrigger: 'click',
|
|
|
+ multiple: false,
|
|
|
+ emitPath: true,
|
|
|
+ value: 'value',
|
|
|
+ label: 'label',
|
|
|
+ children: 'children',
|
|
|
+ disabled: 'disabled',
|
|
|
+ checkStrictly: false,
|
|
|
+ lazy: false
|
|
|
+}
|
|
|
+
|
|
|
+// 基础时间段配置
|
|
|
+const baseTimeSlots = computed(() => {
|
|
|
+ const slots = []
|
|
|
+ const startHour = props.startTime
|
|
|
+ const endHour = props.endTime
|
|
|
+ const interval = props.timeInterval / 60 // 转换为小时
|
|
|
+
|
|
|
+ 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`
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return slots
|
|
|
+})
|
|
|
+
|
|
|
+// 获取今天日期字符串
|
|
|
+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 year = date.getFullYear()
|
|
|
+ const month = date.getMonth() + 1
|
|
|
+ const day = date.getDate()
|
|
|
+ const week = days[date.getDay()]
|
|
|
+ const dateStr = formatDate(date)
|
|
|
+
|
|
|
+ let label = ''
|
|
|
+ if (i === 0) {
|
|
|
+ label = `今天 ${month}月${day}日 ${week}`
|
|
|
+ } else if (i === 1) {
|
|
|
+ label = `明天 ${month}月${day}日 ${week}`
|
|
|
+ } else if (i === 2) {
|
|
|
+ label = `后天 ${month}月${day}日 ${week}`
|
|
|
+ } else {
|
|
|
+ label = `${month}月${day}日 ${week}`
|
|
|
+ }
|
|
|
+
|
|
|
+ list.push({
|
|
|
+ value: dateStr,
|
|
|
+ label: label,
|
|
|
+ year: year,
|
|
|
+ month: month,
|
|
|
+ day: day,
|
|
|
+ week: week,
|
|
|
+ isToday: i === 0,
|
|
|
+ isTomorrow: i === 1,
|
|
|
+ isDayAfterTomorrow: i === 2,
|
|
|
+ fullLabel: `${month}月${day}日 ${week}`,
|
|
|
+ displayDate: `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return list
|
|
|
+})
|
|
|
+
|
|
|
+// 推荐时间段(今天的一小时内)
|
|
|
+const recommendTime = computed(() => {
|
|
|
+ if (!dateList.value.length || !dateList.value[0].isToday) return null
|
|
|
+
|
|
|
+ const now = currentTime.value
|
|
|
+ const currentHour = now.getHours()
|
|
|
+ const currentMinute = now.getMinutes()
|
|
|
+
|
|
|
+ // 找到下一个可用的时间段
|
|
|
+ for (const slot of baseTimeSlots.value) {
|
|
|
+ if (slot.start > currentHour ||
|
|
|
+ (slot.start === currentHour && currentMinute < 50)) {
|
|
|
+ // 如果当前时间在 startTime 到 endTime 之间
|
|
|
+ if (currentHour >= props.startTime && currentHour < props.endTime) {
|
|
|
+ // 计算推荐时间段(当前时间+1小时)
|
|
|
+ let startHour = currentHour
|
|
|
+ let startMinute = Math.ceil(currentMinute / 10) * 10
|
|
|
+ if (startMinute >= 60) {
|
|
|
+ startHour += 1
|
|
|
+ startMinute = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ const endHour = startHour + 1
|
|
|
+ const recommendation = `${formatTime(startHour, startMinute)}-${endHour.toString().padStart(2, '0')}:00`
|
|
|
+
|
|
|
+ // 检查推荐时间是否在可用时间段内
|
|
|
+ if (isTimeSlotAvailable(recommendation)) {
|
|
|
+ return recommendation
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return slot.label
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null
|
|
|
+})
|
|
|
+
|
|
|
+// 检查时间段是否可用
|
|
|
+const isTimeSlotAvailable = (timeLabel) => {
|
|
|
+ const timeMatch = timeLabel.match(/(\d{2}):(\d{2})-(\d{2}):(\d{2})/)
|
|
|
+ if (!timeMatch) return false
|
|
|
+
|
|
|
+ const startHour = parseInt(timeMatch[1])
|
|
|
+ const startMinute = parseInt(timeMatch[2])
|
|
|
+
|
|
|
+ // 检查是否在允许的时间范围内
|
|
|
+ if (startHour < props.startTime || startHour >= props.endTime) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+// 生成级联选择器选项
|
|
|
+const cascaderOptions = computed(() => {
|
|
|
+ const options = []
|
|
|
+
|
|
|
+ dateList.value.forEach(date => {
|
|
|
+ const dateOption = {
|
|
|
+ value: date.value,
|
|
|
+ label: date.label,
|
|
|
+ date: date.value,
|
|
|
+ dateLabel: date.label,
|
|
|
+ isToday: date.isToday,
|
|
|
+ children: [],
|
|
|
+ disabled: false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成时间选项
|
|
|
+ const isToday = date.isToday
|
|
|
+ const now = currentTime.value
|
|
|
+
|
|
|
+ // 今天的时间段需要根据当前时间过滤
|
|
|
+ if (isToday) {
|
|
|
+ const currentHour = now.getHours()
|
|
|
+ const currentMinute = now.getMinutes()
|
|
|
+
|
|
|
+ // 添加推荐时间段(如果有)
|
|
|
+ if (recommendTime.value && props.autoSelect) {
|
|
|
+ dateOption.children.push({
|
|
|
+ value: recommendTime.value,
|
|
|
+ label: recommendTime.value,
|
|
|
+ timeLabel: recommendTime.value,
|
|
|
+ recommend: true,
|
|
|
+ tag: '推荐',
|
|
|
+ tagClass: 'tag-recommend',
|
|
|
+ disabled: false,
|
|
|
+ soon: false,
|
|
|
+ night: isNightTime(recommendTime.value),
|
|
|
+ startHour: getStartHour(recommendTime.value),
|
|
|
+ endHour: getEndHour(recommendTime.value)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加其他时间段
|
|
|
+ baseTimeSlots.value.forEach(slot => {
|
|
|
+ const isDisabled = slot.end <= currentHour ||
|
|
|
+ (slot.start === currentHour && currentMinute > 50)
|
|
|
+ const isSoon = slot.start - currentHour <= 2 && slot.start > currentHour
|
|
|
+ const isRecommend = slot.label === recommendTime.value
|
|
|
+
|
|
|
+ if (!isDisabled || isRecommend) {
|
|
|
+ dateOption.children.push({
|
|
|
+ value: slot.label,
|
|
|
+ label: slot.label,
|
|
|
+ timeLabel: slot.label,
|
|
|
+ recommend: isRecommend,
|
|
|
+ tag: isSoon ? '约满' : (isRecommend ? '推荐' : ''),
|
|
|
+ tagClass: isSoon ? 'tag-soon' : (isRecommend ? 'tag-recommend' : ''),
|
|
|
+ disabled: isDisabled && !isRecommend,
|
|
|
+ soon: isSoon,
|
|
|
+ night: isNightTime(slot.label),
|
|
|
+ startHour: slot.start,
|
|
|
+ endHour: slot.end
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 明天及以后显示所有时间段
|
|
|
+ baseTimeSlots.value.forEach(slot => {
|
|
|
+ dateOption.children.push({
|
|
|
+ value: slot.label,
|
|
|
+ label: slot.label,
|
|
|
+ timeLabel: slot.label,
|
|
|
+ recommend: false,
|
|
|
+ tag: '',
|
|
|
+ tagClass: '',
|
|
|
+ disabled: false,
|
|
|
+ soon: false,
|
|
|
+ night: isNightTime(slot.label),
|
|
|
+ startHour: slot.start,
|
|
|
+ endHour: slot.end
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有可用的时间段,则禁用该日期
|
|
|
+ if (dateOption.children.length === 0) {
|
|
|
+ dateOption.disabled = true
|
|
|
+ dateOption.label = `${dateOption.label} (无可选时间)`
|
|
|
+ }
|
|
|
+
|
|
|
+ options.push(dateOption)
|
|
|
+ })
|
|
|
+
|
|
|
+ return options
|
|
|
+})
|
|
|
+
|
|
|
+// 是否是夜间时间段
|
|
|
+const isNightTime = (timeLabel) => {
|
|
|
+ const match = timeLabel.match(/^(\d{2})/)
|
|
|
+ if (match) {
|
|
|
+ const hour = parseInt(match[1])
|
|
|
+ return hour >= 18
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+// 获取开始小时
|
|
|
+const getStartHour = (timeLabel) => {
|
|
|
+ const match = timeLabel.match(/^(\d{2})/)
|
|
|
+ return match ? parseInt(match[1]) : 0
|
|
|
+}
|
|
|
+
|
|
|
+// 获取结束小时
|
|
|
+const getEndHour = (timeLabel) => {
|
|
|
+ const match = timeLabel.match(/-(\d{2})/)
|
|
|
+ return match ? parseInt(match[1]) : 0
|
|
|
+}
|
|
|
+
|
|
|
+// 处理选择变化
|
|
|
+const handleChange = (value) => {
|
|
|
+ if (!value || value.length === 0) {
|
|
|
+ emit('update:modelValue', [])
|
|
|
+ emit('change', null)
|
|
|
+ emit('clear')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const [date, time] = value
|
|
|
+ const selectedDate = dateList.value.find(d => d.value === date)
|
|
|
+ const selectedTime = findTimeOption(date, time)
|
|
|
+
|
|
|
+ if (!selectedDate || !selectedTime) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const timeData = {
|
|
|
+ date: date,
|
|
|
+ time: time,
|
|
|
+ dateLabel: selectedDate.label,
|
|
|
+ timeLabel: time,
|
|
|
+ fullLabel: formatDisplayText(selectedDate.label, time, selectedDate.isToday),
|
|
|
+ timestamp: new Date(date).setHours(selectedTime.startHour || 0),
|
|
|
+ isToday: selectedDate.isToday,
|
|
|
+ isImmediate: time === recommendTime.value,
|
|
|
+ isNight: selectedTime.night || false,
|
|
|
+ isSoon: selectedTime.soon || false,
|
|
|
+ displayText: formatDisplayText(selectedDate.label, time, selectedDate.isToday),
|
|
|
+ startHour: selectedTime.startHour,
|
|
|
+ endHour: selectedTime.endHour
|
|
|
+ }
|
|
|
+
|
|
|
+ emit('update:modelValue', value)
|
|
|
+ emit('change', timeData)
|
|
|
+ emit('select', timeData)
|
|
|
+}
|
|
|
+
|
|
|
+// 查找时间选项
|
|
|
+const findTimeOption = (date, time) => {
|
|
|
+ const dateOption = cascaderOptions.value.find(opt => opt.value === date)
|
|
|
+ if (!dateOption || !dateOption.children) return null
|
|
|
+ return dateOption.children.find(child => child.value === time)
|
|
|
+}
|
|
|
+
|
|
|
+// 处理弹窗显示/隐藏
|
|
|
+const handleVisibleChange = (visible) => {
|
|
|
+ if (visible) {
|
|
|
+ // 更新当前时间
|
|
|
+ currentTime.value = new Date()
|
|
|
+
|
|
|
+ // 如果没有选中值,自动选择推荐或第一个可用选项
|
|
|
+ if (selectedValue.value.length === 0 && props.autoSelect) {
|
|
|
+ nextTick(() => {
|
|
|
+ const defaultOption = getDefaultOption()
|
|
|
+ if (defaultOption) {
|
|
|
+ selectedValue.value = defaultOption
|
|
|
+ handleChange(defaultOption)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理菜单展开
|
|
|
+const handleExpandChange = (activeLabels) => {
|
|
|
+ // 可以在这里添加菜单展开时的逻辑
|
|
|
+}
|
|
|
+
|
|
|
+// 获取默认选项
|
|
|
+const getDefaultOption = () => {
|
|
|
+ if (!cascaderOptions.value.length) return null
|
|
|
+
|
|
|
+ // 首先尝试今天的推荐时间段
|
|
|
+ const todayOption = cascaderOptions.value[0]
|
|
|
+ if (todayOption && todayOption.children && todayOption.children.length > 0) {
|
|
|
+ // 优先选择推荐时间段
|
|
|
+ const recommendOption = todayOption.children.find(child => child.recommend && !child.disabled)
|
|
|
+ if (recommendOption) {
|
|
|
+ return [todayOption.value, recommendOption.value]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 否则选择第一个可用时间段
|
|
|
+ const firstAvailable = todayOption.children.find(child => !child.disabled)
|
|
|
+ if (firstAvailable) {
|
|
|
+ return [todayOption.value, firstAvailable.value]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试其他日期的第一个可用时间段
|
|
|
+ for (let i = 1; i < cascaderOptions.value.length; i++) {
|
|
|
+ const option = cascaderOptions.value[i]
|
|
|
+ if (option.children && option.children.length > 0) {
|
|
|
+ const firstAvailable = option.children.find(child => !child.disabled)
|
|
|
+ if (firstAvailable) {
|
|
|
+ return [option.value, firstAvailable.value]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化显示文本
|
|
|
+const formatDisplayText = (dateLabel, timeLabel, isToday) => {
|
|
|
+ if (isToday && timeLabel === recommendTime.value) {
|
|
|
+ return '一小时内'
|
|
|
+ }
|
|
|
+ return `${dateLabel} ${timeLabel}`
|
|
|
+}
|
|
|
+
|
|
|
+// 工具函数
|
|
|
+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.modelValue, (newVal) => {
|
|
|
+ if (JSON.stringify(newVal) !== JSON.stringify(selectedValue.value)) {
|
|
|
+ selectedValue.value = newVal || []
|
|
|
+ }
|
|
|
+}, { immediate: true })
|
|
|
+
|
|
|
+// 监听当前时间变化(每分钟更新一次)
|
|
|
+watch(currentTime, () => {
|
|
|
+ // 如果今天的时间选项已经过期,需要重新计算
|
|
|
+ const todayOption = cascaderOptions.value[0]
|
|
|
+ if (todayOption && todayOption.isToday) {
|
|
|
+ // 这里可以添加逻辑来处理时间过期的选项
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 初始化
|
|
|
+onMounted(() => {
|
|
|
+ // 定时更新当前时间(每分钟更新一次)
|
|
|
+ timer.value = setInterval(() => {
|
|
|
+ if (cascaderRef.value && cascaderRef.value.dropDownVisible) {
|
|
|
+ currentTime.value = new Date()
|
|
|
+ }
|
|
|
+ }, 60000) // 每分钟更新一次
|
|
|
+})
|
|
|
+
|
|
|
+// 组件卸载时清除定时器
|
|
|
+onUnmounted(() => {
|
|
|
+ if (timer.value) {
|
|
|
+ clearInterval(timer.value)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 暴露方法
|
|
|
+defineExpose({
|
|
|
+ getSelectedTimeData: () => {
|
|
|
+ if (selectedValue.value.length !== 2) return null
|
|
|
+ const [date, time] = selectedValue.value
|
|
|
+ const selectedDate = dateList.value.find(d => d.value === date)
|
|
|
+ const selectedTime = findTimeOption(date, time)
|
|
|
+
|
|
|
+ return {
|
|
|
+ date: date,
|
|
|
+ time: time,
|
|
|
+ dateLabel: selectedDate?.label,
|
|
|
+ timeLabel: time,
|
|
|
+ fullLabel: formatDisplayText(selectedDate?.label, time, selectedDate?.isToday),
|
|
|
+ timestamp: new Date(date).setHours(selectedTime?.startHour || 0),
|
|
|
+ isToday: selectedDate?.isToday,
|
|
|
+ isImmediate: time === recommendTime.value,
|
|
|
+ isNight: selectedTime?.night || false,
|
|
|
+ isSoon: selectedTime?.soon || false,
|
|
|
+ startHour: selectedTime?.startHour,
|
|
|
+ endHour: selectedTime?.endHour
|
|
|
+ }
|
|
|
+ },
|
|
|
+ clearSelection: () => {
|
|
|
+ selectedValue.value = []
|
|
|
+ emit('update:modelValue', [])
|
|
|
+ emit('change', null)
|
|
|
+ emit('clear')
|
|
|
+ },
|
|
|
+ refreshTime: () => {
|
|
|
+ currentTime.value = new Date()
|
|
|
+ },
|
|
|
+ setSelectedTime: (date, time) => {
|
|
|
+ const dateExists = dateList.value.some(d => d.value === date)
|
|
|
+ if (dateExists) {
|
|
|
+ selectedValue.value = [date, time]
|
|
|
+ handleChange([date, time])
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.pickup-time-cascader {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ :deep(.el-cascader) {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .el-input {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .el-input__wrapper {
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ &.is-focus {
|
|
|
+ box-shadow: 0 0 0 1px var(--el-color-primary) inset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-cascader__dropdown) {
|
|
|
+ max-height: 400px;
|
|
|
+ overflow-y: auto;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+
|
|
|
+ .el-cascader-panel {
|
|
|
+ border: none;
|
|
|
+
|
|
|
+ .el-cascader-menu {
|
|
|
+ min-width: 200px;
|
|
|
+ max-height: 400px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ &:first-child {
|
|
|
+ min-width: 220px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-cascader-node {
|
|
|
+ height: auto;
|
|
|
+ min-height: 40px;
|
|
|
+ padding: 8px 12px;
|
|
|
+
|
|
|
+ &:hover:not(.is-disabled) {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-selectable {
|
|
|
+ &.in-active-path,
|
|
|
+ &.is-active {
|
|
|
+ background-color: #f0f7ff;
|
|
|
+
|
|
|
+ .cascader-item {
|
|
|
+ .item-label {
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-disabled {
|
|
|
+ .cascader-item {
|
|
|
+ .item-label {
|
|
|
+ color: #c0c4cc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .cascader-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 100%;
|
|
|
+ min-height: 32px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ &.is-date {
|
|
|
+ .item-label {
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-today {
|
|
|
+ .item-label {
|
|
|
+ color: #f2270c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-time {
|
|
|
+ .item-label {
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-label {
|
|
|
+ flex: 1;
|
|
|
+ text-align: left;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-tag {
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 10px;
|
|
|
+ margin-left: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ &.tag-recommend {
|
|
|
+ background-color: #fef2f0;
|
|
|
+ color: #f2270c;
|
|
|
+ border: 1px solid #f2270c;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.tag-soon {
|
|
|
+ background-color: #fff7e6;
|
|
|
+ color: #ff9900;
|
|
|
+ border: 1px solid #ff9900;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.tag-night {
|
|
|
+ background-color: #f0f5ff;
|
|
|
+ color: #4a5cff;
|
|
|
+ border: 1px solid #4a5cff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-recommend {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #f2270c;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-left: 8px;
|
|
|
+ padding: 2px 6px;
|
|
|
+ background-color: #fef2f0;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-night {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #4a5cff;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-left: 8px;
|
|
|
+ padding: 2px 6px;
|
|
|
+ background-color: #f0f5ff;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-content {
|
|
|
+ padding: 20px;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .empty-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 夜间时间段特殊样式
|
|
|
+.night-time-option {
|
|
|
+ :deep(.el-cascader-node) {
|
|
|
+ .cascader-item {
|
|
|
+ .item-label {
|
|
|
+ color: #4a5cff !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-active {
|
|
|
+ .cascader-item {
|
|
|
+ .item-label {
|
|
|
+ color: #4a5cff !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 推荐时间段特殊样式
|
|
|
+.recommend-time-option {
|
|
|
+ :deep(.el-cascader-node) {
|
|
|
+ .cascader-item {
|
|
|
+ .item-label {
|
|
|
+ color: #f2270c !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-active {
|
|
|
+ .cascader-item {
|
|
|
+ .item-label {
|
|
|
+ color: #f2270c !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|