From a826f0c5f129cedb6240388c08cd687cd8b09b7a Mon Sep 17 00:00:00 2001 From: sunlei Date: Sun, 7 Jun 2026 04:53:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=20BangDream=20?= =?UTF-8?q?=E9=9A=BE=E5=BA=A6=E8=AF=A6=E6=83=85=E5=88=97=E8=A1=A8=E8=A7=84?= =?UTF-8?q?=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 + .../list-difficulty-detail-spec.ts | 79 +++++++++++++++++++ .../render-blocks/list-difficulty-detail.ts | 46 ++++++----- .../tsugu/list-difficulty-detail-spec.spec.ts | 59 ++++++++++++++ 4 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 src/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail-spec.ts create mode 100644 test/qqbot/plugins/bangDream/tsugu/list-difficulty-detail-spec.spec.ts diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index d810419..c898f69 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -389,6 +389,7 @@ export interface TsuguHook { - 已新增 `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 输出非空。 - 已新增 `render-blocks/list-difficulty-spec.ts`,收口难度列表默认高度/间距、徽章兜底色、圆形布局和等级文字比例/居中计算;`list-difficulty.ts` 改为消费 spec,难度列表绘制顺序和输出结构保持不变;`list-difficulty-spec.spec.ts` 覆盖列表宽度、条目偏移、徽章颜色、圆形布局和文字居中,本地 `/查曲 136` 图片 smoke 输出非空。 +- 已新增 `render-blocks/list-difficulty-detail-spec.ts`,收口玩家难度详情列表的徽章宽度/字号/圆角、正文宽度/行高、项画布高度和通用列表行高/间距;`list-difficulty-detail.ts` 改为消费 spec,玩家详情里的已通关/Full Combo/All Perfect 难度详情绘制顺序和输出结构保持不变;`list-difficulty-detail-spec.spec.ts` 覆盖徽章参数、正文绘制参数、项布局和列表框架参数,本地 `/查玩家 26591455 jp` 图片 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-difficulty-detail-spec.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail-spec.ts new file mode 100644 index 0000000..d4f0526 --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail-spec.ts @@ -0,0 +1,79 @@ +interface ImageLike { + height: number; + width: number; +} + +export const BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC = { + item: { + badgeRadius: 5, + badgeTextSize: 30, + badgeWidth: 140, + textLineHeight: 40, + textOffsetY: 50, + width: 152, + }, + list: { + spacing: 0, + }, +} as const; + +/** + * 计算难度详情项徽章绘制参数。 + * + * @param difficultyName - 难度名称。 + * @param color - 难度颜色。 + */ +export function createDifficultyDetailBadgeSpec( + difficultyName: string, + color: string, +) { + const item = BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC.item; + return { + color, + radius: item.badgeRadius, + text: difficultyName.toUpperCase(), + textSize: item.badgeTextSize, + width: item.badgeWidth, + }; +} + +/** + * 计算难度详情正文绘制参数。 + */ +export function createDifficultyDetailTextSpec() { + const item = BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC.item; + return { + lineHeight: item.textLineHeight, + maxWidth: item.width, + }; +} + +/** + * 计算难度详情项画布和内容位置。 + * + * @param contentImage - 正文图片尺寸。 + */ +export function createDifficultyDetailItemLayout(contentImage: ImageLike) { + const item = BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC.item; + return { + badgeX: (item.width - item.badgeWidth) / 2, + badgeY: 0, + canvasHeight: contentImage.height + item.textOffsetY, + canvasWidth: item.width, + textX: item.width / 2 - contentImage.width / 2, + textY: item.textOffsetY, + }; +} + +/** + * 计算难度详情列表传给通用列表框架的尺寸。 + * + * @param firstItem - 第一个难度详情项。 + */ +export function createDifficultyDetailListFrameSpec(firstItem?: ImageLike) { + return { + lineHeight: firstItem?.height, + spacing: BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC.list.spacing, + textSize: firstItem?.height, + }; +} diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail.ts index a10c604..0b969d8 100644 --- a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail.ts +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail.ts @@ -7,6 +7,12 @@ import { Canvas, Image } from 'skia-canvas'; import { drawTextWithImages } from '@/qqbot/plugins/bangDream/tsugu/canvas/text'; import { drawList } from './list-frame'; import { Player } from '@/qqbot/plugins/bangDream/tsugu/models/player'; +import { + createDifficultyDetailBadgeSpec, + createDifficultyDetailItemLayout, + createDifficultyDetailListFrameSpec, + createDifficultyDetailTextSpec, +} from './list-difficulty-detail-spec'; interface drawDifficultyDetailInListOptions { [difficultyId: number]: Array; @@ -25,37 +31,35 @@ function DifficultyDetailInList( const difficultyAndContentList: Array = []; for (const i in DifficultyDetailInListOptions) { const content = DifficultyDetailInListOptions[i]; - const maxWidth = 152; - const logoWidth = 140; - const tempBandIcon = drawRoundedRectWithText({ - text: difficultyNameList[i].toUpperCase(), - width: logoWidth, - textSize: 30, - radius: 5, - color: difficultyColorList[i], - }); + const tempBandIcon = drawRoundedRectWithText( + createDifficultyDetailBadgeSpec( + difficultyNameList[i], + difficultyColorList[i], + ), + ); + const textSpec = createDifficultyDetailTextSpec(); const tempBandRankText = drawTextWithImages({ content, - maxWidth: maxWidth, - lineHeight: 40, + maxWidth: textSpec.maxWidth, + lineHeight: textSpec.lineHeight, }); - const canvas = new Canvas(maxWidth, tempBandRankText.height + 50); + const layout = createDifficultyDetailItemLayout(tempBandRankText); + const canvas = new Canvas(layout.canvasWidth, layout.canvasHeight); const ctx = canvas.getContext('2d'); - ctx.drawImage(tempBandIcon, (maxWidth - logoWidth) / 2, 0); - ctx.drawImage( - tempBandRankText, - maxWidth / 2 - tempBandRankText.width / 2, - 50, - ); + ctx.drawImage(tempBandIcon, layout.badgeX, layout.badgeY); + ctx.drawImage(tempBandRankText, layout.textX, layout.textY); difficultyAndContentList.push(canvas); } + const frameSpec = createDifficultyDetailListFrameSpec( + difficultyAndContentList?.[0], + ); const difficultyAndContentListImage = drawList({ key, content: difficultyAndContentList, - spacing: 0, - lineHeight: difficultyAndContentList?.[0].height, - textSize: difficultyAndContentList?.[0].height, + spacing: frameSpec.spacing, + lineHeight: frameSpec.lineHeight, + textSize: frameSpec.textSize, }); return difficultyAndContentListImage; } diff --git a/test/qqbot/plugins/bangDream/tsugu/list-difficulty-detail-spec.spec.ts b/test/qqbot/plugins/bangDream/tsugu/list-difficulty-detail-spec.spec.ts new file mode 100644 index 0000000..cb7b248 --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/list-difficulty-detail-spec.spec.ts @@ -0,0 +1,59 @@ +import { + BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC, + createDifficultyDetailBadgeSpec, + createDifficultyDetailItemLayout, + createDifficultyDetailListFrameSpec, + createDifficultyDetailTextSpec, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-detail-spec'; + +describe('BangDream difficulty detail list spec', () => { + it('keeps difficulty detail item constants stable', () => { + expect(BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC.item).toEqual({ + badgeRadius: 5, + badgeTextSize: 30, + badgeWidth: 140, + textLineHeight: 40, + textOffsetY: 50, + width: 152, + }); + expect(BANGDREAM_DIFFICULTY_DETAIL_LIST_SPEC.list.spacing).toBe(0); + }); + + it('creates badge spec with historical uppercase text', () => { + expect(createDifficultyDetailBadgeSpec('expert', '#ff0000')).toEqual({ + color: '#ff0000', + radius: 5, + text: 'EXPERT', + textSize: 30, + width: 140, + }); + }); + + it('keeps text drawing spec stable', () => { + expect(createDifficultyDetailTextSpec()).toEqual({ + lineHeight: 40, + maxWidth: 152, + }); + }); + + it('keeps item layout offsets stable', () => { + expect(createDifficultyDetailItemLayout({ height: 72, width: 88 })).toEqual({ + badgeX: 6, + badgeY: 0, + canvasHeight: 122, + canvasWidth: 152, + textX: 32, + textY: 50, + }); + }); + + it('keeps list frame values derived from the first item', () => { + expect(createDifficultyDetailListFrameSpec({ height: 122, width: 152 })).toEqual( + { + lineHeight: 122, + spacing: 0, + textSize: 122, + }, + ); + }); +});