feat: 完善关卡创作页面
This commit is contained in:
@@ -22,6 +22,8 @@ export const API_ENDPOINTS = {
|
||||
SHARE_PROGRESS: `${API_BASE}/share/progress`,
|
||||
/** 用户信息 */
|
||||
USER_INFO: `${API_BASE}/user/info`,
|
||||
/** 用户所有已通关的关卡(成就墙 / 关卡回看) */
|
||||
COMPLETED_LEVELS: `${API_BASE}/levels/completed`,
|
||||
} as const;
|
||||
|
||||
export function getLevelEnterUrl(levelId: string): string {
|
||||
|
||||
@@ -156,3 +156,35 @@ export interface CreatedShareItem {
|
||||
export interface CreatedShareListData {
|
||||
items: CreatedShareItem[];
|
||||
}
|
||||
|
||||
/** 已通关关卡数据(成就墙 / 关卡回看场景) */
|
||||
export interface CompletedLevel {
|
||||
/** 关卡 ID */
|
||||
id: string;
|
||||
/** 关卡编号(sortOrder) */
|
||||
level: number;
|
||||
/** 图片1 URL */
|
||||
image1Url: string;
|
||||
/** 图片1 文本说明 */
|
||||
image1Description: string | null;
|
||||
/** 图片2 URL */
|
||||
image2Url: string;
|
||||
/** 图片2 文本说明 */
|
||||
image2Description: string | null;
|
||||
/** 答案 */
|
||||
answer: string;
|
||||
/** 谐音梗说明 */
|
||||
punchline: string | null;
|
||||
/** 线索1 */
|
||||
hint1: string | null;
|
||||
/** 线索2 */
|
||||
hint2: string | null;
|
||||
/** 线索3 */
|
||||
hint3: string | null;
|
||||
/** 限时(秒),null 表示不限时 */
|
||||
timeLimit: number | null;
|
||||
/** 首次通关时长(秒) */
|
||||
timeSpent: number;
|
||||
/** 通关时间(ISO 8601) */
|
||||
completedAt: string;
|
||||
}
|
||||
|
||||
138
assets/scripts/utils/CompletedLevelsManager.ts
Normal file
138
assets/scripts/utils/CompletedLevelsManager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
9
assets/scripts/utils/CompletedLevelsManager.ts.meta
Normal file
9
assets/scripts/utils/CompletedLevelsManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "991baa0f-1a4b-4f71-bfff-3de5f7470c22",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user