import { _decorator, Component, Node, instantiate, Label, Sprite, UITransform, ScrollView, } 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"; import { ThemeItem } from "../../uiitems/ThemeItem"; import { NavigationManager, PageTransitionType, PanelType, } from "../../manager/NavigationManager"; import LanguageUtils from "../../../Main/Common/LanguageUtils"; import { InnerMsgCode } from "../../../Main/Config/InnerMsgCode"; import { ThemeService } from "db://assets/Scripts/chat18x/network/services/ThemeService"; import { GirlService } from "db://assets/Scripts/chat18x/network/services/GirlService"; import { ShopService } from "db://assets/Scripts/chat18x/network/services/ShopService"; import { DataManager, DataId } from "../../data/DataManager"; import { ThemeData } from "../../data/ThemeData"; import { GirlData } from "../../data/GirlData"; import { WalletData } from "../../data/WalletData"; import { AccountData } from "../../data/AccountData"; import { ShopData } from "../../data/ShopData"; import { EnvData } from "../../data/EnvData"; import proto from "db://assets/Scripts/proto/proto.pb.js"; import { MainController } from "../../core/MainController"; import { logger } from "db://assets/Scripts/Main/Common/Logger"; import { RecomandItem } from "../../uiitems/RecomandItem"; const { ccclass, property } = _decorator; @ccclass("ThemePanel") export class ThemePanel extends li_BaseView { private _nodeTab: any = {}; coinNum: Label; itemInst: ThemeItem; recItemInst: RecomandItem; themeContent: Node; recGirlContent: Node; private vipLeftTime: Label; //recName: Label; //recSprite: Sprite; cache: ThemeItem[] = []; recItemCache: RecomandItem[] = []; // 子页面管理 // 加载状态保护 private isLoading: boolean = false; private loadingPromise: Promise | null = null; private abortController: AbortController | null = null; private recomandListBtn: Node; private moreThemeBtn: Node; private themeScroll: Node; private recomandScroll: Node; private recomandScrollView: ScrollView = null; // 推荐列表分页状态 private currentPage: number = 1; private pageLimit: number = 10; private isLoadingMore: boolean = false; private hasMoreData: boolean = true; onLoadCT() { Utils.parseNode(this.node, this._nodeTab); this.coinNum = this._nodeTab.coinNum.getComponent(Label); //this.recName = this._nodeTab.characterNameText.getComponent(Label); //this.recSprite = this._nodeTab.recPic.getComponent(Sprite); this.vipLeftTime = this._nodeTab.vipText.getComponent(Label); this.recomandListBtn = this._nodeTab.recomandList; this.moreThemeBtn = this._nodeTab.moreTheme; this.themeScroll = this._nodeTab.themeScroll; this.recomandScroll = this._nodeTab.recomandScroll; // 获取推荐列表的 ScrollView 组件 if (this.recomandScroll) { this.recomandScrollView = this.recomandScroll.getComponentInChildren(ScrollView); } this.registerListener(); this.itemInst = this._nodeTab.ThemeItem.getComponent(ThemeItem); this.itemInst.node.active = false; this.recItemInst = this._nodeTab.recItem.getComponent(RecomandItem); this.recItemInst.node.active = false; this.themeContent = this._nodeTab.allThemecontent; this.recGirlContent = this._nodeTab.allRecGirlContent; this.Show(); } async Show(forceRefresh: boolean = false) { // 防止重复加载 if (this.isLoading) { logger.log("[ThemePanel] 已在加载中,等待当前加载完成"); if (this.loadingPromise) { await this.loadingPromise; } return; } this.isLoading = true; this.loadingPromise = this._refreshDataInternal(forceRefresh); try { await this.loadingPromise; } finally { this.isLoading = false; this.loadingPromise = null; } this.openRecomandList(); } private async _refreshDataInternal(forceRefresh: boolean = false) { // 创建新的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( DataId.Account ); //this.uid.string = "uid: " + accountData.accId; // 检查是否已被取消 if (signal.aborted) { throw new Error("Operation was aborted"); } // 清理旧的cache节点(每次都清理,解决内存泄漏问题) this.clearCache(); // 如果需要强制刷新,先刷新数据 if (forceRefresh) { logger.log("[ThemePanel] 开始强制刷新数据"); await Promise.all([ MainController.I.refreshThemeData(), MainController.I.refreshRecommendList(), ]); logger.log("[ThemePanel] 数据刷新完成"); // 再次检查是否已被取消 if (signal.aborted) { throw new Error("Operation was aborted"); } } // 优先使用MainController中预加载的数据 const preloadStatus = MainController.I.getPreloadStatus(); logger.log("[ThemePanel] 预加载状态:", preloadStatus); // 处理主题数据 await this.handleThemeData(preloadStatus.themeData); // 检查是否已被取消 if (signal.aborted) { throw new Error("Operation was aborted"); } // 处理推荐列表数据 await this.handleRecommendData(preloadStatus.recommendList); // 检查是否已被取消 if (signal.aborted) { throw new Error("Operation was aborted"); } this._nodeTab.loadingRec.active = false; // 处理商品列表数据 await this.handleShopListData(preloadStatus.shopList); } catch (error) { logger.error("[ThemePanel] Show操作出错:", error); this._nodeTab.loadingRec.active = false; // 如果不是取消操作导致的错误,可以考虑显示错误提示 if (error.message !== "Operation was aborted") { // 这里可以添加错误提示逻辑 logger.error("[ThemePanel] 数据加载失败,请稍后重试"); } } finally { this.abortController = null; } } /** * 处理主题数据 */ private async handleThemeData(isPreloaded: boolean): Promise { if (isPreloaded) { logger.log("[ThemePanel] 使用预加载的主题数据"); // 直接使用预加载的数据渲染界面 const themeData = DataManager.I.getDataById(DataId.Theme); if (themeData && themeData.themes) { return; } } // 如果没有预加载数据,则请求数据 logger.log("[ThemePanel] 请求主题数据"); const reqData = {}; let res = await ThemeService.I.reqHallTheme(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { // 保存数据 const themeData = DataManager.I.getDataById(DataId.Theme); themeData.themes = res.data; } } /** * 渲染主题数据 */ private renderThemeData(): void { if (this.cache.length > 0) return; const themeData = DataManager.I.getDataById(DataId.Theme); const themeIds = themeData.themeIds; for (let id of themeIds) { let newNode = instantiate(this.itemInst.node); let item = newNode.getComponent(ThemeItem); item.refresh(id, this.node); newNode.active = true; this.cache.push(item); this.themeContent.addChild(newNode); } } /** * 处理推荐数据 */ private async handleRecommendData(isPreloaded: boolean): Promise { if (isPreloaded) { logger.log("[ThemePanel] 使用预加载的推荐列表数据"); // 直接使用预加载的数据渲染界面 const girlData = DataManager.I.getDataById(DataId.Girl); if (girlData) { return; } } // 如果没有预加载数据,则请求数据 logger.log("[ThemePanel] 请求推荐列表数据"); const reqData = { page: 1, limit: 10, }; let res = await GirlService.I.reqRecommendList(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { const girlData = DataManager.I.getDataById(DataId.Girl); girlData.setRecommendList(res.data); } } /** * 渲染推荐列表数据(首次渲染) */ private renderRecommendData(): void { if (this.recItemCache.length > 0) return; // 重置分页状态 this.currentPage = 1; this.hasMoreData = true; this.isLoadingMore = false; const girlData = DataManager.I.getDataById(DataId.Girl); // 获取推荐列表的所有 ID let girlIDs = girlData.getRecommendListIds(); // 遍历创建推荐项 for (let id of girlIDs) { let newNode = instantiate(this.recItemInst.node); let item = newNode.getComponent(RecomandItem); item.init(); item.refresh(id); newNode.active = true; this.recItemCache.push(item); this.recGirlContent.addChild(newNode); } // 检查首次加载的数据量,判断是否还有更多数据 if (girlIDs.length < this.pageLimit) { this.hasMoreData = false; } logger.log(`[ThemePanel] 渲染推荐列表完成,共 ${girlIDs.length} 个推荐项`); } /** * 滚动到底部回调(Cocos Creator 内置事件) */ private onRecommendScrollToBottom(): void { logger.log("[ThemePanel] 推荐列表滚动到底部"); this.loadMoreRecommendData(); } /** * 滚动中回调,用于检测接近底部 */ private onRecommendScrolling(): void { if (!this.recomandScrollView) return; // 获取滚动进度(0-1) const scrollOffset = this.recomandScrollView.getScrollOffset(); const maxScrollOffset = this.recomandScrollView.getMaxScrollOffset(); // 当滚动到距离底部 100 像素以内时,开始加载 if (maxScrollOffset.y > 0 && scrollOffset.y >= maxScrollOffset.y - 100) { this.loadMoreRecommendData(); } } /** * 加载下一页推荐数据 */ private async loadMoreRecommendData(): Promise { // 防止重复加载 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(DataId.Girl); // 追加数据到 GirlData girlData.addRecommendList(res.data); // 检查是否还有更多数据 const girls = res.data.girls || []; if (girls.length < this.pageLimit) { this.hasMoreData = false; logger.log("[ThemePanel] 已加载所有推荐数据"); } // 渲染新增数据 this.renderMoreRecommendData(girls.length); logger.log( `[ThemePanel] 第 ${this.currentPage} 页加载完成,新增 ${girls.length} 项` ); } else { logger.warn("[ThemePanel] 加载下一页推荐数据失败:", res); // 加载失败,回退页码 this.currentPage--; } } catch (error) { logger.error("[ThemePanel] 加载下一页推荐数据异常:", error); // 加载失败,回退页码 this.currentPage--; } finally { this.isLoadingMore = false; } } /** * 渲染新增的推荐数据 * @param newCount 新增数据的数量 */ private renderMoreRecommendData(newCount: number): void { if (newCount <= 0) return; const girlData = DataManager.I.getDataById(DataId.Girl); const allGirlIDs = girlData.getRecommendListIds(); // 只渲染新增的部分(从末尾开始取 newCount 个) const startIndex = allGirlIDs.length - newCount; const newGirlIDs = allGirlIDs.slice(startIndex); logger.log(`[ThemePanel] 开始渲染 ${newCount} 个新增推荐项`); for (let id of newGirlIDs) { let newNode = instantiate(this.recItemInst.node); let item = newNode.getComponent(RecomandItem); item.init(); item.refresh(id); newNode.active = true; this.recItemCache.push(item); this.recGirlContent.addChild(newNode); } logger.log(`[ThemePanel] 新增推荐项渲染完成`); } /** * 处理商品列表数据 */ private async handleShopListData(isPreloaded: boolean): Promise { if (isPreloaded) { logger.log("[ThemePanel] 商品列表数据已预加载"); // 数据已经在DataManager中,不需要额外处理 return; } // 如果没有预加载数据,则请求数据 logger.log("[ThemePanel] 请求商品列表数据"); await this.reqShopList(); } private registerListener() { GButton.BandClick(this._nodeTab.ShopPanelEntry, this.enterShop, this); GButton.BandClick(this.recomandListBtn, this.openRecomandList, this); GButton.BandClick(this.moreThemeBtn, this.openMoreTheme, this); // 注册推荐列表滚动事件 if (this.recomandScrollView) { this.recomandScrollView.node.on( "scroll-to-bottom", this.onRecommendScrollToBottom, this ); this.recomandScrollView.node.on( "scrolling", this.onRecommendScrolling, this ); } Utils.addInnerEL(InnerMsgCode.LanguageChange, this, this.onLanguageChange); Utils.addInnerEL(InnerMsgCode.BalanceChange, this, this.onBalanceChange); Utils.addInnerEL( InnerMsgCode.VipExpireChange, this, this.onVipExpireChange ); } enterShop() { NavigationManager.Instance.openPopupPanel("PurchasePanel"); } openRecomandList() { this.themeScroll.active = false; this.recomandScroll.active = true; //todo:是否需要刷新数据 this.renderRecommendData(); this.updateTagState(true); } openMoreTheme() { this.themeScroll.active = true; this.recomandScroll.active = false; //todo:是否需要刷新数据 this.renderThemeData(); this.updateTagState(false); } updateTagState(isRecomnend: boolean) { this.recomandListBtn.children[0].active = !isRecomnend; this.recomandListBtn.children[1].active = isRecomnend; this.moreThemeBtn.children[0].active = isRecomnend; this.moreThemeBtn.children[1].active = !isRecomnend; } // 刷新余额 private refreshCoinNum() { const walletData = DataManager.I.getDataById(DataId.Wallet); const balance = walletData.getOriginalBalance(); this.coinNum.string = balance.toString(); } // 刷新 vip失效时间戳 private refreshVipExpire() { const walletData = DataManager.I.getDataById(DataId.Wallet); if (!walletData.isVip) { //this.vipLeftTime.string = LanguageUtils.getText("purchasepanel.notvip"); this._nodeTab.notvipicon.active = true; this._nodeTab.notVip.active = true; this.vipLeftTime.node.active = false; } else { const vipExpire = walletData.vipExpire; const leftTime = Utils.formatVipLeftTime(vipExpire); this.vipLeftTime.node.active = true; this._nodeTab.notvipicon.active = false; this._nodeTab.notVip.active = false; if (leftTime == null) { LanguageUtils.getText("purchasepanel.notvip"); } else { this.vipLeftTime.string = leftTime; } } } // 获取商品列表 async reqShopList() { // 请求购买商品 const reqData = {}; let res = await ShopService.I.reqShopList(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { const resData = res.data; // 保存数据 const shopData = DataManager.I.getDataById(DataId.Shop); shopData.goods = res.data; } } /** * 清理缓存节点 */ private clearCache(): void { // 清理主题缓存 if (this.cache && this.cache.length > 0) { logger.log(`[ThemePanel] 清理 ${this.cache.length} 个主题缓存节点`); 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 = []; } // 清理推荐项缓存 if (this.recItemCache && this.recItemCache.length > 0) { logger.log( `[ThemePanel] 清理 ${this.recItemCache.length} 个推荐项缓存节点` ); for (let i = this.recItemCache.length - 1; i >= 0; i--) { if ( this.recItemCache[i] && this.recItemCache[i].node && this.recItemCache[i].node.isValid ) { this.recItemCache[i].node.destroy(); } } this.recItemCache = []; } // 重置推荐列表分页状态 this.currentPage = 1; this.hasMoreData = true; this.isLoadingMore = false; } /** * 页面被隐藏时调用 */ public onHide(): void { logger.log("[ThemePanel] onHide called"); this.cancelCurrentOperation(); } /** * 取消当前操作 */ private cancelCurrentOperation(): void { if (this.abortController) { logger.log("[ThemePanel] 取消当前操作"); this.abortController.abort(); this.abortController = null; } } onDestroy(): void { logger.log("[ThemePanel] onDestroy called"); // 取消当前操作 this.cancelCurrentOperation(); // 清理缓存 this.clearCache(); // 重置加载状态 this.isLoading = false; this.loadingPromise = null; // 移除推荐列表滚动事件监听 if (this.recomandScrollView) { this.recomandScrollView.node.off( "scroll-to-bottom", this.onRecommendScrollToBottom, this ); this.recomandScrollView.node.off( "scrolling", this.onRecommendScrolling, this ); } // 移除事件监听器(修复回调函数引用问题) Utils.removeInnerEL( InnerMsgCode.LanguageChange, this, this.onLanguageChange ); Utils.removeInnerEL(InnerMsgCode.BalanceChange, this, this.onBalanceChange); Utils.removeInnerEL( InnerMsgCode.VipExpireChange, this, this.onVipExpireChange ); } // 事件回调函数(避免匿名函数导致的内存泄漏) private onLanguageChange = () => { //this.recName.string = LanguageUtils.getText(this.rectNameKey); this.refreshVipExpire(); }; private onBalanceChange = () => { this.refreshCoinNum(); }; private onVipExpireChange = () => { this.refreshVipExpire(); }; }