import { SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc'; import { HttpUtil } from './HttpUtil'; import { WxSDK } from './WxSDK'; import { API_ENDPOINTS, getShareJoinUrl, API_TIMEOUT } from '../config/ApiConfig'; import { ApiEnvelope, CreateShareData, JoinShareData, ReportProgressData, ShareLevelData, CreatedShareItem, CreatedShareListData, } from '../types/ApiTypes'; import { RuntimeLevelConfig } from '../types/LevelTypes'; /** * 分享管理器 * 负责创建分享、加入分享、缓存分享关卡数据 */ export class ShareManager { private static _instance: ShareManager | null = null; /** 分享模式的关卡数据(null 表示正常模式) */ private _shareLevels: RuntimeLevelConfig[] | null = null; /** API 返回的原始关卡数据(保留 image1Url/image2Url 用于懒加载) */ private _shareApiLevels: ShareLevelData[] = []; private _shareTitle: string = ''; private _shareCode: string | null = null; private _createdShares: CreatedShareItem[] = []; /** 图片缓存:URL -> SpriteFrame */ private _imageCache: Map = new Map(); static get instance(): ShareManager { if (!this._instance) { this._instance = new ShareManager(); } return this._instance; } private constructor() {} get isShareMode(): boolean { return this._shareLevels !== null && this._shareLevels.length > 0; } get createdShares(): CreatedShareItem[] { return [...this._createdShares]; } async createShare(title: string, levelIds: string[]): Promise { try { const response = await HttpUtil.post>( API_ENDPOINTS.SHARE_CREATE, { title, levelIds }, API_TIMEOUT.DEFAULT, ); if (!response.success || !response.data) { console.error('[ShareManager] 创建分享失败:', response.message); return null; } return response.data.shareCode; } catch (err) { console.error('[ShareManager] 创建分享异常:', err); return null; } } async joinShare(code: string): Promise { try { const response = await HttpUtil.post>( getShareJoinUrl(code), {}, API_TIMEOUT.DEFAULT, ); if (!response.success || !response.data) { console.error('[ShareManager] 加入分享失败:', response.message); return false; } const { shareCode, title, levels } = response.data; this._shareCode = shareCode; this._shareTitle = title; this._shareApiLevels = levels; const runtimeLevels: RuntimeLevelConfig[] = levels.map((level) => ({ id: level.id, name: `第${level.level}关`, spriteFrame1: null, spriteFrame2: null, image1Description: level.image1Description, image2Description: level.image2Description, punchline: level.punchline, clue1: level.hint1, clue2: level.hint2, clue3: level.hint3, answer: level.answer, completed: false, })); // 预加载首关图片(两张并行加载) if (levels.length > 0) { const [sf1, sf2] = await Promise.all([ this._loadImage(levels[0].image1Url), this._loadImage(levels[0].image2Url), ]); if (sf1) { runtimeLevels[0].spriteFrame1 = sf1; } if (sf2) { runtimeLevels[0].spriteFrame2 = sf2; } } this._shareLevels = runtimeLevels; console.log(`[ShareManager] 加入分享成功: ${title}, ${levels.length} 关`); return true; } catch (err) { console.error('[ShareManager] 加入分享异常:', err); return false; } } async fetchCreatedShares(): Promise { try { const response = await HttpUtil.get>( API_ENDPOINTS.SHARE_CREATED, API_TIMEOUT.DEFAULT, ); if (!response.success || !response.data) { console.error('[ShareManager] 获取我创建的挑战列表失败:', response.message); return null; } this._createdShares = response.data.items ?? []; console.log(`[ShareManager] 获取我创建的挑战列表成功: ${this._createdShares.length} 条`); return this.createdShares; } catch (err) { console.error('[ShareManager] 获取我创建的挑战列表异常:', err); return null; } } async ensureShareLevelReady(index: number): Promise { if (!this._shareLevels || index < 0 || index >= this._shareLevels.length) { return null; } const config = this._shareLevels[index]; if (config.spriteFrame1) { return config; } const apiLevel = this._shareApiLevels[index]; if (apiLevel?.image1Url) { const [sf1, sf2] = await Promise.all([ this._loadImage(apiLevel.image1Url), this._loadImage(apiLevel.image2Url), ]); if (sf1) { config.spriteFrame1 = sf1; } if (sf2) { config.spriteFrame2 = sf2; } } return config; } getShareLevelCount(): number { return this._shareLevels?.length ?? 0; } /** * 上报单关通关进度 * @param levelId 关卡 ID * @param passed 是否通过 * @param timeSpent 用时(秒) */ async reportLevelProgress( levelId: string, passed: boolean, timeSpent: number, ): Promise { if (!this._shareCode) { console.warn('[ShareManager] reportLevelProgress: 无分享码,跳过上报'); return null; } try { const response = await HttpUtil.post>( API_ENDPOINTS.SHARE_PROGRESS, { shareCode: this._shareCode, levelId, passed, timeSpent }, API_TIMEOUT.DEFAULT, ); if (!response.success || !response.data) { console.error('[ShareManager] 上报进度失败:', response.message); return null; } console.log( `[ShareManager] 上报成功: passed=${response.data.passed}, withinTimeLimit=${response.data.withinTimeLimit}`, ); return response.data; } catch (err) { console.error('[ShareManager] 上报进度异常:', err); return null; } } triggerWxShare(title: string, shareCode: string): void { WxSDK.shareAppMessage({ title: title || '来挑战我出的谐音梗吧!', query: `shareCode=${shareCode}`, }); } clearShareMode(): void { this._shareLevels = null; this._shareApiLevels = []; this._shareTitle = ''; this._shareCode = null; this._imageCache.clear(); } private _loadImage(url: string): Promise { const cached = this._imageCache.get(url); if (cached) { return Promise.resolve(cached); } return new Promise((resolve) => { assetManager.loadRemote(url, (err, imageAsset) => { if (err) { console.error('[ShareManager] 加载图片失败:', 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); }); }); } }