2025-08-11 16:08:59 +08:00
|
|
|
|
import {
|
|
|
|
|
|
_decorator,
|
|
|
|
|
|
Component,
|
|
|
|
|
|
Node,
|
|
|
|
|
|
instantiate,
|
|
|
|
|
|
Label,
|
|
|
|
|
|
Sprite,
|
|
|
|
|
|
UITransform,
|
2025-10-11 16:19:57 +08:00
|
|
|
|
ScrollView,
|
2025-08-11 16:08:59 +08:00
|
|
|
|
} from "cc";
|
|
|
|
|
|
import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView";
|
|
|
|
|
|
import Utils from "db://assets/Scripts/Main/Common/Utils";
|
|
|
|
|
|
import { GButton } from "db://assets/Scripts/Main/Common/GButton";
|
|
|
|
|
|
import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
|
2025-08-13 11:31:21 +08:00
|
|
|
|
import { ThemeItem } from "../../uiitems/ThemeItem";
|
2025-09-12 18:20:41 +08:00
|
|
|
|
import {
|
|
|
|
|
|
NavigationManager,
|
|
|
|
|
|
PageTransitionType,
|
|
|
|
|
|
PanelType,
|
|
|
|
|
|
} from "../../manager/NavigationManager";
|
2025-08-12 19:49:50 +08:00
|
|
|
|
import LanguageUtils from "../../../Main/Common/LanguageUtils";
|
2025-08-12 20:22:54 +08:00
|
|
|
|
import { InnerMsgCode } from "../../../Main/Config/InnerMsgCode";
|
2025-09-02 11:33:44 +08:00
|
|
|
|
import { ThemeService } from "db://assets/Scripts/chat18x/network/services/ThemeService";
|
2025-09-02 15:07:53 +08:00
|
|
|
|
import { GirlService } from "db://assets/Scripts/chat18x/network/services/GirlService";
|
2025-09-10 17:37:24 +08:00
|
|
|
|
import { ShopService } from "db://assets/Scripts/chat18x/network/services/ShopService";
|
2025-09-02 11:33:44 +08:00
|
|
|
|
import { DataManager, DataId } from "../../data/DataManager";
|
|
|
|
|
|
import { ThemeData } from "../../data/ThemeData";
|
2025-09-02 15:07:53 +08:00
|
|
|
|
import { GirlData } from "../../data/GirlData";
|
2025-09-09 20:33:43 +08:00
|
|
|
|
import { WalletData } from "../../data/WalletData";
|
|
|
|
|
|
import { AccountData } from "../../data/AccountData";
|
2025-09-10 17:37:24 +08:00
|
|
|
|
import { ShopData } from "../../data/ShopData";
|
2025-09-16 15:58:44 +08:00
|
|
|
|
import { EnvData } from "../../data/EnvData";
|
2025-09-10 18:12:26 +08:00
|
|
|
|
import proto from "db://assets/Scripts/proto/proto.pb.js";
|
2025-09-12 18:20:41 +08:00
|
|
|
|
import { MainController } from "../../core/MainController";
|
2025-09-21 20:18:24 +08:00
|
|
|
|
import { logger } from "db://assets/Scripts/Main/Common/Logger";
|
2025-10-10 16:09:14 +08:00
|
|
|
|
import { RecomandItem } from "../../uiitems/RecomandItem";
|
2025-10-14 16:59:02 +08:00
|
|
|
|
import HXZ_ScrollViewList from "db://assets/Scripts/Main/Common/ScrollViewList";
|
|
|
|
|
|
import ScrollViewListItem from "db://assets/Scripts/Main/Common/ScrollviewListItem";
|
2025-08-11 16:08:59 +08:00
|
|
|
|
|
|
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
|
|
|
|
|
|
|
|
@ccclass("ThemePanel")
|
|
|
|
|
|
export class ThemePanel extends li_BaseView {
|
|
|
|
|
|
private _nodeTab: any = {};
|
|
|
|
|
|
|
|
|
|
|
|
coinNum: Label;
|
2025-10-10 16:09:14 +08:00
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
itemInst: ThemeItem;
|
2025-10-10 16:09:14 +08:00
|
|
|
|
recItemInst: RecomandItem;
|
|
|
|
|
|
|
|
|
|
|
|
themeContent: Node;
|
|
|
|
|
|
recGirlContent: Node;
|
2025-09-10 15:26:56 +08:00
|
|
|
|
private vipLeftTime: Label;
|
2025-10-10 16:09:14 +08:00
|
|
|
|
//recName: Label;
|
|
|
|
|
|
//recSprite: Sprite;
|
2025-08-11 16:08:59 +08:00
|
|
|
|
|
|
|
|
|
|
cache: ThemeItem[] = [];
|
|
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 不再需要手动管理 recItemCache
|
|
|
|
|
|
// recItemCache: RecomandItem[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 虚拟列表组件
|
|
|
|
|
|
private scrollViewList: HXZ_ScrollViewList = null;
|
2025-10-10 16:09:14 +08:00
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
// 子页面管理
|
|
|
|
|
|
|
|
|
|
|
|
// 加载状态保护
|
|
|
|
|
|
private isLoading: boolean = false;
|
|
|
|
|
|
private loadingPromise: Promise<void> | null = null;
|
|
|
|
|
|
private abortController: AbortController | null = null;
|
|
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
private recomandListBtn: Node;
|
|
|
|
|
|
private moreThemeBtn: Node;
|
|
|
|
|
|
|
|
|
|
|
|
private themeScroll: Node;
|
|
|
|
|
|
private recomandScroll: Node;
|
2025-10-11 16:19:57 +08:00
|
|
|
|
private recomandScrollView: ScrollView = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 推荐列表分页状态
|
|
|
|
|
|
private currentPage: number = 1;
|
|
|
|
|
|
private pageLimit: number = 10;
|
|
|
|
|
|
private isLoadingMore: boolean = false;
|
|
|
|
|
|
private hasMoreData: boolean = true;
|
2025-10-10 16:09:14 +08:00
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 记录上次触发加载的 ID,避免重复触发
|
|
|
|
|
|
private lastLoadTriggerId: number = -1;
|
|
|
|
|
|
|
2025-08-11 16:08:59 +08:00
|
|
|
|
onLoadCT() {
|
|
|
|
|
|
Utils.parseNode(this.node, this._nodeTab);
|
|
|
|
|
|
|
|
|
|
|
|
this.coinNum = this._nodeTab.coinNum.getComponent(Label);
|
2025-10-10 16:09:14 +08:00
|
|
|
|
//this.recName = this._nodeTab.characterNameText.getComponent(Label);
|
|
|
|
|
|
//this.recSprite = this._nodeTab.recPic.getComponent(Sprite);
|
2025-09-10 15:26:56 +08:00
|
|
|
|
this.vipLeftTime = this._nodeTab.vipText.getComponent(Label);
|
2025-08-11 16:08:59 +08:00
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
this.recomandListBtn = this._nodeTab.recomandList;
|
|
|
|
|
|
this.moreThemeBtn = this._nodeTab.moreTheme;
|
|
|
|
|
|
this.themeScroll = this._nodeTab.themeScroll;
|
|
|
|
|
|
this.recomandScroll = this._nodeTab.recomandScroll;
|
|
|
|
|
|
|
2025-10-11 16:19:57 +08:00
|
|
|
|
// 获取推荐列表的 ScrollView 组件
|
|
|
|
|
|
if (this.recomandScroll) {
|
|
|
|
|
|
this.recomandScrollView =
|
|
|
|
|
|
this.recomandScroll.getComponentInChildren(ScrollView);
|
2025-10-14 16:59:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取虚拟列表组件(需要在场景中手动添加 HXZ_ScrollViewList 组件)
|
|
|
|
|
|
this.scrollViewList =
|
|
|
|
|
|
this.recomandScroll.getComponentInChildren(HXZ_ScrollViewList);
|
|
|
|
|
|
if (!this.scrollViewList) {
|
|
|
|
|
|
logger.warn(
|
|
|
|
|
|
"[ThemePanel] recomandScroll 节点缺少 HXZ_ScrollViewList 组件,请在编辑器中添加"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-10-11 16:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 16:08:59 +08:00
|
|
|
|
this.registerListener();
|
|
|
|
|
|
this.itemInst = this._nodeTab.ThemeItem.getComponent(ThemeItem);
|
|
|
|
|
|
this.itemInst.node.active = false;
|
2025-10-10 16:09:14 +08:00
|
|
|
|
|
|
|
|
|
|
this.recItemInst = this._nodeTab.recItem.getComponent(RecomandItem);
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 虚拟列表会自动管理模板节点的显隐,但保留兼容性代码
|
2025-10-10 16:09:14 +08:00
|
|
|
|
this.recItemInst.node.active = false;
|
|
|
|
|
|
|
|
|
|
|
|
this.themeContent = this._nodeTab.allThemecontent;
|
|
|
|
|
|
this.recGirlContent = this._nodeTab.allRecGirlContent;
|
2025-08-12 19:49:50 +08:00
|
|
|
|
|
2025-08-11 16:08:59 +08:00
|
|
|
|
this.Show();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
async Show(forceRefresh: boolean = false) {
|
|
|
|
|
|
// 防止重复加载
|
|
|
|
|
|
if (this.isLoading) {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 已在加载中,等待当前加载完成");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
if (this.loadingPromise) {
|
|
|
|
|
|
await this.loadingPromise;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.isLoading = true;
|
2025-10-10 16:09:14 +08:00
|
|
|
|
this.loadingPromise = this._refreshDataInternal(forceRefresh);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.loadingPromise;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.isLoading = false;
|
|
|
|
|
|
this.loadingPromise = null;
|
|
|
|
|
|
}
|
2025-10-10 16:09:14 +08:00
|
|
|
|
|
|
|
|
|
|
this.openRecomandList();
|
2025-09-12 18:20:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
private async _refreshDataInternal(forceRefresh: boolean = false) {
|
2025-09-12 18:20:41 +08:00
|
|
|
|
// 创建新的AbortController用于取消操作
|
|
|
|
|
|
this.abortController = new AbortController();
|
|
|
|
|
|
const signal = this.abortController.signal;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this._nodeTab.loadingRec.active = true;
|
|
|
|
|
|
//some temp data
|
|
|
|
|
|
this.refreshCoinNum();
|
|
|
|
|
|
this.refreshVipExpire();
|
|
|
|
|
|
const accountData = DataManager.I.getDataById<AccountData>(
|
|
|
|
|
|
DataId.Account
|
|
|
|
|
|
);
|
2025-09-18 14:06:53 +08:00
|
|
|
|
//this.uid.string = "uid: " + accountData.accId;
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查是否已被取消
|
|
|
|
|
|
if (signal.aborted) {
|
|
|
|
|
|
throw new Error("Operation was aborted");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理旧的cache节点(每次都清理,解决内存泄漏问题)
|
|
|
|
|
|
this.clearCache();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果需要强制刷新,先刷新数据
|
|
|
|
|
|
if (forceRefresh) {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 开始强制刷新数据");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
await Promise.all([
|
|
|
|
|
|
MainController.I.refreshThemeData(),
|
2025-10-11 16:19:57 +08:00
|
|
|
|
MainController.I.refreshRecommendList(),
|
2025-09-12 18:20:41 +08:00
|
|
|
|
]);
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 数据刷新完成");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 再次检查是否已被取消
|
|
|
|
|
|
if (signal.aborted) {
|
|
|
|
|
|
throw new Error("Operation was aborted");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 优先使用MainController中预加载的数据
|
|
|
|
|
|
const preloadStatus = MainController.I.getPreloadStatus();
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 预加载状态:", preloadStatus);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理主题数据
|
|
|
|
|
|
await this.handleThemeData(preloadStatus.themeData);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已被取消
|
|
|
|
|
|
if (signal.aborted) {
|
|
|
|
|
|
throw new Error("Operation was aborted");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 16:19:57 +08:00
|
|
|
|
// 处理推荐列表数据
|
|
|
|
|
|
await this.handleRecommendData(preloadStatus.recommendList);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查是否已被取消
|
|
|
|
|
|
if (signal.aborted) {
|
|
|
|
|
|
throw new Error("Operation was aborted");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._nodeTab.loadingRec.active = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 处理商品列表数据
|
|
|
|
|
|
await this.handleShopListData(preloadStatus.shopList);
|
|
|
|
|
|
} catch (error) {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.error("[ThemePanel] Show操作出错:", error);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
this._nodeTab.loadingRec.active = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果不是取消操作导致的错误,可以考虑显示错误提示
|
|
|
|
|
|
if (error.message !== "Operation was aborted") {
|
|
|
|
|
|
// 这里可以添加错误提示逻辑
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.error("[ThemePanel] 数据加载失败,请稍后重试");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.abortController = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理主题数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async handleThemeData(isPreloaded: boolean): Promise<void> {
|
|
|
|
|
|
if (isPreloaded) {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 使用预加载的主题数据");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
// 直接使用预加载的数据渲染界面
|
|
|
|
|
|
const themeData = DataManager.I.getDataById<ThemeData>(DataId.Theme);
|
|
|
|
|
|
if (themeData && themeData.themes) {
|
|
|
|
|
|
return;
|
2025-08-11 16:08:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果没有预加载数据,则请求数据
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 请求主题数据");
|
2025-09-10 18:12:26 +08:00
|
|
|
|
const reqData = {};
|
2025-09-02 11:33:44 +08:00
|
|
|
|
let res = await ThemeService.I.reqHallTheme(reqData);
|
2025-09-02 15:11:52 +08:00
|
|
|
|
if (res && res.code === proto.cs.EnmRetCode.SUCCESS) {
|
|
|
|
|
|
// 保存数据
|
|
|
|
|
|
const themeData = DataManager.I.getDataById<ThemeData>(DataId.Theme);
|
|
|
|
|
|
themeData.themes = res.data;
|
2025-09-12 18:20:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 渲染主题数据
|
|
|
|
|
|
*/
|
2025-10-10 16:09:14 +08:00
|
|
|
|
private renderThemeData(): void {
|
|
|
|
|
|
if (this.cache.length > 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
const themeData = DataManager.I.getDataById<ThemeData>(DataId.Theme);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
const themeIds = themeData.themeIds;
|
|
|
|
|
|
for (let id of themeIds) {
|
|
|
|
|
|
let newNode = instantiate(this.itemInst.node);
|
|
|
|
|
|
let item = newNode.getComponent(ThemeItem);
|
2025-09-21 12:05:34 +08:00
|
|
|
|
item.refresh(id, this.node);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
newNode.active = true;
|
|
|
|
|
|
this.cache.push(item);
|
2025-10-10 16:09:14 +08:00
|
|
|
|
this.themeContent.addChild(newNode);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 虚拟列表渲染回调
|
|
|
|
|
|
* 该方法需要在 Cocos Creator 编辑器中配置到 HXZ_ScrollViewList 的 renderEvent
|
|
|
|
|
|
* @param itemNode 虚拟列表传入的节点
|
|
|
|
|
|
* @param index 数据索引
|
|
|
|
|
|
*/
|
|
|
|
|
|
onRenderRecommendItem(itemNode: Node, index: number): void {
|
|
|
|
|
|
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
|
|
|
|
|
|
const girlIDs = girlData.getRecommendListIds();
|
|
|
|
|
|
|
|
|
|
|
|
if (index < girlIDs.length) {
|
|
|
|
|
|
const girlId = girlIDs[index];
|
|
|
|
|
|
const item = itemNode.getComponent(RecomandItem);
|
|
|
|
|
|
|
|
|
|
|
|
if (!item) {
|
|
|
|
|
|
logger.error(
|
|
|
|
|
|
`[ThemePanel] 节点缺少 RecomandItem 组件,index: ${index}`
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保节点已初始化
|
|
|
|
|
|
if (!item._initialized) {
|
|
|
|
|
|
item.init();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新数据
|
|
|
|
|
|
item.refresh(girlId);
|
|
|
|
|
|
itemNode.active = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
/**
|
2025-10-10 16:09:14 +08:00
|
|
|
|
* 处理推荐数据
|
2025-09-12 18:20:41 +08:00
|
|
|
|
*/
|
2025-10-10 16:09:14 +08:00
|
|
|
|
private async handleRecommendData(isPreloaded: boolean): Promise<void> {
|
2025-09-12 18:20:41 +08:00
|
|
|
|
if (isPreloaded) {
|
2025-10-11 16:19:57 +08:00
|
|
|
|
logger.log("[ThemePanel] 使用预加载的推荐列表数据");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
// 直接使用预加载的数据渲染界面
|
|
|
|
|
|
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
|
|
|
|
|
|
if (girlData) {
|
|
|
|
|
|
return;
|
2025-09-02 15:11:52 +08:00
|
|
|
|
}
|
2025-08-11 16:08:59 +08:00
|
|
|
|
}
|
2025-09-02 15:07:53 +08:00
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
// 如果没有预加载数据,则请求数据
|
2025-10-11 16:19:57 +08:00
|
|
|
|
logger.log("[ThemePanel] 请求推荐列表数据");
|
|
|
|
|
|
const reqData = {
|
|
|
|
|
|
page: 1,
|
|
|
|
|
|
limit: 10,
|
|
|
|
|
|
};
|
|
|
|
|
|
let res = await GirlService.I.reqRecommendList(reqData);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
if (res && res.code === proto.cs.EnmRetCode.SUCCESS) {
|
2025-09-02 15:11:52 +08:00
|
|
|
|
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
|
2025-10-11 16:19:57 +08:00
|
|
|
|
girlData.setRecommendList(res.data);
|
2025-09-02 15:11:52 +08:00
|
|
|
|
}
|
2025-09-12 18:20:41 +08:00
|
|
|
|
}
|
2025-09-10 17:37:24 +08:00
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
/**
|
2025-10-11 16:19:57 +08:00
|
|
|
|
* 渲染推荐列表数据(首次渲染)
|
2025-10-14 16:59:02 +08:00
|
|
|
|
* 【已废弃】使用虚拟列表后,不再需要手动渲染,通过 numItems 触发自动渲染
|
2025-09-12 18:20:41 +08:00
|
|
|
|
*/
|
2025-10-14 16:59:02 +08:00
|
|
|
|
private renderRecommendData_DEPRECATED(): void {
|
|
|
|
|
|
// 该方法已被虚拟列表的自动渲染机制替代
|
|
|
|
|
|
// 保留此方法仅用于参考,实际不再调用
|
2025-10-11 16:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-14 16:59:02 +08:00
|
|
|
|
* 滚动到底部回调(已废弃)
|
|
|
|
|
|
* 该方法不再使用,改用 onRecommendScrolling() 中的滚动偏移量检测
|
|
|
|
|
|
* scroll-to-bottom 事件触发不稳定,改为在 scrolling 事件中检测距离底部的距离
|
2025-10-11 16:19:57 +08:00
|
|
|
|
*/
|
2025-10-14 16:59:02 +08:00
|
|
|
|
private onRecommendScrollToBottom_DEPRECATED(): void {
|
2025-10-11 16:19:57 +08:00
|
|
|
|
logger.log("[ThemePanel] 推荐列表滚动到底部");
|
|
|
|
|
|
this.loadMoreRecommendData();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-14 16:59:02 +08:00
|
|
|
|
* 滚动中回调,基于虚拟列表 displayData 检测并触发分页加载
|
|
|
|
|
|
* 当显示到接近已加载数据末尾时(倒数第3个),自动加载下一页
|
2025-10-11 16:19:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
private onRecommendScrolling(): void {
|
2025-10-14 16:59:02 +08:00
|
|
|
|
if (!this.scrollViewList) return;
|
2025-10-11 16:19:57 +08:00
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 获取虚拟列表的 displayData 和总数
|
|
|
|
|
|
const displayData = this.scrollViewList.displayData;
|
|
|
|
|
|
const numItems = this.scrollViewList.numItems;
|
2025-10-11 16:19:57 +08:00
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 检查数据有效性
|
|
|
|
|
|
if (!displayData || displayData.length === 0 || numItems === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前显示的最后一个 item 的 ID
|
|
|
|
|
|
const lastDisplayedId = displayData[displayData.length - 1].id;
|
|
|
|
|
|
|
|
|
|
|
|
// 调试日志(显示当前滚动状态)
|
|
|
|
|
|
const firstDisplayedId = displayData[0].id;
|
|
|
|
|
|
logger.log(
|
|
|
|
|
|
`[滚动] 显示范围: ID ${firstDisplayedId}-${lastDisplayedId}, 总数: ${numItems}, 已加载页: ${this.currentPage}`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 触发条件:显示到倒数第 3 个 item 时开始加载下一页
|
|
|
|
|
|
// 例如:总数 10,显示到 ID=7 时触发(10-3=7)
|
|
|
|
|
|
// 总数 20,显示到 ID=17 时触发(20-3=17)
|
|
|
|
|
|
const threshold = numItems - 3;
|
|
|
|
|
|
|
|
|
|
|
|
if (lastDisplayedId >= threshold && lastDisplayedId > this.lastLoadTriggerId) {
|
|
|
|
|
|
logger.log(
|
|
|
|
|
|
`[ThemePanel] 接近末尾 (${lastDisplayedId}/${numItems}),触发加载第 ${
|
|
|
|
|
|
this.currentPage + 1
|
|
|
|
|
|
} 页`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录本次触发的 ID,避免重复触发
|
|
|
|
|
|
this.lastLoadTriggerId = lastDisplayedId;
|
|
|
|
|
|
|
|
|
|
|
|
// 触发分页加载
|
2025-10-11 16:19:57 +08:00
|
|
|
|
this.loadMoreRecommendData();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载下一页推荐数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async loadMoreRecommendData(): Promise<void> {
|
|
|
|
|
|
// 防止重复加载
|
|
|
|
|
|
if (this.isLoadingMore) {
|
|
|
|
|
|
logger.log("[ThemePanel] 正在加载中,跳过");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否还有更多数据
|
|
|
|
|
|
if (!this.hasMoreData) {
|
|
|
|
|
|
logger.log("[ThemePanel] 没有更多数据了");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.isLoadingMore = true;
|
|
|
|
|
|
this.currentPage++;
|
|
|
|
|
|
|
|
|
|
|
|
logger.log(`[ThemePanel] 开始加载第 ${this.currentPage} 页推荐数据`);
|
|
|
|
|
|
|
|
|
|
|
|
const reqData = {
|
|
|
|
|
|
page: this.currentPage,
|
|
|
|
|
|
limit: this.pageLimit,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const res = await GirlService.I.reqRecommendList(reqData);
|
|
|
|
|
|
|
|
|
|
|
|
if (res && res.code === proto.cs.EnmRetCode.SUCCESS) {
|
|
|
|
|
|
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
|
|
|
|
|
|
|
|
|
|
|
|
// 追加数据到 GirlData
|
|
|
|
|
|
girlData.addRecommendList(res.data);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否还有更多数据
|
|
|
|
|
|
const girls = res.data.girls || [];
|
|
|
|
|
|
if (girls.length < this.pageLimit) {
|
|
|
|
|
|
this.hasMoreData = false;
|
|
|
|
|
|
logger.log("[ThemePanel] 已加载所有推荐数据");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 使用虚拟列表:更新总数量,自动触发渲染
|
|
|
|
|
|
if (this.scrollViewList) {
|
|
|
|
|
|
const allGirlIDs = girlData.getRecommendListIds();
|
|
|
|
|
|
this.scrollViewList.numItems = allGirlIDs.length;
|
|
|
|
|
|
logger.log(
|
|
|
|
|
|
`[ThemePanel] 第 ${this.currentPage} 页加载完成,新增 ${girls.length} 项,总计 ${allGirlIDs.length} 项`
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logger.warn("[ThemePanel] scrollViewList 未初始化,无法更新列表");
|
|
|
|
|
|
}
|
2025-10-11 16:19:57 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
logger.warn("[ThemePanel] 加载下一页推荐数据失败:", res);
|
|
|
|
|
|
// 加载失败,回退页码
|
|
|
|
|
|
this.currentPage--;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error("[ThemePanel] 加载下一页推荐数据异常:", error);
|
|
|
|
|
|
// 加载失败,回退页码
|
|
|
|
|
|
this.currentPage--;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.isLoadingMore = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 渲染新增的推荐数据
|
2025-10-14 16:59:02 +08:00
|
|
|
|
* 【已废弃】使用虚拟列表后,通过更新 numItems 自动触发渲染
|
2025-10-11 16:19:57 +08:00
|
|
|
|
* @param newCount 新增数据的数量
|
|
|
|
|
|
*/
|
2025-10-14 16:59:02 +08:00
|
|
|
|
private renderMoreRecommendData_DEPRECATED(newCount: number): void {
|
|
|
|
|
|
// 该方法已被虚拟列表的自动渲染机制替代
|
|
|
|
|
|
// 保留此方法仅用于参考,实际不再调用
|
2025-09-12 18:20:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理商品列表数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async handleShopListData(isPreloaded: boolean): Promise<void> {
|
|
|
|
|
|
if (isPreloaded) {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 商品列表数据已预加载");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
// 数据已经在DataManager中,不需要额外处理
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有预加载数据,则请求数据
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 请求商品列表数据");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
await this.reqShopList();
|
2025-08-11 16:08:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private registerListener() {
|
2025-08-15 13:55:26 +08:00
|
|
|
|
GButton.BandClick(this._nodeTab.ShopPanelEntry, this.enterShop, this);
|
|
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
GButton.BandClick(this.recomandListBtn, this.openRecomandList, this);
|
|
|
|
|
|
GButton.BandClick(this.moreThemeBtn, this.openMoreTheme, this);
|
|
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 注册推荐列表滚动事件(延迟到下一帧,确保虚拟列表已初始化)
|
|
|
|
|
|
this.scheduleOnce(() => {
|
|
|
|
|
|
if (this.scrollViewList) {
|
|
|
|
|
|
// 只监听 scrolling 事件,通过滚动偏移量检测是否接近底部
|
|
|
|
|
|
this.scrollViewList.node.on("scrolling", this.onRecommendScrolling, this);
|
|
|
|
|
|
logger.log("[ThemePanel] 虚拟列表滚动事件已绑定");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logger.warn(
|
|
|
|
|
|
"[ThemePanel] 无法绑定滚动事件,scrollViewList 未初始化"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 0.1);
|
2025-10-11 16:19:57 +08:00
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
Utils.addInnerEL(InnerMsgCode.LanguageChange, this, this.onLanguageChange);
|
|
|
|
|
|
Utils.addInnerEL(InnerMsgCode.BalanceChange, this, this.onBalanceChange);
|
|
|
|
|
|
Utils.addInnerEL(
|
|
|
|
|
|
InnerMsgCode.VipExpireChange,
|
|
|
|
|
|
this,
|
|
|
|
|
|
this.onVipExpireChange
|
|
|
|
|
|
);
|
2025-08-11 16:08:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-15 13:55:26 +08:00
|
|
|
|
enterShop() {
|
2025-10-13 16:20:37 +08:00
|
|
|
|
NavigationManager.Instance.openPopupPanel("PurchasePanel");
|
2025-08-15 13:55:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
openRecomandList() {
|
|
|
|
|
|
this.themeScroll.active = false;
|
|
|
|
|
|
this.recomandScroll.active = true;
|
2025-09-10 18:12:26 +08:00
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 使用虚拟列表:设置数据总量,自动触发渲染
|
|
|
|
|
|
if (this.scrollViewList) {
|
|
|
|
|
|
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
|
|
|
|
|
|
const girlIDs = girlData.getRecommendListIds();
|
|
|
|
|
|
|
|
|
|
|
|
// 重置分页状态(仅在首次打开时)
|
|
|
|
|
|
if (this.scrollViewList.numItems === 0) {
|
|
|
|
|
|
this.currentPage = 1;
|
|
|
|
|
|
this.hasMoreData = true;
|
|
|
|
|
|
this.isLoadingMore = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.scrollViewList.numItems = girlIDs.length;
|
|
|
|
|
|
logger.log(`[ThemePanel] 打开推荐列表,共 ${girlIDs.length} 项`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logger.warn("[ThemePanel] scrollViewList 未初始化");
|
|
|
|
|
|
}
|
2025-09-21 10:35:27 +08:00
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
this.updateTagState(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
openMoreTheme() {
|
|
|
|
|
|
this.themeScroll.active = true;
|
|
|
|
|
|
this.recomandScroll.active = false;
|
|
|
|
|
|
|
|
|
|
|
|
//todo:是否需要刷新数据
|
|
|
|
|
|
|
|
|
|
|
|
this.renderThemeData();
|
|
|
|
|
|
|
|
|
|
|
|
this.updateTagState(false);
|
2025-08-11 16:08:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
updateTagState(isRecomnend: boolean) {
|
|
|
|
|
|
this.recomandListBtn.children[0].active = !isRecomnend;
|
|
|
|
|
|
this.recomandListBtn.children[1].active = isRecomnend;
|
2025-08-11 16:08:59 +08:00
|
|
|
|
|
2025-10-10 16:09:14 +08:00
|
|
|
|
this.moreThemeBtn.children[0].active = isRecomnend;
|
|
|
|
|
|
this.moreThemeBtn.children[1].active = !isRecomnend;
|
2025-08-11 16:08:59 +08:00
|
|
|
|
}
|
2025-08-12 20:22:54 +08:00
|
|
|
|
|
2025-09-09 20:33:43 +08:00
|
|
|
|
// 刷新余额
|
|
|
|
|
|
private refreshCoinNum() {
|
|
|
|
|
|
const walletData = DataManager.I.getDataById<WalletData>(DataId.Wallet);
|
|
|
|
|
|
const balance = walletData.getOriginalBalance();
|
|
|
|
|
|
this.coinNum.string = balance.toString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新 vip失效时间戳
|
|
|
|
|
|
private refreshVipExpire() {
|
|
|
|
|
|
const walletData = DataManager.I.getDataById<WalletData>(DataId.Wallet);
|
2025-09-20 11:43:13 +08:00
|
|
|
|
|
2025-09-20 18:57:32 +08:00
|
|
|
|
if (!walletData.isVip) {
|
2025-10-13 15:39:35 +08:00
|
|
|
|
//this.vipLeftTime.string = LanguageUtils.getText("purchasepanel.notvip");
|
|
|
|
|
|
this._nodeTab.notvipicon.active = true;
|
|
|
|
|
|
this._nodeTab.notVip.active = true;
|
|
|
|
|
|
this.vipLeftTime.node.active = false;
|
2025-09-20 11:43:13 +08:00
|
|
|
|
} else {
|
2025-09-20 12:52:04 +08:00
|
|
|
|
const vipExpire = walletData.vipExpire;
|
|
|
|
|
|
|
2025-09-20 11:43:13 +08:00
|
|
|
|
const leftTime = Utils.formatVipLeftTime(vipExpire);
|
2025-10-13 15:39:35 +08:00
|
|
|
|
this.vipLeftTime.node.active = true;
|
|
|
|
|
|
|
|
|
|
|
|
this._nodeTab.notvipicon.active = false;
|
|
|
|
|
|
this._nodeTab.notVip.active = false;
|
2025-09-22 11:59:38 +08:00
|
|
|
|
|
|
|
|
|
|
if (leftTime == null) {
|
|
|
|
|
|
LanguageUtils.getText("purchasepanel.notvip");
|
|
|
|
|
|
} else {
|
2025-10-10 18:21:33 +08:00
|
|
|
|
this.vipLeftTime.string = leftTime;
|
2025-09-22 11:59:38 +08:00
|
|
|
|
}
|
2025-09-20 11:43:13 +08:00
|
|
|
|
}
|
2025-09-10 18:12:26 +08:00
|
|
|
|
}
|
2025-09-09 20:33:43 +08:00
|
|
|
|
|
2025-09-10 17:37:24 +08:00
|
|
|
|
// 获取商品列表
|
|
|
|
|
|
async reqShopList() {
|
|
|
|
|
|
// 请求购买商品
|
2025-09-10 18:13:39 +08:00
|
|
|
|
const reqData = {};
|
2025-09-10 17:37:24 +08:00
|
|
|
|
let res = await ShopService.I.reqShopList(reqData);
|
|
|
|
|
|
if (res && res.code === proto.cs.EnmRetCode.SUCCESS) {
|
|
|
|
|
|
const resData = res.data;
|
|
|
|
|
|
// 保存数据
|
|
|
|
|
|
const shopData = DataManager.I.getDataById<ShopData>(DataId.Shop);
|
|
|
|
|
|
shopData.goods = res.data;
|
|
|
|
|
|
}
|
2025-09-10 18:13:39 +08:00
|
|
|
|
}
|
2025-09-10 17:37:24 +08:00
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 清理缓存节点
|
|
|
|
|
|
*/
|
|
|
|
|
|
private clearCache(): void {
|
2025-10-11 16:19:57 +08:00
|
|
|
|
// 清理主题缓存
|
2025-09-12 18:20:41 +08:00
|
|
|
|
if (this.cache && this.cache.length > 0) {
|
2025-10-11 16:19:57 +08:00
|
|
|
|
logger.log(`[ThemePanel] 清理 ${this.cache.length} 个主题缓存节点`);
|
2025-09-12 18:20:41 +08:00
|
|
|
|
for (let i = this.cache.length - 1; i >= 0; i--) {
|
|
|
|
|
|
if (this.cache[i] && this.cache[i].node && this.cache[i].node.isValid) {
|
|
|
|
|
|
this.cache[i].node.destroy();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.cache = [];
|
|
|
|
|
|
}
|
2025-10-11 16:19:57 +08:00
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 虚拟列表自动管理对象池,只需重置数量
|
|
|
|
|
|
if (this.scrollViewList) {
|
|
|
|
|
|
this.scrollViewList.numItems = 0;
|
|
|
|
|
|
logger.log("[ThemePanel] 重置虚拟列表项数量");
|
2025-10-11 16:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置推荐列表分页状态
|
|
|
|
|
|
this.currentPage = 1;
|
|
|
|
|
|
this.hasMoreData = true;
|
|
|
|
|
|
this.isLoadingMore = false;
|
2025-10-14 16:59:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 重置触发记录
|
|
|
|
|
|
this.lastLoadTriggerId = -1;
|
2025-08-12 20:22:54 +08:00
|
|
|
|
}
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 页面被隐藏时调用
|
|
|
|
|
|
*/
|
|
|
|
|
|
public onHide(): void {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] onHide called");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
this.cancelCurrentOperation();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 取消当前操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
private cancelCurrentOperation(): void {
|
|
|
|
|
|
if (this.abortController) {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] 取消当前操作");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
this.abortController.abort();
|
|
|
|
|
|
this.abortController = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onDestroy(): void {
|
2025-09-21 20:18:24 +08:00
|
|
|
|
logger.log("[ThemePanel] onDestroy called");
|
2025-09-12 18:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 取消当前操作
|
|
|
|
|
|
this.cancelCurrentOperation();
|
|
|
|
|
|
|
|
|
|
|
|
// 清理缓存
|
|
|
|
|
|
this.clearCache();
|
|
|
|
|
|
|
|
|
|
|
|
// 重置加载状态
|
|
|
|
|
|
this.isLoading = false;
|
|
|
|
|
|
this.loadingPromise = null;
|
|
|
|
|
|
|
2025-10-14 16:59:02 +08:00
|
|
|
|
// 移除虚拟列表滚动事件监听
|
|
|
|
|
|
if (this.scrollViewList) {
|
|
|
|
|
|
this.scrollViewList.node.off("scrolling", this.onRecommendScrolling, this);
|
|
|
|
|
|
logger.log("[ThemePanel] 虚拟列表滚动事件已移除");
|
2025-10-11 16:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 18:20:41 +08:00
|
|
|
|
// 移除事件监听器(修复回调函数引用问题)
|
|
|
|
|
|
Utils.removeInnerEL(
|
|
|
|
|
|
InnerMsgCode.LanguageChange,
|
|
|
|
|
|
this,
|
|
|
|
|
|
this.onLanguageChange
|
|
|
|
|
|
);
|
|
|
|
|
|
Utils.removeInnerEL(InnerMsgCode.BalanceChange, this, this.onBalanceChange);
|
|
|
|
|
|
Utils.removeInnerEL(
|
|
|
|
|
|
InnerMsgCode.VipExpireChange,
|
|
|
|
|
|
this,
|
|
|
|
|
|
this.onVipExpireChange
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 事件回调函数(避免匿名函数导致的内存泄漏)
|
|
|
|
|
|
private onLanguageChange = () => {
|
2025-10-10 16:09:14 +08:00
|
|
|
|
//this.recName.string = LanguageUtils.getText(this.rectNameKey);
|
2025-09-20 13:47:44 +08:00
|
|
|
|
this.refreshVipExpire();
|
2025-09-12 18:20:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private onBalanceChange = () => {
|
|
|
|
|
|
this.refreshCoinNum();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private onVipExpireChange = () => {
|
|
|
|
|
|
this.refreshVipExpire();
|
|
|
|
|
|
};
|
2025-08-11 16:08:59 +08:00
|
|
|
|
}
|