feat(admin): 恢复按日期生成排期图片,重构多项体验问题
- **导入酷狗功能恢复**:废弃纯文本解析,重写为通过多选日期加载对应排期,并支持“根据日期分别生成图片”的场景。 - **浅色打印主题**:在 SchedulePrinter 和 ScheduleItemPrint 中应用 !important 强制白底黑字,消除系统深色模式对预览与打印效果的干扰。 - **日期选择器精简**:彻底删除 AdminDatePicker 和 AdminMultiDatePicker 中的横向日期盘滚动功能,改用直观的选定日期展示,配合月历面板实现更稳定快捷的选择。 - **自动排入逻辑修复**:严格过滤并匹配歌曲的 `expectedPlayDate` 和 `preferredPlayTimeId`,修复无期望播出时间的歌曲被错误填入的问题。
This commit is contained in:
@@ -1,58 +1,26 @@
|
||||
<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 shrink-0"
|
||||
@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 shrink-0"
|
||||
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 shrink-0',
|
||||
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',
|
||||
date.isToday && modelValue !== date.value ? 'ring-1 ring-emerald-500/50' : ''
|
||||
]"
|
||||
@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 class="flex items-center justify-between px-3 py-2" @click="toggleCalendar" style="cursor: pointer">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex flex-col items-center justify-center w-12 h-12 rounded-lg bg-blue-600 text-white shadow-lg shadow-blue-900/20">
|
||||
<span class="text-[10px] font-bold uppercase tracking-wider opacity-80">{{ getWeekday(modelValue) }}</span>
|
||||
<span class="text-lg font-black leading-none my-0.5">{{ getDay(modelValue) }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-bold text-zinc-200">{{ modelValue }}</span>
|
||||
<span class="text-xs text-zinc-500 font-medium">{{ isToday(modelValue) ? '今天' : '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
:disabled="isLastDateVisible"
|
||||
class="p-2 text-zinc-500 hover:text-zinc-300 disabled:opacity-30 transition-colors shrink-0"
|
||||
@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 shrink-0">
|
||||
<button class="p-2 text-zinc-500 hover:text-emerald-400 transition-colors" title="跳转到今天" @click="scrollToToday">
|
||||
<div class="flex items-center border-l border-zinc-800 pl-3">
|
||||
<button class="p-2 text-zinc-500 hover:text-emerald-400 transition-colors" title="跳转到今天" @click.stop="scrollToToday">
|
||||
<CircleDot class="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
class="p-2 text-zinc-500 hover:text-blue-400 transition-colors"
|
||||
:class="{ 'text-blue-400': showCalendar }"
|
||||
title="打开月历"
|
||||
@click="toggleCalendar"
|
||||
>
|
||||
<CalendarIcon class="w-5 h-5" />
|
||||
</button>
|
||||
@@ -166,10 +134,6 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const dateSelector = ref(null)
|
||||
const isFirstDateVisible = ref(false)
|
||||
const isLastDateVisible = ref(false)
|
||||
const dateRange = ref({ start: -10, end: 15 })
|
||||
const showCalendar = ref(false)
|
||||
|
||||
const pad = (n) => n.toString().padStart(2, '0')
|
||||
@@ -177,27 +141,30 @@ const formatDateStr = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(
|
||||
const addDays = (d, n) => { const x = new Date(d); x.setHours(0, 0, 0, 0); x.setDate(x.getDate() + n); return x }
|
||||
const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
|
||||
|
||||
const weekdayNames = ['日', '一', '二', '三', '四', '五', '六']
|
||||
|
||||
const getWeekday = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const d = new Date(dateStr)
|
||||
return weekdayNames[d.getDay()]
|
||||
}
|
||||
|
||||
const getDay = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const d = new Date(dateStr)
|
||||
return d.getDate().toString().padStart(2, '0')
|
||||
}
|
||||
|
||||
const isToday = (dateStr) => {
|
||||
if (!dateStr) return false
|
||||
const d = new Date(dateStr)
|
||||
return sameDay(d, new Date())
|
||||
}
|
||||
|
||||
const today = new Date(); today.setHours(0, 0, 0, 0)
|
||||
const calendarYear = ref(today.getFullYear())
|
||||
const calendarMonth = ref(today.getMonth())
|
||||
|
||||
const weekdayNames = ['日', '一', '二', '三', '四', '五', '六']
|
||||
|
||||
const availableDates = computed(() => {
|
||||
const dates = []
|
||||
for (let i = dateRange.value.start; i <= dateRange.value.end; i++) {
|
||||
const date = addDays(today, i)
|
||||
dates.push({
|
||||
value: formatDateStr(date),
|
||||
day: date.getDate().toString().padStart(2, '0'),
|
||||
month: (date.getMonth() + 1).toString().padStart(2, '0'),
|
||||
weekday: weekdayNames[date.getDay()],
|
||||
isToday: i === 0
|
||||
})
|
||||
}
|
||||
return dates
|
||||
})
|
||||
|
||||
const quickDates = computed(() => {
|
||||
const t = new Date(); t.setHours(0, 0, 0, 0)
|
||||
const mk = (label, date) => ({ label, value: formatDateStr(date) })
|
||||
@@ -237,63 +204,12 @@ const calendarCells = computed(() => {
|
||||
const selectDate = (dateValue) => {
|
||||
emit('update:modelValue', dateValue)
|
||||
emit('change', dateValue)
|
||||
}
|
||||
|
||||
const scrollToDateElement = (behavior = 'smooth', targetDate = props.modelValue) => {
|
||||
if (!dateSelector.value) return
|
||||
const dateKey = targetDate || formatDateStr(today)
|
||||
const el = dateSelector.value.querySelector(`[data-date="${dateKey}"]`)
|
||||
if (!el) return
|
||||
const cw = dateSelector.value.clientWidth
|
||||
const target = el.offsetLeft - (cw - el.offsetWidth) / 2
|
||||
dateSelector.value.scrollTo({ left: target, behavior })
|
||||
}
|
||||
|
||||
const handleWheel = (event) => {
|
||||
event.preventDefault()
|
||||
if (!dateSelector.value) return
|
||||
const scrollAmount = 160
|
||||
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 = 160
|
||||
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 < 100) {
|
||||
const oldScrollWidth = scrollWidth
|
||||
dateRange.value.start -= 5
|
||||
nextTick(() => {
|
||||
const newScrollWidth = dateSelector.value.scrollWidth
|
||||
dateSelector.value.scrollLeft += newScrollWidth - oldScrollWidth
|
||||
})
|
||||
}
|
||||
if (scrollWidth - scrollLeft - clientWidth < 100) {
|
||||
dateRange.value.end += 5
|
||||
}
|
||||
showCalendar.value = false
|
||||
}
|
||||
|
||||
const scrollToToday = () => {
|
||||
const todayStr = formatDateStr(today)
|
||||
if (props.modelValue !== todayStr) selectDate(todayStr)
|
||||
if (dateRange.value.start > 0 || dateRange.value.end < 0) {
|
||||
dateRange.value.start = -10
|
||||
dateRange.value.end = 15
|
||||
}
|
||||
nextTick(() => scrollToDateElement('smooth'))
|
||||
}
|
||||
|
||||
const toggleCalendar = () => {
|
||||
@@ -312,24 +228,7 @@ const goToday = () => {
|
||||
|
||||
const handleQuickSelect = (dateValue) => {
|
||||
if (props.modelValue !== dateValue) selectDate(dateValue)
|
||||
nextTick(() => {
|
||||
const d = new Date(dateValue)
|
||||
const diffDays = Math.round((d - today) / (1000 * 60 * 60 * 24))
|
||||
if (diffDays < dateRange.value.start || diffDays > dateRange.value.end) {
|
||||
dateRange.value.start = diffDays - 3
|
||||
dateRange.value.end = diffDays + 5
|
||||
}
|
||||
nextTick(() => scrollToDateElement('smooth'))
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
nextTick(() => scrollToDateElement('smooth'))
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => scrollToDateElement(props.modelValue ? 'auto' : 'smooth'))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user