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

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@@ -21,13 +22,37 @@ import 'ui/pages/auth/login_page.dart';
import 'ui/pages/main/main_page.dart';
void main() async {
// 确保 Flutter 绑定初始化
WidgetsFlutterBinding.ensureInitialized();
Provider.debugCheckInvalidValueType = null;
await SharedPreferences.getInstance();
await LocalStorage.init();
// 全局错误处理
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
debugPrint('Flutter Error: ${details.exception}');
debugPrint('Stack trace: ${details.stack}');
};
runApp(const MyApp());
// 捕获异步错误
runZonedGuarded<Future<void>>(
() async {
Provider.debugCheckInvalidValueType = null;
try {
await SharedPreferences.getInstance();
await LocalStorage.init();
debugPrint('App initialized successfully');
} catch (e, stack) {
debugPrint('Initialization error: $e');
debugPrint('Stack: $stack');
}
runApp(const MyApp());
},
(error, stack) {
debugPrint('Uncaught error: $error');
debugPrint('Stack: $stack');
},
);
}
class MyApp extends StatelessWidget {