818 lines
30 KiB
Markdown
818 lines
30 KiB
Markdown
# 梗中作乐 V1.0 - 源代码文档(第 10 部分)
|
||
|
||
软件名称:梗中作乐
|
||
版本号:V1.0
|
||
|
||
---
|
||
|
||
## 第 10 部分:PK 战绩与挑战结算
|
||
|
||
本部分包含 PK 战绩榜单页与 PK 挑战结算页两个核心结算页面。
|
||
|
||
### 10.1 PK 战绩页 (assets/prefabs/PagePKData.ts)
|
||
|
||
```typescript
|
||
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)
|
||
|
||
```typescript
|
||
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;
|
||
}
|
||
}
|
||
```
|