feat: 完善关卡创作页面

This commit is contained in:
richarjiang
2026-04-30 16:35:08 +08:00
parent f8198e0463
commit 3d246de24c
18 changed files with 2135 additions and 841 deletions

View File

@@ -0,0 +1,138 @@
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;
}
}