diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index 4bfe6ab1..eb6e2568 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -141,6 +141,8 @@ class _MapPageEnterpriseState extends State { bool _isToastShowing = false; final MapController _mapController = MapController(); + /// 全部场站高清叠加图层:初始用内置兜底,GetCapabilities 拉取成功后自动替换为动态范围 + List _stationLayers = kAllStationTileLayers; // 初始化轨迹管理器(泛型指定为LatLng) late final TracePoint _traceManager; late String workMode = "覆盖模式"; // 作业模式:默认值为"覆盖模式" @@ -274,9 +276,24 @@ class _MapPageEnterpriseState extends State { } }); + // 🌐 场站高清图层:监听动态范围更新 + 触发一次 GetCapabilities 刷新 + StationLayerRepo.instance.addListener(_onStationLayersUpdated); + StationLayerRepo.instance.refresh(); + + // 🔄 进场刷新场站数据:避免 siteMapName/默认中心沿用上次缓存(无需退回首页重新请求接口) + sl().reloadSites(); + debugPrint('✅ [MapPage] initState 完成'); } + /// GeoServer 动态范围拉取完成 → 用最新范围重建叠加图层 + void _onStationLayersUpdated() { + if (!mounted) return; + setState(() { + _stationLayers = StationLayerRepo.instance.buildLayers(); + }); + } + // 🔥 核心修复:监听路径规划应答流 void _setupPathPlanningListener() { debugPrint('🔗 [MapPage] 开始监听路径规划指令应答 (CMD: 0x01)'); @@ -1607,6 +1624,37 @@ class _MapPageEnterpriseState extends State { _mapController.move(_currentLatLng!, 17); } + /// 手动刷新高清场站图层(不动用户的作业/障碍物绘制数据): + /// ① 清 WMS 瓦片缓存(拿后端最新影像)② 重拉场站接口(更新 siteMapName 映射) + /// ③ 重拉 GetCapabilities(更新图层范围数组)④ 重建图层 ⑤ 定位到最新场站中心 + Future _onRefreshStationLayers() async { + debugPrint('🔄 [刷新图层] 开始手动刷新高清场站图层...'); + _showPageToast(message: '正在刷新高清图层...', type: ToastType.loading); + try { + await CachedWmsImage.clearTileCache(); + await sl().reloadSites(); + await StationLayerRepo.instance.refresh(); + if (!mounted) return; + setState(() { + _stationLayers = StationLayerRepo.instance.buildLayers(); + }); + final c = _getDefaultValidCenter(); + _mapController.move(c, 18); + _cancelAllToast(); + _showPageToast(message: '已刷新到最新图层', type: ToastType.success); + debugPrint('✅ [刷新图层] 完成,定位到 $c'); + } catch (e) { + debugPrint('❌ [刷新图层] 失败: $e'); + if (!mounted) return; + _cancelAllToast(); + final msg = e.toString(); + _showPageToast( + message: '刷新失败: ${msg.length > 50 ? msg.substring(0, 50) : msg}', + type: ToastType.error, + ); + } + } + // ========== 障碍物模式:完成当前障碍物绘制 ========== void _completeObstacle() { debugPrint( @@ -1716,6 +1764,7 @@ class _MapPageEnterpriseState extends State { @override void dispose() { + StationLayerRepo.instance.removeListener(_onStationLayersUpdated); _traceManager.reset(); _mapController.dispose(); _pathPlanningSub?.cancel(); // 🔥 取消订阅 @@ -2178,10 +2227,10 @@ class _MapPageEnterpriseState extends State { ), /// GeoServer 全部场站无人机高清影像叠加层(WGS84 基准,无偏移) - /// 一次性叠加所有场站图层:滑到哪个场站区域(zoom>=16)就自动显示哪个高清图 - /// 每层各自 tileBounds,框外瓦片不请求,开销极小;苏州等手动滑过去即可叠加 - /// 当前 WMS 兜底(原生 EPSG:4326);公司补配 GWC EPSG:3857 后改 geoserver_suzhou_layer.dart - ...kAllStationTileLayers, + /// 图层范围优先取自 GeoServer GetCapabilities(动态),失败回退内置兜底 + /// 滑到哪个场站区域(zoom>=16)就自动显示哪个高清图;每层各自 tileBounds,框外不请求 + /// WMS 瓦片带磁盘缓存+超时(CachedWmsImage),二次进入秒开 + ..._stationLayers, //if (isDjMapShow) DJITileLayer(stationName: _currentStation.name, mapController: _mapController), // 绘制path折线(Line模式) @@ -4542,6 +4591,37 @@ class _MapPageEnterpriseState extends State { ), ), + /// 悬浮“刷新高清图层”按钮:后端改了图层/影像/场站映射后, + /// 点它强制拉最新(清瓦片缓存 + 重拉场站接口 + 重拉 GetCapabilities + 重新定位) + Positioned( + top: 110, + left: 10, + child: GestureDetector( + onTap: _onRefreshStationLayers, + 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), + ), + ], + ), + alignment: Alignment.center, + child: const Icon( + Icons.refresh, + color: Colors.black87, + size: 22, + ), + ), + ), + ), + /// 核心:地图正中心固定定位标 if (_currentRobotMode == RobotMode.point) Positioned( @@ -4900,18 +4980,22 @@ class _MapPageEnterpriseState extends State { .deleteWorkRecord( deletedPlot.plotName, ); - final userId = - context - .read() + // 🔥 修复:删除后按“当前全局场站”重查,与打开列表(onListBox)走同一接口 selectBySiteId + // 原逻辑用 loadWorkRecords(userId) 走 selectByUserId(按登录用户查), + // 与列表口径不一致,导致删除后重查的数据不对;重新点列表才恢复正常。 + final selectedSite = + sl() .state - .user - ?.userId ?? - ""; - await context - .read() - .loadWorkRecords(userId); + .selectedSite; + if (selectedSite != null) { + await context + .read() + .loadWorkRecordsBySiteId( + selectedSite.id, + ); + } - setState(() {}); + if (mounted) setState(() {}); }); }, ), diff --git a/lib/features/v2/home/presentation/pages/home_v2_page.dart b/lib/features/v2/home/presentation/pages/home_v2_page.dart index fb9d38c8..e2b1108a 100644 --- a/lib/features/v2/home/presentation/pages/home_v2_page.dart +++ b/lib/features/v2/home/presentation/pages/home_v2_page.dart @@ -17,6 +17,7 @@ import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/power_tre import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/plant_overview_card.dart'; import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/tcp_status_indicator.dart'; import 'package:maibu_satabot_v2/components/device_status_modal.dart'; +import 'package:maibu_satabot_v2/components/agora_video_player.dart'; import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart'; import '../../../../remote_control/presentation/bloc/remote_control_cubit.dart'; @@ -31,6 +32,14 @@ class HomeV2Page extends StatefulWidget { class _HomeV2PageState extends State { late HomeV2Bloc _bloc; + // ===== 电站实时视频(声网)默认测试配置 ===== + // 弹窗打开就用这组值自动开播,输入框仍保留,可随时改完点「重新加载」 + // TODO: 接口对接后改为后端下发的真实值 + static const String _agoraAppId = '875f6494d87548e783fefbf33f598c97'; + static const String _agoraChannel = '36test-01'; + static const String _agoraToken = + '007eJxTYLhsYtaz3e2gTMKZ27JzrOs/KB3OTFaYKfGX2+/Y6zem5TMUGCzMTdPMTCxNUoAME4tUcwvjtNS0pDRj4zRTS4tkS3P55PlZDYGMDGsfyTEyMkAgiM/JYGxWklpcomtgyMAAAN9rIEI='; + @override void initState() { super.initState(); @@ -203,6 +212,7 @@ class _HomeV2PageState extends State { const SizedBox(height: 10), PlantOverviewCard( overview: state.homeData.plantOverview, + onTap: _showPlantVideo, ), const SizedBox(height: 10), WorkOrderCard(stats: state.homeData.workOrderStats), @@ -247,6 +257,19 @@ class _HomeV2PageState extends State { } } + /// 点击「电站概览」卡片 → 弹出声网视频组件 + /// + /// 默认带上测试配置自动开播,同时保留三个输入框可手动修改 + void _showPlantVideo() { + AgoraVideoPlayer.showManualInput( + context, + title: '电站实时视频', + initialAppId: _agoraAppId, + initialChannelId: _agoraChannel, + initialToken: _agoraToken, + ); + } + void _showPlantSelector() { if (_bloc.state is! HomeV2Loaded) return; diff --git a/lib/features/v2/home/presentation/widgets/plant_overview_card.dart b/lib/features/v2/home/presentation/widgets/plant_overview_card.dart index fdb6570f..261c87db 100644 --- a/lib/features/v2/home/presentation/widgets/plant_overview_card.dart +++ b/lib/features/v2/home/presentation/widgets/plant_overview_card.dart @@ -6,10 +6,21 @@ import 'package:maibu_satabot_v2/features/v2/home/domain/entities/home_entity.da class PlantOverviewCard extends StatelessWidget { final PlantOverview overview; - const PlantOverviewCard({super.key, required this.overview}); + /// 点击整张卡片的回调(如弹出电站实时视频) + final VoidCallback? onTap; + + const PlantOverviewCard({super.key, required this.overview, this.onTap}); @override Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: _buildContent(context), + ); + } + + Widget _buildContent(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/features/v2/my/data/datasources/impl/my_remote_datasource_impl.dart b/lib/features/v2/my/data/datasources/impl/my_remote_datasource_impl.dart index 59f7d0ff..c5980186 100644 --- a/lib/features/v2/my/data/datasources/impl/my_remote_datasource_impl.dart +++ b/lib/features/v2/my/data/datasources/impl/my_remote_datasource_impl.dart @@ -53,12 +53,13 @@ class MyRemoteDataSourceImpl implements MyRemoteDataSource { title: '帮助与反馈', icon: 'help', ), - MenuItemModel( - id: 'about_us', - title: '关于我们', - icon: 'info', - subTitle: 'V1.0.1', - ), + // 关于我们(已注释掉,其位置由“退出登录”占用) + // MenuItemModel( + // id: 'about_us', + // title: '关于我们', + // icon: 'info', + // subTitle: 'V1.0.1', + // ), ]; } diff --git a/lib/features/v2/my/presentation/pages/my_page.dart b/lib/features/v2/my/presentation/pages/my_page.dart index 79072d0c..b2f3e849 100644 --- a/lib/features/v2/my/presentation/pages/my_page.dart +++ b/lib/features/v2/my/presentation/pages/my_page.dart @@ -1,10 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; +import 'package:go_router/go_router.dart'; import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart'; import 'package:maibu_satabot_v2/core/localization/app_localizations.dart'; +import 'package:maibu_satabot_v2/core/router/route_paths.dart'; +import 'package:maibu_satabot_v2/core/storage/user_storage.dart'; import 'package:maibu_satabot_v2/core/theme/AppTheme.dart'; import 'package:maibu_satabot_v2/core/theme/theme_cubit.dart'; +import 'package:maibu_satabot_v2/features/auth/presentation/bloc/auth_cubit.dart'; import 'package:maibu_satabot_v2/features/v2/my/presentation/cubit/my_cubit.dart'; import 'package:maibu_satabot_v2/features/v2/my/presentation/states/my_state.dart'; import 'package:maibu_satabot_v2/features/v2/my/di/my_di.dart'; @@ -125,8 +130,13 @@ class _MyPageContent extends StatelessWidget { child: ListView.builder( shrinkWrap: true, padding: EdgeInsets.zero, - itemCount: state.menuItems.length, + // 末尾追加退出登录,占据“关于我们”原来的位置 + itemCount: state.menuItems.length + 1, itemBuilder: (context, index) { + // 退出登录(列表最后一项,在可滚动区域内避免底部溢出) + if (index == state.menuItems.length) { + return _buildLogoutButton(context); + } final item = state.menuItems[index]; // 🔥 夜间模式开关值以全局 ThemeCubit 状态为准 final displayItem = item.id == 'night_mode' @@ -221,4 +231,61 @@ class _MyPageContent extends StatelessWidget { ), ); } + + /// 退出登录(作为菜单列表最后一项,占据“关于我们”原来的位置) + Widget _buildLogoutButton(BuildContext context) { + return GestureDetector( + onTap: () { + _showLogoutDialog(context); + }, + behavior: HitTestBehavior.opaque, + child: SizedBox( + height: 56, + child: Center( + child: Text( + AppLocalizations.of(context).translate('login.logout'), + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Color(0xFFF53F3F), + ), + ), + ), + ), + ); + } + + /// 显示退出登录确认对话框 + void _showLogoutDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(AppLocalizations.of(context).translate('login.logout')), + content: Text( + AppLocalizations.of(context).translate('login.confirm_logout'), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context).translate('my_v2.cancel')), + ), + TextButton( + onPressed: () async { + Navigator.pop(context); + // 执行退出登录逻辑 + await GetIt.I().deleteUser(); + GetIt.I().logout(); + context.go(RoutePaths.login); + }, + style: TextButton.styleFrom( + foregroundColor: const Color(0xFFF53F3F), + ), + child: Text( + AppLocalizations.of(context).translate('my_v2.confirm'), + ), + ), + ], + ), + ); + } } diff --git a/lib/features/v2/my/presentation/pages/system_settings_page.dart b/lib/features/v2/my/presentation/pages/system_settings_page.dart index 0d717eee..0ed8c71d 100644 --- a/lib/features/v2/my/presentation/pages/system_settings_page.dart +++ b/lib/features/v2/my/presentation/pages/system_settings_page.dart @@ -77,9 +77,9 @@ class _SystemSettingsPageState extends State { // 版本更新 _buildUpdateSection(), const SizedBox(height: 12), - // 退出登录 - _buildLogoutButton(context), - const SizedBox(height: 20), + // 退出登录(已挪至“我的”页面最下边,此处注释掉) + // _buildLogoutButton(context), + // const SizedBox(height: 20), ], ), ); @@ -421,70 +421,70 @@ class _SystemSettingsPageState extends State { ); } - /// 退出登录按钮 - Widget _buildLogoutButton(BuildContext context) { - return GestureDetector( - onTap: () { - _showLogoutDialog(context); - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: context.appColors.cardBackground, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: context.appColors.cardShadow, - blurRadius: 8, - offset: Offset(0, 2), - ), - ], - ), - child: Center( - child: Text( - AppLocalizations.of(context).translate('login.logout'), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Color(0xFFF53F3F), - ), - ), - ), - ), - ); - } + /// 退出登录按钮(已挪至“我的”页面最下边,此处注释掉) + // Widget _buildLogoutButton(BuildContext context) { + // return GestureDetector( + // onTap: () { + // _showLogoutDialog(context); + // }, + // child: Container( + // padding: const EdgeInsets.symmetric(vertical: 16), + // decoration: BoxDecoration( + // color: context.appColors.cardBackground, + // borderRadius: BorderRadius.circular(12), + // boxShadow: [ + // BoxShadow( + // color: context.appColors.cardShadow, + // blurRadius: 8, + // offset: Offset(0, 2), + // ), + // ], + // ), + // child: Center( + // child: Text( + // AppLocalizations.of(context).translate('login.logout'), + // style: const TextStyle( + // fontSize: 16, + // fontWeight: FontWeight.w500, + // color: Color(0xFFF53F3F), + // ), + // ), + // ), + // ), + // ); + // } - /// 显示退出登录确认对话框 - void _showLogoutDialog(BuildContext context) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(AppLocalizations.of(context).translate('login.logout')), - content: Text( - AppLocalizations.of(context).translate('login.confirm_logout'), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context).translate('my_v2.cancel')), - ), - TextButton( - onPressed: () async { - Navigator.pop(context); - // 执行退出登录逻辑 - await GetIt.I().deleteUser(); - GetIt.I().logout(); - context.go(RoutePaths.login); - }, - style: TextButton.styleFrom( - foregroundColor: const Color(0xFFF53F3F), - ), - child: Text( - AppLocalizations.of(context).translate('my_v2.confirm'), - ), - ), - ], - ), - ); - } + /// 显示退出登录确认对话框(已挪至“我的”页面最下边,此处注释掉) + // void _showLogoutDialog(BuildContext context) { + // showDialog( + // context: context, + // builder: (context) => AlertDialog( + // title: Text(AppLocalizations.of(context).translate('login.logout')), + // content: Text( + // AppLocalizations.of(context).translate('login.confirm_logout'), + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(context), + // child: Text(AppLocalizations.of(context).translate('my_v2.cancel')), + // ), + // TextButton( + // onPressed: () async { + // Navigator.pop(context); + // // 执行退出登录逻辑 + // await GetIt.I().deleteUser(); + // GetIt.I().logout(); + // context.go(RoutePaths.login); + // }, + // style: TextButton.styleFrom( + // foregroundColor: const Color(0xFFF53F3F), + // ), + // child: Text( + // AppLocalizations.of(context).translate('my_v2.confirm'), + // ), + // ), + // ], + // ), + // ); + // } }