Commit d2db426e authored by 张宏's avatar 张宏

11111

parent 74d50b40
{
"APP_ENV": "dev",
"SERVICE_URL": "http://121.41.173.189/laki-merchant-service"
"SERVICE_URL": "http://192.168.112.129:8089"
}
......@@ -38,9 +38,34 @@ class BluetoothReadBloc extends Bloc<BluetoothReadEvent, BluetoothReadState> {
) async {
emit(state.copyWith(isLoading: true, clearError: true));
try {
final info = await _storageService.getBluetoothReadInfo();
Log.warn('**************sn************: ${info.sn}');
emit(state.copyWith(info: info, isLoading: false, clearError: true));
BluetoothReadModel? info;
const maxRetries = 5;
const retryInterval = Duration(seconds: 10);
for (int i = 0; i < maxRetries; i++) {
try {
info = await _storageService.getBluetoothReadInfo();
Log.warn(
'**************sn 第${i + 1}次获取************: ${info.sn}');
if (info.hasSn) {
Log.warn('**************sn 获取成功,停止重试************');
break;
}
} catch (e) {
Log.warn('**************sn 第${i + 1}次获取失败: $e************');
}
// 如果还没获取到sn且不是最后一次,则等待后重试
if (i < maxRetries - 1 && (info == null || !info.hasSn)) {
await Future.delayed(retryInterval);
}
}
emit(state.copyWith(
info: info ?? BluetoothReadModel.empty,
isLoading: false,
clearError: true,
));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
......
This diff is collapsed.
import 'mock/cabin_mock_data.dart';
import 'package:laki_icu_app/models/bo/cabin_detail_response.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 CabinRepository {
CabinRepository._();
static final CabinRepository instance = CabinRepository._();
/// 获取舱详情
///
/// [carbinSn] 舱序列号(设备 SN)
Future<ResponseModel<CabinDetailResponse>> getCabinDetail(
String carbinSn,
) {
return DioRequest.instance.get<CabinDetailResponse>(
'/icu/detail',
queryParameters: {'carbinSn': carbinSn},
fromJsonT: (data) =>
CabinDetailResponse.fromJson(data as Map<String, dynamic>),
);
}
Future<ResponseModel<CabinDetailResponse>> _mockCabinDetail() async {
await Future.delayed(const Duration(milliseconds: 300));
return ResponseModel<CabinDetailResponse>(
code: 200,
msg: '获取舱详情成功(mock)',
data: CabinMockData.getMockCabinDetail(),
uuid: 'mock_uuid_${DateTime.now().millisecondsSinceEpoch}',
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
}
import '../../models/bo/cabin_detail_response.dart';
/// 舱详情模块本地 mock 数据。
///
/// 所有 mock 数据直接使用 models 层构造函数创建,
/// 确保数据结构与 models 定义完全一致。
/// 后续接入真实接口时,只需修改 Repository 层开关即可。
class CabinMockData {
CabinMockData._();
static CabinDetailResponse getMockCabinDetail() {
return CabinDetailResponse(
pageTitle: 'ICU 监护舱',
deviceId: 1001,
deviceName: 'TSAAS ICU 设备',
cabinId: 1,
cabinName: '1号舱',
cabinType: 'ICU',
carbinSn: 'VE10085871QOXG',
cameraSn: 'VE10085871QOXG',
wifiPwd: '143548',
wifiName: 'TSAAS-ICU-WiFi',
petInfo: PetInfoResponse(
petId: 1,
name: '妮蔻',
avatar: '',
type: '猫',
breedTag: '长毛三花',
disease: '胃炎',
ownerName: '张先生',
ownerPhone: '130-1111-3333',
basicInfo: '3岁 雌性 4.5kg',
petName: '妮蔻',
petType: '猫',
monitoringStatus: 'monitoring',
),
cameraInfo: CameraInfoResponse(
cameraId: 1,
cameraName: '主摄像头',
sn: 'VE10085871QOXG',
cameraPwd: '143548',
status: 'online',
streamUrl: '',
),
monitoringData: MonitoringDataResponse(
totalDuration: 7200,
elapsedDuration: 3600,
status: 'monitoring',
careLevel: '特级',
planId: 1,
planName: '标准监护方案',
planType: 'standard',
temperatureSetting: 25,
mainTemperature: 24,
humidity: 55,
humiditySetting: 50,
oxygenConcentration: 21,
oxygenSetting: 20,
co2Measurement: 400,
co2Setting: 500,
),
cabinControls: CabinControlsResponse(
temperature: 25.0,
oxygen: 21.0,
humidity: 55.0,
co2: 400.0,
nebulizer: 0,
sterilize: 0,
fanMode: 'auto',
airMode: 'cycle',
newFan: true,
openOxygen: false,
checkLight: false,
light: true,
blueLight: false,
redLight: false,
),
currentPlanId: 1,
currentPetId: 1,
status: 'occupied',
state: 'normal',
);
}
}
import 'package:laki_icu_app/models/bo/cabin_detail_response.dart';
import 'package:laki_icu_app/repositories/cabin_repository.dart';
class CabinService {
final CabinRepository _repository;
CabinService() : _repository = CabinRepository.instance;
/// 获取舱详情
///
/// [carbinSn] 舱序列号(设备 SN)
/// 返回 [CabinDetailResponse],包含 cameraSn、wifiPwd、petInfo 等
Future<CabinDetailResponse> getCabinDetail(String carbinSn) async {
final result = await _repository.getCabinDetail(carbinSn);
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
......@@ -3,7 +3,9 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/enums/video_stream_mode_enum.dart';
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/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';
......@@ -16,11 +18,11 @@ import 'package:laki_icu_app/utils/storage/storage_service.dart';
import 'monitoring_index_state.dart';
/// 设备凭据 —— Mock 阶段使用占位值,后续接口接入后替换
/// TODO: 从舱详情接口获取真实的 cameraSn 和 wifiPwd
/// 设备凭据 —— 从舱详情接口获取 cameraSn 和 wifiPwd
class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final MonitoringService _monitoringService;
final CabinService _cabinService;
final WebrtcService _webrtcService;
final P2pVideoService _p2pVideoService;
final StorageService _storageService;
......@@ -47,12 +49,17 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
bool _isMqttConnecting = false;
String? _mqttDeviceSn;
/// 记录已请求过舱详情的 SN,避免每个 BLE 数据帧都重复请求接口
String? _lastFetchedCabinSn;
bool _isFetchingCabinDetail = false;
/// 连接代际计数器 —— 每次切换模式时 +1,
/// 防止旧连接的异步结果污染当前模式的状态。
int _switchGen = 0;
MonitoringIndexCubit()
: _monitoringService = MonitoringService(),
_cabinService = CabinService(),
_webrtcService = WebrtcService(),
_p2pVideoService = P2pVideoService(),
_storageService = StorageService(),
......@@ -141,6 +148,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
if (bluetoothReadInfo.hasSn) {
await _storageService.saveBluetoothReadSn(bluetoothReadInfo.sn!);
await _connectMqttForDeviceSn(bluetoothReadInfo.sn!);
await _fetchCabinDetailAndConnectVideo(bluetoothReadInfo.sn!);
}
eventBus.emit(
BluetoothReadInfoChangedEvent(bluetoothReadInfo),
......@@ -216,6 +224,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
);
}
_connectMqttForDeviceSn(sn);
_fetchCabinDetailAndConnectVideo(sn);
}
}
if (deviceId != null && deviceId.isNotEmpty) {
......@@ -270,10 +279,22 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
}
/// 初始化 WebRTC 视频连接
///
/// 使用从舱详情接口获取的 [cabinCameraSn] 和 [cabinWifiPwd],
/// 凭证未就绪时跳过连接。
Future<void> _initWebrtc() async {
final cameraSn = state.cabinCameraSn;
final wifiPwd = state.cabinWifiPwd;
if (cameraSn == null || cameraSn.isEmpty ||
wifiPwd == null || wifiPwd.isEmpty) {
debugPrint('[MonitoringIndexCubit] WebRTC 凭证未就绪,跳过连接');
return;
}
await _webrtcService.connect(
deviceNo: 'VE10085871QOXG',
password: '143548',
deviceNo: cameraSn,
password: wifiPwd,
);
}
......@@ -283,11 +304,73 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
}
/// 初始化 P2P 视频连接
///
/// 使用从舱详情接口获取的 [cabinCameraSn] 和 [cabinWifiPwd],
/// 凭证未就绪时跳过连接。
Future<void> _initP2p() async {
final cameraSn = state.cabinCameraSn;
final wifiPwd = state.cabinWifiPwd;
if (cameraSn == null || cameraSn.isEmpty ||
wifiPwd == null || wifiPwd.isEmpty) {
debugPrint('[MonitoringIndexCubit] P2P 凭证未就绪,跳过连接');
return;
}
await _p2pVideoService.connect(
deviceId: 'VE10085871QOXG',
deviceId: cameraSn,
username: 'admin',
password: '143548',
password: wifiPwd,
);
}
/// 根据舱 SN 获取舱详情,并将摄像头凭证和宠物信息同步到 State
///
/// 成功后自动触发视频连接(按当前 [VideoStreamMode] 方式)。
/// 同一 SN 只会请求一次,避免 BLE 数据帧高频回调导致重复调用接口。
Future<void> _fetchCabinDetailAndConnectVideo(String carbinSn) async {
// 相同 SN 已请求过则跳过,避免每个 BLE 数据帧都触发一次接口调用
if (_lastFetchedCabinSn == carbinSn) return;
// 上一次请求尚未完成则跳过
if (_isFetchingCabinDetail) return;
_isFetchingCabinDetail = true;
_lastFetchedCabinSn = carbinSn;
try {
final detail = await _cabinService.getCabinDetail(carbinSn);
if (isClosed) return;
emit(state.copyWith(
cabinCameraSn: detail.cameraSn,
cabinWifiPwd: detail.wifiPwd,
patientInfo: detail.petInfo != null
? _mapPetInfoToPatientInfo(detail.petInfo!)
: state.patientInfo,
));
// 凭证就绪后发起视频连接
_initVideoByMode();
} catch (e) {
if (isClosed) return;
debugPrint('[MonitoringIndexCubit] 获取舱详情失败: $e');
} finally {
_isFetchingCabinDetail = false;
}
}
/// 将 [PetInfoResponse] 映射为 [PatientInfoBO]
PatientInfoBO _mapPetInfoToPatientInfo(PetInfoResponse pet) {
return PatientInfoBO(
name: (pet.petName ?? pet.name) ?? '',
type: (pet.petType ?? pet.type) ?? '',
phone: pet.ownerPhone ?? '',
breed: pet.breedTag ?? '',
doctor: '',
avatarUrl: pet.avatar ?? '',
disease: pet.disease ?? '',
checkInDate: '',
careLevel: '',
);
}
......@@ -440,6 +523,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
await _storageService.deleteBluetoothReadInfo();
_isMqttReconnectEnabled = false;
_mqttDeviceSn = null;
_lastFetchedCabinSn = null;
await _disposeMqttClient();
eventBus.emit(const BluetoothReadInfoClearedEvent());
_mcuReportDecoder.clear();
......@@ -766,6 +850,52 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
/// 获取 P2P 视频播放控制器供 UI 层使用
P2pVideoService get p2pVideoService => _p2pVideoService;
/// 选中告警(用于打开详情弹窗)
void selectAlert(AlertInfoBO alert) {
if (isClosed) return;
emit(state.copyWith(selectedAlert: alert));
}
/// 清除选中的告警(用于关闭弹窗)
void clearSelectedAlert() {
if (isClosed) return;
emit(state.copyWith(selectedAlert: null));
}
/// 标记告警为已处理
void markAlertAsProcessed(AlertInfoBO alert) {
if (isClosed) return;
final updatedAlerts = state.alerts.map((item) {
if (item.title == alert.title && item.time == alert.time) {
return AlertInfoBO(
title: item.title,
description: item.description,
time: item.time,
status: '已处理',
);
}
return item;
}).toList();
emit(state.copyWith(alerts: updatedAlerts, selectedAlert: null));
}
/// 标记告警为误报
void markAlertAsFalse(AlertInfoBO alert) {
if (isClosed) return;
final updatedAlerts = state.alerts.map((item) {
if (item.title == alert.title && item.time == alert.time) {
return AlertInfoBO(
title: item.title,
description: item.description,
time: item.time,
status: '误报',
);
}
return item;
}).toList();
emit(state.copyWith(alerts: updatedAlerts, selectedAlert: null));
}
@override
Future<void> close() {
_cancelBluetoothReconnect();
......
......@@ -71,6 +71,15 @@ class MonitoringIndexState extends Equatable {
/// 当前 MQTT 连接使用的设备 SN
final String? mqttDeviceSn;
/// 设备摄像头序列号(从舱详情接口 /cabin/detail 获取)
final String? cabinCameraSn;
/// 设备 WiFi 密码(从舱详情接口 /cabin/detail 获取)
final String? cabinWifiPwd;
/// 当前选中的告警(用于弹窗显示)
final AlertInfoBO? selectedAlert;
const MonitoringIndexState({
this.status = MonitoringIndexStatus.initial,
this.isLoading = false,
......@@ -95,6 +104,9 @@ class MonitoringIndexState extends Equatable {
this.mqttConnectionState = LakiMqttConnectionState.disconnected,
this.mqttMessage,
this.mqttDeviceSn,
this.cabinCameraSn,
this.cabinWifiPwd,
this.selectedAlert,
});
MonitoringIndexState copyWith({
......@@ -121,6 +133,9 @@ class MonitoringIndexState extends Equatable {
LakiMqttConnectionState? mqttConnectionState,
String? mqttMessage,
String? mqttDeviceSn,
String? cabinCameraSn,
String? cabinWifiPwd,
AlertInfoBO? selectedAlert,
bool clearBoundBluetoothDevice = false,
bool clearLatestMcuReport = false,
bool clearPatientInfo = false,
......@@ -159,6 +174,9 @@ class MonitoringIndexState extends Equatable {
mqttMessage: mqttMessage ?? this.mqttMessage,
mqttDeviceSn:
clearMqttDeviceSn ? null : mqttDeviceSn ?? this.mqttDeviceSn,
cabinCameraSn: cabinCameraSn ?? this.cabinCameraSn,
cabinWifiPwd: cabinWifiPwd ?? this.cabinWifiPwd,
selectedAlert: selectedAlert ?? this.selectedAlert,
);
}
......@@ -187,5 +205,8 @@ class MonitoringIndexState extends Equatable {
mqttConnectionState,
mqttMessage,
mqttDeviceSn,
cabinCameraSn,
cabinWifiPwd,
selectedAlert,
];
}
......@@ -13,6 +13,7 @@ import 'cubit/monitoring_index_cubit.dart';
import 'cubit/monitoring_index_state.dart';
import 'widgets/bluetooth_bind_dialog.dart';
import 'widgets/monitoring_alert_card.dart';
import 'widgets/monitoring_alert_detail_dialog.dart';
import 'widgets/monitoring_metric_card.dart';
import 'widgets/monitoring_pet_info_card.dart';
import 'widgets/video_player_widget.dart';
......@@ -59,19 +60,26 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
@override
Widget build(BuildContext context) {
return BlocBuilder<MonitoringIndexCubit, MonitoringIndexState>(
builder: (context, state) {
if (state.isLoading && state.metrics.isEmpty) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
return BlocListener<MonitoringIndexCubit, MonitoringIndexState>(
listener: (context, state) {
if (state.selectedAlert != null) {
_showAlertDetailDialog(state.selectedAlert!);
}
if (state.status == MonitoringIndexStatus.failure &&
state.metrics.isEmpty) {
return _buildError(state.error);
}
return _buildMainArea(state);
},
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);
}
return _buildMainArea(state);
},
),
);
}
......@@ -243,6 +251,18 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
);
}
void _showAlertDetailDialog(AlertInfoBO alert) {
showDialog<void>(
context: context,
builder: (_) => BlocProvider.value(
value: _monitoringIndexCubit,
child: MonitoringAlertDetailDialog(alert: alert),
),
).then((_) {
_monitoringIndexCubit.clearSelectedAlert();
});
}
List<MonitoringMetricBO> _environmentMetrics(
List<MonitoringMetricBO> metrics) {
final matched = metrics
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/views/monitoring/index/cubit/monitoring_index_cubit.dart';
class MonitoringAlertCard extends StatelessWidget {
final List<AlertInfoBO> alerts;
......@@ -73,53 +75,57 @@ class MonitoringAlertCard extends StatelessWidget {
itemBuilder: (context, index) {
final item = alerts[index];
final pending = item.status.contains('待处理');
return _buildAlertItem(item, pending);
return _buildAlertItem(context, item, pending);
},
);
}
Widget _buildAlertItem(AlertInfoBO item, bool pending) {
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildIconBlock(pending),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.title,
style: TextStyle(
color: pending ? const Color(0xFFF26522) : const Color(0xFFFFC14A),
fontSize: 28.sp,
fontWeight: FontWeight.bold,
Widget _buildAlertItem(BuildContext context, AlertInfoBO item, bool pending) {
return InkWell(
onTap: () => context.read<MonitoringIndexCubit>().selectAlert(item),
borderRadius: BorderRadius.circular(16.r),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildIconBlock(pending),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.title,
style: TextStyle(
color: pending ? const Color(0xFFF26522) : const Color(0xFFFFC14A),
fontSize: 28.sp,
fontWeight: FontWeight.bold,
),
),
),
SizedBox(height: 8.h),
Text(
item.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 23.sp,
SizedBox(height: 8.h),
Text(
item.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 23.sp,
),
),
),
// SizedBox(height: 10.h),
Text(
item.time,
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
// SizedBox(height: 10.h),
Text(
item.time,
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
),
),
),
],
],
),
),
),
],
],
),
),
);
}
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/views/monitoring/index/cubit/monitoring_index_cubit.dart';
class MonitoringAlertDetailDialog extends StatelessWidget {
final AlertInfoBO alert;
const MonitoringAlertDetailDialog({
super.key,
required this.alert,
});
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.zero,
child: _buildDialogContent(context),
);
}
Widget _buildDialogContent(BuildContext context) {
return Container(
margin: EdgeInsets.fromLTRB(100.w, 100.h, 100.w, 100.h),
decoration: BoxDecoration(
color: const Color(0xFF0A1929),
border: Border.all(
color: const Color(0xFF00D4FF),
width: 3.w,
),
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: const Color(0xFF00D4FF).withValues(alpha: 77),
blurRadius: 20.r,
offset: const Offset(0, 0),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
SizedBox(height: 30.h),
_buildContent(context),
SizedBox(height: 40.h),
_buildBottomButtons(context),
],
),
);
}
Widget _buildHeader() {
return Container(
padding: EdgeInsets.fromLTRB(40.w, 24.h, 40.w, 24.h),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color(0xFF00D4FF),
width: 2,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'告警详情',
style: TextStyle(
color: const Color(0xFF00D4FF),
fontSize: 36.sp,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
Widget _buildContent(BuildContext context) {
return Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildImagePlaceholder(),
SizedBox(width: 30.w),
Expanded(
child: _buildAlertInfo(),
),
],
),
),
);
}
Widget _buildImagePlaceholder() {
return Container(
width: 280.w,
height: 200.h,
decoration: BoxDecoration(
color: const Color(0xFF1A2332),
borderRadius: BorderRadius.circular(12.r),
),
);
}
Widget _buildAlertInfo() {
final isPending = alert.status.contains('待处理');
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
alert.title,
style: TextStyle(
color: const Color(0xFFFFC14A),
fontSize: 34.sp,
fontWeight: FontWeight.bold,
),
),
Text(
alert.time,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
),
),
],
),
SizedBox(height: 20.h),
_buildInfoRow('处理状态', isPending ? '待处理' : '已处理', isPending),
_buildInfoRow('处理状态', isPending ? '待处理' : '已处理', isPending),
_buildInfoRow('处理时间', '', false),
SizedBox(height: 30.h),
Expanded(
child: SingleChildScrollView(
child: Text(
alert.description,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
height: 1.5,
),
),
),
),
],
);
}
Widget _buildInfoRow(String label, String value, bool isHighlight) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
children: [
SizedBox(
width: 140.w,
child: Text(
label,
style: TextStyle(
color: Colors.white.withValues(alpha: 153),
fontSize: 26.sp,
),
),
),
SizedBox(width: 20.w),
Text(
value,
style: TextStyle(
color: isHighlight ? const Color(0xFFFFC14A) : Colors.white,
fontSize: 26.sp,
fontWeight: isHighlight ? FontWeight.bold : FontWeight.normal,
),
),
],
),
);
}
Widget _buildBottomButtons(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(40.w, 20.h, 40.w, 30.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildButton(
'关闭窗口',
const Color(0xFF2A2E3B),
Colors.white,
const Color(0xFF4A4E5B),
() => Navigator.of(context).pop(),
),
SizedBox(width: 30.w),
_buildButton(
'标记误报',
const Color(0xFF2A2E3B),
Colors.white,
const Color(0xFF4A4E5B),
() => _handleMarkAsFalse(context),
),
SizedBox(width: 30.w),
_buildButton(
'已处理',
const Color(0xFF00D4FF),
Colors.black,
const Color(0xFF00D4FF),
() => _handleMarkAsProcessed(context),
isActive: true,
),
],
),
);
}
Widget _buildButton(
String text,
Color backgroundColor,
Color textColor,
Color borderColor,
VoidCallback onPressed, {
bool isActive = false,
}) {
return Container(
width: 220.w,
height: 72.h,
decoration: BoxDecoration(
color: backgroundColor,
border: Border.all(
color: borderColor,
width: 2.w,
),
borderRadius: BorderRadius.circular(8.r),
boxShadow: isActive
? [
BoxShadow(
color: borderColor.withValues(alpha: 128),
blurRadius: 10.r,
offset: const Offset(0, 0),
),
]
: null,
),
child: TextButton(
onPressed: onPressed,
child: Text(
text,
style: TextStyle(
color: textColor,
fontSize: 28.sp,
fontWeight: FontWeight.bold,
),
),
),
);
}
void _handleMarkAsFalse(BuildContext context) {
context.read<MonitoringIndexCubit>().markAlertAsFalse(alert);
Navigator.of(context).pop();
}
void _handleMarkAsProcessed(BuildContext context) {
context.read<MonitoringIndexCubit>().markAlertAsProcessed(alert);
Navigator.of(context).pop();
}
}
\ No newline at end of file
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