退出登陆的重新设计到我的页面;优化高清地图的加载和显示(冷启动稍慢,再次秒开);声网通用组件遗漏文件;

This commit is contained in:
2026-09-10 09:02:23 +08:00
parent b4bedd9737
commit 85e9632326
6 changed files with 276 additions and 90 deletions

View File

@@ -141,6 +141,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
bool _isToastShowing = false;
final MapController _mapController = MapController();
/// 全部场站高清叠加图层:初始用内置兜底,GetCapabilities 拉取成功后自动替换为动态范围
List<TileLayer> _stationLayers = kAllStationTileLayers;
// 初始化轨迹管理器(泛型指定为LatLng)
late final TracePoint<PlotPoint> _traceManager;
late String workMode = "覆盖模式"; // 作业模式:默认值为"覆盖模式"
@@ -274,9 +276,24 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
});
// 🌐 场站高清图层:监听动态范围更新 + 触发一次 GetCapabilities 刷新
StationLayerRepo.instance.addListener(_onStationLayersUpdated);
StationLayerRepo.instance.refresh();
// 🔄 进场刷新场站数据:避免 siteMapName/默认中心沿用上次缓存(无需退回首页重新请求接口)
sl<SiteCubit>().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<MapPageEnterprise> {
_mapController.move(_currentLatLng!, 17);
}
/// 手动刷新高清场站图层(不动用户的作业/障碍物绘制数据):
/// ① 清 WMS 瓦片缓存(拿后端最新影像)② 重拉场站接口(更新 siteMapName 映射)
/// ③ 重拉 GetCapabilities(更新图层范围数组)④ 重建图层 ⑤ 定位到最新场站中心
Future<void> _onRefreshStationLayers() async {
debugPrint('🔄 [刷新图层] 开始手动刷新高清场站图层...');
_showPageToast(message: '正在刷新高清图层...', type: ToastType.loading);
try {
await CachedWmsImage.clearTileCache();
await sl<SiteCubit>().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<MapPageEnterprise> {
@override
void dispose() {
StationLayerRepo.instance.removeListener(_onStationLayersUpdated);
_traceManager.reset();
_mapController.dispose();
_pathPlanningSub?.cancel(); // 🔥 取消订阅
@@ -2178,10 +2227,10 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
),
/// 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<MapPageEnterprise> {
),
),
/// 悬浮“刷新高清图层”按钮:后端改了图层/影像/场站映射后,
/// 点它强制拉最新(清瓦片缓存 + 重拉场站接口 + 重拉 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<MapPageEnterprise> {
.deleteWorkRecord(
deletedPlot.plotName,
);
final userId =
context
.read<AppUserCubit>()
// 🔥 修复:删除后按“当前全局场站”重查,与打开列表(onListBox)走同一接口 selectBySiteId
// 原逻辑用 loadWorkRecords(userId) 走 selectByUserId(按登录用户查),
// 与列表口径不一致,导致删除后重查的数据不对;重新点列表才恢复正常。
final selectedSite =
sl<SiteCubit>()
.state
.user
?.userId ??
"";
await context
.read<DevicesCubit>()
.loadWorkRecords(userId);
.selectedSite;
if (selectedSite != null) {
await context
.read<DevicesCubit>()
.loadWorkRecordsBySiteId(
selectedSite.id,
);
}
setState(() {});
if (mounted) setState(() {});
});
},
),

View File

@@ -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<HomeV2Page> {
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<HomeV2Page> {
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<HomeV2Page> {
}
}
/// 点击「电站概览」卡片 → 弹出声网视频组件
///
/// 默认带上测试配置自动开播,同时保留三个输入框可手动修改
void _showPlantVideo() {
AgoraVideoPlayer.showManualInput(
context,
title: '电站实时视频',
initialAppId: _agoraAppId,
initialChannelId: _agoraChannel,
initialToken: _agoraToken,
);
}
void _showPlantSelector() {
if (_bloc.state is! HomeV2Loaded) return;

View File

@@ -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: [

View File

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

View File

@@ -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<UserStorage>().deleteUser();
GetIt.I<AuthCubit>().logout();
context.go(RoutePaths.login);
},
style: TextButton.styleFrom(
foregroundColor: const Color(0xFFF53F3F),
),
child: Text(
AppLocalizations.of(context).translate('my_v2.confirm'),
),
),
],
),
);
}
}

View File

@@ -77,9 +77,9 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
// 版本更新
_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<SystemSettingsPage> {
);
}
/// 退出登录按钮
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<UserStorage>().deleteUser();
GetIt.I<AuthCubit>().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<UserStorage>().deleteUser();
// GetIt.I<AuthCubit>().logout();
// context.go(RoutePaths.login);
// },
// style: TextButton.styleFrom(
// foregroundColor: const Color(0xFFF53F3F),
// ),
// child: Text(
// AppLocalizations.of(context).translate('my_v2.confirm'),
// ),
// ),
// ],
// ),
// );
// }
}