refactor: 收口 BangDream 卡牌标题块规格

This commit is contained in:
sunlei 2026-06-07 05:29:45 +08:00
parent e48ea5100a
commit 6bda3f306f
4 changed files with 144 additions and 19 deletions

View File

@ -393,6 +393,7 @@ export interface TsuguHook {
- 已新增 `render-blocks/list-band-detail-spec.ts`,收口玩家详情乐队等级、舞台挑战达成情况和乐队编成等级列表的 Logo 缩放、正文宽度/行高、项画布、通用列表框架和 Rank 等级图片布局;`list-band-detail.ts` 改为消费 spec玩家详情里的乐队详情绘制顺序、资源 repository 和输出结构保持不变;`list-band-detail-spec.spec.ts` 覆盖 Logo/正文参数、项布局、列表框架、Rank 画布/图片位置和等级图片 Rank ID 封顶,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。 - 已新增 `render-blocks/list-band-detail-spec.ts`,收口玩家详情乐队等级、舞台挑战达成情况和乐队编成等级列表的 Logo 缩放、正文宽度/行高、项画布、通用列表框架和 Rank 等级图片布局;`list-band-detail.ts` 改为消费 spec玩家详情里的乐队详情绘制顺序、资源 repository 和输出结构保持不变;`list-band-detail-spec.spec.ts` 覆盖 Logo/正文参数、项布局、列表框架、Rank 画布/图片位置和等级图片 Rank ID 封顶,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-character-detail-spec.ts`,收口玩家详情角色等级列表的头像缩放、正文宽度/行高、项画布和通用列表框架;`list-character-detail.ts` 改为消费 spec玩家详情角色等级绘制顺序、角色数据读取和输出结构保持不变`list-character-detail-spec.spec.ts` 覆盖头像/正文参数、项布局和列表框架参数,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。 - 已新增 `render-blocks/list-character-detail-spec.ts`,收口玩家详情角色等级列表的头像缩放、正文宽度/行高、项画布和通用列表框架;`list-character-detail.ts` 改为消费 spec玩家详情角色等级绘制顺序、角色数据读取和输出结构保持不变`list-character-detail-spec.spec.ts` 覆盖头像/正文参数、项布局和列表框架参数,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-card-sd-character-spec.ts`,收口卡牌详情演出缩略图的 SD 角色 sprite 裁切网格、列表缩放行高、字号和间距;`list-card-sd-character.ts` 改为消费 spec素材加载、四帧裁切和列表绘制顺序保持不变`list-card-sd-character-spec.spec.ts` 覆盖四帧裁切坐标、单帧裁切计算和列表规格,本地 `/查卡 472` 图片 smoke 输出非空。 - 已新增 `render-blocks/list-card-sd-character-spec.ts`,收口卡牌详情演出缩略图的 SD 角色 sprite 裁切网格、列表缩放行高、字号和间距;`list-card-sd-character.ts` 改为消费 spec素材加载、四帧裁切和列表绘制顺序保持不变`list-card-sd-character-spec.spec.ts` 覆盖四帧裁切坐标、单帧裁切计算和列表规格,本地 `/查卡 472` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-card-prefix-spec.ts`,收口卡牌详情顶部标题块的画布、背景、乐队 Logo 等比缩放、卡牌标题和角色名文字布局;`list-card-prefix.ts` 改为消费 spec乐队/角色/服务器优先级读取和绘制顺序保持不变;`list-card-prefix-spec.spec.ts` 覆盖标题块尺寸、背景、文字坐标和 Logo 缩放,本地 `/查卡 472` 图片 smoke 输出非空。
- smoke/Jenkins/远程调试卡点继续按已固化规则处理同一卡点第二次尝试前必须改变可验证变量Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值smoke 没有真实图片落盘时必须失败。 - smoke/Jenkins/远程调试卡点继续按已固化规则处理同一卡点第二次尝试前必须改变可验证变量Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值smoke 没有真实图片落盘时必须失败。
### Phase 6策略 policy 和时间/档线规则 ### Phase 6策略 policy 和时间/档线规则

View File

@ -0,0 +1,56 @@
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
export interface CardPrefixBandLogoSource {
height: number;
width: number;
}
export const BANGDREAM_CARD_PREFIX_SPEC = {
background: {
color: '#f1f1ef',
radius: [15, 15, 0, 0],
},
bandLogo: {
width: 240,
x: 30,
y: 25,
},
canvas: {
height: 155,
width: 800,
},
text: {
align: 'left',
baseline: 'hanging',
characterName: {
fontSize: 40,
maxWidth: 470,
x: 300,
y: 75,
},
color: BANGDREAM_RENDER_THEME.color.labelBackground,
font: BANGDREAM_RENDER_THEME.font.body,
prefix: {
fontSize: 30,
maxWidth: 470,
x: 300,
y: 35,
},
},
} as const;
/**
* Logo
*
* @param source - Logo
*/
export function getCardPrefixBandLogoLayout(source: CardPrefixBandLogoSource) {
const { width, x, y } = BANGDREAM_CARD_PREFIX_SPEC.bandLogo;
return {
height: (source.height * width) / source.width,
width,
x,
y,
};
}

View File

@ -9,6 +9,10 @@ import {
} from '@/qqbot/plugins/bangDream/tsugu/models/server'; } from '@/qqbot/plugins/bangDream/tsugu/models/server';
import { setFontStyle } from '@/qqbot/plugins/bangDream/tsugu/canvas/text'; import { setFontStyle } from '@/qqbot/plugins/bangDream/tsugu/canvas/text';
import { drawRoundedRect } from '@/qqbot/plugins/bangDream/tsugu/canvas/rect'; import { drawRoundedRect } from '@/qqbot/plugins/bangDream/tsugu/canvas/rect';
import {
BANGDREAM_CARD_PREFIX_SPEC,
getCardPrefixBandLogoLayout,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix-spec';
let prefixBG: Canvas; let prefixBG: Canvas;
/** /**
@ -16,10 +20,10 @@ let prefixBG: Canvas;
*/ */
async function loadImageOnce() { async function loadImageOnce() {
prefixBG = drawRoundedRect({ prefixBG = drawRoundedRect({
width: 800, width: BANGDREAM_CARD_PREFIX_SPEC.canvas.width,
height: 155, height: BANGDREAM_CARD_PREFIX_SPEC.canvas.height,
color: '#f1f1ef', color: BANGDREAM_CARD_PREFIX_SPEC.background.color,
radius: [15, 15, 0, 0], radius: [...BANGDREAM_CARD_PREFIX_SPEC.background.radius],
}); });
} }
loadImageOnce(); loadImageOnce();
@ -34,38 +38,57 @@ export async function drawCardPrefixInList(
card: Card, card: Card,
displayedServerList: Server[] = globalDefaultServer, displayedServerList: Server[] = globalDefaultServer,
) { ) {
const canvas = new Canvas(800, 155); const canvas = new Canvas(
BANGDREAM_CARD_PREFIX_SPEC.canvas.width,
BANGDREAM_CARD_PREFIX_SPEC.canvas.height,
);
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.drawImage(prefixBG, 0, 0); ctx.drawImage(prefixBG, 0, 0);
//bandLogo
const band = new Band(card.bandId); const band = new Band(card.bandId);
const bandLogo = await band.getLogo(); const bandLogo = await band.getLogo();
const bandLogoLayout = getCardPrefixBandLogoLayout(bandLogo);
ctx.drawImage( ctx.drawImage(
bandLogo, bandLogo,
30, bandLogoLayout.x,
25, bandLogoLayout.y,
240, bandLogoLayout.width,
(bandLogo.height * 240) / bandLogo.width, bandLogoLayout.height,
); );
//prefix
const server = getServerByPriority(card.releasedAt, displayedServerList); const server = getServerByPriority(card.releasedAt, displayedServerList);
ctx.fillStyle = '#5b5b5b'; ctx.fillStyle = BANGDREAM_CARD_PREFIX_SPEC.text.color;
ctx.textBaseline = 'hanging'; ctx.textBaseline = BANGDREAM_CARD_PREFIX_SPEC.text.baseline;
ctx.textAlign = 'left'; ctx.textAlign = BANGDREAM_CARD_PREFIX_SPEC.text.align;
setFontStyle(ctx, 30, 'old'); setFontStyle(
ctx.fillText(card.prefix[server], 300, 35, 470); ctx,
BANGDREAM_CARD_PREFIX_SPEC.text.prefix.fontSize,
BANGDREAM_CARD_PREFIX_SPEC.text.font,
);
ctx.fillText(
card.prefix[server],
BANGDREAM_CARD_PREFIX_SPEC.text.prefix.x,
BANGDREAM_CARD_PREFIX_SPEC.text.prefix.y,
BANGDREAM_CARD_PREFIX_SPEC.text.prefix.maxWidth,
);
//characterName
const character = new Character(card.characterId); const character = new Character(card.characterId);
const tempserver = getServerByPriority( const tempserver = getServerByPriority(
character.characterName, character.characterName,
displayedServerList, displayedServerList,
); );
const characterName = character.characterName[tempserver]; const characterName = character.characterName[tempserver];
setFontStyle(ctx, 40, 'old'); setFontStyle(
ctx.fillText(characterName, 300, 75, 470); ctx,
BANGDREAM_CARD_PREFIX_SPEC.text.characterName.fontSize,
BANGDREAM_CARD_PREFIX_SPEC.text.font,
);
ctx.fillText(
characterName,
BANGDREAM_CARD_PREFIX_SPEC.text.characterName.x,
BANGDREAM_CARD_PREFIX_SPEC.text.characterName.y,
BANGDREAM_CARD_PREFIX_SPEC.text.characterName.maxWidth,
);
return canvas; return canvas;
} }

View File

@ -0,0 +1,45 @@
import {
BANGDREAM_CARD_PREFIX_SPEC,
getCardPrefixBandLogoLayout,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix-spec';
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
describe('BangDream card prefix spec', () => {
it('keeps the card prefix canvas and background stable', () => {
expect(BANGDREAM_CARD_PREFIX_SPEC.canvas).toEqual({
height: 155,
width: 800,
});
expect(BANGDREAM_CARD_PREFIX_SPEC.background).toEqual({
color: '#f1f1ef',
radius: [15, 15, 0, 0],
});
});
it('keeps the historical prefix and character text layout', () => {
expect(BANGDREAM_CARD_PREFIX_SPEC.text.color).toBe(
BANGDREAM_RENDER_THEME.color.labelBackground,
);
expect(BANGDREAM_CARD_PREFIX_SPEC.text.prefix).toEqual({
fontSize: 30,
maxWidth: 470,
x: 300,
y: 35,
});
expect(BANGDREAM_CARD_PREFIX_SPEC.text.characterName).toEqual({
fontSize: 40,
maxWidth: 470,
x: 300,
y: 75,
});
});
it('scales the band logo by the fixed display width', () => {
expect(getCardPrefixBandLogoLayout({ height: 120, width: 360 })).toEqual({
height: 80,
width: 240,
x: 30,
y: 25,
});
});
});