fix: 修复 Flutter Web 白屏问题

- main.dart: 添加全局错误处理和 FlutterError.onError
- index.html: 添加加载指示器,vconsole 仅在开发环境启用
- dio_client.dart: 添加详细错误日志,优化超时配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 18:54:01 +08:00
parent 9cd9e8d0bf
commit 6bf54eb849
6 changed files with 263 additions and 29 deletions

View File

@@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import '../constants/api_endpoints.dart';
import '../storage/local_storage.dart';
import 'api_exception.dart';
@@ -7,8 +8,9 @@ import 'api_response.dart';
/// 网络配置常量
class NetworkConfig {
static const String baseUrl = ApiEndpoints.baseUrl;
static const Duration connectTimeout = Duration(seconds: 30);
static const Duration receiveTimeout = Duration(seconds: 30);
static const Duration connectTimeout = Duration(seconds: 15);
static const Duration receiveTimeout = Duration(seconds: 15);
static const Duration sendTimeout = Duration(seconds: 15);
}
/// Dio 网络客户端
@@ -18,6 +20,7 @@ class DioClient {
DioClient() {
_dio = _createDio();
_setupInterceptors();
debugPrint('DioClient initialized with baseUrl: ${NetworkConfig.baseUrl}');
}
Dio _createDio() {
@@ -25,6 +28,7 @@ class DioClient {
baseUrl: NetworkConfig.baseUrl,
connectTimeout: NetworkConfig.connectTimeout,
receiveTimeout: NetworkConfig.receiveTimeout,
sendTimeout: NetworkConfig.sendTimeout,
headers: {'Content-Type': 'application/json'},
));
}
@@ -32,11 +36,7 @@ class DioClient {
void _setupInterceptors() {
_dio.interceptors.addAll([
_AuthInterceptor(),
LogInterceptor(
requestHeader: false,
responseHeader: false,
error: true,
),
_LoggingInterceptor(),
]);
}
@@ -80,6 +80,15 @@ class DioClient {
}
ApiResponse<T> _handleError<T>(DioException e) {
// 详细错误日志
debugPrint('=== Network Error ===');
debugPrint('Type: ${e.type}');
debugPrint('Message: ${e.message}');
debugPrint('URL: ${e.requestOptions.uri}');
debugPrint('StatusCode: ${e.response?.statusCode}');
debugPrint('ResponseData: ${e.response?.data}');
debugPrint('====================');
if (_isUnauthorized(e)) {
_clearUserData();
return ApiResponse.unauthorized('登录已过期,请重新登录');
@@ -106,15 +115,55 @@ class DioClient {
case DioExceptionType.receiveTimeout:
return '响应超时,请重试';
case DioExceptionType.connectionError:
return '网络连接失败';
return '网络连接失败,请检查网络设置';
case DioExceptionType.badResponse:
return '服务器错误 (${e.response?.statusCode})';
final statusCode = e.response?.statusCode;
if (statusCode == 500) {
return '服务器内部错误';
} else if (statusCode == 502 || statusCode == 503) {
return '服务暂时不可用';
}
return '服务器错误 ($statusCode)';
default:
return e.message ?? '网络请求失败';
}
}
}
/// 日志拦截器
class _LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
debugPrint('┌──────────────────────────────────────────────────────────');
debugPrint('│ REQUEST: ${options.method} ${options.uri}');
debugPrint('│ Headers: ${options.headers}');
if (options.data != null) {
debugPrint('│ Data: ${options.data}');
}
debugPrint('└──────────────────────────────────────────────────────────');
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
debugPrint('┌──────────────────────────────────────────────────────────');
debugPrint('│ RESPONSE: ${response.statusCode} ${response.requestOptions.uri}');
debugPrint('│ Data: ${response.data}');
debugPrint('└──────────────────────────────────────────────────────────');
super.onResponse(response, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
debugPrint('┌──────────────────────────────────────────────────────────');
debugPrint('│ ERROR: ${err.type} ${err.requestOptions.uri}');
debugPrint('│ Message: ${err.message}');
debugPrint('│ StatusCode: ${err.response?.statusCode}');
debugPrint('└──────────────────────────────────────────────────────────');
super.onError(err, handler);
}
}
/// 认证拦截器
class _AuthInterceptor extends Interceptor {
@override