初次提交
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="relative bg-zinc-900/50 border border-zinc-800/50 rounded-2xl p-1 overflow-hidden">
|
||||
<div class="flex items-center" @touchstart.stop>
|
||||
<button
|
||||
:disabled="isFirstDateVisible"
|
||||
class="p-2 text-zinc-500 hover:text-zinc-300 disabled:opacity-30 transition-colors"
|
||||
@click="scrollDates('left')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="15,18 9,12 15,6" /></svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
ref="dateSelector"
|
||||
class="flex-1 flex overflow-x-auto scrollbar-hide gap-2 px-2 py-1 scroll-smooth overscroll-x-contain"
|
||||
style="overscroll-behavior-x: contain; touch-action: pan-x"
|
||||
@wheel="handleWheel"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<button
|
||||
v-for="date in availableDates"
|
||||
:key="date.value"
|
||||
:data-date="date.value"
|
||||
:class="[
|
||||
'flex flex-col items-center justify-center min-w-[64px] h-16 rounded-lg transition-all duration-200 border',
|
||||
modelValue === date.value
|
||||
? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20'
|
||||
: 'bg-zinc-900 border-zinc-800 text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300 hover:border-zinc-700'
|
||||
]"
|
||||
@click="selectDate(date.value)"
|
||||
>
|
||||
<span class="text-[10px] font-bold uppercase tracking-wider opacity-80">{{ date.weekday }}</span>
|
||||
<span class="text-lg font-black leading-none my-0.5">{{ date.day }}</span>
|
||||
<span class="text-[9px] font-bold opacity-60">{{ date.month }}月</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
:disabled="isLastDateVisible"
|
||||
class="p-2 text-zinc-500 hover:text-zinc-300 disabled:opacity-30 transition-colors"
|
||||
@click="scrollDates('right')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="9,18 15,12 9,6" /></svg>
|
||||
</button>
|
||||
|
||||
<!-- 操作按钮组 -->
|
||||
<div class="flex items-center border-l border-zinc-800 ml-1 pl-1">
|
||||
<button class="p-2 text-zinc-500 hover:text-emerald-400 transition-colors" title="跳转到今天" @click="scrollToToday">
|
||||
<CircleDot class="w-5 h-5" />
|
||||
</button>
|
||||
<button class="p-2 text-zinc-500 hover:text-blue-400 transition-colors" title="选择特定日期" @click="openManualDatePicker">
|
||||
<CalendarIcon class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 手动日期选择弹窗 -->
|
||||
<div
|
||||
v-if="showManualDatePicker"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
||||
>
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl w-full max-w-sm overflow-hidden" @click.stop>
|
||||
<div class="flex items-center justify-between p-4 border-b border-zinc-800">
|
||||
<h3 class="text-sm font-black text-zinc-100 uppercase tracking-widest">选择日期</h3>
|
||||
<button class="text-zinc-500 hover:text-zinc-300 transition-colors" @click="showManualDatePicker = false">
|
||||
<CloseIcon class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 space-y-6">
|
||||
<input
|
||||
v-model="manualSelectedDate"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-3 text-zinc-200 focus:outline-none focus:border-blue-500 transition-colors"
|
||||
type="date"
|
||||
>
|
||||
<div class="flex gap-3">
|
||||
<button class="flex-1 py-3 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-xs font-bold rounded-xl transition-colors uppercase tracking-wider" @click="showManualDatePicker = false">取消</button>
|
||||
<button class="flex-1 py-3 bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-blue-900/20 transition-colors uppercase tracking-wider" @click="confirmManualDate">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { CircleDot, Calendar as CalendarIcon, X as CloseIcon } from '@lucide/vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const dateSelector = ref(null)
|
||||
const isFirstDateVisible = ref(false)
|
||||
const isLastDateVisible = ref(false)
|
||||
const dateRange = ref({ start: -15, end: 15 })
|
||||
const showManualDatePicker = ref(false)
|
||||
const manualSelectedDate = ref('')
|
||||
|
||||
const availableDates = computed(() => {
|
||||
const dates = []
|
||||
const today = new Date()
|
||||
for (let i = dateRange.value.start; i <= dateRange.value.end; i++) {
|
||||
const date = new Date(today)
|
||||
date.setDate(today.getDate() + i)
|
||||
const dateStr = date.toISOString().split('T')[0]
|
||||
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
||||
dates.push({
|
||||
value: dateStr,
|
||||
day: date.getDate().toString().padStart(2, '0'),
|
||||
month: (date.getMonth() + 1).toString().padStart(2, '0'),
|
||||
weekday: weekdays[date.getDay()],
|
||||
isToday: i === 0
|
||||
})
|
||||
}
|
||||
return dates
|
||||
})
|
||||
|
||||
const selectDate = (dateValue) => {
|
||||
emit('update:modelValue', dateValue)
|
||||
emit('change', dateValue)
|
||||
}
|
||||
|
||||
const scrollToDateElement = (behavior = 'smooth') => {
|
||||
if (!dateSelector.value || !props.modelValue) return
|
||||
const el = dateSelector.value.querySelector(`[data-date="${props.modelValue}"]`)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior, block: 'nearest', inline: 'center' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleWheel = (event) => {
|
||||
event.preventDefault()
|
||||
if (!dateSelector.value) return
|
||||
const scrollAmount = 200
|
||||
const currentScroll = dateSelector.value.scrollLeft
|
||||
dateSelector.value.scrollTo({
|
||||
left: event.deltaY > 0 ? currentScroll + scrollAmount : currentScroll - scrollAmount,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
|
||||
const scrollDates = (direction) => {
|
||||
if (!dateSelector.value) return
|
||||
const scrollAmount = 200
|
||||
const currentScroll = dateSelector.value.scrollLeft
|
||||
dateSelector.value.scrollTo({
|
||||
left: direction === 'right' ? currentScroll + scrollAmount : currentScroll - scrollAmount,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!dateSelector.value) return
|
||||
const { scrollLeft, scrollWidth, clientWidth } = dateSelector.value
|
||||
if (scrollLeft < 50) {
|
||||
const oldScrollWidth = scrollWidth
|
||||
dateRange.value.start -= 7
|
||||
nextTick(() => {
|
||||
const newScrollWidth = dateSelector.value.scrollWidth
|
||||
dateSelector.value.scrollLeft += newScrollWidth - oldScrollWidth
|
||||
})
|
||||
}
|
||||
if (scrollWidth - scrollLeft - clientWidth < 50) {
|
||||
dateRange.value.end += 7
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToToday = () => {
|
||||
const todayStr = new Date().toISOString().split('T')[0]
|
||||
if (props.modelValue !== todayStr) {
|
||||
selectDate(todayStr)
|
||||
}
|
||||
if (dateRange.value.start > 0 || dateRange.value.end < 0) {
|
||||
dateRange.value.start = -15
|
||||
dateRange.value.end = 15
|
||||
}
|
||||
nextTick(() => scrollToDateElement('smooth'))
|
||||
}
|
||||
|
||||
const openManualDatePicker = () => {
|
||||
manualSelectedDate.value = props.modelValue
|
||||
showManualDatePicker.value = true
|
||||
}
|
||||
|
||||
const confirmManualDate = () => {
|
||||
if (manualSelectedDate.value && manualSelectedDate.value !== props.modelValue) {
|
||||
selectDate(manualSelectedDate.value)
|
||||
}
|
||||
showManualDatePicker.value = false
|
||||
nextTick(() => scrollToDateElement('smooth'))
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
nextTick(() => scrollToDateElement('smooth'))
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => scrollToDateElement('auto'))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user