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