perf: 接入预览页面

This commit is contained in:
richarjiang
2026-04-06 15:43:48 +08:00
parent de674148b9
commit c7f52ab032
8 changed files with 3271 additions and 21 deletions

View File

@@ -0,0 +1,246 @@
import { _decorator, Node, Button, Sprite, Label, ScrollView, instantiate, UITransform, Vec2 } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager';
const { ccclass, property } = _decorator;
/**
* 预览试卷页面
* 垂直滚动展示用户在 PageWriteLevels 中选中的 6 个关卡
* 每个关卡展示封面图、提示1、提示2、答案
*
* prefab 节点结构(已在编辑器中搭建):
* PagePreviewLevels
* ├── Bg
* ├── IconBack ← backBtn (返回按钮)
* ├── PKTitle ← 标题 "挑战"
* ├── ScrollView ← scrollView
* │ ├── scrollBar
* │ └── view
* │ └── content ← listContent
* ├── ListTpl ← listTemplate (关卡模板)
* │ ├── LevelCover ← Sprite 封面图
* │ ├── Tips1 ← Label 提示1
* │ ├── Tips2 ← Label 提示2
* │ └── Answer ← Label 答案
* └── BackButton ← backButton (底部返回按钮)
*/
/** 布局配置 — 垂直列表 */
const LAYOUT = {
/** 关卡项高度(与 ListTpl UITransform 高度一致) */
ITEM_HEIGHT: 300,
/** 关卡项之间的垂直间距 */
SPACING_Y: 30,
/** 列表顶部内边距 */
PADDING_TOP: 20,
};
@ccclass('PagePreviewLevels')
export class PagePreviewLevels extends BaseView {
@property({ type: Node, tooltip: '返回按钮(左上角)' })
backBtn: Node | null = null;
@property({ type: Node, tooltip: 'ScrollView 节点' })
scrollView: Node | null = null;
@property({ type: Node, tooltip: '列表 content 节点' })
listContent: Node | null = null;
@property({ type: Node, tooltip: '关卡模板节点' })
listTemplate: Node | null = null;
@property({ type: Node, tooltip: '底部返回按钮' })
backButton: Node | null = null;
@property({ type: Node, tooltip: '标题Label节点' })
pkTitle: Node | null = null;
/** 已创建的 item 节点列表 */
private _itemNodes: Node[] = [];
// ─── 生命周期 ───────────────────────────────────────
onViewLoad(): void {
console.log('[PagePreviewLevels] onViewLoad');
this._initButtons();
this._initScrollView();
}
onViewShow(): void {
console.log('[PagePreviewLevels] onViewShow');
this._buildList();
}
onViewHide(): void {
console.log('[PagePreviewLevels] onViewHide');
}
onViewDestroy(): void {
console.log('[PagePreviewLevels] onViewDestroy');
this._offButtons();
this._clearList();
}
// ─── 初始化 ─────────────────────────────────────────
private _initButtons(): void {
if (this.backBtn) {
this.backBtn.on(Button.EventType.CLICK, this._onBackClick, this);
}
if (this.backButton) {
this.backButton.on(Button.EventType.CLICK, this._onBackClick, this);
}
}
private _offButtons(): void {
if (this.backBtn) {
this.backBtn.off(Button.EventType.CLICK, this._onBackClick, this);
}
if (this.backButton) {
this.backButton.off(Button.EventType.CLICK, this._onBackClick, this);
}
}
private _initScrollView(): void {
if (!this.listContent) return;
const contentTransform = this.listContent.getComponent(UITransform);
if (contentTransform) {
contentTransform.setAnchorPoint(0.5, 1);
}
}
// ─── 列表构建 ───────────────────────────────────────
private _buildList(): void {
this._clearList();
const params = this.getParams();
if (!params || !params.selectedIndices || params.selectedIndices.length === 0) {
console.warn('[PagePreviewLevels] 未传入选中关卡数据');
return;
}
// 显示用户输入的标题
if (this.pkTitle) {
const label = this.pkTitle.getComponent(Label);
if (label) {
label.string = params.shareTitle || '挑战';
}
}
const indices: number[] = params.selectedIndices;
console.log('[PagePreviewLevels] 选中关卡索引:', indices);
// 更新 content 高度
this._updateContentSize(indices.length);
// 创建每个关卡 item
for (let i = 0; i < indices.length; i++) {
const levelIndex = indices[i];
const itemNode = this._createItem(i);
if (itemNode) {
this.listContent!.addChild(itemNode);
this._itemNodes.push(itemNode);
this._loadLevelData(itemNode, levelIndex, i);
}
}
// 滚动到顶部
const scrollComp = this.scrollView?.getComponent(ScrollView);
if (scrollComp) {
scrollComp.scrollToTop(0);
}
}
private _clearList(): void {
for (const node of this._itemNodes) {
if (node && node.isValid) {
node.destroy();
}
}
this._itemNodes = [];
}
private _updateContentSize(count: number): void {
if (!this.listContent) return;
const contentTransform = this.listContent.getComponent(UITransform);
if (!contentTransform) return;
const totalHeight = LAYOUT.PADDING_TOP
+ count * LAYOUT.ITEM_HEIGHT
+ (count > 0 ? (count - 1) * LAYOUT.SPACING_Y : 0)
+ LAYOUT.PADDING_TOP;
contentTransform.setContentSize(contentTransform.contentSize.width, totalHeight);
}
/**
* 创建单个关卡展示项
* content anchor=(0.5, 1)y 轴负向下
*/
private _createItem(displayIndex: number): Node | null {
if (!this.listTemplate) return null;
const item = instantiate(this.listTemplate);
item.active = true;
item.name = `preview_item_${displayIndex}`;
// 垂直居中排列x=0水平居中于 contenty 负向下
const y = -(LAYOUT.PADDING_TOP + displayIndex * (LAYOUT.ITEM_HEIGHT + LAYOUT.SPACING_Y) + LAYOUT.ITEM_HEIGHT / 2);
item.setPosition(0, y, 0);
return item;
}
/**
* 异步加载关卡数据并填充到 item 节点
*/
private async _loadLevelData(item: Node, levelIndex: number, displayIndex: number): Promise<void> {
const config = await LevelDataManager.instance.ensureLevelReady(levelIndex);
if (!config || !item.isValid) return;
// 填充封面图
const levelCover = item.getChildByName('LevelCover');
if (levelCover) {
const sprite = levelCover.getComponent(Sprite);
if (sprite && config.spriteFrame) {
sprite.spriteFrame = config.spriteFrame;
}
}
// 填充提示1
const tips1 = item.getChildByName('Tips1');
if (tips1) {
const label = tips1.getComponent(Label);
if (label) {
label.string = `线索一:${config.clue1 || ''}`;
}
}
// 填充提示2
const tips2 = item.getChildByName('Tips2');
if (tips2) {
const label = tips2.getComponent(Label);
if (label) {
label.string = `线索二:${config.clue2 || ''}`;
}
}
// 填充答案
const answer = item.getChildByName('Answer');
if (answer) {
const label = answer.getComponent(Label);
if (label) {
label.string = `答案:${config.answer || ''}`;
}
}
}
// ─── 事件处理 ───────────────────────────────────────
private _onBackClick(): void {
console.log('[PagePreviewLevels] 返回');
ViewManager.instance.back();
}
}