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
b95e2acc
Commit
b95e2acc
authored
Jun 05, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ui调整
parent
5bd72d8e
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
908 additions
and
45 deletions
+908
-45
inspection_cubit.dart
lib/views/home/inspection/cubit/inspection_cubit.dart
+28
-28
inspection_state.dart
lib/views/home/inspection/cubit/inspection_state.dart
+1
-1
filter_panel.dart
lib/views/home/inspection/widget/filter_panel.dart
+6
-6
inspection_device_view.dart
lib/views/home/inspection_device/inspection_device_view.dart
+30
-0
start_inspection_button.dart
...ome/inspection_device/widget/start_inspection_button.dart
+39
-3
profile_menu_block.dart
lib/views/profile/index/widget/profile_menu_block.dart
+11
-7
上手帮助.md
上手帮助.md
+390
-0
技术方案.md
技术方案.md
+403
-0
No files found.
lib/views/home/inspection/cubit/inspection_cubit.dart
View file @
b95e2acc
...
@@ -4,21 +4,21 @@ import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dar
...
@@ -4,21 +4,21 @@ import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dar
class
InspectionCubit
extends
Cubit
<
InspectionState
>
{
class
InspectionCubit
extends
Cubit
<
InspectionState
>
{
static
const
List
<
String
>
allRooms
=
[
static
const
List
<
String
>
allRooms
=
[
'
客厅
'
,
'
101
'
,
'
主卧
'
,
'
102
'
,
'
玄关
'
,
'
103
'
,
'
厨房
'
,
'
104
'
,
'
书房
'
,
'
105
'
,
'
阳台
'
,
'
106
'
,
];
];
static
final
List
<
InspectionDevice
>
allDevices
=
[
static
final
List
<
InspectionDevice
>
allDevices
=
[
InspectionDevice
(
InspectionDevice
(
id:
'1'
,
id:
'1'
,
icon:
Icons
.
wifi
,
icon:
Icons
.
wifi
,
name:
'
客厅
主控网关'
,
name:
'
101
主控网关'
,
type:
'智能网关'
,
type:
'智能网关'
,
location:
'
客厅
电视柜'
,
location:
'
101
电视柜'
,
status:
'正常'
,
status:
'正常'
,
time:
'2024-01-15 14:30:00'
,
time:
'2024-01-15 14:30:00'
,
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
...
@@ -26,9 +26,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -26,9 +26,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'2'
,
id:
'2'
,
icon:
Icons
.
thermostat
,
icon:
Icons
.
thermostat
,
name:
'温湿度传感器-
客厅
'
,
name:
'温湿度传感器-
101
'
,
type:
'传感器'
,
type:
'传感器'
,
location:
'
客厅
角落'
,
location:
'
101
角落'
,
status:
'正常'
,
status:
'正常'
,
time:
'2024-01-15 14:25:00'
,
time:
'2024-01-15 14:25:00'
,
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
...
@@ -36,9 +36,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -36,9 +36,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'3'
,
id:
'3'
,
icon:
Icons
.
videocam
,
icon:
Icons
.
videocam
,
name:
'智能摄像头-
门口
'
,
name:
'智能摄像头-
102
'
,
type:
'摄像头'
,
type:
'摄像头'
,
location:
'
入户
门口'
,
location:
'
102
门口'
,
status:
'告警'
,
status:
'告警'
,
time:
'2024-01-15 10:00:00'
,
time:
'2024-01-15 10:00:00'
,
statusColor:
const
Color
.
fromRGBO
(
255
,
77
,
79
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
255
,
77
,
79
,
1.0
),
...
@@ -46,9 +46,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -46,9 +46,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'4'
,
id:
'4'
,
icon:
Icons
.
light
,
icon:
Icons
.
light
,
name:
'智能吸顶灯-
主卧
'
,
name:
'智能吸顶灯-
102
'
,
type:
'传感器'
,
type:
'传感器'
,
location:
'
主卧
天花板'
,
location:
'
102
天花板'
,
status:
'正常'
,
status:
'正常'
,
time:
'2024-01-15 14:25:00'
,
time:
'2024-01-15 14:25:00'
,
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
...
@@ -56,9 +56,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -56,9 +56,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'5'
,
id:
'5'
,
icon:
Icons
.
thermostat
,
icon:
Icons
.
thermostat
,
name:
'温湿度传感器-
主卧
'
,
name:
'温湿度传感器-
103
'
,
type:
'传感器'
,
type:
'传感器'
,
location:
'
主卧
角落'
,
location:
'
103
角落'
,
status:
'正常'
,
status:
'正常'
,
time:
'2024-01-15 14:25:00'
,
time:
'2024-01-15 14:25:00'
,
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
...
@@ -66,9 +66,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -66,9 +66,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'6'
,
id:
'6'
,
icon:
Icons
.
light
,
icon:
Icons
.
light
,
name:
'
玄关
灯-智能'
,
name:
'
103
灯-智能'
,
type:
'智能灯'
,
type:
'智能灯'
,
location:
'
玄关
'
,
location:
'
103
'
,
status:
'开启'
,
status:
'开启'
,
time:
'2024-01-15 14:20:00'
,
time:
'2024-01-15 14:20:00'
,
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
...
@@ -76,9 +76,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -76,9 +76,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'7'
,
id:
'7'
,
icon:
Icons
.
sensor_door
,
icon:
Icons
.
sensor_door
,
name:
'门窗传感器-
阳台
'
,
name:
'门窗传感器-
104
'
,
type:
'传感器'
,
type:
'传感器'
,
location:
'
阳台
门'
,
location:
'
104
门'
,
status:
'离线'
,
status:
'离线'
,
time:
'2024-01-15 12:00:00'
,
time:
'2024-01-15 12:00:00'
,
statusColor:
const
Color
.
fromRGBO
(
102
,
102
,
102
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
102
,
102
,
102
,
1.0
),
...
@@ -86,9 +86,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -86,9 +86,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'8'
,
id:
'8'
,
icon:
Icons
.
kitchen
,
icon:
Icons
.
kitchen
,
name:
'智能插座-
厨房
'
,
name:
'智能插座-
105
'
,
type:
'插座'
,
type:
'插座'
,
location:
'
厨房
台面'
,
location:
'
105
台面'
,
status:
'关闭'
,
status:
'关闭'
,
time:
'2024-01-15 14:15:00'
,
time:
'2024-01-15 14:15:00'
,
statusColor:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1.0
),
...
@@ -96,9 +96,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -96,9 +96,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'9'
,
id:
'9'
,
icon:
Icons
.
smoke_free
,
icon:
Icons
.
smoke_free
,
name:
'烟雾报警器-
厨房
'
,
name:
'烟雾报警器-
105
'
,
type:
'报警器'
,
type:
'报警器'
,
location:
'
厨房
天花板'
,
location:
'
105
天花板'
,
status:
'正常'
,
status:
'正常'
,
time:
'2024-01-15 14:30:00'
,
time:
'2024-01-15 14:30:00'
,
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
...
@@ -106,9 +106,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -106,9 +106,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'10'
,
id:
'10'
,
icon:
Icons
.
desk
,
icon:
Icons
.
desk
,
name:
'智能台灯-
书房
'
,
name:
'智能台灯-
106
'
,
type:
'智能灯'
,
type:
'智能灯'
,
location:
'
书房
书桌'
,
location:
'
106
书桌'
,
status:
'关闭'
,
status:
'关闭'
,
time:
'2024-01-15 14:10:00'
,
time:
'2024-01-15 14:10:00'
,
statusColor:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1.0
),
...
@@ -116,9 +116,9 @@ class InspectionCubit extends Cubit<InspectionState> {
...
@@ -116,9 +116,9 @@ class InspectionCubit extends Cubit<InspectionState> {
InspectionDevice
(
InspectionDevice
(
id:
'11'
,
id:
'11'
,
icon:
Icons
.
ac_unit
,
icon:
Icons
.
ac_unit
,
name:
'智能空调-
客厅
'
,
name:
'智能空调-
101
'
,
type:
'空调'
,
type:
'空调'
,
location:
'
客厅
'
,
location:
'
101
'
,
status:
'正常'
,
status:
'正常'
,
time:
'2024-01-15 14:30:00'
,
time:
'2024-01-15 14:30:00'
,
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
statusColor:
const
Color
.
fromRGBO
(
26
,
188
,
156
,
1.0
),
...
...
lib/views/home/inspection/cubit/inspection_state.dart
View file @
b95e2acc
...
@@ -33,7 +33,7 @@ class InspectionState extends Equatable {
...
@@ -33,7 +33,7 @@ class InspectionState extends Equatable {
const
InspectionState
({
const
InspectionState
({
this
.
selectedTab
=
DeviceStatusTab
.
total
,
this
.
selectedTab
=
DeviceStatusTab
.
total
,
this
.
selectedRooms
=
const
[
'
客厅
'
],
this
.
selectedRooms
=
const
[
'
101
'
],
this
.
deviceList
=
const
[],
this
.
deviceList
=
const
[],
this
.
tabCounts
=
const
{
this
.
tabCounts
=
const
{
DeviceStatusTab
.
total
:
11
,
DeviceStatusTab
.
total
:
11
,
...
...
lib/views/home/inspection/widget/filter_panel.dart
View file @
b95e2acc
...
@@ -19,12 +19,12 @@ class FilterPanel extends StatelessWidget {
...
@@ -19,12 +19,12 @@ class FilterPanel extends StatelessWidget {
});
});
static
const
List
<
String
>
allRooms
=
[
static
const
List
<
String
>
allRooms
=
[
'
客厅
'
,
'
101
'
,
'
主卧
'
,
'
102
'
,
'
玄关
'
,
'
103
'
,
'
厨房
'
,
'
104
'
,
'
书房
'
,
'
105
'
,
'
阳台
'
,
'
106
'
,
];
];
@override
@override
...
...
lib/views/home/inspection_device/inspection_device_view.dart
View file @
b95e2acc
...
@@ -83,6 +83,36 @@ class InspectionDeviceView extends StatelessWidget {
...
@@ -83,6 +83,36 @@ class InspectionDeviceView extends StatelessWidget {
),
),
SizedBox
(
height:
30
.
h
),
SizedBox
(
height:
30
.
h
),
StartInspectionButton
(
deviceId:
state
.
deviceId
),
StartInspectionButton
(
deviceId:
state
.
deviceId
),
SizedBox
(
height:
20
.
h
),
GestureDetector
(
onTap:
()
{
context
.
pushRoute
(
InspectionHistoryRoute
(
deviceId:
state
.
deviceId
),
);
},
child:
Container
(
width:
double
.
infinity
,
height:
88
.
h
,
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
24
.
r
),
border:
Border
.
all
(
color:
const
Color
.
fromRGBO
(
66
,
165
,
245
,
1.0
),
width:
2
.
w
,
),
),
child:
Center
(
child:
Text
(
'巡检记录'
,
style:
TextStyle
(
color:
const
Color
.
fromRGBO
(
66
,
165
,
245
,
1.0
),
fontSize:
32
.
sp
,
fontWeight:
FontWeight
.
bold
,
),
),
),
),
),
SizedBox
(
height:
30
.
h
),
SizedBox
(
height:
30
.
h
),
],
],
);
);
...
...
lib/views/home/inspection_device/widget/start_inspection_button.dart
View file @
b95e2acc
import
'package:auto_route/auto_route.dart'
;
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:loading_animation_widget/loading_animation_widget.dart'
;
import
'package:smart_hotel_app/routes/app_router.gr.dart'
;
import
'package:smart_hotel_app/routes/app_router.gr.dart'
;
class
StartInspectionButton
extends
StatelessWidget
{
class
StartInspectionButton
extends
StatelessWidget
{
...
@@ -11,10 +12,45 @@ class StartInspectionButton extends StatelessWidget {
...
@@ -11,10 +12,45 @@ class StartInspectionButton extends StatelessWidget {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
GestureDetector
(
return
GestureDetector
(
onTap:
()
{
onTap:
()
async
{
context
.
pushRoute
(
showDialog
(
InspectionHistoryRoute
(
deviceId:
deviceId
),
context:
context
,
barrierDismissible:
false
,
builder:
(
context
)
=>
Center
(
child:
Container
(
width:
140
.
w
,
height:
140
.
h
,
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
LoadingAnimationWidget
.
waveDots
(
color:
const
Color
.
fromRGBO
(
66
,
165
,
245
,
1.0
),
size:
50
,
),
SizedBox
(
height:
12
.
h
),
Text
(
'巡检中...'
,
style:
TextStyle
(
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1.0
),
fontSize:
24
.
sp
,
),
),
],
),
),
),
);
);
await
Future
.
delayed
(
const
Duration
(
seconds:
3
));
if
(
context
.
mounted
)
{
context
.
popRoute
();
context
.
pushRoute
(
InspectionHistoryRoute
(
deviceId:
deviceId
),
);
}
},
},
child:
Container
(
child:
Container
(
width:
double
.
infinity
,
width:
double
.
infinity
,
...
...
lib/views/profile/index/widget/profile_menu_block.dart
View file @
b95e2acc
...
@@ -29,8 +29,8 @@ class ProfileMenuBlock extends StatelessWidget {
...
@@ -29,8 +29,8 @@ class ProfileMenuBlock extends StatelessWidget {
child:
Row
(
child:
Row
(
children:
[
children:
[
Container
(
Container
(
width:
96
.
w
,
width:
68
.
w
,
height:
96
.
h
,
height:
68
.
h
,
decoration:
BoxDecoration
(
decoration:
BoxDecoration
(
color:
const
Color
.
fromRGBO
(
229
,
241
,
255
,
1
),
color:
const
Color
.
fromRGBO
(
229
,
241
,
255
,
1
),
borderRadius:
BorderRadius
.
circular
(
28
.
r
),
borderRadius:
BorderRadius
.
circular
(
28
.
r
),
...
@@ -52,11 +52,12 @@ class ProfileMenuBlock extends StatelessWidget {
...
@@ -52,11 +52,12 @@ class ProfileMenuBlock extends StatelessWidget {
),
),
),
),
),
),
Icon
(
if
(
item
.
title
==
'告警信息'
||
item
.
title
==
'设备列表'
)
Icons
.
chevron_right
,
Icon
(
Icons
.
chevron_right
,
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1.0
),
color:
const
Color
.
fromRGBO
(
100
,
116
,
139
,
1.0
),
size:
32
.
sp
,
size:
32
.
sp
,
),
),
],
],
),
),
),
),
...
@@ -65,6 +66,9 @@ class ProfileMenuBlock extends StatelessWidget {
...
@@ -65,6 +66,9 @@ class ProfileMenuBlock extends StatelessWidget {
void
_handleMenuTap
(
BuildContext
context
,
String
title
)
{
void
_handleMenuTap
(
BuildContext
context
,
String
title
)
{
switch
(
title
)
{
switch
(
title
)
{
case
'设备列表'
:
context
.
pushRoute
(
const
InspectionDetailRoute
());
break
;
case
'告警信息'
:
case
'告警信息'
:
context
.
pushRoute
(
const
AbnormalListRoute
());
context
.
pushRoute
(
const
AbnormalListRoute
());
break
;
break
;
...
...
上手帮助.md
0 → 100644
View file @
b95e2acc
# 智慧酒店 App 上手帮助
# 智慧酒店 App 上手帮助
## 1. 环境搭建
### 1.1 前置条件
```
bash
# 确认 Flutter 版本 (需要 3.2.5+)
flutter
--version
# 确认 Android SDK / Xcode 已安装
flutter doctor
```
### 1.2 项目初始化
```
bash
cd
smart_hotel_app
# 安装依赖
flutter pub get
# 生成路由代码(修改路由配置后需要重新执行)
flutter pub run build_runner build
--delete-conflicting-outputs
```
### 1.3 运行项目
```
bash
# 连接设备后运行
flutter run
# 指定设备
flutter run
-d
<device_id>
```
---
## 2. 项目架构速览
```
lib/
├── main.dart ← 入口,全局依赖初始化
├── blocs/auth/ ← 全局认证状态(BLoC 模式)
├── http/ ← 网络层:Dio封装 + 拦截器
├── models/bo/ ← 数据模型
├── repositories/ ← API 调用层
├── routes/ ← 路由配置(auto_route)
├── services/ ← 业务逻辑层
├── utils/ ← 工具:常量、事件总线、存储、日志
└── views/ ← 页面,每个页面 = cubit/ + widget/ + view.dart
├── layout/ ← 底部Tab框架
├── login/ ← 登录
├── home/ ← 告警Tab
├── service/ ← 管理Tab
├── report/ ← 能耗Tab
├── profile/ ← 我的Tab
└── search/ ← 搜索
```
**分层关系**
: View → Cubit/BLoC → Service → Repository → DioRequest → API
---
## 3. 如何新增一个页面
以新增「运维日志」页面为例,演示完整开发流程。
### 3.1 第一步:新建文件夹结构
```
bash
mkdir
-p
lib/views/home/operation_log/cubit
mkdir
-p
lib/views/home/operation_log/widget
```
### 3.2 第二步:定义 State
创建
`lib/views/home/operation_log/cubit/operation_log_state.dart`
:
```
dart
import
'package:equatable/equatable.dart'
;
class
OperationLogState
extends
Equatable
{
final
List
<
LogItem
>
logs
;
final
bool
isLoading
;
final
String
?
error
;
const
OperationLogState
({
this
.
logs
=
const
[],
this
.
isLoading
=
false
,
this
.
error
,
});
OperationLogState
copyWith
({
List
<
LogItem
>?
logs
,
bool
?
isLoading
,
String
?
error
,
})
{
return
OperationLogState
(
logs:
logs
??
this
.
logs
,
isLoading:
isLoading
??
this
.
isLoading
,
error:
error
??
this
.
error
,
);
}
@override
List
<
Object
?>
get
props
=>
[
logs
,
isLoading
,
error
];
}
class
LogItem
extends
Equatable
{
final
String
id
;
final
String
title
;
final
String
time
;
const
LogItem
({
required
this
.
id
,
required
this
.
title
,
required
this
.
time
});
@override
List
<
Object
?>
get
props
=>
[
id
,
title
,
time
];
}
```
### 3.3 第三步:定义 Cubit
创建
`lib/views/home/operation_log/cubit/operation_log_cubit.dart`
:
```
dart
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'operation_log_state.dart'
;
class
OperationLogCubit
extends
Cubit
<
OperationLogState
>
{
OperationLogCubit
()
:
super
(
const
OperationLogState
())
{
loadData
();
}
Future
<
void
>
loadData
()
async
{
emit
(
state
.
copyWith
(
isLoading:
true
));
try
{
// TODO: 调用 API 获取数据
await
Future
.
delayed
(
const
Duration
(
seconds:
1
));
emit
(
state
.
copyWith
(
isLoading:
false
,
logs:
[
const
LogItem
(
id:
'1'
,
title:
'设备巡检完成'
,
time:
'2026-06-05 10:00'
),
],
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
}
}
}
```
### 3.4 第四步:创建 View
创建
`lib/views/home/operation_log/operation_log_view.dart`
:
```
dart
import
'package:auto_route/auto_route.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'cubit/operation_log_cubit.dart'
;
import
'cubit/operation_log_state.dart'
;
@RoutePage
()
class
OperationLogView
extends
StatelessWidget
{
const
OperationLogView
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
BlocProvider
(
create:
(
_
)
=>
OperationLogCubit
(),
child:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'运维日志'
)),
body:
BlocBuilder
<
OperationLogCubit
,
OperationLogState
>(
builder:
(
context
,
state
)
{
if
(
state
.
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
return
ListView
.
builder
(
itemCount:
state
.
logs
.
length
,
itemBuilder:
(
context
,
index
)
{
final
log
=
state
.
logs
[
index
];
return
ListTile
(
title:
Text
(
log
.
title
),
subtitle:
Text
(
log
.
time
),
);
},
);
},
),
),
);
}
}
```
### 3.5 第五步:注册路由
在
[
app_router.dart
](
file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/routes/app_router.dart
)
中添加:
```
dart
AutoRoute
(
page:
OperationLogRoute
.
page
),
```
然后运行代码生成:
```
bash
flutter pub run build_runner build
--delete-conflicting-outputs
```
### 3.6 第六步:页面跳转
```
dart
// 在任意页面中导航
context
.
pushRoute
(
const
OperationLogRoute
());
```
---
## 4. 如何发起网络请求
### 4.1 新增 Repository
在
`lib/repositories/`
下新建文件:
```
dart
import
'../http/response_model.dart'
;
import
'../http/dio_request.dart'
;
class
DeviceRepository
{
Future
<
ResponseModel
>
getDeviceList
()
{
return
DioRequest
.
instance
.
get
(
'/api/devices'
);
}
Future
<
ResponseModel
>
getDeviceDetail
(
String
id
)
{
return
DioRequest
.
instance
.
get
(
'/api/devices/
$id
'
);
}
}
```
### 4.2 在 Cubit 中调用
```
dart
class
DeviceCubit
extends
Cubit
<
DeviceState
>
{
final
DeviceRepository
_repository
=
DeviceRepository
();
Future
<
void
>
loadDevices
()
async
{
emit
(
state
.
copyWith
(
isLoading:
true
));
try
{
final
result
=
await
_repository
.
getDeviceList
();
if
(
result
.
success
)
{
// 处理 result.data
}
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
error:
e
.
toString
()));
}
}
}
```
### 4.3 API 响应格式约定
后端接口返回标准 JSON 格式:
```
json
{
"code"
:
200
,
"msg"
:
"success"
,
"data"
:
{
...
}
}
```
`ResponseModel.fromJson`
会自动解析,
`fromJsonT`
参数可传入自定义反序列化函数。
---
## 5. 全局状态访问
### 5.1 读取 AuthBloc 状态
```
dart
final
authState
=
context
.
read
<
AuthBloc
>().
state
;
if
(
authState
is
AuthSuccess
)
{
final
username
=
authState
.
userInfo
.
username
;
}
```
### 5.2 监听 Token 过期
```
dart
// Token 过期时自动弹出登录页,无需手动处理
// 网络拦截器检测到 401 时发出 TokenExpiredEvent
// main.dart 中已订阅该事件,自动跳转登录页
```
---
## 6. 本地存储使用
```
dart
final
storage
=
StorageService
();
// Token 操作
await
storage
.
saveToken
(
'your_token'
);
final
token
=
await
storage
.
getToken
();
final
isExpired
=
await
storage
.
isTokenExpired
();
// 记住密码
await
storage
.
saveRememberCredentials
(
username:
'admin'
,
password:
'123456'
);
final
(
username
,
password
,
enabled
)
=
await
storage
.
getRememberCredentials
();
await
storage
.
clearRememberCredentials
();
```
---
## 7. 屏幕适配
项目使用
`flutter_screenutil`
,设计稿基准为常规移动端尺寸。
```
dart
// 使用 .w / .h / .sp / .r 进行适配
Container
(
width:
100
.
w
,
// 根据屏幕宽度等比缩放
height:
50
.
h
,
// 根据屏幕高度等比缩放
padding:
EdgeInsets
.
all
(
28
.
w
),
child:
Text
(
'标题'
,
style:
TextStyle
(
fontSize:
32
.
sp
)),
)
```
---
## 8. 图表使用
使用
`fl_chart`
绘制图表,项目中已有示例:
| 文件 | 图表类型 | 示例组件 |
|------|---------|---------|
|
`home/abnormal/widget/temperature_char_block.dart`
| 折线图 | 温度曲线 |
|
`home/device/widget/power_char_block.dart`
| 柱状图 | 功率图表 |
|
`report/index/widget/weekly_power_chart.dart`
| 柱状图 | 周用电量 |
|
`report/energy/widget/hourly_chart_block.dart`
| 折线图 | 分时能耗 |
|
`report/energy/widget/zone_chart_block.dart`
| 饼图 | 区域能耗占比 |
新增图表可参考这些文件。
---
## 9. 常见问题
### Q: `build_runner` 报错怎么办?
```
bash
# 先清理再重新生成
flutter pub run build_runner clean
flutter pub run build_runner build
--delete-conflicting-outputs
```
### Q: 如何切换 API 环境地址?
修改
[
constants.dart
](
file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/utils/constants.dart
)
中的
`baseUrl`
。
### Q: 路由跳转后白屏?
检查是否执行了
`build_runner`
生成
`app_router.gr.dart`
,以及 View 文件是否添加
`@RoutePage()`
。
### Q: 如何添加新的底部 Tab?
1.
在
[
app_router.dart
](
file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/routes/app_router.dart
)
的
`DefaultLayoutRoute`
的
`children`
中添加新路由
2.
在
[
default_view.dart
](
file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/views/layout/default_view.dart
)
的
`routes`
和
`BottomNavigationBar`
中添加对应项
3.
运行
`build_runner`
---
## 10. 代码规范提醒
-
**命名约定**
:
-
文件名:
`snake_case.dart`
-
类名:
`PascalCase`
-
变量/方法:
`camelCase`
-
常量:
`camelCase`
或
`SCREAMING_SNAKE_CASE`
-
**每个页面模块结构**
: 必须包含
`cubit/`
+
`widget/`
+
`view.dart`
-
**State 不可变**
: 始终使用
`copyWith`
更新状态,不要直接修改 state 字段
-
**UI 与逻辑分离**
: View 只负责 UI 渲染,业务逻辑放在 Cubit/Service 中
-
**组件化**
: 可复用的 UI 块抽成
`widget/`
下的独立组件,通过参数接收数据
\ No newline at end of file
技术方案.md
0 → 100644
View file @
b95e2acc
# 智慧酒店 App 技术方案
# 智慧酒店 App 技术方案
## 1. 项目概述
**项目名称**
: smart_hotel_app(智慧酒店管理 App)
**技术栈**
: Flutter 3.x + Dart 3.2+
**项目定位**
: 为酒店运维管理人员提供设备监控、巡检管理、能耗报告、服务管理等一站式移动端解决方案。
**核心功能模块**
:
| 模块 | TAB 名称 | 功能描述 |
|------|----------|---------|
| 告警模块 | 告警 | 异常告警列表、告警详情(温度/电压异常) |
| 管理模块 | 管理 | 巡检任务管理、巡检拓扑图、设备详情、房间设备控制 |
| 能耗模块 | 能耗 | 能耗总览、设备能耗列表、房间能耗报告、区域能耗报告、规则管理 |
| 个人中心 | 我的 | 用户信息、菜单入口 |
---
## 2. 架构设计
### 2.1 整体架构
项目采用
**分层架构 + BLoC 状态管理**
模式,自上而下分为:
```
┌─────────────────────────────────────────────┐
│ Views(UI 层) │
│ 页面/视图组件(View) + 可复用组件(Widget) │
├─────────────────────────────────────────────┤
│ Cubit/BLoC(状态管理层) │
│ 业务状态管理 + 事件处理 │
├─────────────────────────────────────────────┤
│ Service(业务逻辑层) │
│ 业务逻辑封装、数据转换 │
├─────────────────────────────────────────────┤
│ Repository(数据仓库层) │
│ 数据源抽象,连接 HTTP 请求 │
├─────────────────────────────────────────────┤
│ HTTP(网络层) │
│ Dio 封装 + 拦截器链(请求/响应/错误) │
├─────────────────────────────────────────────┤
│ Models(数据模型层) │
│ BO/DTO 对象定义 │
└─────────────────────────────────────────────┘
```
### 2.2 依赖注入
项目未使用 DI 框架(如 get_it),而是采用
**手动注入**
方式。在
`main.dart`
的
`_MyAppState.initState()`
中集中创建核心单例,通过构造函数向下传递。
```
main.dart
└─ initState()
├─ StorageService() → 全局存储服务
├─ DioRequest.instance.init() → 网络请求初始化
├─ AuthRepository() → 认证仓库
├─ AuthService() → 认证业务服务
├─ AuthBloc() → 全局认证状态
└─ AppRouter() → 路由配置
```
---
## 3. 目录结构详解
```
lib/
├── main.dart # 应用入口,全局初始化
├── assets/ # 静态资源
│ ├── icon/ # 图标资源
│ ├── login/ # 登录页资源
│ └── tab/ # 底部导航栏图标
│
├── blocs/ # 全局 BLoC 状态管理
│ └── auth/ # 认证模块
│ ├── auth_bloc.dart # AuthBloc 业务逻辑
│ ├── auth_event.dart # AuthEvent 事件定义
│ └── auth_state.dart # AuthState 状态定义
│
├── http/ # 网络请求层
│ ├── dio_request.dart # Dio 单例封装(GET/POST/PUT/DELETE)
│ ├── response_model.dart # 统一响应模型 ResponseModel<T>
│ ├── exceptions/ # 异常定义
│ │ ├── app_exception.dart # 基类异常
│ │ ├── business_exception.dart
│ │ ├── cancel_exception.dart
│ │ ├── network_exception.dart
│ │ └── server_exception.dart
│ └── interceptors/ # Dio 拦截器
│ ├── request_interceptor.dart # 请求拦截(Token 注入、UUID)
│ ├── response_interceptor.dart # 响应拦截(统一处理)
│ └── error_interceptor.dart # 错误拦截(异常转换)
│
├── models/ # 数据模型
│ └── bo/ # 业务对象 Business Object
│ └── user_info_bo.dart # 用户信息
│
├── repositories/ # 数据仓库层
│ └── auth_repository.dart # 认证 API 调用
│
├── routes/ # 路由配置(auto_route)
│ ├── app_router.dart # 路由表定义
│ └── app_router.gr.dart # 自动生成的代码
│
├── services/ # 业务服务层
│ └── auth_service.dart # 认证业务逻辑
│
├── utils/ # 工具类
│ ├── constants.dart # 全局常量(BaseURL、超时、错误码)
│ ├── event_bus.dart # 全局事件总线
│ ├── logger.dart # 日志工具
│ └── storage/ # 本地存储
│ └── storage_service.dart # FlutterSecureStorage 封装
│
└── views/ # 视图层
├── layout/ # 布局框架
│ └── default_view.dart # 底部 Tab 框架(AutoTabsScaffold)
├── login/ # 登录模块
│ ├── login_view.dart
│ ├── cubit/
│ └── widget/
├── home/ # 告警 Tab(首页)
│ ├── index/ # 告警首页
│ ├── abnormal/ # 告警详情
│ ├── abnormal_list/ # 告警列表
│ ├── device/ # 设备详情
│ ├── inspection/ # 巡检详情
│ ├── inspection_device/ # 巡检设备
│ ├── inspection_history/ # 巡检历史
│ └── inspection_topology/ # 巡检拓扑
├── service/ # 管理 Tab
│ ├── index/ # 管理首页
│ ├── room/ # 房间详情
│ └── device/ # 设备控制
├── report/ # 能耗 Tab
│ ├── index/ # 能耗首页
│ ├── device/ # 设备能耗列表/详情
│ ├── energy/ # 区域能耗
│ ├── room/ # 房间能耗
│ └── rule/ # 规则管理
├── profile/ # 我的 Tab
│ └── index/ # 个人中心
└── search/ # 全局搜索
└── index_view.dart
```
---
## 4. 核心技术说明
### 4.1 状态管理:BLoC + Cubit
项目混合使用
**BLoC**
(全局认证)和
**Cubit**
(页面级状态)两种模式:
#### BLoC 使用场景:全局认证流程
| 文件 | 职责 |
|------|------|
|
`auth_event.dart`
| 定义事件:
`AuthLoginRequested`
、
`AuthTokenExpiredEvent`
、
`AuthLogoutRequestedEvent`
|
|
`auth_state.dart`
| 定义状态:
`AuthInitial`
、
`AuthLoading`
、
`AuthSuccess`
、
`AuthFailure`
、
`AuthTokenExpired`
、
`AuthLoggedOut`
|
|
`auth_bloc.dart`
| 事件处理:登录、Token过期、退出登录 |
在
`main.dart`
中通过
`BlocProvider`
注入全局 AuthBloc,监听
`AuthTokenExpired`
状态跳转登录页,监听
`AuthLoggedOut`
执行退出。
#### Cubit 使用场景:页面级状态
每个功能页面都有独立的
**Cubit + State**
组合:
```
views/{模块}/{页面}/
├── cubit/
│ ├── xxx_cubit.dart # 业务逻辑
│ └── xxx_state.dart # 数据状态 + copyWith
└── widget/ # 可复用组件
```
State 类使用
`Equatable`
便于比较,提供
`copyWith`
方法实现不可变更新。
**数据流向**
:
```
Cubit.emit(state.copyWith(xxx: newValue))
→ BlocBuilder 监听变化
→ Widget rebuild
```
### 4.2 路由系统:auto_route
使用
`auto_route`
实现声明式路由。
**路由配置**
(
[
app_router.dart
](
file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/routes/app_router.dart
)
):
```
LoginRoute (initial)
→ DefaultLayoutRoute (AutoTabsScaffold)
├── HomeRoute (告警首页, initial tab)
├── ServiceIndexRoute (管理首页)
├── ReportIndexRoute (能耗首页)
└── ProfileRoute (我的)
→ SearchRoute (全局搜索)
→ AbnormalDetailRoute (告警详情)
→ DeviceDetailRoute (设备详情)
→ ... 其他二级页面
```
-
所有页面使用
`@RoutePage()`
注解标记
-
根路由为
`LoginRoute`
,登录成功跳转到
`DefaultLayoutRoute`
-
`DefaultLayoutRoute`
内嵌 4 个 Tab,通过
`AutoTabsScaffold`
+
`BottomNavigationBar`
实现
-
二级页面(详情)在 Tab 外层定义,支持全屏 push
### 4.3 网络请求层
#### DioRequest 单例
封装 Dio 实例,提供
`get/post/put/delete`
方法,返回
`ResponseModel<T>`
。
**配置项**
(
[
constants.dart
](
file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/utils/constants.dart
)
):
| 配置 | 值 | 说明 |
|------|-----|------|
| baseUrl |
`http://localhost:8080`
| API 基础地址 |
| connectTimeout | 30000ms | 连接超时 |
| receiveTimeout | 30000ms | 接收超时 |
| sendTimeout | 30000ms | 发送超时 |
#### 拦截器链
```
RequestInterceptor
→ 注入 Authorization Bearer Token
→ 注入 X-Request-UUID 追踪ID
→ 注入 X-Timestamp 时间戳
→ 输出请求日志
ResponseInterceptor
→ 响应日志输出
→ 响应数据预处理
ErrorInterceptor
→ DioException 转 AppException 子类
→ 业务异常 / 网络异常 / 服务器异常分离处理
```
#### ResponseModel
```
dart
class
ResponseModel
<
T
>
{
final
int
code
;
// 业务状态码
final
String
msg
;
// 提示信息
final
T
?
data
;
// 泛型数据
final
String
uuid
;
// 请求唯一标识
final
bool
success
;
// code == 200
final
int
timestamp
;
// 时间戳
}
```
### 4.4 数据持久化:FlutterSecureStorage
StorageService 封装
`flutter_secure_storage`
,提供:
| 功能 | 方法 |
|------|------|
| Token 存取 |
`saveToken()`
/
`getToken()`
/
`deleteToken()`
/
`hasToken()`
|
| Token 过期检查 |
`isTokenExpired()`
/
`getTokenExpiry()`
|
| 记住密码 |
`saveRememberCredentials()`
/
`getRememberCredentials()`
/
`clearRememberCredentials()`
|
Android 端启用
`encryptedSharedPreferences: true`
增强安全性。
### 4.5 事件总线
全局
`eventBus`
(
`StreamController.broadcast`
)用于跨模块通信:
| 事件 | 用途 |
|------|------|
|
`TokenExpiredEvent`
| Token 过期通知,触发全局重新登录 |
|
`LogoutEvent`
| 退出登录通知 |
在
`main.dart`
中订阅
`TokenExpiredEvent`
,触发
`AuthBloc`
的
`AuthTokenExpiredEvent`
。
---
## 5. 业务模块设计
### 5.1 认证流程
```
LoginView → 输入用户名密码 → AuthBloc(AuthLoginRequested)
→ AuthService.login()
→ AuthRepository.login() → POST /login
→ 成功: StorageService.saveToken() → AuthSuccess → 跳转首页
→ 失败: AuthFailure → 显示错误
Token 过期:
网络拦截器 (401) → ErrorInterceptor → eventBus(TokenExpiredEvent)
→ AuthBloc(AuthTokenExpiredEvent) → 清除 Token → 跳转登录页
```
### 5.2 页面状态管理模式
所有业务页面遵循统一模式:
```
1. 定义 State: extends Equatable, 提供 copyWith
2. 定义 Cubit: extends Cubit<State>, 构造函数初始化数据
3. View 使用 BlocProvider 创建 Cubit
4. UI 通过 BlocBuilder 监听 State 变化
5. 子组件通过接口参数接收数据(props down, events up)
```
### 5.3 子模块页面与路由对应
#### 告警模块(Home)
| 页面 | 路由 | 功能 |
|------|------|------|
| 告警首页 |
`HomeRoute`
| 统计概览、任务列表、温度告警、电压波动 |
| 告警详情 |
`AbnormalDetailRoute`
| 温度曲线、告警信息、处理操作 |
| 告警列表 |
`AbnormalListRoute`
| 告警筛选、状态过滤、列表展示 |
| 设备详情 |
`DeviceDetailRoute`
| 设备参数、功率曲线、状态信息 |
| 巡检详情 |
`InspectionDetailRoute`
| 巡检任务、设备列表、筛选面板 |
| 巡检设备 |
`InspectionDeviceRoute`
| 设备概览、巡检项目、巡检历史 |
| 巡检历史 |
`InspectionHistoryRoute`
| 巡检记录列表、统计卡片 |
| 巡检拓扑 |
`InspectionTopologyRoute`
| 网络拓扑图、状态图例 |
#### 管理模块(Service)
| 页面 | 路由 | 功能 |
|------|------|------|
| 管理首页 |
`ServiceIndexRoute`
| 服务统计、房间管理卡片 |
| 房间详情 |
`ServiceRoomDetailRoute`
| 房间信息、设备状态、设备控制 |
| 设备控制 |
`ServiceDeviceDetailRoute`
| 模式选择、温度控制、风速调节 |
#### 能耗模块(Report)
| 页面 | 路由 | 功能 |
|------|------|------|
| 能耗首页 |
`ReportIndexRoute`
| 设备在线率、能耗指标、周用电图、房间分布 |
| 设备能耗列表 |
`ReportDeviceListRoute`
| 设备卡片、筛选Tab |
| 设备能耗详情 |
`ReportDeviceDetailRoute`
| 单设备能耗详情 |
| 区域能耗 |
`ReportEnergyDetailRoute`
| 能耗统计、分时/分区图表 |
| 房间能耗 |
`ReportRoomDetailRoute`
| 楼层选择、房间网格、详情卡片 |
| 规则管理 |
`ReportRuleManagementRoute`
| 规则卡片管理 |
---
## 6. 依赖库清单
| 库 | 版本 | 用途 |
|-----|------|------|
| flutter_bloc | ^8.1.5 | BLoC/Cubit 状态管理 |
| equatable | ^2.0.5 | 值对象相等比较 |
| auto_route | ^7.9.2 | 声明式路由管理 |
| dio | ^5.4.0 | HTTP 网络请求 |
| flutter_secure_storage | ^9.0.0 | 安全本地存储 |
| flutter_screenutil | ^5.9.3 | 屏幕适配(设计稿基准) |
| fl_chart | ^0.71.0 | 图表绘制(折线/柱状/饼图) |
| flutter_switch | ^0.3.2 | 开关组件 |
| fluttertoast | ^8.2.5 | Toast 提示 |
| loading_animation_widget | ^1.3.0 | 加载动画 |
---
## 7. 开发环境要求
| 工具 | 版本要求 |
|------|---------|
| Flutter SDK | >= 3.2.5 |
| Dart SDK | >= 3.2.5 < 4.0.0 |
| Android Studio / VS Code | 最新稳定版 |
| Android minSdk | 21+ |
| iOS Deployment Target | 12.0+ |
---
## 8. 状态码约定
| 状态码 | 含义 |
|--------|------|
| 200 | 请求成功 |
| 401 | Token 过期/未授权 |
| 403 | 禁止访问 |
| 404 | 资源不存在 |
| 500 | 服务器错误 |
---
## 9. 关键设计决策
1.
**Cubit 而非 BLoC**
: 大多数页面业务逻辑简单,使用 Cubit 减少样板代码(无需定义 Event 类),仅在认证等复杂流程使用 BLoC。
2.
**手动依赖注入**
: 项目规模适中,未引入 get_it 等 DI 框架,依赖关系清晰可控。
3.
**auto_route 代码生成**
: 路由通过
`@RoutePage()`
注解 +
`build_runner`
自动生成
`app_router.gr.dart`
,减少手写路由模板代码。
4.
**State copyWith 模式**
: 所有 State 不可变,通过
`copyWith`
更新部分字段,保证状态变更可追踪。
5.
**Token 自动注入**
:
`RequestInterceptor`
在每次请求时自动从 SecureStorage 读取 Token 并注入 Header。
6.
**统一错误处理**
:
`ErrorInterceptor`
将 Dio 异常转换为项目内部异常类型,方便上层统一处理。
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment