import { _decorator, Component, Node, ScrollView } from "cc"; import li_BaseView from "../../../Main/Common/li_BaseView"; import { GirlService } from "../../network/services/GirlService"; import proto from "db://assets/Scripts/proto/proto.pb.js"; import { DataManager, DataId } from "../../data/DataManager"; import { GirlData } from "../../data/GirlData"; import Utils from "../../../Main/Common/Utils"; import { GButton } from "../../../Main/Common/GButton"; import HXZ_ScrollViewList from "../../../Main/Common/ScrollViewList"; import { RankItem } from "../../uiitems/RankItem"; import { logger } from "../../../Main/Common/Logger"; const { ccclass, property } = _decorator; @ccclass("RankPanel") export class RankPanel extends li_BaseView { private _nodeTab: any = {}; // 虚拟列表组件 private scrollViewList: HXZ_ScrollViewList = null; // 排行榜项模板 private rankItemInst: RankItem; // 当前选中的榜单类型 private currentRankType: proto.cs.EnmRankType = proto.cs.EnmRankType.ERT_Day; // 分页状态 private currentPage: number = 1; private pageLimit: number = 20; private isLoadingMore: boolean = false; private hasMoreData: boolean = true; // 记录上次触发加载的 ID,避免重复触发 private lastLoadTriggerId: number = -1; // 榜单切换按钮 private dayRankBtn: Node; private weekRankBtn: Node; private monthRankBtn: Node; // 滚动视图 private rankScroll: Node; private rankScrollView: ScrollView = null; onLoadCT() { Utils.parseNode(this.node, this._nodeTab); // 获取榜单切换按钮 this.dayRankBtn = this._nodeTab.dayRankBtn; this.weekRankBtn = this._nodeTab.weekRankBtn; this.monthRankBtn = this._nodeTab.monthRankBtn; // 获取滚动视图 this.rankScroll = this._nodeTab.rankScroll; // 获取推荐列表的 ScrollView 组件 if (this.rankScroll) { this.rankScrollView = this.rankScroll.getComponent(ScrollView); // 获取虚拟列表组件(需要在场景中手动添加 HXZ_ScrollViewList 组件) this.scrollViewList = this.rankScroll.getComponent(HXZ_ScrollViewList); if (!this.scrollViewList) { logger.warn( "[RankPanel] rankScroll 节点缺少 HXZ_ScrollViewList 组件,请在编辑器中添加" ); } } // 获取排行榜项模板 this.rankItemInst = this._nodeTab.RankItem?.getComponent(RankItem); if (this.rankItemInst) { this.rankItemInst.node.active = false; } this.registerListener(); } openUIDataCT(data: any): void { // 默认打开日榜 this.switchRankType(proto.cs.EnmRankType.ERT_Day); } /** * 注册事件监听 */ private registerListener() { // 榜单切换按钮 if (this.dayRankBtn) { GButton.BandClick(this.dayRankBtn, this.openDayRank, this); } if (this.weekRankBtn) { GButton.BandClick(this.weekRankBtn, this.openWeekRank, this); } if (this.monthRankBtn) { GButton.BandClick(this.monthRankBtn, this.openMonthRank, this); } // 注册虚拟列表滚动事件(延迟到下一帧,确保虚拟列表已初始化) this.scheduleOnce(() => { if (this.scrollViewList) { // 监听 scrolling 事件,通过滚动偏移量检测是否接近底部 this.scrollViewList.node.on("scrolling", this.onRankScrolling, this); logger.log("[RankPanel] 虚拟列表滚动事件已绑定"); } else { logger.warn("[RankPanel] 无法绑定滚动事件,scrollViewList 未初始化"); } }, 0.1); } /** * 虚拟列表渲染回调 * 该方法需要在 Cocos Creator 编辑器中配置到 HXZ_ScrollViewList 的 renderEvent * @param itemNode 虚拟列表传入的节点 * @param index 数据索引 */ onRenderRankItem(itemNode: Node, index: number): void { const girlData = DataManager.I.getDataById(DataId.Girl); const girlIDs = girlData.getRankIdsByType(this.currentRankType); if (index < girlIDs.length) { const girlId = girlIDs[index]; const item = itemNode.getComponent(RankItem); if (!item) { logger.error(`[RankPanel] 节点缺少 RankItem 组件,index: ${index}`); return; } // 确保节点已初始化 if (!item._initialized) { item.init(); } // 刷新数据(index + 1 作为排名序号) item.refresh(girlId, index + 1); itemNode.active = true; } } /** * 打开日榜 */ openDayRank() { this.switchRankType(proto.cs.EnmRankType.ERT_Day); } /** * 打开周榜 */ openWeekRank() { this.switchRankType(proto.cs.EnmRankType.ERT_Week); } /** * 打开月榜 */ openMonthRank() { this.switchRankType(proto.cs.EnmRankType.ERT_Month); } /** * 切换榜单类型 * @param rankType 榜单类型 */ private async switchRankType(rankType: proto.cs.EnmRankType) { // 如果是同一个榜单,不重复请求 if ( this.currentRankType === rankType && this.scrollViewList?.numItems > 0 ) { logger.log(`[RankPanel] 当前已是该榜单类型: ${rankType}`); this.updateTabState(rankType); return; } this.currentRankType = rankType; // 重置分页状态 this.currentPage = 1; this.hasMoreData = true; this.isLoadingMore = false; this.lastLoadTriggerId = -1; // 请求榜单数据 await this.reqGirlRankData(rankType); // 更新虚拟列表 if (this.scrollViewList) { const girlData = DataManager.I.getDataById(DataId.Girl); const girlIDs = girlData.getRankIdsByType(rankType); this.scrollViewList.numItems = girlIDs.length; logger.log( `[RankPanel] 切换到榜单类型 ${rankType},共 ${girlIDs.length} 项` ); } else { logger.warn("[RankPanel] scrollViewList 未初始化"); } // 更新标签页状态 this.updateTabState(rankType); } /** * 更新标签页状态 * @param rankType 当前榜单类型 */ private updateTabState(rankType: proto.cs.EnmRankType) { if (this.dayRankBtn && this.dayRankBtn.children.length >= 2) { this.dayRankBtn.children[0].active = rankType !== proto.cs.EnmRankType.ERT_Day; this.dayRankBtn.children[1].active = rankType === proto.cs.EnmRankType.ERT_Day; } if (this.weekRankBtn && this.weekRankBtn.children.length >= 2) { this.weekRankBtn.children[0].active = rankType !== proto.cs.EnmRankType.ERT_Week; this.weekRankBtn.children[1].active = rankType === proto.cs.EnmRankType.ERT_Week; } if (this.monthRankBtn && this.monthRankBtn.children.length >= 2) { this.monthRankBtn.children[0].active = rankType !== proto.cs.EnmRankType.ERT_Month; this.monthRankBtn.children[1].active = rankType === proto.cs.EnmRankType.ERT_Month; } } /** * 滚动中回调,基于虚拟列表 displayData 检测并触发分页加载 * 当显示到接近已加载数据末尾时(倒数第3个),自动加载下一页 */ private onRankScrolling(): void { if (!this.scrollViewList) return; // 获取虚拟列表的 displayData 和总数 const displayData = this.scrollViewList.displayData; const numItems = this.scrollViewList.numItems; // 检查数据有效性 if (!displayData || displayData.length === 0 || numItems === 0) return; // 获取当前显示的最后一个 item 的 ID const lastDisplayedId = displayData[displayData.length - 1].id; // 触发条件:显示到倒数第 3 个 item 时开始加载下一页 const threshold = numItems - 3; if ( lastDisplayedId >= threshold && lastDisplayedId > this.lastLoadTriggerId ) { logger.log( `[RankPanel] 接近末尾 (${lastDisplayedId}/${numItems}),触发加载第 ${ this.currentPage + 1 } 页` ); // 记录本次触发的 ID,避免重复触发 this.lastLoadTriggerId = lastDisplayedId; // 触发分页加载 this.loadMoreRankData(); } } /** * 加载下一页排行榜数据 */ private async loadMoreRankData(): Promise { // 防止重复加载 if (this.isLoadingMore) { logger.log("[RankPanel] 正在加载中,跳过"); return; } // 检查是否还有更多数据 if (!this.hasMoreData) { logger.log("[RankPanel] 没有更多数据了"); return; } try { this.isLoadingMore = true; this.currentPage++; logger.log(`[RankPanel] 开始加载第 ${this.currentPage} 页排行榜数据`); const reqData = { rankType: this.currentRankType, page: this.currentPage, limit: this.pageLimit, }; const res = await GirlService.I.reqGirlRankData(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { const girlData = DataManager.I.getDataById(DataId.Girl); // 追加数据到 GirlData girlData.addRankData(this.currentRankType, res.data); // 检查是否还有更多数据 const girls = res.data.girls || []; if (girls.length < this.pageLimit) { this.hasMoreData = false; logger.log("[RankPanel] 已加载所有排行榜数据"); } // 使用虚拟列表:更新总数量,自动触发渲染 if (this.scrollViewList) { const allGirlIDs = girlData.getRankIdsByType(this.currentRankType); this.scrollViewList.numItems = allGirlIDs.length; logger.log( `[RankPanel] 第 ${this.currentPage} 页加载完成,新增 ${girls.length} 项,总计 ${allGirlIDs.length} 项` ); } else { logger.warn("[RankPanel] scrollViewList 未初始化,无法更新列表"); } } else { logger.warn("[RankPanel] 加载下一页排行榜数据失败:", res); // 加载失败,回退页码 this.currentPage--; } } catch (error) { logger.error("[RankPanel] 加载下一页排行榜数据异常:", error); // 加载失败,回退页码 this.currentPage--; } finally { this.isLoadingMore = false; } } /** * 获取排行榜数据 * @param type 榜单类型 */ private async reqGirlRankData(type: proto.cs.EnmRankType) { const reqData = { rankType: type, page: 1, limit: this.pageLimit, }; let res = await GirlService.I.reqGirlRankData(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { const resData = res.data; const girlData = DataManager.I.getDataById(DataId.Girl); girlData.setGirlRank(type, resData); // 检查是否还有更多数据 const girls = resData.girls || []; if (girls.length < this.pageLimit) { this.hasMoreData = false; logger.log("[RankPanel] 首次加载数据不足一页,无需分页"); } logger.log( `[RankPanel] 榜单类型 ${type} 首次加载完成,共 ${girls.length} 项` ); } } onDestroy(): void { logger.log("[RankPanel] onDestroy called"); // 移除虚拟列表滚动事件监听 if (this.scrollViewList) { this.scrollViewList.node.off("scrolling", this.onRankScrolling, this); logger.log("[RankPanel] 虚拟列表滚动事件已移除"); } } }