feat: 支持加载进度提示和 API 重试机制
- 添加 ProgressTips 节点到 PageLoading 展示加载状态消息 - 连接 statusLabel 到 ProgressTips 组件 - LevelDataManager 添加 API 请求重试机制(重试 2 次) - 优化进度消息:正在请求服务端数据、正在加载游戏必备资源等 - 初始化失败时显示"网络异常,请重新打开游戏" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,22 +23,25 @@
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
},
|
||||
{
|
||||
"__id__": 24
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 24
|
||||
"__id__": 30
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 32
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 34
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 30
|
||||
"__id__": 36
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -530,6 +533,165 @@
|
||||
"targetOverrides": 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",
|
||||
"_name": "",
|
||||
@@ -540,7 +702,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 25
|
||||
"__id__": 31
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -568,11 +730,14 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 27
|
||||
"__id__": 33
|
||||
},
|
||||
"progressBar": {
|
||||
"__id__": 21
|
||||
},
|
||||
"statusLabel": {
|
||||
"__id__": 27
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
@@ -589,7 +754,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 29
|
||||
"__id__": 35
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
|
||||
@@ -29,15 +29,20 @@ export class PageLoading extends Component {
|
||||
}
|
||||
|
||||
// 阶段1: 初始化 LevelDataManager (0-80%)
|
||||
await LevelDataManager.instance.initialize((progress, message) => {
|
||||
const success = await LevelDataManager.instance.initialize((progress, message) => {
|
||||
this._updateProgress(progress);
|
||||
this._updateStatusLabel(message);
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 阶段2: 预加载 PageHome (80-100%)
|
||||
ViewManager.instance.preload('PageHome',
|
||||
(progress) => {
|
||||
this._updateProgress(0.8 + progress * 0.2);
|
||||
this._updateStatusLabel('正在加载界面资源...');
|
||||
},
|
||||
() => {
|
||||
this._onPreloadComplete();
|
||||
|
||||
@@ -478,10 +478,6 @@
|
||||
"__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"passModalPrefab": {
|
||||
"__uuid__": "29ff0bfc-d5cf-4ad1-b8cb-61bdfd4850ef",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"_id": "c2b3nbzv9JuZmP2jxQyN72"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6264,7 +6264,7 @@
|
||||
"__expectedType__": "cc.AudioClip"
|
||||
},
|
||||
"successAudio": {
|
||||
"__uuid__": "45d1f0b7-d9d7-41d8-84b2-7abad9304148",
|
||||
"__uuid__": "c635eec0-7a71-4bd3-be49-0d627c3adaec",
|
||||
"__expectedType__": "cc.AudioClip"
|
||||
},
|
||||
"failAudio": {
|
||||
|
||||
BIN
assets/resources/audios/SuccessV2.mp3
Normal file
BIN
assets/resources/audios/SuccessV2.mp3
Normal file
Binary file not shown.
14
assets/resources/audios/SuccessV2.mp3.meta
Normal file
14
assets/resources/audios/SuccessV2.mp3.meta
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ export class LevelDataManager {
|
||||
/** 请求超时时间(毫秒) */
|
||||
private readonly REQUEST_TIMEOUT = 8000;
|
||||
|
||||
/** API 请求重试次数 */
|
||||
private readonly API_RETRY_COUNT = 2;
|
||||
|
||||
/** API 返回的原始关卡数据 */
|
||||
private _apiData: ApiLevelData[] = [];
|
||||
|
||||
@@ -62,12 +65,12 @@ export class LevelDataManager {
|
||||
|
||||
try {
|
||||
// 阶段1: 获取 API 数据 (0-30%)
|
||||
onProgress?.(0, '正在获取关卡数据...');
|
||||
const apiData = await this._fetchApiData();
|
||||
onProgress?.(0, '正在请求服务端数据...');
|
||||
const apiData = await this._fetchApiData(onProgress);
|
||||
|
||||
if (!apiData || apiData.length === 0) {
|
||||
console.warn('[LevelDataManager] API 返回空数据');
|
||||
onProgress?.(0.3, 'API 数据为空,使用本地配置');
|
||||
onProgress?.(0.3, '网络异常,请重新打开游戏');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -78,7 +81,7 @@ export class LevelDataManager {
|
||||
|
||||
// 阶段2: 只预加载第一关图片 (30-80%)
|
||||
const firstLevel = apiData[0];
|
||||
onProgress?.(0.3, '正在加载第一关资源...');
|
||||
onProgress?.(0.3, '正在加载游戏必备资源...');
|
||||
|
||||
const spriteFrame = await this._loadImage(firstLevel.imageUrl);
|
||||
if (spriteFrame) {
|
||||
@@ -86,12 +89,12 @@ export class LevelDataManager {
|
||||
}
|
||||
|
||||
console.log('[LevelDataManager] 初始化完成,第一关资源已加载');
|
||||
onProgress?.(0.8, '第一关资源加载完成');
|
||||
onProgress?.(0.8, '游戏资源加载完成');
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[LevelDataManager] 初始化失败:', error);
|
||||
onProgress?.(0.3, '获取数据失败,使用本地配置');
|
||||
onProgress?.(0.3, '网络异常,请重新打开游戏');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -205,22 +208,44 @@ export class LevelDataManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 API 获取关卡数据
|
||||
* 从 API 获取关卡数据(带重试机制)
|
||||
* @param onProgress 进度回调
|
||||
*/
|
||||
private async _fetchApiData(): Promise<ApiLevelData[] | null> {
|
||||
try {
|
||||
const response = await HttpUtil.get<ApiResponse>(this.API_URL, this.REQUEST_TIMEOUT);
|
||||
private async _fetchApiData(onProgress?: ProgressCallback): Promise<ApiLevelData[] | null> {
|
||||
let lastError: Error | null = null;
|
||||
|
||||
if (!response.success) {
|
||||
console.warn(`[LevelDataManager] API 返回失败, 消息: ${response.message}`);
|
||||
return null;
|
||||
for (let attempt = 1; attempt <= this.API_RETRY_COUNT; attempt++) {
|
||||
const progress = (attempt - 1) / this.API_RETRY_COUNT * 0.3;
|
||||
|
||||
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;
|
||||
} catch (error) {
|
||||
console.error('[LevelDataManager] API 请求失败:', error);
|
||||
return null;
|
||||
// 重试逻辑(无论是 response.success 为 false 还是抛出异常)
|
||||
if (attempt < this.API_RETRY_COUNT) {
|
||||
onProgress?.(progress + 0.05, `请求失败,正在重试...`);
|
||||
await this._delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
console.error('[LevelDataManager] API 请求重试全部失败:', lastError);
|
||||
return null;
|
||||
}
|
||||
|
||||
private _delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
"customSplash": {
|
||||
"id": "customSplash",
|
||||
"label": "customSplash",
|
||||
"enable": false,
|
||||
"enable": true,
|
||||
"customSplash": {
|
||||
"complete": false,
|
||||
"form": "https://creator-api.cocos.com/api/form/show?"
|
||||
"form": "https://creator-api.cocos.com/api/form/show?sid=51d0aa399094bb683b7737f0a16a459b"
|
||||
}
|
||||
},
|
||||
"removeSplash": {
|
||||
"id": "removeSplash",
|
||||
"label": "removeSplash",
|
||||
"enable": false,
|
||||
"enable": true,
|
||||
"removeSplash": {
|
||||
"complete": false,
|
||||
"form": "https://creator-api.cocos.com/api/form/show?"
|
||||
"form": "https://creator-api.cocos.com/api/form/show?sid=51d0aa399094bb683b7737f0a16a459b"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user