Commit 72d5f64e authored by 张宏's avatar 张宏

重构结构-----------------------------1

parent 00f5040c
import '../../models/bo/monitoring_bo.dart';
/// 首页监护舱模块本地 mock 数据。
///
/// 所有 mock 数据直接使用 models 层的构造函数创建,
/// 确保数据结构与 models 定义完全一致。
/// 后续接入真实接口时,只需修改 Repository 层的开关即可。
class MonitoringMockData {
MonitoringMockData._();
static List<MonitoringMetricBO> getMockMetrics() {
return const [
MonitoringMetricBO(
value: '--',
unit: '℃',
label: '舱内温度',
borderColorValue: 0xFF00FF9D,
backgroundColorValue: 0x1A00FF9D,
),
MonitoringMetricBO(
value: '--',
unit: 'RH',
label: '舱内湿度',
borderColorValue: 0xFF00B8FF,
backgroundColorValue: 0x1A00B8FF,
),
MonitoringMetricBO(
value: '--',
unit: '%',
label: '氧气浓度',
borderColorValue: 0xFF00FF9D,
backgroundColorValue: 0x1A00FF9D,
),
MonitoringMetricBO(
value: '--',
unit: '%',
label: '二氧化碳',
borderColorValue: 0xFFFF7D00,
backgroundColorValue: 0x1AFF7D00,
),
MonitoringMetricBO(
value: '特级',
unit: '',
label: '护理等级',
borderColorValue: 0xFFFF7D00,
backgroundColorValue: 0x1AFF7D00,
),
];
}
static PatientInfoBO getMockPatientInfo() {
return const PatientInfoBO(
name: '妮蔻',
type: '猫',
phone: '130-1111-3333',
breed: '长毛三花',
doctor: '张医生',
disease: '胃炎',
checkInDate: '2025-6-15',
careLevel: '特级',
avatarUrl: '',
);
}
static List<AlertInfoBO> getMockAlerts() {
return const [
AlertInfoBO(
title: '设备告警',
description: '温度高于阈值26℃,当前温度29℃',
time: '2026-05-04 16:22:49',
status: '待处理',
),
AlertInfoBO(
title: '温度异常',
description: '2023年2月,华为云集中发布了三款软件开发工具:需求管理服务 CodeArts Req、测试管理服务 CodeArts TestPlan、代码检查服务 CodeArts Check。随着 CodeArts 不断的丰富与发展,华为云在 DevSecOps 领域的布局又迈出了稳健的一步。 [22]2023年2月14日,华为云发布分布式编译构建系统 CodeArts Build,旨在支撑企业实现高效的软件开发,缩短产品上市周期,帮助企业的软件产品快速形成关键竞争力。',
time: '2026-05-04 16:22:49',
status: '已处理',
),
AlertInfoBO(
title: '温度异常',
description: '温度高于阈值26℃,当前温度29℃',
time: '2026-05-04 16:22:49',
status: '待处理',
),
AlertInfoBO(
title: '温度异常',
description: '温度高于阈值26℃,当前温度29℃,请立即检查温度传感器,字…',
time: '2026-05-04 16:22:49',
status: '已处理',
),
];
}
static List<MonitoringMenuItemBO> getMockMenuItems() {
return const [
MonitoringMenuItemBO(
id: 'icu_central',
title: 'ICU中控',
icon: '🏥',
iconPath: 'lib/assets/menu/menu1.png',
),
MonitoringMenuItemBO(
id: 'blood_pressure',
title: '血压监测',
icon: '❤️',
iconPath: 'lib/assets/menu/menu2.png',
),
MonitoringMenuItemBO(
id: 'vital_signs',
title: '体征仪',
icon: '📏',
iconPath: 'lib/assets/menu/menu3.png',
),
MonitoringMenuItemBO(
id: 'ecg_system',
title: '多导心电系统',
icon: '💓',
iconPath: 'lib/assets/menu/menu4.png',
),
MonitoringMenuItemBO(
id: 'tsaas',
title: 'TSAAS',
icon: '🔵',
iconPath: 'lib/assets/menu/menu5.png',
isTsaas: true,
),
MonitoringMenuItemBO(
id: 'record',
title: '记录',
icon: '📝',
iconPath: 'lib/assets/menu/menu6.png',
),
MonitoringMenuItemBO(
id: 'settings',
title: '设置',
icon: '⚙️',
iconPath: 'lib/assets/menu/menu7.png',
),
];
}
}
import 'mock/monitoring_mock_data.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/utils/constants.dart';
import 'package:laki_icu_app/utils/http/dio_request.dart';
import 'package:laki_icu_app/utils/http/response_model.dart';
class MonitoringRepository {
......@@ -9,104 +6,66 @@ class MonitoringRepository {
static final MonitoringRepository instance = MonitoringRepository._();
Future<ResponseModel<List<MonitoringMetricBO>>> getMetrics() {
if (Constants.useMockData) {
return _mockMetrics();
}
return DioRequest.instance.get<List<MonitoringMetricBO>>(
'/app/monitoring/metrics',
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => MonitoringMetricBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
Future<ResponseModel<PatientInfoBO>> getPatientInfo() {
if (Constants.useMockData) {
return _mockPatientInfo();
}
return DioRequest.instance.get<PatientInfoBO>(
'/app/monitoring/patient-info',
fromJsonT: (data) => PatientInfoBO.fromJson(data as Map<String, dynamic>),
);
}
Future<ResponseModel<List<AlertInfoBO>>> getAlerts() {
if (Constants.useMockData) {
return _mockAlerts();
}
return DioRequest.instance.get<List<AlertInfoBO>>(
'/app/monitoring/alerts',
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => AlertInfoBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
Future<ResponseModel<List<MonitoringMenuItemBO>>> getMenuItems() {
if (Constants.useMockData) {
return _mockMenuItems();
}
return DioRequest.instance.get<List<MonitoringMenuItemBO>>(
'/app/monitoring/menu-items',
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map(
(e) => MonitoringMenuItemBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
Future<ResponseModel<List<MonitoringMetricBO>>> _mockMetrics() async {
await Future.delayed(const Duration(milliseconds: 300));
return ResponseModel<List<MonitoringMetricBO>>(
code: 200,
msg: '获取监护指标成功(mock)',
data: MonitoringMockData.getMockMetrics(),
uuid: 'mock_uuid_${DateTime.now().millisecondsSinceEpoch}',
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
Future<ResponseModel<PatientInfoBO>> _mockPatientInfo() async {
await Future.delayed(const Duration(milliseconds: 300));
return ResponseModel<PatientInfoBO>(
code: 200,
msg: '获取患者信息成功(mock)',
data: MonitoringMockData.getMockPatientInfo(),
uuid: 'mock_uuid_${DateTime.now().millisecondsSinceEpoch}',
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
Future<ResponseModel<List<AlertInfoBO>>> _mockAlerts() async {
await Future.delayed(const Duration(milliseconds: 300));
return ResponseModel<List<AlertInfoBO>>(
code: 200,
msg: '获取告警信息成功(mock)',
data: MonitoringMockData.getMockAlerts(),
uuid: 'mock_uuid_${DateTime.now().millisecondsSinceEpoch}',
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
Future<ResponseModel<List<MonitoringMenuItemBO>>> _mockMenuItems() async {
Future<ResponseModel<List<MonitoringMenuItemBO>>> getMenuItems() async {
// return DioRequest.instance.get<List<MonitoringMenuItemBO>>(
// '/app/monitoring/menu-items',
// fromJsonT: (data) {
// final list = data as List<dynamic>;
// return list
// .map(
// (e) => MonitoringMenuItemBO.fromJson(e as Map<String, dynamic>))
// .toList();
// },
// );
await Future.delayed(const Duration(milliseconds: 300));
return ResponseModel<List<MonitoringMenuItemBO>>(
code: 200,
msg: '获取设备菜单成功(mock)',
data: MonitoringMockData.getMockMenuItems(),
data: const [
MonitoringMenuItemBO(
id: 'icu_central',
title: 'ICU中控',
icon: '🏥',
iconPath: 'lib/assets/menu/menu1.png',
),
MonitoringMenuItemBO(
id: 'blood_pressure',
title: '血压监测',
icon: '❤️',
iconPath: 'lib/assets/menu/menu2.png',
),
MonitoringMenuItemBO(
id: 'vital_signs',
title: '体征仪',
icon: '📏',
iconPath: 'lib/assets/menu/menu3.png',
),
MonitoringMenuItemBO(
id: 'ecg_system',
title: '多导心电系统',
icon: '💓',
iconPath: 'lib/assets/menu/menu4.png',
),
MonitoringMenuItemBO(
id: 'tsaas',
title: 'TSAAS',
icon: '🔵',
iconPath: 'lib/assets/menu/menu5.png',
isTsaas: true,
),
MonitoringMenuItemBO(
id: 'record',
title: '记录',
icon: '📝',
iconPath: 'lib/assets/menu/menu6.png',
),
MonitoringMenuItemBO(
id: 'settings',
title: '设置',
icon: '⚙️',
iconPath: 'lib/assets/menu/menu7.png',
),
],
uuid: 'mock_uuid_${DateTime.now().millisecondsSinceEpoch}',
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
......
......@@ -6,30 +6,6 @@ class MonitoringService {
MonitoringService() : _repository = MonitoringRepository.instance;
Future<List<MonitoringMetricBO>> getMetrics() async {
final result = await _repository.getMetrics();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
Future<PatientInfoBO> getPatientInfo() async {
final result = await _repository.getPatientInfo();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
Future<List<AlertInfoBO>> getAlerts() async {
final result = await _repository.getAlerts();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
Future<List<MonitoringMenuItemBO>> getMenuItems() async {
final result = await _repository.getMenuItems();
if (result.success && result.data != null) {
......
......@@ -73,7 +73,7 @@ class LakiMqttTopics {
static String icuPetBind(String deviceSn) => 'icu/$deviceSn/pet/bind';
static String cameraTo(String cabinSn) => 'camera/$cabinSn/to';
static String cameraTo(String cabinSn) => 'camera/$cabinSn/to'; // 获取摄像头通道的告警信息
static const String clientConnected = r'$events/client_connected';
......
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/services/monitoring_service.dart';
import 'device_control_index_state.dart';
class DeviceControlIndexCubit extends Cubit<DeviceControlIndexState> {
final MonitoringService _monitoringService;
/// 设备控制页 4 个环境指标卡的描述符。
///
/// 标签固定不变,实际展示值由 [DeviceControlBloc]
/// 从蓝牙 MCU 数据中实时更新。
const List<MonitoringMetricBO> _fixedMetrics = [
MonitoringMetricBO(value: '--', unit: '℃', label: '舱内温度'),
MonitoringMetricBO(value: '--', unit: 'RH', label: '舱内湿度'),
MonitoringMetricBO(value: '--', unit: '%', label: '氧气浓度'),
MonitoringMetricBO(value: '--', unit: 'PPM', label: '二氧化碳'),
];
DeviceControlIndexCubit({
MonitoringService? monitoringService,
}) : _monitoringService = monitoringService ?? MonitoringService(),
super(DeviceControlIndexState()) {
loadMetrics();
}
class DeviceControlIndexCubit extends Cubit<DeviceControlIndexState> {
DeviceControlIndexCubit()
: super(const DeviceControlIndexState(metrics: _fixedMetrics));
void selectMetric(String label) {
emit(state.copyWith(selectedMetricLabel: label));
}
Future<void> loadMetrics() async {
emit(state.copyWith(
status: DeviceControlIndexStatus.loading,
isLoading: true,
error: null,
));
try {
final allMetrics = await _monitoringService.getMetrics();
final envMetrics = _environmentMetrics(allMetrics);
emit(state.copyWith(
status: DeviceControlIndexStatus.success,
isLoading: false,
metrics: envMetrics,
error: null,
));
} catch (e) {
emit(state.copyWith(
status: DeviceControlIndexStatus.failure,
isLoading: false,
error: e.toString(),
));
}
}
/// 过滤出环境指标(温度/湿度/氧气/二氧化碳),最多取4个
List<MonitoringMetricBO> _environmentMetrics(
List<MonitoringMetricBO> metrics) {
final matched = metrics
.where((item) =>
item.label.contains('温度') ||
item.label.contains('湿度') ||
item.label.contains('氧气') ||
item.label.contains('二氧化碳') ||
item.label.toUpperCase().contains('CO'))
.take(4)
.toList();
if (matched.length == 4) return matched;
return const [
MonitoringMetricBO(value: '--', unit: '℃', label: '舱内温度'),
MonitoringMetricBO(value: '--', unit: 'RH', label: '舱内湿度'),
MonitoringMetricBO(value: '--', unit: '%', label: '氧气浓度'),
MonitoringMetricBO(value: '--', unit: '%', label: '二氧化碳'),
];
}
/// 指标描述符已在构造时填充,实际值由 [DeviceControlBloc] 提供。
Future<void> loadMetrics() async {}
}
......@@ -11,7 +11,7 @@ class DeviceControlIndexState extends Equatable {
final String selectedMetricLabel;
const DeviceControlIndexState({
this.status = DeviceControlIndexStatus.initial,
this.status = DeviceControlIndexStatus.success,
this.isLoading = false,
this.error,
this.metrics = const [],
......
......@@ -42,38 +42,6 @@ class DeviceControlIndexContent extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<DeviceControlIndexCubit, DeviceControlIndexState>(
builder: (context, state) {
if (state.isLoading && state.metrics.isEmpty) {
return const Scaffold(
backgroundColor: Color(0xFF010B1D),
body: Center(
child: CircularProgressIndicator(color: Colors.white),
),
);
}
if (state.status == DeviceControlIndexStatus.failure &&
state.metrics.isEmpty) {
return Scaffold(
backgroundColor: const Color(0xFF010B1D),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'加载失败: ${state.error ?? "未知错误"}',
style: TextStyle(color: Colors.white, fontSize: 22.sp),
),
SizedBox(height: 22.h),
ElevatedButton(
onPressed: () =>
context.read<DeviceControlIndexCubit>().loadMetrics(),
child: const Text('重试'),
),
],
),
),
);
}
return _DeviceControlLayout(
metrics: state.metrics,
selectedLabel: state.selectedMetricLabel,
......
......@@ -7,7 +7,6 @@ import 'package:laki_icu_app/models/bo/cabin_detail_response.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/models/bo/camera_alarm_bo.dart';
import 'package:laki_icu_app/services/cabin_service.dart';
import 'package:laki_icu_app/services/monitoring_service.dart';
import 'package:laki_icu_app/services/p2p_video_service.dart';
import 'package:laki_icu_app/services/webrtc_service.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
......@@ -22,7 +21,6 @@ import 'monitoring_index_state.dart';
/// 设备凭据 —— 从舱详情接口获取 cameraSn 和 wifiPwd
class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final MonitoringService _monitoringService;
final CabinService _cabinService;
final WebrtcService _webrtcService;
final P2pVideoService _p2pVideoService;
......@@ -64,8 +62,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
int _switchGen = 0;
MonitoringIndexCubit()
: _monitoringService = MonitoringService(),
_cabinService = CabinService(),
: _cabinService = CabinService(),
_webrtcService = WebrtcService(),
_p2pVideoService = P2pVideoService(),
_storageService = StorageService(),
......@@ -78,7 +75,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_listenBluetoothReadInfo();
_listenMcuReportChanged();
_loadBoundBluetoothDevice();
loadData();
}
/// 监听 WebRTC 连接状态变化并同步到 State
......@@ -267,40 +263,13 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
}
}
Future<void> loadData() async {
emit(state.copyWith(
status: MonitoringIndexStatus.loading,
isLoading: true,
error: null,
));
try {
// final metrics = await _monitoringService.getMetrics();
// if (isClosed) return;
final patientInfo = await _monitoringService.getPatientInfo();
if (isClosed) return;
final alerts = await _monitoringService.getAlerts();
if (isClosed) return;
emit(state.copyWith(
status: MonitoringIndexStatus.success,
isLoading: false,
patientInfo: patientInfo,
alerts: alerts,
error: null,
));
// 数据加载完成后,按当前模式初始化视频连接
_initVideoByMode();
} catch (e) {
if (isClosed) return;
emit(state.copyWith(
status: MonitoringIndexStatus.failure,
isLoading: false,
error: e.toString(),
));
}
}
/// 页面初始化入口。
///
/// 数据不再通过 HTTP 获取:
/// - 宠物信息由蓝牙 SN → /icu/detail 接口([_fetchCabinDetailAndConnectVideo])获取
/// - 告警信息由 MQTT camera/{sn}/to 推送获取
/// - 指标数据由蓝牙 MCU 上报实时填充
Future<void> loadData() async {}
/// 依据当前 [VideoStreamMode] 启动对应的视频连接
void _initVideoByMode() {
......@@ -399,11 +368,11 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
type: (pet.petType ?? pet.type) ?? '',
phone: pet.ownerPhone ?? '',
breed: pet.breedTag ?? '',
doctor: '',
doctor: '缺失字段',
avatarUrl: pet.avatar ?? '',
disease: pet.disease ?? '',
checkInDate: '',
careLevel: '',
checkInDate: '缺失字段',
careLevel: '缺失字段',
);
}
......@@ -461,29 +430,10 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
}
}
Future<void> refreshData() async {
try {
// final metrics = await _monitoringService.getMetrics();
// if (isClosed) return;
final patientInfo = await _monitoringService.getPatientInfo();
if (isClosed) return;
final alerts = await _monitoringService.getAlerts();
if (isClosed) return;
emit(state.copyWith(
status: MonitoringIndexStatus.success,
patientInfo: patientInfo,
alerts: alerts,
error: null,
));
} catch (e) {
if (isClosed) return;
emit(state.copyWith(
status: MonitoringIndexStatus.failure,
error: e.toString(),
));
}
}
/// 刷新数据。
///
/// 数据来源已切换为 BLE + MQTT 流式推送,不需要主动拉取。
Future<void> refreshData() async {}
// ==================== 蓝牙扫描/绑定 ====================
......
......@@ -84,7 +84,7 @@ class MonitoringIndexState extends Equatable {
final AlertInfoBO? selectedAlert;
const MonitoringIndexState({
this.status = MonitoringIndexStatus.initial,
this.status = MonitoringIndexStatus.success,
this.isLoading = false,
this.error,
this.metrics = const [],
......@@ -107,7 +107,7 @@ class MonitoringIndexState extends Equatable {
this.latestMcuReport,
this.mqttConnectionState = LakiMqttConnectionState.disconnected,
this.mqttMessage,
this.mqttDeviceSn,
this.mqttDeviceSn = 'CNA07212L',
this.cabinCameraSn,
this.cabinWifiPwd,
this.selectedAlert,
......
......@@ -71,11 +71,6 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
},
child: BlocBuilder<MonitoringIndexCubit, MonitoringIndexState>(
builder: (context, state) {
if (state.isLoading && state.metrics.isEmpty) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
}
if (state.status == MonitoringIndexStatus.failure &&
state.metrics.isEmpty) {
return _buildError(state.error);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment