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

@@ -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 {

View File

@@ -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;
}

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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "991baa0f-1a4b-4f71-bfff-3de5f7470c22",
"files": [],
"subMetas": {},
"userData": {}
}