From 71bbd88bb84af02341cddddcbb383e4c85f6d650 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Sun, 13 Sep 2026 08:36:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E4=BF=AE=E5=A4=8D=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E9=80=89=E6=8B=A9=E5=99=A8=E4=B8=8E=E9=80=89=E6=9B=B2?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E7=9A=84=20UTC=20=E6=97=B6=E5=B7=AE=E9=94=99?= =?UTF-8?q?=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根本原因: 使用 toISOString().split('T')[0] 会按 UTC 时区截断日期, 在 UTC+8 时区下,本地 00:00~07:59 期间会少 1 天,导致: - AdminDatePicker/AdminMultiDatePicker 横向条、月历选中值错位 - ScheduleManager selectedDate 今日初始化与今日排期过滤错位 - 月历今日高亮与实际日期不匹配 修复方法: 所有 YYYY-MM-DD 格式化统一使用本地时区的 getFullYear/getMonth/getDate + pad, addDays 前先 setHours(0,0,0,0) 归零 避免 DST 与时区偏移导致的跨天误差 --- app/components/Admin/AdminDatePicker.vue | 11 ++++++----- app/components/Admin/AdminMultiDatePicker.vue | 13 +++++++------ app/components/Admin/ScheduleManager.vue | 17 ++++++++++++----- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/components/Admin/AdminDatePicker.vue b/app/components/Admin/AdminDatePicker.vue index 9017dc5..5053b75 100644 --- a/app/components/Admin/AdminDatePicker.vue +++ b/app/components/Admin/AdminDatePicker.vue @@ -172,16 +172,17 @@ const isLastDateVisible = ref(false) const dateRange = ref({ start: -15, end: 15 }) const showCalendar = ref(false) -const today = new Date() +const pad = (n) => n.toString().padStart(2, '0') +const formatDateStr = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +const addDays = (d, n) => { const x = new Date(d); x.setHours(0, 0, 0, 0); x.setDate(x.getDate() + n); return x } +const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() + +const today = new Date(); today.setHours(0, 0, 0, 0) const calendarYear = ref(today.getFullYear()) const calendarMonth = ref(today.getMonth()) const weekdayNames = ['日', '一', '二', '三', '四', '五', '六'] -const formatDateStr = (d) => d.toISOString().split('T')[0] -const addDays = (d, n) => { const x = new Date(d); x.setDate(x.getDate() + n); return x } -const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() - const availableDates = computed(() => { const dates = [] for (let i = dateRange.value.start; i <= dateRange.value.end; i++) { diff --git a/app/components/Admin/AdminMultiDatePicker.vue b/app/components/Admin/AdminMultiDatePicker.vue index 2457eac..0fc0140 100644 --- a/app/components/Admin/AdminMultiDatePicker.vue +++ b/app/components/Admin/AdminMultiDatePicker.vue @@ -195,17 +195,18 @@ const dateRange = ref({ start: 0, end: 45 }) const showCalendar = ref(false) const lastClickedDate = ref('') -const today = new Date() +const pad = (n) => n.toString().padStart(2, '0') +const formatDateStr = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +const addDays = (d, n) => { const x = new Date(d); x.setHours(0, 0, 0, 0); x.setDate(x.getDate() + n); return x } +const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() +const sortDates = (arr) => [...new Set(arr)].sort() + +const today = new Date(); today.setHours(0, 0, 0, 0) const calendarYear = ref(today.getFullYear()) const calendarMonth = ref(today.getMonth()) const weekdayNames = ['日', '一', '二', '三', '四', '五', '六'] -const formatDateStr = (d) => d.toISOString().split('T')[0] -const addDays = (d, n) => { const x = new Date(d); x.setDate(x.getDate() + n); return x } -const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() -const sortDates = (arr) => [...new Set(arr)].sort() - const availableDates = computed(() => { const dates = [] for (let i = dateRange.value.start; i <= dateRange.value.end; i++) { diff --git a/app/components/Admin/ScheduleManager.vue b/app/components/Admin/ScheduleManager.vue index c87344a..5449eb3 100644 --- a/app/components/Admin/ScheduleManager.vue +++ b/app/components/Admin/ScheduleManager.vue @@ -974,7 +974,9 @@ const defaultAutoConfig: AutoConfig = { // 响应式数据 const showCardGenerator = ref(false) -const selectedDate = ref(new Date().toISOString().split('T')[0]) +const _pad = (n) => n.toString().padStart(2, '0') +const _today = new Date(); _today.setHours(0, 0, 0, 0) +const selectedDate = ref(`${_today.getFullYear()}-${_pad(_today.getMonth() + 1)}-${_pad(_today.getDate())}`) const loading = ref(false) const songSortOption = ref('votes-desc') const hasChanges = ref(false) @@ -1695,17 +1697,22 @@ const updateLocalScheduledSongs = () => { console.log('公开排期数量:', publicSchedules.value.length) console.log('草稿数量:', drafts.value.length) - // 获取已发布的排期 + const _fmtLocal = (d) => { + const x = new Date(d) + return `${x.getFullYear()}-${_pad(x.getMonth() + 1)}-${_pad(x.getDate())}` +} + +// 获取已发布的排期 const todaySchedules = publicSchedules.value.filter((s) => { if (!s.playDate) return false - const scheduleDateStr = new Date(s.playDate).toISOString().split('T')[0] + const scheduleDateStr = _fmtLocal(s.playDate) return scheduleDateStr === selectedDate.value }) // 获取草稿排期 const todayDrafts = drafts.value.filter((draft) => { if (!draft.playDate) return false - const draftDateStr = new Date(draft.playDate).toISOString().split('T')[0] + const draftDateStr = _fmtLocal(draft.playDate) return draftDateStr === selectedDate.value }) @@ -2210,7 +2217,7 @@ const confirmMoveDate = async () => { const sourceDate = selectedDate.value const sourceSchedules = [...publicSchedules.value, ...drafts.value].filter((schedule) => { if (!schedule.playDate) return false - return new Date(schedule.playDate).toISOString().split('T')[0] === sourceDate + return _fmtLocal(schedule.playDate) === sourceDate }) if (sourceSchedules.length === 0) {