Files
mp-xieyingeng/assets/scripts/utils/CompletedLevelsManager.ts
2026-04-30 16:35:08 +08:00

139 lines
4.2 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 { 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<string, SpriteFrame> = new Map();
/** 正在进行中的请求,用于去重并发调用 */
private _inflight: Promise<CompletedLevel[] | null> | 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<CompletedLevel[] | null> {
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<CompletedLevel[] | null> {
try {
const response = await HttpUtil.get<ApiEnvelope<CompletedLevel[]>>(
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<SpriteFrame | null> {
if (!url) return Promise.resolve(null);
const cached = this._imageCache.get(url);
if (cached) {
return Promise.resolve(cached);
}
return new Promise((resolve) => {
assetManager.loadRemote<ImageAsset>(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;
}
}