import { Color, Label, Node, Sprite } from 'cc'; import { RankBoardEntry, RankBoardScope } from '../../manager/RankBoardManager'; import { RankDataManager } from '../../manager/RankDataManager'; import { HeadIconManager } from '../../manager/HeadIconManager'; import { TableNames } from '../../game/ConfigTableData'; import { SpriteLoad } from '../../../mx/components/SpriteLoad'; /** 排行条目:head / region / name / level / lay(num+星星) / winNum,以及名次背景 */ export class RankItemView { /** * 绑定排行条目 * @param scope 榜单类型,决定 num / winNum / level / 星星 展示 */ static bind(root: Node, data: RankBoardEntry, scope: RankBoardScope, skipBg = false): void { if (!root?.isValid) return; const daily = scope === RankBoardScope.DailyWins; this._bindRankDecor(root, data.rank, skipBg); const regionNode = root.find('region'); this._setActive(regionNode, true); this._setLabelAutoWidth(regionNode, this._formatRegion(data.region)); this._setLabelAutoWidth(root.find('name'), this._formatNickname(data.nickname)); const lay = root.find('lay'); const numNode = root.find('lay/num') ?? root.find('num'); const starNode = root.find('lay/星星') ?? root.find('星星'); const winNumNode = root.find('winNum'); this._setActive(root.find('level'), !daily); this._setActive(lay, !daily); this._setActive(numNode, !daily); this._setActive(starNode, !daily); this._setActive(winNumNode, daily); if (!daily) { const totalStars = RankDataManager.clampRankStars(data.score); const intraStars = RankDataManager.getIntraTierStars(totalStars); this._setLabel(root.find('level'), this._rankNameByScore(totalStars)); this._setLabel(numNode, String(intraStars)); } else { this._setLabel(winNumNode, String(data.score ?? 0)); } this._bindAvatar(root, data.avatarUrl); } /** * rank1/2/3 前三名,otherRank 其他名次(rank 写入数字)。 * ui_rankItem 另切 bg1~bg4;ui_selfRank 不改背景。 */ private static _bindRankDecor(root: Node, rank: number, skipBg: boolean): void { const r = Math.max(0, Math.floor(Number(rank) || 0)); const top = r === 1 || r === 2 || r === 3 ? r : 0; if (!skipBg) { this._setActive(root.find('bg1'), top === 1); this._setActive(root.find('bg2'), top === 2); this._setActive(root.find('bg3'), top === 3); this._setActive(root.find('bg4'), top === 0); } this._setActive(root.find('rank1'), top === 1); this._setActive(root.find('rank2'), top === 2); this._setActive(root.find('rank3'), top === 3); const other = root.find('otherRank'); this._setActive(other, top === 0); if (other?.isValid && top === 0) { if(r > 100){ //100名以上未上榜 other.find('rank').active = false; other.find('noRank').active = true; this._setLabel(other.find('noRank'), "未上榜"); }else{ other.find('rank').active = true; other.find('noRank').active = false; this._setLabel(other.find('rank'), String(r)); } } } private static _setActive(node: Node | null, active: boolean): void { if (node?.isValid) node.active = active; } /** 地区展示 */ private static _formatRegion(region: string): string { return String(region ?? '').trim(); } /** 昵称展示最多 7 字 */ private static _formatNickname(nickname: string): string { const text = String(nickname ?? '').trim() || '棋手'; return text.length > 7 ? text.slice(0, 7) : text; } /** 用总星数反查 Rank 表段位名 */ private static _rankNameByScore(score: number): string { return (RankDataManager.resolveByStars(score)?.name ?? '').trim() || '初出茅庐'; } private static _setLabel(node: Node | null, text: string): void { if (!node?.isValid) return; const label = node.getComponent(Label); if (label) label.string = text; } /** region / name 等左对齐文案:按内容撑开宽度,避免 prefab 默认宽度过窄只显示一字 */ private static _setLabelAutoWidth(node: Node | null, text: string): void { if (!node?.isValid) return; const label = node.getComponent(Label); if (!label) return; label.overflow = Label.Overflow.NONE; label.string = text; label.updateRenderData(true); } /** headMask:远程头像用 head,本地头像用 localHead */ private static _bindAvatar(root: Node, url: string): void { const headMask = root.find('headMask'); const headNode = headMask?.find('head') ?? root.find('head'); const localHeadNode = headMask?.find('localHead') ?? null; const avatar = (url ?? '').trim(); if (!avatar) { this._clearAvatarNode(headNode); this._clearAvatarNode(localHeadNode); this._setActive(headNode, false); this._setActive(localHeadNode, false); if (headMask?.isValid) headMask.active = false; return; } if (headMask?.isValid) headMask.active = true; const localId = this._parseLocalHeadIconId(avatar); if (localId > 0) { const row = gg.data.table.getTableData(TableNames.Headicon, localId); if (!row) { console.error('[RankItemView] Headicon 不存在', localId); this._clearAvatarNode(headNode); this._clearAvatarNode(localHeadNode); this._setActive(headNode, false); this._setActive(localHeadNode, false); if (headMask?.isValid) headMask.active = false; return; } this._setActive(headNode, false); this._clearAvatarNode(headNode); this._setActive(localHeadNode, true); if (!localHeadNode?.isValid) { console.error('[RankItemView] localHead 节点缺失', localId); return; } localHeadNode.getOrAddComponent(SpriteLoad); HeadIconManager.applyRowToNode(localHeadNode, row); return; } this._setActive(localHeadNode, false); this._clearAvatarNode(localHeadNode); this._setActive(headNode, true); if (!headNode?.isValid) return; const sp = headNode.getComponent(Sprite); if (sp) sp.color = Color.WHITE; headNode.getOrAddComponent(SpriteLoad).loadRemote(avatar); } /** 清空头像节点 SpriteLoad */ private static _clearAvatarNode(node: Node | null): void { if (!node?.isValid) return; node.getComponent(SpriteLoad)?.clear(); } /** 纯数字头像字段视为本地 Headicon.id */ private static _parseLocalHeadIconId(avatar: string): number { const text = avatar.trim(); if (!/^\d+$/.test(text)) return 0; const id = Math.floor(Number(text)); return id > 0 ? id : 0; } }