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
14b0b287
Commit
14b0b287
authored
Jun 23, 2026
by
akari
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 添加中控页
parent
5c739763
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
990 additions
and
252 deletions
+990
-252
frame_button_324x164.png
lib/assets/monitoring/frame_button_324x164.png
+0
-0
frame_button_arrows_561x637.png
lib/assets/monitoring/frame_button_arrows_561x637.png
+0
-0
frame_card_blue_325x509.png
lib/assets/monitoring/frame_card_blue_325x509.png
+0
-0
frame_card_green_325x372.png
lib/assets/monitoring/frame_card_green_325x372.png
+0
-0
frame_panel_1042x2048.png
lib/assets/monitoring/frame_panel_1042x2048.png
+0
-0
frame_panel_674x509.png
lib/assets/monitoring/frame_panel_674x509.png
+0
-0
controller_index_cubit.dart
...s/monitoring/controller/cubit/controller_index_cubit.dart
+13
-0
controller_index_state.dart
...s/monitoring/controller/cubit/controller_index_state.dart
+20
-0
monitoring_controller_view.dart
...ews/monitoring/controller/monitoring_controller_view.dart
+58
-0
monitoring_environment_detail_view.dart
...ontroller/widgets/monitoring_environment_detail_view.dart
+756
-0
monitoring_index_view.dart
lib/views/monitoring/index/monitoring_index_view.dart
+28
-1
bluetooth_bind_dialog.dart
...views/monitoring/index/widgets/bluetooth_bind_dialog.dart
+1
-139
monitoring_metric_card.dart
...iews/monitoring/index/widgets/monitoring_metric_card.dart
+114
-112
No files found.
lib/assets/monitoring/frame_button_324x164.png
0 → 100644
View file @
14b0b287
222 KB
lib/assets/monitoring/frame_button_arrows_561x637.png
0 → 100644
View file @
14b0b287
297 KB
lib/assets/monitoring/frame_card_blue_325x509.png
0 → 100644
View file @
14b0b287
9.47 KB
lib/assets/monitoring/frame_card_green_325x372.png
0 → 100644
View file @
14b0b287
7.31 KB
lib/assets/monitoring/frame_panel_1042x2048.png
0 → 100644
View file @
14b0b287
68.4 KB
lib/assets/monitoring/frame_panel_674x509.png
0 → 100644
View file @
14b0b287
282 KB
lib/views/monitoring/controller/cubit/controller_index_cubit.dart
0 → 100644
View file @
14b0b287
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'controller_index_state.dart'
;
class
ControllerIndexCubit
extends
Cubit
<
ControllerIndexState
>
{
ControllerIndexCubit
({
required
String
selectedMetricLabel
,
})
:
super
(
ControllerIndexState
(
selectedMetricLabel:
selectedMetricLabel
));
void
selectMetric
(
String
label
)
{
emit
(
state
.
copyWith
(
selectedMetricLabel:
label
));
}
}
lib/views/monitoring/controller/cubit/controller_index_state.dart
0 → 100644
View file @
14b0b287
import
'package:equatable/equatable.dart'
;
class
ControllerIndexState
extends
Equatable
{
final
String
selectedMetricLabel
;
const
ControllerIndexState
({
required
this
.
selectedMetricLabel
,
});
ControllerIndexState
copyWith
({
String
?
selectedMetricLabel
,
})
{
return
ControllerIndexState
(
selectedMetricLabel:
selectedMetricLabel
??
this
.
selectedMetricLabel
,
);
}
@override
List
<
Object
?>
get
props
=>
[
selectedMetricLabel
];
}
lib/views/monitoring/controller/monitoring_controller_view.dart
0 → 100644
View file @
14b0b287
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:laki_icu_app/models/bo/monitoring_bo.dart'
;
import
'cubit/controller_index_cubit.dart'
;
import
'cubit/controller_index_state.dart'
;
import
'widgets/monitoring_environment_detail_view.dart'
;
class
MonitoringControllerView
extends
StatelessWidget
{
const
MonitoringControllerView
({
super
.
key
,
required
this
.
metrics
,
required
this
.
selectedMetricLabel
,
required
this
.
onBack
,
});
final
List
<
MonitoringMetricBO
>
metrics
;
final
String
selectedMetricLabel
;
final
VoidCallback
onBack
;
@override
Widget
build
(
BuildContext
context
)
{
return
BlocProvider
(
create:
(
_
)
=>
ControllerIndexCubit
(
selectedMetricLabel:
selectedMetricLabel
,
),
child:
ControllerIndexContent
(
metrics:
metrics
,
onBack:
onBack
,
),
);
}
}
class
ControllerIndexContent
extends
StatelessWidget
{
const
ControllerIndexContent
({
super
.
key
,
required
this
.
metrics
,
required
this
.
onBack
,
});
final
List
<
MonitoringMetricBO
>
metrics
;
final
VoidCallback
onBack
;
@override
Widget
build
(
BuildContext
context
)
{
return
BlocBuilder
<
ControllerIndexCubit
,
ControllerIndexState
>(
builder:
(
context
,
state
)
{
return
MonitoringEnvironmentDetailView
(
metrics:
metrics
,
selectedLabel:
state
.
selectedMetricLabel
,
onBack:
onBack
,
onMetricSelected:
context
.
read
<
ControllerIndexCubit
>().
selectMetric
,
);
},
);
}
}
lib/views/monitoring/controller/widgets/monitoring_environment_detail_view.dart
0 → 100644
View file @
14b0b287
import
'package:flutter/material.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:laki_icu_app/models/bo/monitoring_bo.dart'
;
const
_assetMetricBlue
=
'lib/assets/monitoring/box_bg_325x372.png'
;
const
_assetMetricGreen
=
'lib/assets/monitoring/frame_card_green_325x372.png'
;
const
_assetPanelWide
=
'lib/assets/monitoring/frame_button_324x164.png'
;
const
_assetPanelLarge
=
'lib/assets/monitoring/frame_panel_674x509.png'
;
const
_assetPanelTall
=
'lib/assets/monitoring/box_bg_460x905.png'
;
const
_assetButton
=
'lib/assets/monitoring/frame_button_324x164.png'
;
const
_assetArrowButton
=
'lib/assets/monitoring/frame_button_arrows_561x637.png'
;
class
MonitoringEnvironmentDetailView
extends
StatelessWidget
{
const
MonitoringEnvironmentDetailView
({
super
.
key
,
required
this
.
metrics
,
required
this
.
selectedLabel
,
required
this
.
onBack
,
required
this
.
onMetricSelected
,
});
final
List
<
MonitoringMetricBO
>
metrics
;
final
String
selectedLabel
;
final
VoidCallback
onBack
;
final
ValueChanged
<
String
>
onMetricSelected
;
@override
Widget
build
(
BuildContext
context
)
{
return
Row
(
children:
[
Expanded
(
flex:
74
,
child:
Column
(
children:
[
Expanded
(
flex:
42
,
child:
Row
(
children:
[
for
(
int
index
=
0
;
index
<
metrics
.
length
;
index
++)
...[
Expanded
(
child:
_MetricControlCard
(
metric:
metrics
[
index
])),
if
(
index
<
metrics
.
length
-
1
)
SizedBox
(
width:
24
.
w
),
],
],
),
),
SizedBox
(
height:
24
.
h
),
Expanded
(
flex:
58
,
child:
Row
(
children:
[
Expanded
(
flex:
48
,
child:
_buildModePanel
()),
SizedBox
(
width:
24
.
w
),
Expanded
(
flex:
23
,
child:
_buildWindPanel
()),
SizedBox
(
width:
24
.
w
),
Expanded
(
flex:
29
,
child:
_buildTimePanel
()),
],
),
),
],
),
),
SizedBox
(
width:
24
.
w
),
Expanded
(
flex:
26
,
child:
Column
(
children:
[
Expanded
(
flex:
21
,
child:
_buildCarePanel
()),
SizedBox
(
height:
24
.
h
),
const
Expanded
(
flex:
20
,
child:
_InfoPanel
(
title:
'新风进化'
,
actionText:
'Auto'
,
icon:
Icons
.
cyclone
,
body:
'CO₂超过设定值时自动启动外循环
\n
净化,当降至2000ppm后,本
\n
功能自动关闭'
,
),
),
SizedBox
(
height:
24
.
h
),
const
Expanded
(
flex:
20
,
child:
_InfoPanel
(
title:
'开放供氧'
,
actionText:
'Off'
,
icon:
Icons
.
air
,
body:
'开启后风扇打开、制冷、加热和
\n
循环等设备暂停。'
,
warning:
'*保持设备周围空气相对禁止,严禁烟火'
,
),
),
SizedBox
(
height:
24
.
h
),
Expanded
(
flex:
26
,
child:
_buildOxygenTimerPanel
()),
],
),
),
],
);
}
Widget
_buildModePanel
()
{
const
items
=
[
_ModeItem
(
label:
'外循环'
,
icon:
Icons
.
sync
,
active:
true
),
_ModeItem
(
label:
'检查灯'
,
icon:
Icons
.
light
),
_ModeItem
(
label:
'照明灯'
,
icon:
Icons
.
lightbulb
),
_ModeItem
(
label:
'内循环'
,
icon:
Icons
.
autorenew
),
_ModeItem
(
label:
'蓝光'
,
icon:
Icons
.
wb_sunny_outlined
),
_ModeItem
(
label:
'红光'
,
icon:
Icons
.
wb_sunny
),
];
return
_PanelFrame
(
backgroundAsset:
_assetPanelLarge
,
padding:
EdgeInsets
.
all
(
28
.
w
),
child:
GridView
.
count
(
crossAxisCount:
3
,
crossAxisSpacing:
24
.
w
,
mainAxisSpacing:
24
.
h
,
physics:
const
NeverScrollableScrollPhysics
(),
childAspectRatio:
1.1
,
children:
[
for
(
final
item
in
items
)
_ModeButton
(
label:
item
.
label
,
icon:
item
.
icon
,
active:
item
.
active
,
),
],
),
);
}
Widget
_buildWindPanel
()
{
return
_PanelFrame
(
backgroundAsset:
_assetPanelTall
,
padding:
EdgeInsets
.
fromLTRB
(
34
.
w
,
28
.
h
,
34
.
w
,
28
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
_PanelTitle
(
'风速调节'
),
SizedBox
(
height:
24
.
h
),
const
Expanded
(
child:
_WideModeButton
(
label:
'睡眠模式'
,
icon:
Icons
.
nightlight_round
),
),
SizedBox
(
height:
18
.
h
),
const
Expanded
(
child:
_WideModeButton
(
label:
'舒适模式'
,
icon:
Icons
.
favorite_border
),
),
SizedBox
(
height:
18
.
h
),
const
Expanded
(
child:
_WideModeButton
(
label:
'强劲模式'
,
icon:
Icons
.
air
)),
],
),
);
}
Widget
_buildTimePanel
()
{
return
_PanelFrame
(
backgroundAsset:
_assetPanelTall
,
padding:
EdgeInsets
.
fromLTRB
(
34
.
w
,
28
.
h
,
34
.
w
,
28
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
_PanelTitle
(
'设置雾化时间'
),
SizedBox
(
height:
18
.
h
),
const
Expanded
(
child:
_DigitalValueBox
(
value:
'00'
,
unit:
'分钟'
)),
Divider
(
color:
Colors
.
white
.
withValues
(
alpha:
0.16
),
height:
36
.
h
),
const
_PanelTitle
(
'设置消毒时间'
),
SizedBox
(
height:
18
.
h
),
const
Expanded
(
child:
_DigitalValueBox
(
value:
'14'
,
unit:
'分钟'
,
active:
true
,
),
),
],
),
);
}
Widget
_buildCarePanel
()
{
return
_PanelFrame
(
backgroundAsset:
_assetPanelWide
,
padding:
EdgeInsets
.
fromLTRB
(
30
.
w
,
26
.
h
,
30
.
w
,
24
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
_PanelTitle
(
'护理等级'
),
const
Spacer
(),
Container
(
height:
20
.
h
,
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
10
.
r
),
gradient:
const
LinearGradient
(
colors:
[
Color
(
0xFFDCEFFC
),
Color
(
0xFF71FFCF
),
Color
(
0xFFE4CF1D
),
Color
(
0xFFFFA11B
),
Color
(
0xFFFF5945
),
Color
(
0xFFFFFFFF
),
],
),
),
),
SizedBox
(
height:
24
.
h
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
for
(
final
item
in
const
[
'关闭'
,
'三级'
,
'二级'
,
'一级'
,
'特级'
])
Text
(
item
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
24
.
sp
),
),
],
),
],
),
);
}
Widget
_buildOxygenTimerPanel
()
{
return
_PanelFrame
(
backgroundAsset:
_assetPanelWide
,
padding:
EdgeInsets
.
fromLTRB
(
30
.
w
,
26
.
h
,
30
.
w
,
28
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
_PanelTitle
(
'供氧计时'
),
const
Spacer
(),
Row
(
children:
[
Text
(
'00:00:00'
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
42
.
sp
,
letterSpacing:
2
,
fontWeight:
FontWeight
.
w500
,
),
),
const
Spacer
(),
const
_Pill
(
text:
'清零'
),
],
),
Divider
(
color:
Colors
.
white
.
withValues
(
alpha:
0.16
),
height:
38
.
h
),
Text
(
'ICU舱设置重置'
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w600
,
),
),
const
Spacer
(),
const
_BigActionButton
(
text:
'初始化设置'
),
],
),
);
}
}
class
_MetricControlCard
extends
StatelessWidget
{
const
_MetricControlCard
({
required
this
.
metric
});
final
MonitoringMetricBO
metric
;
@override
Widget
build
(
BuildContext
context
)
{
final
isOxygen
=
metric
.
label
.
contains
(
'氧气'
);
final
isCo2
=
metric
.
label
.
contains
(
'二氧化碳'
)
||
metric
.
label
.
toUpperCase
().
contains
(
'CO'
);
return
_PanelFrame
(
active:
isOxygen
,
backgroundAsset:
isOxygen
?
_assetMetricGreen
:
_assetMetricBlue
,
padding:
EdgeInsets
.
fromLTRB
(
32
.
w
,
26
.
h
,
32
.
w
,
28
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
children:
[
Expanded
(
child:
_PanelTitle
(
_title
(
metric
.
label
))),
_Pill
(
text:
isOxygen
?
'ON'
:
isCo2
?
'PPM'
:
'Off'
,
active:
isOxygen
),
],
),
const
Spacer
(),
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
end
,
children:
[
Text
(
metric
.
value
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
isCo2
?
60
.
sp
:
82
.
sp
,
height:
0.9
,
fontWeight:
FontWeight
.
bold
,
),
),
SizedBox
(
width:
12
.
w
),
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
8
.
h
),
child:
Text
(
isCo2
?
''
:
metric
.
unit
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w600
,
),
),
),
const
Spacer
(),
Image
.
asset
(
_iconAsset
(
metric
.
label
),
width:
72
.
w
,
height:
72
.
w
,
fit:
BoxFit
.
contain
,
),
],
),
Divider
(
color:
Colors
.
white
.
withValues
(
alpha:
0.18
),
height:
28
.
h
),
Text
(
isCo2
?
'告警阈值设定'
:
metric
.
label
.
contains
(
'温度'
)
?
'温度设定'
:
'湿度设定'
,
style:
TextStyle
(
color:
Colors
.
white
.
withValues
(
alpha:
0.78
),
fontSize:
26
.
sp
),
),
const
Spacer
(),
Row
(
children:
[
_AdjustText
(
'-'
,
active:
isOxygen
||
!
isCo2
),
const
Spacer
(),
Text
(
isCo2
?
'4000'
:
isOxygen
?
'90'
:
metric
.
label
.
contains
(
'湿度'
)
?
'10'
:
'24'
,
style:
TextStyle
(
color:
isCo2
?
const
Color
(
0xFFFFC400
)
:
const
Color
(
0xFF00B7F4
),
fontSize:
64
.
sp
,
height:
1
,
fontWeight:
FontWeight
.
bold
,
),
),
const
Spacer
(),
const
_AdjustText
(
'+'
,
active:
true
),
],
),
],
),
);
}
static
String
_title
(
String
label
)
{
if
(
label
.
contains
(
'二氧化碳'
))
return
'二氧化碳浓度'
;
return
label
;
}
}
class
_InfoPanel
extends
StatelessWidget
{
const
_InfoPanel
({
required
this
.
title
,
required
this
.
actionText
,
required
this
.
icon
,
required
this
.
body
,
this
.
warning
,
});
final
String
title
;
final
String
actionText
;
final
IconData
icon
;
final
String
body
;
final
String
?
warning
;
@override
Widget
build
(
BuildContext
context
)
{
return
_PanelFrame
(
backgroundAsset:
_assetPanelWide
,
padding:
EdgeInsets
.
fromLTRB
(
30
.
w
,
22
.
h
,
30
.
w
,
24
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
children:
[
Expanded
(
child:
_PanelTitle
(
title
)),
_Pill
(
text:
actionText
),
],
),
const
Spacer
(),
Row
(
children:
[
Icon
(
icon
,
color:
const
Color
(
0xFF6AC6F2
),
size:
54
.
w
),
SizedBox
(
width:
28
.
w
),
Expanded
(
child:
Text
(
body
,
style:
TextStyle
(
color:
const
Color
(
0xFFC9F8FF
),
fontSize:
20
.
sp
,
height:
1.35
,
),
),
),
],
),
if
(
warning
!=
null
)
...[
SizedBox
(
height:
8
.
h
),
Text
(
warning
!,
style:
TextStyle
(
color:
const
Color
(
0xFFFFC400
),
fontSize:
16
.
sp
),
),
],
],
),
);
}
}
class
_ModeButton
extends
StatelessWidget
{
const
_ModeButton
({
required
this
.
label
,
required
this
.
icon
,
this
.
active
=
false
,
});
final
String
label
;
final
IconData
icon
;
final
bool
active
;
@override
Widget
build
(
BuildContext
context
)
{
final
color
=
active
?
const
Color
(
0xFF45F36F
)
:
const
Color
(
0xFF7DC4EA
);
return
Container
(
decoration:
_buttonDecoration
(
active:
active
,
backgroundAsset:
_assetArrowButton
,
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
.
w
,
vertical:
18
.
h
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Icon
(
icon
,
color:
color
,
size:
56
.
w
),
SizedBox
(
height:
20
.
h
),
Text
(
label
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
TextStyle
(
color:
color
,
fontSize:
26
.
sp
,
fontWeight:
FontWeight
.
w700
,
),
),
],
),
);
}
}
class
_WideModeButton
extends
StatelessWidget
{
const
_WideModeButton
({
required
this
.
label
,
required
this
.
icon
,
});
final
String
label
;
final
IconData
icon
;
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
width:
double
.
infinity
,
decoration:
_buttonDecoration
(
backgroundAsset:
_assetButton
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
28
.
w
),
child:
Row
(
children:
[
Icon
(
icon
,
color:
const
Color
(
0xFF7DC4EA
),
size:
52
.
w
),
SizedBox
(
width:
34
.
w
),
Expanded
(
child:
Text
(
label
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
26
.
sp
,
fontWeight:
FontWeight
.
w600
,
),
),
),
],
),
);
}
}
class
_DigitalValueBox
extends
StatelessWidget
{
const
_DigitalValueBox
({
required
this
.
value
,
required
this
.
unit
,
this
.
active
=
false
,
});
final
String
value
;
final
String
unit
;
final
bool
active
;
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
width:
double
.
infinity
,
decoration:
_buttonDecoration
(
active:
active
,
backgroundAsset:
_assetButton
,
),
alignment:
Alignment
.
center
,
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Text
(
value
,
style:
TextStyle
(
color:
active
?
const
Color
(
0xFFC8FFFF
)
:
Colors
.
white
,
fontSize:
46
.
sp
,
letterSpacing:
2
,
fontWeight:
FontWeight
.
w500
,
),
),
SizedBox
(
width:
28
.
w
),
Text
(
unit
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
30
.
sp
,
fontWeight:
FontWeight
.
w600
,
),
),
],
),
);
}
}
class
_BigActionButton
extends
StatelessWidget
{
const
_BigActionButton
({
required
this
.
text
});
final
String
text
;
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
width:
double
.
infinity
,
height:
64
.
h
,
decoration:
_buttonDecoration
(
backgroundAsset:
_assetButton
),
alignment:
Alignment
.
center
,
child:
Text
(
'《《
$text
》》'
,
style:
TextStyle
(
color:
const
Color
(
0xFFC9F8FF
),
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w700
,
),
),
);
}
}
class
_PanelFrame
extends
StatelessWidget
{
const
_PanelFrame
({
required
this
.
child
,
required
this
.
padding
,
this
.
active
=
false
,
this
.
backgroundAsset
,
});
final
Widget
child
;
final
EdgeInsetsGeometry
padding
;
final
bool
active
;
final
String
?
backgroundAsset
;
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
width:
double
.
infinity
,
height:
double
.
infinity
,
padding:
padding
,
decoration:
BoxDecoration
(
color:
backgroundAsset
==
null
?
active
?
const
Color
(
0xFF003F14
).
withValues
(
alpha:
0.92
)
:
const
Color
(
0xFF061C34
).
withValues
(
alpha:
0.92
)
:
null
,
image:
backgroundAsset
==
null
?
null
:
DecorationImage
(
image:
AssetImage
(
backgroundAsset
!),
fit:
BoxFit
.
fill
,
),
border:
backgroundAsset
==
null
?
Border
.
all
(
color:
active
?
const
Color
(
0xFF3EFF5F
)
:
const
Color
(
0xFF89DFFF
),
width:
3
.
w
,
)
:
null
,
borderRadius:
BorderRadius
.
circular
(
8
.
r
),
boxShadow:
[
BoxShadow
(
color:
(
active
?
const
Color
(
0xFF3EFF5F
)
:
const
Color
(
0xFF4FC8FF
))
.
withValues
(
alpha:
0.32
),
blurRadius:
18
.
r
,
spreadRadius:
2
.
r
,
),
],
),
child:
child
,
);
}
}
class
_PanelTitle
extends
StatelessWidget
{
const
_PanelTitle
(
this
.
text
);
final
String
text
;
@override
Widget
build
(
BuildContext
context
)
{
return
Text
(
text
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w700
,
),
);
}
}
class
_Pill
extends
StatelessWidget
{
const
_Pill
({
required
this
.
text
,
this
.
active
=
false
,
});
final
String
text
;
final
bool
active
;
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
height:
40
.
h
,
constraints:
BoxConstraints
(
minWidth:
90
.
w
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
18
.
w
),
alignment:
Alignment
.
center
,
decoration:
BoxDecoration
(
color:
Colors
.
white
.
withValues
(
alpha:
active
?
0.14
:
0.06
),
border:
Border
.
all
(
color:
active
?
const
Color
(
0xFF52FFBB
)
:
const
Color
(
0xFF75C1DD
),
width:
1
.
w
,
),
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
),
child:
Text
(
text
,
style:
TextStyle
(
color:
active
?
const
Color
(
0xFF52FFBB
)
:
const
Color
(
0xFF75C1DD
),
fontSize:
22
.
sp
,
fontWeight:
FontWeight
.
w700
,
),
),
);
}
}
class
_AdjustText
extends
StatelessWidget
{
const
_AdjustText
(
this
.
text
,
{
this
.
active
=
false
});
final
String
text
;
final
bool
active
;
@override
Widget
build
(
BuildContext
context
)
{
return
Text
(
text
,
style:
TextStyle
(
color:
active
?
const
Color
(
0xFF20E5FF
)
:
const
Color
(
0xFF4B7191
),
fontSize:
58
.
sp
,
height:
1
,
fontWeight:
FontWeight
.
w700
,
),
);
}
}
class
_ModeItem
{
final
String
label
;
final
IconData
icon
;
final
bool
active
;
const
_ModeItem
({
required
this
.
label
,
required
this
.
icon
,
this
.
active
=
false
,
});
}
BoxDecoration
_buttonDecoration
(
{
bool
active
=
false
,
String
?
backgroundAsset
})
{
return
BoxDecoration
(
color:
backgroundAsset
==
null
?
active
?
const
Color
(
0xFF063F1A
).
withValues
(
alpha:
0.96
)
:
const
Color
(
0xFF0A243F
).
withValues
(
alpha:
0.96
)
:
null
,
image:
backgroundAsset
==
null
?
null
:
DecorationImage
(
image:
AssetImage
(
backgroundAsset
),
fit:
BoxFit
.
fill
,
),
border:
backgroundAsset
==
null
?
Border
.
all
(
color:
active
?
const
Color
(
0xFF2DF765
)
:
const
Color
(
0xFF66BDE8
),
width:
2
,
)
:
null
,
borderRadius:
BorderRadius
.
circular
(
6
),
);
}
String
_iconAsset
(
String
label
)
{
if
(
label
.
contains
(
'温度'
))
{
return
'lib/assets/monitoring/cabin_temperature.png'
;
}
if
(
label
.
contains
(
'湿度'
))
{
return
'lib/assets/monitoring/cabin_humidity.png'
;
}
if
(
label
.
contains
(
'氧气'
))
{
return
'lib/assets/monitoring/oxygen_concentration.png'
;
}
return
'lib/assets/monitoring/carbon_dioxide_concentration.png'
;
}
lib/views/monitoring/index/monitoring_index_view.dart
View file @
14b0b287
...
...
@@ -8,6 +8,7 @@ import 'package:laki_icu_app/enums/video_stream_mode_enum.dart';
import
'package:laki_icu_app/models/bo/monitoring_bo.dart'
;
import
'package:laki_icu_app/utils/event_bus.dart'
;
import
'../controller/monitoring_controller_view.dart'
;
import
'cubit/monitoring_index_cubit.dart'
;
import
'cubit/monitoring_index_state.dart'
;
import
'widgets/bluetooth_bind_dialog.dart'
;
...
...
@@ -39,6 +40,7 @@ class MonitoringIndexContent extends StatefulWidget {
class
_MonitoringIndexContentState
extends
State
<
MonitoringIndexContent
>
{
late
final
MonitoringIndexCubit
_monitoringIndexCubit
;
StreamSubscription
<
OpenBluetoothScanEvent
>?
_bluetoothScanEventSub
;
String
?
_selectedEnvironmentMetricLabel
;
@override
void
initState
()
{
...
...
@@ -69,6 +71,9 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
state
.
metrics
.
isEmpty
)
{
return
_buildError
(
state
.
error
);
}
if
(
_selectedEnvironmentMetricLabel
!=
null
)
{
return
_buildEnvironmentDetail
(
state
);
}
return
_buildMainArea
(
state
);
},
);
...
...
@@ -154,7 +159,10 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
for
(
int
i
=
0
;
i
<
displayMetrics
.
length
;
i
++)
{
children
.
add
(
Expanded
(
child:
MonitoringMetricCard
(
metric:
displayMetrics
[
i
]),
child:
MonitoringMetricCard
(
metric:
displayMetrics
[
i
],
onTap:
()
=>
_openEnvironmentDetail
(
displayMetrics
[
i
]),
),
),
);
if
(
i
<
displayMetrics
.
length
-
1
)
{
...
...
@@ -166,6 +174,25 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
);
}
Widget
_buildEnvironmentDetail
(
MonitoringIndexState
state
)
{
final
metrics
=
_environmentMetrics
(
state
.
metrics
);
return
MonitoringControllerView
(
metrics:
metrics
,
selectedMetricLabel:
_selectedEnvironmentMetricLabel
!,
onBack:
()
{
setState
(()
{
_selectedEnvironmentMetricLabel
=
null
;
});
},
);
}
void
_openEnvironmentDetail
(
MonitoringMetricBO
metric
)
{
setState
(()
{
_selectedEnvironmentMetricLabel
=
metric
.
label
;
});
}
/// 模式切换回调
void
_onToggleStreamMode
()
{
final
cubit
=
_monitoringIndexCubit
;
...
...
lib/views/monitoring/index/widgets/bluetooth_bind_dialog.dart
View file @
14b0b287
...
...
@@ -127,7 +127,6 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> {
_buildPanelStatus
(
context
,
state
),
SizedBox
(
height:
28
.
h
),
Expanded
(
child:
_buildDeviceList
(
context
,
state
)),
_buildLatestData
(
state
),
],
),
);
...
...
@@ -138,7 +137,7 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> {
?
'自动发现附近可配对的监护舱设备,搜索中...'
:
state
.
bluetoothDevices
.
isEmpty
?
'未发现设备,可重新搜索附近蓝牙设备'
:
'
自动发现附近可配对的监护舱
设备'
;
:
'
蓝牙扫描结束,共发现
${state.bluetoothDevices.length}
个
设备'
;
return
Row
(
children:
[
...
...
@@ -266,143 +265,6 @@ class _BluetoothBindDialogState extends State<BluetoothBindDialog> {
await
context
.
read
<
MonitoringIndexCubit
>().
unbindBluetoothDevice
();
}
}
Widget
_buildLatestData
(
MonitoringIndexState
state
)
{
final
report
=
state
.
latestMcuReport
;
final
rawHex
=
state
.
latestBluetoothRawHex
;
final
message
=
state
.
bluetoothMessage
;
if
(
report
==
null
&&
(
rawHex
==
null
||
rawHex
.
isEmpty
)
&&
(
message
==
null
||
message
.
isEmpty
))
{
return
SizedBox
(
height:
10
.
h
);
}
if
(
report
!=
null
)
{
return
_BluetoothReportSummary
(
report:
report
);
}
return
Padding
(
padding:
EdgeInsets
.
only
(
top:
18
.
h
),
child:
Text
(
rawHex
!=
null
&&
rawHex
.
isNotEmpty
?
'最新数据:
$rawHex
'
:
message
!,
style:
TextStyle
(
color:
rawHex
!=
null
&&
rawHex
.
isNotEmpty
?
const
Color
(
0xFF00F6FF
)
:
Colors
.
white
.
withValues
(
alpha:
0.66
),
fontSize:
22
.
sp
,
),
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
),
);
}
}
class
_BluetoothReportSummary
extends
StatelessWidget
{
const
_BluetoothReportSummary
({
required
this
.
report
});
final
McuReport
report
;
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
width:
double
.
infinity
,
margin:
EdgeInsets
.
only
(
top:
18
.
h
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
18
.
w
,
vertical:
14
.
h
),
decoration:
BoxDecoration
(
color:
Colors
.
black
.
withValues
(
alpha:
0.18
),
borderRadius:
BorderRadius
.
circular
(
8
.
r
),
border:
Border
.
all
(
color:
Colors
.
white
.
withValues
(
alpha:
0.12
)),
),
child:
Row
(
children:
[
Expanded
(
flex:
2
,
child:
Text
(
report
.
sn
.
isEmpty
?
'SN --'
:
'SN
${report.sn}
'
,
style:
TextStyle
(
color:
const
Color
(
0xFF00F6FF
),
fontSize:
22
.
sp
,
fontWeight:
FontWeight
.
w600
,
),
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
),
),
_BluetoothReportMetric
(
label:
'温度'
,
value:
report
.
mainTemperature
.
toStringAsFixed
(
1
),
unit:
'℃'
,
),
_BluetoothReportMetric
(
label:
'湿度'
,
value:
'
${report.humidity}
'
,
unit:
'%'
,
),
_BluetoothReportMetric
(
label:
'氧气'
,
value:
'
${report.oxygenConcentration}
'
,
unit:
'%'
,
),
_BluetoothReportMetric
(
label:
'CO2'
,
value:
'
${report.co2Measurement}
'
,
unit:
'ppm'
,
),
],
),
);
}
}
class
_BluetoothReportMetric
extends
StatelessWidget
{
const
_BluetoothReportMetric
({
required
this
.
label
,
required
this
.
value
,
required
this
.
unit
,
});
final
String
label
;
final
String
value
;
final
String
unit
;
@override
Widget
build
(
BuildContext
context
)
{
return
SizedBox
(
width:
128
.
w
,
child:
RichText
(
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
text:
TextSpan
(
children:
[
TextSpan
(
text:
'
$label
'
,
style:
TextStyle
(
color:
Colors
.
white
.
withValues
(
alpha:
0.58
),
fontSize:
20
.
sp
,
),
),
TextSpan
(
text:
value
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
23
.
sp
,
fontWeight:
FontWeight
.
w600
,
),
),
TextSpan
(
text:
unit
,
style:
TextStyle
(
color:
Colors
.
white
.
withValues
(
alpha:
0.72
),
fontSize:
19
.
sp
,
),
),
],
),
),
);
}
}
class
_BluetoothDeviceRow
extends
StatelessWidget
{
...
...
lib/views/monitoring/index/widgets/monitoring_metric_card.dart
View file @
14b0b287
...
...
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:flutter_switch/flutter_switch.dart'
;
import
'package:laki_icu_app/models/bo/monitoring_bo.dart'
;
class
MonitoringMetricCard
extends
StatelessWidget
{
final
MonitoringMetricBO
metric
;
...
...
@@ -13,151 +12,154 @@ class MonitoringMetricCard extends StatelessWidget {
/// 开关切换回调
final
ValueChanged
<
bool
>?
onSwitchToggle
;
/// 卡片点击回调
final
VoidCallback
?
onTap
;
const
MonitoringMetricCard
({
super
.
key
,
required
this
.
metric
,
this
.
switchValue
=
false
,
this
.
onSwitchToggle
,
this
.
onTap
,
});
@override
Widget
build
(
BuildContext
context
)
{
final
displayLabel
=
_displayLabel
(
metric
.
label
);
return
Container
(
decoration:
BoxDecoration
(
image:
DecorationImage
(
image:
AssetImage
(
'lib/assets/monitoring/box_bg_325x372.png'
),
fit:
BoxFit
.
fill
,
),
// border: BoxBorder.all(width: 1.w,color: Colors.red)
),
padding:
EdgeInsets
.
only
(
left:
10
.
w
,
right:
10
.
w
,
top:
54
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
children:
[
SizedBox
(
width:
32
.
w
),
Expanded
(
flex:
3
,
child:
Text
(
displayLabel
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
bold
,
),
),
),
Expanded
(
flex:
2
,
child:
Align
(
alignment:
Alignment
.
topLeft
,
child:
FlutterSwitch
(
width:
60
.
w
,
height:
30
.
h
,
value:
switchValue
,
onToggle:
onSwitchToggle
??
(
_
)
{},
activeColor:
const
Color
(
0xFF75C1DD
),
inactiveColor:
Color
(
0xFF0A1B34
),
toggleSize:
22
.
w
,
padding:
4
.
w
,
),
),
),
],
return
GestureDetector
(
onTap:
onTap
,
behavior:
HitTestBehavior
.
opaque
,
child:
Container
(
decoration:
const
BoxDecoration
(
image:
DecorationImage
(
image:
AssetImage
(
'lib/assets/monitoring/box_bg_325x372.png'
),
fit:
BoxFit
.
fill
,
),
// const Spacer(),
Container
(
padding:
EdgeInsets
.
only
(
left:
32
.
w
,
top:
24
.
h
),
child:
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
end
,
// border: BoxBorder.all(width: 1.w,color: Colors.red)
),
padding:
EdgeInsets
.
only
(
left:
10
.
w
,
right:
10
.
w
,
top:
54
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
children:
[
Text
(
metric
.
value
,
style:
TextStyle
(
color:
const
Color
(
0xFF9DF5FF
),
fontSize:
90
.
sp
,
height:
1
,
fontWeight:
FontWeight
.
bold
,
),
),
SizedBox
(
width:
18
.
w
),
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
3
.
h
),
SizedBox
(
width:
32
.
w
),
Expanded
(
flex:
3
,
child:
Text
(
metric
.
unit
,
displayLabel
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
30
.
sp
,
fontWeight:
FontWeight
.
w500
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
bold
,
),
),
),
Expanded
(
flex:
2
,
child:
Align
(
alignment:
Alignment
.
topLeft
,
child:
FlutterSwitch
(
width:
60
.
w
,
height:
30
.
h
,
value:
switchValue
,
onToggle:
onSwitchToggle
??
(
_
)
{},
activeColor:
const
Color
(
0xFF75C1DD
),
inactiveColor:
const
Color
(
0xFF0A1B34
),
toggleSize:
22
.
w
,
padding:
4
.
w
,
),
),
),
],
),
),
SizedBox
(
height:
22
.
h
),
Container
(
width:
220
.
w
,
margin:
EdgeInsets
.
only
(
left:
32
.
w
,),
decoration:
BoxDecoration
(
// border: Border.all(
// color: Colors.red,
// width: 1.w
// )
)
,
child:
Stack
(
clipBehavior:
Clip
.
none
,
children:
[
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
"设定24"
+
metric
.
unit
,
// const Spacer(),
Container
(
padding:
EdgeInsets
.
only
(
left:
32
.
w
,
top:
24
.
h
),
child:
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
end
,
children:
[
Text
(
metric
.
value
,
style:
TextStyle
(
color:
const
Color
(
0xFF9DF5FF
),
fontSize:
90
.
sp
,
height:
1
,
fontWeight:
FontWeight
.
bold
,
)
,
),
SizedBox
(
width:
18
.
w
),
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
3
.
h
),
child:
Text
(
metric
.
unit
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
24
.
sp
,
fontSize:
30
.
sp
,
fontWeight:
FontWeight
.
w500
,
),
),
Container
(
height:
5
.
h
,
margin:
EdgeInsets
.
only
(
top:
18
.
h
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
3
.
r
),
gradient:
const
LinearGradient
(
colors:
[
Color
(
0xFF20E5FF
),
Color
(
0x0020E5FF
)],
),
],
),
),
SizedBox
(
height:
22
.
h
),
Container
(
width:
220
.
w
,
margin:
EdgeInsets
.
only
(
left:
32
.
w
,
),
child:
Stack
(
clipBehavior:
Clip
.
none
,
children:
[
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
'设定24
${metric.unit}
'
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
24
.
sp
,
),
),
Container
(
height:
5
.
h
,
margin:
EdgeInsets
.
only
(
top:
18
.
h
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
3
.
r
),
gradient:
const
LinearGradient
(
colors:
[
Color
(
0xFF20E5FF
),
Color
(
0x0020E5FF
)],
),
),
),
],
),
Positioned
(
right:
-
65
.
w
,
top:
-
20
.
h
,
child:
Image
.
asset
(
_iconAsset
(
metric
.
label
),
width:
102
.
w
,
height:
102
.
w
,
fit:
BoxFit
.
contain
,
),
],
),
Positioned
(
right:
-
65
.
w
,
top:
-
20
.
h
,
child:
Image
.
asset
(
_iconAsset
(
metric
.
label
),
width:
102
.
w
,
height:
102
.
w
,
fit:
BoxFit
.
contain
,
),
)
,
]
,
]
,
)
,
),
),
Container
(
margin:
EdgeInsets
.
only
(
left:
2
.
w
),
child:
Image
.
asset
(
Container
(
margin:
EdgeInsets
.
only
(
left:
2
.
w
),
child:
Image
.
asset
(
'lib/assets/monitoring/box_bottom_char.png'
,
// width: 140.w,
// height: 60.w,
fit:
BoxFit
.
contain
,
),
)
],
)
],
),
),
);
}
...
...
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