fix(admin): 修复日期选择框尺寸过大与自动排入跨日期逻辑
- **日期选择器尺寸优化**:AdminDatePicker 与 AdminMultiDatePicker 采用 w-9 h-9 的紧凑卡片,缩小字号、边距与圆角,贴合后台窄布局。 - **自动排入逻辑重构**:取消严格的 `expectedPlayDate === selectedDate` 过滤,改为按歌曲期望日期/时段自动分配 `playDate` 与 `playTimeId`;其他歌曲排到当前选中日期。 - **跨日期本地暂存池**:引入 `crossDateLocalPool` 保存尚未保存但已排入其他日期的临时排期,切换 `selectedDate` 或时段时正确回填对应日期的草稿,避免自动排入后切换视图导致丢失。 - **排期列表去重与过滤**:时段过滤逻辑修复 `playTimeId == null` 的边界,并在合并公开/草稿/本地暂存时按优先级去重。
This commit is contained in:
@@ -1083,6 +1083,9 @@ const localScheduledSongs = ref([])
|
||||
const replayRequests = ref([])
|
||||
const scheduledSongIds = ref(new Set())
|
||||
|
||||
// 跨日期本地暂存池:保存未保存但分配到其他日期的排期,避免切换日期时丢失
|
||||
const crossDateLocalPool = ref([])
|
||||
|
||||
// 计算是否有未发布的草稿
|
||||
const hasUnpublishedDrafts = computed(() => {
|
||||
return localScheduledSongs.value.some((schedule) => schedule.isDraft)
|
||||
@@ -1693,9 +1696,23 @@ const onSemesterChange = async () => {
|
||||
|
||||
// 更新本地排期数据(包括草稿)
|
||||
const updateLocalScheduledSongs = () => {
|
||||
// 第一步:把当前 localScheduledSongs 中的本地暂存(isLocalOnly)回收到跨日期池
|
||||
for (const s of localScheduledSongs.value) {
|
||||
if (s.isLocalOnly) {
|
||||
const idx = crossDateLocalPool.value.findIndex(p => p.id === s.id)
|
||||
if (idx === -1) crossDateLocalPool.value.push(s)
|
||||
else crossDateLocalPool.value[idx] = s
|
||||
}
|
||||
}
|
||||
// 清理池子里已经在公开排期或草稿中的(避免重复)
|
||||
crossDateLocalPool.value = crossDateLocalPool.value.filter(p => {
|
||||
return ![...publicSchedules.value, ...drafts.value].some(s => s.id === p.id || (s.song?.id && p.song?.id && s.song.id === p.song.id))
|
||||
})
|
||||
|
||||
console.log('更新本地排期数据 - 当前日期:', selectedDate.value)
|
||||
console.log('公开排期数量:', publicSchedules.value.length)
|
||||
console.log('草稿数量:', drafts.value.length)
|
||||
console.log('跨日期本地暂存池数量:', crossDateLocalPool.value.length)
|
||||
|
||||
const _fmtLocal = (d) => {
|
||||
const x = new Date(d)
|
||||
@@ -1715,28 +1732,59 @@ const updateLocalScheduledSongs = () => {
|
||||
const draftDateStr = _fmtLocal(draft.playDate)
|
||||
return draftDateStr === selectedDate.value
|
||||
})
|
||||
|
||||
// 获取本地暂存池中属于当前日期的
|
||||
const todayLocalOnly = crossDateLocalPool.value.filter((p) => {
|
||||
return p.playDate === selectedDate.value
|
||||
})
|
||||
// 从池中移除今天的,因为接下来要放入 localScheduledSongs
|
||||
crossDateLocalPool.value = crossDateLocalPool.value.filter(p => p.playDate !== selectedDate.value)
|
||||
|
||||
console.log('当天已发布排期:', todaySchedules.length)
|
||||
console.log('当天草稿排期:', todayDrafts.length)
|
||||
console.log('当天本地暂存排期:', todayLocalOnly.length)
|
||||
|
||||
// 合并已发布和草稿排期
|
||||
let allSchedules = [...todaySchedules, ...todayDrafts]
|
||||
// 合并已发布、草稿、本地暂存
|
||||
let allSchedules = [...todaySchedules, ...todayDrafts, ...todayLocalOnly]
|
||||
|
||||
// 如果选择了特定播出时段,进行过滤
|
||||
if (selectedPlayTime.value) {
|
||||
allSchedules = allSchedules.filter((s) => s.playTimeId === parseInt(selectedPlayTime.value))
|
||||
const ptId = parseInt(selectedPlayTime.value)
|
||||
allSchedules = allSchedules.filter((s) => {
|
||||
if (!selectedPlayTime.value) return true
|
||||
if (s.playTimeId === ptId) return true
|
||||
// 如果未选择时段(为空)则也不显示,除非时段本身就是空
|
||||
if (s.playTimeId == null && ptId == null) return true
|
||||
return s.playTimeId === ptId
|
||||
})
|
||||
}
|
||||
|
||||
// 去重(根据 song.id):保留优先级:已发布 > 草稿 > 本地暂存
|
||||
const seenIds = new Set()
|
||||
allSchedules = allSchedules.filter(s => {
|
||||
const key = s.song?.id || s.songId || s.id
|
||||
if (!key) return true
|
||||
if (seenIds.has(key)) return false
|
||||
seenIds.add(key)
|
||||
return true
|
||||
})
|
||||
|
||||
// 按 sequence 字段排序
|
||||
allSchedules.sort((a, b) => (a.sequence || 0) - (b.sequence || 0))
|
||||
allSchedules.sort((a, b) => {
|
||||
const sa = a.sequence || 0
|
||||
const sb = b.sequence || 0
|
||||
if (sa !== sb) return sa - sb
|
||||
return (a.id || 0) - (b.id || 0)
|
||||
})
|
||||
|
||||
localScheduledSongs.value = allSchedules.map((s) => ({ ...s }))
|
||||
|
||||
console.log('最终显示排期数量:', localScheduledSongs.value.length)
|
||||
|
||||
// 更新已排期歌曲ID集合(包括草稿)
|
||||
// 更新已排期歌曲ID集合(包括草稿、跨日期暂存、公开排期)
|
||||
const allScheduledSongs = [...publicSchedules.value, ...drafts.value, ...crossDateLocalPool.value, ...localScheduledSongs.value]
|
||||
scheduledSongIds.value = new Set(
|
||||
[...publicSchedules.value, ...drafts.value]
|
||||
allScheduledSongs
|
||||
.filter((s) => s.song && s.song.id)
|
||||
.map((s) => s.song.id)
|
||||
)
|
||||
@@ -2055,21 +2103,18 @@ const saveSequence = async () => {
|
||||
// 一键自动排入期望播出的歌曲
|
||||
const autoScheduleExpected = () => {
|
||||
let count = 0
|
||||
const expectedSongs = [...allUnscheduledSongs.value].filter(song => {
|
||||
// 严格按期望播出日期排期:如果歌曲有期望播出日期,必须匹配当前选择的日期
|
||||
if (song.expectedPlayDate && song.expectedPlayDate !== selectedDate.value) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// 1. 收集所有待排期歌曲(不做严格的日期过滤,根据期望日期自动分配)
|
||||
const pool = [...allUnscheduledSongs.value]
|
||||
|
||||
if (expectedSongs.length === 0) {
|
||||
if (pool.length === 0) {
|
||||
showToast('当前没有待排期的歌曲', 'info')
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 排序(和之前保持一致:按策略)
|
||||
if (autoScheduleStrategy.value === 'contributor-count') {
|
||||
expectedSongs.sort((a, b) => {
|
||||
pool.sort((a, b) => {
|
||||
const diffA = getSongContributorCount(b) - getSongContributorCount(a)
|
||||
if (diffA !== 0) return diffA
|
||||
const seqDiff = (a.expectedPlaySequence || 9999) - (b.expectedPlaySequence || 9999)
|
||||
@@ -2077,45 +2122,62 @@ const autoScheduleExpected = () => {
|
||||
return (b.voteCount || 0) - (a.voteCount || 0)
|
||||
})
|
||||
} else {
|
||||
expectedSongs.sort((a, b) => (a.expectedPlaySequence || 9999) - (b.expectedPlaySequence || 9999))
|
||||
pool.sort((a, b) => (a.expectedPlaySequence || 9999) - (b.expectedPlaySequence || 9999))
|
||||
}
|
||||
|
||||
const baseId = Date.now()
|
||||
const filteredPtId = selectedPlayTime.value ? parseInt(selectedPlayTime.value) : null
|
||||
|
||||
const playTimeSeqCounters = new Map()
|
||||
for (const s of localScheduledSongs.value) {
|
||||
const key = String(s.playTimeId || '')
|
||||
playTimeSeqCounters.set(key, (playTimeSeqCounters.get(key) || 0) + 1)
|
||||
// 3. 初始化每个日期 × 时段的序列计数器
|
||||
// 结构: dateStr -> Map(playTimeIdStr -> seq)
|
||||
const dateSeqCounters = new Map()
|
||||
const initDateCounters = (dateStr) => {
|
||||
if (!dateSeqCounters.has(dateStr)) {
|
||||
const ptMap = new Map()
|
||||
for (const s of localScheduledSongs.value) {
|
||||
if (s.playDate !== dateStr) continue
|
||||
const k = String(s.playTimeId || '')
|
||||
ptMap.set(k, (ptMap.get(k) || 0) + 1)
|
||||
}
|
||||
dateSeqCounters.set(dateStr, ptMap)
|
||||
}
|
||||
return dateSeqCounters.get(dateStr)
|
||||
}
|
||||
|
||||
expectedSongs.forEach((song, idx) => {
|
||||
const existingIndex = localScheduledSongs.value.findIndex((s) => s.song?.id === song.id)
|
||||
if (existingIndex !== -1) return
|
||||
|
||||
// 4. 处理歌曲,根据期望日期分配 playDate
|
||||
pool.forEach((song, idx) => {
|
||||
// 4.1 确定播出日期:优先期望日期,否则用当前 selectedDate
|
||||
let targetPlayDate = (song.expectedPlayDate && String(song.expectedPlayDate).trim()) || selectedDate.value
|
||||
|
||||
// 4.2 确定播出时段:优先期望时段,其次是当前选中的时段视图
|
||||
let targetPlayTimeId = null
|
||||
if (song.preferredPlayTimeId != null) {
|
||||
const matched = (playTimes.value || []).find(pt => String(pt.id) === String(song.preferredPlayTimeId))
|
||||
if (matched) targetPlayTimeId = matched.id
|
||||
}
|
||||
|
||||
// 如果没有明确期望时段,但当前处于特定时段视图下,则排入该时段
|
||||
if (targetPlayTimeId == null && filteredPtId != null) {
|
||||
targetPlayTimeId = filteredPtId
|
||||
}
|
||||
|
||||
// 如果处于特定时段视图,但该歌曲的期望时段不是当前时段,则跳过不排入
|
||||
// 4.3 如果处于特定时段视图,且该歌曲的时段不匹配(且非空),则跳过
|
||||
if (filteredPtId != null && targetPlayTimeId != null && String(targetPlayTimeId) !== String(filteredPtId)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 4.4 检查是否已经存在于已排期列表中
|
||||
const existingIndex = localScheduledSongs.value.findIndex((s) => s.song?.id === song.id)
|
||||
if (existingIndex !== -1) return
|
||||
|
||||
// 4.5 获取该日期下的计数器
|
||||
const ptSeq = initDateCounters(targetPlayDate)
|
||||
const ptKey = String(targetPlayTimeId || '')
|
||||
const nextSeq = (playTimeSeqCounters.get(ptKey) || 0) + 1
|
||||
playTimeSeqCounters.set(ptKey, nextSeq)
|
||||
const nextSeq = (ptSeq.get(ptKey) || 0) + 1
|
||||
ptSeq.set(ptKey, nextSeq)
|
||||
|
||||
const newSchedule = {
|
||||
id: baseId + idx,
|
||||
song: song,
|
||||
playDate: selectedDate.value,
|
||||
playDate: targetPlayDate,
|
||||
sequence: nextSeq,
|
||||
playTimeId: targetPlayTimeId,
|
||||
isNew: true,
|
||||
@@ -2126,6 +2188,7 @@ const autoScheduleExpected = () => {
|
||||
count++
|
||||
})
|
||||
|
||||
// 5. 按 playDate + playTime 顺序重排 sequence
|
||||
const ptOrder = (id) => {
|
||||
if (id == null || id === '') return 1e9
|
||||
const pt = (playTimes.value || []).find(p => p.id === id)
|
||||
@@ -2133,20 +2196,33 @@ const autoScheduleExpected = () => {
|
||||
const [h, m] = pt.startTime.split(':').map(Number)
|
||||
return h * 60 + (m || 0)
|
||||
}
|
||||
localScheduledSongs.value.sort((a, b) => {
|
||||
const po = ptOrder(a.playTimeId) - ptOrder(b.playTimeId)
|
||||
if (po !== 0) return po
|
||||
return (a.sequence || 0) - (b.sequence || 0)
|
||||
})
|
||||
localScheduledSongs.value.forEach((item, idx) => {
|
||||
item.sequence = idx + 1
|
||||
})
|
||||
// 5.1 按日期分组后再排序
|
||||
const dateGroups = new Map()
|
||||
for (const s of localScheduledSongs.value) {
|
||||
const key = String(s.playDate || '')
|
||||
if (!dateGroups.has(key)) dateGroups.set(key, [])
|
||||
dateGroups.get(key).push(s)
|
||||
}
|
||||
|
||||
const allSorted = []
|
||||
const sortedDates = [...dateGroups.keys()].sort()
|
||||
for (const d of sortedDates) {
|
||||
const group = dateGroups.get(d)
|
||||
group.sort((a, b) => {
|
||||
const po = ptOrder(a.playTimeId) - ptOrder(b.playTimeId)
|
||||
if (po !== 0) return po
|
||||
return (a.sequence || 0) - (b.sequence || 0)
|
||||
})
|
||||
group.forEach((item, idx) => { item.sequence = idx + 1 })
|
||||
allSorted.push(...group)
|
||||
}
|
||||
localScheduledSongs.value = allSorted
|
||||
|
||||
hasChanges.value = true
|
||||
const strategyLabel =
|
||||
autoScheduleStrategy.value === 'contributor-count' ? '(按投稿人数优先)' : ''
|
||||
const byPref = expectedSongs.filter(s => s.preferredPlayTimeId).length
|
||||
const prefInfo = byPref ? `,其中 ${byPref} 首按期望播出时段排入` : ''
|
||||
const byPref = pool.filter(s => s.preferredPlayTimeId || s.expectedPlayDate).length
|
||||
const prefInfo = byPref ? `,其中 ${byPref} 首按期望日期/时段排入` : ''
|
||||
showToast(`已自动排入 ${count} 首待排歌曲${strategyLabel}${prefInfo},请注意调整顺序后保存`, 'success')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user