import { SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc'; import { HttpUtil } from './HttpUtil'; import { API_ENDPOINTS, API_TIMEOUT } from '../config/ApiConfig'; import { ApiEnvelope, CompletedLevel } from '../types/ApiTypes'; /** * 已通关关卡管理器 * 单例模式,负责拉取当前用户所有已通关关卡 + 封面图缓存 * 适用于「成就墙」「关卡回看」「出题 / 预览」等场景 */ export class CompletedLevelsManager { private static _instance: CompletedLevelsManager | null = null; /** 关卡数据按服务端返回顺序缓存 */ private _levels: CompletedLevel[] = []; /** 是否已经成功拉取过一次 */ private _loaded: boolean = false; /** 图片缓存:URL -> SpriteFrame */ private _imageCache: Map = new Map(); /** 正在进行中的请求,用于去重并发调用 */ private _inflight: Promise | null = null; static get instance(): CompletedLevelsManager { if (!this._instance) { this._instance = new CompletedLevelsManager(); } return this._instance; } private constructor() {} /** * 获取已缓存的关卡列表(需先 fetch 或 ensureLoaded) */ get levels(): CompletedLevel[] { return this._levels; } get count(): number { return this._levels.length; } isLoaded(): boolean { return this._loaded; } /** * 按索引(0-based)获取关卡,越界返回 null */ getByIndex(index: number): CompletedLevel | null { if (index < 0 || index >= this._levels.length) return null; return this._levels[index]; } /** * 拉取并缓存已通关关卡列表 * - forceRefresh=true 强制重新请求 * - 并发调用会共享同一次请求 */ async fetch(forceRefresh: boolean = false): Promise { if (!forceRefresh && this._loaded) { return this._levels; } if (this._inflight) { return this._inflight; } this._inflight = this._doFetch(); try { return await this._inflight; } finally { this._inflight = null; } } private async _doFetch(): Promise { try { const response = await HttpUtil.get>( API_ENDPOINTS.COMPLETED_LEVELS, API_TIMEOUT.DEFAULT, ); if (!response.success || !response.data) { console.error('[CompletedLevelsManager] 拉取失败:', response.message); return null; } this._levels = response.data; this._loaded = true; console.log(`[CompletedLevelsManager] 拉取成功,共 ${this._levels.length} 关`); return this._levels; } catch (err) { console.error('[CompletedLevelsManager] 拉取异常:', err); return null; } } /** * 按图片 URL 加载并缓存 SpriteFrame * 已缓存直接返回 */ loadImage(url: string): Promise { if (!url) return Promise.resolve(null); const cached = this._imageCache.get(url); if (cached) { return Promise.resolve(cached); } return new Promise((resolve) => { assetManager.loadRemote(url, (err, imageAsset) => { if (err) { console.error('[CompletedLevelsManager] 加载图片失败:', url, err); resolve(null); return; } const texture = new Texture2D(); texture.image = imageAsset; const spriteFrame = new SpriteFrame(); spriteFrame.texture = texture; this._imageCache.set(url, spriteFrame); resolve(spriteFrame); }); }); } /** 清除缓存(登出等场景) */ clear(): void { this._levels = []; this._loaded = false; this._imageCache.clear(); this._inflight = null; } }