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
14c26670
Commit
14c26670
authored
Jun 12, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
3333
parent
70c95cd5
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
229 additions
and
200 deletions
+229
-200
device_topology_bo.dart
lib/models/bo/device_topology_bo.dart
+10
-3
device_cubit.dart
lib/views/home/device/cubit/device_cubit.dart
+21
-6
device_state.dart
lib/views/home/device/cubit/device_state.dart
+5
-0
device_detail_view.dart
lib/views/home/device/device_detail_view.dart
+67
-91
device_list_item.dart
lib/views/home/inspection/widget/device_list_item.dart
+1
-0
inspection_device_view.dart
lib/views/home/inspection_device/inspection_device_view.dart
+12
-8
device_overview_card.dart
...s/home/inspection_device/widget/device_overview_card.dart
+7
-3
inspection_record_item.dart
...ome/inspection_history/widget/inspection_record_item.dart
+1
-0
inspection_topology_cubit.dart
.../inspection_topology/cubit/inspection_topology_cubit.dart
+76
-83
inspection_topology_state.dart
.../inspection_topology/cubit/inspection_topology_state.dart
+11
-1
inspection_topology_view.dart
...ws/home/inspection_topology/inspection_topology_view.dart
+1
-0
device_detail_dialog.dart
...home/inspection_topology/widget/device_detail_dialog.dart
+15
-4
对接方案.md
对接方案.md
+2
-1
No files found.
lib/models/bo/device_topology_bo.dart
View file @
14c26670
...
@@ -24,13 +24,15 @@ class DeviceTopologyInfoBO {
...
@@ -24,13 +24,15 @@ class DeviceTopologyInfoBO {
final
int
deviceId
;
final
int
deviceId
;
final
String
deviceName
;
final
String
deviceName
;
final
String
deviceCode
;
final
String
deviceCode
;
final
String
locationText
;
final
String
deviceLocation
;
final
String
statusText
;
const
DeviceTopologyInfoBO
({
const
DeviceTopologyInfoBO
({
required
this
.
deviceId
,
required
this
.
deviceId
,
required
this
.
deviceName
,
required
this
.
deviceName
,
required
this
.
deviceCode
,
required
this
.
deviceCode
,
required
this
.
locationText
,
required
this
.
deviceLocation
,
required
this
.
statusText
});
});
factory
DeviceTopologyInfoBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
factory
DeviceTopologyInfoBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
...
@@ -38,7 +40,8 @@ class DeviceTopologyInfoBO {
...
@@ -38,7 +40,8 @@ class DeviceTopologyInfoBO {
deviceId:
json
[
'deviceId'
]
as
int
?
??
0
,
deviceId:
json
[
'deviceId'
]
as
int
?
??
0
,
deviceName:
json
[
'deviceName'
]
as
String
?
??
''
,
deviceName:
json
[
'deviceName'
]
as
String
?
??
''
,
deviceCode:
json
[
'deviceCode'
]
as
String
?
??
''
,
deviceCode:
json
[
'deviceCode'
]
as
String
?
??
''
,
locationText:
json
[
'locationText'
]
as
String
?
??
''
,
deviceLocation:
json
[
'deviceLocation'
]
as
String
?
??
''
,
statusText:
json
[
'statusText'
]
as
String
?
??
''
);
);
}
}
}
}
...
@@ -51,6 +54,7 @@ class TreeNodeBO {
...
@@ -51,6 +54,7 @@ class TreeNodeBO {
final
String
status
;
final
String
status
;
final
String
statusText
;
final
String
statusText
;
final
String
deviceCode
;
final
String
deviceCode
;
final
int
?
gatewayId
;
const
TreeNodeBO
({
const
TreeNodeBO
({
required
this
.
deviceId
,
required
this
.
deviceId
,
...
@@ -60,6 +64,7 @@ class TreeNodeBO {
...
@@ -60,6 +64,7 @@ class TreeNodeBO {
required
this
.
status
,
required
this
.
status
,
required
this
.
statusText
,
required
this
.
statusText
,
required
this
.
deviceCode
,
required
this
.
deviceCode
,
this
.
gatewayId
,
});
});
factory
TreeNodeBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
factory
TreeNodeBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
...
@@ -71,6 +76,7 @@ class TreeNodeBO {
...
@@ -71,6 +76,7 @@ class TreeNodeBO {
status:
json
[
'status'
]
as
String
?
??
''
,
status:
json
[
'status'
]
as
String
?
??
''
,
statusText:
json
[
'statusText'
]
as
String
?
??
''
,
statusText:
json
[
'statusText'
]
as
String
?
??
''
,
deviceCode:
json
[
'deviceCode'
]
as
String
?
??
''
,
deviceCode:
json
[
'deviceCode'
]
as
String
?
??
''
,
gatewayId:
json
[
'gatewayId'
]
as
int
?,
);
);
}
}
}
}
\ No newline at end of file
lib/views/home/device/cubit/device_cubit.dart
View file @
14c26670
...
@@ -63,10 +63,15 @@ class DeviceCubit extends Cubit<DeviceState> {
...
@@ -63,10 +63,15 @@ class DeviceCubit extends Cubit<DeviceState> {
emit
(
state
.
copyWith
(
isPoweringOn:
true
));
emit
(
state
.
copyWith
(
isPoweringOn:
true
));
try
{
try
{
await
_service
.
powerOn
(
deviceId:
_deviceId
);
await
_service
.
powerOn
(
deviceId:
_deviceId
);
emit
(
state
.
copyWith
(
isPoweringOn:
false
));
emit
(
state
.
copyWith
(
isPoweringOn:
false
,
operationMessage:
'远程送电成功'
,
));
}
catch
(
e
)
{
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isPoweringOn:
false
));
emit
(
state
.
copyWith
(
rethrow
;
isPoweringOn:
false
,
operationMessage:
'远程送电失败:
$e
'
,
));
}
}
}
}
...
@@ -75,10 +80,19 @@ class DeviceCubit extends Cubit<DeviceState> {
...
@@ -75,10 +80,19 @@ class DeviceCubit extends Cubit<DeviceState> {
emit
(
state
.
copyWith
(
isPoweringOff:
true
));
emit
(
state
.
copyWith
(
isPoweringOff:
true
));
try
{
try
{
await
_service
.
powerOff
(
deviceId:
_deviceId
);
await
_service
.
powerOff
(
deviceId:
_deviceId
);
emit
(
state
.
copyWith
(
isPoweringOff:
false
));
emit
(
state
.
copyWith
(
isPoweringOff:
false
,
operationMessage:
'远程断电成功'
,
));
}
catch
(
e
)
{
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isPoweringOff:
false
));
emit
(
state
.
copyWith
(
rethrow
;
isPoweringOff:
false
,
operationMessage:
'远程断电失败:
$e
'
,
));
}
}
}
}
void
clearOperationMessage
()
{
emit
(
state
.
copyWith
(
operationMessage:
null
));
}
}
}
\ No newline at end of file
lib/views/home/device/cubit/device_state.dart
View file @
14c26670
...
@@ -30,6 +30,7 @@ class DeviceState {
...
@@ -30,6 +30,7 @@ class DeviceState {
final
DeviceInfo
?
deviceInfo
;
final
DeviceInfo
?
deviceInfo
;
final
bool
isPoweringOn
;
final
bool
isPoweringOn
;
final
bool
isPoweringOff
;
final
bool
isPoweringOff
;
final
String
?
operationMessage
;
const
DeviceState
({
const
DeviceState
({
this
.
isLoading
=
false
,
this
.
isLoading
=
false
,
...
@@ -37,6 +38,7 @@ class DeviceState {
...
@@ -37,6 +38,7 @@ class DeviceState {
this
.
deviceInfo
,
this
.
deviceInfo
,
this
.
isPoweringOn
=
false
,
this
.
isPoweringOn
=
false
,
this
.
isPoweringOff
=
false
,
this
.
isPoweringOff
=
false
,
this
.
operationMessage
,
});
});
DeviceState
copyWith
({
DeviceState
copyWith
({
...
@@ -45,6 +47,7 @@ class DeviceState {
...
@@ -45,6 +47,7 @@ class DeviceState {
DeviceInfo
?
deviceInfo
,
DeviceInfo
?
deviceInfo
,
bool
?
isPoweringOn
,
bool
?
isPoweringOn
,
bool
?
isPoweringOff
,
bool
?
isPoweringOff
,
String
?
operationMessage
,
})
{
})
{
return
DeviceState
(
return
DeviceState
(
isLoading:
isLoading
??
this
.
isLoading
,
isLoading:
isLoading
??
this
.
isLoading
,
...
@@ -52,6 +55,7 @@ class DeviceState {
...
@@ -52,6 +55,7 @@ class DeviceState {
deviceInfo:
deviceInfo
??
this
.
deviceInfo
,
deviceInfo:
deviceInfo
??
this
.
deviceInfo
,
isPoweringOn:
isPoweringOn
??
this
.
isPoweringOn
,
isPoweringOn:
isPoweringOn
??
this
.
isPoweringOn
,
isPoweringOff:
isPoweringOff
??
this
.
isPoweringOff
,
isPoweringOff:
isPoweringOff
??
this
.
isPoweringOff
,
operationMessage:
operationMessage
??
this
.
operationMessage
,
);
);
}
}
}
}
\ No newline at end of file
lib/views/home/device/device_detail_view.dart
View file @
14c26670
...
@@ -18,65 +18,76 @@ class DeviceDetailView extends StatelessWidget {
...
@@ -18,65 +18,76 @@ class DeviceDetailView extends StatelessWidget {
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
BlocProvider
(
return
BlocProvider
(
create:
(
_
)
=>
DeviceCubit
(
deviceId:
deviceId
),
create:
(
_
)
=>
DeviceCubit
(
deviceId:
deviceId
),
child:
Scaffold
(
child:
BlocListener
<
DeviceCubit
,
DeviceState
>(
backgroundColor:
const
Color
.
fromRGBO
(
242
,
243
,
245
,
1
),
listener:
(
context
,
state
)
{
appBar:
AppBar
(
final
message
=
state
.
operationMessage
;
backgroundColor:
Colors
.
white
,
if
(
message
!=
null
)
{
elevation:
0
,
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
leading:
IconButton
(
SnackBar
(
content:
Text
(
message
)),
icon:
const
Icon
(
Icons
.
arrow_back_ios
,
);
color:
Color
.
fromRGBO
(
100
,
116
,
139
,
1
)),
context
.
read
<
DeviceCubit
>().
clearOperationMessage
();
onPressed:
()
{
}
context
.
popRoute
();
},
},
child:
Scaffold
(
),
backgroundColor:
const
Color
.
fromRGBO
(
242
,
243
,
245
,
1
),
title:
Text
(
appBar:
AppBar
(
'设备控制'
,
backgroundColor:
Colors
.
white
,
style:
TextStyle
(
elevation:
0
,
color:
const
Color
.
fromRGBO
(
10
,
13
,
20
,
1
),
leading:
IconButton
(
fontSize:
32
.
sp
,
icon:
const
Icon
(
Icons
.
arrow_back_ios
,
fontWeight:
FontWeight
.
w200
,
color:
Color
.
fromRGBO
(
100
,
116
,
139
,
1
)),
onPressed:
()
{
context
.
maybePop
();
},
),
title:
Text
(
'设备控制'
,
style:
TextStyle
(
color:
const
Color
.
fromRGBO
(
10
,
13
,
20
,
1
),
fontSize:
32
.
sp
,
fontWeight:
FontWeight
.
w200
,
),
),
),
centerTitle:
true
,
),
),
centerTitle:
true
,
body:
BlocBuilder
<
DeviceCubit
,
DeviceState
>(
),
builder:
(
context
,
state
)
{
body:
BlocBuilder
<
DeviceCubit
,
DeviceState
>(
if
(
state
.
isLoading
)
{
builder:
(
context
,
state
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
if
(
state
.
isLoading
)
{
}
return
const
Center
(
child:
CircularProgressIndicator
());
if
(
state
.
error
!=
null
)
{
}
return
Center
(
if
(
state
.
error
!=
null
)
{
child:
Text
(
return
Center
(
'加载失败:
${state.error}
'
,
child:
Text
(
style:
TextStyle
(
'加载失败:
${state.error}
'
,
fontSize:
28
.
sp
,
style:
TextStyle
(
color:
const
Color
.
fromRGBO
(
255
,
100
,
101
,
1
),
fontSize:
28
.
sp
,
),
color:
const
Color
.
fromRGBO
(
255
,
100
,
101
,
1
),
),
),
);
}
final
deviceInfo
=
state
.
deviceInfo
;
if
(
deviceInfo
==
null
)
{
return
const
SizedBox
.
shrink
();
}
final
cubit
=
context
.
read
<
DeviceCubit
>();
return
SingleChildScrollView
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
28
.
w
),
child:
Column
(
children:
[
StateInfoBlock
(
deviceInfo:
deviceInfo
),
SizedBox
(
height:
10
.
h
),
DeviceParmsBlock
(
deviceInfo:
deviceInfo
),
SizedBox
(
height:
10
.
h
),
PowerCharBlock
(
deviceInfo:
deviceInfo
),
SizedBox
(
height:
10
.
h
),
_buildBottomButtons
(
context
,
cubit
,
state
),
SizedBox
(
height:
30
.
h
),
],
),
),
);
);
}
},
final
deviceInfo
=
state
.
deviceInfo
;
),
if
(
deviceInfo
==
null
)
{
return
const
SizedBox
.
shrink
();
}
final
cubit
=
context
.
read
<
DeviceCubit
>();
return
SingleChildScrollView
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
28
.
w
),
child:
Column
(
children:
[
StateInfoBlock
(
deviceInfo:
deviceInfo
),
SizedBox
(
height:
10
.
h
),
DeviceParmsBlock
(
deviceInfo:
deviceInfo
),
SizedBox
(
height:
10
.
h
),
PowerCharBlock
(
deviceInfo:
deviceInfo
),
SizedBox
(
height:
10
.
h
),
_buildBottomButtons
(
context
,
cubit
,
state
),
SizedBox
(
height:
30
.
h
),
],
),
);
},
),
),
),
),
);
);
...
@@ -103,7 +114,7 @@ class DeviceDetailView extends StatelessWidget {
...
@@ -103,7 +114,7 @@ class DeviceDetailView extends StatelessWidget {
child:
TextButton
(
child:
TextButton
(
onPressed:
state
.
isPoweringOn
onPressed:
state
.
isPoweringOn
?
null
?
null
:
()
=>
_handlePowerOn
(
context
,
cubit
),
:
()
=>
cubit
.
remotePowerOn
(
),
child:
state
.
isPoweringOn
child:
state
.
isPoweringOn
?
SizedBox
(
?
SizedBox
(
width:
32
.
sp
,
width:
32
.
sp
,
...
@@ -140,7 +151,7 @@ class DeviceDetailView extends StatelessWidget {
...
@@ -140,7 +151,7 @@ class DeviceDetailView extends StatelessWidget {
child:
TextButton
(
child:
TextButton
(
onPressed:
state
.
isPoweringOff
onPressed:
state
.
isPoweringOff
?
null
?
null
:
()
=>
_handlePowerOff
(
context
,
cubit
),
:
()
=>
cubit
.
remotePowerOff
(
),
child:
state
.
isPoweringOff
child:
state
.
isPoweringOff
?
SizedBox
(
?
SizedBox
(
width:
32
.
sp
,
width:
32
.
sp
,
...
@@ -162,38 +173,4 @@ class DeviceDetailView extends StatelessWidget {
...
@@ -162,38 +173,4 @@ class DeviceDetailView extends StatelessWidget {
),
),
);
);
}
}
Future
<
void
>
_handlePowerOn
(
BuildContext
context
,
DeviceCubit
cubit
)
async
{
try
{
await
cubit
.
remotePowerOn
();
if
(
context
.
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'远程送电成功'
)),
);
}
}
catch
(
e
)
{
if
(
context
.
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'远程送电失败:
$e
'
)),
);
}
}
}
Future
<
void
>
_handlePowerOff
(
BuildContext
context
,
DeviceCubit
cubit
)
async
{
try
{
await
cubit
.
remotePowerOff
();
if
(
context
.
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'远程断电成功'
)),
);
}
}
catch
(
e
)
{
if
(
context
.
mounted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'远程断电失败:
$e
'
)),
);
}
}
}
}
}
\ No newline at end of file
lib/views/home/inspection/widget/device_list_item.dart
View file @
14c26670
...
@@ -22,6 +22,7 @@ class DeviceListItem extends StatelessWidget {
...
@@ -22,6 +22,7 @@ class DeviceListItem extends StatelessWidget {
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
),
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
20
.
w
,
vertical:
16
.
h
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
20
.
w
,
vertical:
16
.
h
),
margin:
EdgeInsets
.
only
(
bottom:
20
.
h
),
child:
Row
(
child:
Row
(
children:
[
children:
[
Container
(
Container
(
...
...
lib/views/home/inspection_device/inspection_device_view.dart
View file @
14c26670
...
@@ -80,14 +80,18 @@ class InspectionDeviceView extends StatelessWidget {
...
@@ -80,14 +80,18 @@ class InspectionDeviceView extends StatelessWidget {
lastInspectionTime:
state
.
lastInspectionTime
,
lastInspectionTime:
state
.
lastInspectionTime
,
icon:
state
.
icon
,
icon:
state
.
icon
,
),
),
SizedBox
(
height:
20
.
h
),
if
(
state
.
inspectionHistory
.
isNotEmpty
)
...[
InspectionHistoryCard
(
SizedBox
(
height:
20
.
h
),
historyList:
state
.
inspectionHistory
,
InspectionHistoryCard
(
),
historyList:
state
.
inspectionHistory
,
// SizedBox(height: 20.h),
),
InspectionItemsCard
(
],
items:
state
.
inspectionItems
,
if
(
state
.
inspectionItems
.
isNotEmpty
)
...[
),
SizedBox
(
height:
20
.
h
),
InspectionItemsCard
(
items:
state
.
inspectionItems
,
),
],
SizedBox
(
height:
30
.
h
),
SizedBox
(
height:
30
.
h
),
StartInspectionButton
(
deviceId:
state
.
deviceId
),
StartInspectionButton
(
deviceId:
state
.
deviceId
),
SizedBox
(
height:
20
.
h
),
SizedBox
(
height:
20
.
h
),
...
...
lib/views/home/inspection_device/widget/device_overview_card.dart
View file @
14c26670
...
@@ -76,14 +76,18 @@ class DeviceOverviewCard extends StatelessWidget {
...
@@ -76,14 +76,18 @@ class DeviceOverviewCard extends StatelessWidget {
Container
(
Container
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
24
.
w
,
vertical:
10
.
h
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
24
.
w
,
vertical:
10
.
h
),
decoration:
BoxDecoration
(
decoration:
BoxDecoration
(
color:
const
Color
.
fromRGBO
(
225
,
245
,
238
,
1.0
),
color:
deviceStatus
==
'1'
?
const
Color
.
fromRGBO
(
225
,
245
,
238
,
1.0
)
:
const
Color
.
fromRGBO
(
255
,
235
,
238
,
1.0
),
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
),
),
child:
Text
(
child:
Text
(
deviceStatus
,
deviceStatus
==
'1'
?
'在线'
:
'离线'
,
style:
TextStyle
(
style:
TextStyle
(
fontSize:
28
.
sp
,
fontSize:
28
.
sp
,
color:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
color:
deviceStatus
==
'1'
?
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
)
:
const
Color
.
fromRGBO
(
231
,
76
,
60
,
1.0
),
fontWeight:
FontWeight
.
bold
,
fontWeight:
FontWeight
.
bold
,
),
),
),
),
...
...
lib/views/home/inspection_history/widget/inspection_record_item.dart
View file @
14c26670
...
@@ -18,6 +18,7 @@ class InspectionRecordItem extends StatelessWidget {
...
@@ -18,6 +18,7 @@ class InspectionRecordItem extends StatelessWidget {
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
),
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
20
.
w
,
vertical:
16
.
h
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
20
.
w
,
vertical:
16
.
h
),
margin:
EdgeInsets
.
only
(
bottom:
20
.
h
),
child:
Column
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
children:
[
...
...
lib/views/home/inspection_topology/cubit/inspection_topology_cubit.dart
View file @
14c26670
...
@@ -41,111 +41,104 @@ class InspectionTopologyCubit extends Cubit<InspectionTopologyState> {
...
@@ -41,111 +41,104 @@ class InspectionTopologyCubit extends Cubit<InspectionTopologyState> {
final
rootNode
=
_buildTopologyTree
(
nodes
);
final
rootNode
=
_buildTopologyTree
(
nodes
);
// 计算连接数: 父节点到每个子节点一条连接
int
connectionCount
=
0
;
for
(
final
child
in
rootNode
.
children
)
{
connectionCount
++;
// 根到子
connectionCount
+=
child
.
children
.
length
;
// 子到孙
}
emit
(
InspectionTopologyState
(
emit
(
InspectionTopologyState
(
deviceName:
info
.
deviceName
,
deviceName:
info
.
deviceName
,
deviceModel:
'
${info.deviceCode}
·
${info.
locationText
}
'
,
deviceModel:
'
${info.deviceCode}
·
${info.
deviceLocation
}
'
,
topologyNodes:
[
rootNode
],
topologyNodes:
[
rootNode
],
deviceCount:
_countAllNodes
(
rootNode
),
deviceCount:
_countAllNodes
(
rootNode
),
connectionCount:
connectionCount
,
connectionCount:
_countConnections
(
rootNode
),
// statusText:
));
));
}
}
///
将 API 返回的扁平节点列表转换为树结构 + 自动计算
位置
///
根据 gatewayId 将扁平节点列表构建为树结构,并自动计算布局
位置
TopologyNode
_buildTopologyTree
(
List
<
TreeNodeBO
>
nodes
)
{
TopologyNode
_buildTopologyTree
(
List
<
TreeNodeBO
>
nodes
)
{
if
(
nodes
.
isEmpty
)
{
if
(
nodes
.
isEmpty
)
{
return
TopologyNode
(
return
const
TopologyNode
(
id:
'0'
,
id:
'0'
,
name:
'未知设备'
,
name:
'未知设备'
,
status:
'offline'
,
status:
'offline'
,
children:
[],
children:
[],
x:
0.5
,
x:
0.5
,
y:
0.
15
,
y:
0.
08
,
);
);
}
}
// 第一个节点作为根节点
// 构建子节点映射表:parent deviceId -> [child TreeNodeBO]
final
root
=
nodes
.
first
;
final
childrenMap
=
<
int
,
List
<
TreeNodeBO
>>{};
final
children
=
nodes
.
length
>
1
?
nodes
.
sublist
(
1
)
:
<
TreeNodeBO
>[];
final
nodeMap
=
<
int
,
TreeNodeBO
>{};
for
(
final
node
in
nodes
)
{
// 如果子节点 <= 6,平分到第二层;否则将前6个放第二层,其余作为叶子
nodeMap
[
node
.
deviceId
]
=
node
;
final
int
maxLevel2
=
6
;
final
parentId
=
node
.
gatewayId
??
0
;
final
level2Nodes
=
children
.
take
(
maxLevel2
).
toList
();
childrenMap
.
putIfAbsent
(
parentId
,
()
=>
[]).
add
(
node
);
final
leafNodes
=
children
.
length
>
maxLevel2
}
?
children
.
sublist
(
maxLevel2
)
:
<
TreeNodeBO
>[];
// 查找根节点:gatewayId 为 null/0,或引用的父节点不存在于列表中
final
nodeIds
=
nodeMap
.
keys
.
toSet
();
final
rootNode
=
TopologyNode
(
final
roots
=
nodes
.
where
((
n
)
=>
id:
root
.
deviceId
.
toString
(),
n
.
gatewayId
==
null
||
name:
root
.
deviceName
,
n
.
gatewayId
==
0
||
status:
root
.
status
,
!
nodeIds
.
contains
(
n
.
gatewayId
),
x:
0.5
,
).
toList
();
y:
0.15
,
children:
level2Nodes
.
asMap
().
entries
.
map
((
entry
)
{
if
(
roots
.
isEmpty
)
{
final
idx
=
entry
.
key
;
// fallback:取第一个节点作为根
final
node
=
entry
.
value
;
roots
.
add
(
nodes
.
first
);
// 在第二层均匀分布
}
final
xPos
=
(
level2Nodes
.
length
==
1
)
?
0.5
// 递归构建节点并计算布局位置
:
0.15
+
(
0.7
*
idx
/
(
level2Nodes
.
length
-
1
));
TopologyNode
buildNode
(
TreeNodeBO
node
,
int
depth
,
double
xCenter
,
double
xSpan
)
{
return
TopologyNode
(
final
children
=
childrenMap
[
node
.
deviceId
]
??
[];
id:
node
.
deviceId
.
toString
(),
final
childNodes
=
<
TopologyNode
>[];
name:
node
.
deviceName
,
status:
node
.
status
,
if
(
children
.
isNotEmpty
)
{
x:
xPos
,
for
(
int
i
=
0
;
i
<
children
.
length
;
i
++)
{
y:
0.45
,
final
childX
=
children
.
length
==
1
children:
[],
?
xCenter
);
:
xCenter
-
xSpan
/
2
+
(
xSpan
*
i
/
(
children
.
length
-
1
));
}).
toList
(),
childNodes
.
add
(
buildNode
(
children
[
i
],
depth
+
1
,
childX
,
xSpan
*
0.55
));
);
}
}
// 补充叶子节点:附着到最近的第二层节点上
if
(
leafNodes
.
isNotEmpty
&&
rootNode
.
children
.
isNotEmpty
)
{
return
TopologyNode
(
final
leafNode
=
leafNodes
.
first
;
id:
node
.
deviceId
.
toString
(),
final
lastLevel2Idx
=
rootNode
.
children
.
length
-
1
;
name:
node
.
deviceName
,
final
lastLevel2
=
rootNode
.
children
[
lastLevel2Idx
];
status:
node
.
status
,
final
updatedChildren
=
List
<
TopologyNode
>.
from
(
rootNode
.
children
);
statusText:
node
.
statusText
,
updatedChildren
[
lastLevel2Idx
]
=
TopologyNode
(
gatewayId:
node
.
gatewayId
?.
toString
(),
id:
lastLevel2
.
id
,
x:
xCenter
.
clamp
(
0.05
,
0.95
),
name:
lastLevel2
.
name
,
y:
(
0.08
+
depth
*
0.17
).
clamp
(
0.02
,
0.98
),
status:
lastLevel2
.
status
,
children:
childNodes
,
x:
lastLevel2
.
x
,
y:
lastLevel2
.
y
,
children:
leafNodes
.
asMap
().
entries
.
map
((
entry
)
{
final
idx
=
entry
.
key
;
final
leaf
=
entry
.
value
;
final
xPos
=
(
leafNodes
.
length
==
1
)
?
lastLevel2
.
x
:
lastLevel2
.
x
-
0.1
+
(
0.2
*
idx
/
(
leafNodes
.
length
-
1
));
return
TopologyNode
(
id:
leaf
.
deviceId
.
toString
(),
name:
leaf
.
deviceName
,
status:
leaf
.
status
,
x:
xPos
,
y:
0.75
,
children:
[],
);
}).
toList
(),
);
);
}
if
(
roots
.
length
==
1
)
{
return
buildNode
(
roots
.
first
,
0
,
0.5
,
0.7
);
}
else
{
// 多个根节点:创建一个虚拟根,将所有根均匀分布在第二层
final
virtualChildren
=
<
TopologyNode
>[];
for
(
int
i
=
0
;
i
<
roots
.
length
;
i
++)
{
final
xPos
=
0.1
+
(
0.8
*
i
/
(
roots
.
length
-
1
));
virtualChildren
.
add
(
buildNode
(
roots
[
i
],
1
,
xPos
,
0.35
));
}
return
TopologyNode
(
return
TopologyNode
(
id:
rootNode
.
id
,
id:
'0'
,
name:
rootNode
.
name
,
name:
'根节点'
,
status:
rootNode
.
status
,
status:
'online'
,
x:
rootNode
.
x
,
x:
0.5
,
y:
rootNode
.
y
,
y:
0.08
,
children:
updated
Children
,
children:
virtual
Children
,
);
);
}
}
}
return
rootNode
;
/// 递归计算连接数
int
_countConnections
(
TopologyNode
node
)
{
int
count
=
0
;
for
(
final
child
in
node
.
children
)
{
count
+=
1
+
_countConnections
(
child
);
}
return
count
;
}
}
int
_countAllNodes
(
TopologyNode
node
)
{
int
_countAllNodes
(
TopologyNode
node
)
{
...
...
lib/views/home/inspection_topology/cubit/inspection_topology_state.dart
View file @
14c26670
...
@@ -10,6 +10,7 @@ class InspectionTopologyState extends Equatable {
...
@@ -10,6 +10,7 @@ class InspectionTopologyState extends Equatable {
final
int
deviceCount
;
final
int
deviceCount
;
final
int
connectionCount
;
final
int
connectionCount
;
final
TopologyNode
?
selectedNode
;
final
TopologyNode
?
selectedNode
;
final
String
statusText
;
const
InspectionTopologyState
({
const
InspectionTopologyState
({
this
.
isLoading
=
false
,
this
.
isLoading
=
false
,
...
@@ -20,6 +21,7 @@ class InspectionTopologyState extends Equatable {
...
@@ -20,6 +21,7 @@ class InspectionTopologyState extends Equatable {
this
.
deviceCount
=
0
,
this
.
deviceCount
=
0
,
this
.
connectionCount
=
0
,
this
.
connectionCount
=
0
,
this
.
selectedNode
,
this
.
selectedNode
,
this
.
statusText
=
''
});
});
InspectionTopologyState
copyWith
({
InspectionTopologyState
copyWith
({
...
@@ -31,6 +33,7 @@ class InspectionTopologyState extends Equatable {
...
@@ -31,6 +33,7 @@ class InspectionTopologyState extends Equatable {
int
?
deviceCount
,
int
?
deviceCount
,
int
?
connectionCount
,
int
?
connectionCount
,
TopologyNode
?
selectedNode
,
TopologyNode
?
selectedNode
,
String
?
statusText
,
})
{
})
{
return
InspectionTopologyState
(
return
InspectionTopologyState
(
isLoading:
isLoading
??
this
.
isLoading
,
isLoading:
isLoading
??
this
.
isLoading
,
...
@@ -41,6 +44,7 @@ class InspectionTopologyState extends Equatable {
...
@@ -41,6 +44,7 @@ class InspectionTopologyState extends Equatable {
deviceCount:
deviceCount
??
this
.
deviceCount
,
deviceCount:
deviceCount
??
this
.
deviceCount
,
connectionCount:
connectionCount
??
this
.
connectionCount
,
connectionCount:
connectionCount
??
this
.
connectionCount
,
selectedNode:
selectedNode
,
selectedNode:
selectedNode
,
statusText:
statusText
??
this
.
statusText
,
);
);
}
}
...
@@ -54,6 +58,7 @@ class InspectionTopologyState extends Equatable {
...
@@ -54,6 +58,7 @@ class InspectionTopologyState extends Equatable {
deviceCount
,
deviceCount
,
connectionCount
,
connectionCount
,
selectedNode
,
selectedNode
,
statusText
];
];
}
}
...
@@ -64,6 +69,8 @@ class TopologyNode extends Equatable {
...
@@ -64,6 +69,8 @@ class TopologyNode extends Equatable {
final
List
<
TopologyNode
>
children
;
final
List
<
TopologyNode
>
children
;
final
double
x
;
final
double
x
;
final
double
y
;
final
double
y
;
final
String
?
gatewayId
;
final
String
?
statusText
;
const
TopologyNode
({
const
TopologyNode
({
required
this
.
id
,
required
this
.
id
,
...
@@ -72,8 +79,10 @@ class TopologyNode extends Equatable {
...
@@ -72,8 +79,10 @@ class TopologyNode extends Equatable {
required
this
.
children
,
required
this
.
children
,
required
this
.
x
,
required
this
.
x
,
required
this
.
y
,
required
this
.
y
,
this
.
gatewayId
,
this
.
statusText
,
});
});
@override
@override
List
<
Object
?>
get
props
=>
[
id
,
name
,
status
,
children
,
x
,
y
];
List
<
Object
?>
get
props
=>
[
id
,
name
,
status
,
children
,
x
,
y
,
gatewayId
,
statusText
];
}
}
\ No newline at end of file
lib/views/home/inspection_topology/inspection_topology_view.dart
View file @
14c26670
...
@@ -118,6 +118,7 @@ class InspectionTopologyPage extends StatelessWidget {
...
@@ -118,6 +118,7 @@ class InspectionTopologyPage extends StatelessWidget {
if
(
state
.
selectedNode
!=
null
)
if
(
state
.
selectedNode
!=
null
)
DeviceDetailDialog
(
DeviceDetailDialog
(
node:
state
.
selectedNode
!,
node:
state
.
selectedNode
!,
curentState:
state
,
onClose:
()
=>
onClose:
()
=>
context
.
read
<
InspectionTopologyCubit
>().
selectNode
(
null
),
context
.
read
<
InspectionTopologyCubit
>().
selectNode
(
null
),
),
),
...
...
lib/views/home/inspection_topology/widget/device_detail_dialog.dart
View file @
14c26670
import
'package:auto_route/auto_route.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:smart_hotel_app/routes/app_router.gr.dart'
;
import
'../cubit/inspection_topology_state.dart'
;
import
'../cubit/inspection_topology_state.dart'
;
class
DeviceDetailDialog
extends
StatelessWidget
{
class
DeviceDetailDialog
extends
StatelessWidget
{
final
TopologyNode
node
;
final
TopologyNode
node
;
final
InspectionTopologyState
curentState
;
final
VoidCallback
onClose
;
final
VoidCallback
onClose
;
const
DeviceDetailDialog
({
const
DeviceDetailDialog
({
super
.
key
,
super
.
key
,
required
this
.
node
,
required
this
.
node
,
required
this
.
curentState
,
required
this
.
onClose
,
required
this
.
onClose
,
});
});
...
@@ -87,7 +91,7 @@ class DeviceDetailDialog extends StatelessWidget {
...
@@ -87,7 +91,7 @@ class DeviceDetailDialog extends StatelessWidget {
),
),
SizedBox
(
height:
12
.
h
),
SizedBox
(
height:
12
.
h
),
Text
(
Text
(
'传感器'
,
curentState
.
deviceModel
,
style:
TextStyle
(
style:
TextStyle
(
fontSize:
24
.
sp
,
fontSize:
24
.
sp
,
color:
const
Color
.
fromRGBO
(
148
,
163
,
184
,
1
),
color:
const
Color
.
fromRGBO
(
148
,
163
,
184
,
1
),
...
@@ -106,7 +110,7 @@ class DeviceDetailDialog extends StatelessWidget {
...
@@ -106,7 +110,7 @@ class DeviceDetailDialog extends StatelessWidget {
borderRadius:
BorderRadius
.
circular
(
40
.
r
),
borderRadius:
BorderRadius
.
circular
(
40
.
r
),
),
),
child:
Text
(
child:
Text
(
'正常
'
,
node
.
statusText
??
'-
'
,
style:
TextStyle
(
style:
TextStyle
(
fontSize:
24
.
sp
,
fontSize:
24
.
sp
,
fontWeight:
FontWeight
.
w500
,
fontWeight:
FontWeight
.
w500
,
...
@@ -141,7 +145,7 @@ class DeviceDetailDialog extends StatelessWidget {
...
@@ -141,7 +145,7 @@ class DeviceDetailDialog extends StatelessWidget {
),
),
),
),
Text
(
Text
(
'
dev-
${node.id.padLeft(3, '0')
}
'
,
'
${node.id
}
'
,
style:
TextStyle
(
style:
TextStyle
(
fontSize:
24
.
sp
,
fontSize:
24
.
sp
,
fontWeight:
FontWeight
.
w500
,
fontWeight:
FontWeight
.
w500
,
...
@@ -179,7 +183,14 @@ class DeviceDetailDialog extends StatelessWidget {
...
@@ -179,7 +183,14 @@ class DeviceDetailDialog extends StatelessWidget {
width:
double
.
infinity
,
width:
double
.
infinity
,
height:
104
.
h
,
height:
104
.
h
,
child:
ElevatedButton
(
child:
ElevatedButton
(
onPressed:
()
{},
onPressed:
()
{
final
deviceId
=
int
.
tryParse
(
node
.
id
)
??
0
;
if
(
deviceId
>
0
)
{
context
.
pushRoute
(
DeviceDetailRoute
(
deviceId:
deviceId
),
);
}
},
style:
ElevatedButton
.
styleFrom
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
const
Color
.
fromRGBO
(
59
,
130
,
246
,
1
),
backgroundColor:
const
Color
.
fromRGBO
(
59
,
130
,
246
,
1
),
shape:
RoundedRectangleBorder
(
shape:
RoundedRectangleBorder
(
...
...
对接方案.md
View file @
14c26670
# 智慧酒
店 App 接口对接方案
# 智慧酒
店 App 接口对接方案
...
@@ -1153,6 +1153,7 @@ class xxxBO extends Equatable {
...
@@ -1153,6 +1153,7 @@ class xxxBO extends Equatable {
{
{
"deviceId": 0,
"deviceId": 0,
"deviceName": "string",
"deviceName": "string",
"gatewayId":0, // 父级id
"deviceType": "string",
"deviceType": "string",
"icon": "string",
"icon": "string",
"status": "string",
"status": "string",
...
...
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