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
83539172
Commit
83539172
authored
Jun 24, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
22222222
parent
d6fc2a2b
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
174 additions
and
40 deletions
+174
-40
dialog_alert_bg.png
lib/assets/monitoring/dialog_alert_bg.png
+0
-0
camera_alarm_bo.dart
lib/models/bo/camera_alarm_bo.dart
+75
-0
monitoring_index_cubit.dart
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
+52
-1
monitoring_index_state.dart
lib/views/monitoring/index/cubit/monitoring_index_state.dart
+7
-0
monitoring_index_view.dart
lib/views/monitoring/index/monitoring_index_view.dart
+6
-1
monitoring_alert_detail_dialog.dart
...itoring/index/widgets/monitoring_alert_detail_dialog.dart
+34
-38
No files found.
lib/assets/monitoring/dialog_alert_bg.png
0 → 100644
View file @
83539172
This diff is collapsed.
Click to expand it.
lib/models/bo/camera_alarm_bo.dart
0 → 100644
View file @
83539172
/// MQTT 摄像头告警消息数据模型
/// 对应 topic: camera/{cabinSn}/to 中 type="abnormal_behavior" 的消息
///
/// 与 Android 项目 qisuanfa_0621 MqttManager.processControlCommand() 保持一致:
/// - 只提取 behaviorCode 和 timestamp 两个字段用于标题映射
/// - 其余字段保留解析,方便后续扩展
class
CameraAlarmBO
{
/// 事件唯一标识(UUID 去横线),用于去重
final
String
eventId
;
/// 仓 SN
final
String
deviceId
;
/// 设备类型,固定 "camera"
final
String
deviceType
;
/// 毫秒时间戳
final
int
timestamp
;
/// 消息类型,如 "abnormal_behavior"
final
String
type
;
/// 告警 ID
final
int
alarmId
;
/// 告警类别
final
String
alarmType
;
/// 行为编码
final
int
?
behaviorCode
;
/// 行为描述
final
String
?
behaviorDesc
;
/// 宠物名称
final
String
?
petName
;
const
CameraAlarmBO
({
required
this
.
eventId
,
required
this
.
deviceId
,
required
this
.
deviceType
,
required
this
.
timestamp
,
required
this
.
type
,
required
this
.
alarmId
,
required
this
.
alarmType
,
this
.
behaviorCode
,
this
.
behaviorDesc
,
this
.
petName
,
});
factory
CameraAlarmBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
CameraAlarmBO
(
eventId:
json
[
'eventId'
]
as
String
?
??
''
,
deviceId:
json
[
'deviceId'
]
as
String
?
??
''
,
deviceType:
json
[
'deviceType'
]
as
String
?
??
''
,
timestamp:
json
[
'timestamp'
]
as
int
?
??
0
,
type:
json
[
'type'
]
as
String
?
??
''
,
alarmId:
json
[
'alarmId'
]
as
int
?
??
0
,
alarmType:
json
[
'alarmType'
]
as
String
?
??
''
,
behaviorCode:
json
[
'behaviorCode'
]
as
int
?,
behaviorDesc:
json
[
'behaviorDesc'
]
as
String
?,
petName:
json
[
'petName'
]
as
String
?,
);
}
/// 与 Android 行为编码映射完全一致:
/// 2 → 异常液体
/// 3 → 检测到排泄物
/// 其他 → 未知异常
String
get
titleByBehaviorCode
{
if
(
behaviorCode
==
2
)
return
'异常液体'
;
if
(
behaviorCode
==
3
)
return
'检测到排泄物'
;
return
'未知异常'
;
}
}
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
View file @
83539172
...
...
@@ -5,6 +5,7 @@ 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/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'
;
...
...
@@ -50,6 +51,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
bool
_isMqttConnecting
=
false
;
String
?
_mqttDeviceSn
;
/// 已收到的摄像头告警 eventId 集合,用于去重
final
Set
<
String
>
_seenAlarmEventIds
=
{};
/// 记录已请求过舱详情的 SN,避免每个 BLE 数据帧都重复请求接口
String
?
_lastFetchedCabinSn
;
bool
_isFetchingCabinDetail
=
false
;
...
...
@@ -563,7 +567,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
mqttMessage:
'MQTT 已断开'
,
bluetoothMessage:
'蓝牙设备已解绑'
,
latestBluetoothRawHex:
''
,
cameraAlerts:
const
[],
));
_seenAlarmEventIds
.
clear
();
_isManualUnbindingBluetooth
=
false
;
}
...
...
@@ -673,7 +679,8 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
await
_mqttClient
!.
connect
();
_mqttClient
!
..
subscribeIcuCommands
(
deviceSn
)
..
subscribePetBind
(
deviceSn
);
..
subscribePetBind
(
deviceSn
)
..
subscribe
(
LakiMqttTopics
.
cameraTo
(
deviceSn
));
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
...
...
@@ -711,6 +718,12 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_mqttMessageSub
=
client
.
messageStream
.
listen
((
message
)
{
if
(
isClosed
)
return
;
final
json
=
message
.
json
;
if
(
json
!=
null
&&
message
.
topic
==
LakiMqttTopics
.
cameraTo
(
_mqttDeviceSn
??
''
))
{
_handleCameraAlarmMessage
(
json
);
return
;
}
emit
(
state
.
copyWith
(
mqttMessage:
'MQTT 收到
${message.topic}
:
${message.payload}
'
,
));
...
...
@@ -722,6 +735,44 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
});
}
/// 处理摄像头告警消息(与 Android qisuanfa_0621 逻辑保持一致)
///
/// 仅处理 type == "abnormal_behavior" 的消息,
/// 按 behaviorCode 映射标题:2→异常液体、3→检测到排泄物、其他→未知异常
void
_handleCameraAlarmMessage
(
Map
<
String
,
dynamic
>
json
)
{
if
(
json
[
'type'
]
!=
'abnormal_behavior'
)
return
;
final
alarm
=
CameraAlarmBO
.
fromJson
(
json
);
// 按 eventId 去重
if
(
_seenAlarmEventIds
.
contains
(
alarm
.
eventId
))
return
;
_seenAlarmEventIds
.
add
(
alarm
.
eventId
);
final
timeStr
=
_formatTimestamp
(
alarm
.
timestamp
);
final
title
=
alarm
.
titleByBehaviorCode
;
final
alert
=
AlertInfoBO
(
title:
title
,
description:
title
,
time:
timeStr
,
status:
'待处理'
,
);
emit
(
state
.
copyWith
(
cameraAlerts:
[
alert
,
...
state
.
cameraAlerts
],
));
}
/// 毫秒时间戳 → HH:mm:ss 格式(与 Android 一致)
String
_formatTimestamp
(
int
milliseconds
)
{
final
dateTime
=
DateTime
.
fromMillisecondsSinceEpoch
(
milliseconds
,
isUtc:
true
);
final
local
=
dateTime
.
toLocal
();
return
'
${local.hour.toString().padLeft(2, '0')}
:'
'
${local.minute.toString().padLeft(2, '0')}
:'
'
${local.second.toString().padLeft(2, '0')}
'
;
}
void
_publishMqttMonitorReport
(
McuReport
report
)
{
final
client
=
_mqttClient
;
if
(
client
==
null
||
!
client
.
isConnected
||
report
.
sn
.
isEmpty
)
{
...
...
lib/views/monitoring/index/cubit/monitoring_index_state.dart
View file @
83539172
...
...
@@ -77,6 +77,9 @@ class MonitoringIndexState extends Equatable {
/// 设备 WiFi 密码(从舱详情接口 /cabin/detail 获取)
final
String
?
cabinWifiPwd
;
/// MQTT 实时推送的摄像头告警列表(来自 camera/{sn}/to)
final
List
<
AlertInfoBO
>
cameraAlerts
;
/// 当前选中的告警(用于弹窗显示)
final
AlertInfoBO
?
selectedAlert
;
...
...
@@ -87,6 +90,7 @@ class MonitoringIndexState extends Equatable {
this
.
metrics
=
const
[],
this
.
patientInfo
,
this
.
alerts
=
const
[],
this
.
cameraAlerts
=
const
[],
this
.
videoConnectionState
=
WebrtcConnectionState
.
disconnected
,
this
.
videoStreamMode
=
VideoStreamMode
.
p2p
,
this
.
isSwitchingMode
=
false
,
...
...
@@ -116,6 +120,7 @@ class MonitoringIndexState extends Equatable {
List
<
MonitoringMetricBO
>?
metrics
,
PatientInfoBO
?
patientInfo
,
List
<
AlertInfoBO
>?
alerts
,
List
<
AlertInfoBO
>?
cameraAlerts
,
WebrtcConnectionState
?
videoConnectionState
,
VideoStreamMode
?
videoStreamMode
,
bool
?
isSwitchingMode
,
...
...
@@ -148,6 +153,7 @@ class MonitoringIndexState extends Equatable {
metrics:
metrics
??
this
.
metrics
,
patientInfo:
clearPatientInfo
?
null
:
patientInfo
??
this
.
patientInfo
,
alerts:
alerts
??
this
.
alerts
,
cameraAlerts:
cameraAlerts
??
this
.
cameraAlerts
,
videoConnectionState:
videoConnectionState
??
this
.
videoConnectionState
,
videoStreamMode:
videoStreamMode
??
this
.
videoStreamMode
,
isSwitchingMode:
isSwitchingMode
??
this
.
isSwitchingMode
,
...
...
@@ -188,6 +194,7 @@ class MonitoringIndexState extends Equatable {
metrics
,
patientInfo
,
alerts
,
cameraAlerts
,
videoConnectionState
,
videoStreamMode
,
isSwitchingMode
,
...
...
lib/views/monitoring/index/monitoring_index_view.dart
View file @
83539172
...
...
@@ -129,7 +129,12 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
children:
[
Expanded
(
flex:
10
,
child:
MonitoringAlertCard
(
alerts:
state
.
alerts
),
child:
MonitoringAlertCard
(
alerts:
[
...
state
.
cameraAlerts
,
...
state
.
alerts
,
],
),
),
SizedBox
(
width:
10
.
w
),
Expanded
(
...
...
lib/views/monitoring/index/widgets/monitoring_alert_detail_dialog.dart
View file @
83539172
...
...
@@ -23,21 +23,15 @@ class MonitoringAlertDetailDialog extends StatelessWidget {
Widget
_buildDialogContent
(
BuildContext
context
)
{
return
Container
(
width:
1300
.
w
,
height:
800
.
h
,
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
,
image:
const
DecorationImage
(
image:
AssetImage
(
'lib/assets/monitoring/dialog_alert_bg.png'
),
fit:
BoxFit
.
fill
,
),
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
,
...
...
@@ -227,34 +221,36 @@ class MonitoringAlertDetailDialog extends StatelessWidget {
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
,
return
GestureDetector
(
onTap:
onPressed
,
child:
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
,
),
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
,
child:
Center
(
child:
Text
(
text
,
style:
TextStyle
(
color:
textColor
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
bold
,
),
),
),
),
...
...
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