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__": 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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
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;
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user