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
+65 -19
View File
@@ -86,13 +86,48 @@ const aiPrompt = ref('anime style school campus sunset beautiful sky highly deta
const isGeneratingAi = ref(false)
const bgImageObj = ref(null)
const maxDisplaySongs = 35
const maxDisplaySongs = 200
const songLeftX = 44
const songRightX = 360
const songAreaWidth = songRightX - songLeftX
const fitText = (ctx, text, fontSize, maxWidth, lineHeight) => {
ctx.font = `${fontSize}px "Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif`
const chars = Array.from(text)
const lines = []
let current = ''
for (const ch of chars) {
const tryStr = current + ch
const w = ctx.measureText(tryStr).width
if (w > maxWidth && current) {
lines.push(current)
current = ch
} else {
current = tryStr
}
}
if (current) lines.push(current)
return { lines, lineHeight }
}
const canvasHeight = computed(() => {
const count = Math.min(props.songs.length, maxDisplaySongs)
if (count <= 10) return 600
// 每多一首增加 40px 高度
return 600 + (count - 10) * 40
if (!count) return 600
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
let totalH = 0
const maxWidthTitle = songAreaWidth
const maxWidthArtist = Math.max(100, songAreaWidth - 10)
for (let i = 0; i < count; i++) {
const s = props.songs[i]
const song = s?.song || s || {}
const title = song.title || '未知歌曲'
const artist = song.artist || '未知歌手'
const t1 = fitText(ctx, title, 16, maxWidthTitle, 22)
const t2 = fitText(ctx, `- ${artist}`, 13, maxWidthArtist, 18)
totalH += Math.max(28, (t1.lines.length * t1.lineHeight) + (t2.lines.length * t2.lineHeight) + 10)
}
return 240 + totalH
})
@@ -155,33 +190,44 @@ const drawCanvas = () => {
// 绘制歌曲列表
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
let y = 160
const displaySongs = props.songs.slice(0, maxDisplaySongs) // 最多显示 35 首
const displaySongs = props.songs.slice(0, maxDisplaySongs)
displaySongs.forEach((schedule, index) => {
const song = schedule.song || schedule
// 序号
ctx.fillStyle = '#2563eb'
ctx.font = 'bold 16px "Inter", "system-ui", sans-serif'
ctx.font = 'bold 16px "Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif'
ctx.textBaseline = 'top'
ctx.fillText(`${index + 1}.`, 40, y)
// 歌名
ctx.fillStyle = '#0F172A' // Neutral Dark
ctx.font = 'bold 16px "Inter", "system-ui", sans-serif'
// 限制歌名长度
let title = song.title || '未知歌曲'
if (title.length > 12) title = title.substring(0, 12) + '...'
ctx.fillText(title, 70, y)
const maxWidthTitle = songAreaWidth
const maxWidthArtist = Math.max(120, songAreaWidth - 10)
const title = song.title || '未知歌曲'
const artist = song.artist || '未知歌手'
// 歌手
const t1 = fitText(ctx, title, 16, maxWidthTitle, 22)
ctx.fillStyle = '#0F172A'
ctx.font = `bold 16px "Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif`
let ty = y
for (const line of t1.lines) {
ctx.fillText(line, songLeftX, ty)
ty += t1.lineHeight
}
const t2 = fitText(ctx, `- ${artist}`, 13, maxWidthArtist, 18)
ctx.fillStyle = '#64748B'
ctx.font = '14px "Inter", "system-ui", sans-serif'
let artist = song.artist || '未知歌手'
if (artist.length > 10) artist = artist.substring(0, 10) + '...'
ctx.fillText(`- ${artist}`, 240, y)
ctx.font = `13px "Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif`
let ay = ty + 4
for (const line of t2.lines) {
ctx.fillText(line, songLeftX, ay)
ay += t2.lineHeight
}
y += 40
const songBlockHeight = Math.max(28, (ty - y) + (ay - ty) + 4)
y += songBlockHeight + 8
})
if (props.songs.length > maxDisplaySongs) {