Files
18xchat/assets/Scripts/chat18x/ui/panels/RankPanel.ts
T

375 lines
11 KiB
TypeScript

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<GirlData>(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<GirlData>(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<void> {
// 防止重复加载
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<GirlData>(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<GirlData>(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] 虚拟列表滚动事件已移除");
}
}
}