feat(admin): 恢复按日期生成排期图片,重构多项体验问题

- **导入酷狗功能恢复**:废弃纯文本解析,重写为通过多选日期加载对应排期,并支持“根据日期分别生成图片”的场景。
- **浅色打印主题**:在 SchedulePrinter 和 ScheduleItemPrint 中应用 !important 强制白底黑字,消除系统深色模式对预览与打印效果的干扰。
- **日期选择器精简**:彻底删除 AdminDatePicker 和 AdminMultiDatePicker 中的横向日期盘滚动功能,改用直观的选定日期展示,配合月历面板实现更稳定快捷的选择。
- **自动排入逻辑修复**:严格过滤并匹配歌曲的 `expectedPlayDate` 和 `preferredPlayTimeId`,修复无期望播出时间的歌曲被错误填入的问题。
This commit is contained in:
2026-09-13 15:49:05 +08:00
parent ccb39203b8
commit c4c30de416
6 changed files with 194 additions and 573 deletions
+22 -152
View File
@@ -1,64 +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 relative',
modelValue.includes(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.includes(date.value) ? 'ring-1 ring-emerald-500/50' : ''
]"
@click="toggleDate(date.value, $event)"
>
<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>
<div
v-if="modelValue.includes(date.value)"
class="absolute top-1 right-1 w-3.5 h-3.5 rounded-full bg-white text-blue-600 flex items-center justify-center"
>
<svg class="w-2.5 h-2.5" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><polyline points="20,6 9,17 4,12" /></svg>
</div>
</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">
<span class="text-sm font-bold text-zinc-200">
已选择 {{ modelValue.length }} 天
</span>
<span class="text-xs text-zinc-500 font-medium truncate max-w-[200px]">
{{ modelValue.join(', ') || '未选择日期' }}
</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"
title="打开月历"
>
<CalendarIcon class="w-5 h-5" />
</button>
@@ -188,10 +150,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: -3, end: 38 })
const showCalendar = ref(false)
const lastClickedDate = ref('')
@@ -207,21 +165,6 @@ 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 batchActions = [
{ label: '今天', action: 'today' },
{ label: '明天起7天', action: 'next7' },
@@ -340,99 +283,26 @@ const applyBatchAction = (action) => {
commitChange(merged)
}
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
}
}
const scrollToDateElement = (targetDate, behavior = 'smooth') => {
if (!dateSelector.value || !targetDate) return
const el = dateSelector.value.querySelector(`[data-date="${targetDate}"]`)
if (!el) return
const cw = dateSelector.value.clientWidth
const target = el.offsetLeft - (cw - el.offsetWidth) / 2
dateSelector.value.scrollTo({ left: target, behavior })
}
const scrollToToday = () => {
if (dateRange.value.start > 0 || dateRange.value.end < 0) {
dateRange.value.start = -3
dateRange.value.end = 25
const todayStr = formatDateStr(today)
if (!props.modelValue.includes(todayStr)) {
toggleDate(todayStr, { shiftKey: false })
}
nextTick(() => scrollToDateElement(formatDateStr(today), 'smooth'))
}
const toggleCalendar = () => {
showCalendar.value = !showCalendar.value
if (showCalendar.value && props.modelValue.length > 0) {
const d = new Date(props.modelValue[props.modelValue.length - 1])
calendarYear.value = d.getFullYear()
calendarMonth.value = d.getMonth()
}
}
const goToday = () => {
calendarYear.value = today.getFullYear()
calendarMonth.value = today.getMonth()
}
watch(() => props.modelValue, (newVal) => {
if (!newVal || !newVal.length) return
nextTick(() => {
// 跳到第一个 >= 今天的日期,否则跳第一个选中项
const todayStr = formatDateStr(today)
const target = newVal.find(d => d >= todayStr) || newVal[0]
// 如果目标超出当前范围,扩展 range
const diff0 = Math.round((new Date(target) - new Date(todayStr)) / 86400000)
if (diff0 < dateRange.value.start || diff0 > dateRange.value.end) {
dateRange.value.start = Math.min(dateRange.value.start, diff0 - 3)
dateRange.value.end = Math.max(dateRange.value.end, diff0 + 5)
}
nextTick(() => scrollToDateElement(target, 'smooth'))
})
})
onMounted(() => {
if (props.modelValue && props.modelValue.length) {
const todayStr = formatDateStr(today)
const target = props.modelValue.find(d => d >= todayStr) || props.modelValue[0]
const diff0 = Math.round((new Date(target) - new Date(todayStr)) / 86400000)
if (diff0 < dateRange.value.start || diff0 > dateRange.value.end) {
dateRange.value.start = Math.min(dateRange.value.start, diff0 - 3)
dateRange.value.end = Math.max(dateRange.value.end, diff0 + 5)
}
nextTick(() => nextTick(() => scrollToDateElement(target, 'smooth')))
} else {
nextTick(() => scrollToToday())
}
})
</script>
<style scoped>