diff --git a/lib/core/network/dio_client.dart b/lib/core/network/dio_client.dart index 22c2ae85..898cb898 100644 --- a/lib/core/network/dio_client.dart +++ b/lib/core/network/dio_client.dart @@ -9,8 +9,8 @@ class DioClient { final dio = Dio( BaseOptions( baseUrl: HttpApiConsts.baseUrl, - connectTimeout: const Duration(seconds: 5), - receiveTimeout: const Duration(seconds: 5), + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), headers: {'Content-Type': 'application/json'}, ), ); diff --git a/lib/core/network/tcp/tcp_client.dart b/lib/core/network/tcp/tcp_client.dart index 26ee94e7..2b6b5f1a 100644 --- a/lib/core/network/tcp/tcp_client.dart +++ b/lib/core/network/tcp/tcp_client.dart @@ -216,6 +216,7 @@ class TcpClient { debugPrint('tcp使用用户信息:$user'); if (user == null || user.token == null) { debugPrint('❌ [TCP] 认证失败:用户未登录或 Token 为空,无法发送认证包'); + disconnect(); return; // 直接返回,不要发送无效包 } username = user.username; @@ -254,12 +255,14 @@ class TcpClient { (failure) { // 处理失败:打印日志或抛出异常 debugPrint('❌ [AuthTcp] 获取设备列表失败:$failure'); + _sendAuthPacket(); throw Exception('获取设备列表失败:$failure'); }, (devices) async { // 处理成功:devices 现在是真正的 List if (devices.isEmpty) { debugPrint('⚠️ [AuthTcp] 当前用户无可用设备,跳过切换步骤'); + return; } @@ -273,6 +276,7 @@ class TcpClient { await switchResult.fold( (failure) { debugPrint('❌ [AuthTcp] 切换设备失败:$failure'); + _sendAuthPacket(); throw Exception('切换设备失败:$failure'); }, (success) { diff --git a/lib/features/devices/presentation/bloc/devices_cubit.dart b/lib/features/devices/presentation/bloc/devices_cubit.dart index 8f3e9328..98519f18 100644 --- a/lib/features/devices/presentation/bloc/devices_cubit.dart +++ b/lib/features/devices/presentation/bloc/devices_cubit.dart @@ -200,13 +200,21 @@ class DevicesCubit extends Cubit { /// 绑定设备 Future bindDevice(String deviceId, String deviceAlias) async { + emit(state.copyWith(isLoading: true, errorMessage: '')); try { final params = BindDeviceParams(deviceId, deviceAlias); final result = await _bindDeviceUseCase.call(params); result.fold( // 失败处理 - (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '绑定设备失败')), + (failure) { + emit(state.copyWith( + isLoading: false, + errorMessage: failure.message ?? '绑定设备失败', + )); + // 抛出异常,携带后端返回的错误消息(如“设备不存在”) + throw Exception(failure.message ?? '绑定设备失败'); + }, // 成功处理(返回 int 状态码) (successCode) { if (successCode == 1) { @@ -219,7 +227,12 @@ class DevicesCubit extends Cubit { }, ); } catch (e) { - emit(state.copyWith(isLoading: false, errorMessage: '绑定异常:${e.toString()}')); + // emit(state.copyWith( + // isLoading: false, + // errorMessage: '绑定异常:${e.toString()}', + // )); + // 🔥 重新抛出,传递给 UI + rethrow; } } diff --git a/lib/features/my/presentation/web_view/user_info_pages.dart b/lib/features/my/presentation/web_view/user_info_pages.dart index 60525c0a..15068d4c 100644 --- a/lib/features/my/presentation/web_view/user_info_pages.dart +++ b/lib/features/my/presentation/web_view/user_info_pages.dart @@ -184,43 +184,132 @@ class _UserInfoCardState extends State { // 扫码绑定设备逻辑(保留) Future _handleScanAndBind(BuildContext context) async { - final String? qrCode = await Navigator.push( + bool isScanned = false; + + // 1. 扫码逻辑 (保持不变) + final String? deviceCode = await Navigator.push( context, MaterialPageRoute( - builder: (context) => MobileScanner( - onDetect: (capture) { - final List barcodes = capture.barcodes; - if (barcodes.isNotEmpty) { - final String? code = barcodes.first.rawValue; - if (code != null && code.isNotEmpty) { - Navigator.pop(context, code); + builder: (context) => Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + title: const Text('扫码绑定设备'), + backgroundColor: Colors.black54, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ), + body: MobileScanner( + onDetect: (BarcodeCapture capture) { + if (isScanned) return; + final List barcodes = capture.barcodes; + if (barcodes.isNotEmpty) { + final String? code = barcodes.first.rawValue; + if (code != null && code.isNotEmpty) { + isScanned = true; + // 使用 addPostFrameCallback 确保在帧渲染后执行,避免锁死 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted && Navigator.canPop(context)) { + Navigator.pop(context, code); + } + }); + } } - } - }, + }, + ), ), ), ); - if (qrCode == null || qrCode.isEmpty) return; + if (deviceCode == null || deviceCode.isEmpty) { + return; + } + // 2. 确认对话框 (保持不变) + final bool? confirmBind = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + title: const Text('确认绑定设备'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('设备信息:'), + const SizedBox(height: 8), + Text('设备编码:$deviceCode'), + const Text('设备状态:未绑定'), + const Text('设备类型:智能设备'), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('取消'), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('确定绑定'), + style: ElevatedButton.styleFrom(backgroundColor: Colors.blue), + ), + ], + ), + ); + + if (confirmBind != true) { + return; + } + + // 3. 显示加载框 + // 保存 BuildContext 引用,防止后续 context 变化导致找不到正确的 Navigator showDialog( context: context, barrierDismissible: false, - builder: (context) => const Center(child: CircularProgressIndicator(color: Color(0xFF1677FF), strokeWidth: 4)), + builder: (loadingContext) => const Center(child: CircularProgressIndicator()), ); try { - final devicesCubit = _getIt(); - // await devicesCubit.bindDevice(qrCode); - if (context.mounted) Navigator.pop(context); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('设备绑定成功!'), backgroundColor: Colors.green)); + final devicesCubit = GetIt.I(); + await devicesCubit.bindDevice(deviceCode, ''); + + // ✅ 成功:安全关闭加载框 + if (context.mounted && Navigator.canPop(context)) { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('设备绑定成功!'), backgroundColor: Colors.green), + ); } } catch (e) { - if (context.mounted) Navigator.pop(context); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('绑定失败:$e'), backgroundColor: Colors.red)); + // ✅ 失败:安全关闭加载框 (关键修复点) + // 即使发生了 Framework 错误,也要尝试关闭弹窗 + if (context.mounted && Navigator.canPop(context)) { + Navigator.pop(context); + + // 显示具体错误信息 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('绑定失败:${e.toString()}'), + backgroundColor: Colors.red, + duration: const Duration(seconds: 4), + ), + ); + } else { + // 极端情况:如果 Navigator 已经锁死无法 pop,尝试用其他方式提示用户 + // 或者等待下一帧再试 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + // 尝试再次关闭,或者只显示错误提示 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('绑定失败:${e.toString()} (请手动返回)'), + backgroundColor: Colors.orange, + ), + ); + } + }); } + debugPrint('设备绑定失败详情:$e'); } }