diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index e2a14633..8e108c8b 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -3,6 +3,7 @@ import 'package:maibu_satabot_v2/core/router/route_paths.dart'; import 'package:maibu_satabot_v2/features/ai/presentation/routes/ai_routes.dart'; import 'package:maibu_satabot_v2/features/auth/presentation/pages/login_page.dart'; import 'package:maibu_satabot_v2/features/auth/presentation/routes/auth_routes.dart'; +import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart'; import 'package:maibu_satabot_v2/features/home/presentation/routes/home_routes.dart'; import 'package:maibu_satabot_v2/features/my/presentation/routes/my_routes.dart'; @@ -14,7 +15,6 @@ import 'go_router_refresh_stream.dart'; GoRouter createRouter(AuthCubit authCubit) { return GoRouter( - initialLocation: RoutePaths.login, refreshListenable: GoRouterRefreshStream(authCubit.stream), redirect: (context, state) { @@ -33,7 +33,14 @@ GoRouter createRouter(AuthCubit authCubit) { // 注册 machineDetails 路由 GoRoute( path: RoutePaths.machineDetails, - builder: (context, state) => const MachineDetailsPage(), // 替换为实际页面 + name: 'machineDetails', // 建议加 name,方便跳转 + builder: (context, state) { + // 从 extra 中取出设备数据(核心) + final DeviceEntity? device = state.extra as DeviceEntity?; + + // 跳转到详情页组件,并传入设备数据 + return MachineDetailsPage(device: device); + }, ), // 注册登录注册 路由 diff --git a/lib/features/home/presentation/widgets/ImmersionHeader.dart b/lib/features/home/presentation/widgets/ImmersionHeader.dart index 399abfe7..a37a46a3 100644 --- a/lib/features/home/presentation/widgets/ImmersionHeader.dart +++ b/lib/features/home/presentation/widgets/ImmersionHeader.dart @@ -355,7 +355,9 @@ class ImmersionHeader extends StatelessWidget { ), const SizedBox(width: 4), Text( - device.deviceAlias ?? '未知设备', + (device.deviceAlias?.trim() ?? '').isEmpty + ? '未知设备' + : device.deviceAlias!, style: const TextStyle(fontWeight: FontWeight.bold), ), ], @@ -393,8 +395,11 @@ class ImmersionHeader extends StatelessWidget { const SizedBox(height: 8), CCPrimaryButton( onPressed: () { - context.push(RoutePaths.machineDetails); - print("通过按钮进入详情"); + context.push( + RoutePaths.machineDetails, + extra: device, + ); // 把当前点击的这个设备对象传过去 + print("通过按钮进入详情,设备名称:$device"); }, text: "详情", diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index 680d5e3c..b3665579 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -354,13 +354,20 @@ class _MapPageEnterpriseState extends State { ); } + // ========== 新增:返回上一级页面的方法 ========== + void _navigateBack() { + // 关闭当前页面,返回上一级 + Navigator.of(context).pop(); + } + @override void dispose() { _positionSub?.cancel(); _traceManager.reset(); _mapController.dispose(); - _sub?.cancel(); - + // 修复:_sub 是 non-null 类型,直接 cancel 无需 ? + _sub.cancel(); + _plotNameController.dispose(); // 新增:释放文本控制器资源 super.dispose(); } @@ -673,8 +680,6 @@ class _MapPageEnterpriseState extends State { ), /// ========== 新增:中心标与历史打点的虚线连线 ========== - /// - /// if (_markedPoints.length >= 2) PolylineLayer( polylines: [ for (int i = 0; i < _markedPoints.length - 1; i++) @@ -771,6 +776,53 @@ class _MapPageEnterpriseState extends State { ], ), + // ========== 悬浮返回按钮(核心修改) ========== + Positioned( + top: 10, + left: 10, + child: GestureDetector( + // 自定义点击事件 + onTap: _navigateBack, + // 自定义点击反馈(替代 IconButton 的高亮/水波纹) + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.8), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 3, + offset: const Offset(0, 2), + ), + ], + ), + // 核心:用 Stack 实现点击高亮层 + 图标层 + child: Stack( + alignment: Alignment.center, // 所有子组件居中 + children: [ + // 1. 点击高亮层(默认隐藏,点击时显示) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, // 默认透明 + borderRadius: BorderRadius.circular(20), + ), + ), + ), + // 2. 图标层(绝对居中) + const Icon( + Icons.arrow_back_ios, + color: Colors.black87, + size: 20, + ), + ], + ), + ), + ), + ), + /// ========== 核心:地图正中心固定定位标 ========== Positioned( left: 0, @@ -788,45 +840,37 @@ class _MapPageEnterpriseState extends State { ), ), ), + //保存按钮 if (_saveBoxOpen) Positioned( - left: 16, + right: 80, top: maxTop > 10 ? 11 : maxTop, - child: // 改用小尺寸按钮容器,替代默认大尺寸FloatingActionButton - Container( - width: 40, // 按钮宽度(默认FAB是56,改为40更小巧) - height: 40, // 按钮高度 + child: Container( + width: 40, + height: 40, decoration: BoxDecoration( - color: const Color(0xFF00C853), // 保留绿色主题 - shape: BoxShape.circle, // 圆形 + color: const Color(0xFF00C853), + shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black12, - blurRadius: 4, // 对应elevation:4的阴影效果 - offset: const Offset(0, 2), // 阴影偏移 + blurRadius: 4, + offset: const Offset(0, 2), ), ], ), child: IconButton( onPressed: () { _showSavePlotDialog(); - //// 点击保存按钮的逻辑 - //// 1. 可以直接调用保存方法 - //_saveCurrentPath(); - //// 2. 也可以弹出输入框让用户输入路径名称(推荐) - //_showSavePathDialog(); }, - icon: const Icon( - Icons.save, - color: Colors.white, - size: 18, // 图标尺寸缩小(原20→18) - ), - padding: EdgeInsets.zero, // 移除IconButton默认内边距 - constraints: const BoxConstraints(), // 解除IconButton尺寸限制 + icon: const Icon(Icons.save, color: Colors.white, size: 18), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), ), ), ), + // 右侧悬浮菜单 Positioned( height: 336, @@ -852,7 +896,6 @@ class _MapPageEnterpriseState extends State { ), ), ), - //开始作业面板 // 底部操作面板 if (_isPanelOpen) @@ -879,13 +922,12 @@ class _MapPageEnterpriseState extends State { setState(() => _currentAreaMode = mode); debugPrint('外部收到区域模式:$mode'); }, - // ========== 绑定加号打点事件 ========== onAddTap: () { - _addMarkedPoint(); // 点击加号打点 + _addMarkedPoint(); debugPrint('外部处理加号按钮点击,已添加打点'); }, onDistanceTap: (distance) { - _workDistance = distance; // 更新距离状态 + _workDistance = distance; debugPrint( '外部处理作业行距距离设置,当前距离:${distance.toStringAsFixed(1)}米', ); @@ -920,7 +962,7 @@ class _MapPageEnterpriseState extends State { ); _angle = result['optimalHeading'] == true ? -1 - : result['direction']; // 更新航线方向角状态 + : result['direction']; debugPrint('外部处理航线方向设置,当前角度:${_angle}'); setState(() => _directionBoxOpen = false); }, @@ -929,14 +971,13 @@ class _MapPageEnterpriseState extends State { }, ), ), - // 在build方法的Stack中添加抽屉组件 + + // 列表面板 if (_isListBoxOpen) - // 半透明背景 + 底部抽屉 Positioned.fill( child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - // 1. 半透明遮罩(点击关闭抽屉) Expanded( child: GestureDetector( onTap: () { @@ -947,14 +988,12 @@ class _MapPageEnterpriseState extends State { child: Container(color: Colors.black.withOpacity(0.3)), ), ), - - // 2. 底部抽屉主体 Container( width: double.infinity, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical( - top: Radius.circular(16), // 顶部圆角 + top: Radius.circular(16), ), boxShadow: [ BoxShadow( @@ -964,14 +1003,12 @@ class _MapPageEnterpriseState extends State { ), ], ), - // 限制抽屉高度(可自定义) constraints: const BoxConstraints( maxHeight: 600, minHeight: 200, ), child: Column( children: [ - // 顶部标题栏 Container( padding: const EdgeInsets.symmetric( horizontal: 16, @@ -980,7 +1017,6 @@ class _MapPageEnterpriseState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // 标题:显示数据总数 Text( '地块列表(共${_plotList.length}条)', style: const TextStyle( @@ -989,8 +1025,6 @@ class _MapPageEnterpriseState extends State { color: Colors.black87, ), ), - - // 关闭按钮 IconButton( onPressed: () { setState(() { @@ -1006,14 +1040,10 @@ class _MapPageEnterpriseState extends State { ], ), ), - const Divider(height: 1, color: Colors.grey), - - // 列表内容(可滚动) Expanded( child: _plotList.isEmpty - ? // 空数据提示 - const Center( + ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -1034,8 +1064,7 @@ class _MapPageEnterpriseState extends State { ], ), ) - : // 列表项 - ListView.builder( + : ListView.builder( padding: const EdgeInsets.symmetric( vertical: 8, ), diff --git a/lib/features/home/presentation/widgets/work_params_card.dart b/lib/features/home/presentation/widgets/work_params_card.dart index afeaff50..c22c7c3b 100644 --- a/lib/features/home/presentation/widgets/work_params_card.dart +++ b/lib/features/home/presentation/widgets/work_params_card.dart @@ -117,13 +117,13 @@ class _WorkParamsCardState extends State { final result = await useCase(params); print("发送获取历史作业记录结束"); result.fold( - (failure) { + (failure) { setState(() { _errorMessage = failure.message; _isLoading = false; }); }, - (data) { + (data) { setState(() { _statistics = data; _isLoading = false; @@ -141,9 +141,12 @@ class _WorkParamsCardState extends State { } // 弹窗展示详情 -// 弹窗展示详情(从底部弹出) -// 弹窗展示详情(从底部弹出,覆盖底部导航栏) - void _showDetailsDialog(BuildContext context, List data) { + // 弹窗展示详情(从底部弹出) + // 弹窗展示详情(从底部弹出,覆盖底部导航栏) + void _showDetailsDialog( + BuildContext context, + List data, + ) { showModalBottomSheet( context: context, isScrollControlled: true, // 支持全屏高度 @@ -203,15 +206,31 @@ class _WorkParamsCardState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildDetailRow(Icons.calendar_today, '开始时间', item.startTime), - _buildDetailRow(Icons.calendar_today_outlined, '结束时间', item.endTime), - _buildDetailRow(Icons.square_foot, '工作面积', '${item.workArea} 亩'), + _buildDetailRow( + Icons.calendar_today, + '开始时间', + item.startTime, + ), + _buildDetailRow( + Icons.calendar_today_outlined, + '结束时间', + item.endTime, + ), + _buildDetailRow( + Icons.square_foot, + '工作面积', + '${item.workArea} 亩', + ), _buildDetailRow( Icons.directions_car, '工作距离', '${item.distance?.toStringAsFixed(2) ?? '未计算'} km', ), - _buildDetailRow(Icons.timer, '花费时间', '${item.time} 小时'), + _buildDetailRow( + Icons.timer, + '花费时间', + '${item.time} 小时', + ), ], ), ); @@ -244,9 +263,7 @@ class _WorkParamsCardState extends State { ); } - - -// 构建详情行 + // 构建详情行 Widget _buildDetailRow(IconData icon, String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), @@ -265,10 +282,7 @@ class _WorkParamsCardState extends State { Expanded( child: Text( value, - style: const TextStyle( - fontSize: 14, - color: Colors.black54, - ), + style: const TextStyle(fontSize: 14, color: Colors.black54), ), ), ], @@ -276,9 +290,6 @@ class _WorkParamsCardState extends State { ); } - - - // 构建单个参数项 Widget _buildParamItem(String label, String value, String unit) { return Expanded( diff --git a/lib/features/machine_details/presentation/pages/machine_details_page.dart b/lib/features/machine_details/presentation/pages/machine_details_page.dart index af21cea0..97948e01 100644 --- a/lib/features/machine_details/presentation/pages/machine_details_page.dart +++ b/lib/features/machine_details/presentation/pages/machine_details_page.dart @@ -1,30 +1,42 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart'; class MachineDetailsPage extends StatelessWidget { - const MachineDetailsPage({super.key}); + final DeviceEntity? device; // 接收传过来的设备数据 + + const MachineDetailsPage({super.key, this.device}); @override Widget build(BuildContext context) { - // 移除 MaterialApp,直接返回 Scaffold - return const DeviceDetailPage(); + // 把 device 参数传递给 DeviceDetailPage + return DeviceDetailPage(device: device); } } class DeviceDetailPage extends StatelessWidget { - const DeviceDetailPage({super.key}); + final DeviceEntity? device; // 定义参数接收设备数据 + + // 构造函数接收 device 参数 + const DeviceDetailPage({super.key, this.device}); @override Widget build(BuildContext context) { + // 空值校验:无设备数据时显示提示 + if (device == null) { + return const Scaffold(body: Center(child: Text("未获取到设备信息"))); + } + + // 非空断言:已经校验过 device 非空,放心使用 + final currentDevice = device!; + return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( centerTitle: true, title: const Text( '设备详情', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), leading: IconButton( icon: const Icon(Icons.arrow_back_ios), @@ -35,9 +47,9 @@ class DeviceDetailPage extends StatelessWidget { padding: const EdgeInsets.all(16), child: Column( children: [ - _buildDeviceStatusCard(), + _buildDeviceStatusCard(currentDevice), // 传递设备数据 const SizedBox(height: 16), - _buildBasicInfoCard(), + _buildBasicInfoCard(context, currentDevice), // 传递context和设备数据 const SizedBox(height: 16), _buildResourceCenterCard(), const SizedBox(height: 24), @@ -48,8 +60,8 @@ class DeviceDetailPage extends StatelessWidget { ); } - // 设备状态卡片 - Widget _buildDeviceStatusCard() { + // 设备状态卡片(接收设备数据参数) + Widget _buildDeviceStatusCard(DeviceEntity device) { return Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( @@ -58,17 +70,16 @@ class DeviceDetailPage extends StatelessWidget { ), child: Column( children: [ - Image.asset( - 'assets/images/car.png', - width: 380, - fit: BoxFit.contain, - ), + Image.asset('assets/images/car.png', width: 380, fit: BoxFit.contain), const SizedBox(height: 12), - const Text( - '离线', + // 使用设备真实在线状态 + Text( + device.isOnline ? '在线' : '离线', style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: device.isOnline + ? const Color(0xFF00C853) + : const Color(0xFF999999), ), ), ], @@ -76,8 +87,17 @@ class DeviceDetailPage extends StatelessWidget { ); } - // 基本信息卡片 - Widget _buildBasicInfoCard() { + // 基本信息卡片(接收context和设备数据参数) + Widget _buildBasicInfoCard(BuildContext context, DeviceEntity device) { + // 处理设备名称(空值兜底) + final deviceName = (device.deviceAlias?.trim() ?? '').isEmpty + ? '未知设备' + : device.deviceAlias!; + // 处理设备ID(空值兜底) + final deviceId = device.deviceName ?? '未知ID'; + //print('激活时间:$device'); // 调试输出激活时间 + + //final activeTime = device.activeTime != null ? device.activeTime : '未知时间'; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -91,47 +111,114 @@ class DeviceDetailPage extends StatelessWidget { padding: EdgeInsets.only(bottom: 16), child: Text( '基本信息', - style: TextStyle( - fontSize: 14, - color: Color(0xFF999999), - ), + style: TextStyle(fontSize: 14, color: Color(0xFF999999)), ), ), + // 设备名称行 - 传递context参数 _buildInfoRow( + context: context, // 关键:补上必填的context参数 icon: Icons.info_outline, label: '设备名称', trailing: Row( - children: const [ - Text(''), - SizedBox(width: 8), - Icon(Icons.edit, size: 16, color: Color(0xFF007AFF)), + mainAxisSize: MainAxisSize.min, // 防止Row撑满宽度 + children: [ + // 显示真实设备名称 + Text(deviceName), + const SizedBox(width: 8), + const Icon(Icons.edit, size: 16, color: Color(0xFF007AFF)), ], ), ), const Divider(height: 32, color: Color(0xFFF0F0F0)), + // 设备ID行 - 传递context参数 + // 设备ID行 - 传递context参数 _buildInfoRow( + context: context, icon: Icons.shield_outlined, label: '设备ID', - trailing: Row( - children: const [ - Text('undefined'), - SizedBox(width: 8), - Icon(Icons.copy, size: 16, color: Color(0xFF999999)), + trailing: Column( + crossAxisAlignment: CrossAxisAlignment.end, // 右对齐 + mainAxisSize: MainAxisSize.min, // 只占必要高度 + children: [ + // 核心修改:去掉宽度限制,允许换行显示全部ID + SizedBox( + // 取消固定宽度,让文本自适应(也可设置最大宽度适配屏幕) + width: + MediaQuery.of(context).size.width - 180, // 适配屏幕宽度(避免超出) + child: GestureDetector( + onLongPress: () { + // 长按文本复制 + Clipboard.setData(ClipboardData(text: deviceId)); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('设备ID已复制'), + duration: Duration(seconds: 1), + ), + ); + } + }, + child: Text( + deviceId, + textAlign: TextAlign.right, // 右对齐更美观 + softWrap: true, // 允许换行 + overflow: TextOverflow.visible, // 显示全部内容(无省略号) + + style: const TextStyle( + fontSize: 14, + color: Colors.black87, + ), + ), + ), + ), + const SizedBox(height: 4), // 文本和复制图标间距 + // 复制图标单独一行 + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: deviceId)); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('设备ID已复制到剪贴板'), + duration: Duration(seconds: 1), + ), + ); + } + }, + child: const Icon( + Icons.copy, + size: 16, + color: Color(0xFF999999), + ), + ), + + //_buildInfoRow( + // context: context, // 关键:补上必填的context参数 + // icon: Icons.info_outline, + // label: '激活时间', + // trailing: Row( + // mainAxisSize: MainAxisSize.min, // 防止Row撑满宽度 + // children: [ + // // 显示真实设备名称 + // Text(activeTime), + // const SizedBox(width: 8), + // const Icon( + // Icons.edit, + // size: 16, + // color: Color(0xFF007AFF), + // ), + // ], + // ), + //), ], ), ), - const Divider(height: 32, color: Color(0xFFF0F0F0)), - _buildInfoRow( - icon: Icons.date_range_outlined, - label: '激活时间', - trailing: const Text(''), - ), ], ), ); } - // 资源中心卡片 + // 资源中心卡片(无设备数据依赖) Widget _buildResourceCenterCard() { return Container( padding: const EdgeInsets.all(16), @@ -146,10 +233,7 @@ class DeviceDetailPage extends StatelessWidget { padding: EdgeInsets.only(bottom: 16), child: Text( '资源中心', - style: TextStyle( - fontSize: 14, - color: Color(0xFF999999), - ), + style: TextStyle(fontSize: 14, color: Color(0xFF999999)), ), ), _buildResourceItem('使用说明', Icons.description_outlined), @@ -160,8 +244,9 @@ class DeviceDetailPage extends StatelessWidget { ); } - // 信息行组件 + // 信息行组件(添加context必填参数) Widget _buildInfoRow({ + required BuildContext context, // 必填的context参数 required IconData icon, required String label, required Widget trailing, @@ -170,10 +255,7 @@ class DeviceDetailPage extends StatelessWidget { children: [ Icon(icon, size: 18, color: const Color(0xFF999999)), const SizedBox(width: 12), - Text( - label, - style: const TextStyle(fontSize: 16), - ), + Text(label, style: const TextStyle(fontSize: 16)), const Spacer(), trailing, ], @@ -186,10 +268,7 @@ class DeviceDetailPage extends StatelessWidget { children: [ Icon(icon, size: 18, color: const Color(0xFF007AFF)), const SizedBox(width: 12), - Text( - title, - style: const TextStyle(fontSize: 16), - ), + Text(title, style: const TextStyle(fontSize: 16)), const Spacer(), const Icon(Icons.arrow_forward_ios, size: 16, color: Color(0xFF999999)), ], @@ -213,18 +292,19 @@ class DeviceDetailPage extends StatelessWidget { // 实现解绑逻辑 showDialog( context: context, - builder: (context) => AlertDialog( + builder: (dialogContext) => AlertDialog( title: const Text('确认解绑'), - content: const Text('确定要解绑该设备吗?'), + content: Text('确定要解绑【${device?.deviceAlias ?? '该设备'}】吗?'), actions: [ TextButton( - onPressed: () => Navigator.pop(context), + onPressed: () => Navigator.pop(dialogContext), child: const Text('取消'), ), TextButton( onPressed: () { - Navigator.pop(context); - // 执行解绑操作 + Navigator.pop(dialogContext); + // 执行解绑操作(传入设备ID) + // _unbindDevice(device?.deviceName ?? ''); }, child: const Text('确认'), ), @@ -237,13 +317,15 @@ class DeviceDetailPage extends StatelessWidget { children: [ Icon(Icons.unarchive_outlined, size: 18), SizedBox(width: 12), - Text( - '解绑设备', - style: TextStyle(fontSize: 12), - ), + Text('解绑设备', style: TextStyle(fontSize: 12)), ], ), ), ); } + + // 可选:解绑设备的方法(根据你的业务实现) + // void _unbindDevice(String deviceId) { + // // 调用解绑接口的逻辑 + // } }