Files
mp-xieyingeng/软著源代码/10-PK战绩与结算.md
2026-06-29 19:55:40 +08:00

30 KiB
Raw Blame History

梗中作乐 V1.0 - 源代码文档(第 10 部分)

软件名称:梗中作乐 版本号V1.0


第 10 部分PK 战绩与挑战结算

本部分包含 PK 战绩榜单页与 PK 挑战结算页两个核心结算页面。

10.1 PK 战绩页 (assets/prefabs/PagePKData.ts)

import { _decorator, Node, Button, instantiate, Label, ScrollView, UITransform, Sprite, SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { CreatedShareItem, ParticipatedShareItem, ShareParticipantRankSummary } from 'db://assets/scripts/types/ApiTypes';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
const { ccclass, property } = _decorator;

@ccclass('PagePKData')
export class PagePKData extends BaseView {
    private static readonly CREATED_ITEM_TOP_PADDING = 20;
    private static readonly CREATED_ITEM_BOTTOM_PADDING = 20;
    private static readonly CREATED_ITEM_SPACING = 20;
    private static readonly PARTICIPATED_ITEM_TOP_PADDING = 20;
    private static readonly PARTICIPATED_ITEM_BOTTOM_PADDING = 20;
    private static readonly PARTICIPATED_ITEM_SPACING = 20;

    @property({ type: Node, tooltip: '返回按钮' })
    backBtn: Node | null = null;

    @property({ type: Node, tooltip: '我创建的挑战列表 content 节点' })
    createdListContent: Node | null = null;

    @property({ type: Node, tooltip: '我创建的挑战列表条目模板 FriendsPKRankListItem' })
    createdListItemTemplate: Node | null = null;

    @property({ type: Node, tooltip: '我参与的挑战列表 content 节点' })
    participatedListContent: Node | null = null;

    @property({ type: Node, tooltip: '我参与的挑战列表条目模板 MyPKListItem' })
    participatedListItemTemplate: Node | null = null;

    private _createdShares: CreatedShareItem[] = [];
    private _participatedShares: ParticipatedShareItem[] = [];
    private _createdItemNodes: Node[] = [];
    private _participatedItemNodes: Node[] = [];
    private _createdButtonBindings: Array<{ node: Node; handler: () => void }> = [];
    private _isLoading: boolean = false;
    private _renderVersion: number = 0;

    onViewLoad(): void {
        this._resolveNodes();
        if (this.backBtn) {
            this.backBtn.on(Button.EventType.CLICK, this._onBackClick, this);
        }
        this._hideCreatedItemTemplate();
        this._hideParticipatedItemTemplate();
    }

    onViewShow(): void {
        this._resolveNodes();
        void this._loadShareLists();
    }

    onViewHide(): void {
        this._renderVersion++;
    }

    private _onBackClick(): void {
        AudioManager.instance.playButtonClick();
        ViewManager.instance.back();
    }

    private async _loadShareLists(): Promise<void> {
        if (this._isLoading) {
            return;
        }

        this._isLoading = true;
        try {
            const [createdItems, participatedItems] = await Promise.all([
                ShareManager.instance.fetchCreatedShares(),
                ShareManager.instance.fetchParticipatedShares(),
            ]);

            if (!createdItems || !participatedItems) {
                ToastManager.instance.show('获取挑战列表失败,请稍后重试');
            }

            this._createdShares = createdItems ?? [];
            this._participatedShares = participatedItems ?? [];
            console.log('[PagePKData] 我创建的挑战列表:', this._createdShares);
            console.log('[PagePKData] 我参与的挑战列表:', this._participatedShares);
            this._renderCreatedShares();
            this._renderParticipatedShares();
        } finally {
            this._isLoading = false;
        }
    }

    onViewDestroy(): void {
        if (this.backBtn) {
            this.backBtn.off(Button.EventType.CLICK, this._onBackClick, this);
        }
        this._clearCreatedItems();
        this._clearParticipatedItems();
    }

    private _resolveNodes(): void {
        if (!this.backBtn || !this.backBtn.isValid) {
            this.backBtn = this.node.getChildByName('BtnBack');
        }

        const createdList = this.node.getChildByName('FriendsPKRankList');
        const view = createdList?.getChildByName('view');
        this.createdListContent = this.createdListContent ?? view?.getChildByName('content') ?? null;
        this.createdListItemTemplate = this.createdListItemTemplate
            ?? this.createdListContent?.getChildByName('FriendsPKRankListItem')
            ?? null;

        const participatedList = this.node.getChildByName('MyPKList');
        const participatedView = participatedList?.getChildByName('view');
        this.participatedListContent = this.participatedListContent ?? participatedView?.getChildByName('content') ?? null;
        this.participatedListItemTemplate = this.participatedListItemTemplate
            ?? this.participatedListContent?.getChildByName('MyPKListItem')
            ?? null;

        if (!this.createdListContent) {
            console.warn('[PagePKData] 未找到 FriendsPKRankList/content 节点');
        }
        if (!this.createdListItemTemplate) {
            console.warn('[PagePKData] 未找到 FriendsPKRankListItem 模板节点');
        }
        if (!this.participatedListContent) {
            console.warn('[PagePKData] 未找到 MyPKList/content 节点');
        }
        if (!this.participatedListItemTemplate) {
            console.warn('[PagePKData] 未找到 MyPKListItem 模板节点');
        }
    }

    private _renderCreatedShares(): void {
        this._renderVersion++;
        this._clearCreatedItems();

        if (!this.createdListContent || !this.createdListItemTemplate) {
            return;
        }

        this._layoutCreatedList(this._createdShares.length);
        this._hideCreatedItemTemplate();

        this._createdShares.forEach((share, index) => {
            const item = instantiate(this.createdListItemTemplate!);
            item.name = `FriendsPKRankListItem_${index + 1}`;
            item.active = true;
            this.createdListContent!.addChild(item);
            this._createdItemNodes.push(item);
            this._positionCreatedItem(item, index);
            this._applyCreatedShare(item, share, this._renderVersion);
        });

        this._scrollCreatedListToTop();
    }

    private _renderParticipatedShares(): void {
        this._clearParticipatedItems();

        if (!this.participatedListContent || !this.participatedListItemTemplate) {
            return;
        }

        this._layoutParticipatedList(this._participatedShares.length);
        this._hideParticipatedItemTemplate();

        this._participatedShares.forEach((share, index) => {
            const item = instantiate(this.participatedListItemTemplate!);
            item.name = `MyPKListItem_${index + 1}`;
            item.active = true;
            this.participatedListContent!.addChild(item);
            this._participatedItemNodes.push(item);
            this._positionParticipatedItem(item, index);
            this._applyParticipatedShare(item, share);
        });

        this._scrollParticipatedListToTop();
    }

    private _applyCreatedShare(item: Node, share: CreatedShareItem, version: number): void {
        this._setLabel(this._findLabelIn(item, 'Name'), share.title || '未命名挑战');
        this._setLabel(this._findLabelIn(item, 'ParticipateLabel'), `共${share.participantCount ?? 0}人参与`);

        const firstParticipant = this._getFirstParticipant(share);
        const firstName = this._getParticipantName(firstParticipant);
        const rankNumber = firstParticipant?.rank ?? 1;

        this._setLabel(this._findLabelIn(item, 'NameLabel'), firstName || (share.participantCount > 0 ? '微信用户' : '暂无参与'));
        this._setLabel(this._findLabelIn(item, 'RankNumberLabel'), firstParticipant ? `第 ${rankNumber} 名` : '暂无排名');
        this._loadAvatar(firstParticipant?.avatarUrl ?? '', this._findAvatarSprite(item), version);

        const viewButton = this._findChild(item, 'ViewButton');
        if (viewButton) {
            const handler = () => {
                AudioManager.instance.playButtonClick();
                this._openShareDetail(share);
            };
            viewButton.on(Button.EventType.CLICK, handler, this);
            this._createdButtonBindings.push({ node: viewButton, handler });
        }

        const shareButton = this._findChild(item, 'ShareButton');
        if (shareButton) {
            const handler = () => {
                AudioManager.instance.playButtonClick();
                ShareManager.instance.triggerWxShare(share.title, share.shareCode);
            };
            shareButton.on(Button.EventType.CLICK, handler, this);
            this._createdButtonBindings.push({ node: shareButton, handler });
        }
    }

    private _applyParticipatedShare(item: Node, share: ParticipatedShareItem): void {
        this._setLabel(this._findLabelIn(item, 'ChallangeName'), share.title || '未命名挑战');
        this._setLabel(this._findLabelIn(item, 'ParticipateLabel'), `共${share.participantCount ?? 0}人参与`);
        this._applyParticipatedRank(item, share.userRank);
    }

    private _openShareDetail(share: CreatedShareItem): void {
        if (!share.shareCode) {
            ToastManager.instance.show('挑战数据异常,请稍后重试');
            return;
        }

        ViewManager.instance.open('PagePKDetail', {
            params: {
                share,
                shareCode: share.shareCode,
            },
            onError: (err) => {
                console.error('[PagePKData] 打开挑战详情失败:', err);
                ToastManager.instance.show('打开挑战详情失败,请稍后重试');
            },
        });
    }

    private _getFirstParticipant(share: CreatedShareItem): ShareParticipantRankSummary | null {
        return share.firstPlaceUser ?? share.topParticipant ?? share.firstParticipant ?? share.champion ?? null;
    }

    private _getParticipantName(participant: ShareParticipantRankSummary | null): string {
        return participant?.nickname || participant?.nickName || '';
    }

    private _layoutCreatedList(itemCount: number): void {
        if (!this.createdListContent || !this.createdListItemTemplate) {
            return;
        }

        const contentTransform = this.createdListContent.getComponent(UITransform);
        const viewTransform = this.createdListContent.parent?.getComponent(UITransform) ?? null;
        const itemTransform = this.createdListItemTemplate.getComponent(UITransform);
        if (!contentTransform || !viewTransform || !itemTransform) {
            return;
        }

        const contentHeight = Math.max(
            viewTransform.height,
            PagePKData.CREATED_ITEM_TOP_PADDING
                + PagePKData.CREATED_ITEM_BOTTOM_PADDING
                + itemCount * itemTransform.height
                + Math.max(0, itemCount - 1) * PagePKData.CREATED_ITEM_SPACING,
        );

        contentTransform.setContentSize(contentTransform.width, contentHeight);
        this.createdListContent.setPosition(
            this.createdListContent.position.x,
            viewTransform.height / 2,
            this.createdListContent.position.z,
        );
    }

    private _layoutParticipatedList(itemCount: number): void {
        if (!this.participatedListContent || !this.participatedListItemTemplate) {
            return;
        }

        const contentTransform = this.participatedListContent.getComponent(UITransform);
        const viewTransform = this.participatedListContent.parent?.getComponent(UITransform) ?? null;
        const itemTransform = this.participatedListItemTemplate.getComponent(UITransform);
        if (!contentTransform || !viewTransform || !itemTransform) {
            return;
        }

        const contentHeight = Math.max(
            viewTransform.height,
            PagePKData.PARTICIPATED_ITEM_TOP_PADDING
                + PagePKData.PARTICIPATED_ITEM_BOTTOM_PADDING
                + itemCount * itemTransform.height
                + Math.max(0, itemCount - 1) * PagePKData.PARTICIPATED_ITEM_SPACING,
        );

        contentTransform.setContentSize(contentTransform.width, contentHeight);
        this.participatedListContent.setPosition(
            this.participatedListContent.position.x,
            viewTransform.height / 2,
            this.participatedListContent.position.z,
        );
    }

    private _positionCreatedItem(item: Node, index: number): void {
        const itemTransform = item.getComponent(UITransform);
        if (!itemTransform) {
            return;
        }

        const y = -PagePKData.CREATED_ITEM_TOP_PADDING
            - itemTransform.height / 2
            - index * (itemTransform.height + PagePKData.CREATED_ITEM_SPACING);
        item.setPosition(0, y, item.position.z);
    }

    private _positionParticipatedItem(item: Node, index: number): void {
        const itemTransform = item.getComponent(UITransform);
        if (!itemTransform) {
            return;
        }

        const y = -PagePKData.PARTICIPATED_ITEM_TOP_PADDING
            - itemTransform.height / 2
            - index * (itemTransform.height + PagePKData.PARTICIPATED_ITEM_SPACING);
        item.setPosition(0, y, item.position.z);
    }

    private _scrollCreatedListToTop(): void {
        const scrollView = this.node.getChildByName('FriendsPKRankList')?.getComponent(ScrollView);
        scrollView?.scrollToTop(0);
    }

    private _scrollParticipatedListToTop(): void {
        const scrollView = this.node.getChildByName('MyPKList')?.getComponent(ScrollView);
        scrollView?.scrollToTop(0);
    }

    private _applyParticipatedRank(item: Node, rank: number | null): void {
        const rankRoot = this._findChild(item, 'Rank');
        if (!rankRoot) {
            return;
        }

        const rank1Badge = this._findChild(rankRoot, 'rank1badge');
        const rank2Badge = this._findChild(rankRoot, 'rank2badge');
        const rank3Badge = this._findChild(rankRoot, 'rank3badge');
        const rankNumberNode = this._findChild(rankRoot, 'RankNumber');
        const normalizedRank = rank && rank > 0 ? rank : null;

        if (rank1Badge) {
            rank1Badge.active = normalizedRank === 1;
        }
        if (rank2Badge) {
            rank2Badge.active = normalizedRank === 2;
        }
        if (rank3Badge) {
            rank3Badge.active = normalizedRank === 3;
        }
        if (rankNumberNode) {
            rankNumberNode.active = normalizedRank !== 1 && normalizedRank !== 2 && normalizedRank !== 3;
            this._setLabel(rankNumberNode.getComponent(Label), normalizedRank ? `${normalizedRank}` : '-');
        }
    }

    private _findAvatarSprite(item: Node): Sprite | null {
        const headNode = this._findChild(item, 'Head');
        return headNode?.children[0]?.getComponent(Sprite) ?? headNode?.getComponent(Sprite) ?? null;
    }

    private _loadAvatar(url: string, sprite: Sprite | null, version: number): void {
        if (!url || !sprite) {
            return;
        }

        assetManager.loadRemote<ImageAsset>(url, (err, imageAsset) => {
            if (err || !imageAsset || version !== this._renderVersion || !sprite.node.isValid) {
                if (err) {
                    console.error('[PagePKData] 加载第一名头像失败:', url, err);
                }
                return;
            }

            const texture = new Texture2D();
            texture.image = imageAsset;
            const spriteFrame = new SpriteFrame();
            spriteFrame.texture = texture;
            sprite.spriteFrame = spriteFrame;
        });
    }

    private _clearCreatedItems(): void {
        this._unbindCreatedButtons();

        for (const item of this._createdItemNodes) {
            if (item.isValid) {
                item.removeFromParent();
                item.destroy();
            }
        }
        this._createdItemNodes = [];
        this._hideCreatedItemTemplate();
    }

    private _clearParticipatedItems(): void {
        for (const item of this._participatedItemNodes) {
            if (item.isValid) {
                item.removeFromParent();
                item.destroy();
            }
        }

        this._participatedItemNodes = [];
        this._hideParticipatedItemTemplate();
    }

    private _unbindCreatedButtons(): void {
        for (const binding of this._createdButtonBindings) {
            if (binding.node.isValid) {
                binding.node.off(Button.EventType.CLICK, binding.handler, this);
            }
        }
        this._createdButtonBindings = [];
    }

    private _hideCreatedItemTemplate(): void {
        if (this.createdListItemTemplate?.isValid) {
            this.createdListItemTemplate.active = false;
        }
    }

    private _hideParticipatedItemTemplate(): void {
        if (this.participatedListItemTemplate?.isValid) {
            this.participatedListItemTemplate.active = false;
        }
    }

    private _setLabel(label: Label | null, text: string): void {
        if (label) {
            label.string = text;
        }
    }

    private _findLabelIn(root: Node, nodeName: string): Label | null {
        return this._findChild(root, nodeName)?.getComponent(Label) ?? null;
    }

    private _findChild(root: Node, nodeName: string): Node | null {
        if (root.name === nodeName) {
            return root;
        }

        for (const child of root.children) {
            const found = this._findChild(child, nodeName);
            if (found) {
                return found;
            }
        }

        return null;
    }
}

10.2 PK 结算页 (assets/prefabs/PagePKEnd.ts)

import { _decorator, assetManager, Button, ImageAsset, instantiate, Label, Node, ScrollView, Sprite, SpriteFrame, Texture2D, UITransform } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { SubmitShareData, SubmittedShareLevelData } from 'db://assets/scripts/types/ApiTypes';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
const { ccclass, property } = _decorator;

@ccclass('PagePKEnd')
export class PagePKEnd extends BaseView {
    private static readonly ANSWER_ITEM_TOP_PADDING = 16;
    private static readonly ANSWER_ITEM_BOTTOM_PADDING = 16;
    private static readonly ANSWER_ITEM_SPACING = 16;
    private static readonly COVER_IMAGE_WIDTH = 1299;
    private static readonly COVER_IMAGE_HEIGHT = 1004;

    @property({ type: Node, tooltip: '返回首页按钮' })
    settingButton: Node | null = null;

    @property({ type: Label, tooltip: '顶部排名文案例如获得了第1名' })
    rankLabel: Label | null = null;

    @property({ type: Label, tooltip: '答对题数文案例如答对了4题' })
    rightNumberLabel: Label | null = null;

    @property({ type: Label, tooltip: '本人排名文案例如您获得了第1名' })
    rankNumberLabel: Label | null = null;

    @property({ type: Label, tooltip: '参与人数文案例如一共66人参与了挑战' })
    participateNumberLabel: Label | null = null;

    @property({ type: Label, tooltip: '答案列表标题' })
    answerTitleLabel: Label | null = null;

    @property({ type: Node, tooltip: '答案列表 content 节点' })
    answerListContent: Node | null = null;

    @property({ type: Node, tooltip: '答案列表条目模板 AnswerItem' })
    answerItemTemplate: Node | null = null;

    private _answerItemNodes: Node[] = [];
    private _answerButtonBindings: Array<{ node: Node; handler: () => void }> = [];
    private _renderVersion: number = 0;

    onViewLoad(): void {
        this._resolveNodes();
        this._bindEvents();
        this._hideAnswerTemplate();
    }

    onViewShow(): void {
        console.log('[PagePKEnd] onViewShow');
        this._resolveNodes();
        this._renderResult(this.getParams()?.result ?? null);
    }

    onViewDestroy(): void {
        this._unbindEvents();
        this._clearAnswerItems();
    }

    private _resolveNodes(): void {
        if (!this.settingButton || !this.settingButton.isValid) {
            this.settingButton = this.node.getChildByName('SettingButton');
        }

        this.rankLabel = this.rankLabel ?? this._findLabel('RankLabel');
        this.rightNumberLabel = this.rightNumberLabel ?? this._findLabel('RightNumberLabel');
        this.rankNumberLabel = this.rankNumberLabel ?? this._findLabel('RankNumberLabel');
        this.participateNumberLabel = this.participateNumberLabel ?? this._findLabel('PartipateNumberLabel');
        this.answerTitleLabel = this.answerTitleLabel ?? this._findLabel('AnswerTitle');

        const answerList = this.node.getChildByName('AnswerList');
        const view = answerList?.getChildByName('view');
        this.answerListContent = this.answerListContent ?? view?.getChildByName('content') ?? null;
        this.answerItemTemplate = this.answerItemTemplate
            ?? this.answerListContent?.getChildByName('AnswerItem')
            ?? null;

        if (!this.settingButton) {
            console.warn('[PagePKEnd] 未找到 SettingButton 节点');
        }
        if (!this.answerListContent) {
            console.warn('[PagePKEnd] 未找到 AnswerList/content 节点');
        }
        if (!this.answerItemTemplate) {
            console.warn('[PagePKEnd] 未找到 AnswerItem 模板节点');
        }
    }

    private _bindEvents(): void {
        const button = this.settingButton?.getComponent(Button);
        if (!button) {
            console.warn('[PagePKEnd] SettingButton 缺少 Button 组件');
            return;
        }

        this.settingButton?.on(Button.EventType.CLICK, this._onHomeClick, this);
    }

    private _unbindEvents(): void {
        if (this.settingButton && this.settingButton.isValid) {
            this.settingButton.off(Button.EventType.CLICK, this._onHomeClick, this);
        }
        this._unbindAnswerButtons();
    }

    private _onHomeClick(): void {
        AudioManager.instance.playButtonClick();
        ShareManager.instance.clearShareMode();
        ViewManager.instance.replace('PageHome');
    }

    private _renderResult(result: SubmitShareData | null): void {
        this._renderVersion++;
        this._clearAnswerItems();

        if (!result) {
            this._setLabel(this.rankLabel, '暂无排名');
            this._setLabel(this.rightNumberLabel, '答对了0题');
            this._setLabel(this.rankNumberLabel, '您暂未上榜');
            this._setLabel(this.participateNumberLabel, '暂无参与数据');
            this._setLabel(this.answerTitleLabel, '暂无挑战结果');
            this._hideAnswerTemplate();
            return;
        }

        this._setLabel(this.rankLabel, `获得了第${result.rank}名`);
        this._setLabel(this.rightNumberLabel, `答对了${result.correctCount}题`);
        this._setLabel(this.rankNumberLabel, `您获得了第${result.rank}名`);
        this._setLabel(this.participateNumberLabel, `一共${result.participantCount}人参与了挑战`);
        this._setLabel(this.answerTitleLabel, `共用时${result.totalTimeSpent}s揭晓答案吧`);
        this._renderAnswerList(result.levels ?? []);
    }

    private _renderAnswerList(levels: SubmittedShareLevelData[]): void {
        if (!this.answerListContent || !this.answerItemTemplate) {
            return;
        }

        this._hideAnswerTemplate();
        const version = this._renderVersion;
        this._layoutAnswerContent(levels.length);

        levels.forEach((level, index) => {
            const item = instantiate(this.answerItemTemplate!);
            item.name = `AnswerItem_${index + 1}`;
            item.active = true;
            this.answerListContent!.addChild(item);
            this._answerItemNodes.push(item);
            this._positionAnswerItem(item, index);

            this._applyAnswerState(item, level);

            const coverSprite = this._findChild(item, 'CoverImage')?.getComponent(Sprite) ?? null;
            this._prepareCoverSprite(coverSprite);
            this._loadCoverImage(level.image1Url, coverSprite, version);
        });

        this._scrollAnswerListToTop();
    }

    private _layoutAnswerContent(itemCount: number): void {
        if (!this.answerListContent || !this.answerItemTemplate) {
            return;
        }

        const contentTransform = this.answerListContent.getComponent(UITransform);
        const viewTransform = this.answerListContent.parent?.getComponent(UITransform) ?? null;
        const itemTransform = this.answerItemTemplate.getComponent(UITransform);
        if (!contentTransform || !viewTransform || !itemTransform) {
            return;
        }

        const itemHeight = itemTransform.height;
        const contentHeight = Math.max(
            viewTransform.height,
            PagePKEnd.ANSWER_ITEM_TOP_PADDING
                + PagePKEnd.ANSWER_ITEM_BOTTOM_PADDING
                + itemCount * itemHeight
                + Math.max(0, itemCount - 1) * PagePKEnd.ANSWER_ITEM_SPACING,
        );

        contentTransform.setContentSize(contentTransform.width, contentHeight);
        this.answerListContent.setPosition(
            this.answerListContent.position.x,
            viewTransform.height / 2,
            this.answerListContent.position.z,
        );
    }

    private _positionAnswerItem(item: Node, index: number): void {
        const itemTransform = item.getComponent(UITransform);
        if (!itemTransform) {
            return;
        }

        const y = -PagePKEnd.ANSWER_ITEM_TOP_PADDING
            - itemTransform.height / 2
            - index * (itemTransform.height + PagePKEnd.ANSWER_ITEM_SPACING);
        item.setPosition(0, y, item.position.z);
    }

    private _applyAnswerState(item: Node, level: SubmittedShareLevelData): void {
        const answerButton = this._findChild(item, 'ButtonViewAnswer');
        const buttonLabel = answerButton?.getChildByName('Label')?.getComponent(Label) ?? null;
        const answerLabelNode = this._findChild(item, 'AnswerLabel');
        const answerLabel = answerLabelNode?.getComponent(Label) ?? null;

        this._setLabel(buttonLabel, '查看答案');
        this._setLabel(answerLabel, level.answer || '-');

        if (level.isCorrect) {
            if (answerButton) {
                answerButton.active = false;
            }
            if (answerLabelNode) {
                answerLabelNode.active = true;
            }
            return;
        }

        if (answerButton) {
            answerButton.active = true;
        }
        if (answerLabelNode) {
            answerLabelNode.active = false;
        }

        const handler = () => {
            AudioManager.instance.playButtonClick();
            if (answerButton?.isValid) {
                answerButton.active = false;
            }
            if (answerLabelNode?.isValid) {
                answerLabelNode.active = true;
            }
        };
        if (answerButton) {
            answerButton.on(Button.EventType.CLICK, handler, this);
            this._answerButtonBindings.push({ node: answerButton, handler });
        }
    }

    private _scrollAnswerListToTop(): void {
        const scrollView = this.node.getChildByName('AnswerList')?.getComponent(ScrollView);
        scrollView?.scrollToTop(0);
    }

    private _loadCoverImage(url: string, sprite: Sprite | null, version: number): void {
        if (!url || !sprite) {
            return;
        }

        this._prepareCoverSprite(sprite);
        assetManager.loadRemote<ImageAsset>(url, (err, imageAsset) => {
            if (err || !imageAsset || version !== this._renderVersion || !sprite.node.isValid) {
                if (err) {
                    console.error('[PagePKEnd] 加载答案封面失败:', url, err);
                }
                return;
            }

            const texture = new Texture2D();
            texture.image = imageAsset;
            const spriteFrame = new SpriteFrame();
            spriteFrame.texture = texture;
            this._prepareCoverSprite(sprite);
            sprite.spriteFrame = spriteFrame;
            this._prepareCoverSprite(sprite);
        });
    }

    private _prepareCoverSprite(sprite: Sprite | null): void {
        if (!sprite?.node.isValid) {
            return;
        }

        sprite.sizeMode = Sprite.SizeMode.CUSTOM;
        const transform = sprite.node.getComponent(UITransform);
        if (transform) {
            transform.setContentSize(PagePKEnd.COVER_IMAGE_WIDTH, PagePKEnd.COVER_IMAGE_HEIGHT);
        }
        sprite.node.setScale(0.242, 0.242, 0.242);
    }

    private _clearAnswerItems(): void {
        this._unbindAnswerButtons();

        for (const item of this._answerItemNodes) {
            if (item.isValid) {
                item.removeFromParent();
                item.destroy();
            }
        }
        this._answerItemNodes = [];
        this._hideAnswerTemplate();
    }

    private _unbindAnswerButtons(): void {
        for (const binding of this._answerButtonBindings) {
            if (binding.node.isValid) {
                binding.node.off(Button.EventType.CLICK, binding.handler, this);
            }
        }
        this._answerButtonBindings = [];
    }

    private _hideAnswerTemplate(): void {
        if (this.answerItemTemplate?.isValid) {
            this.answerItemTemplate.active = false;
        }
    }

    private _setLabel(label: Label | null, text: string): void {
        if (label) {
            label.string = text;
        }
    }

    private _findLabel(nodeName: string): Label | null {
        return this._findChild(this.node, nodeName)?.getComponent(Label) ?? null;
    }

    private _findChild(root: Node, nodeName: string): Node | null {
        if (root.name === nodeName) {
            return root;
        }

        for (const child of root.children) {
            const found = this._findChild(child, nodeName);
            if (found) {
                return found;
            }
        }

        return null;
    }
}