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

662 lines
19 KiB
TypeScript
Raw Normal View History

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";
import { ThemeItem } from "../../uiitems/ThemeItem";
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";
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-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
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-10 16:09:14 +08:00
recItemCache: RecomandItem[] = [];
// 子页面管理
// 加载状态保护
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-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-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);
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();
}
async Show(forceRefresh: boolean = false) {
// 防止重复加载
if (this.isLoading) {
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] 已在加载中,等待当前加载完成");
if (this.loadingPromise) {
await this.loadingPromise;
}
return;
}
this.isLoading = true;
2025-10-10 16:09:14 +08:00
this.loadingPromise = this._refreshDataInternal(forceRefresh);
try {
await this.loadingPromise;
} finally {
this.isLoading = false;
this.loadingPromise = null;
}
2025-10-10 16:09:14 +08:00
this.openRecomandList();
}
2025-10-10 16:09:14 +08:00
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
);
2025-09-18 14:06:53 +08:00
//this.uid.string = "uid: " + accountData.accId;
// 检查是否已被取消
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] 开始强制刷新数据");
await Promise.all([
MainController.I.refreshThemeData(),
2025-10-11 16:19:57 +08:00
MainController.I.refreshRecommendList(),
]);
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] 数据刷新完成");
// 再次检查是否已被取消
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);
// 处理主题数据
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);
// 检查是否已被取消
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);
this._nodeTab.loadingRec.active = false;
// 如果不是取消操作导致的错误,可以考虑显示错误提示
if (error.message !== "Operation was aborted") {
// 这里可以添加错误提示逻辑
2025-09-21 20:18:24 +08:00
logger.error("[ThemePanel] 数据加载失败,请稍后重试");
}
} finally {
this.abortController = null;
}
}
/**
* 处理主题数据
*/
private async handleThemeData(isPreloaded: boolean): Promise<void> {
if (isPreloaded) {
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] 使用预加载的主题数据");
// 直接使用预加载的数据渲染界面
const themeData = DataManager.I.getDataById<ThemeData>(DataId.Theme);
if (themeData && themeData.themes) {
return;
2025-08-11 16:08:59 +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-10-10 16:09:14 +08:00
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);
2025-10-10 16:09:14 +08:00
this.themeContent.addChild(newNode);
}
}
/**
2025-10-10 16:09:14 +08:00
* 处理推荐数据
*/
2025-10-10 16:09:14 +08:00
private async handleRecommendData(isPreloaded: boolean): Promise<void> {
if (isPreloaded) {
2025-10-11 16:19:57 +08:00
logger.log("[ThemePanel] 使用预加载的推荐列表数据");
// 直接使用预加载的数据渲染界面
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-10-11 16:19:57 +08:00
logger.log("[ThemePanel] 请求推荐列表数据");
const reqData = {
page: 1,
limit: 10,
};
let res = await GirlService.I.reqRecommendList(reqData);
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-10 17:37:24 +08:00
/**
2025-10-11 16:19:57 +08:00
* 渲染推荐列表数据(首次渲染)
*/
2025-10-10 16:09:14 +08:00
private renderRecommendData(): void {
if (this.recItemCache.length > 0) return;
2025-10-11 16:19:57 +08:00
// 重置分页状态
this.currentPage = 1;
this.hasMoreData = true;
this.isLoadingMore = false;
2025-10-10 16:09:14 +08:00
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
2025-10-11 16:19:57 +08:00
// 获取推荐列表的所有 ID
let girlIDs = girlData.getRecommendListIds();
// 遍历创建推荐项
2025-10-10 16:09:14 +08:00
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);
}
2025-10-11 16:19:57 +08:00
// 检查首次加载的数据量,判断是否还有更多数据
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) {
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] 商品列表数据已预加载");
// 数据已经在DataManager中,不需要额外处理
return;
}
// 如果没有预加载数据,则请求数据
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] 请求商品列表数据");
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-11 16:19:57 +08:00
// 注册推荐列表滚动事件
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
);
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-10 16:09:14 +08:00
//todo:是否需要刷新数据
this.renderRecommendData();
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;
if (leftTime == null) {
LanguageUtils.getText("purchasepanel.notvip");
} else {
2025-10-10 18:21:33 +08:00
this.vipLeftTime.string = leftTime;
}
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() {
// 请求购买商品
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 17:37:24 +08:00
/**
* 清理缓存节点
*/
private clearCache(): void {
2025-10-11 16:19:57 +08:00
// 清理主题缓存
if (this.cache && this.cache.length > 0) {
2025-10-11 16:19:57 +08:00
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 = [];
}
2025-10-11 16:19:57 +08:00
// 清理推荐项缓存
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;
2025-08-12 20:22:54 +08:00
}
/**
* 页面被隐藏时调用
*/
public onHide(): void {
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] onHide called");
this.cancelCurrentOperation();
}
/**
* 取消当前操作
*/
private cancelCurrentOperation(): void {
if (this.abortController) {
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] 取消当前操作");
this.abortController.abort();
this.abortController = null;
}
}
onDestroy(): void {
2025-09-21 20:18:24 +08:00
logger.log("[ThemePanel] onDestroy called");
// 取消当前操作
this.cancelCurrentOperation();
// 清理缓存
this.clearCache();
// 重置加载状态
this.isLoading = false;
this.loadingPromise = null;
2025-10-11 16:19:57 +08:00
// 移除推荐列表滚动事件监听
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 = () => {
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();
};
private onBalanceChange = () => {
this.refreshCoinNum();
};
private onVipExpireChange = () => {
this.refreshVipExpire();
};
2025-08-11 16:08:59 +08:00
}