完成解决合并扫码绑定代码问题

完成优化tcp的重连和认证
完成登录响应慢和热加载时候tcp不能统一对象的重连
This commit is contained in:
2026-03-05 10:00:18 +08:00
parent ac9ee670ec
commit e7ac4b00f3
5 changed files with 133 additions and 32 deletions

View File

@@ -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'},
),
);

View File

@@ -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<DeviceEntity>
if (devices.isEmpty) {
debugPrint('⚠️ [AuthTcp] 当前用户无可用设备,跳过切换步骤');
return;
}
@@ -273,6 +276,7 @@ class TcpClient {
await switchResult.fold(
(failure) {
debugPrint('❌ [AuthTcp] 切换设备失败:$failure');
_sendAuthPacket();
throw Exception('切换设备失败:$failure');
},
(success) {

View File

@@ -53,8 +53,8 @@ class AuthCubit extends Cubit<AuthState> {
Future<void> loginSuccess(UserEntity user) async {
await storage.saveUser(user);
await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
await _authTcpDatasource.sendAuthPacket();//包括发送认证包和获取列表和切换函数
tcp.startHeartbeat(interval: const Duration(seconds: 4)); //启动心跳
//await _authTcpDatasource.sendAuthPacket();//包括发送认证包和获取列表和切换函数
tcp.startHeartbeat(interval: const Duration(seconds: 4)); //启动心跳*/
appCubit.setAuth(user);
emit(AuthAuthenticated(user));
}

View File

@@ -180,16 +180,21 @@ class DevicesCubit extends Cubit<DevicesState> {
}
/// 绑定设备
Future<void> 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) {
@@ -201,14 +206,17 @@ class DevicesCubit extends Cubit<DevicesState> {
isLoading: false,
errorMessage: '绑定失败:状态码 $successCode',
));
throw Exception('绑定失败:状态码 $successCode');
}
},
);
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: '绑定异常:${e.toString()}',
));
// emit(state.copyWith(
// isLoading: false,
// errorMessage: '绑定异常:${e.toString()}',
// ));
// 🔥 重新抛出,传递给 UI
rethrow;
}
}

View File

@@ -179,43 +179,132 @@ class _UserInfoCardState extends State<UserInfoCard> {
// 扫码绑定设备逻辑(保留)
Future<void> _handleScanAndBind(BuildContext context) async {
final String? qrCode = await Navigator.push<String>(
bool isScanned = false;
// 1. 扫码逻辑 (保持不变)
final String? deviceCode = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => MobileScanner(
onDetect: (capture) {
final List<Barcode> 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<Barcode> 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<bool>(
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<DevicesCubit>();
// 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<DevicesCubit>();
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');
}
}