From 6bda3f306fb3404313352973f73b56d63dee1c55 Mon Sep 17 00:00:00 2001 From: sunlei Date: Sun, 7 Jun 2026 05:29:45 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=20BangDream=20?= =?UTF-8?q?=E5=8D=A1=E7=89=8C=E6=A0=87=E9=A2=98=E5=9D=97=E8=A7=84=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ot-bangdream-tsugu-global-refactor-plan.md | 1 + .../render-blocks/list-card-prefix-spec.ts | 56 +++++++++++++++++ .../tsugu/render-blocks/list-card-prefix.ts | 61 +++++++++++++------ .../tsugu/list-card-prefix-spec.spec.ts | 45 ++++++++++++++ 4 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix-spec.ts create mode 100644 test/qqbot/plugins/bangDream/tsugu/list-card-prefix-spec.spec.ts diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index d3eba57..16442d8 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -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-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-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 没有真实图片落盘时必须失败。 ### Phase 6:策略 policy 和时间/档线规则 diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix-spec.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix-spec.ts new file mode 100644 index 0000000..6b6c631 --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix-spec.ts @@ -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, + }; +} diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix.ts index 1975f5a..5468deb 100644 --- a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix.ts +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-prefix.ts @@ -9,6 +9,10 @@ import { } from '@/qqbot/plugins/bangDream/tsugu/models/server'; import { setFontStyle } from '@/qqbot/plugins/bangDream/tsugu/canvas/text'; 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; /** @@ -16,10 +20,10 @@ let prefixBG: Canvas; */ async function loadImageOnce() { prefixBG = drawRoundedRect({ - width: 800, - height: 155, - color: '#f1f1ef', - radius: [15, 15, 0, 0], + width: BANGDREAM_CARD_PREFIX_SPEC.canvas.width, + height: BANGDREAM_CARD_PREFIX_SPEC.canvas.height, + color: BANGDREAM_CARD_PREFIX_SPEC.background.color, + radius: [...BANGDREAM_CARD_PREFIX_SPEC.background.radius], }); } loadImageOnce(); @@ -34,38 +38,57 @@ export async function drawCardPrefixInList( card: Card, 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'); ctx.drawImage(prefixBG, 0, 0); - //bandLogo const band = new Band(card.bandId); const bandLogo = await band.getLogo(); + const bandLogoLayout = getCardPrefixBandLogoLayout(bandLogo); ctx.drawImage( bandLogo, - 30, - 25, - 240, - (bandLogo.height * 240) / bandLogo.width, + bandLogoLayout.x, + bandLogoLayout.y, + bandLogoLayout.width, + bandLogoLayout.height, ); - //prefix const server = getServerByPriority(card.releasedAt, displayedServerList); - ctx.fillStyle = '#5b5b5b'; - ctx.textBaseline = 'hanging'; - ctx.textAlign = 'left'; - setFontStyle(ctx, 30, 'old'); - ctx.fillText(card.prefix[server], 300, 35, 470); + ctx.fillStyle = BANGDREAM_CARD_PREFIX_SPEC.text.color; + ctx.textBaseline = BANGDREAM_CARD_PREFIX_SPEC.text.baseline; + ctx.textAlign = BANGDREAM_CARD_PREFIX_SPEC.text.align; + setFontStyle( + 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 tempserver = getServerByPriority( character.characterName, displayedServerList, ); const characterName = character.characterName[tempserver]; - setFontStyle(ctx, 40, 'old'); - ctx.fillText(characterName, 300, 75, 470); + setFontStyle( + 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; } diff --git a/test/qqbot/plugins/bangDream/tsugu/list-card-prefix-spec.spec.ts b/test/qqbot/plugins/bangDream/tsugu/list-card-prefix-spec.spec.ts new file mode 100644 index 0000000..8f51546 --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/list-card-prefix-spec.spec.ts @@ -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, + }); + }); +});