Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
smart_hotel_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
张宏
smart_hotel_app
Commits
7ba2abc6
Commit
7ba2abc6
authored
Jun 26, 2026
by
胡曲
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix bugs
parent
b57da32b
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
747 additions
and
506 deletions
+747
-506
room_bo.dart
lib/models/bo/room_bo.dart
+19
-6
room_repository.dart
lib/repositories/room_repository.dart
+15
-11
room_service.dart
lib/services/room_service.dart
+17
-16
deal_utils.dart
lib/utils/deal_utils.dart
+30
-1
abnormal_detail_view.dart
lib/views/home/abnormal/abnormal_detail_view.dart
+1
-1
abnormal_list_cubit.dart
lib/views/home/abnormal_list/cubit/abnormal_list_cubit.dart
+44
-3
abnormal_list_state.dart
lib/views/home/abnormal_list/cubit/abnormal_list_state.dart
+7
-2
index_view.dart
lib/views/home/abnormal_list/index_view.dart
+1
-1
alarm_list_item.dart
lib/views/home/abnormal_list/widget/alarm_list_item.dart
+25
-5
index_view.dart
lib/views/home/index/index_view.dart
+70
-36
voltage_operate_block.dart
lib/views/home/index/widget/voltage_operate_block.dart
+1
-0
inspection_cubit.dart
lib/views/home/inspection/cubit/inspection_cubit.dart
+2
-3
inspection_device_cubit.dart
...home/inspection_device/cubit/inspection_device_cubit.dart
+1
-1
device_overview_card.dart
...s/home/inspection_device/widget/device_overview_card.dart
+3
-3
energy_detail_view.dart
lib/views/report/energy/energy_detail_view.dart
+1
-1
zone_chart_block.dart
lib/views/report/energy/widget/zone_chart_block.dart
+5
-3
report_index_cubit.dart
lib/views/report/index/cubit/report_index_cubit.dart
+1
-1
device_online_rate.dart
lib/views/report/index/widget/device_online_rate.dart
+10
-8
quick_actions_block.dart
lib/views/report/index/widget/quick_actions_block.dart
+8
-4
report_metrics_block.dart
lib/views/report/index/widget/report_metrics_block.dart
+22
-18
room_status_distribution.dart
lib/views/report/index/widget/room_status_distribution.dart
+9
-6
weekly_power_chart.dart
lib/views/report/index/widget/weekly_power_chart.dart
+6
-6
room_report_cubit.dart
lib/views/report/room/cubit/room_report_cubit.dart
+38
-11
floor_selector.dart
lib/views/report/room/widget/floor_selector.dart
+31
-31
room_detail_card.dart
lib/views/report/room/widget/room_detail_card.dart
+5
-8
service_index_cubit.dart
lib/views/service/index/cubit/service_index_cubit.dart
+77
-91
room_management_block.dart
lib/views/service/index/widget/room_management_block.dart
+226
-162
service_room_cubit.dart
lib/views/service/room/cubit/service_room_cubit.dart
+34
-48
room_detail_view.dart
lib/views/service/room/room_detail_view.dart
+2
-1
room_bottom_buttons.dart
lib/views/service/room/widget/room_bottom_buttons.dart
+34
-16
room_overview_card.dart
lib/views/service/room/widget/room_overview_card.dart
+2
-2
No files found.
lib/models/bo/room_bo.dart
View file @
7ba2abc6
...
...
@@ -34,11 +34,13 @@ class RoomOverviewSummaryBO extends Equatable {
/// 楼层Tab列表
class
RoomFloorBO
extends
Equatable
{
final
int
floorId
;
final
int
floorNumber
;
final
String
floorName
;
final
bool
isActive
;
const
RoomFloorBO
({
this
.
floorId
=
0
,
this
.
floorNumber
=
0
,
this
.
floorName
=
''
,
this
.
isActive
=
false
,
});
...
...
@@ -46,18 +48,20 @@ class RoomFloorBO extends Equatable {
factory
RoomFloorBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
RoomFloorBO
(
floorId:
json
[
'floorId'
]
as
int
?
??
0
,
floorNumber:
json
[
'floorNumber'
]
as
int
?
??
0
,
floorName:
json
[
'floorName'
]
as
String
?
??
''
,
isActive:
json
[
'isActive'
]
as
bool
?
??
false
,
);
}
@override
List
<
Object
?>
get
props
=>
[
floorId
,
floorName
,
isActive
];
List
<
Object
?>
get
props
=>
[
floorId
,
floorN
umber
,
floorN
ame
,
isActive
];
}
/// 房间网格(房间卡片网格布局)
class
RoomGridBO
extends
Equatable
{
final
int
roomId
;
final
int
floorNumber
;
final
String
roomNumber
;
final
String
statusText
;
final
String
statusColor
;
...
...
@@ -65,6 +69,7 @@ class RoomGridBO extends Equatable {
const
RoomGridBO
({
this
.
roomId
=
0
,
this
.
floorNumber
=
0
,
this
.
roomNumber
=
''
,
this
.
statusText
=
''
,
this
.
statusColor
=
''
,
...
...
@@ -78,11 +83,12 @@ class RoomGridBO extends Equatable {
statusText:
json
[
'statusText'
]
as
String
?
??
''
,
statusColor:
json
[
'statusColor'
]
as
String
?
??
''
,
hasAlarmIcon:
json
[
'hasAlarmIcon'
]
as
bool
?
??
false
,
floorNumber:
json
[
'floorNumber'
]
as
int
?
??
0
,
);
}
@override
List
<
Object
?>
get
props
=>
[
roomId
,
roomNumber
,
statusText
,
statusColor
,
hasAlarmIcon
];
List
<
Object
?>
get
props
=>
[
roomId
,
roomNumber
,
statusText
,
statusColor
,
hasAlarmIcon
,
floorNumber
];
}
/// 房间基础信息(客房概览统计)
...
...
@@ -90,11 +96,13 @@ class RoomOverviewBasicInfoBO extends Equatable {
final
int
roomId
;
final
String
roomNumber
;
final
String
roomStatusText
;
final
int
floorNumber
;
const
RoomOverviewBasicInfoBO
({
this
.
roomId
=
0
,
this
.
roomNumber
=
''
,
this
.
roomStatusText
=
''
,
this
.
floorNumber
=
0
,
});
factory
RoomOverviewBasicInfoBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
...
...
@@ -102,11 +110,12 @@ class RoomOverviewBasicInfoBO extends Equatable {
roomId:
json
[
'roomId'
]
as
int
?
??
0
,
roomNumber:
json
[
'roomNumber'
]
as
String
?
??
''
,
roomStatusText:
json
[
'roomStatusText'
]
as
String
?
??
''
,
floorNumber:
json
[
'floorNumber'
]
as
int
?
??
0
,
);
}
@override
List
<
Object
?>
get
props
=>
[
roomId
,
roomNumber
,
roomStatusText
];
List
<
Object
?>
get
props
=>
[
roomId
,
roomNumber
,
roomStatusText
,
floorNumber
];
}
/// 状态标签
...
...
@@ -261,11 +270,15 @@ String _parseString(dynamic value, {String fallback = ''}) {
class
FloorAreaBO
extends
Equatable
{
final
int
floorId
;
final
int
floorNumber
;
final
String
floorName
;
final
List
<
String
>
areas
;
final
List
<
FloorRoomSimpleBO
>
rooms
;
const
FloorAreaBO
({
required
this
.
floorId
,
required
this
.
floorNumber
,
required
this
.
floorName
,
required
this
.
areas
,
required
this
.
rooms
,
});
...
...
@@ -273,6 +286,8 @@ class FloorAreaBO extends Equatable {
factory
FloorAreaBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
FloorAreaBO
(
floorId:
_parseInt
(
json
[
'floorId'
]),
floorNumber:
_parseInt
(
json
[
'floorNumber'
]),
floorName:
json
[
'floorName'
]
as
String
?
??
''
,
areas:
(
json
[
'areas'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
e
as
String
)
.
toList
()
??
...
...
@@ -286,9 +301,7 @@ class FloorAreaBO extends Equatable {
}
@override
List
<
Object
?>
get
props
=>
[
floorId
,
areas
,
rooms
];
String
get
floorName
=>
'
${floorId}
楼'
;
List
<
Object
?>
get
props
=>
[
floorId
,
floorNumber
,
floorName
,
areas
,
rooms
];
}
class
FloorRoomSimpleBO
extends
Equatable
{
...
...
lib/repositories/room_repository.dart
View file @
7ba2abc6
...
...
@@ -65,27 +65,31 @@ class RoomRepository {
);
}
///
客房详情-房间总电源-开
Future
<
ResponseModel
>
roomPowerOn
({
required
int
room
Id
})
{
return
DioRequest
.
instance
.
post
(
'/app/room/
power
/on'
,
data:
{
'
roomId'
:
room
Id
},
///
单设备送电
Future
<
ResponseModel
>
powerSingleOn
({
required
int
device
Id
})
{
return
DioRequest
.
instance
.
post
<
Map
<
String
,
dynamic
>>
(
'/app/room/
device/power/single
/on'
,
data:
{
'
deviceId'
:
device
Id
},
);
}
///
客房详情-房间总电源-关
Future
<
ResponseModel
>
roomPowerOff
({
required
int
room
Id
})
{
return
DioRequest
.
instance
.
post
(
'/app/room/
power
/off'
,
data:
{
'
roomId'
:
room
Id
},
///
单设备断电
Future
<
ResponseModel
>
powerSingleOff
({
required
int
device
Id
})
{
return
DioRequest
.
instance
.
post
<
Map
<
String
,
dynamic
>>
(
'/app/room/
device/power/single
/off'
,
data:
{
'
deviceId'
:
device
Id
},
);
}
/// 客房状态总览-客房概览统计
Future
<
ResponseModel
<
RoomOverviewBO
>>
getOverview
(
int
?
floorId
)
{
Map
<
String
,
dynamic
>
param
=
{};
if
(
floorId
!=
null
&&
floorId
!=
0
)
{
param
[
'floorId'
]
=
floorId
;
}
return
DioRequest
.
instance
.
get
<
RoomOverviewBO
>(
'/app/room/overview'
,
queryParameters:
{
'floorId'
:
floorId
==
0
?
null
:
floorId
}
,
queryParameters:
param
,
fromJsonT:
(
data
)
=>
RoomOverviewBO
.
fromJson
(
data
as
Map
<
String
,
dynamic
>),
);
}
...
...
lib/services/room_service.dart
View file @
7ba2abc6
...
...
@@ -57,22 +57,6 @@ class RoomService {
throw
Exception
(
result
.
msg
);
}
/// 客房详情-房间总电源-开
Future
<
void
>
roomPowerOn
({
required
int
roomId
})
async
{
final
result
=
await
_repository
.
roomPowerOn
(
roomId:
roomId
);
if
(!
result
.
success
)
{
throw
Exception
(
result
.
msg
);
}
}
/// 客房详情-房间总电源-关
Future
<
void
>
roomPowerOff
({
required
int
roomId
})
async
{
final
result
=
await
_repository
.
roomPowerOff
(
roomId:
roomId
);
if
(!
result
.
success
)
{
throw
Exception
(
result
.
msg
);
}
}
/// 客房状态总览-客房概览统计
Future
<
RoomOverviewBO
>
getOverview
(
int
?
floorId
)
async
{
final
result
=
await
_repository
.
getOverview
(
floorId
);
...
...
@@ -96,4 +80,20 @@ class RoomService {
}
throw
Exception
(
result
.
msg
);
}
/// 单设备送电
Future
<
void
>
powerSingleOn
({
required
int
deviceId
})
async
{
final
result
=
await
_repository
.
powerSingleOn
(
deviceId:
deviceId
);
if
(!
result
.
success
)
{
throw
Exception
(
result
.
msg
);
}
}
/// 单设备断电
Future
<
void
>
powerSingleOff
({
required
int
deviceId
})
async
{
final
result
=
await
_repository
.
powerSingleOff
(
deviceId:
deviceId
);
if
(!
result
.
success
)
{
throw
Exception
(
result
.
msg
);
}
}
}
\ No newline at end of file
lib/utils/deal_utils.dart
View file @
7ba2abc6
...
...
@@ -4,7 +4,36 @@ class DealUtils {
/// 去除多余0
static
String
cleanNumber
(
String
value
)
{
return
value
.
replaceAll
(
RegExp
(
r'\.?0+$'
),
''
);
try
{
return
value
.
replaceAll
(
RegExp
(
r'\.?0+$'
),
''
);
}
catch
(
e
)
{
// rethrow;
return
value
;
}
}
/// 电流,电压
static
String
formatVoltage
(
String
input
)
{
if
(
input
.
isEmpty
)
return
input
;
try
{
// 匹配开头的数字部分(包括小数)
RegExp
regExp
=
RegExp
(
r'^([\d.]+)'
);
String
?
numberPart
=
regExp
.
firstMatch
(
input
)?.
group
(
1
);
String
unitPart
=
input
.
replaceFirst
(
RegExp
(
r'^[\d.]+'
),
''
);
if
(
numberPart
==
null
)
return
input
;
// 去掉尾随的零和多余的小数点
String
formattedNumber
=
numberPart
.
replaceFirst
(
RegExp
(
r'\.?0+$'
),
''
)
// 去掉末尾的0和可能的小数点
.
replaceFirst
(
RegExp
(
r'\.$'
),
''
);
// 如果只剩小数点,去掉
return
formattedNumber
+
unitPart
;
}
catch
(
e
)
{
// rethrow;
return
input
;
}
}
/// 设备图标
...
...
lib/views/home/abnormal/abnormal_detail_view.dart
View file @
7ba2abc6
...
...
@@ -102,7 +102,7 @@ class AbnormalDetailView extends StatelessWidget {
mainAxisSize:
MainAxisSize
.
min
,
spacing:
20
.
h
,
children:
[
if
(
alarmStatus
!=
AlarmStatus
.
processed
)
if
(
alarmStatus
!=
AlarmStatus
.
processed
)
//不等于已处理才显示
BlocBuilder
<
AbnormalCubit
,
AbnormalState
>(
builder:
(
context
,
state
)
{
final
isDisabled
=
state
.
isHandling
||
state
.
isHandled
;
...
...
lib/views/home/abnormal_list/cubit/abnormal_list_cubit.dart
View file @
7ba2abc6
...
...
@@ -14,8 +14,12 @@ String? alarmStatusToApi(AlarmStatus status) {
return
null
;
case
AlarmStatus
.
pending
:
return
'0'
;
case
AlarmStatus
.
processing
:
return
'1'
;
case
AlarmStatus
.
processed
:
return
'2'
;
case
AlarmStatus
.
close
:
return
'3'
;
case
AlarmStatus
.
falseAlarm
:
return
'4'
;
}
...
...
@@ -75,9 +79,46 @@ class AbnormalListCubit extends Cubit<AbnormalListState> {
_pagingController
?.
refresh
();
}
void
onAlarmItemTap
(
AlarmItem
item
,
BuildContext
context
)
{
Future
<
void
>
onAlarmItemTap
(
AlarmItem
item
,
BuildContext
context
)
async
{
final
alertId
=
int
.
tryParse
(
item
.
id
)
??
0
;
context
.
pushRoute
(
AbnormalDetailRoute
(
alertId:
alertId
,
alarmStatus:
item
.
status
));
final
handled
=
await
context
.
pushRoute
(
AbnormalDetailRoute
(
alertId:
alertId
,
alarmStatus:
item
.
status
),
);
// if (handled != null && handled is bool && handled == true) {
// _updateItemStatus(item.id);
// }
}
/// 在分页列表中定位并更新单条记录的状态(不重新请求接口)
void
_updateItemStatus
(
String
alertId
)
{
final
state
=
_pagingController
?.
value
;
if
(
state
==
null
)
return
;
final
pages
=
state
.
pages
;
if
(
pages
==
null
)
return
;
var
found
=
false
;
final
newPages
=
pages
.
map
((
page
)
{
if
(
found
)
return
page
;
final
index
=
page
.
indexWhere
((
i
)
=>
i
.
id
==
alertId
);
if
(
index
==
-
1
)
return
page
;
found
=
true
;
final
newPage
=
List
<
AlarmItem
>.
from
(
page
);
newPage
[
index
]
=
AlarmItem
(
id:
newPage
[
index
].
id
,
title:
newPage
[
index
].
title
,
deviceInfo:
newPage
[
index
].
deviceInfo
,
alarmTime:
newPage
[
index
].
alarmTime
,
processTime:
newPage
[
index
].
processTime
,
status:
AlarmStatus
.
processed
,
level:
newPage
[
index
].
level
,
alertIcon:
newPage
[
index
].
alertIcon
,
);
return
newPage
;
}).
toList
();
if
(
found
)
{
_pagingController
?.
value
=
state
.
copyWith
(
pages:
newPages
);
}
}
}
lib/views/home/abnormal_list/cubit/abnormal_list_state.dart
View file @
7ba2abc6
import
'package:equatable/equatable.dart'
;
import
'package:smart_hotel_app/models/bo/alert_list_bo.dart'
;
enum
AlarmStatus
{
all
,
pending
,
processed
,
falseAlarm
}
/// 0=待处理 1=处理中 2=已处理 3=已关闭 4=误报关闭
enum
AlarmStatus
{
all
,
pending
,
processing
,
processed
,
close
,
falseAlarm
}
class
AlarmItem
extends
Equatable
{
final
String
id
;
...
...
@@ -37,13 +38,17 @@ class AlarmItem extends Equatable {
);
}
// 0=待处理 1=处理中 2=已
确认
3=已关闭 4=误报关闭
// 0=待处理 1=处理中 2=已
处理
3=已关闭 4=误报关闭
static
AlarmStatus
_mapAlertStatus
(
String
alertStatus
)
{
switch
(
alertStatus
)
{
case
'0'
:
return
AlarmStatus
.
pending
;
case
'1'
:
return
AlarmStatus
.
processing
;
case
'2'
:
return
AlarmStatus
.
processed
;
case
'3'
:
return
AlarmStatus
.
close
;
case
'4'
:
return
AlarmStatus
.
falseAlarm
;
default
:
...
...
lib/views/home/abnormal_list/index_view.dart
View file @
7ba2abc6
...
...
@@ -185,7 +185,7 @@ class _AbnormalListBodyState extends State<_AbnormalListBody> {
itemBuilder:
(
_
,
item
,
__
)
{
return
AlarmListItem
(
item:
item
,
onTap:
(
i
)
=>
cubit
.
onAlarmItemTap
(
i
,
context
)
,
onTap:
(
i
)
{
cubit
.
onAlarmItemTap
(
i
,
context
);
}
,
);
},
),
...
...
lib/views/home/abnormal_list/widget/alarm_list_item.dart
View file @
7ba2abc6
...
...
@@ -170,7 +170,8 @@ class AlarmListItem extends StatelessWidget {
Color
?
bgColor
;
Color
?
textColor
;
String
text
=
''
;
String
icon
=
''
;
String
iconImg
=
''
;
IconData
?
iconData
;
double
iconW
=
0
;
double
iconH
=
0
;
...
...
@@ -179,23 +180,39 @@ class AlarmListItem extends StatelessWidget {
bgColor
=
const
Color
.
fromRGBO
(
254
,
247
,
217
,
1
);
textColor
=
const
Color
.
fromRGBO
(
250
,
204
,
21
,
1
);
text
=
'待处理'
;
icon
=
'lib/assets/icon/info_dcl_bg.png'
;
icon
Img
=
'lib/assets/icon/info_dcl_bg.png'
;
iconW
=
4
.
w
;
iconH
=
15
.
h
;
break
;
case
AlarmStatus
.
processing
:
bgColor
=
const
Color
.
fromRGBO
(
245
,
245
,
245
,
1
);
textColor
=
const
Color
.
fromRGBO
(
51
,
51
,
51
,
1
);
text
=
'处理中'
;
iconData
=
Icons
.
check
;
iconW
=
18
.
w
;
iconH
=
11
.
h
;
break
;
case
AlarmStatus
.
processed
:
bgColor
=
const
Color
.
fromRGBO
(
209
,
250
,
229
,
1
);
textColor
=
const
Color
.
fromRGBO
(
20
,
184
,
166
,
1
);
text
=
'已处理'
;
icon
=
'lib/assets/icon/info_ycl_bg.png'
;
icon
Data
=
Icons
.
check
;
iconW
=
18
.
w
;
iconH
=
11
.
h
;
break
;
case
AlarmStatus
.
close
:
bgColor
=
const
Color
.
fromRGBO
(
245
,
245
,
245
,
1
);
textColor
=
const
Color
.
fromRGBO
(
51
,
51
,
51
,
1
);
text
=
'关闭'
;
iconData
=
Icons
.
close
;
iconW
=
18
.
w
;
iconH
=
15
.
h
;
break
;
case
AlarmStatus
.
falseAlarm
:
bgColor
=
const
Color
.
fromRGBO
(
255
,
224
,
224
,
1
);
textColor
=
const
Color
.
fromRGBO
(
255
,
100
,
101
,
1
);
text
=
'误报'
;
icon
=
'lib/assets/icon/info_wb_bg.png'
;
icon
Data
=
Icons
.
close
;
iconW
=
14
.
w
;
iconH
=
11
.
h
;
break
;
...
...
@@ -218,7 +235,10 @@ class AlarmListItem extends StatelessWidget {
mainAxisAlignment:
MainAxisAlignment
.
center
,
spacing:
6
.
w
,
children:
[
Image
.
asset
(
icon
,
width:
iconW
,
height:
iconH
),
if
(
iconData
!=
null
)
Icon
(
iconData
,
color:
textColor
,
size:
26
.
w
)
else
Image
.
asset
(
iconImg
,
width:
iconW
,
height:
iconH
),
//没有纯感叹号的图标
Text
(
text
,
style:
TextStyle
(
...
...
lib/views/home/index/index_view.dart
View file @
7ba2abc6
...
...
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:smart_hotel_app/models/bo/alert_dashboard_bo.dart'
;
import
'package:smart_hotel_app/utils/deal_utils.dart'
;
import
'package:smart_hotel_app/views/home/index/cubit/home_index_cubit.dart'
;
import
'package:smart_hotel_app/views/home/index/cubit/home_index_state.dart'
;
import
'package:smart_hotel_app/views/home/index/widget/equipment_list_block.dart'
;
...
...
@@ -50,42 +51,75 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV
@override
Widget
build
(
BuildContext
context
)
{
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
return
SingleChildScrollView
(
child:
ConstrainedBox
(
constraints:
BoxConstraints
(
minHeight:
constraints
.
maxHeight
,
),
child:
Container
(
width:
double
.
infinity
,
color:
const
Color
.
fromRGBO
(
242
,
243
,
245
,
1
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
28
.
w
),
child:
BlocBuilder
<
HomeIndexCubit
,
HomeIndexState
>(
builder:
(
context
,
state
)
{
return
Column
(
children:
[
const
TaskHeadBlock
(),
SizedBox
(
height:
20
.
h
),
TaskTotalBlock
(
highAlarmCount:
state
.
highAlarmCount
,
mediumAlarmCount:
state
.
mediumAlarmCount
,
onlineDeviceCount:
state
.
onlineDeviceCount
,
),
...
_buildAlertBlocks
(
state
.
alerts
),
SizedBox
(
height:
20
.
h
),
EquipmentListBlock
(
equipmentList:
state
.
equipmentList
,
),
SizedBox
(
height:
20
.
h
),
],
);
},
),
return
BlocBuilder
<
HomeIndexCubit
,
HomeIndexState
>(
builder:
(
context
,
state
)
{
if
(
state
.
isLoading
)
{
return
Container
(
color:
const
Color
.
fromRGBO
(
242
,
243
,
245
,
1
),
child:
const
Center
(
child:
CircularProgressIndicator
(
color:
Color
.
fromRGBO
(
66
,
165
,
245
,
1.0
))),
);
}
if
(
state
.
error
!=
null
)
{
return
Container
(
color:
const
Color
.
fromRGBO
(
242
,
243
,
245
,
1
),
child:
Center
(
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Text
(
'加载失败:
${state.error}
'
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
),
),
),
SizedBox
(
height:
16
.
h
),
ElevatedButton
(
onPressed:
()
=>
context
.
read
<
HomeIndexCubit
>().
reloadDashboard
(),
child:
const
Text
(
'重试'
),
),
],
),
),
);
},
}
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
return
SingleChildScrollView
(
child:
ConstrainedBox
(
constraints:
BoxConstraints
(
minHeight:
constraints
.
maxHeight
,
),
child:
Container
(
width:
double
.
infinity
,
color:
const
Color
.
fromRGBO
(
242
,
243
,
245
,
1
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
28
.
w
),
child:
Column
(
children:
[
const
TaskHeadBlock
(),
SizedBox
(
height:
20
.
h
),
TaskTotalBlock
(
highAlarmCount:
state
.
highAlarmCount
,
mediumAlarmCount:
state
.
mediumAlarmCount
,
onlineDeviceCount:
state
.
onlineDeviceCount
,
),
...
_buildAlertBlocks
(
state
.
alerts
),
SizedBox
(
height:
20
.
h
),
EquipmentListBlock
(
equipmentList:
state
.
equipmentList
,
),
SizedBox
(
height:
20
.
h
),
],
),
),
),
);
},
);
},
);
}
...
...
@@ -105,8 +139,8 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV
temperatureAlarmTitle:
alert
.
alertTitle
,
temperatureAlarmLocation:
location
,
currentTemperature:
alert
.
temperature
,
currentVoltage:
alert
.
voltage
,
currentCurrent:
alert
.
current
,
currentVoltage:
DealUtils
.
formatVoltage
(
alert
.
voltage
)
,
currentCurrent:
DealUtils
.
formatVoltage
(
alert
.
current
)
,
waitTime:
alert
.
waitingDurationDesc
,
alarmLevel:
alert
.
alertLevelText
,
),
...
...
@@ -121,7 +155,7 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV
alertId:
alert
.
alertId
,
voltageAlarmTitle:
alert
.
alertTitle
,
voltageAlarmLocation:
location
,
voltageValue:
alert
.
voltage
,
voltageValue:
DealUtils
.
formatVoltage
(
alert
.
voltage
)
,
alertContent:
alert
.
alertContent
.
isNotEmpty
?
'(
${alert.alertContent}
)'
:
''
,
voltageWaitTime:
alert
.
waitingDurationDesc
,
...
...
lib/views/home/index/widget/voltage_operate_block.dart
View file @
7ba2abc6
...
...
@@ -83,6 +83,7 @@ class VoltageOperateBlock extends StatelessWidget {
],
),
),
SizedBox
(
width:
15
.
w
),
Text
(
voltageAlarmLevel
,
style:
TextStyle
(
...
...
lib/views/home/inspection/cubit/inspection_cubit.dart
View file @
7ba2abc6
...
...
@@ -31,7 +31,7 @@ class InspectionCubit extends Cubit<InspectionState> {
final
roomGroups
=
rooms
.
roomGroups
;
// 默认选中第一个区域、第一个房间
final
defaultAreaIndex
=
roomGroups
.
isNotEmpty
?
0
:
0
;
const
defaultAreaIndex
=
0
;
final
defaultRoomId
=
roomGroups
.
isNotEmpty
&&
roomGroups
[
defaultAreaIndex
].
rooms
.
isNotEmpty
?
roomGroups
[
defaultAreaIndex
].
rooms
.
first
.
roomId
...
...
@@ -54,8 +54,7 @@ class InspectionCubit extends Cubit<InspectionState> {
}
/// 从顶层 runStatus 汇总计算各 tab 数量
Map
<
DeviceStatusTab
,
int
>
_computeTabCounts
(
InspectionRoomRunStatusBO
runStatus
)
{
Map
<
DeviceStatusTab
,
int
>
_computeTabCounts
(
InspectionRoomRunStatusBO
runStatus
)
{
final
total
=
runStatus
.
normal
+
runStatus
.
warning
+
runStatus
.
offline
+
runStatus
.
fault
;
return
{
...
...
lib/views/home/inspection_device/cubit/inspection_device_cubit.dart
View file @
7ba2abc6
...
...
@@ -65,7 +65,7 @@ class InspectionDeviceCubit extends Cubit<InspectionDeviceState> {
lastInspectionTime:
basicInfo
.
lastInspectTime
,
inspectionHistory:
historyList
,
inspectionItems:
items
,
icon:
Icons
.
devices
,
icon:
Icons
.
devices
_other
,
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
...
...
lib/views/home/inspection_device/widget/device_overview_card.dart
View file @
7ba2abc6
...
...
@@ -82,16 +82,16 @@ class DeviceOverviewCard extends StatelessWidget {
padding:
EdgeInsets
.
symmetric
(
horizontal:
12
.
w
,
vertical:
4
.
h
),
decoration:
BoxDecoration
(
color:
deviceStatus
==
'1'
?
const
Color
.
fromRGBO
(
2
25
,
245
,
238
,
1.0
)
?
const
Color
.
fromRGBO
(
2
09
,
250
,
229
,
1.0
)
:
const
Color
.
fromRGBO
(
255
,
235
,
238
,
1.0
),
borderRadius:
BorderRadius
.
circular
(
2
0
.
r
),
borderRadius:
BorderRadius
.
circular
(
4
0
.
r
),
),
child:
Text
(
deviceStatus
==
'1'
?
'在线'
:
'离线'
,
style:
TextStyle
(
fontSize:
24
.
sp
,
color:
deviceStatus
==
'1'
?
const
Color
.
fromRGBO
(
20
9
,
250
,
229
,
1.0
)
?
const
Color
.
fromRGBO
(
20
,
184
,
166
,
1.0
)
:
const
Color
.
fromRGBO
(
231
,
76
,
60
,
1.0
),
),
),
...
...
lib/views/report/energy/energy_detail_view.dart
View file @
7ba2abc6
...
...
@@ -82,7 +82,7 @@ class ReportEnergyDetailView extends StatelessWidget {
),
SizedBox
(
height:
16
.
h
),
ZoneChartBlock
(
zoneData:
state
.
zoneData
),
SizedBox
(
height:
20
.
h
),
SizedBox
(
height:
16
.
h
),
],
),
);
...
...
lib/views/report/energy/widget/zone_chart_block.dart
View file @
7ba2abc6
import
'package:flutter/material.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:smart_hotel_app/utils/deal_utils.dart'
;
import
'package:smart_hotel_app/views/report/energy/cubit/energy_state.dart'
;
class
ZoneChartBlock
extends
StatelessWidget
{
...
...
@@ -36,11 +37,13 @@ class ZoneChartBlock extends StatelessWidget {
bottom:
index
<
zoneData
.
length
-
1
?
20
.
h
:
0
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
spacing:
12
.
h
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Row
(
spacing:
8
.
w
,
children:
[
Container
(
width:
12
.
w
,
...
...
@@ -52,7 +55,6 @@ class ZoneChartBlock extends StatelessWidget {
shape:
BoxShape
.
circle
,
),
),
SizedBox
(
width:
8
.
w
),
Text
(
data
.
name
,
style:
TextStyle
(
...
...
@@ -63,7 +65,8 @@ class ZoneChartBlock extends StatelessWidget {
],
),
Text
(
'
${data.percentage.toInt()}
% (
${data.energy.toInt()}
kWh)'
,
'
${DealUtils.cleanNumber(data.percentage.toString())}
% '
'(
${DealUtils.cleanNumber(data.energy.toString())}
kWh)'
,
style:
TextStyle
(
color:
index
==
0
?
const
Color
.
fromRGBO
(
59
,
130
,
246
,
1
)
...
...
@@ -73,7 +76,6 @@ class ZoneChartBlock extends StatelessWidget {
),
],
),
SizedBox
(
height:
12
.
h
),
Stack
(
children:
[
Container
(
...
...
lib/views/report/index/cubit/report_index_cubit.dart
View file @
7ba2abc6
...
...
@@ -12,7 +12,7 @@ class ReportIndexCubit extends Cubit<ReportIndexState> {
ReportIndexCubit
({
DashboardService
?
service
})
:
_service
=
service
??
DashboardService
(
repository:
DashboardRepository
()),
super
(
const
ReportIndexState
())
{
loadData
();
//
loadData();
}
Future
<
void
>
loadData
()
async
{
...
...
lib/views/report/index/widget/device_online_rate.dart
View file @
7ba2abc6
...
...
@@ -2,7 +2,6 @@ import 'package:auto_route/auto_route.dart';
import
'package:flutter/material.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:fl_chart/fl_chart.dart'
;
import
'package:smart_hotel_app/routes/app_router.dart'
;
import
'package:smart_hotel_app/routes/app_router.gr.dart'
;
class
DeviceOnlineRate
extends
StatelessWidget
{
...
...
@@ -13,10 +12,13 @@ class DeviceOnlineRate extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
final
devices
=
deviceOnlineRate
[
'devices'
]
as
List
<
Map
<
String
,
dynamic
>>;
(
deviceOnlineRate
[
'devices'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
e
as
Map
<
String
,
dynamic
>)
.
toList
()
??
<
Map
<
String
,
dynamic
>>[];
final
totalDevices
=
devices
.
fold
<
int
>(
0
,
(
sum
,
device
)
=>
sum
+
(
device
[
'count'
]
as
int
));
final
rate
=
deviceOnlineRate
[
'rate'
]
as
double
;
devices
.
fold
<
int
>(
0
,
(
sum
,
device
)
=>
sum
+
(
(
device
[
'count'
]
as
int
?)
??
0
));
final
rate
=
deviceOnlineRate
[
'rate'
]
as
double
?
??
0.0
;
return
GestureDetector
(
onTap:
()
{
...
...
@@ -36,7 +38,7 @@ class DeviceOnlineRate extends StatelessWidget {
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
deviceOnlineRate
[
'title'
]
as
String
,
(
deviceOnlineRate
[
'title'
]
as
String
?)
??
''
,
style:
TextStyle
(
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
normal
,
...
...
@@ -93,7 +95,7 @@ class DeviceOnlineRate extends StatelessWidget {
),
),
Text
(
deviceOnlineRate
[
'rateText'
]
as
String
,
(
deviceOnlineRate
[
'rateText'
]
as
String
?)
??
''
,
style:
TextStyle
(
fontSize:
24
.
sp
,
fontWeight:
FontWeight
.
bold
,
...
...
@@ -113,14 +115,14 @@ class DeviceOnlineRate extends StatelessWidget {
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
device
[
'name'
]
as
String
,
(
device
[
'name'
]
as
String
?)
??
''
,
style:
TextStyle
(
fontSize:
22
.
sp
,
color:
const
Color
.
fromRGBO
(
107
,
114
,
128
,
1
),
),
),
Text
(
'
${device['count']}
台'
,
'
${device['count']
?? 0
}
台'
,
style:
TextStyle
(
fontSize:
22
.
sp
,
color:
const
Color
.
fromRGBO
(
55
,
65
,
81
,
1
),
...
...
lib/views/report/index/widget/quick_actions_block.dart
View file @
7ba2abc6
...
...
@@ -11,7 +11,11 @@ class QuickActionsBlock extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
final
actions
=
quickActions
[
'actions'
]
as
List
<
Map
<
String
,
dynamic
>>;
final
actions
=
(
quickActions
[
'actions'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
e
as
Map
<
String
,
dynamic
>)
.
toList
()
??
<
Map
<
String
,
dynamic
>>[];
return
Container
(
width:
double
.
infinity
,
...
...
@@ -24,7 +28,7 @@ class QuickActionsBlock extends StatelessWidget {
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
quickActions
[
'title'
]
as
String
,
(
quickActions
[
'title'
]
as
String
?)
??
''
,
style:
TextStyle
(
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
normal
,
...
...
@@ -34,7 +38,7 @@ class QuickActionsBlock extends StatelessWidget {
SizedBox
(
height:
28
.
h
),
Row
(
children:
actions
.
map
((
action
)
{
final
name
=
action
[
'name'
]
as
String
;
final
name
=
(
action
[
'name'
]
as
String
?)
??
''
;
return
Padding
(
padding:
EdgeInsets
.
only
(
right:
60
.
w
),
child:
GestureDetector
(
...
...
@@ -55,7 +59,7 @@ class QuickActionsBlock extends StatelessWidget {
borderRadius:
BorderRadius
.
circular
(
16
.
r
),
),
child:
Icon
(
action
[
'icon'
]
as
IconData
,
(
action
[
'icon'
]
as
IconData
?)
??
Icons
.
apps
,
color:
const
Color
.
fromRGBO
(
59
,
130
,
246
,
1
),
size:
36
.
sp
,
),
...
...
lib/views/report/index/widget/report_metrics_block.dart
View file @
7ba2abc6
...
...
@@ -9,20 +9,25 @@ class ReportMetricsBlock extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
final
powerUsage
=
reportMetrics
[
'powerUsage'
]
as
Map
<
String
,
dynamic
>;
final
pendingAlerts
=
reportMetrics
[
'pendingAlerts'
]
as
Map
<
String
,
dynamic
>;
final
powerUsage
=
reportMetrics
.
containsKey
(
'powerUsage'
)
&&
reportMetrics
[
'powerUsage'
]
!=
null
?
Map
<
String
,
dynamic
>.
from
(
reportMetrics
[
'powerUsage'
])
:
{};
final
pendingAlerts
=
reportMetrics
.
containsKey
(
'pendingAlerts'
)
&&
reportMetrics
[
'pendingAlerts'
]
!=
null
?
Map
<
String
,
dynamic
>.
from
(
reportMetrics
[
'pendingAlerts'
])
:
{};
return
Row
(
children:
[
Expanded
(
child:
_buildMetricCard
(
title:
'今日用电'
,
value:
DealUtils
.
cleanNumber
(
powerUsage
[
'value'
]
as
String
),
unit:
powerUsage
[
'unit'
]
as
String
,
changeText:
powerUsage
[
'changeText'
]
as
String
,
value:
DealUtils
.
cleanNumber
(
powerUsage
.
containsKey
(
'value'
)
?
powerUsage
[
'value'
]
as
String
:
''
),
unit:
powerUsage
[
'unit'
]
as
String
?,
changeText:
powerUsage
[
'changeText'
]
as
String
?,
changeColor:
const
Color
.
fromRGBO
(
20
,
184
,
166
,
1
),
valueColor:
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
),
progress:
powerUsage
[
'progress'
]
as
double
,
progress:
powerUsage
[
'progress'
]
as
double
?
,
progressColor:
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
),
),
),
...
...
@@ -30,12 +35,12 @@ class ReportMetricsBlock extends StatelessWidget {
Expanded
(
child:
_buildMetricCard
(
title:
'待处理告警'
,
value:
pendingAlerts
[
'value'
],
unit:
pendingAlerts
[
'unit'
]
as
String
,
changeText:
pendingAlerts
[
'changeText'
]
as
String
,
value:
pendingAlerts
[
'value'
]
as
String
?
,
unit:
pendingAlerts
[
'unit'
]
as
String
?
,
changeText:
pendingAlerts
[
'changeText'
]
as
String
?
,
changeColor:
const
Color
.
fromRGBO
(
250
,
204
,
21
,
1
),
valueColor:
const
Color
.
fromRGBO
(
250
,
204
,
21
,
1
),
progress:
pendingAlerts
[
'progress'
]
as
double
,
progress:
pendingAlerts
[
'progress'
]
as
double
?
,
progressColor:
const
Color
.
fromRGBO
(
250
,
204
,
21
,
1
),
),
),
...
...
@@ -45,12 +50,12 @@ class ReportMetricsBlock extends StatelessWidget {
Widget
_buildMetricCard
({
required
String
title
,
required
String
value
,
required
String
unit
,
required
String
changeText
,
required
String
?
value
,
required
String
?
unit
,
required
String
?
changeText
,
required
Color
changeColor
,
required
Color
valueColor
,
required
double
progress
,
required
double
?
progress
,
required
Color
progressColor
,
})
{
return
Container
(
...
...
@@ -73,7 +78,7 @@ class ReportMetricsBlock extends StatelessWidget {
),
),
Text
(
changeText
,
changeText
??
''
,
style:
TextStyle
(
fontSize:
24
.
sp
,
color:
changeColor
,
...
...
@@ -86,8 +91,7 @@ class ReportMetricsBlock extends StatelessWidget {
crossAxisAlignment:
CrossAxisAlignment
.
end
,
children:
[
Text
(
value
,
// cleanNumber(value),
value
??
''
,
style:
TextStyle
(
fontSize:
40
.
sp
,
fontWeight:
FontWeight
.
bold
,
...
...
@@ -98,7 +102,7 @@ class ReportMetricsBlock extends StatelessWidget {
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
6
.
h
),
child:
Text
(
unit
,
unit
??
''
,
style:
TextStyle
(
fontSize:
20
.
sp
,
color:
const
Color
.
fromRGBO
(
107
,
114
,
128
,
1
),
...
...
lib/views/report/index/widget/room_status_distribution.dart
View file @
7ba2abc6
...
...
@@ -12,8 +12,11 @@ class RoomStatusDistribution extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
final
statuses
=
roomStatusDistribution
[
'statuses'
]
as
List
<
Map
<
String
,
dynamic
>>;
final
totalRooms
=
roomStatusDistribution
[
'totalRooms'
]
as
int
;
(
roomStatusDistribution
[
'statuses'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
e
as
Map
<
String
,
dynamic
>)
.
toList
()
??
<
Map
<
String
,
dynamic
>>[];
final
totalRooms
=
roomStatusDistribution
[
'totalRooms'
]
as
int
?
??
0
;
return
GestureDetector
(
onTap:
()
{
...
...
@@ -33,7 +36,7 @@ class RoomStatusDistribution extends StatelessWidget {
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
roomStatusDistribution
[
'title'
]
as
String
,
(
roomStatusDistribution
[
'title'
]
as
String
?)
??
''
,
style:
TextStyle
(
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
normal
,
...
...
@@ -61,9 +64,9 @@ class RoomStatusDistribution extends StatelessWidget {
),
SizedBox
(
height:
20
.
h
),
...
statuses
.
map
((
status
)
{
final
count
=
status
[
'count'
]
as
int
;
final
color
=
status
[
'color'
]
as
Color
;
final
name
=
status
[
'name'
]
as
String
;
final
count
=
status
[
'count'
]
as
int
?
??
0
;
final
color
=
status
[
'color'
]
as
Color
?
??
Colors
.
grey
;
final
name
=
status
[
'name'
]
as
String
?
??
''
;
final
percentage
=
totalRooms
>
0
?
count
/
totalRooms
:
0.0
;
final
percentText
=
'
${(percentage * 100).toInt()}
%'
;
...
...
lib/views/report/index/widget/weekly_power_chart.dart
View file @
7ba2abc6
...
...
@@ -34,8 +34,8 @@ class WeeklyPowerChart extends StatelessWidget {
double
get
_bottomInterval
{
final
len
=
_spots
.
length
;
if
(
len
<=
1
)
return
1
;
if
(
len
<=
6
)
return
1
;
if
(
len
<=
1
2
)
return
2
;
if
(
len
<=
7
)
return
1
;
if
(
len
<=
1
4
)
return
2
;
return
(
len
-
1
)
/
4
;
}
...
...
@@ -71,6 +71,7 @@ class WeeklyPowerChart extends StatelessWidget {
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
spacing:
20
.
h
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
...
...
@@ -84,6 +85,7 @@ class WeeklyPowerChart extends StatelessWidget {
),
),
Row
(
spacing:
8
.
w
,
children:
[
Text
(
weeklyPowerUsage
[
'dateRange'
]
as
String
?
??
''
,
...
...
@@ -92,7 +94,6 @@ class WeeklyPowerChart extends StatelessWidget {
color:
const
Color
.
fromRGBO
(
156
,
163
,
175
,
1
),
),
),
SizedBox
(
width:
8
.
w
),
Icon
(
Icons
.
arrow_forward_ios
,
size:
24
.
sp
,
...
...
@@ -102,7 +103,6 @@ class WeeklyPowerChart extends StatelessWidget {
),
],
),
SizedBox
(
height:
20
.
h
),
Container
(
height:
200
.
h
,
padding:
EdgeInsets
.
only
(
right:
28
.
w
),
...
...
@@ -131,7 +131,7 @@ class WeeklyPowerChart extends StatelessWidget {
bottomTitles:
AxisTitles
(
sideTitles:
SideTitles
(
showTitles:
true
,
interval:
_bottomInterval
,
interval:
_bottomInterval
,
//刻度间隔
getTitlesWidget:
(
value
,
meta
)
{
return
Padding
(
padding:
EdgeInsets
.
only
(
top:
8
.
h
),
...
...
@@ -144,7 +144,7 @@ class WeeklyPowerChart extends StatelessWidget {
),
);
},
reservedSize:
2
8
,
reservedSize:
2
0
,
//轴标签预留的外部空间
),
),
leftTitles:
AxisTitles
(
...
...
lib/views/report/room/cubit/room_report_cubit.dart
View file @
7ba2abc6
...
...
@@ -7,20 +7,27 @@ import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
class
RoomReportCubit
extends
Cubit
<
RoomReportState
>
{
final
RoomService
_service
;
/// 全量数据缓存,切换楼层时直接从内存过滤,不重新请求接口
List
<
RoomGridBO
>
_allRoomGrid
=
[];
List
<
RoomDetailListBO
>
_allDetailRooms
=
[];
RoomReportCubit
({
RoomService
?
service
})
:
_service
=
service
??
RoomService
(
repository:
RoomRepository
()),
super
(
const
RoomReportState
())
{
loadData
();
}
Future
<
void
>
loadData
(
{
int
floorId
=
1
}
)
async
{
emit
(
state
.
copyWith
(
isLoading:
true
,
error:
null
));
Future
<
void
>
loadData
()
async
{
emit
(
state
.
copyWith
(
isLoading:
true
,
error:
null
,
clearError:
true
));
try
{
final
overview
=
await
_service
.
getOverview
(
floorId
);
// 不传 floorId,获取全量数据
final
overview
=
await
_service
.
getOverview
(
null
);
final
activeIndex
=
overview
.
floorList
.
indexWhere
((
f
)
=>
f
.
isActive
);
final
selectedFloorIndex
=
activeIndex
>=
0
?
activeIndex
:
0
;
// 过滤掉每个房间 deviceList 中名称含"分控"的设备
final
filteredDetailRooms
=
overview
.
roomDetailList
.
map
((
room
)
{
// 过滤掉每个房间 deviceList 中名称含"分控"的设备,并缓存全量
_allRoomGrid
=
overview
.
roomGrid
;
_allDetailRooms
=
overview
.
roomDetailList
.
map
((
room
)
{
return
RoomDetailListBO
(
roomBasicInfo:
room
.
roomBasicInfo
,
statusTags:
room
.
statusTags
,
...
...
@@ -30,13 +37,16 @@ class RoomReportCubit extends Cubit<RoomReportState> {
);
}).
toList
();
// 按选中楼层的 floorNumber 过滤
final
floorNumber
=
overview
.
floorList
[
selectedFloorIndex
].
floorNumber
;
emit
(
state
.
copyWith
(
isLoading:
false
,
summary:
overview
.
summary
,
floors:
overview
.
floorList
,
selectedFloorIndex:
activeIndex
>=
0
?
activeIndex
:
0
,
roomGrid:
overview
.
roomGrid
,
detailRooms:
filteredDetailRooms
,
selectedFloorIndex:
selectedFloorIndex
,
roomGrid:
_filterRoomGrid
(
floorNumber
)
,
detailRooms:
_filterDetailRooms
(
floorNumber
)
,
selectedRoomId:
null
,
));
}
catch
(
e
)
{
...
...
@@ -44,16 +54,33 @@ class RoomReportCubit extends Cubit<RoomReportState> {
}
}
/// 切换楼层
void
selectFloor
(
int
index
)
{
if
(
state
.
selectedFloorIndex
==
index
)
return
;
if
(
index
<
0
||
index
>=
state
.
floors
.
length
)
return
;
final
floorId
=
state
.
floors
[
index
].
floorId
;
emit
(
state
.
copyWith
(
selectedFloorIndex:
index
,
selectedRoomId:
null
));
loadData
(
floorId:
floorId
);
final
floorNumber
=
state
.
floors
[
index
].
floorNumber
;
emit
(
state
.
copyWith
(
selectedFloorIndex:
index
,
roomGrid:
_filterRoomGrid
(
floorNumber
),
detailRooms:
_filterDetailRooms
(
floorNumber
),
selectedRoomId:
null
,
));
}
void
selectRoom
(
int
roomId
)
{
final
newId
=
state
.
selectedRoomId
==
roomId
?
null
:
roomId
;
emit
(
state
.
copyWith
(
selectedRoomId:
newId
));
}
List
<
RoomGridBO
>
_filterRoomGrid
(
int
floorNumber
)
{
return
_allRoomGrid
.
where
((
r
)
=>
r
.
floorNumber
==
floorNumber
).
toList
();
}
List
<
RoomDetailListBO
>
_filterDetailRooms
(
int
floorNumber
)
{
return
_allDetailRooms
.
where
((
r
)
=>
r
.
roomBasicInfo
.
floorNumber
==
floorNumber
)
.
toList
();
}
}
lib/views/report/room/widget/floor_selector.dart
View file @
7ba2abc6
...
...
@@ -15,44 +15,44 @@ class FloorSelector extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
width:
double
.
infinity
,
padding:
EdgeInsets
.
symmetric
(
vertical:
20
.
h
),
return
SingleChildScrollView
(
scrollDirection:
Axis
.
horizontal
,
padding:
EdgeInsets
.
symmetric
(
horizontal:
28
.
w
,
vertical:
20
.
h
),
child:
Row
(
spacing:
30
.
w
,
children:
floorNames
.
asMap
().
entries
.
map
((
entry
)
{
final
index
=
entry
.
key
;
final
name
=
entry
.
value
;
final
isSelected
=
index
==
selectedIndex
;
return
Expanded
(
child:
GestureDetector
(
onTap:
()
{
context
.
read
<
RoomReportCubit
>().
selectFloor
(
index
);
},
child:
Column
(
children:
[
Text
(
name
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
isSelected
?
const
Color
.
fromRGBO
(
80
,
162
,
255
,
1
)
:
const
Color
.
fromRGBO
(
10
,
13
,
20
,
1
),
fontWeight:
isSelected
?
FontWeight
.
w500
:
FontWeight
.
normal
,
)
,
return
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
()
{
context
.
read
<
RoomReportCubit
>().
selectFloor
(
index
);
},
child:
Column
(
spacing:
10
.
h
,
children:
[
Text
(
name
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
isSelected
?
const
Color
.
fromRGBO
(
80
,
162
,
255
,
1
)
:
const
Color
.
fromRGBO
(
10
,
13
,
20
,
1
)
,
fontWeight:
isSelected
?
FontWeight
.
bold
:
FontWeight
.
normal
,
),
SizedBox
(
height:
4
.
h
),
Container
(
width:
50
.
w
,
height:
3
.
h
,
decoration:
BoxDecoration
(
color:
isSelected
?
const
Color
.
fromRGBO
(
80
,
162
,
255
,
1
)
:
Colors
.
transparent
,
borderRadius:
BorderRadius
.
circular
(
2
.
r
),
),
),
Container
(
width:
60
.
w
,
height:
3
.
h
,
decoration:
BoxDecoration
(
color:
isSelected
?
const
Color
.
fromRGBO
(
80
,
162
,
255
,
1
)
:
Colors
.
transparent
,
borderRadius:
BorderRadius
.
circular
(
2
.
r
),
),
]
,
)
,
)
,
]
,
),
);
}).
toList
(),
...
...
lib/views/report/room/widget/room_detail_card.dart
View file @
7ba2abc6
...
...
@@ -73,10 +73,10 @@ class RoomDetailCard extends StatelessWidget {
label:
'通电中'
,
isActive:
room
.
statusTags
.
powerSupplyStatus
,
),
_StatusTag
(
label:
'wifi正常'
,
isActive:
room
.
statusTags
.
wifiNormalStatus
,
),
//
_StatusTag(
//
label: 'wifi正常',
//
isActive: room.statusTags.wifiNormalStatus,
//
),
],
),
],
...
...
@@ -176,10 +176,7 @@ class _DeviceControl extends StatelessWidget {
),
SizedBox
(
width:
16
.
w
),
Expanded
(
child:
Text
(
device
.
deviceName
.
contains
(
'-'
)
?
device
.
deviceName
.
split
(
'-'
).
last
:
device
.
deviceName
,
child:
Text
(
device
.
deviceName
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
const
Color
.
fromRGBO
(
59
,
130
,
246
,
1
),
...
...
lib/views/service/index/cubit/service_index_cubit.dart
View file @
7ba2abc6
...
...
@@ -17,72 +17,65 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
repository:
RoomStatisticsRepository
(),
),
super
(
const
ServiceIndexState
())
{
_init
();
//
_init();
}
Future
<
void
>
_init
()
async
{
await
Future
.
wait
([
loadStatistics
(),
loadFloorAndAreas
(),
]);
}
//
Future<void> _init() async {
//
await Future.wait([
//
loadStatistics(),
//
loadFloorAndAreas(),
//
]);
//
}
Future
<
void
>
reloadAll
()
async
{
await
Future
.
wait
([
loadStatistics
(),
loadFloorAndAreas
(),
]);
}
/// 加载客房服务看板-统计数据(待响应、入住中、空闲可清洁)
Future
<
void
>
loadStatistics
()
async
{
emit
(
state
.
copyWith
(
isLoading:
true
,
error:
null
));
try
{
final
stats
=
await
_statisticsService
.
getStatistics
();
emit
(
state
.
copyWith
(
isLoading:
false
,
pendingCount:
stats
.
pendingResponseCount
,
occupiedRooms:
stats
.
occupiedCount
,
vacantRooms:
stats
.
vacantCleanableCount
,
));
await
Future
.
wait
([
loadStatistics
(),
loadFloorAndAreas
(),
]);
emit
(
state
.
copyWith
(
isLoading:
false
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
}
}
/// 加载客房服务看板-统计数据(待响应、入住中、空闲可清洁)
Future
<
void
>
loadStatistics
()
async
{
final
stats
=
await
_statisticsService
.
getStatistics
();
emit
(
state
.
copyWith
(
pendingCount:
stats
.
pendingResponseCount
,
occupiedRooms:
stats
.
occupiedCount
,
vacantRooms:
stats
.
vacantCleanableCount
,
));
}
/// 加载楼层和区域信息 + 合并房间数据
Future
<
void
>
loadFloorAndAreas
()
async
{
emit
(
state
.
copyWith
(
isLoading:
true
,
error:
null
));
try
{
final
floorAreas
=
await
_roomService
.
getFloorAreas
();
_floorAreasCache
=
floorAreas
;
final
floorAreas
=
await
_roomService
.
getFloorAreas
();
_floorAreasCache
=
floorAreas
;
final
floors
=
floorAreas
.
map
((
f
)
=>
{
f
.
floorId
:
f
.
floorId
.
toString
()
}).
toList
();
final
floors
=
floorAreas
.
map
((
f
)
=>
{
f
.
floorId
:
f
.
floorName
}).
toList
();
emit
(
state
.
copyWith
(
isLoading:
false
,
floors:
floors
,
));
emit
(
state
.
copyWith
(
floors:
floors
));
// 默认选中第一个楼层,加载其全部房间数据,再默认选中第一个区域和房间
if
(
floors
.
isNotEmpty
)
{
final
firstFloor
=
floors
.
first
;
final
firstFloorId
=
firstFloor
.
keys
.
first
;
final
firstFloorLabel
=
firstFloor
.
values
.
first
;
_updateAreasForFloor
(
firstFloorLabel
);
selectFloor
(
firstFloorLabel
);
await
_loadAllRooms
(
floorId:
firstFloorId
);
_selectFirstTypeAndRoom
();
}
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
// 默认选中第一个楼层,加载其全部房间数据,再默认选中第一个区域和房间
if
(
floors
.
isNotEmpty
)
{
final
firstFloor
=
floors
.
first
;
final
firstFloorId
=
firstFloor
.
keys
.
first
;
final
firstFloorLabel
=
firstFloor
.
values
.
first
;
_updateAreasForFloor
(
firstFloorLabel
);
selectFloor
(
firstFloorLabel
);
await
_loadAllRooms
(
floorId:
firstFloorId
);
_selectFirstTypeAndRoom
();
}
}
/// 根据选中的楼层更新 areas
void
_updateAreasForFloor
(
String
floorLabel
)
{
final
matched
=
_floorAreasCache
.
where
(
(
f
)
=>
f
.
floor
Id
.
toString
()
==
floorLabel
,
(
f
)
=>
f
.
floor
Name
==
floorLabel
,
);
final
areas
=
matched
.
isNotEmpty
?
matched
.
first
.
areas
:
<
String
>[];
emit
(
state
.
copyWith
(
areas:
areas
));
...
...
@@ -90,45 +83,40 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
/// 加载某楼层的全部房间状态,与 FloorAreaBO.rooms 合并
Future
<
void
>
_loadAllRooms
({
required
int
floorId
})
async
{
emit
(
state
.
copyWith
(
isLoading:
true
,
error:
null
));
try
{
// 获取当前楼层的 FloorAreaBO
final
floorArea
=
_floorAreasCache
.
cast
<
FloorAreaBO
?>().
firstWhere
(
(
f
)
=>
f
?.
floorId
==
floorId
,
orElse:
()
=>
null
,
);
if
(
floorArea
==
null
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
rooms:
[]));
return
;
}
// 拉取全楼层房间状态(不传 areaType)
final
roomStatuses
=
await
_roomService
.
getRoomStatus
(
floorId:
floorId
);
// 获取当前楼层的 FloorAreaBO
final
floorArea
=
_floorAreasCache
.
cast
<
FloorAreaBO
?>().
firstWhere
(
(
f
)
=>
f
?.
floorId
==
floorId
,
orElse:
()
=>
null
,
);
if
(
floorArea
==
null
)
{
emit
(
state
.
copyWith
(
rooms:
[]));
return
;
}
// 以 roomId 为 key 建立状态映射
final
statusMap
=
<
int
,
RoomStatusBO
>{};
for
(
final
rs
in
roomStatuses
)
{
statusMap
[
rs
.
roomId
]
=
rs
;
}
// 拉取全楼层房间状态(不传 areaType)
final
roomStatuses
=
await
_roomService
.
getRoomStatus
(
floorId:
floorId
);
// 合并:遍历 floorArea.rooms,匹配状态数据,推断 area
final
mergedRooms
=
<
Map
<
String
,
dynamic
>>[];
for
(
final
floorRoom
in
floorArea
.
rooms
)
{
final
status
=
statusMap
[
floorRoom
.
roomId
];
final
area
=
_getAreaForRoom
(
floorRoom
.
roomName
,
floorArea
.
areas
);
mergedRooms
.
add
(
_buildRoomMap
(
roomId:
floorRoom
.
roomId
,
roomName:
floorRoom
.
roomName
,
areaType:
area
,
status:
status
,
));
}
// 以 roomId 为 key 建立状态映射
final
roomIdMap
=
<
int
,
RoomStatusBO
>{};
for
(
final
rs
in
roomStatuses
)
{
roomIdMap
[
rs
.
roomId
]
=
rs
;
}
emit
(
state
.
copyWith
(
isLoading:
false
,
rooms:
mergedRooms
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
// 合并:遍历 floorArea.rooms,匹配状态数据,推断 area
final
mergedRooms
=
<
Map
<
String
,
dynamic
>>[];
for
(
final
floorRoom
in
floorArea
.
rooms
)
{
final
status
=
roomIdMap
[
floorRoom
.
roomId
];
final
area
=
_getAreaForRoom
(
floorRoom
.
roomName
,
floorArea
.
areas
);
mergedRooms
.
add
(
_buildRoomMap
(
roomId:
floorRoom
.
roomId
,
roomName:
floorRoom
.
roomName
,
areaType:
area
,
status:
status
,
));
}
emit
(
state
.
copyWith
(
rooms:
mergedRooms
));
}
/// 根据 roomName 匹配所属区域类型(最长匹配优先)
...
...
@@ -204,22 +192,18 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
}
}
/// UI 层切换楼层(切换时重置类型/房间/搜索,重新加载数据)
/// UI 层切换楼层(
选中后不能取消,
切换时重置类型/房间/搜索,重新加载数据)
Future
<
void
>
toggleFloor
(
String
floorName
,
int
floorId
)
async
{
final
isSameFloor
=
state
.
selectedFloor
==
floorName
;
final
newFloor
=
isSameFloor
?
null
:
floorName
;
if
(
state
.
selectedFloor
==
floorName
)
return
;
// 计算新楼层的 areas
List
<
String
>
areas
=
state
.
areas
;
if
(
newFloor
!=
null
)
{
final
matched
=
_floorAreasCache
.
where
(
(
f
)
=>
f
.
floorId
.
toString
()
==
newFloor
,
);
areas
=
matched
.
isNotEmpty
?
matched
.
first
.
areas
:
<
String
>[];
}
final
matched
=
_floorAreasCache
.
where
(
(
f
)
=>
f
.
floorName
==
floorName
,
);
final
areas
=
matched
.
isNotEmpty
?
matched
.
first
.
areas
:
<
String
>[];
emit
(
state
.
copyWith
(
selectedFloor:
newFloor
,
selectedFloor:
floorName
,
selectedType:
null
,
selectedRoom:
null
,
selectedRoomId:
null
,
...
...
@@ -227,9 +211,11 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
areas:
areas
,
));
if
(
newFloor
!=
null
)
{
try
{
await
_loadAllRooms
(
floorId:
floorId
);
_selectFirstTypeAndRoom
();
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
error:
e
.
toString
()));
}
}
...
...
lib/views/service/index/widget/room_management_block.dart
View file @
7ba2abc6
import
'dart:convert'
;
import
'package:auto_route/auto_route.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
...
...
@@ -41,7 +39,7 @@ class RoomManagementBlock extends StatelessWidget {
_buildSearchBox
(
state
,
cubit
),
SizedBox
(
height:
32
.
h
),
_buildLine
(),
SizedBox
(
height:
24
.
h
),
SizedBox
(
height:
32
.
h
),
_buildRoomGrid
(
state
,
cubit
),
SizedBox
(
height:
24
.
h
),
if
(
state
.
selectedRoom
!=
null
)
...[
...
...
@@ -60,36 +58,33 @@ class RoomManagementBlock extends StatelessWidget {
Widget
_buildFloorSelector
(
ServiceIndexState
state
,
ServiceIndexCubit
cubit
)
{
final
floors
=
state
.
floors
;
return
Row
(
children:
(
floors
??
[]).
map
((
floorMap
)
{
final
floorName
=
floorMap
.
values
.
first
;
final
floorNameNew
=
floorName
==
'1'
?
'一'
:
floorName
==
'2'
?
'二'
:
floorName
==
'3'
?
'三'
:
floorName
==
'4'
?
'四'
:
floorName
==
'5'
?
'五'
:
floorName
==
'6'
?
'六'
:
''
;
final
floorId
=
floorMap
.
keys
.
first
;
final
isSelected
=
state
.
selectedFloor
==
floorName
;
return
Expanded
(
child:
GestureDetector
(
return
SingleChildScrollView
(
scrollDirection:
Axis
.
horizontal
,
child:
Row
(
spacing:
30
.
w
,
children:
(
floors
??
[]).
map
((
floorMap
)
{
final
floorName
=
floorMap
.
values
.
first
;
final
floorId
=
floorMap
.
keys
.
first
;
final
isSelected
=
state
.
selectedFloor
==
floorName
;
return
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
()
=>
cubit
.
toggleFloor
(
floorName
,
floorId
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
spacing:
4
.
h
,
children:
[
Text
(
'
$floorNameNew
楼'
,
floorName
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
isSelected
?
const
Color
.
fromRGBO
(
80
,
162
,
255
,
1
)
:
const
Color
.
fromRGBO
(
0
,
0
,
0
,
1
),
fontWeight:
isSelected
?
FontWeight
.
bold
:
FontWeight
.
normal
,
fontWeight:
isSelected
?
FontWeight
.
bold
:
FontWeight
.
normal
,
),
),
SizedBox
(
height:
4
.
h
),
Container
(
width:
5
0
.
w
,
width:
6
0
.
w
,
height:
3
.
h
,
decoration:
BoxDecoration
(
color:
isSelected
...
...
@@ -100,14 +95,15 @@ class RoomManagementBlock extends StatelessWidget {
),
],
),
)
,
);
}).
toList
(
),
)
;
}).
toList
(),
),
);
}
Widget
_buildTypeSelector
(
ServiceIndexState
state
,
ServiceIndexCubit
cubit
)
{
return
Row
(
spacing:
10
.
w
,
children:
state
.
areas
.
asMap
().
entries
.
map
((
entry
)
{
final
type
=
entry
.
value
;
final
isSelected
=
state
.
selectedType
==
type
;
...
...
@@ -117,7 +113,6 @@ class RoomManagementBlock extends StatelessWidget {
alignment:
Alignment
.
center
,
width:
110
.
w
,
height:
45
.
h
,
margin:
EdgeInsets
.
only
(
right:
10
.
w
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
.
r
),
border:
Border
.
all
(
...
...
@@ -187,16 +182,16 @@ class RoomManagementBlock extends StatelessWidget {
Widget
_buildRoomGrid
(
ServiceIndexState
state
,
ServiceIndexCubit
cubit
)
{
final
filteredRooms
=
cubit
.
getFilteredRooms
();
const
crossAxisCount
=
12
;
final
floorPart
=
state
.
selectedFloor
?.
replaceAll
(
'楼'
,
''
)
??
''
;
final
floorPart
=
state
.
selectedFloor
??
''
;
final
areaLabel
=
state
.
selectedType
??
'全部'
;
final
filterLabel
=
floorPart
.
isNotEmpty
?
'
$areaLabel
·
$floorPart
楼
·左右滑动选择'
?
'
$areaLabel
·
$floorPart
·左右滑动选择'
:
'
$areaLabel
·左右滑动选择'
;
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
spacing:
18
.
h
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
...
...
@@ -217,7 +212,6 @@ class RoomManagementBlock extends StatelessWidget {
),
],
),
SizedBox
(
height:
18
.
h
),
if
(
filteredRooms
.
isEmpty
)
Container
(
width:
double
.
infinity
,
...
...
@@ -237,126 +231,17 @@ class RoomManagementBlock extends StatelessWidget {
),
)
else
SingleChildScrollView
(
scrollDirection:
Axis
.
horizontal
,
child:
SizedBox
(
width:
(
120
.
w
+
12
.
w
)
*
crossAxisCount
,
child:
GridView
.
builder
(
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
padding:
EdgeInsets
.
zero
,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
crossAxisCount
,
crossAxisSpacing:
12
.
w
,
mainAxisSpacing:
12
.
h
,
childAspectRatio:
1
,
),
itemCount:
filteredRooms
.
length
,
itemBuilder:
(
context
,
index
)
{
final
room
=
filteredRooms
[
index
];
final
isSelected
=
state
.
selectedRoom
==
room
[
'number'
];
return
_buildRoomItem
(
room
,
isSelected
,
cubit
);
},
),
),
_RoomGrid
(
filteredRooms:
filteredRooms
,
selectedFloor:
state
.
selectedFloor
,
selectedType:
state
.
selectedType
,
selectedRoom:
state
.
selectedRoom
,
cubit:
cubit
,
),
],
);
}
Widget
_buildRoomItem
(
Map
<
String
,
dynamic
>
room
,
bool
isSelected
,
ServiceIndexCubit
cubit
)
{
final
status
=
room
[
'status'
]
as
String
;
Color
backgroundColor
;
Color
borderColor
;
Color
textColor
;
switch
(
status
)
{
case
'入住'
:
backgroundColor
=
const
Color
.
fromRGBO
(
229
,
241
,
255
,
1
);
borderColor
=
isSelected
?
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
)
:
Colors
.
transparent
;
textColor
=
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
);
break
;
case
'待清洁'
:
backgroundColor
=
const
Color
.
fromRGBO
(
238
,
238
,
238
,
1
);
borderColor
=
isSelected
?
const
Color
.
fromRGBO
(
64
,
158
,
255
,
1
)
:
Colors
.
transparent
;
textColor
=
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
);
break
;
case
'空闲'
:
default
:
backgroundColor
=
const
Color
.
fromRGBO
(
238
,
238
,
238
,
1
);
borderColor
=
isSelected
?
const
Color
.
fromRGBO
(
64
,
158
,
255
,
1
)
:
Colors
.
transparent
;
textColor
=
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
);
}
return
GestureDetector
(
onTap:
()
=>
cubit
.
selectRoom
(
room:
room
[
'number'
]
as
String
,
roomId:
room
[
'roomId'
]
as
int
?,
),
child:
Container
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
16
.
r
),
color:
backgroundColor
,
border:
Border
.
all
(
color:
borderColor
,
width:
isSelected
?
1
.
w
:
0
,
),
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
spacing:
6
.
h
,
children:
[
Text
(
room
[
'number'
]
as
String
,
style:
TextStyle
(
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w500
,
color:
textColor
,
),
textAlign:
TextAlign
.
center
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
),
Builder
(
builder:
(
context
)
{
if
(
status
==
'入住'
)
{
return
Text
(
'入住'
,
style:
TextStyle
(
fontSize:
24
.
sp
,
color:
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
),
),
);
}
else
if
(
status
==
'待清洁'
)
{
return
Image
.
asset
(
'lib/assets/icon/jth.png'
,
width:
24
.
sp
,
height:
30
.
sp
,
);
// return Icon(Icons.cleaning_services_rounded, size: 26.w,
// color: const Color.fromRGBO(255, 138, 139, 1));
}
else
{
return
Text
(
' '
,
style:
TextStyle
(
fontSize:
24
.
sp
,
color:
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
),
),
);
}
},
),
],
),
),
);
}
Widget
_buildRoomDetail
(
ServiceIndexState
state
,
ServiceIndexCubit
cubit
)
{
final
room
=
cubit
.
getSelectedRoomDetail
();
if
(
room
==
null
)
return
const
SizedBox
.
shrink
();
...
...
@@ -428,13 +313,22 @@ class RoomManagementBlock extends StatelessWidget {
}
}
:
null
,
child:
Text
(
'送电'
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
w500
),
),
child:
state
.
isPoweringOn
?
SizedBox
(
width:
32
.
w
,
height:
32
.
w
,
child:
CircularProgressIndicator
(
strokeWidth:
3
.
w
,
color:
const
Color
.
fromRGBO
(
66
,
165
,
245
,
1.0
),
),
)
:
Text
(
'送电'
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
w500
),
),
),
),
Expanded
(
...
...
@@ -446,10 +340,6 @@ class RoomManagementBlock extends StatelessWidget {
padding:
EdgeInsets
.
zero
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
16
.
r
),
// side: BorderSide(
// color: const Color.fromRGBO(73, 149, 234, 1),
// width: 1.w,
// ),
),
elevation:
0
,
),
...
...
@@ -467,13 +357,22 @@ class RoomManagementBlock extends StatelessWidget {
}
}
:
null
,
child:
Text
(
'断电'
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
),
fontWeight:
FontWeight
.
w500
),
),
child:
state
.
isPoweringOff
?
SizedBox
(
width:
32
.
w
,
height:
32
.
w
,
child:
CircularProgressIndicator
(
strokeWidth:
3
.
w
,
color:
const
Color
.
fromRGBO
(
229
,
241
,
255
,
1
),
),
)
:
Text
(
'断电'
,
style:
TextStyle
(
fontSize:
28
.
sp
,
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
),
fontWeight:
FontWeight
.
w500
),
),
),
),
Expanded
(
...
...
@@ -565,3 +464,168 @@ class _SearchBoxState extends State<_SearchBox> {
);
}
}
/// 房间网格 — 独立 StatefulWidget 管理 ScrollController,楼层/区域切换时自动滚回起始位置
class
_RoomGrid
extends
StatefulWidget
{
final
List
<
Map
<
String
,
dynamic
>>
filteredRooms
;
final
String
?
selectedFloor
;
final
String
?
selectedType
;
final
String
?
selectedRoom
;
final
ServiceIndexCubit
cubit
;
const
_RoomGrid
({
required
this
.
filteredRooms
,
required
this
.
selectedFloor
,
required
this
.
selectedType
,
required
this
.
selectedRoom
,
required
this
.
cubit
,
});
@override
State
<
_RoomGrid
>
createState
()
=>
_RoomGridState
();
}
class
_RoomGridState
extends
State
<
_RoomGrid
>
{
final
ScrollController
_scrollController
=
ScrollController
();
String
?
_prevFloor
;
String
?
_prevType
;
@override
void
dispose
()
{
_scrollController
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
// 楼层或区域切换后,房间网格滚回起始位置
if
(
widget
.
selectedFloor
!=
_prevFloor
||
widget
.
selectedType
!=
_prevType
)
{
_prevFloor
=
widget
.
selectedFloor
;
_prevType
=
widget
.
selectedType
;
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
if
(
_scrollController
.
hasClients
)
{
_scrollController
.
jumpTo
(
0
);
}
});
}
const
crossAxisCount
=
12
;
return
SingleChildScrollView
(
controller:
_scrollController
,
scrollDirection:
Axis
.
horizontal
,
child:
SizedBox
(
width:
(
120
.
w
+
12
.
w
)
*
crossAxisCount
,
child:
GridView
.
builder
(
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
padding:
EdgeInsets
.
zero
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
crossAxisCount
,
crossAxisSpacing:
12
,
mainAxisSpacing:
12
,
childAspectRatio:
1
,
),
itemCount:
widget
.
filteredRooms
.
length
,
itemBuilder:
(
context
,
index
)
{
final
room
=
widget
.
filteredRooms
[
index
];
final
isSelected
=
widget
.
selectedRoom
==
room
[
'number'
];
return
_buildRoomItem
(
room
,
isSelected
);
},
),
),
);
}
Widget
_buildRoomItem
(
Map
<
String
,
dynamic
>
room
,
bool
isSelected
)
{
final
status
=
room
[
'status'
]
as
String
;
Color
backgroundColor
;
Color
borderColor
;
Color
textColor
;
switch
(
status
)
{
case
'入住'
:
backgroundColor
=
const
Color
.
fromRGBO
(
229
,
241
,
255
,
1
);
borderColor
=
isSelected
?
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
)
:
Colors
.
transparent
;
textColor
=
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
);
break
;
case
'待清洁'
:
backgroundColor
=
const
Color
.
fromRGBO
(
238
,
238
,
238
,
1
);
borderColor
=
isSelected
?
const
Color
.
fromRGBO
(
64
,
158
,
255
,
1
)
:
Colors
.
transparent
;
textColor
=
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
);
break
;
case
'空闲'
:
default
:
backgroundColor
=
const
Color
.
fromRGBO
(
238
,
238
,
238
,
1
);
borderColor
=
isSelected
?
const
Color
.
fromRGBO
(
64
,
158
,
255
,
1
)
:
Colors
.
transparent
;
textColor
=
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
);
}
return
GestureDetector
(
onTap:
()
=>
widget
.
cubit
.
selectRoom
(
room:
room
[
'number'
]
as
String
,
roomId:
room
[
'roomId'
]
as
int
?,
),
child:
Container
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
16
.
r
),
color:
backgroundColor
,
border:
Border
.
all
(
color:
borderColor
,
width:
isSelected
?
1
.
w
:
0
,
),
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
spacing:
6
.
h
,
children:
[
Text
(
room
[
'number'
]
as
String
,
style:
TextStyle
(
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w500
,
color:
textColor
,
),
textAlign:
TextAlign
.
center
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
),
Builder
(
builder:
(
context
)
{
if
(
status
==
'入住'
)
{
return
Text
(
'入住'
,
style:
TextStyle
(
fontSize:
24
.
sp
,
color:
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
),
),
);
}
else
if
(
status
==
'待清洁'
)
{
return
Image
.
asset
(
'lib/assets/icon/jth.png'
,
width:
24
.
sp
,
height:
30
.
sp
,
);
}
else
{
return
Text
(
' '
,
style:
TextStyle
(
fontSize:
24
.
sp
,
color:
const
Color
.
fromRGBO
(
73
,
149
,
234
,
1
),
),
);
}
},
),
],
),
),
);
}
}
lib/views/service/room/cubit/service_room_cubit.dart
View file @
7ba2abc6
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:smart_hotel_app/models/bo/room_bo.dart'
;
import
'package:smart_hotel_app/repositories/room_repository.dart'
;
...
...
@@ -21,8 +20,9 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
loadDetail
();
}
Future
<
void
>
loadDetail
()
async
{
emit
(
state
.
copyWith
(
isLoading:
true
,
error:
null
));
/// 客房详情接口
Future
<
void
>
loadDetail
({
bool
isLoading
=
true
})
async
{
emit
(
state
.
copyWith
(
isLoading:
isLoading
,
error:
null
));
try
{
final
detail
=
await
_service
.
getRoomDetail
(
roomId:
_roomId
);
emit
(
_mapDetailToState
(
detail
));
...
...
@@ -31,6 +31,7 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
}
}
/// 数据处理
ServiceRoomState
_mapDetailToState
(
RoomDetailBO
detail
)
{
final
basic
=
detail
.
roomBasicInfo
;
final
statusTags
=
_buildStatusTags
(
...
...
@@ -39,13 +40,12 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
wifiOnlineStatus:
basic
.
wifiOnlineStatus
,
);
// 房间设备状态
final
roomDevices
=
detail
.
deviceStatusList
.
where
((
d
)
=>
!
d
.
deviceName
.
contains
(
'分控'
))
.
map
((
d
)
{
return
RoomDevice
(
name:
d
.
deviceName
.
contains
(
'-'
)
?
d
.
deviceName
.
split
(
'-'
).
last
:
d
.
deviceName
,
name:
d
.
deviceName
,
icon:
DealUtils
.
deviceIcon
(
d
.
deviceTypeIcon
),
isOn:
d
.
powerStatus
==
'1'
||
d
.
powerStatus
==
'on'
,
statusText:
_buildDeviceStatusText
(
d
),
...
...
@@ -58,14 +58,13 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
iconMap
[
d
.
deviceId
]
=
d
.
deviceTypeIcon
;
}
final
controlDevices
=
detail
.
deviceControl
.
subDeviceControls
// 分设备控制
final
subDeviceControls
=
detail
.
deviceControl
.
subDeviceControls
.
where
((
c
)
=>
!
c
.
deviceName
.
contains
(
'分控'
))
.
map
((
c
)
{
return
ControlDevice
(
deviceId:
c
.
deviceId
,
name:
c
.
deviceName
.
contains
(
'-'
)
?
c
.
deviceName
.
split
(
'-'
).
last
:
c
.
deviceName
,
name:
c
.
deviceName
,
icon:
DealUtils
.
deviceIcon
(
iconMap
[
c
.
deviceId
]
??
''
),
isOn:
c
.
powerOn
,
detailText:
c
.
currentParamDisplay
.
isNotEmpty
?
c
.
currentParamDisplay
:
null
,
...
...
@@ -80,7 +79,7 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
statusTags:
statusTags
,
roomDevices:
roomDevices
,
mainPowerOn:
detail
.
deviceControl
.
roomPowerControl
.
powerOn
,
controlDevices:
controlDevice
s
,
controlDevices:
subDeviceControl
s
,
);
}
...
...
@@ -129,34 +128,6 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
}
}
/// 房间总电源开关
Future
<
void
>
toggleMainPowerForSwitch
(
bool
value
)
async
{
if
(
state
.
isPoweringOn
||
state
.
isPoweringOff
)
return
;
// final loadingKey = value ? 'isPoweringOn' : 'isPoweringOff';
emit
(
state
.
copyWith
(
isPoweringOn:
value
,
isPoweringOff:
!
value
,
));
try
{
if
(
value
)
{
await
_service
.
roomPowerOn
(
roomId:
_roomId
);
}
else
{
await
_service
.
roomPowerOff
(
roomId:
_roomId
);
}
emit
(
state
.
copyWith
(
isPoweringOn:
false
,
isPoweringOff:
false
,
mainPowerOn:
value
,
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isPoweringOn:
false
,
isPoweringOff:
false
,
));
rethrow
;
}
}
/// 房间总电源开关
Future
<
void
>
toggleMainPower
(
bool
value
)
async
{
if
(
state
.
isPoweringOn
||
state
.
isPoweringOff
)
return
;
...
...
@@ -175,6 +146,7 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
isPoweringOff:
false
,
mainPowerOn:
value
,
));
await
loadDetail
(
isLoading:
false
);
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isPoweringOn:
false
,
...
...
@@ -188,19 +160,33 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
Future
<
void
>
toggleControlDevice
(
int
index
,
bool
value
)
async
{
final
devices
=
state
.
controlDevices
;
if
(
index
<
0
||
index
>=
devices
.
length
)
return
;
// Note: The API for individual device power control uses deviceId.
// However, we don't have deviceId in ControlDevice model yet.
//
For now, just update UI state. The device control card in the view
// navigates to ServiceDeviceDetailRoute for AC control.
final
device
=
devices
[
index
];
//
乐观更新 UI
final
oldDevices
=
devices
;
final
updatedDevices
=
List
<
ControlDevice
>.
from
(
devices
);
updatedDevices
[
index
]
=
ControlDevice
(
deviceId:
updatedDevices
[
index
]
.
deviceId
,
name:
updatedDevices
[
index
]
.
name
,
icon:
updatedDevices
[
index
]
.
icon
,
deviceId:
device
.
deviceId
,
name:
device
.
name
,
icon:
device
.
icon
,
isOn:
value
,
detailText:
updatedDevices
[
index
]
.
detailText
,
deviceTypeName:
updatedDevices
[
index
]
.
deviceTypeName
,
detailText:
device
.
detailText
,
deviceTypeName:
device
.
deviceTypeName
,
);
emit
(
state
.
copyWith
(
controlDevices:
updatedDevices
));
try
{
if
(
value
)
{
await
_service
.
powerSingleOn
(
deviceId:
device
.
deviceId
);
}
else
{
await
_service
.
powerSingleOff
(
deviceId:
device
.
deviceId
);
}
await
loadDetail
(
isLoading:
false
);
}
catch
(
e
)
{
// 失败回滚
emit
(
state
.
copyWith
(
controlDevices:
oldDevices
));
rethrow
;
}
}
}
lib/views/service/room/room_detail_view.dart
View file @
7ba2abc6
...
...
@@ -130,7 +130,7 @@ class ServiceRoomDetailView extends StatelessWidget {
controlDevices:
state
.
controlDevices
,
onMainPowerToggle:
(
value
)
async
{
try
{
await
cubit
.
toggleMainPower
ForSwitch
(
value
);
await
cubit
.
toggleMainPower
(
value
);
if
(
context
.
mounted
)
{
showToast
(
value
?
'送电成功'
:
'断电成功'
);
}
...
...
@@ -143,6 +143,7 @@ class ServiceRoomDetailView extends StatelessWidget {
onDeviceToggle:
(
index
,
value
)
=>
cubit
.
toggleControlDevice
(
index
,
value
),
onDeviceTap:
(
index
,
device
)
{
/// item click
if
(
device
.
name
.
contains
(
'空调'
))
{
context
.
pushRoute
(
ServiceDeviceDetailRoute
(
deviceId:
device
.
deviceId
));
}
...
...
lib/views/service/room/widget/room_bottom_buttons.dart
View file @
7ba2abc6
...
...
@@ -32,14 +32,23 @@ class RoomBottomButtons extends StatelessWidget {
borderRadius:
BorderRadius
.
circular
(
16
.
r
),
),
child:
Center
(
child:
Text
(
'送电'
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w500
,
),
),
child:
isPoweringOn
?
SizedBox
(
width:
32
.
w
,
height:
32
.
w
,
child:
CircularProgressIndicator
(
strokeWidth:
3
.
w
,
color:
const
Color
.
fromRGBO
(
66
,
165
,
245
,
1.0
),
),
)
:
Text
(
'送电'
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w500
,
),
),
),
),
),
...
...
@@ -56,14 +65,23 @@ class RoomBottomButtons extends StatelessWidget {
borderRadius:
BorderRadius
.
circular
(
16
.
r
),
),
child:
Center
(
child:
Text
(
'断电'
,
style:
TextStyle
(
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
),
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w500
,
),
),
child:
isPoweringOff
?
SizedBox
(
width:
32
.
w
,
height:
32
.
w
,
child:
CircularProgressIndicator
(
strokeWidth:
3
.
w
,
color:
const
Color
.
fromRGBO
(
229
,
241
,
255
,
1
),
),
)
:
Text
(
'断电'
,
style:
TextStyle
(
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1
),
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w500
,
),
),
),
),
),
...
...
lib/views/service/room/widget/room_overview_card.dart
View file @
7ba2abc6
...
...
@@ -66,8 +66,8 @@ class RoomOverviewCard extends StatelessWidget {
return
Container
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
.
w
,
vertical:
8
.
h
),
decoration:
BoxDecoration
(
color:
const
Color
.
fromRGBO
(
22
5
,
245
,
238
,
1.0
),
borderRadius:
BorderRadius
.
circular
(
12
.
r
),
color:
const
Color
.
fromRGBO
(
22
9
,
241
,
255
,
1.0
),
borderRadius:
BorderRadius
.
circular
(
24
.
r
),
),
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
...
...
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