refactor: 收口 BangDream 歌曲列表规格

This commit is contained in:
sunlei 2026-06-07 04:32:10 +08:00
parent e1ace51d6e
commit b360a005e7
4 changed files with 189 additions and 30 deletions

View File

@ -387,6 +387,7 @@ export interface TsuguHook {
- 已新增 `event-stage-spec.spec.ts`,覆盖试炼歌曲行尺寸、类型顶部文字规格、换列判断和按列高拆分算法;本地生成 `event-stage-split-310*.jpg`、`event-stage-split-310-meta*.jpg`,验证 `/查试炼 310``/查试炼 310 -m` 图片输出正常。
- 已新增 `render-blocks/list-player-card-icon-spec.ts`,收口玩家详情主卡组展示顺序、默认行高、文本字号比例、卡牌间距比例和卡牌图标可见性标记;`list-player-card-icon-list.ts` 改为消费 spec主卡组渲染顺序和输出结构保持不变`list-player-card-icon-spec.spec.ts` 覆盖历史卡牌顺序、缺失条目跳过和字号/间距计算,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-card-icon-spec.ts`,收口通用卡牌图标列表默认行高、文本字号比例、卡牌间距比例、默认可见性标记和历史排序比较器;`list-card-icon-list.ts` 改为消费 spec卡牌图标列表排序和输出结构保持不变`list-card-icon-spec.spec.ts` 覆盖稀有度排序、优先卡牌类型排序、普通卡按 ID 排序和字号/间距计算,本地 `/查卡 472` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-song-spec.ts`,收口歌曲列表单行封面、文本、难度块位置、列表内容宽度、分割线高度和外层行高计算;`list-song.ts` 改为消费 spec歌曲列表绘制顺序和输出结构保持不变`list-song-spec.spec.ts` 覆盖单行尺寸、难度块垂直居中和歌曲组列表尺寸,本地 `/查曲 136` 图片 smoke 输出非空。
- smoke/Jenkins/远程调试卡点继续按已固化规则处理同一卡点第二次尝试前必须改变可验证变量Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值smoke 没有真实图片落盘时必须失败。
### Phase 6策略 policy 和时间/档线规则

View File

@ -0,0 +1,91 @@
import { getListFrameTextMaxWidth } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-frame-spec';
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
interface ImageLike {
height: number;
width: number;
}
export const BANGDREAM_SONG_LIST_SPEC = {
item: {
canvasHeight: 75,
difficultyHeight: 45,
difficultySpacing: 10,
idTextX: 0,
idTextY: 0,
jacketHeight: 65,
jacketSourceHeightMax: 80,
jacketSourceWidthMax: 80,
jacketWidth: 65,
jacketX: 50,
jacketY: 5,
textLineHeight: 37.5,
textSize: 23,
titleTextX: 120,
titleTextY: 0,
},
list: {
key: '歌榜歌曲',
lineHeightPadding: 20,
separatorHeight: 10,
spacing: 0,
},
} as const;
/**
*
*
* @param difficultyImage -
* @param contentWidth -
*/
export function createSongInListLayout(
difficultyImage: ImageLike,
contentWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth,
) {
const item = BANGDREAM_SONG_LIST_SPEC.item;
return {
canvasHeight: item.canvasHeight,
canvasWidth: contentWidth,
difficultyX: contentWidth - difficultyImage.width,
difficultyY: item.canvasHeight / 2 - difficultyImage.height / 2,
idTextX: item.idTextX,
idTextY: item.idTextY,
jacketHeight: item.jacketHeight,
jacketSourceHeightMax: item.jacketSourceHeightMax,
jacketSourceWidthMax: item.jacketSourceWidthMax,
jacketWidth: item.jacketWidth,
jacketX: item.jacketX,
jacketY: item.jacketY,
textLineHeight: item.textLineHeight,
textMaxWidth: contentWidth,
textSize: item.textSize,
titleTextX: item.titleTextX,
titleTextY: item.titleTextY,
};
}
/**
*
*/
export function getSongListContentWidth() {
return getListFrameTextMaxWidth(BANGDREAM_RENDER_THEME.layout.contentWidth);
}
/**
*
*
* @param songCount -
*/
export function getSongListCanvasHeight(songCount: number) {
const { item, list } = BANGDREAM_SONG_LIST_SPEC;
return item.canvasHeight * songCount + list.separatorHeight * (songCount - 1);
}
/**
*
*
* @param canvasHeight -
*/
export function getSongListFrameLineHeight(canvasHeight: number) {
return canvasHeight + BANGDREAM_SONG_LIST_SPEC.list.lineHeightPadding;
}

View File

@ -11,8 +11,14 @@ import { drawDifficultyList, drawDifficulty } from './list-difficulty';
import { globalDefaultServer } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
import { drawList } from './list-frame';
import { drawDottedLine } from '@/qqbot/plugins/bangDream/tsugu/canvas/dotted-line';
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
import { createHorizontalSeparatorSpec } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/layout-spec';
import {
BANGDREAM_SONG_LIST_SPEC,
createSongInListLayout,
getSongListCanvasHeight,
getSongListContentWidth,
getSongListFrameLineHeight,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-song-spec';
/**
* In列表
@ -32,21 +38,40 @@ export async function drawSongInList(
const server = getServerByPriority(song.publishedAt, displayedServerList);
const songImage = resizeImage({
image: await song.getSongJacketImage(),
widthMax: 80,
heightMax: 80,
widthMax: BANGDREAM_SONG_LIST_SPEC.item.jacketSourceWidthMax,
heightMax: BANGDREAM_SONG_LIST_SPEC.item.jacketSourceHeightMax,
});
const canvas = new Canvas(BANGDREAM_RENDER_THEME.layout.contentWidth, 75);
const difficultyImage =
difficulty == undefined
? drawDifficultyList(
song,
BANGDREAM_SONG_LIST_SPEC.item.difficultyHeight,
BANGDREAM_SONG_LIST_SPEC.item.difficultySpacing,
)
: drawDifficulty(
difficulty,
song.difficulty[difficulty].playLevel,
BANGDREAM_SONG_LIST_SPEC.item.difficultyHeight,
);
const layout = createSongInListLayout(difficultyImage);
const canvas = new Canvas(layout.canvasWidth, layout.canvasHeight);
const ctx = canvas.getContext('2d');
ctx.drawImage(songImage, 50, 5, 65, 65);
ctx.drawImage(
songImage,
layout.jacketX,
layout.jacketY,
layout.jacketWidth,
layout.jacketHeight,
);
//id
const idImage = drawText({
text: song.songId.toString(),
textSize: 23,
lineHeight: 37.5,
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
textSize: layout.textSize,
lineHeight: layout.textLineHeight,
maxWidth: layout.textMaxWidth,
});
ctx.drawImage(idImage, 0, 0);
ctx.drawImage(idImage, layout.idTextX, layout.idTextY);
//曲名与乐队名
let fullText = `${song.musicTitle[server]}`;
if (!text) {
@ -58,22 +83,13 @@ export async function drawSongInList(
}
const textImage = drawText({
text: fullText,
textSize: 23,
lineHeight: 37.5,
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
textSize: layout.textSize,
lineHeight: layout.textLineHeight,
maxWidth: layout.textMaxWidth,
});
ctx.drawImage(textImage, 120, 0);
ctx.drawImage(textImage, layout.titleTextX, layout.titleTextY);
//难度
const difficultyImage =
difficulty == undefined
? drawDifficultyList(song, 45, 10)
: drawDifficulty(difficulty, song.difficulty[difficulty].playLevel, 45);
ctx.drawImage(
difficultyImage,
BANGDREAM_RENDER_THEME.layout.contentWidth - difficultyImage.width,
75 / 2 - difficultyImage.height / 2,
);
ctx.drawImage(difficultyImage, layout.difficultyX, layout.difficultyY);
return canvas;
}
@ -92,13 +108,18 @@ export async function drawSongListInList(
text?: string,
displayedServerList: Server[] = globalDefaultServer,
): Promise<Canvas> {
const height: number = 75 * songs.length + 10 * (songs.length - 1);
const canvas = new Canvas(760, height);
const contentWidth = getSongListContentWidth();
const height = getSongListCanvasHeight(songs.length);
const canvas = new Canvas(contentWidth, height);
const ctx = canvas.getContext('2d');
const x = 0;
let y = 0;
const views: Canvas[] = [];
const line = drawDottedLine(createHorizontalSeparatorSpec({ height: 10 }));
const line = drawDottedLine(
createHorizontalSeparatorSpec({
height: BANGDREAM_SONG_LIST_SPEC.list.separatorHeight,
}),
);
for (let i = 0; i < songs.length; i++) {
views.push(
resizeImage({
@ -108,7 +129,7 @@ export async function drawSongListInList(
text,
displayedServerList,
),
widthMax: 760,
widthMax: contentWidth,
}),
);
views.push(line);
@ -119,10 +140,10 @@ export async function drawSongListInList(
y += views[i].height;
}
return await drawList({
key: '歌榜歌曲',
key: BANGDREAM_SONG_LIST_SPEC.list.key,
content: [canvas],
textSize: canvas.height,
lineHeight: canvas.height + 20,
spacing: 0,
lineHeight: getSongListFrameLineHeight(canvas.height),
spacing: BANGDREAM_SONG_LIST_SPEC.list.spacing,
});
}

View File

@ -0,0 +1,46 @@
import {
BANGDREAM_SONG_LIST_SPEC,
createSongInListLayout,
getSongListCanvasHeight,
getSongListContentWidth,
getSongListFrameLineHeight,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-song-spec';
describe('BangDream song list spec', () => {
it('keeps song list item dimensions stable', () => {
const layout = createSongInListLayout({ height: 45, width: 270 });
expect(layout).toEqual({
canvasHeight: 75,
canvasWidth: 800,
difficultyX: 530,
difficultyY: 15,
idTextX: 0,
idTextY: 0,
jacketHeight: 65,
jacketSourceHeightMax: 80,
jacketSourceWidthMax: 80,
jacketWidth: 65,
jacketX: 50,
jacketY: 5,
textLineHeight: 37.5,
textMaxWidth: 800,
textSize: 23,
titleTextX: 120,
titleTextY: 0,
});
});
it('centers the difficulty image vertically for the row height', () => {
expect(createSongInListLayout({ height: 30, width: 100 }).difficultyY).toBe(
22.5,
);
});
it('keeps song list group dimensions stable', () => {
expect(BANGDREAM_SONG_LIST_SPEC.list.key).toBe('歌榜歌曲');
expect(getSongListContentWidth()).toBe(760);
expect(getSongListCanvasHeight(3)).toBe(245);
expect(getSongListFrameLineHeight(245)).toBe(265);
});
});