139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
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;
|
||
}
|
||
}
|