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
+104 -2
View File
@@ -8,7 +8,7 @@
定义校园广播的常规播放窗口,用户仅可在这些时间段内进行点歌或查看排期
</p>
</div>
<div class="flex items-center gap-3">
<div class="flex flex-wrap items-center gap-3">
<div
class="bg-zinc-900/40 border border-zinc-800 rounded-xl px-4 py-2 flex items-center gap-3"
>
@@ -30,6 +30,21 @@
/>
</button>
</div>
<div v-if="playTimes.length > 1" class="flex items-center gap-2 bg-zinc-900/40 border border-zinc-800 rounded-xl px-3 py-2">
<span class="text-[10px] font-black text-zinc-500 uppercase tracking-widest">批量</span>
<button
class="px-3 py-1.5 text-[10px] font-black bg-emerald-600/20 text-emerald-400 border border-emerald-500/30 rounded-lg hover:bg-emerald-600/30 transition-all"
@click="batchToggleAll(true)"
>
全部启用
</button>
<button
class="px-3 py-1.5 text-[10px] font-black bg-zinc-800 text-zinc-400 border border-zinc-700 rounded-lg hover:bg-zinc-700 transition-all"
@click="batchToggleAll(false)"
>
全部禁用
</button>
</div>
<button
class="flex items-center gap-2 px-6 py-2.5 bg-blue-600 hover:bg-blue-500 text-white text-xs font-black rounded-lg shadow-lg shadow-blue-900/20 transition-all active:scale-95"
@click="openAddForm"
@@ -147,6 +162,13 @@
<div class="mt-8 pt-6 border-t border-zinc-800/50 flex items-center justify-between">
<div class="flex gap-2">
<button
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-500 hover:text-emerald-400 hover:border-emerald-500/30 transition-all"
title="复制此时段"
@click="duplicatePlayTime(playTime)"
>
<Copy :size="14" />
</button>
<button
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-500 hover:text-blue-400 hover:border-blue-500/30 transition-all"
@click="editPlayTime(playTime)"
@@ -201,6 +223,23 @@
</div>
<div class="space-y-6">
<!-- 快速预设 -->
<div v-if="!editingPlayTime" class="space-y-2">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1">快速模板</label>
<div class="flex flex-wrap gap-2">
<button
v-for="p in playTimePresets"
:key="p.name"
type="button"
class="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-lg text-[11px] font-bold text-zinc-400 hover:text-zinc-200 hover:border-zinc-700 hover:bg-zinc-900 transition-all"
@click="applyPreset(p)"
>
<span class="block">{{ p.name }}</span>
<span class="block text-[9px] text-zinc-600 font-medium">{{ p.startTime }} - {{ p.endTime }}</span>
</button>
</div>
</div>
<div class="space-y-2">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>时段名称</label
@@ -366,9 +405,19 @@ import {
Info,
AlertCircle,
X,
AlignLeft
AlignLeft,
Copy
} from '@lucide/vue'
const playTimePresets = [
{ name: '早间广播', startTime: '07:00', endTime: '07:40', description: '早自习前广播时段' },
{ name: '课间操时段', startTime: '09:30', endTime: '10:00', description: '大课间操期间' },
{ name: '午间广播', startTime: '12:00', endTime: '12:40', description: '午休后午间广播' },
{ name: '傍晚广播', startTime: '17:30', endTime: '18:15', description: '放学后时段' },
{ name: '晚间新闻', startTime: '21:00', endTime: '21:30', description: '晚自习结束前' },
{ name: '全天开放', startTime: '', endTime: '', description: '无时间边界限制' }
]
const { getAuthConfig, isAdmin } = useAuth()
const { showToast: showNotification } = useToast()
@@ -636,6 +685,59 @@ const savePlayTime = async () => {
}
}
// 应用快速模板
const applyPreset = (preset) => {
Object.assign(formData, {
name: preset.name,
startTime: preset.startTime,
endTime: preset.endTime,
description: preset.description,
enabled: true
})
}
// 复制时段
const duplicatePlayTime = (playTime: PlayTime) => {
showAddForm.value = true
editingPlayTime.value = null
Object.assign(formData, {
id: 0,
name: `${playTime.name} (副本)`,
startTime: playTime.startTime || '',
endTime: playTime.endTime || '',
description: playTime.description || '',
enabled: playTime.enabled
})
}
// 批量切换启用状态
const batchToggleAll = async (enable: boolean) => {
if (!isAdmin.value) return
const ids = playTimes.value
.filter((pt) => pt.enabled !== enable)
.map((pt) => pt.id)
if (ids.length === 0) {
showNotification(`所有时段已是${enable ? '启用' : '禁用'}状态`, 'info')
return
}
try {
const authConfig = getAuthConfig()
await Promise.all(
ids.map((id) =>
$fetch(`/api/admin/play-times/${id}`, {
method: 'PATCH',
body: { enabled: enable },
...authConfig
})
)
)
await fetchPlayTimes()
showNotification(`已${enable ? '启用' : '禁用'} ${ids.length} 个时段`, 'success')
} catch (err: any) {
showNotification(err.message || '批量操作失败', 'error')
}
}
// 取消表单
const cancelForm = () => {
showAddForm.value = false