import { _decorator, Button, Color, EditBox, instantiate, Label, Node, NodePool, Prefab, ProgressBar, RichText, Sprite, SpriteFrame, UITransform } from 'cc'; import { UIView } from '@scripts/framework/ui/UIView'; import { AppKernel } from '@scripts/app/AppKernel'; import { UIViewID } from '@scripts/define/UIDefine'; import { UICloseReason } from '@scripts/framework/ui/UILayer'; import { formatCurrency } from '@scripts/utils/Helper'; import { addBtnClick } from '@scripts/utils/UIHelper'; import { RewardItem, RewardType } from './Tasks/RewardItem'; import { NodeItem } from './Prize/NodeItem'; const { ccclass, property } = _decorator; interface CDTask { targetTs: number; label: Label; format?: (timeStr: string) => string; // 改成接收已经格式化好的时间字符串,可选 timerFunc: () => void; } @ccclass('SeasonPassView') export class SeasonPassView extends UIView { readonly viewId = UIViewID.SeasonPassView; @property(Node) btn_close: Node = null; @property(Node) btn_tasks: Node = null; @property(Node) btn_prize: Node = null; @property(Node) btn_buyPass: Node = null; @property(Node) tasks: Node = null; @property(Node) prize: Node = null; @property(Label) season_end: Label = null; @property(Label) tasks_daily_done: Label = null; @property(Label) tasks_daily_reset: Label = null; @property(Node) tasks_daily_content: Node = null; @property(Label) tasks_seasonal_done: Label = null; @property(Label) tasks_seasonal_reset: Label = null; @property(Node) tasks_seasonal_content: Node = null; @property(Prefab) tasks_reward_item: Prefab = null; @property(Node) prize_content: Node = null; @property(Prefab) prize_node_item: Prefab = null; @property(Node) prize_fixed_dlx: Node = null; @property(Node) redPoint_tasks: Node = null; @property(Node) redPoint_prize: Node = null; @property(ProgressBar) progressBar: ProgressBar = null; @property(Label) progress_label: Label = null; @property(Label) next_need_exp: Label = null; @property(Node) scrollView_content: Node = null; private _seasonPassData: any = null; private _dailyItemPool: NodePool = new NodePool(); private _dailyUsedItems: Node[] = []; private _seasonalItemPool: NodePool = new NodePool(); private _seasonalUsedItems: Node[] = []; private _prizeItemPool: NodePool = new NodePool(); private _prizeUsedItems: Node[] = []; protected async onBeforeOpen(_params: unknown): Promise { const seasonPassStore = AppKernel.stores.seasonPassStore; const res = await seasonPassStore.reqSeasonPassInfo(); return res; } protected onCreate(): void | Promise { AppKernel.events.on('seasonPass:update', this.updateSeasonPassView, this); AppKernel.events.on('checkIn:update', this.handleCheckInUpdate, this); addBtnClick(this.btn_close, this.onBtnCloseClick, this); addBtnClick(this.btn_tasks, this.onBtnTasksClick, this); addBtnClick(this.btn_prize, this.onBtnPrizeClick, this); addBtnClick(this.btn_buyPass, this.onBtnBuyPassClick, this); this.initView(); } // protected onBeforeClose(_reason: UICloseReason): boolean | Promise { // return true; // } private handleCheckInUpdate() { AppKernel.stores.seasonPassStore.reqSeasonPassInfo(); } private updateSeasonPassView() { this._seasonPassData = AppKernel.stores.seasonPassStore.seasonPassData; this.updateTasks(); this.updatePrize(); this.updateRedPoint(); this.updateProgress(); } private updateTasks() { const { season_tasks } = this._seasonPassData ?? {}; this.updateDailyTasks(); this.updateSeasonalTasks(); const seasonal_targetTs = season_tasks?.end_at * 1000; this.startTimer(seasonal_targetTs, this.season_end); } private updateDailyTasks() { const { tasks, task_end_timestamp } = this._seasonPassData ?? {}; // 将当前正在使用的item全部回收进对象池 for (const node of this._dailyUsedItems) { this._dailyItemPool.put(node); } this._dailyUsedItems.length = 0; for (const task of tasks ?? []) { let itemNode: Node; if (this._dailyItemPool.size() > 0) { itemNode = this._dailyItemPool.get(); } else { itemNode = instantiate(this.tasks_reward_item); } itemNode.active = true; itemNode.parent = this.tasks_daily_content; const itemCmp = itemNode.getComponent(RewardItem); if (itemCmp) itemCmp.initItem(task, RewardType.Daily); this._dailyUsedItems.push(itemNode); } const daily_done = tasks?.filter(task => task.status > 0)?.length ?? 0; this.tasks_daily_done.string = `${daily_done}/${tasks?.length ?? 0} DONE`; const daily_targetTs = task_end_timestamp * 1000; this.startTimer(daily_targetTs, this.tasks_daily_reset, (cd) => "RESET IN " + cd); } private updateSeasonalTasks() { const { season_tasks } = this._seasonPassData ?? {}; for (const node of this._seasonalUsedItems) { this._seasonalItemPool.put(node); } this._seasonalUsedItems.length = 0; for (const task of season_tasks?.tasks ?? []) { let itemNode: Node; if (this._seasonalItemPool.size() > 0) { itemNode = this._seasonalItemPool.get(); } else { itemNode = instantiate(this.tasks_reward_item); } itemNode.active = true; itemNode.parent = this.tasks_seasonal_content; const itemCmp = itemNode.getComponent(RewardItem); if (itemCmp) itemCmp.initItem(task, RewardType.Seasonal); this._seasonalUsedItems.push(itemNode); } const seasonal_done = season_tasks?.tasks.filter(task => task.status > 0)?.length ?? 0; this.tasks_seasonal_done.string = `${seasonal_done}/${season_tasks?.tasks.length ?? 0} DONE`; const seasonal_targetTs = season_tasks?.end_at * 1000; this.startTimer(seasonal_targetTs, this.tasks_seasonal_reset, (cd) => "RESET IN " + cd); } private updatePrize() { const { pass } = this._seasonPassData ?? {}; const { current_xp, is_deluxe } = pass ?? {}; // 将当前正在使用的item全部回收进对象池 for (const node of this._prizeUsedItems) { this._prizeItemPool.put(node); } this._prizeUsedItems.length = 0; for (const node of pass?.nodes ?? []) { let itemNode: Node; if (this._prizeItemPool.size() > 0) { itemNode = this._prizeItemPool.get(); } else { itemNode = instantiate(this.prize_node_item); } itemNode.active = true; itemNode.parent = this.prize_content; const itemCmp = itemNode.getComponent(NodeItem); if (itemCmp) itemCmp.initItem({ node, current_xp, is_deluxe }); this._prizeUsedItems.push(itemNode); } const activeLabel = this.prize_fixed_dlx.getChildByName('label'); const btn_unlock = this.prize_fixed_dlx.getChildByName('btn_unlock'); const lock = this.prize_fixed_dlx.getChildByName('lock'); activeLabel.active = is_deluxe; btn_unlock.active = !is_deluxe; lock.active = !is_deluxe; } private updateRedPoint() { const { tasks, season_tasks, pass } = this._seasonPassData ?? {}; const hasTaskReward = tasks?.some(task => task.status === 1) ?? false; const hasSeasonalReward = season_tasks?.tasks.some(task => task.status === 1) ?? false; const { is_deluxe, nodes } = pass ?? {}; const hasFreePrizeReward = nodes?.some(node => { const unlocked = node.unlocked === 1; const canClaim = node.free.claimed === 0; const hasReward = Object.values(node.free.reward)?.some(reward => reward != 0); return unlocked && canClaim && hasReward; }); const hasDeluxePrizeReward = nodes?.some(node => { const unlocked = node.unlocked === 1; const canClaim = node.deluxe.claimed === 0; const hasReward = Object.values(node.deluxe.reward)?.some(reward => reward != 0); return unlocked && canClaim && hasReward && is_deluxe; }) console.log("hasTaskReward", hasTaskReward, "hasSeasonalReward", hasSeasonalReward, "hasFreePrizeReward", hasFreePrizeReward, "hasDeluxePrizeReward", hasDeluxePrizeReward); this.redPoint_tasks.active = hasTaskReward || hasSeasonalReward; this.redPoint_prize.active = hasFreePrizeReward || hasDeluxePrizeReward; } private updateProgress() { const { pass } = this._seasonPassData ?? {}; const { current_xp, nodes } = pass ?? {}; const next_need_exp = nodes?.find(node => node.required_xp > current_xp)?.required_xp; if (!next_need_exp) { this.progress_label.string = 'MAX'; this.progressBar.progress = 1; this.next_need_exp.string = 'MAX LEVEL'; } else { this.progress_label.string = `${current_xp}/${next_need_exp ?? 0} XP`; this.progressBar.progress = current_xp / next_need_exp; this.next_need_exp.string = `${next_need_exp - current_xp} XP TO\nNEXT PRIZE`; } } // 弹窗关闭,回收节点 protected async onAfterClose() { for (const node of this._dailyUsedItems) { this._dailyItemPool.put(node); } this._dailyUsedItems.length = 0; for (const node of this._seasonalUsedItems) { this._seasonalItemPool.put(node); } this._seasonalUsedItems.length = 0; for (const node of this._prizeUsedItems) { this._prizeItemPool.put(node); } this._prizeUsedItems.length = 0; } // 弹窗销毁,清空池子释放资源 protected onDestroy(): void { this._dailyItemPool.clear(); this._seasonalItemPool.clear(); this._prizeItemPool.clear(); this.stopAllTimer(); AppKernel.events.off('seasonPass:update', this.updateSeasonPassView, this); AppKernel.events.off('checkIn:update', this.handleCheckInUpdate, this); super.onDestroy(); } private initView() { this.onToggleSidebar("tasks"); // const items_data = new Array(10).fill(0); // items_data.forEach((item, index) => { // const item_DLX = instantiate(this.item_DLX); // item_DLX.parent = this.item_DLX.parent; // item_DLX.active = true; // const item_free = instantiate(this.item_free); // item_free.parent = this.item_free.parent; // item_free.active = true; // const item_progress = instantiate(this.item_progress); // item_progress.parent = this.item_progress.parent; // item_progress.active = true; // }) // this.scheduleOnce(() => { // console.log("item_DLX.parent.width", this.item_DLX.parent.getComponent(UITransform).width) // console.log("item_free.parent.width", this.item_free.parent.getComponent(UITransform).width) // console.log("item_progress.parent.width", this.item_progress.parent.getComponent(UITransform).width) // this.scrollView_content.getComponent(UITransform).width = this.item_free.parent.getComponent(UITransform).width; // }) } private onBtnCloseClick() { AppKernel.ui.close(this.viewId); } private onToggleSidebar(type: string) { const selected_tasks = this.btn_tasks.getChildByName('selected'); const unselected_tasks = this.btn_tasks.getChildByName('unselected'); const selected_prize = this.btn_prize.getChildByName('selected'); const unselected_prize = this.btn_prize.getChildByName('unselected'); selected_tasks.active = type === "tasks"; unselected_tasks.active = type !== "tasks"; selected_prize.active = type === "prize"; unselected_prize.active = type !== "prize"; this.tasks.active = type === "tasks"; this.prize.active = type === "prize"; } private onBtnTasksClick() { this.onToggleSidebar("tasks"); } private onBtnPrizeClick() { this.onToggleSidebar("prize"); } private onBtnBuyPassClick() { const { pass } = this._seasonPassData ?? {}; const gear = pass?.purchase_gear; if (!gear) return; AppKernel.ui.open(UIViewID.SeasonPassPurchase, gear); } private _cdTaskList: CDTask[] = []; /** * 开启倒计时 * @param targetTs 结束时间戳(ms) * @param label 显示文字的Label * @param format 格式化回调,入参剩余秒数,返回要展示的字符串 */ private startTimer(targetTs: number, label: Label, format?: (timeStr: string) => string) { if (targetTs == null || isNaN(targetTs)) return; // 先查找这个label有没有正在跑的倒计时,存在就先停止旧的 const existIdx = this._cdTaskList.findIndex(item => item.label === label); if (existIdx >= 0) { const oldTask = this._cdTaskList[existIdx]; this.unschedule(oldTask.timerFunc); this._cdTaskList.splice(existIdx, 1); } // 计算当前剩余秒数 const diff = targetTs - Date.now(); const remainSec = Math.max(0, Math.ceil(diff / 1000)); const timeStr = this.formatHMS(remainSec); // 有format就调用,没有直接用timeStr label.string = format ? format(timeStr) : timeStr; if (remainSec <= 0) { return; } // 新建任务 const task: CDTask = { targetTs, label, format, timerFunc: () => this.onUpdateCDTask(task) }; this._cdTaskList.push(task); this.onUpdateCDTask(task); this.schedule(task.timerFunc, 1); } private stopTimerByLabel(label: Label) { const idx = this._cdTaskList.findIndex(item => item.label === label); if (idx >= 0) { const task = this._cdTaskList[idx]; this.unschedule(task.timerFunc); this._cdTaskList.splice(idx, 1); } } private stopAllTimer() { for (const task of this._cdTaskList) { this.unschedule(task.timerFunc); } this._cdTaskList.length = 0; } private onUpdateCDTask(task: CDTask) { const now = Date.now(); const diff = task.targetTs - now; const remainSec = Math.max(0, Math.ceil(diff / 1000)); const timeStr = this.formatHMS(remainSec); // 存在format回调就调用,没有直接使用时间字符串 task.label.string = task.format ? task.format(timeStr) : timeStr; if (remainSec <= 0) { this.unschedule(task.timerFunc); const idx = this._cdTaskList.indexOf(task); if (idx > -1) { this._cdTaskList.splice(idx, 1); } } } private formatHMS(totalSec: number): string { const sec = Math.max(0, Math.floor(totalSec)); const d = Math.floor(sec / (3600 * 24)); const h = Math.floor((sec % (3600 * 24)) / 3600); const m = Math.floor((sec % 3600) / 60); const s = sec % 60; const pad = (n: number) => n.toString().padStart(2, '0'); const hhmmss = `${pad(h)}:${pad(m)}:${pad(s)}`; if (d > 0) { return `${d}:${hhmmss}`; } return hhmmss; } }