From 9f3a7e578ae9cc7838ef713207708aef9d2ddebf Mon Sep 17 00:00:00 2001 From: Songzex <2402265378@qq.com> Date: Mon, 9 Mar 2026 13:21:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BC=98=E5=8C=96=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E7=9A=84=E4=BC=98=E5=8C=96=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=92=8C=E5=88=87=E5=9B=9EAPP=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84=E5=8F=8A=E6=97=B6=E5=93=8D=E5=BA=94=20?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9C=8D=E5=8A=A1=E4=B8=8B=E5=8F=91=E9=80=80?= =?UTF-8?q?=E5=87=BA=E8=B4=A6=E5=8F=B7=E7=9A=84=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/network/dio_client.dart | 4 +- .../auth/presentation/bloc/auth_cubit.dart | 18 +- .../presentation/bloc/device_status_bloc.dart | 20 +- .../presentation/bloc/devices_cubit.dart | 2 +- .../home/presentation/pages/home_page.dart | 189 +++++++++++++----- 5 files changed, 159 insertions(+), 74 deletions(-) diff --git a/lib/core/network/dio_client.dart b/lib/core/network/dio_client.dart index 898cb898..dbb1ff41 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: 10), - receiveTimeout: const Duration(seconds: 10), + connectTimeout: const Duration(seconds: 20), + receiveTimeout: const Duration(seconds: 20), headers: {'Content-Type': 'application/json'}, ), ); diff --git a/lib/features/auth/presentation/bloc/auth_cubit.dart b/lib/features/auth/presentation/bloc/auth_cubit.dart index 4575dc20..f6e6627f 100644 --- a/lib/features/auth/presentation/bloc/auth_cubit.dart +++ b/lib/features/auth/presentation/bloc/auth_cubit.dart @@ -14,6 +14,7 @@ import '../../../../core/network/tcp/tcp_client.dart'; import '../../../../core/storage/user_storage.dart'; import '../../../devices/domain/usecases/get_user_device_usecase.dart'; import '../../../devices/domain/usecases/switch_device_usecase.dart'; +import '../../../devices/presentation/bloc/devices_cubit.dart'; import '../../data/datasources/auth_tcp_datasource.dart'; import '../../data/datasources/impl/auth_tcp_datasource_impl.dart'; import 'auth_state.dart'; @@ -47,12 +48,9 @@ class AuthCubit extends Cubit { data: {'user': user, 'data': user} ); if (user != null) { - // print("App 启动时检查本地缓存user != null) "); - // 1. print("App 启动时检查本地缓存user != null) "); - // print("App 启动时检查本地缓存 tcp.connect) "); - tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT); + tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT); // await _authTcpDatasource.sendAuthPacket();//包括发送认证包和获取列表和切换函数 - tcp.startHeartbeat(interval: const Duration(seconds: 4)); + tcp.startHeartbeat(interval: const Duration(seconds: 4)); // 2. 同步全局 App 状态 appCubit.setAuth(user); // 3. 进入已登录状态 @@ -67,9 +65,9 @@ class AuthCubit extends Cubit { Future loginSuccess(UserEntity user) async { await storage.saveUser(user); //await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT); - // await _authTcpDatasource.sendAuthPacket(); //包括发送认证包和获取列表和切换函数 + // await _authTcpDatasource.sendAuthPacket(); //包括发送认证包和获取列表和切换函数 //tcp.startHeartbeat(interval: const Duration(seconds: 4)); //启动心跳 - // appCubit.setAuth(user); + appCubit.setAuth(user); emit(AuthAuthenticated(user)); } @@ -90,7 +88,11 @@ class AuthCubit extends Cubit { _kickOutSub = dispatcher.onJsonMessage(0x12).listen((json) { // 如果后端发来指令确认需要退出 // print('>>> [AUTH] 收到 0x12: $json'); // - logout(); + // logout(); + final respond = json['respond'] ?? ''; + if (respond == 'have_logged_in') { + logout(); // ✅ 只有特定消息才退出 + } }); } diff --git a/lib/features/devices/presentation/bloc/device_status_bloc.dart b/lib/features/devices/presentation/bloc/device_status_bloc.dart index 7f04cfa8..aefd1cec 100644 --- a/lib/features/devices/presentation/bloc/device_status_bloc.dart +++ b/lib/features/devices/presentation/bloc/device_status_bloc.dart @@ -51,16 +51,16 @@ class DeviceStatusBloc extends Bloc { ); // 订阅 JSON 流 (0x12) - _jsonSub = _dispatcher.onJsonMessage(0x12).listen( - (jsonData) { - debugPrint('📩 [Bloc] 收到 JSON 数据事件:$jsonData'); - if (!isClosed) { - add(PushMessageReceived(jsonData)); - } - }, - onDone: () => debugPrint('⚠️ [Bloc] JSON 流已结束 (onDone)'), - onError: (e) => debugPrint('❌ [Bloc] JSON 流发生错误:$e'), - ); + // _jsonSub = _dispatcher.onJsonMessage(0x12).listen( + // (jsonData) { + // debugPrint('📩 [Bloc] 收到 JSON 数据事件:$jsonData'); + // if (!isClosed) { + // add(PushMessageReceived(jsonData)); + // } + // }, + // onDone: () => debugPrint('⚠️ [Bloc] JSON 流已结束 (onDone)'), + // onError: (e) => debugPrint('❌ [Bloc] JSON 流发生错误:$e'), + // ); _isSubscribed = true; debugPrint('✅ [DeviceStatusBloc] 订阅建立完成,将持续监听数据流'); diff --git a/lib/features/devices/presentation/bloc/devices_cubit.dart b/lib/features/devices/presentation/bloc/devices_cubit.dart index 8e956169..c383a7f7 100644 --- a/lib/features/devices/presentation/bloc/devices_cubit.dart +++ b/lib/features/devices/presentation/bloc/devices_cubit.dart @@ -221,7 +221,7 @@ class DevicesCubit extends Cubit { debugPrint('🛑 检测到已连接,先断开旧 TCP 连接...'); _tcpClient.disconnects(forSwitch: true); // 稍微等待一下,确保底层 Socket 资源释放 (可选,但推荐) - await Future.delayed(const Duration(milliseconds: 100)); + // await Future.delayed(const Duration(milliseconds: 100)); } // 🔥 关键修复 2:发起新连接 diff --git a/lib/features/home/presentation/pages/home_page.dart b/lib/features/home/presentation/pages/home_page.dart index 8e8604c3..9c813e73 100644 --- a/lib/features/home/presentation/pages/home_page.dart +++ b/lib/features/home/presentation/pages/home_page.dart @@ -2,81 +2,164 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart'; +import 'package:maibu_satabot_v2/core/consts/tcp_consts.dart'; +import 'package:maibu_satabot_v2/core/network/tcp/tcp_client.dart'; import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart'; -import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_state.dart'; // 导入设备状态类 -import '../../../../core/consts/tcp_consts.dart'; -import '../../../../core/network/tcp/tcp_client.dart'; import '../../../devices/domain/entities/device_entity.dart'; import '../widgets/ImmersionHeader.dart'; import '../widgets/quick_actions_grid.dart'; import '../widgets/work_params_card.dart'; -class HomePage extends StatelessWidget { +class HomePage extends StatefulWidget { const HomePage({super.key}); - /// 校验选中设备是否有效,无效则返回列表第一个设备 - DeviceEntity? _getValidCurrentDevice({required List deviceList, required DeviceEntity? selectedDevice}) { - if (deviceList.isEmpty) return null; + @override + State createState() => _HomePageState(); +} - // 用deviceName判断设备唯一性(如果有deviceId建议替换为deviceId) - final bool isSelectedDeviceValid = deviceList.any((device) => device.deviceName == selectedDevice?.deviceName); +class _HomePageState extends State { + bool _isInitializing = false; // 标记是否正在初始化 + bool _hasLoadedDevices = false; // 标记是否已加载过设备列表 - debugPrint("选中设备有效性:$isSelectedDeviceValid,选中设备名:${selectedDevice?.deviceName},设备列表:${deviceList.map((e) => e.deviceName).toList()}"); + /// 🔥 核心方法:页面显示前的初始化操作 + Future _initializePage() async { + if (_isInitializing) { + debugPrint('[HomePage] 正在初始化,跳过重复调用'); + return; + } - return isSelectedDeviceValid ? selectedDevice : deviceList.first; + _isInitializing = true; + + try { + final userState = context.read().state; + final String? username = userState.user?.username; + + if (username == null || username.isEmpty) { + debugPrint('[HomePage] 用户未登录,跳过初始化'); + return; + } + + final devicesCubit = context.read(); + + // 🔥 步骤 1: 先检查设备列表是否需要刷新 + if (!_hasLoadedDevices && (devicesCubit.state.devices == null || devicesCubit.state.devices!.isEmpty)) { + debugPrint('[HomePage] 设备列表为空,触发获取...'); + + // ❌ 不要 await,让它后台执行 + devicesCubit.fetchAllDevices(username); + + // 🔥 等待一小段时间让数据过来 + // await Future.delayed(const Duration(milliseconds: 500)); + + _hasLoadedDevices = true; + } + + // 🔥 步骤 2: 获取最新设备列表 + final deviceState = devicesCubit.state; + final List deviceList = deviceState.devices ?? []; + + if (deviceList.isNotEmpty) { + // 🔥 步骤 3: TCP 连接(异步执行,不阻塞 UI) + final firstDevice = deviceState.selectedDevice ?? deviceList.first; + debugPrint('🔌 [HomePage] 准备连接设备:${firstDevice.deviceName}'); + + final tcpClient = GetIt.instance(); + var bool = tcpClient.isConnected; + debugPrint('tcpClient,$bool'); + if (!tcpClient.isConnected) { + // ❌ 异步执行,不阻塞 + tcpClient.connectBySwitch( + host: TCPConsts.TCP_IP, + port: TCPConsts.TCP_PORT, + deviceName: firstDevice.deviceName + ).then((_) { + debugPrint('✅ [HomePage] TCP 连接成功'); + tcpClient.startHeartbeat(interval: const Duration(seconds: 4)); + }).catchError((e) { + debugPrint('❌ [HomePage] TCP 连接失败:$e'); + }); + } else { + debugPrint('✅ [HomePage] TCP 已连接'); + } + } else { + debugPrint('⚠️ [HomePage] 设备列表仍为空'); + } + + } catch (e) { + debugPrint('❌ [HomePage] 初始化异常:$e'); + } finally { + _isInitializing = false; + } + } + + @override + void initState() { + super.initState(); + debugPrint('📄 [HomePage] initState - 页面开始初始化'); + + // 🔥 关键:在页面显示之前执行初始化 + // 使用 addPostFrameCallback 确保 build 完成后立即执行 + WidgetsBinding.instance.addPostFrameCallback((_) { + _initializePage(); + }); } @override Widget build(BuildContext context) { - // 获取用户状态(确保用户已登录) + debugPrint('🎨 [HomePage] build - 渲染 UI'); + + // 同时监听用户和设备状态 final userState = context.watch().state; - final String? username = userState.user?.username; + final deviceState = context.watch().state; + final currentDevice = deviceState.selectedDevice; - // 页面首次渲染完成后,触发设备列表请求(避免同步调用导致的生命周期问题) - WidgetsBinding.instance.addPostFrameCallback((_) { - if (username != null && username.isNotEmpty) { - final devicesCubit = context.read(); - // 仅在设备列表为空或首次进入时请求(避免重复请求) - devicesCubit.fetchAllDevices(username); - //context.read().connectBySwitch(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT, deviceName: device.deviceName); - print("开始tcp连接。。。。"); - final tcpClient = GetIt.instance(); - tcpClient.connectBySwitch(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT, deviceName: "MC700AIR-CN-JS-1762486208349-0000001A-FD"); - tcpClient.startHeartbeat(interval: const Duration(seconds: 4)); - } - }); + final defaultDevice = DeviceEntity( + deviceName: '暂未绑定设备', + productId: 00000, + productName: '', + tenantId: 00000, + tenantName: '' + ); - // 构建默认设备(无绑定设备时展示) - final DeviceEntity defaultDevice = DeviceEntity(deviceName: '暂未绑定设备', productId: 00000, productName: '', tenantId: 00000, tenantName: ''); + // 如果没有绑定设备,展示未绑定设备的提示页面 + if (currentDevice == null) { + debugPrint('ℹ️ [HomePage] 无选中设备,显示默认设备'); - return BlocBuilder( - builder: (context, deviceState) { - // 3. 正常状态:处理设备数据并渲染页面 - final List deviceList = deviceState.devices ?? []; - final DeviceEntity? selectedDevice = deviceState.selectedDevice; - final DeviceEntity? currentDevice = _getValidCurrentDevice(deviceList: deviceList, selectedDevice: selectedDevice); + return Scaffold( + backgroundColor: const Color(0xFFF7F7F7), + body: CustomScrollView( + physics: const BouncingScrollPhysics(), + slivers: [ + SliverToBoxAdapter(child: ImmersionHeader(device: defaultDevice)), + const SliverToBoxAdapter(child: QuickActionsGrid()), + const SliverToBoxAdapter(child: WorkParamsCard()), + const SliverToBoxAdapter(child: SizedBox(height: 120)), + ], + ), + ); + } - return Scaffold( - backgroundColor: const Color(0xFFF7F7F7), - body: CustomScrollView( - physics: const BouncingScrollPhysics(), - slivers: [ - // 1. 沉浸式头部(优先展示有效设备,无则展示默认设备) - SliverToBoxAdapter(child: ImmersionHeader(device: currentDevice ?? defaultDevice)), + // 如果有绑定设备,正常展示页面 + debugPrint('✅ [HomePage] 有选中设备:${currentDevice.deviceName}'); - // 2. 功能网格 - const SliverToBoxAdapter(child: QuickActionsGrid()), - - // 3. 作业参数卡片 - SliverToBoxAdapter(child: WorkParamsCard()), - - // 4. 地图区域占位 - const SliverToBoxAdapter(child: SizedBox(height: 120)), - ], - ), - ); - }, + return Scaffold( + backgroundColor: const Color(0xFFF7F7F7), + body: CustomScrollView( + physics: const BouncingScrollPhysics(), + slivers: [ + SliverToBoxAdapter(child: ImmersionHeader(device: currentDevice)), + const SliverToBoxAdapter(child: QuickActionsGrid()), + SliverToBoxAdapter(child: WorkParamsCard()), + const SliverToBoxAdapter(child: SizedBox(height: 120)), + ], + ), ); } + + @override + void dispose() { + debugPrint('🗑️ [HomePage] dispose - 页面销毁'); + super.dispose(); + } }