Files
18xchat/assets/Scripts/chat18x/ui/panels/ThemePanel.ts
T
2025-10-13 15:39:35 +08:00

670 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<void> | 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<AccountData>(
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<void> {
if (isPreloaded) {
logger.log("[ThemePanel] 使用预加载的主题数据");
// 直接使用预加载的数据渲染界面
const themeData = DataManager.I.getDataById<ThemeData>(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<ThemeData>(DataId.Theme);
themeData.themes = res.data;
}
}
/**
* 渲染主题数据
*/
private renderThemeData(): void {
if (this.cache.length > 0) return;
const themeData = DataManager.I.getDataById<ThemeData>(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<void> {
if (isPreloaded) {
logger.log("[ThemePanel] 使用预加载的推荐列表数据");
// 直接使用预加载的数据渲染界面
const girlData = DataManager.I.getDataById<GirlData>(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<GirlData>(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<GirlData>(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<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] 已加载所有推荐数据");
}
// 渲染新增数据
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<GirlData>(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<void> {
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.openSubPanel(
"PurchasePanel",
this.node,
{ base: this.node },
{
openAnimation: PageTransitionType.SLIDE_LEFT,
closeAnimation: PageTransitionType.SLIDE_RIGHT,
}
);
}
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<WalletData>(DataId.Wallet);
const balance = walletData.getOriginalBalance();
this.coinNum.string = balance.toString();
}
// 刷新 vip失效时间戳
private refreshVipExpire() {
const walletData = DataManager.I.getDataById<WalletData>(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<ShopData>(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();
};
}