feat(admin): 优化后台日期选择器和播出时段交互流程
- AdminDatePicker: 新增可展开月历网格 + 6 个快捷日期跳转按钮 - AdminMultiDatePicker: 新增 8 种批量预设 + Shift 范围选择 + 月历网格 - PlayTimeManager: 新增 6 个校园广播快速模板 + 时段复制 + 批量启停 - ScheduleManager: 播出时段从下拉改为标签按钮组 + 增加统计卡片 - RequestTimeManager: datetime-local 拆分为日期+时间双输入 + 6 种快捷预设
This commit is contained in:
@@ -291,26 +291,64 @@
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between px-1">
|
||||
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">快捷时间填充</label>
|
||||
<span class="text-[10px] font-bold text-zinc-500">点击可一键套用常见时段</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="p in requestTimeQuickPresets"
|
||||
:key="p.label"
|
||||
type="button"
|
||||
class="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-xl text-[11px] font-bold text-zinc-400 hover:text-zinc-200 hover:border-zinc-700 hover:bg-zinc-900 transition-all whitespace-nowrap"
|
||||
@click="applyRequestTimePreset(p)"
|
||||
>
|
||||
{{ p.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
|
||||
>开始日期时间</label
|
||||
>
|
||||
<input
|
||||
v-model="formData.startTime"
|
||||
type="datetime-local"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30"
|
||||
>开始时间</label
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<input
|
||||
v-model="formData.startDate"
|
||||
type="date"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30"
|
||||
>
|
||||
<input
|
||||
v-model="formData.startTimeOfDay"
|
||||
type="time"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30"
|
||||
>
|
||||
</div>
|
||||
<p class="text-[9px] text-zinc-600 px-1">
|
||||
实际值: {{ formData.startTime || '尚未设置' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
|
||||
>结束日期时间</label
|
||||
>
|
||||
<input
|
||||
v-model="formData.endTime"
|
||||
type="datetime-local"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30"
|
||||
>结束时间</label
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<input
|
||||
v-model="formData.endDate"
|
||||
type="date"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30"
|
||||
>
|
||||
<input
|
||||
v-model="formData.endTimeOfDay"
|
||||
type="time"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30"
|
||||
>
|
||||
</div>
|
||||
<p class="text-[9px] text-zinc-600 px-1">
|
||||
实际值: {{ formData.endTime || '尚未设置' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -502,7 +540,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, reactive, ref, computed } from 'vue'
|
||||
import { onMounted, onUnmounted, reactive, ref, computed, watch } from 'vue'
|
||||
import { useAuth } from '~/composables/useAuth'
|
||||
import type { RequestTime } from '~~/types'
|
||||
import {
|
||||
@@ -521,6 +559,57 @@ import {
|
||||
Filter
|
||||
} from '@lucide/vue'
|
||||
|
||||
const addMinutes = (d: Date, mins: number) => {
|
||||
const r = new Date(d); r.setMinutes(r.getMinutes() + mins); return r
|
||||
}
|
||||
const addHours = (d: Date, h: number) => addMinutes(d, h * 60)
|
||||
const addDays = (d: Date, n: number) => {
|
||||
const r = new Date(d); r.setDate(r.getDate() + n); return r
|
||||
}
|
||||
const nextWeekday = (base: Date, target: number) => {
|
||||
const r = new Date(base)
|
||||
const diff = (target - r.getDay() + 7) % 7 || 7
|
||||
r.setDate(r.getDate() + diff)
|
||||
return r
|
||||
}
|
||||
const pad = (n: number) => n.toString().padStart(2, '0')
|
||||
const fmt = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
const splitDT = (dt: string): [string, string] => {
|
||||
if (!dt || !dt.includes('T')) return ['', '']
|
||||
const [d, t] = dt.split('T')
|
||||
return [d, t.slice(0, 5)]
|
||||
}
|
||||
|
||||
const requestTimeQuickPresets = [
|
||||
{ label: '立即开放 2 小时', apply: () => {
|
||||
const n = new Date()
|
||||
return { startTime: addMinutes(n, 5), endTime: addHours(n, 2) }
|
||||
}},
|
||||
{ label: '今晚 18:00-22:00', apply: () => {
|
||||
const t = new Date(); t.setHours(18, 0, 0, 0)
|
||||
return { startTime: t, endTime: addHours(t, 4) }
|
||||
}},
|
||||
{ label: '明天 12:00-14:00', apply: () => {
|
||||
const t = addDays(new Date(), 1); t.setHours(12, 0, 0, 0)
|
||||
return { startTime: t, endTime: addHours(t, 2) }
|
||||
}},
|
||||
{ label: '下周一至周五', apply: () => {
|
||||
const s = nextWeekday(new Date(), 1); s.setHours(0, 5, 0, 0)
|
||||
const e = addDays(s, 4); e.setHours(23, 59, 0, 0)
|
||||
return { startTime: s, endTime: e }
|
||||
}},
|
||||
{ label: '下周整周 7 天', apply: () => {
|
||||
const s = nextWeekday(new Date(), 1); s.setHours(0, 5, 0, 0)
|
||||
const e = addDays(s, 6); e.setHours(23, 59, 0, 0)
|
||||
return { startTime: s, endTime: e }
|
||||
}},
|
||||
{ label: '持续开放 30 天', apply: () => {
|
||||
const s = addMinutes(new Date(), 5)
|
||||
const e = addDays(s, 30); e.setHours(23, 59, 0, 0)
|
||||
return { startTime: s, endTime: e }
|
||||
}}
|
||||
]
|
||||
|
||||
const { getAuthConfig, isAdmin } = useAuth()
|
||||
|
||||
const requestTimes = ref<RequestTime[]>([])
|
||||
@@ -544,11 +633,51 @@ const formData = reactive({
|
||||
name: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
startDate: '',
|
||||
startTimeOfDay: '',
|
||||
endDate: '',
|
||||
endTimeOfDay: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
expected: 0
|
||||
})
|
||||
|
||||
// 日期+时间分离字段 <-> 合并的 datetime-local 字段双向同步
|
||||
const syncStartFromParts = () => {
|
||||
if (formData.startDate && formData.startTimeOfDay) {
|
||||
formData.startTime = `${formData.startDate}T${formData.startTimeOfDay}`
|
||||
} else if (!formData.startDate && !formData.startTimeOfDay) {
|
||||
formData.startTime = ''
|
||||
}
|
||||
}
|
||||
const syncEndFromParts = () => {
|
||||
if (formData.endDate && formData.endTimeOfDay) {
|
||||
formData.endTime = `${formData.endDate}T${formData.endTimeOfDay}`
|
||||
} else if (!formData.endDate && !formData.endTimeOfDay) {
|
||||
formData.endTime = ''
|
||||
}
|
||||
}
|
||||
watch(() => formData.startDate, syncStartFromParts)
|
||||
watch(() => formData.startTimeOfDay, syncStartFromParts)
|
||||
watch(() => formData.endDate, syncEndFromParts)
|
||||
watch(() => formData.endTimeOfDay, syncEndFromParts)
|
||||
|
||||
// 将 startTime/endTime 拆分到辅助字段(编辑时调用)
|
||||
const splitFormDateTime = () => {
|
||||
const [sd, st] = splitDT(formData.startTime)
|
||||
formData.startDate = sd; formData.startTimeOfDay = st
|
||||
const [ed, et] = splitDT(formData.endTime)
|
||||
formData.endDate = ed; formData.endTimeOfDay = et
|
||||
}
|
||||
|
||||
// 应用快捷时间预设
|
||||
const applyRequestTimePreset = (p: any) => {
|
||||
const { startTime, endTime } = p.apply()
|
||||
formData.startTime = fmt(startTime)
|
||||
formData.endTime = fmt(endTime)
|
||||
splitFormDateTime()
|
||||
}
|
||||
|
||||
// 统计数据
|
||||
const stats = computed(() => [
|
||||
{
|
||||
@@ -711,6 +840,7 @@ const editRequestTime = (RequestTime: RequestTime) => {
|
||||
enabled: RequestTime.enabled,
|
||||
expected: RequestTime.expected || 0
|
||||
})
|
||||
splitFormDateTime()
|
||||
}
|
||||
|
||||
const toggleRequestTimeStatus = async (RequestTime: RequestTime) => {
|
||||
@@ -827,6 +957,10 @@ const cancelForm = () => {
|
||||
name: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
startDate: '',
|
||||
startTimeOfDay: '',
|
||||
endDate: '',
|
||||
endTimeOfDay: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
expected: 0
|
||||
|
||||
Reference in New Issue
Block a user