feat(admin): 优化后台日期选择器和播出时段交互流程
- AdminDatePicker: 新增可展开月历网格 + 6 个快捷日期跳转按钮 - AdminMultiDatePicker: 新增 8 种批量预设 + Shift 范围选择 + 月历网格 - PlayTimeManager: 新增 6 个校园广播快速模板 + 时段复制 + 批量启停 - ScheduleManager: 播出时段从下拉改为标签按钮组 + 增加统计卡片 - RequestTimeManager: datetime-local 拆分为日期+时间双输入 + 6 种快捷预设
This commit is contained in:
@@ -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' },
|
||||
|
||||
Reference in New Issue
Block a user