375 lines
11 KiB
TypeScript
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] 虚拟列表滚动事件已移除");
|
|
}
|
|
}
|
|
}
|