完成优化登录的优化

完成切换和切回APP数据的及时响应
完成服务下发退出账号的命令
This commit is contained in:
2026-03-09 13:21:18 +08:00
parent 0fd5d620b5
commit 9f3a7e578a
5 changed files with 159 additions and 74 deletions

View File

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

View File

@@ -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<AuthState> {
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<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(); //包括发送认证包和获取列表和切换函数
// 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<AuthState> {
_kickOutSub = dispatcher.onJsonMessage(0x12).listen((json) {
// 如果后端发来指令确认需要退出
// print('>>> [AUTH] 收到 0x12: $json'); //
logout();
// logout();
final respond = json['respond'] ?? '';
if (respond == 'have_logged_in') {
logout(); // ✅ 只有特定消息才退出
}
});
}

View File

@@ -51,16 +51,16 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
);
// 订阅 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] 订阅建立完成,将持续监听数据流');

View File

@@ -221,7 +221,7 @@ class DevicesCubit extends Cubit<DevicesState> {
debugPrint('🛑 检测到已连接,先断开旧 TCP 连接...');
_tcpClient.disconnects(forSwitch: true);
// 稍微等待一下,确保底层 Socket 资源释放 (可选,但推荐)
await Future.delayed(const Duration(milliseconds: 100));
// await Future.delayed(const Duration(milliseconds: 100));
}
// 🔥 关键修复 2:发起新连接

View File

@@ -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<DeviceEntity> deviceList, required DeviceEntity? selectedDevice}) {
if (deviceList.isEmpty) return null;
@override
State<HomePage> createState() => _HomePageState();
}
// 用deviceName判断设备唯一性(如果有deviceId建议替换为deviceId)
final bool isSelectedDeviceValid = deviceList.any((device) => device.deviceName == selectedDevice?.deviceName);
class _HomePageState extends State<HomePage> {
bool _isInitializing = false; // 标记是否正在初始化
bool _hasLoadedDevices = false; // 标记是否已加载过设备列表
debugPrint("选中设备有效性:$isSelectedDeviceValid,选中设备名:${selectedDevice?.deviceName},设备列表:${deviceList.map((e) => e.deviceName).toList()}");
/// 🔥 核心方法:页面显示前的初始化操作
Future<void> _initializePage() async {
if (_isInitializing) {
debugPrint('[HomePage] 正在初始化,跳过重复调用');
return;
}
return isSelectedDeviceValid ? selectedDevice : deviceList.first;
_isInitializing = true;
try {
final userState = context.read<AppUserCubit>().state;
final String? username = userState.user?.username;
if (username == null || username.isEmpty) {
debugPrint('[HomePage] 用户未登录,跳过初始化');
return;
}
final devicesCubit = context.read<DevicesCubit>();
// 🔥 步骤 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<DeviceEntity> deviceList = deviceState.devices ?? [];
if (deviceList.isNotEmpty) {
// 🔥 步骤 3: TCP 连接(异步执行,不阻塞 UI)
final firstDevice = deviceState.selectedDevice ?? deviceList.first;
debugPrint('🔌 [HomePage] 准备连接设备:${firstDevice.deviceName}');
final tcpClient = GetIt.instance<TcpClient>();
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<AppUserCubit>().state;
final String? username = userState.user?.username;
final deviceState = context.watch<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
// 页面首次渲染完成后,触发设备列表请求(避免同步调用导致的生命周期问题)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (username != null && username.isNotEmpty) {
final devicesCubit = context.read<DevicesCubit>();
// 仅在设备列表为空或首次进入时请求(避免重复请求)
devicesCubit.fetchAllDevices(username);
//context.read<TcpClient>().connectBySwitch(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT, deviceName: device.deviceName);
print("开始tcp连接。。。。");
final tcpClient = GetIt.instance<TcpClient>();
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<DevicesCubit, DevicesState>(
builder: (context, deviceState) {
// 3. 正常状态:处理设备数据并渲染页面
final List<DeviceEntity> 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();
}
}