import SevenDayGiftBootstrapApi, { SevenDayActivityInfo } from "./SevenDayGiftBootstrapApi"; import HomePopupQueue from "../../home_popup_queue/HomePopupQueue"; const { ccclass } = cc._decorator; const AUTO_OPEN_KEY = "seven_day_gift_auto_open_v1"; const BUNDLE_NAME = "seven_day_gift"; const GIFT_PREFAB_PATH = "prefab/sevenDayGift"; // 主包通过接口调用分包,避免静态引入活动脚本和资源。 interface SevenDayGiftView { initializeRuntime(info: SevenDayActivityInfo, closeHandler: () => void): boolean; updateRuntimeInfo(info: SevenDayActivityInfo): void; show(autoClaim?: boolean): boolean; } /** 主页入口:查询活动、安排自动展示、管理分包生命周期。 */ @ccclass export default class SevenDayGiftHost extends cc.Component { private giftNode: cc.Node = null; private giftView: SevenDayGiftView = null; private giftBundle: cc.AssetManager.Bundle = null; private giftLoading = false; private giftLoadId = 0; // 加载批次号,用于忽略过期回调。 private giftLoadCallbacks: Array<(error?: Error) => void> = []; private info: SevenDayActivityInfo = null; private querying = false; private retryCount = 0; private testPanelNode: cc.Node = null; // 监听回到前台事件,并在正式环境查询活动。 onLoad() { if (this.node.parent) this.node.setContentSize(this.node.parent.getContentSize()); cc.game.on(cc.game.EVENT_SHOW, this.onGameShow, this); const gameTool = cc.fx && cc.fx.GameTool; if (CC_PREVIEW || (gameTool && gameTool.getWechatGameVersion() === "开发版")) this.loadTestPanel(); if (CC_PREVIEW) return; this.queryActivity(true); } // 移除监听,同时清理弹窗和分包资源。 onDestroy() { if (this.testPanelNode && cc.isValid(this.testPanelNode)) this.testPanelNode.destroy(); cc.game.off(cc.game.EVENT_SHOW, this.onGameShow, this); this.releaseGiftAssets(true); } // 开发环境按需加载接口测试面板。 private loadTestPanel() { // 主包不静态引用测试脚本;发布构建可以直接排除 testSeven。 cc.assetManager.loadBundle("testSeven", error => { if (!cc.isValid(this.node, true)) return; if (error) { cc.warn("[SevenDayGiftHost] 测试面板未加载(需要包含 testSeven Bundle)", error); return; } const canvas = cc.find("Canvas"); if (!canvas) return; const node = new cc.Node("SevenApiTestPanel"); canvas.addChild(node); node.setPosition(0, 0); node.zIndex = 3000; this.testPanelNode = node; const panel: any = node.addComponent("SevenApiPanel"); panel.testHome = (info: SevenDayActivityInfo) => { if (this.giftLoading || (this.giftNode && this.giftNode.active)) { cc.warn("[SevenDayGiftHost] 请先关闭当前七日活动,再测试自动弹窗"); return; } this.info = info; if (this.shouldAutoOpen()) this.openGift(true); }; }); } // 等待登录 UID 就绪后查询,避免并发重复请求。 private queryActivity(allowAutoOpen: boolean) { if (this.querying) return; if (!SevenDayGiftBootstrapApi.getUid()) { if (this.retryCount < 10) { this.retryCount++; this.scheduleOnce(() => this.queryActivity(allowAutoOpen), 1); } else { cc.warn("[SevenDayGiftHost] 10 秒内没有取得 uid"); } return; } this.querying = true; SevenDayGiftBootstrapApi.getInfo(response => { this.querying = false; if (!response || response.code !== 1 || !response.data) return; this.retryCount = 0; this.info = response.data; if (this.giftView) this.giftView.updateRuntimeInfo(this.info); if (allowAutoOpen && this.shouldAutoOpen()) this.openGift(true); }); } // 把七日活动加入主页弹窗队列。 private openGift(autoClaim: boolean) { // 保留旧参数名;现在表示自动展示来源,不会自动领取。 const canvas = cc.find("Canvas"); if (!canvas) return; HomePopupQueue.get(canvas).enqueue("sevenDayGift", 10, done => this.openQueuedGift(autoClaim, done)); } // 轮到本活动时加载并显示,成功显示后才记录当天已弹。 private openQueuedGift(autoClaim: boolean, done: () => void) { this.ensureGiftLoaded((error?: Error) => { done(); if (error) { cc.error("[SevenDayGiftHost] 七日活动分包加载失败", error); return; } if (!this.info || !this.giftView) return; if (autoClaim && !this.shouldAutoOpen()) return; if (!this.initializeGiftView() || !this.giftView.show(autoClaim)) { cc.error("[SevenDayGiftHost] 弹窗初始化或显示失败,未记录当天已弹"); this.releaseGiftAssets(); return; } if (autoClaim) this.markAutoOpened(); console.log("[SevenDayGiftHost] 弹窗已打开", { autoClaim }); }); } // 把活动快照和关闭后的资源清理回调交给分包。 private initializeGiftView(): boolean { if (!this.giftView) return false; return this.giftView.initializeRuntime(this.info, () => this.releaseGiftAssets()); } // 复用已加载视图;多个加载请求共用一次分包加载。 private ensureGiftLoaded(callback: (error?: Error) => void) { if (this.giftView && this.giftNode && cc.isValid(this.giftNode)) { callback(); return; } this.giftLoadCallbacks.push(callback); if (this.giftLoading) return; this.giftLoading = true; const loadId = ++this.giftLoadId; const loadPrefab = (bundle: cc.AssetManager.Bundle) => { bundle.load(GIFT_PREFAB_PATH, cc.Prefab, (error: Error, prefab: cc.Prefab) => { if (loadId !== this.giftLoadId || !cc.isValid(this.node, true)) { this.releaseBundle(bundle); return; } if (error || !prefab) { this.releaseBundle(bundle); this.finishGiftLoad(error || new Error("sevenDayGift prefab is missing")); return; } let giftNode: cc.Node = null; try { giftNode = cc.instantiate(prefab); giftNode.active = false; giftNode.setPosition(0, 0); this.node.addChild(giftNode); } catch (instantiateError) { if (giftNode && cc.isValid(giftNode)) giftNode.destroy(); this.releaseBundle(bundle); this.finishGiftLoad(instantiateError as Error); return; } const giftView = giftNode.getComponent("SevenDayGift") as any as SevenDayGiftView; if (!giftView) { giftNode.destroy(); this.releaseBundle(bundle); this.finishGiftLoad(new Error("SevenDayGift component is missing")); return; } this.giftBundle = bundle; this.giftNode = giftNode; this.giftView = giftView; this.finishGiftLoad(); }); }; const loadedBundle = cc.assetManager.getBundle(BUNDLE_NAME); if (loadedBundle) { loadPrefab(loadedBundle); return; } cc.assetManager.loadBundle(BUNDLE_NAME, (error: Error, bundle: cc.AssetManager.Bundle) => { if (error || !bundle) { this.finishGiftLoad(error || new Error("seven_day_gift bundle is missing")); return; } loadPrefab(bundle); }); } // 统一通知本次等待加载的调用方。 private finishGiftLoad(error?: Error) { this.giftLoading = false; const callbacks = this.giftLoadCallbacks.splice(0); callbacks.forEach(callback => callback(error)); } // 使旧加载回调失效,并在节点销毁后释放资源。 private releaseGiftAssets(immediate: boolean = false) { this.giftLoadId++; this.giftLoadCallbacks.length = 0; if (this.giftNode && cc.isValid(this.giftNode)) { this.giftNode.active = false; this.giftNode.removeFromParent(); this.giftNode.destroy(); } this.giftNode = null; this.giftView = null; this.info = null; const bundle = this.giftBundle; this.giftBundle = null; if (!bundle) return; const release = () => { if (this.giftBundle === bundle) return; this.releaseBundle(bundle); }; if (immediate) release(); else this.scheduleOnce(release, 0); } // 仅释放当前仍登记在资源管理器中的活动分包。 private releaseBundle(bundle: cc.AssetManager.Bundle) { if (!bundle || cc.assetManager.getBundle(BUNDLE_NAME) !== bundle) return; bundle.releaseAll(); if (cc.assetManager.getBundle(BUNDLE_NAME) === bundle) { cc.assetManager.removeBundle(bundle); } } // 根据触发条件、活动状态和服务端时间判断是否可展示。 private isActivityVisible(): boolean { if (!this.info || !this.info.activityId || !this.info.triggerReached) return false; if (this.info.status === "expired" || this.info.status === "completed") return false; const serverNow = this.toTime(this.info.serverNow); const endAt = this.toTime(this.info.endAt); return !serverNow || !endAt || serverNow < endAt; } // 今天可领且当前账号当天未弹出过,才自动展示。 private shouldAutoOpen(): boolean { if (!this.isActivityVisible() || !this.info.canClaim || this.info.todayClaimed) { console.log("[SevenDayGiftHost] 跳过弹窗:活动不可见或今天不可领取"); return false; } const key = this.getAutoOpenStorageKey(); const recordedDay = cc.fx.StorageMessage.getStorage(key); const serverDay = this.getServerDayKey(); const shouldOpen = recordedDay !== serverDay; console.log("[SevenDayGiftHost] 自动弹窗判断", JSON.stringify({ key, recordedDay, serverDay, shouldOpen })); return shouldOpen; } // 只记录已展示,不代表奖励已经领取。 private markAutoOpened() { cc.fx.StorageMessage.setStorage(this.getAutoOpenStorageKey(), this.getServerDayKey()); } // 按账号和活动隔离自动弹窗记录。 private getAutoOpenStorageKey(): string { return AUTO_OPEN_KEY + ":" + SevenDayGiftBootstrapApi.getUid() + ":" + this.info.activityId; } // 按北京时间划分服务端日期,避免设备时区影响。 private getServerDayKey(): string { const time = this.toTime(this.info.serverNow) || Date.now(); return Math.floor((time + 8 * 60 * 60 * 1000) / (24 * 60 * 60 * 1000)).toString(); } // 回到前台时刷新活动;正在展示时继续使用原快照。 private onGameShow() { // 弹窗正在使用本次查询结果,切前后台不重新查询或重复展示。 if (this.giftLoading || (this.giftNode && cc.isValid(this.giftNode) && this.giftNode.active)) return; if (!CC_PREVIEW) this.queryActivity(true); } // 兼容秒、毫秒时间戳和日期字符串。 private toTime(value: string | number): number { if (typeof value === "number") return value < 100000000000 ? value * 1000 : value; if (!value) return 0; const parsed = new Date(value).getTime(); return isNaN(parsed) ? 0 : parsed; } }