Files
mp-xieyingeng/assets/prefabs/PagePreviewLevels.ts
2026-04-19 14:19:13 +08:00

247 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.spriteFrame1) {
sprite.spriteFrame = config.spriteFrame1;
}
}
// 填充提示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();
}
}