feat(admin): 图片歌单改导入酷狗+自动排入按期望时段+日期滚动修复
构建并部署 BYW VoiceHub / Preflight 语法与构建检查 (push) Failing after 7m34s
构建并部署 BYW VoiceHub / 镜像构建·预热(与 Preflight 并发:系统/镜像源/Builder 初始化) (push) Successful in 7m54s
构建并部署 BYW VoiceHub / 构建并推送容器镜像 (push) Skipped
构建并部署 BYW VoiceHub / 部署到生产服务器 (push) Skipped

1. 菜单重命名: 图片歌单 -> 导入酷狗 (Sidebar/Dashboard/教程 + 页面标题)
2. PlaylistCardPage(导入酷狗) 新增:
   - 粘贴酷狗歌单文本解析(支持序号/横杠/括号格式)
   - 预览区歌名与歌手名完整显示(去除truncate, 支持换行展示)
   - 选日期+选播出时段, 一键将解析后的歌曲(没找到则自动创建)生成为该日期的排期
   - 批量保存后展示状态徽章(匹配/创建/失败)
3. PlaylistCardGenerator 画布:
   - 去除 substring(0,12)/substring(0,10) 截断, 改用 fitText 按 canvas 宽度逐字符换行切分
   - canvas 高度根据实际行数动态计算, 最多支持 200 首
   - 中文字体堆栈加入 PingFang SC/Microsoft YaHei
4. ScheduleManager.autoScheduleExpected:
   - 优先按歌曲自己的 preferredPlayTimeId(期望播出时段) 分组排入
   - 未指定才 fallback 到顶部当前选中时段
   - 分时段各自维护 sequence, 最后按时段 startTime 升序+分序 合并重排
   - 顶部过滤了单时段时, 只排入匹配该时段的歌曲(含未指定fallback)
5. 日期滚动修复:
   - AdminDatePicker / AdminMultiDatePicker scrollToDateElement 弃用 scrollIntoView
   - 改用精确的 offsetLeft - (clientWidth-offsetWidth)/2 计算 scrollTo left 值, 避免受父级滚动容器/左箭头影响
This commit is contained in:
2026-09-13 12:22:28 +08:00
parent 27d718bf01
commit ac4727551c
8 changed files with 355 additions and 84 deletions
+37 -4
View File
@@ -2075,17 +2075,36 @@ const autoScheduleExpected = () => {
}
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)
}
expectedSongs.forEach((song, idx) => {
const existingIndex = localScheduledSongs.value.findIndex((s) => s.song?.id === song.id)
if (existingIndex !== -1) return
const playTimeId = selectedPlayTime.value ? parseInt(selectedPlayTime.value) : null
let targetPlayTimeId = null
if (song.preferredPlayTimeId) {
const matched = (playTimes.value || []).find(pt => pt.id === song.preferredPlayTimeId)
if (matched) targetPlayTimeId = song.preferredPlayTimeId
}
if (!targetPlayTimeId && filteredPtId != null) targetPlayTimeId = filteredPtId
if (filteredPtId != null && targetPlayTimeId !== filteredPtId) return
const ptKey = String(targetPlayTimeId || '')
const nextSeq = (playTimeSeqCounters.get(ptKey) || 0) + 1
playTimeSeqCounters.set(ptKey, nextSeq)
const newSchedule = {
id: baseId + idx,
song: song,
playDate: selectedDate.value,
sequence: localScheduledSongs.value.length + 1,
playTimeId,
sequence: nextSeq,
playTimeId: targetPlayTimeId,
isNew: true,
isLocalOnly: true
}
@@ -2094,6 +2113,18 @@ const autoScheduleExpected = () => {
count++
})
const ptOrder = (id) => {
if (id == null || id === '') return 1e9
const pt = (playTimes.value || []).find(p => p.id === id)
if (!pt || !pt.startTime) return 1e8
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
})
@@ -2101,7 +2132,9 @@ const autoScheduleExpected = () => {
hasChanges.value = true
const strategyLabel =
autoScheduleStrategy.value === 'contributor-count' ? '(按投稿人数优先)' : ''
showToast(`已自动排入 ${count} 首待排歌曲${strategyLabel},请注意调整顺序后保存`, 'success')
const byPref = expectedSongs.filter(s => s.preferredPlayTimeId).length
const prefInfo = byPref ? `,其中 ${byPref} 首按期望播出时段排入` : ''
showToast(`已自动排入 ${count} 首待排歌曲${strategyLabel}${prefInfo},请注意调整顺序后保存`, 'success')
}
// 构建播放队列