Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
laki_icu_app
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张宏
laki_icu_app
Commits
d2db426e
Commit
d2db426e
authored
Jun 24, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
11111
parent
74d50b40
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
678 additions
and
60 deletions
+678
-60
dev.json
env/dev.json
+1
-1
bluetooth_read_bloc.dart
lib/blocs/bluetooth_read/bluetooth_read_bloc.dart
+28
-3
cabin_detail_response.dart
lib/models/bo/cabin_detail_response.dart
+0
-0
cabin_repository.dart
lib/repositories/cabin_repository.dart
+38
-0
cabin_mock_data.dart
lib/repositories/mock/cabin_mock_data.dart
+84
-0
cabin_service.dart
lib/services/cabin_service.dart
+20
-0
monitoring_index_cubit.dart
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
+136
-6
monitoring_index_state.dart
lib/views/monitoring/index/cubit/monitoring_index_state.dart
+21
-0
monitoring_index_view.dart
lib/views/monitoring/index/monitoring_index_view.dart
+31
-11
monitoring_alert_card.dart
...views/monitoring/index/widgets/monitoring_alert_card.dart
+45
-39
monitoring_alert_detail_dialog.dart
...itoring/index/widgets/monitoring_alert_detail_dialog.dart
+274
-0
No files found.
env/dev.json
View file @
d2db426e
{
"APP_ENV"
:
"dev"
,
"SERVICE_URL"
:
"http://1
21.41.173.189/laki-merchant-service
"
"SERVICE_URL"
:
"http://1
92.168.112.129:8089
"
}
lib/blocs/bluetooth_read/bluetooth_read_bloc.dart
View file @
d2db426e
...
...
@@ -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
()));
}
...
...
lib/models/bo/cabin_detail_response.dart
0 → 100644
View file @
d2db426e
This diff is collapsed.
Click to expand it.
lib/repositories/cabin_repository.dart
0 → 100644
View file @
d2db426e
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
,
);
}
}
lib/repositories/mock/cabin_mock_data.dart
0 → 100644
View file @
d2db426e
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'
,
);
}
}
lib/services/cabin_service.dart
0 → 100644
View file @
d2db426e
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
);
}
}
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
View file @
d2db426e
...
...
@@ -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
();
...
...
lib/views/monitoring/index/cubit/monitoring_index_state.dart
View file @
d2db426e
...
...
@@ -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
,
];
}
lib/views/monitoring/index/monitoring_index_view.dart
View file @
d2db426e
...
...
@@ -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
...
...
lib/views/monitoring/index/widgets/monitoring_alert_card.dart
View file @
d2db426e
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
,
)
,
),
)
,
]
,
]
,
)
,
),
)
,
]
,
]
,
)
,
),
);
}
...
...
lib/views/monitoring/index/widgets/monitoring_alert_detail_dialog.dart
0 → 100644
View file @
d2db426e
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment