feat: 支持加载进度提示和 API 重试机制

- 添加 ProgressTips 节点到 PageLoading 展示加载状态消息
- 连接 statusLabel 到 ProgressTips 组件
- LevelDataManager 添加 API 请求重试机制(重试 2 次)
- 优化进度消息:正在请求服务端数据、正在加载游戏必备资源等
- 初始化失败时显示"网络异常,请重新打开游戏"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
richarjiang
2026-03-30 09:14:10 +08:00
parent 8794a3495c
commit e0d2ff5d57
8 changed files with 239 additions and 34 deletions

View File

@@ -23,22 +23,25 @@
}, },
{ {
"__id__": 10 "__id__": 10
},
{
"__id__": 24
} }
], ],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 24 "__id__": 30
}, },
{ {
"__id__": 26 "__id__": 32
}, },
{ {
"__id__": 28 "__id__": 34
} }
], ],
"_prefab": { "_prefab": {
"__id__": 30 "__id__": 36
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -530,6 +533,165 @@
"targetOverrides": null, "targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
}, },
{
"__type__": "cc.Node",
"_name": "ProgressTips",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 25
},
{
"__id__": 27
}
],
"_prefab": {
"__id__": 29
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -10.607,
"y": -622.285,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 24
},
"_enabled": true,
"__prefab": {
"__id__": 26
},
"_contentSize": {
"__type__": "cc.Size",
"width": 80,
"height": 50.4
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "6eZWHqpJtCTpZ/0qF30Pdo"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 24
},
"_enabled": true,
"__prefab": {
"__id__": 28
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_string": "进度",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 40,
"_fontSize": 40,
"_fontFamily": "Arial",
"_lineHeight": 40,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
"_underlineHeight": 2,
"_cacheMode": 0,
"_enableOutline": false,
"_outlineColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_outlineWidth": 2,
"_enableShadow": false,
"_shadowColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_shadowOffset": {
"__type__": "cc.Vec2",
"x": 2,
"y": 2
},
"_shadowBlur": 2,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "e1paQuq1lB8bP8W2opCL2A"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "f1spDHT01G1LKPBUMVSzhP",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{ {
"__type__": "cc.UITransform", "__type__": "cc.UITransform",
"_name": "", "_name": "",
@@ -540,7 +702,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 25 "__id__": 31
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
@@ -568,11 +730,14 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 27 "__id__": 33
}, },
"progressBar": { "progressBar": {
"__id__": 21 "__id__": 21
}, },
"statusLabel": {
"__id__": 27
},
"_id": "" "_id": ""
}, },
{ {
@@ -589,7 +754,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 29 "__id__": 35
}, },
"_alignFlags": 45, "_alignFlags": 45,
"_target": null, "_target": null,

View File

@@ -29,15 +29,20 @@ export class PageLoading extends Component {
} }
// 阶段1: 初始化 LevelDataManager (0-80%) // 阶段1: 初始化 LevelDataManager (0-80%)
await LevelDataManager.instance.initialize((progress, message) => { const success = await LevelDataManager.instance.initialize((progress, message) => {
this._updateProgress(progress); this._updateProgress(progress);
this._updateStatusLabel(message); this._updateStatusLabel(message);
}); });
if (!success) {
return;
}
// 阶段2: 预加载 PageHome (80-100%) // 阶段2: 预加载 PageHome (80-100%)
ViewManager.instance.preload('PageHome', ViewManager.instance.preload('PageHome',
(progress) => { (progress) => {
this._updateProgress(0.8 + progress * 0.2); this._updateProgress(0.8 + progress * 0.2);
this._updateStatusLabel('正在加载界面资源...');
}, },
() => { () => {
this._onPreloadComplete(); this._onPreloadComplete();

View File

@@ -478,10 +478,6 @@
"__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54", "__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54",
"__expectedType__": "cc.Prefab" "__expectedType__": "cc.Prefab"
}, },
"passModalPrefab": {
"__uuid__": "29ff0bfc-d5cf-4ad1-b8cb-61bdfd4850ef",
"__expectedType__": "cc.Prefab"
},
"_id": "c2b3nbzv9JuZmP2jxQyN72" "_id": "c2b3nbzv9JuZmP2jxQyN72"
}, },
{ {

View File

@@ -6264,7 +6264,7 @@
"__expectedType__": "cc.AudioClip" "__expectedType__": "cc.AudioClip"
}, },
"successAudio": { "successAudio": {
"__uuid__": "45d1f0b7-d9d7-41d8-84b2-7abad9304148", "__uuid__": "c635eec0-7a71-4bd3-be49-0d627c3adaec",
"__expectedType__": "cc.AudioClip" "__expectedType__": "cc.AudioClip"
}, },
"failAudio": { "failAudio": {

Binary file not shown.

View File

@@ -0,0 +1,14 @@
{
"ver": "1.0.0",
"importer": "audio-clip",
"imported": true,
"uuid": "c635eec0-7a71-4bd3-be49-0d627c3adaec",
"files": [
".json",
".mp3"
],
"subMetas": {},
"userData": {
"downloadMode": 0
}
}

View File

@@ -22,6 +22,9 @@ export class LevelDataManager {
/** 请求超时时间(毫秒) */ /** 请求超时时间(毫秒) */
private readonly REQUEST_TIMEOUT = 8000; private readonly REQUEST_TIMEOUT = 8000;
/** API 请求重试次数 */
private readonly API_RETRY_COUNT = 2;
/** API 返回的原始关卡数据 */ /** API 返回的原始关卡数据 */
private _apiData: ApiLevelData[] = []; private _apiData: ApiLevelData[] = [];
@@ -62,12 +65,12 @@ export class LevelDataManager {
try { try {
// 阶段1: 获取 API 数据 (0-30%) // 阶段1: 获取 API 数据 (0-30%)
onProgress?.(0, '正在获取关卡数据...'); onProgress?.(0, '正在请求服务端数据...');
const apiData = await this._fetchApiData(); const apiData = await this._fetchApiData(onProgress);
if (!apiData || apiData.length === 0) { if (!apiData || apiData.length === 0) {
console.warn('[LevelDataManager] API 返回空数据'); console.warn('[LevelDataManager] API 返回空数据');
onProgress?.(0.3, 'API 数据为空,使用本地配置'); onProgress?.(0.3, '网络异常,请重新打开游戏');
return false; return false;
} }
@@ -78,7 +81,7 @@ export class LevelDataManager {
// 阶段2: 只预加载第一关图片 (30-80%) // 阶段2: 只预加载第一关图片 (30-80%)
const firstLevel = apiData[0]; const firstLevel = apiData[0];
onProgress?.(0.3, '正在加载第一关资源...'); onProgress?.(0.3, '正在加载游戏必备资源...');
const spriteFrame = await this._loadImage(firstLevel.imageUrl); const spriteFrame = await this._loadImage(firstLevel.imageUrl);
if (spriteFrame) { if (spriteFrame) {
@@ -86,12 +89,12 @@ export class LevelDataManager {
} }
console.log('[LevelDataManager] 初始化完成,第一关资源已加载'); console.log('[LevelDataManager] 初始化完成,第一关资源已加载');
onProgress?.(0.8, '第一关资源加载完成'); onProgress?.(0.8, '游戏资源加载完成');
return true; return true;
} catch (error) { } catch (error) {
console.error('[LevelDataManager] 初始化失败:', error); console.error('[LevelDataManager] 初始化失败:', error);
onProgress?.(0.3, '获取数据失败,使用本地配置'); onProgress?.(0.3, '网络异常,请重新打开游戏');
return false; return false;
} }
} }
@@ -205,22 +208,44 @@ export class LevelDataManager {
} }
/** /**
* 从 API 获取关卡数据 * 从 API 获取关卡数据(带重试机制)
* @param onProgress 进度回调
*/ */
private async _fetchApiData(): Promise<ApiLevelData[] | null> { private async _fetchApiData(onProgress?: ProgressCallback): Promise<ApiLevelData[] | null> {
try { let lastError: Error | null = null;
const response = await HttpUtil.get<ApiResponse>(this.API_URL, this.REQUEST_TIMEOUT);
if (!response.success) { for (let attempt = 1; attempt <= this.API_RETRY_COUNT; attempt++) {
console.warn(`[LevelDataManager] API 返回失败, 消息: ${response.message}`); const progress = (attempt - 1) / this.API_RETRY_COUNT * 0.3;
return null;
try {
onProgress?.(progress, `正在请求服务端数据 (第${attempt}次)...`);
const response = await HttpUtil.get<ApiResponse>(this.API_URL, this.REQUEST_TIMEOUT);
if (!response.success) {
console.warn(`[LevelDataManager] API 返回失败, 消息: ${response.message}`);
lastError = new Error(response.message || 'API 返回失败');
} else {
return response.data.levels;
}
} catch (error) {
console.warn(`[LevelDataManager] 第${attempt}次请求失败:`, error);
lastError = error as Error;
} }
return response.data.levels; // 重试逻辑(无论是 response.success 为 false 还是抛出异常)
} catch (error) { if (attempt < this.API_RETRY_COUNT) {
console.error('[LevelDataManager] API 请求失败:', error); onProgress?.(progress + 0.05, `请求失败,正在重试...`);
return null; await this._delay(1000);
}
} }
console.error('[LevelDataManager] API 请求重试全部失败:', lastError);
return null;
}
private _delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
} }
/** /**

View File

@@ -4,19 +4,19 @@
"customSplash": { "customSplash": {
"id": "customSplash", "id": "customSplash",
"label": "customSplash", "label": "customSplash",
"enable": false, "enable": true,
"customSplash": { "customSplash": {
"complete": false, "complete": false,
"form": "https://creator-api.cocos.com/api/form/show?" "form": "https://creator-api.cocos.com/api/form/show?sid=51d0aa399094bb683b7737f0a16a459b"
} }
}, },
"removeSplash": { "removeSplash": {
"id": "removeSplash", "id": "removeSplash",
"label": "removeSplash", "label": "removeSplash",
"enable": false, "enable": true,
"removeSplash": { "removeSplash": {
"complete": false, "complete": false,
"form": "https://creator-api.cocos.com/api/form/show?" "form": "https://creator-api.cocos.com/api/form/show?sid=51d0aa399094bb683b7737f0a16a459b"
} }
} }
} }