Files
BYW-VoiceHub/app/components/Admin/PlaylistCardPage.client.vue
T
Henry_Du c4c30de416 feat(admin): 恢复按日期生成排期图片,重构多项体验问题
- **导入酷狗功能恢复**:废弃纯文本解析,重写为通过多选日期加载对应排期,并支持“根据日期分别生成图片”的场景。
- **浅色打印主题**:在 SchedulePrinter 和 ScheduleItemPrint 中应用 !important 强制白底黑字,消除系统深色模式对预览与打印效果的干扰。
- **日期选择器精简**:彻底删除 AdminDatePicker 和 AdminMultiDatePicker 中的横向日期盘滚动功能,改用直观的选定日期展示,配合月历面板实现更稳定快捷的选择。
- **自动排入逻辑修复**:严格过滤并匹配歌曲的 `expectedPlayDate` 和 `preferredPlayTimeId`,修复无期望播出时间的歌曲被错误填入的问题。
2026-09-13 15:49:05 +08:00

163 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="flex flex-col space-y-8 pb-12 lg:pb-20 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div class="flex flex-col space-y-2">
<h2 class="text-2xl font-bold tracking-tight text-zinc-100">导入酷狗</h2>
<p class="text-sm text-zinc-500">选择排期日期,生成对应的广播排期卡片,用于扫描导入酷狗音乐</p>
</div>
<div class="flex flex-col gap-8">
<!-- 设置面板 -->
<div class="w-full flex flex-col gap-6">
<div class="bg-zinc-900/40 border border-zinc-800 rounded-xl p-6 space-y-6 shadow-lg">
<h3 class="text-lg font-bold flex items-center gap-2 text-zinc-100 shrink-0">
<Layout class="w-4 h-4 text-blue-500" /> 选择日期
</h3>
<div class="flex flex-col gap-5 items-end">
<!-- 日期选择 -->
<div class="space-y-2 flex-1 w-full">
<label class="text-[11px] font-black uppercase text-zinc-600 tracking-wider">选择要生成图片的日期(可多选)</label>
<AdminMultiDatePicker
v-model="selectedDates"
@change="fetchSchedules"
/>
</div>
<button
:disabled="loading || selectedDates.length === 0"
class="w-full py-3 h-[54px] 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 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 shrink-0"
@click="fetchSchedules"
>
<CalendarDays class="w-4 h-4" />
{{ loading ? '加载排期中...' : '读取所选日期排期' }}
</button>
</div>
</div>
</div>
<!-- 预览区 -->
<div class="w-full flex flex-col gap-4">
<template v-if="Object.keys(schedulesByDate).length > 0">
<div v-for="(schedules, date) in schedulesByDate" :key="date" class="bg-zinc-900/40 border border-zinc-800 rounded-xl p-6 shadow-lg mb-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-zinc-100">{{ date }} 排期预览 ({{ schedules.length }} 首)</h3>
<button
:disabled="schedules.length === 0"
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-emerald-900/20 transition-colors flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
@click="openGenerator(date, schedules)"
>
<ImageIcon class="w-4 h-4" />
生成该日图片
</button>
</div>
<div v-if="schedules.length > 0" class="space-y-3">
<div v-for="(schedule, index) in schedules" :key="schedule.id || index" class="flex items-center gap-3 p-3 bg-zinc-950/50 border border-zinc-800/50 rounded-lg">
<span class="text-blue-500 font-bold w-6">{{ index + 1 }}.</span>
<div class="flex-1 overflow-hidden">
<p class="text-sm text-zinc-200 font-medium truncate">{{ schedule.song?.title || schedule.title }}</p>
<p class="text-xs text-zinc-500 truncate">{{ schedule.song?.artist || schedule.artist }}</p>
</div>
</div>
</div>
<div v-else class="text-center py-8 text-zinc-500">
该日期没有排期歌曲
</div>
</div>
</template>
<div v-else-if="!loading && selectedDates.length > 0" class="flex flex-col items-center justify-center py-20 bg-zinc-900/20 border border-zinc-800/50 rounded-xl border-dashed">
<CalendarDays class="w-12 h-12 text-zinc-700 mb-4" />
<p class="text-zinc-500">所选日期均没有排期歌曲</p>
</div>
</div>
</div>
<AdminPlaylistCardGenerator
:show="showModal"
:songs="currentGeneratorSongs"
:default-title="`${currentGeneratorDate} 校园广播歌单`"
@close="showModal = false"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { Layout, Image as ImageIcon, CalendarDays } from '@lucide/vue'
import AdminMultiDatePicker from './AdminMultiDatePicker.vue'
import AdminPlaylistCardGenerator from './PlaylistCardGenerator.vue'
import { useAuth } from '~/composables/useAuth'
const auth = useAuth()
const todayStr = new Date().toISOString().split('T')[0]
const selectedDates = ref([todayStr])
const schedulesByDate = ref({})
const loading = ref(false)
const showModal = ref(false)
const currentGeneratorDate = ref('')
const currentGeneratorSongs = ref([])
const fetchSchedulesForDate = async (date) => {
try {
const response = await $fetch('/api/admin/schedule/full', {
...auth.getAuthConfig(),
query: {
date: date,
includeDrafts: 'true'
}
})
if (response.success && response.data && response.data.schedules) {
return response.data.schedules
}
} catch (error) {
console.error(`Failed to fetch schedules for ${date}:`, error)
}
return []
}
const fetchSchedules = async () => {
if (selectedDates.value.length === 0) {
schedulesByDate.value = {}
return
}
loading.value = true
const newSchedules = {}
try {
// 按日期顺序获取
const sortedDates = [...selectedDates.value].sort()
for (const date of sortedDates) {
const schedules = await fetchSchedulesForDate(date)
if (schedules.length > 0) {
newSchedules[date] = schedules
}
}
schedulesByDate.value = newSchedules
if (Object.keys(newSchedules).length === 0 && window.$showNotification) {
window.$showNotification('所选日期没有排期歌曲', 'info')
}
} catch (error) {
console.error('Failed to fetch schedules:', error)
if (window.$showNotification) {
window.$showNotification('获取排期失败', 'error')
}
} finally {
loading.value = false
}
}
const openGenerator = (date, songs) => {
currentGeneratorDate.value = date
currentGeneratorSongs.value = songs
showModal.value = true
}
onMounted(() => {
fetchSchedules()
})
</script>