diff --git a/app/components/Admin/AdminDatePicker.vue b/app/components/Admin/AdminDatePicker.vue index fb513ad..7303b81 100644 --- a/app/components/Admin/AdminDatePicker.vue +++ b/app/components/Admin/AdminDatePicker.vue @@ -244,7 +244,9 @@ const scrollToDateElement = (behavior = 'smooth', targetDate = props.modelValue) const dateKey = targetDate || formatDateStr(today) const el = dateSelector.value.querySelector(`[data-date="${dateKey}"]`) if (!el) return - el.scrollIntoView({ behavior, block: 'nearest', inline: 'center' }) + const cw = dateSelector.value.clientWidth + const target = el.offsetLeft - (cw - el.offsetWidth) / 2 + dateSelector.value.scrollTo({ left: target, behavior }) } const handleWheel = (event) => { diff --git a/app/components/Admin/AdminMultiDatePicker.vue b/app/components/Admin/AdminMultiDatePicker.vue index dfe6b9f..e3dc583 100644 --- a/app/components/Admin/AdminMultiDatePicker.vue +++ b/app/components/Admin/AdminMultiDatePicker.vue @@ -380,7 +380,10 @@ const handleScroll = () => { const scrollToDateElement = (targetDate, behavior = 'smooth') => { if (!dateSelector.value || !targetDate) return const el = dateSelector.value.querySelector(`[data-date="${targetDate}"]`) - if (el) el.scrollIntoView({ behavior, block: 'nearest', inline: 'center' }) + if (!el) return + const cw = dateSelector.value.clientWidth + const target = el.offsetLeft - (cw - el.offsetWidth) / 2 + dateSelector.value.scrollTo({ left: target, behavior }) } const scrollToToday = () => { diff --git a/app/components/Admin/PlaylistCardGenerator.vue b/app/components/Admin/PlaylistCardGenerator.vue index f612fd6..64ddb84 100644 --- a/app/components/Admin/PlaylistCardGenerator.vue +++ b/app/components/Admin/PlaylistCardGenerator.vue @@ -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) { diff --git a/app/components/Admin/PlaylistCardPage.vue b/app/components/Admin/PlaylistCardPage.vue index fb82f60..7146463 100644 --- a/app/components/Admin/PlaylistCardPage.vue +++ b/app/components/Admin/PlaylistCardPage.vue @@ -1,67 +1,130 @@