refactor: 接入 BangDream 详情区块构建器
This commit is contained in:
parent
1603f7f657
commit
5a02effd9d
@ -345,6 +345,8 @@ export interface TsuguHook {
|
||||
- 已新增 `render-blocks/layout-spec.ts`,统一横向/纵向虚线分割规格,歌曲列表、活动列表、活动详情和通用列表框架不再重复维护分割线宽高/颜色。
|
||||
- 已新增 `runtime/asset-manifest.ts`,收口本地 `BG`、`Card`、`Skill`、`SongChart`、字体和标题资源路径;`canvas/text.ts`、`canvas/rect.ts`、`canvas/output.ts`、`canvas/background.ts`、`card-art.ts`、`list-rarity.ts`、`skill-text.ts`、`title.ts`、`song-chart-preview.ts` 已改走 manifest。
|
||||
- 已生成查曲、查活动和查谱面 smoke 图片,验证 theme/spec/manifest 第一段迁移后本地图片输出非空,谱面预览资源路径未断。
|
||||
- 已新增 `render-blocks/detail-block-builder.ts`,活动详情页先接入 `DetailBlockBuilder` 统一 section、spacer 和 data block 拼装,去掉局部 `pushSection` 和手动分割线维护;`detail-block-builder.spec.ts` 覆盖 section 分割线与 spacer/data block 输出。
|
||||
- 本地验证时发现 Jest 无法解析 `skia-canvas/lib/v6/index.node`,根因是 `moduleFileExtensions` 未包含 `node`;已把 `.node` 原生扩展解析固化进 Jest 配置,避免后续渲染单测重复卡在同类问题。
|
||||
- 线上 `/qqbot/command/test` smoke 已固化为先按 `operationKey` 查询启用命令、传 `commandId`,并保留完整命令文本;避免默认 `preview` selfId 未绑定命令时误报“未匹配到命令”。
|
||||
|
||||
### Phase 6:策略 policy 和时间/档线规则
|
||||
|
||||
@ -104,6 +104,7 @@
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"node",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": ".",
|
||||
|
||||
@ -2,7 +2,6 @@ import { Event } from '@/qqbot/plugins/bangDream/tsugu/models/event';
|
||||
import { Card } from '@/qqbot/plugins/bangDream/tsugu/models/card';
|
||||
import {
|
||||
drawList,
|
||||
line,
|
||||
drawListByServerList,
|
||||
drawListMerge,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-frame';
|
||||
@ -37,7 +36,7 @@ import {
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-song';
|
||||
import { drawDottedLine } from '@/qqbot/plugins/bangDream/tsugu/canvas/dotted-line';
|
||||
import { createHorizontalSeparatorSpec } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/layout-spec';
|
||||
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
|
||||
import { DetailBlockBuilder } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/detail-block-builder';
|
||||
|
||||
const songSeparatorLine = drawDottedLine(
|
||||
createHorizontalSeparatorSpec({ height: 10 }),
|
||||
@ -59,17 +58,6 @@ async function drawSongListDataBlock(songList: Song[], topLeftText?: string) {
|
||||
return drawDataBlock({ list, topLeftText });
|
||||
}
|
||||
|
||||
/**
|
||||
* 在QQBot 图片视图层中推入区块。
|
||||
*
|
||||
* @param list - 待处理列表。
|
||||
* @param section - 区块参数。
|
||||
*/
|
||||
function pushSection(list: Array<Image | Canvas>, section: Image | Canvas) {
|
||||
list.push(section);
|
||||
list.push(line);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在QQBot 图片视图层中判断对象是否包含指定自有属性。
|
||||
*
|
||||
@ -84,21 +72,20 @@ function hasOwn(source: object, key: string): boolean {
|
||||
/**
|
||||
* 在QQBot 图片视图层中追加活动加成区块列表。
|
||||
*
|
||||
* @param list - 待处理列表。
|
||||
* @param builder - 详情区块构建器。
|
||||
* @param event - 活动参数。
|
||||
*/
|
||||
async function appendEventBonusSections(
|
||||
list: Array<Image | Canvas>,
|
||||
builder: DetailBlockBuilder,
|
||||
event: Event,
|
||||
): Promise<void> {
|
||||
pushSection(list, drawList({ key: '活动加成' }));
|
||||
builder.addSection(drawList({ key: '活动加成' }));
|
||||
const attributeList = event.getAttributeList();
|
||||
for (const percent in attributeList) {
|
||||
if (!hasOwn(attributeList, percent)) {
|
||||
continue;
|
||||
}
|
||||
pushSection(
|
||||
list,
|
||||
builder.addSection(
|
||||
await drawAttributeInList({
|
||||
content: attributeList[percent],
|
||||
text: ` +${percent}%`,
|
||||
@ -106,14 +93,13 @@ async function appendEventBonusSections(
|
||||
);
|
||||
}
|
||||
|
||||
pushSection(list, drawList({ key: '活动角色加成' }));
|
||||
builder.addSection(drawList({ key: '活动角色加成' }));
|
||||
const characterList = event.getCharacterList();
|
||||
for (const percent in characterList) {
|
||||
if (!hasOwn(characterList, percent)) {
|
||||
continue;
|
||||
}
|
||||
pushSection(
|
||||
list,
|
||||
builder.addSection(
|
||||
await drawCharacterInList({
|
||||
content: characterList[percent],
|
||||
text: ` +${percent}%`,
|
||||
@ -146,16 +132,18 @@ function getEventStatBonusText(event: Event): string {
|
||||
/**
|
||||
* 在QQBot 图片视图层中追加活动数值加成。
|
||||
*
|
||||
* @param list - 待处理列表。
|
||||
* @param builder - 详情区块构建器。
|
||||
* @param event - 活动参数。
|
||||
*/
|
||||
function appendEventStatBonus(list: Array<Image | Canvas>, event: Event): void {
|
||||
function appendEventStatBonus(
|
||||
builder: DetailBlockBuilder,
|
||||
event: Event,
|
||||
): void {
|
||||
const statText = getEventStatBonusText(event);
|
||||
if (!statText) {
|
||||
return;
|
||||
}
|
||||
pushSection(
|
||||
list,
|
||||
builder.addSection(
|
||||
drawList({
|
||||
key: '活动偏科加成',
|
||||
text: statText,
|
||||
@ -166,19 +154,18 @@ function appendEventStatBonus(list: Array<Image | Canvas>, event: Event): void {
|
||||
/**
|
||||
* 在QQBot 图片视图层中追加活动奖励区块列表。
|
||||
*
|
||||
* @param list - 待处理列表。
|
||||
* @param builder - 详情区块构建器。
|
||||
* @param event - 活动参数。
|
||||
* @param displayedServerList - 允许展示或下载资源的服务器优先级列表。
|
||||
*/
|
||||
async function appendEventRewardSections(
|
||||
list: Array<Image | Canvas>,
|
||||
builder: DetailBlockBuilder,
|
||||
event: Event,
|
||||
displayedServerList: Server[],
|
||||
): Promise<void> {
|
||||
const decoImage = await event.getRewardDeco(displayedServerList[0]);
|
||||
if (decoImage) {
|
||||
pushSection(
|
||||
list,
|
||||
builder.addSection(
|
||||
await drawList({
|
||||
key: '活动装饰',
|
||||
content: [decoImage],
|
||||
@ -188,12 +175,11 @@ async function appendEventRewardSections(
|
||||
);
|
||||
}
|
||||
|
||||
pushSection(list, await drawDegreeListOfEvent(event, displayedServerList));
|
||||
builder.addSection(await drawDegreeListOfEvent(event, displayedServerList));
|
||||
|
||||
const stampImage = await event.getRewardStamp(displayedServerList[0]);
|
||||
if (stampImage) {
|
||||
pushSection(
|
||||
list,
|
||||
builder.addSection(
|
||||
await drawList({
|
||||
key: '活动表情',
|
||||
content: [stampImage],
|
||||
@ -204,8 +190,7 @@ async function appendEventRewardSections(
|
||||
}
|
||||
|
||||
const rewardCardList = event.rewardCards.map((cardId) => new Card(cardId));
|
||||
pushSection(
|
||||
list,
|
||||
builder.addSection(
|
||||
await drawCardListInList({
|
||||
key: '奖励卡牌',
|
||||
cardList: rewardCardList,
|
||||
@ -231,12 +216,12 @@ function getEventMusicServer(event: Event, displayedServerList: Server[]) {
|
||||
/**
|
||||
* 在QQBot 图片视图层中追加活动音乐区块。
|
||||
*
|
||||
* @param list - 待处理列表。
|
||||
* @param builder - 详情区块构建器。
|
||||
* @param event - 活动参数。
|
||||
* @param displayedServerList - 允许展示或下载资源的服务器优先级列表。
|
||||
*/
|
||||
async function appendEventMusicSection(
|
||||
list: Array<Image | Canvas>,
|
||||
builder: DetailBlockBuilder,
|
||||
event: Event,
|
||||
displayedServerList: Server[],
|
||||
): Promise<void> {
|
||||
@ -253,7 +238,7 @@ async function appendEventMusicSection(
|
||||
const songs = event.musics[musicServer].map(
|
||||
(music) => new Song(music.musicId),
|
||||
);
|
||||
pushSection(list, await drawSongListInList(songs));
|
||||
builder.addSection(await drawSongListInList(songs));
|
||||
}
|
||||
|
||||
interface EventGachaSections {
|
||||
@ -381,23 +366,21 @@ export async function drawEventDetail(
|
||||
return ['错误: 活动不存在'];
|
||||
}
|
||||
await event.initFull();
|
||||
const list: Array<Image | Canvas> = [];
|
||||
const builder = new DetailBlockBuilder();
|
||||
|
||||
//bannner
|
||||
const eventBannerImage = await event.getBannerImage();
|
||||
const eventBannerImageCanvas = drawBannerImageCanvas(eventBannerImage);
|
||||
list.push(eventBannerImageCanvas);
|
||||
list.push(new Canvas(BANGDREAM_RENDER_THEME.layout.contentWidth, 30));
|
||||
builder.add(eventBannerImageCanvas).addSpacer(30);
|
||||
|
||||
//标题
|
||||
list.push(
|
||||
builder.addSection(
|
||||
await drawListByServerList(
|
||||
event.eventName,
|
||||
'活动名称',
|
||||
displayedServerList,
|
||||
),
|
||||
);
|
||||
list.push(line);
|
||||
|
||||
//类型
|
||||
const typeImage = drawList({
|
||||
@ -411,11 +394,10 @@ export async function drawEventDetail(
|
||||
text: event.eventId.toString(),
|
||||
});
|
||||
|
||||
list.push(drawListMerge([typeImage, idImage]));
|
||||
list.push(line);
|
||||
builder.addSection(drawListMerge([typeImage, idImage]));
|
||||
|
||||
//开始时间
|
||||
list.push(
|
||||
builder.addSection(
|
||||
await drawTimeInList({
|
||||
key: '开始时间',
|
||||
content: event.startAt,
|
||||
@ -423,35 +405,33 @@ export async function drawEventDetail(
|
||||
estimateCNTime: true,
|
||||
}),
|
||||
);
|
||||
list.push(line);
|
||||
|
||||
//结束时间
|
||||
list.push(
|
||||
builder.addSection(
|
||||
await drawTimeInList({
|
||||
key: '结束时间',
|
||||
content: event.endAt,
|
||||
}),
|
||||
);
|
||||
list.push(line);
|
||||
|
||||
//活动属性加成
|
||||
await appendEventBonusSections(list, event);
|
||||
await appendEventBonusSections(builder, event);
|
||||
|
||||
//活动偏科加成(stat)
|
||||
appendEventStatBonus(list, event);
|
||||
appendEventStatBonus(builder, event);
|
||||
|
||||
//有歌榜活动的歌榜歌曲
|
||||
await appendEventMusicSection(list, event, displayedServerList);
|
||||
await appendEventMusicSection(builder, event, displayedServerList);
|
||||
|
||||
// 活动装饰、牌子、表情和奖励卡牌
|
||||
await appendEventRewardSections(list, event, displayedServerList);
|
||||
await appendEventRewardSections(builder, event, displayedServerList);
|
||||
|
||||
const { gachaCardList, gachaImageList } = await collectEventGachaSections(
|
||||
event,
|
||||
displayedServerList,
|
||||
);
|
||||
|
||||
list.push(
|
||||
builder.add(
|
||||
await drawCardListInList({
|
||||
key: '活动期间卡池卡牌',
|
||||
cardList: gachaCardList,
|
||||
@ -462,7 +442,7 @@ export async function drawEventDetail(
|
||||
}),
|
||||
);
|
||||
|
||||
const listImage = drawDataBlock({ list });
|
||||
const listImage = builder.toDataBlock();
|
||||
//创建最终输出数组
|
||||
|
||||
const all = [];
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import { Canvas, Image } from 'skia-canvas';
|
||||
import { drawDataBlock } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/data-block';
|
||||
import { line } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-frame';
|
||||
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
|
||||
|
||||
export interface DetailBlockDataOptions {
|
||||
BG?: boolean;
|
||||
opacity?: number;
|
||||
topLeftText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按详情页顺序收集区块,并统一处理区块分割线。
|
||||
*/
|
||||
export class DetailBlockBuilder {
|
||||
private readonly list: Array<Canvas | Image> = [];
|
||||
|
||||
add(block: Canvas | Image): this {
|
||||
this.list.push(block);
|
||||
return this;
|
||||
}
|
||||
|
||||
addSection(block: Canvas | Image): this {
|
||||
this.list.push(block);
|
||||
this.list.push(line);
|
||||
return this;
|
||||
}
|
||||
|
||||
addSpacer(
|
||||
height: number,
|
||||
width: number = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||
): this {
|
||||
this.list.push(new Canvas(width, height));
|
||||
return this;
|
||||
}
|
||||
|
||||
toList(): Array<Canvas | Image> {
|
||||
return [...this.list];
|
||||
}
|
||||
|
||||
toDataBlock(options: DetailBlockDataOptions = {}): Canvas {
|
||||
return drawDataBlock({ ...options, list: this.list });
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
import { Canvas } from 'skia-canvas';
|
||||
import { DetailBlockBuilder } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/detail-block-builder';
|
||||
import { line } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-frame';
|
||||
|
||||
describe('DetailBlockBuilder', () => {
|
||||
it('keeps section separators centralized', () => {
|
||||
const first = new Canvas(10, 20);
|
||||
const second = new Canvas(30, 40);
|
||||
const list = new DetailBlockBuilder()
|
||||
.addSection(first)
|
||||
.add(second)
|
||||
.toList();
|
||||
|
||||
expect(list).toEqual([first, line, second]);
|
||||
});
|
||||
|
||||
it('creates spacers and data blocks from collected sections', () => {
|
||||
const dataBlock = new DetailBlockBuilder()
|
||||
.add(new Canvas(100, 50))
|
||||
.addSpacer(30, 100)
|
||||
.toDataBlock();
|
||||
|
||||
expect(dataBlock.width).toBeGreaterThan(0);
|
||||
expect(dataBlock.height).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user