d738ca5dd8
CI / docker (push) Failing after 1h50m32s
Co-authored-by: Cursor <cursoragent@cursor.com>
819 lines
20 KiB
Vue
819 lines
20 KiB
Vue
<template>
|
||
<div class="datetime-picker-wrapper">
|
||
<!-- 自定义日期时间选择器面板 -->
|
||
<Transition name="picker">
|
||
<div v-if="show" class="datetime-picker-panel" @click.stop>
|
||
<div class="picker-container">
|
||
<!-- 左侧日期选择区域 -->
|
||
<div class="date-picker-section">
|
||
<!-- 年月导航 -->
|
||
<div class="date-header">
|
||
<div class="nav-buttons">
|
||
<button @click="prevYear" class="nav-btn" title="上一年">«</button>
|
||
<button @click="prevMonth" class="nav-btn" title="上一月">‹</button>
|
||
</div>
|
||
<div
|
||
v-if="!isEditingMonthYear"
|
||
@click="startEditingMonthYear"
|
||
class="current-month-year editable"
|
||
title="点击输入年月"
|
||
>
|
||
{{ currentViewDate.getFullYear() }} - {{ String(currentViewDate.getMonth() + 1).padStart(2, '0') }}
|
||
</div>
|
||
<input
|
||
v-else
|
||
v-model="monthYearInput"
|
||
@blur="confirmMonthYear"
|
||
@keyup.enter="confirmMonthYear"
|
||
@keyup.esc="cancelEditingMonthYear"
|
||
class="month-year-input"
|
||
placeholder="YYYY-MM"
|
||
ref="monthYearInputRef"
|
||
/>
|
||
<div class="nav-buttons">
|
||
<button @click="nextMonth" class="nav-btn" title="下一月">›</button>
|
||
<button @click="nextYear" class="nav-btn" title="下一年">»</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 星期标题 -->
|
||
<div class="weekdays">
|
||
<div class="weekday" v-for="day in ['日', '一', '二', '三', '四', '五', '六']" :key="day">
|
||
{{ day }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 日期网格 -->
|
||
<div class="calendar-grid">
|
||
<div
|
||
v-for="(day, index) in getCalendarDays()"
|
||
:key="index"
|
||
@click="selectDate(day)"
|
||
:class="[
|
||
'calendar-day',
|
||
{ 'other-month': !day.isCurrentMonth },
|
||
{ 'today': isToday(day) },
|
||
{ 'selected': isSelected(day) }
|
||
]"
|
||
>
|
||
{{ day.date.getDate() }}
|
||
<span v-if="isToday(day) && !isSelected(day)" class="today-dot"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 此刻按钮 -->
|
||
<button @click="selectNow" class="now-btn">此刻</button>
|
||
</div>
|
||
|
||
<!-- 右侧时间选择区域 -->
|
||
<div class="time-picker-section">
|
||
<div class="time-header">选择时间</div>
|
||
|
||
<div class="time-selectors">
|
||
<!-- 小时选择 -->
|
||
<div class="time-column">
|
||
<div class="time-list" ref="hourListRef">
|
||
<div
|
||
v-for="hour in generateTimeOptions('hour')"
|
||
:key="hour"
|
||
:data-value="hour"
|
||
@click="selectTime('hour', hour)"
|
||
:class="['time-item', { 'selected': selectedTime.hour === parseInt(hour) }]"
|
||
>
|
||
{{ hour }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分钟选择 -->
|
||
<div class="time-column">
|
||
<div class="time-list" ref="minuteListRef">
|
||
<div
|
||
v-for="minute in generateTimeOptions('minute')"
|
||
:key="minute"
|
||
:data-value="minute"
|
||
@click="selectTime('minute', minute)"
|
||
:class="['time-item', { 'selected': selectedTime.minute === parseInt(minute) }]"
|
||
>
|
||
{{ minute }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 秒选择 -->
|
||
<div class="time-column">
|
||
<div class="time-list" ref="secondListRef">
|
||
<div
|
||
v-for="second in generateTimeOptions('second')"
|
||
:key="second"
|
||
:data-value="second"
|
||
@click="selectTime('second', second)"
|
||
:class="['time-item', { 'selected': selectedTime.second === parseInt(second) }]"
|
||
>
|
||
{{ second }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 确定按钮 -->
|
||
<button @click="confirmSelection" class="confirm-btn">确定</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
|
||
<!-- 遮罩层 -->
|
||
<Transition name="mask">
|
||
<div v-if="show" class="picker-mask" @click="close"></div>
|
||
</Transition>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch, nextTick } from 'vue'
|
||
|
||
const props = defineProps({
|
||
modelValue: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
precisionType: {
|
||
type: String,
|
||
default: 'milliseconds', // seconds, milliseconds, nanoseconds
|
||
validator: (value) => ['seconds', 'milliseconds', 'nanoseconds'].includes(value)
|
||
},
|
||
show: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['update:modelValue', 'update:show', 'confirm'])
|
||
|
||
// 日期时间选择器状态
|
||
const currentViewDate = ref(new Date()) // 当前查看的年月
|
||
const selectedDate = ref(null) // 选中的日期
|
||
const selectedTime = ref({ hour: 0, minute: 0, second: 0 }) // 选中的时间
|
||
const hourListRef = ref(null)
|
||
const minuteListRef = ref(null)
|
||
const secondListRef = ref(null)
|
||
const isEditingMonthYear = ref(false) // 是否正在编辑年月
|
||
const monthYearInput = ref('') // 年月输入值
|
||
const monthYearInputRef = ref(null) // 年月输入框引用
|
||
|
||
// 日期选择功能
|
||
const getCalendarDays = () => {
|
||
const year = currentViewDate.value.getFullYear()
|
||
const month = currentViewDate.value.getMonth()
|
||
|
||
// 获取当月第一天和最后一天
|
||
const firstDay = new Date(year, month, 1)
|
||
const lastDay = new Date(year, month + 1, 0)
|
||
|
||
// 获取第一天是星期几(0=周日)
|
||
const firstDayWeek = firstDay.getDay()
|
||
|
||
// 获取上个月的最后几天
|
||
const prevMonthLastDay = new Date(year, month, 0).getDate()
|
||
|
||
const days = []
|
||
|
||
// 添加上个月的日期
|
||
for (let i = firstDayWeek - 1; i >= 0; i--) {
|
||
days.push({
|
||
date: new Date(year, month - 1, prevMonthLastDay - i),
|
||
isCurrentMonth: false
|
||
})
|
||
}
|
||
|
||
// 添加当月的日期
|
||
for (let i = 1; i <= lastDay.getDate(); i++) {
|
||
days.push({
|
||
date: new Date(year, month, i),
|
||
isCurrentMonth: true
|
||
})
|
||
}
|
||
|
||
// 添加下个月的日期,补齐到42个(6行7列)
|
||
const remainingDays = 42 - days.length
|
||
for (let i = 1; i <= remainingDays; i++) {
|
||
days.push({
|
||
date: new Date(year, month + 1, i),
|
||
isCurrentMonth: false
|
||
})
|
||
}
|
||
|
||
return days
|
||
}
|
||
|
||
const prevYear = () => {
|
||
const date = new Date(currentViewDate.value)
|
||
date.setFullYear(date.getFullYear() - 1)
|
||
currentViewDate.value = date
|
||
}
|
||
|
||
const nextYear = () => {
|
||
const date = new Date(currentViewDate.value)
|
||
date.setFullYear(date.getFullYear() + 1)
|
||
currentViewDate.value = date
|
||
}
|
||
|
||
const prevMonth = () => {
|
||
const date = new Date(currentViewDate.value)
|
||
date.setMonth(date.getMonth() - 1)
|
||
currentViewDate.value = date
|
||
}
|
||
|
||
const nextMonth = () => {
|
||
const date = new Date(currentViewDate.value)
|
||
date.setMonth(date.getMonth() + 1)
|
||
currentViewDate.value = date
|
||
}
|
||
|
||
const selectDate = (day) => {
|
||
selectedDate.value = new Date(day.date)
|
||
// 滚动到选中的时间项
|
||
nextTick(() => {
|
||
scrollToSelected()
|
||
})
|
||
}
|
||
|
||
const isToday = (day) => {
|
||
const today = new Date()
|
||
return day.date.getDate() === today.getDate() &&
|
||
day.date.getMonth() === today.getMonth() &&
|
||
day.date.getFullYear() === today.getFullYear()
|
||
}
|
||
|
||
const isSelected = (day) => {
|
||
if (!selectedDate.value) return false
|
||
return day.date.getDate() === selectedDate.value.getDate() &&
|
||
day.date.getMonth() === selectedDate.value.getMonth() &&
|
||
day.date.getFullYear() === selectedDate.value.getFullYear()
|
||
}
|
||
|
||
// 时间选择功能
|
||
const generateTimeOptions = (type) => {
|
||
if (type === 'hour') {
|
||
return Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0'))
|
||
} else {
|
||
return Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0'))
|
||
}
|
||
}
|
||
|
||
const selectTime = (type, value) => {
|
||
selectedTime.value[type] = parseInt(value)
|
||
// 滚动到选中的项
|
||
nextTick(() => {
|
||
scrollToSelected()
|
||
})
|
||
}
|
||
|
||
const scrollToSelected = () => {
|
||
// 滚动小时列表
|
||
if (hourListRef.value) {
|
||
const hourItem = hourListRef.value.querySelector(`[data-value="${String(selectedTime.value.hour).padStart(2, '0')}"]`)
|
||
if (hourItem) {
|
||
hourItem.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||
}
|
||
}
|
||
|
||
// 滚动分钟列表
|
||
if (minuteListRef.value) {
|
||
const minuteItem = minuteListRef.value.querySelector(`[data-value="${String(selectedTime.value.minute).padStart(2, '0')}"]`)
|
||
if (minuteItem) {
|
||
minuteItem.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||
}
|
||
}
|
||
|
||
// 滚动秒列表
|
||
if (secondListRef.value) {
|
||
const secondItem = secondListRef.value.querySelector(`[data-value="${String(selectedTime.value.second).padStart(2, '0')}"]`)
|
||
if (secondItem) {
|
||
secondItem.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||
}
|
||
}
|
||
}
|
||
|
||
// 面板控制功能
|
||
const close = () => {
|
||
emit('update:show', false)
|
||
isEditingMonthYear.value = false
|
||
monthYearInput.value = ''
|
||
}
|
||
|
||
const confirmSelection = () => {
|
||
if (!selectedDate.value) {
|
||
selectedDate.value = new Date()
|
||
}
|
||
|
||
const date = new Date(selectedDate.value)
|
||
date.setHours(selectedTime.value.hour)
|
||
date.setMinutes(selectedTime.value.minute)
|
||
date.setSeconds(selectedTime.value.second)
|
||
date.setMilliseconds(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 minutes = String(date.getMinutes()).padStart(2, '0')
|
||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||
const milliseconds = String(date.getMilliseconds()).padStart(3, '0')
|
||
|
||
// 根据精度类型设置格式
|
||
let formattedDate = ''
|
||
if (props.precisionType === 'seconds') {
|
||
formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||
} else if (props.precisionType === 'milliseconds') {
|
||
formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`
|
||
} else {
|
||
// 纳秒级
|
||
const nanosecondsStr = '000000'
|
||
formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}${nanosecondsStr}`
|
||
}
|
||
|
||
emit('update:modelValue', formattedDate)
|
||
emit('confirm', formattedDate)
|
||
close()
|
||
}
|
||
|
||
const selectNow = () => {
|
||
const now = new Date()
|
||
selectedDate.value = new Date(now)
|
||
currentViewDate.value = new Date(now)
|
||
selectedTime.value = {
|
||
hour: now.getHours(),
|
||
minute: now.getMinutes(),
|
||
second: now.getSeconds()
|
||
}
|
||
|
||
nextTick(() => {
|
||
scrollToSelected()
|
||
})
|
||
}
|
||
|
||
// 开始编辑年月
|
||
const startEditingMonthYear = () => {
|
||
isEditingMonthYear.value = true
|
||
const year = currentViewDate.value.getFullYear()
|
||
const month = String(currentViewDate.value.getMonth() + 1).padStart(2, '0')
|
||
monthYearInput.value = `${year}-${month}`
|
||
|
||
nextTick(() => {
|
||
if (monthYearInputRef.value) {
|
||
monthYearInputRef.value.focus()
|
||
monthYearInputRef.value.select()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 确认年月输入
|
||
const confirmMonthYear = () => {
|
||
const input = monthYearInput.value.trim()
|
||
|
||
// 验证格式:YYYY-MM 或 YYYY-M
|
||
const match = input.match(/^(\d{4})-(\d{1,2})$/)
|
||
|
||
if (match) {
|
||
const year = parseInt(match[1])
|
||
const month = parseInt(match[2])
|
||
|
||
// 验证年份和月份范围
|
||
if (year >= 1000 && year <= 9999 && month >= 1 && month <= 12) {
|
||
const date = new Date(year, month - 1, 1)
|
||
currentViewDate.value = date
|
||
isEditingMonthYear.value = false
|
||
monthYearInput.value = ''
|
||
} else {
|
||
// 这里可以添加错误提示,但组件中不依赖父组件的 showToast
|
||
if (monthYearInputRef.value) {
|
||
monthYearInputRef.value.focus()
|
||
monthYearInputRef.value.select()
|
||
}
|
||
}
|
||
} else {
|
||
if (monthYearInputRef.value) {
|
||
monthYearInputRef.value.focus()
|
||
monthYearInputRef.value.select()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 取消编辑年月
|
||
const cancelEditingMonthYear = () => {
|
||
isEditingMonthYear.value = false
|
||
monthYearInput.value = ''
|
||
}
|
||
|
||
// 监听 show 变化,初始化选择器
|
||
watch(() => props.show, (newVal) => {
|
||
if (newVal) {
|
||
// 如果输入框有值,尝试解析并设置到选择器
|
||
if (props.modelValue) {
|
||
try {
|
||
const dateStr = props.modelValue.trim()
|
||
let date = null
|
||
if (dateStr.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d{1,3})?$/)) {
|
||
date = new Date(dateStr.replace(' ', 'T'))
|
||
} else if (dateStr.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?/)) {
|
||
date = new Date(dateStr)
|
||
} else {
|
||
date = new Date(dateStr)
|
||
}
|
||
|
||
if (!isNaN(date.getTime())) {
|
||
selectedDate.value = new Date(date)
|
||
currentViewDate.value = new Date(date)
|
||
selectedTime.value = {
|
||
hour: date.getHours(),
|
||
minute: date.getMinutes(),
|
||
second: date.getSeconds()
|
||
}
|
||
}
|
||
} catch (error) {
|
||
// 如果解析失败,使用当前时间
|
||
const now = new Date()
|
||
selectedDate.value = new Date(now)
|
||
currentViewDate.value = new Date(now)
|
||
selectedTime.value = {
|
||
hour: now.getHours(),
|
||
minute: now.getMinutes(),
|
||
second: now.getSeconds()
|
||
}
|
||
}
|
||
} else {
|
||
// 如果输入框为空,使用当前时间
|
||
const now = new Date()
|
||
selectedDate.value = new Date(now)
|
||
currentViewDate.value = new Date(now)
|
||
selectedTime.value = {
|
||
hour: now.getHours(),
|
||
minute: now.getMinutes(),
|
||
second: now.getSeconds()
|
||
}
|
||
}
|
||
|
||
// 等待DOM更新后滚动到选中项
|
||
nextTick(() => {
|
||
scrollToSelected()
|
||
})
|
||
} else {
|
||
isEditingMonthYear.value = false
|
||
monthYearInput.value = ''
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.datetime-picker-wrapper {
|
||
position: absolute;
|
||
top: calc(100% + 0.5rem);
|
||
left: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 日期时间选择器面板 */
|
||
.picker-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
z-index: 998;
|
||
}
|
||
|
||
.datetime-picker-panel {
|
||
position: relative;
|
||
z-index: 999;
|
||
width: 500px;
|
||
min-width: 500px;
|
||
background: #ffffff;
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||
border: 1px solid #e5e5e5;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.picker-container {
|
||
display: flex;
|
||
min-height: 320px;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 左侧日期选择区域 */
|
||
.date-picker-section {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 0.75rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
border-right: 1px solid #e5e5e5;
|
||
}
|
||
|
||
.date-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.nav-buttons {
|
||
display: flex;
|
||
gap: 0.125rem;
|
||
}
|
||
|
||
.nav-btn {
|
||
padding: 0.125rem 0.375rem;
|
||
background: transparent;
|
||
border: 1px solid #d0d0d0;
|
||
border-radius: 3px;
|
||
color: #333333;
|
||
cursor: pointer;
|
||
font-size: 0.75rem;
|
||
transition: all 0.2s;
|
||
min-width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.nav-btn:hover {
|
||
background: #f5f5f5;
|
||
border-color: #1a1a1a;
|
||
}
|
||
|
||
.current-month-year {
|
||
font-size: 0.8125rem;
|
||
font-weight: 500;
|
||
color: #333333;
|
||
}
|
||
|
||
.current-month-year.editable {
|
||
cursor: pointer;
|
||
padding: 0.125rem 0.25rem;
|
||
border-radius: 3px;
|
||
transition: all 0.2s;
|
||
user-select: none;
|
||
}
|
||
|
||
.current-month-year.editable:hover {
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.month-year-input {
|
||
font-size: 0.8125rem;
|
||
font-weight: 500;
|
||
color: #333333;
|
||
border: 1px solid #1a1a1a;
|
||
border-radius: 3px;
|
||
padding: 0.125rem 0.25rem;
|
||
text-align: center;
|
||
width: 80px;
|
||
outline: none;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.month-year-input:focus {
|
||
border-color: #1a1a1a;
|
||
box-shadow: 0 0 0 2px rgba(26, 26, 26, 0.1);
|
||
}
|
||
|
||
.weekdays {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 0;
|
||
margin-bottom: 0.375rem;
|
||
}
|
||
|
||
.weekday {
|
||
width: 28px;
|
||
text-align: center;
|
||
font-size: 0.6875rem;
|
||
color: #666666;
|
||
font-weight: 500;
|
||
padding: 0.25rem 0;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.calendar-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.calendar-day {
|
||
width: 28px;
|
||
height: 28px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.75rem;
|
||
color: #333333;
|
||
cursor: pointer;
|
||
border-radius: 3px;
|
||
transition: all 0.2s;
|
||
position: relative;
|
||
padding: 0;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.calendar-day:hover {
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.calendar-day.other-month {
|
||
color: #999999;
|
||
}
|
||
|
||
.calendar-day.today {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.calendar-day.selected {
|
||
background: #1a1a1a;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.today-dot {
|
||
position: absolute;
|
||
bottom: 4px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 4px;
|
||
height: 4px;
|
||
background: #1a1a1a;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.calendar-day.selected .today-dot {
|
||
background: #ffffff;
|
||
}
|
||
|
||
.now-btn {
|
||
margin-top: 0.5rem;
|
||
padding: 0.375rem 0.75rem;
|
||
background: #ffffff;
|
||
border: 1px solid #d0d0d0;
|
||
border-radius: 4px;
|
||
color: #666666;
|
||
font-size: 0.75rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
align-self: flex-start;
|
||
}
|
||
|
||
.now-btn:hover {
|
||
background: #f5f5f5;
|
||
border-color: #1a1a1a;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
/* 右侧时间选择区域 */
|
||
.time-picker-section {
|
||
flex: 0 0 160px;
|
||
min-width: 160px;
|
||
padding: 0.75rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #fafafa;
|
||
}
|
||
|
||
.time-header {
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
color: #333333;
|
||
margin-bottom: 0.5rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.time-selectors {
|
||
display: flex;
|
||
gap: 0.25rem;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.time-column {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.time-list {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
scroll-behavior: smooth;
|
||
max-height: 240px;
|
||
scrollbar-width: none; /* Firefox */
|
||
-ms-overflow-style: none; /* IE and Edge */
|
||
}
|
||
|
||
.time-list::-webkit-scrollbar {
|
||
display: none; /* Chrome, Safari, Opera */
|
||
}
|
||
|
||
.time-item {
|
||
padding: 0.25rem 0.375rem;
|
||
text-align: center;
|
||
font-size: 0.75rem;
|
||
color: #333333;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
border-radius: 3px;
|
||
margin: 0.0625rem 0;
|
||
}
|
||
|
||
.time-item:hover {
|
||
background: #f0f0f0;
|
||
}
|
||
|
||
.time-item.selected {
|
||
background: #1a1a1a;
|
||
color: #ffffff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.confirm-btn {
|
||
margin-top: 0.5rem;
|
||
padding: 0.5rem 0.75rem;
|
||
background: #1a1a1a;
|
||
border: none;
|
||
border-radius: 4px;
|
||
color: #ffffff;
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
width: 100%;
|
||
}
|
||
|
||
.confirm-btn:hover {
|
||
background: #333333;
|
||
}
|
||
|
||
.confirm-btn:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
/* 过渡动画 */
|
||
.picker-enter-active,
|
||
.picker-leave-active {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.picker-enter-from {
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
}
|
||
|
||
.picker-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
}
|
||
|
||
.mask-enter-active,
|
||
.mask-leave-active {
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.mask-enter-from,
|
||
.mask-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.datetime-picker-panel {
|
||
width: calc(100vw - 2rem);
|
||
min-width: calc(100vw - 2rem);
|
||
max-width: calc(100vw - 2rem);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.picker-container {
|
||
flex-direction: column;
|
||
min-height: 280px;
|
||
}
|
||
|
||
.date-picker-section {
|
||
border-right: none;
|
||
border-bottom: 1px solid #e5e5e5;
|
||
}
|
||
|
||
.time-picker-section {
|
||
flex: 1;
|
||
min-width: 140px;
|
||
min-height: 250px;
|
||
}
|
||
|
||
.time-selectors {
|
||
max-height: 200px;
|
||
}
|
||
}
|
||
</style>
|