feat(admin): 优化后台日期选择器和播出时段交互流程
构建并部署 BYW VoiceHub / Preflight 语法与构建检查 (push) Successful in 2m15s
构建并部署 BYW VoiceHub / 构建并推送容器镜像 (push) Successful in 9m18s
构建并部署 BYW VoiceHub / 部署到生产服务器 (push) Successful in 6s

- AdminDatePicker: 新增可展开月历网格 + 6 个快捷日期跳转按钮
- AdminMultiDatePicker: 新增 8 种批量预设 + Shift 范围选择 + 月历网格
- PlayTimeManager: 新增 6 个校园广播快速模板 + 时段复制 + 批量启停
- ScheduleManager: 播出时段从下拉改为标签按钮组 + 增加统计卡片
- RequestTimeManager: datetime-local 拆分为日期+时间双输入 + 6 种快捷预设
This commit is contained in:
2026-09-13 02:04:48 +08:00
parent a481e0ae4c
commit 19ab33b102
5 changed files with 839 additions and 176 deletions
+115 -19
View File
@@ -7,27 +7,107 @@
@update:modelValue="handleDateSelect"
/>
<!-- 播出时段与自动排期配置 -->
<!-- 播出时段标签组 + 自动排期策略 -->
<div
class="grid grid-cols-1 md:grid-cols-2 gap-3 bg-zinc-900/30 border border-zinc-800 rounded-xl p-3 md:p-4"
class="space-y-3 bg-zinc-900/30 border border-zinc-800 rounded-xl p-3 md:p-4"
>
<div v-if="playTimeEnabled">
<CustomSelect
:model-value="selectedPlayTime"
label="播出时段"
:options="playTimeOptions"
class-name="w-full"
@update:model-value="handlePlayTimeSelect"
/>
<!-- 播出时段:标签按钮组(直接点击切换) -->
<div v-if="playTimeEnabled && playTimes.length > 0" class="space-y-2">
<div class="flex items-center justify-between px-1">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">播出时段</span>
<span class="text-[10px] font-bold text-zinc-500">
{{ selectedPlayTime ? '已按时段过滤' : '显示全天排期' }}
</span>
</div>
<div class="flex flex-wrap gap-2">
<button
:class="[
'px-4 py-2 rounded-xl text-xs font-black border transition-all whitespace-nowrap',
selectedPlayTime === ''
? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20'
: 'bg-zinc-950 border-zinc-800 text-zinc-400 hover:text-zinc-200 hover:border-zinc-700'
]"
@click="handlePlayTimeSelect('')"
>
<span class="flex items-center gap-1.5">
<Layers :size="12" />
全部时段 (全天)
</span>
</button>
<button
v-for="pt in playTimes"
:key="pt.id"
:disabled="!pt.enabled"
:class="[
'px-4 py-2 rounded-xl text-xs font-black border transition-all whitespace-nowrap',
selectedPlayTime === String(pt.id)
? pt.enabled
? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20'
: 'bg-zinc-700 border-zinc-600 text-zinc-300'
: pt.enabled
? 'bg-zinc-950 border-zinc-800 text-zinc-400 hover:text-zinc-200 hover:border-zinc-700'
: 'bg-zinc-950/40 border-zinc-800/40 text-zinc-700 cursor-not-allowed'
]"
@click="pt.enabled && handlePlayTimeSelect(String(pt.id))"
>
<span class="flex items-center gap-1.5">
<Clock :size="12" />
<span>{{ pt.name }}</span>
<span
v-if="pt.startTime || pt.endTime"
class="opacity-70 font-bold"
>
{{ pt.startTime || '--' }}-{{ pt.endTime || '--' }}
</span>
<span
v-if="!pt.enabled"
class="ml-1 px-1.5 py-0.5 text-[9px] bg-zinc-800 rounded text-zinc-500"
>已禁用</span>
</span>
</button>
</div>
</div>
<div :class="playTimeEnabled ? '' : 'md:col-span-2'">
<CustomSelect
:model-value="autoScheduleStrategy"
label="自动排期策略"
:options="scheduleStrategyOptions"
class-name="w-full"
@update:model-value="handleStrategyChange"
/>
<!-- 自动排期策略 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div :class="playTimeEnabled ? '' : 'md:col-span-2'">
<CustomSelect
:model-value="autoScheduleStrategy"
label="自动排期策略"
:options="scheduleStrategyOptions"
class-name="w-full"
@update:model-value="handleStrategyChange"
/>
</div>
<!-- 快速统计信息 -->
<div
v-if="playTimeEnabled && playTimes.length > 0"
class="hidden md:flex items-center gap-3 bg-zinc-950/60 border border-zinc-800 rounded-xl px-4 py-2"
>
<div class="flex-1 flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-blue-600/20 text-blue-400 flex items-center justify-center">
<ListMusic :size="14" />
</div>
<div class="flex flex-col leading-tight">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">当前列表</span>
<span class="text-sm font-black text-zinc-200">
{{ localScheduledSongs.length }} 首 / {{ playTimeScheduledCount }} 首
</span>
</div>
</div>
<div class="w-px h-8 bg-zinc-800" />
<div class="flex-1 flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-purple-600/20 text-purple-400 flex items-center justify-center">
<Inbox :size="14" />
</div>
<div class="flex flex-col leading-tight">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">待排歌曲</span>
<span class="text-sm font-black text-zinc-200">
{{ filteredUnscheduledSongs.length }} 首
</span>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -851,7 +931,9 @@ import {
MessageSquare,
Image,
CalendarDays,
Image as ImageIcon
Image as ImageIcon,
Layers,
Inbox
} from '@lucide/vue'
import AdminDatePicker from './AdminDatePicker.vue'
import PlaylistCardGenerator from './PlaylistCardGenerator.vue'
@@ -1033,6 +1115,20 @@ const playTimeOptions = computed(() => {
return options
})
// 当前时段(全天)内所有已排歌曲数(用于对比显示)
const playTimeScheduledCount = computed(() => {
if (!selectedPlayTime.value) return localScheduledSongs.length
try {
const ptId = parseInt(selectedPlayTime.value)
return (playTimes.value || []).reduce((sum, pt) => {
if (pt.id === ptId) return sum
return sum
}, 0)
} catch (e) {
return localScheduledSongs.length
}
})
// 排序选项
const sortOptions = [
{ label: '最新投稿', value: 'time-desc' },