Files
mp-xieyingeng/assets/scripts/utils/ShareManager.ts
2026-05-14 16:55:26 +08:00

310 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc';
import { HttpUtil } from './HttpUtil';
import { WxSDK } from './WxSDK';
import { API_ENDPOINTS, getShareDetailUrl, getShareJoinUrl, getShareSubmitUrl, API_TIMEOUT } from '../config/ApiConfig';
import {
ApiEnvelope,
CreateShareData,
JoinShareData,
ShareLevelData,
CreatedShareItem,
CreatedShareListData,
ParticipatedShareItem,
ParticipatedShareListData,
ShareDetailData,
SubmitShareData,
SubmitShareLevel,
} 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[] = [];
private _participatedShares: ParticipatedShareItem[] = [];
/** 图片缓存URL -> SpriteFrame */
private _imageCache: Map<string, SpriteFrame> = 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];
}
get participatedShares(): ParticipatedShareItem[] {
return [...this._participatedShares];
}
get shareCode(): string | null {
return this._shareCode;
}
get shareTitle(): string {
return this._shareTitle;
}
async createShare(title: string, levelIds: string[]): Promise<string | null> {
try {
const response = await HttpUtil.post<ApiEnvelope<CreateShareData>>(
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<boolean> {
try {
const response = await HttpUtil.post<ApiEnvelope<JoinShareData>>(
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,
timeLimit: null,
}));
// 预加载首关图片(两张并行加载)
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<CreatedShareItem[] | null> {
try {
const response = await HttpUtil.get<ApiEnvelope<CreatedShareListData>>(
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 fetchParticipatedShares(): Promise<ParticipatedShareItem[] | null> {
try {
const response = await HttpUtil.get<ApiEnvelope<ParticipatedShareListData>>(
API_ENDPOINTS.SHARE_PARTICIPATED,
API_TIMEOUT.DEFAULT,
);
if (!response.success || !response.data) {
console.error('[ShareManager] 获取我参与的挑战列表失败:', response.message);
return null;
}
this._participatedShares = response.data.items ?? [];
console.log(`[ShareManager] 获取我参与的挑战列表成功: ${this._participatedShares.length}`);
return this.participatedShares;
} catch (err) {
console.error('[ShareManager] 获取我参与的挑战列表异常:', err);
return null;
}
}
async fetchShareDetail(code: string): Promise<ShareDetailData | null> {
try {
const response = await HttpUtil.get<ApiEnvelope<ShareDetailData>>(
getShareDetailUrl(code),
API_TIMEOUT.DEFAULT,
);
if (!response.success || !response.data) {
console.error('[ShareManager] 获取分享挑战详情失败:', response.message);
return null;
}
console.log(`[ShareManager] 获取分享挑战详情成功: ${response.data.title}, ${response.data.rankings?.length ?? 0} 条排行`);
return response.data;
} catch (err) {
console.error('[ShareManager] 获取分享挑战详情异常:', err);
return null;
}
}
async ensureShareLevelReady(index: number): Promise<RuntimeLevelConfig | null> {
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;
}
getShareLevelIds(): string[] {
return this._shareApiLevels.map(level => level.id);
}
async submitShareChallenge(levels: SubmitShareLevel[]): Promise<SubmitShareData | null> {
if (!this._shareCode) {
console.warn('[ShareManager] submitShareChallenge: 无分享码,跳过提交');
return null;
}
try {
const response = await HttpUtil.post<ApiEnvelope<SubmitShareData>>(
getShareSubmitUrl(this._shareCode),
{ levels },
API_TIMEOUT.DEFAULT,
);
if (!response.success || !response.data) {
console.error('[ShareManager] 提交挑战结果失败:', response.message);
return null;
}
console.log(
`[ShareManager] 提交挑战结果成功: rank=${response.data.rank}, correct=${response.data.correctCount}/${response.data.levelCount}`,
);
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<SpriteFrame | 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('[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);
});
});
}
}