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'; 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 { ViewManager.instance.back(); } private async _loadShareLists(): Promise { 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 = () => 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 = () => 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(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; } }