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
a1177508
Commit
a1177508
authored
Jun 05, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
.
parent
b95e2acc
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
412 additions
and
665 deletions
+412
-665
.gitignore
.gitignore
+7
-0
ARCHITECTURE_MIGRATION_PLAN.md
ARCHITECTURE_MIGRATION_PLAN.md
+0
-613
上手帮助.md
上手帮助.md
+311
-51
技术方案.md
技术方案.md
+94
-1
No files found.
.gitignore
View file @
a1177508
...
...
@@ -50,3 +50,9 @@ app.*.map.json
# ai相关
ai_work/
## 文档相关
测试方案.md
对接方案.md
技术方案.md
上手帮助.md
\ No newline at end of file
ARCHITECTURE_MIGRATION_PLAN.md
deleted
100644 → 0
View file @
b95e2acc
# 智能酒店管理系统 - 架构迁移方案
## 一、现有架构分析
### 1.1 整体分层
```
lib/
├── assets/ # 静态资源
├── blocs/auth/ # 全局认证 Bloc(唯一的 Bloc)
├── http/ # 网络层(Dio + 拦截器)
├── models/bo/ # 业务对象
├── repositories/ # 数据仓库层
├── routes/ # 路由(auto_route)
├── services/ # 服务层
├── utils/ # 工具类(storage、event_bus、logger)
└── views/ # 视图层
├── login/ # 登录模块
├── home/ # 告警模块(index / abnormal / device)
├── service/ # 服务模块(index / device / room)
├── report/ # 看板模块(index / energy / room)
├── profile/ # 我的模块
├── search/ # 搜索页
└── layout/ # Tab 布局容器
```
### 1.2 当前状态管理方式
| 模块 | 当前方式 | 问题 |
|------|---------|------|
| Auth |
`flutter_bloc`
的
`Bloc`
(Event-driven)| 无 |
| 所有页面 | 自定义
`Controller`
类 +
`StatefulWidget.setState()`
| 无响应式、不可追溯、与 Widget 强耦合 |
### 1.3 当前 Controller 与 Widget Block 的交互模式
| 模式 | 描述 | 涉及模块 |
|------|------|---------|
| A | 父 View 创建 Controller,传给所有子组件 | home/index、profile/index |
| B | 父 View 创建 Controller,提取数据对象后传给子组件 | home/abnormal、home/device |
| C | 每个子组件独立创建 Controller 实例 | report/index(6个实例)、service/index、service/room、service/device |
| D | Controller 是静态类 | report/energy |
| E | 无 Controller 或空 Controller | report/room、search、layout |
---
## 二、目标架构
### 2.1 技术选型
| 层级 | 技术 | 说明 |
|------|------|------|
| 全局状态 |
`flutter_bloc`
的
`Bloc`
| Event-driven,支持事件追踪 |
| 页面状态 |
`flutter_bloc`
的
`Cubit`
| Function-call-driven,简洁高效 |
| 路由 |
`auto_route`
| 保持不变 |
| 网络 |
`Dio`
+ 拦截器 | 保持不变 |
### 2.2 Bloc vs Cubit 划分原则
| 场景 | 使用 |
|------|------|
| 跨页面/全局共享状态(Auth、WebSocket、主题) |
**Bloc**
|
| 需要事件溯源、复杂业务流程 |
**Bloc**
|
| 单页面内 UI 状态、简单数据展示 |
**Cubit**
|
### 2.3 本项目的划分
#### 保持 Bloc(global)
| 名称 | 路径 | 状态 |
|------|------|------|
| AuthBloc |
`blocs/auth/`
| ✅ 已有,不变 |
#### 新增 Cubit(page-level,替代 Controller)
| Cubit | 路径 | 替代的 Controller |
|-------|------|-------------------|
| LoginCubit |
`views/login/cubit/`
| LoginController |
| HomeIndexCubit |
`views/home/index/cubit/`
| HomeIndexController |
| AbnormalCubit |
`views/home/abnormal/cubit/`
| HomeAbnormalController |
| DeviceCubit |
`views/home/device/cubit/`
| HomeDeviceController |
| ServiceIndexCubit |
`views/service/index/cubit/`
| ServiceIndexController |
| ServiceDeviceCubit |
`views/service/device/cubit/`
| ServiceDeviceController |
| ServiceRoomCubit |
`views/service/room/cubit/`
| ServiceRoomController |
| ReportIndexCubit |
`views/report/index/cubit/`
| ReportIndexController |
| EnergyCubit |
`views/report/energy/cubit/`
| ReportEnergyController |
| RoomReportCubit |
`views/report/room/cubit/`
| ReportRoomController |
| ProfileCubit |
`views/profile/index/cubit/`
| ProfileIndexController |
---
## 三、目录结构调整
### 3.1 调整后完整结构
```
lib/
blocs/ # 全局 Bloc
auth/
auth_bloc.dart # ✅ 不变
auth_event.dart # ✅ 不变
auth_state.dart # ✅ 不变
views/
login/
cubit/
login_cubit.dart # 新增
login_state.dart # 新增
widget/ # 原 child/ 改名
btn_login.dart
btn_reset.dart
password_field.dart
remember_password_row.dart
username_field.dart
login_view.dart # 重构
layout/
default_view.dart # 不变
search/
index_view.dart # 不变
home/
index/
cubit/
home_index_cubit.dart # 新增
home_index_state.dart # 新增
widget/ # 原 child/ 改名
equipment_list_block.dart
task_head_block.dart
task_total_block.dart
temperature_block.dart
voltage_operate_block.dart
index_view.dart # 重构
abnormal/
cubit/
abnormal_cubit.dart # 新增
abnormal_state.dart # 新增
widget/ # 原 child/ 改名
alarm_info_block.dart
temperature_block.dart
temperature_char_block.dart
abnormal_detail_view.dart # 重构
device/
cubit/
device_cubit.dart # 新增
device_state.dart # 新增
widget/ # 原 child/ 改名
device_parms_block.dart
power_char_block.dart
state_info_block.dart
device_detail_view.dart # 重构
service/
index/
cubit/
service_index_cubit.dart # 新增
service_index_state.dart # 新增
widget/ # 原 child/ 改名
room_management_block.dart
service_head_block.dart
service_stats_block.dart
index_view.dart # 重构
device/
cubit/
service_device_cubit.dart # 新增
service_device_state.dart # 新增
widget/ # 原 child/ 改名
mode_selection_block.dart
temperature_control_block.dart
wind_speed_block.dart
device_detail_view.dart # 重构
room/
cubit/
service_room_cubit.dart # 新增
service_room_state.dart # 新增
widget/ # 原 child/ 改名
bottom_operation_block.dart
device_control_block.dart
room_info_block.dart
room_detail_view.dart # 重构
report/
index/
cubit/
report_index_cubit.dart # 新增
report_index_state.dart # 新增
widget/ # 原 child/ 改名
device_online_rate.dart
quick_actions_block.dart
report_header_block.dart
report_metrics_block.dart
room_status_distribution.dart
weekly_power_chart.dart
index_view.dart # 重构
energy/
cubit/
energy_cubit.dart # 新增
energy_state.dart # 新增
widget/ # 原 child/ 改名
energy_stats_card_block.dart
hourly_chart_block.dart
zone_chart_block.dart
energy_detail_view.dart # 重构
room/
cubit/
room_report_cubit.dart # 新增
room_report_state.dart # 新增
room_detail.dart # 重构
profile/
index/
cubit/
profile_cubit.dart # 新增
profile_state.dart # 新增
widget/ # 原 child/ 改名
profile_header_block.dart
profile_menu_block.dart
index_view.dart # 重构
```
### 3.2 变更汇总
#### 删除的文件(11 个 Controller)
```
views/login/controller.dart
views/home/index/controller.dart
views/home/abnormal/controller.dart
views/home/device/controller.dart
views/service/index/controller.dart
views/service/device/controller.dart
views/service/room/controller.dart
views/report/index/controller.dart
views/report/energy/controller.dart
views/report/room/controller.dart
views/profile/index/controller.dart
```
#### 新增的文件(22 个,每个模块 1 个 cubit + 1 个 state)
```
各模块下 cubit/xxx_cubit.dart + cubit/xxx_state.dart
```
#### 重命名的文件夹(10 个 child → widget)
```
views/login/child/ → views/login/widget/
views/home/index/child/ → views/home/index/widget/
views/home/abnormal/child/ → views/home/abnormal/widget/
views/home/device/child/ → views/home/device/widget/
views/service/index/child/ → views/service/index/widget/
views/service/device/child/ → views/service/device/widget/
views/service/room/child/ → views/service/room/widget/
views/report/index/child/ → views/report/index/widget/
views/report/energy/child/ → views/report/energy/widget/
views/profile/index/child/ → views/profile/index/widget/
```
> `report/room/` 没有 child 文件夹,不需要重命名。
---
## 四、Widget Block 改造标准方案
### 4.1 方案选择
经过对比三种候选方案(A - Props 下传、B - 数据对象下传、C - 子组件直连 Cubit),确定采用
**A + C 混合方案**
:
> **核心原则:区块级 BlocBuilder + 数据提取下传**
| 层级 | 职责 | 方式 |
|------|------|------|
|
**View 层**
| 提供 Cubit,布局 UI 区块 |
`BlocProvider(create: ...)`
|
|
**View Body 层**
| 每个 UI 区块用
`BlocBuilder`
读取 state,配合
`buildWhen`
|
`BlocBuilder<Cubit, State>(buildWhen: ..., builder: ...)`
|
|
**Widget 叶子节点**
| 接收具体类型参数,纯 UI 渲染 |
`StatelessWidget`
+ 构造函数参数 |
### 4.2 为什么选择此方案
| 优势 | 说明 |
|------|------|
|
**精细化重建控制**
|
`buildWhen`
确保只重建有变化的区块,避免全页面刷新 |
|
**叶子组件零依赖**
| 不依赖
`flutter_bloc`
、不依赖
`BuildContext`
,可在任何项目复用 |
|
**单元测试极简**
| 测试叶子组件只需传入参数,不需要 mock 任何框架 |
|
**状态可追溯**
| 所有变更通过
`emit`
,配合 bloc DevTools 可追踪完整状态流转 |
|
**职责清晰**
| View 负责布局+分发,Widget 负责渲染,Cubit 负责业务逻辑 |
### 4.3 代码模板
#### Cubit + State
```
dart
// cubit/xxx_state.dart
import
'package:equatable/equatable.dart'
;
class
XxxState
extends
Equatable
{
final
String
title
;
final
int
count
;
const
XxxState
({
this
.
title
=
''
,
this
.
count
=
0
,
});
XxxState
copyWith
({
String
?
title
,
int
?
count
,
})
{
return
XxxState
(
title:
title
??
this
.
title
,
count:
count
??
this
.
count
,
);
}
@override
List
<
Object
?>
get
props
=>
[
title
,
count
];
}
```
```
dart
// cubit/xxx_cubit.dart
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'xxx_state.dart'
;
class
XxxCubit
extends
Cubit
<
XxxState
>
{
XxxCubit
()
:
super
(
const
XxxState
());
void
loadData
()
{
emit
(
state
.
copyWith
(
title:
'Hello'
,
count:
42
));
}
void
updateCount
(
int
newCount
)
{
emit
(
state
.
copyWith
(
count:
newCount
));
}
}
```
#### View(StatelessWidget + BlocProvider)
```
dart
// xxx_view.dart
@RoutePage
()
class
XxxView
extends
StatelessWidget
{
const
XxxView
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
BlocProvider
(
create:
(
_
)
=>
XxxCubit
(),
child:
const
_XxxBody
(),
);
}
}
class
_XxxBody
extends
StatelessWidget
{
const
_XxxBody
();
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
body:
SingleChildScrollView
(
child:
Column
(
children:
[
// 区块1 - 只在 count 变化时重建
BlocBuilder
<
XxxCubit
,
XxxState
>(
buildWhen:
(
prev
,
curr
)
=>
prev
.
count
!=
curr
.
count
,
builder:
(
context
,
state
)
{
return
ChildBlockA
(
count:
state
.
count
);
},
),
// 区块2 - 只在 title 变化时重建
BlocBuilder
<
XxxCubit
,
XxxState
>(
buildWhen:
(
prev
,
curr
)
=>
prev
.
title
!=
curr
.
title
,
builder:
(
context
,
state
)
{
return
ChildBlockB
(
title:
state
.
title
,
onTap:
()
=>
context
.
read
<
XxxCubit
>().
updateCount
(
100
),
);
},
),
],
),
),
);
}
}
```
#### 叶子 Widget(StatelessWidget + 具体参数)
```
dart
// widget/child_block_a.dart
class
ChildBlockA
extends
StatelessWidget
{
final
int
count
;
const
ChildBlockA
({
super
.
key
,
required
this
.
count
});
@override
Widget
build
(
BuildContext
context
)
{
return
Text
(
'当前计数:
$count
'
);
}
}
```
### 4.4 有内部交互状态的 Widget Block 处理
部分 Widget Block 有内部交互状态(如开关、Tab 切换、选中项),改造方案:
| 交互类型 | 处理方式 | 示例 |
|---------|---------|------|
|
**纯 UI 状态**
(不影响其他组件)| 保留为局部
`StatefulWidget`
| 密码可见性切换 |
|
**影响其他组件**
| 提升到 Cubit State | 楼层/类型筛选、房间选中 |
|
**混合状态**
| 拆分:局部状态保留,共享状态提升到 Cubit | 设备开关(局部)+ 总电源(Cubit) |
```
dart
// 示例:RoomManagementBlock 的筛选状态提升到 Cubit
class
ServiceIndexState
extends
Equatable
{
final
String
?
selectedFloor
;
final
String
?
selectedType
;
final
String
?
selectedRoom
;
final
String
searchText
;
final
List
<
Map
<
String
,
dynamic
>>
rooms
;
// ...
}
class
ServiceIndexCubit
extends
Cubit
<
ServiceIndexState
>
{
void
selectFloor
(
String
?
floor
)
{
emit
(
state
.
copyWith
(
selectedFloor:
floor
));
}
void
selectType
(
String
?
type
)
{
emit
(
state
.
copyWith
(
selectedType:
type
));
}
void
searchRoom
(
String
text
)
{
emit
(
state
.
copyWith
(
searchText:
text
));
}
}
```
---
## 五、各模块迁移要点
### 5.1 login(登录)
-
**改造难度**
:⭐⭐⭐ 中等
-
**涉及 AuthBloc 交互**
:登录逻辑委托给
`AuthBloc`
,
`LoginCubit`
管理表单状态
-
**LoginState 字段**
:
`rememberPassword`
、
`isLoading`
-
**widget 子组件**
:保持现有纯 UI 设计,不需要大改
### 5.2 home/index(告警首页)
-
**改造难度**
:⭐⭐⭐⭐ 较高
-
**子组件最多**
:5 个 widget block 需要改造
-
**HomeIndexState 字段**
:~20 个字段(告警数、温度、电压、设备列表等)
-
**关键**
:配合
`buildWhen`
控制各区块的细粒度重建
### 5.3 home/abnormal(告警详情)
-
**改造难度**
:⭐⭐ 低
-
**子组件已有数据对象**
:
`AlarmInfo`
数据对象可迁移到 State 中
-
**子组件已为 StatelessWidget**
:改动最小
### 5.4 home/device(设备详情)
-
**改造难度**
:⭐⭐ 低
-
**类似 abnormal**
:
`DeviceInfo`
数据对象迁移到 State
-
**PowerCharBlock**
:内部有 Tab 切换状态,保留为局部 StatefulWidget
### 5.5 service/index(服务首页)
-
**改造难度**
:⭐⭐⭐⭐ 较高
-
**RoomManagementBlock**
:复杂的筛选/搜索/选中逻辑需提升到 Cubit
-
**性能关键**
:房间数量多时,筛选逻辑应避免全量重建
### 5.6 service/device(设备控制)
-
**改造难度**
:⭐⭐⭐ 中等
-
**子组件有交互状态**
:温度调节、模式选择、风速选择需提升到 Cubit
-
**开关状态**
:可保留局部或提升到 Cubit
### 5.7 service/room(客房详情)
-
**改造难度**
:⭐⭐⭐ 中等
-
**DeviceControlBlock**
:电源开关状态需提升到 Cubit
-
**导航回调**
:空调点击导航跳转需通过回调或 Cubit 方法
### 5.8 report/index(看板首页)
-
**改造难度**
:⭐⭐⭐ 中等
-
**当前问题**
:6 个子组件各创建独立 Controller,改为共享 Cubit
-
**子组件改为 StatelessWidget**
:通过 BlocBuilder 接收数据
### 5.9 report/energy(能耗详情)
-
**改造难度**
:⭐ 低
-
**当前是静态类**
:数据直接迁移到 State,Cubit 支持动态更新
-
**子组件已为 StatelessWidget**
:改动最小
### 5.10 report/room(客房报表)
-
**改造难度**
:⭐ 最低
-
**Controller 是空类**
:几乎无改动
### 5.11 profile/index(个人中心)
-
**改造难度**
:⭐⭐ 低
-
**Controller 简单**
:菜单列表 + 导航回调
-
**Menu item 的 onTap**
:改为通过 Cubit 方法触发
---
## 六、迁移顺序
| 序号 | 模块 | 难度 | 原因 |
|------|------|------|------|
| 1 |
`report/room`
| ⭐ | Controller 空类,可作为练手 |
| 2 |
`report/energy`
| ⭐ | 静态类,结构最简单 |
| 3 |
`profile/index`
| ⭐⭐ | Controller 简单,影响范围小 |
| 4 |
`home/abnormal`
| ⭐⭐ | 子组件已是 StatelessWidget |
| 5 |
`home/device`
| ⭐⭐ | 类似 abnormal |
| 6 |
`report/index`
| ⭐⭐⭐ | 需要将 6 个独立 Controller 合并为 1 个 Cubit |
| 7 |
`service/device`
| ⭐⭐⭐ | 需要处理交互状态提升 |
| 8 |
`service/room`
| ⭐⭐⭐ | 需要处理开关状态提升 |
| 9 |
`service/index`
| ⭐⭐⭐⭐ | RoomManagementBlock 复杂筛选逻辑 |
| 10 |
`home/index`
| ⭐⭐⭐⭐ | 子组件最多,State 字段最多 |
| 11 |
`login`
| ⭐⭐⭐ | 需要配合 AuthBloc |
---
## 七、风险点与注意事项
### 7.1 import 路径变更
-
所有
`child/`
→
`widget/`
会导致 import 路径全变
-
建议:迁移一个模块后立即编译,避免大量报错
### 7.2 auto_route 生成文件
-
`app_router.gr.dart`
中的 import 需同步更新
-
迁移完成后重新运行
`dart run build_runner build`
### 7.3 全局 BlocProvider 注册
`main.dart`
中当前只注册 AuthBloc:
```
dart
MultiBlocProvider
(
providers:
[
BlocProvider
<
AuthBloc
>.
value
(
value:
_authBloc
),
],
)
```
**保持不变**
。页面级 Cubit 在各页面内部通过
`BlocProvider(create: ...)`
创建,遵循"谁使用谁创建"原则。
### 7.4 不迁移的部分
| 部分 | 说明 |
|------|------|
|
`routes/`
| auto_route 配置不改 |
|
`http/`
| 网络层不变 |
|
`services/`
| 服务层不变 |
|
`repositories/`
| 仓储层不变 |
|
`models/`
| 数据模型不变 |
|
`utils/`
| 工具类不变 |
|
`assets/`
| 资源不变 |
|
`layout/default_view.dart`
| Tab 布局不变 |
|
`search/index_view.dart`
| 搜索页不变 |
---
## 八、迁移后的架构全景
```
┌──────────────────────────────────────────────────┐
│ main.dart │
│ ┌────────────────────────────────────────────┐ │
│ │ MultiBlocProvider([ │ │
│ │ BlocProvider<AuthBloc> ← 全局 Bloc │ │
│ │ ]) │ │
│ └────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ Views │
│ ┌──────────────────────────────────────────┐ │
│ │ BlocProvider(create: PageCubit) │ │
│ │ └── BlocBuilder<Cubit, State> │ │
│ │ ├── buildWhen: 区块1条件 │ │
│ │ │ └── ChildWidgetA(props) │ │
│ │ ├── BlocBuilder: 区块2条件 │ │
│ │ │ └── ChildWidgetB(props) │ │
│ │ └── BlocBuilder: 区块3条件 │ │
│ │ └── ChildWidgetC(props) │ │
│ └──────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ Cubits (页面级) Blocs (全局) │
│ - LoginCubit - AuthBloc │
│ - HomeIndexCubit │
│ - AbnormalCubit │
│ - DeviceCubit │
│ - ServiceIndexCubit │
│ - ServiceDeviceCubit │
│ - ServiceRoomCubit │
│ - ReportIndexCubit │
│ - EnergyCubit │
│ - RoomReportCubit │
│ - ProfileCubit │
├──────────────────────────────────────────────────┤
│ Services → Repositories → HTTP (Dio) │
└──────────────────────────────────────────────────┘
```
\ No newline at end of file
上手帮助.md
View file @
a1177508
# 智慧酒
店 App 上手帮助
# 智慧酒
店 App 上手帮助
...
...
@@ -60,28 +60,103 @@ lib/
**分层关系**
: View → Cubit/BLoC → Service → Repository → DioRequest → API
> **注意**: 当前阶段 View 层已完成,但 Cubit 中数据为 mock 硬编码。对接真实 API 时需要补充 Service 和 Repository 层,详见 [对接方案.md](./对接方案.md)。
---
## 3. 如何新增一个页面
## 3. 如何新增一个页面
(完整分层流程)
以新增「运维日志」页面为例,演示完整开发流程。
以新增「运维日志」页面为例,演示
**View → Cubit → Service → Repository → API**
的
完整开发流程。
### 3.1 第一步:
新建文件夹结构
### 3.1 第一步:
定义 BO 模型
```
bash
mkdir
-p
lib/views/home/operation_log/cubit
mkdir
-p
lib/views/home/operation_log/widget
创建
`lib/models/bo/operation_log_bo.dart`
:
```
dart
import
'package:equatable/equatable.dart'
;
class
OperationLogBO
extends
Equatable
{
final
String
id
;
final
String
title
;
final
String
time
;
const
OperationLogBO
({
required
this
.
id
,
required
this
.
title
,
required
this
.
time
,
});
factory
OperationLogBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
OperationLogBO
(
id:
json
[
'id'
]
as
String
,
title:
json
[
'title'
]
as
String
,
time:
json
[
'time'
]
as
String
,
);
}
@override
List
<
Object
?>
get
props
=>
[
id
,
title
,
time
];
}
```
### 3.2 第二步:新建 Repository
创建
`lib/repositories/operation_log_repository.dart`
:
```
dart
import
'../http/response_model.dart'
;
import
'../http/dio_request.dart'
;
import
'../models/bo/operation_log_bo.dart'
;
class
OperationLogRepository
{
Future
<
ResponseModel
<
List
<
OperationLogBO
>>>
getLogs
()
{
return
DioRequest
.
instance
.
get
<
List
<
OperationLogBO
>>(
'/api/operation-logs'
,
fromJsonT:
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
OperationLogBO
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
();
},
);
}
}
```
### 3.3 第三步:新建 Service
创建
`lib/services/operation_log_service.dart`
:
```
dart
import
'../repositories/operation_log_repository.dart'
;
import
'../models/bo/operation_log_bo.dart'
;
class
OperationLogService
{
final
OperationLogRepository
_repository
;
OperationLogService
({
required
OperationLogRepository
repository
})
:
_repository
=
repository
;
Future
<
List
<
OperationLogBO
>>
getLogs
()
async
{
final
result
=
await
_repository
.
getLogs
();
if
(
result
.
success
&&
result
.
data
!=
null
)
{
return
result
.
data
!;
}
throw
Exception
(
result
.
msg
);
}
}
```
### 3.
2 第二
步:定义 State
### 3.
4 第四
步:定义 State
创建
`lib/views/home/operation_log/cubit/operation_log_state.dart`
:
```
dart
import
'package:equatable/equatable.dart'
;
import
'package:smart_hotel_app/models/bo/operation_log_bo.dart'
;
class
OperationLogState
extends
Equatable
{
final
List
<
LogItem
>
logs
;
final
List
<
OperationLogBO
>
logs
;
final
bool
isLoading
;
final
String
?
error
;
...
...
@@ -92,57 +167,45 @@ class OperationLogState extends Equatable {
});
OperationLogState
copyWith
({
List
<
LogItem
>?
logs
,
List
<
OperationLogBO
>?
logs
,
bool
?
isLoading
,
String
?
error
,
})
{
return
OperationLogState
(
logs:
logs
??
this
.
logs
,
isLoading:
isLoading
??
this
.
isLoading
,
error:
error
??
this
.
error
,
error:
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
### 3.
5 第五步:定义 Cubit(调用 Service)
创建
`lib/views/home/operation_log/cubit/operation_log_cubit.dart`
:
```
dart
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:smart_hotel_app/services/operation_log_service.dart'
;
import
'operation_log_state.dart'
;
class
OperationLogCubit
extends
Cubit
<
OperationLogState
>
{
OperationLogCubit
()
:
super
(
const
OperationLogState
())
{
final
OperationLogService
_service
;
OperationLogCubit
({
required
OperationLogService
service
})
:
_service
=
service
,
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'
),
],
));
final
logs
=
await
_service
.
getLogs
();
emit
(
state
.
copyWith
(
isLoading:
false
,
logs:
logs
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
}
...
...
@@ -150,7 +213,7 @@ class OperationLogCubit extends Cubit<OperationLogState> {
}
```
### 3.
4 第四
步:创建 View
### 3.
6 第六
步:创建 View
创建
`lib/views/home/operation_log/operation_log_view.dart`
:
...
...
@@ -160,6 +223,8 @@ import 'package:flutter/material.dart';
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'cubit/operation_log_cubit.dart'
;
import
'cubit/operation_log_state.dart'
;
import
'package:smart_hotel_app/services/operation_log_service.dart'
;
import
'package:smart_hotel_app/repositories/operation_log_repository.dart'
;
@RoutePage
()
class
OperationLogView
extends
StatelessWidget
{
...
...
@@ -168,7 +233,11 @@ class OperationLogView extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
return
BlocProvider
(
create:
(
_
)
=>
OperationLogCubit
(),
create:
(
_
)
=>
OperationLogCubit
(
service:
OperationLogService
(
repository:
OperationLogRepository
(),
),
),
child:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'运维日志'
)),
body:
BlocBuilder
<
OperationLogCubit
,
OperationLogState
>(
...
...
@@ -176,6 +245,9 @@ class OperationLogView extends StatelessWidget {
if
(
state
.
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
if
(
state
.
error
!=
null
)
{
return
Center
(
child:
Text
(
'错误:
${state.error}
'
));
}
return
ListView
.
builder
(
itemCount:
state
.
logs
.
length
,
itemBuilder:
(
context
,
index
)
{
...
...
@@ -194,7 +266,7 @@ class OperationLogView extends StatelessWidget {
}
```
### 3.
5 第五
步:注册路由
### 3.
7 第七
步:注册路由
在
[
app_router.dart
](
file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/routes/app_router.dart
)
中添加:
...
...
@@ -208,7 +280,7 @@ AutoRoute(page: OperationLogRoute.page),
flutter pub run build_runner build
--delete-conflicting-outputs
```
### 3.
6 第六
步:页面跳转
### 3.
8 第八
步:页面跳转
```
dart
// 在任意页面中导航
...
...
@@ -217,48 +289,236 @@ context.pushRoute(const OperationLogRoute());
---
## 4. 如何发起网络请求
## 4. 如何发起网络请求(完整流程)
### 4.1 数据流向总览
### 4.1 新增 Repository
```
View (UI) → Cubit (状态管理) → Service (业务逻辑) → Repository (API调用) → DioRequest (网络层)
```
在
`lib/repositories/`
下新建文件:
### 4.2 新增 Repository
在
`lib/repositories/`
下新建文件,使用
`fromJsonT`
参数传入反序列化函数:
```
dart
import
'../http/response_model.dart'
;
import
'../http/dio_request.dart'
;
import
'../models/bo/device_bo.dart'
;
class
DeviceRepository
{
Future
<
ResponseModel
>
getDeviceList
()
{
return
DioRequest
.
instance
.
get
(
'/api/devices'
);
/// 获取设备列表
Future
<
ResponseModel
<
List
<
DeviceBO
>>>
getDeviceList
()
{
return
DioRequest
.
instance
.
get
<
List
<
DeviceBO
>>(
'/api/devices'
,
fromJsonT:
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
DeviceBO
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
();
},
);
}
/// 获取设备详情
Future
<
ResponseModel
<
DeviceBO
>>
getDeviceDetail
(
String
id
)
{
return
DioRequest
.
instance
.
get
<
DeviceBO
>(
'/api/devices/
$id
'
,
fromJsonT:
(
data
)
=>
DeviceBO
.
fromJson
(
data
as
Map
<
String
,
dynamic
>),
);
}
Future
<
ResponseModel
>
getDeviceDetail
(
String
id
)
{
return
DioRequest
.
instance
.
get
(
'/api/devices/
$id
'
);
/// 发送设备控制指令
Future
<
ResponseModel
>
controlDevice
(
String
id
,
Map
<
String
,
dynamic
>
params
)
{
return
DioRequest
.
instance
.
post
(
'/api/devices/
$id
/control'
,
data:
params
);
}
}
```
### 4.2 在 Cubit 中调用
#### 4.2.1 POST 请求示例(简单到复杂)
**示例 1:简单 POST,无请求体,无响应解析**
```
dart
/// 退出登录 - 最简单的 POST
Future
<
ResponseModel
>
logout
()
{
return
DioRequest
.
instance
.
post
(
'/api/logout'
);
}
```
**示例 2:POST 带简单请求体,不解析响应**
```
dart
/// 登录 - 表单提交
Future
<
ResponseModel
>
login
(
String
username
,
String
password
)
{
return
DioRequest
.
instance
.
post
(
'/api/login'
,
data:
{
'username'
:
username
,
'password'
:
password
,
});
}
```
**示例 3:POST 带路径参数,不解析响应**
```
dart
/// 切换规则开关
Future
<
ResponseModel
>
toggleRule
(
String
ruleId
,
bool
enabled
)
{
return
DioRequest
.
instance
.
post
(
'/api/rules/
$ruleId
/toggle'
,
data:
{
'enabled'
:
enabled
,
});
}
```
**示例 4:POST 带路径参数 + 查询参数,不解析响应**
```
dart
/// 提交巡检结果(带查询参数如 ?inspectionId=xxx)
Future
<
ResponseModel
>
submitInspectionResult
(
String
deviceId
,
{
required
String
inspectionId
,
required
Map
<
String
,
dynamic
>
result
,
})
{
return
DioRequest
.
instance
.
post
(
'/api/inspections/devices/
$deviceId
/submit'
,
data:
result
,
queryParameters:
{
'inspectionId'
:
inspectionId
},
);
}
```
**示例 5:POST 带复杂请求体,解析响应数据**
```
dart
/// 批量设备控制 - 返回操作结果列表
Future
<
ResponseModel
<
List
<
ControlResultBO
>>>
batchControlDevices
(
List
<
Map
<
String
,
dynamic
>>
commands
,
)
{
return
DioRequest
.
instance
.
post
<
List
<
ControlResultBO
>>(
'/api/devices/batch-control'
,
data:
{
'commands'
:
commands
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
},
fromJsonT:
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
ControlResultBO
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
();
},
);
}
```
**示例 6:POST 带可选参数,解析单个响应对象**
```
dart
/// 创建巡检任务 - 部分字段可选
Future
<
ResponseModel
<
InspectionTaskBO
>>
createInspectionTask
({
required
String
name
,
required
List
<
String
>
deviceIds
,
String
?
assignee
,
DateTime
?
scheduledTime
,
String
?
remark
,
})
{
final
body
=
<
String
,
dynamic
>{
'name'
:
name
,
'deviceIds'
:
deviceIds
,
};
if
(
assignee
!=
null
)
body
[
'assignee'
]
=
assignee
;
if
(
scheduledTime
!=
null
)
body
[
'scheduledTime'
]
=
scheduledTime
.
toIso8601String
();
if
(
remark
!=
null
)
body
[
'remark'
]
=
remark
;
return
DioRequest
.
instance
.
post
<
InspectionTaskBO
>(
'/api/inspections'
,
data:
body
,
fromJsonT:
(
data
)
=>
InspectionTaskBO
.
fromJson
(
data
as
Map
<
String
,
dynamic
>),
);
}
```
**示例 7:PUT 请求(更新资源)**
```
dart
/// 更新房间设备状态
Future
<
ResponseModel
<
RoomBO
>>
updateRoomDevice
(
String
roomNumber
,
String
deviceName
,
Map
<
String
,
dynamic
>
settings
,
)
{
return
DioRequest
.
instance
.
put
<
RoomBO
>(
'/api/rooms/
$roomNumber
/devices/
$deviceName
'
,
data:
settings
,
fromJsonT:
(
data
)
=>
RoomBO
.
fromJson
(
data
as
Map
<
String
,
dynamic
>),
);
}
```
**示例 8:DELETE 请求**
```
dart
/// 删除巡检记录
Future
<
ResponseModel
>
deleteInspectionRecord
(
String
recordId
)
{
return
DioRequest
.
instance
.
delete
(
'/api/inspections/records/
$recordId
'
);
}
```
### 4.3 新增 Service(业务逻辑封装)
在
`lib/services/`
下新建文件:
```
dart
import
'../repositories/device_repository.dart'
;
import
'../models/bo/device_bo.dart'
;
class
DeviceService
{
final
DeviceRepository
_repository
;
DeviceService
({
required
DeviceRepository
repository
})
:
_repository
=
repository
;
Future
<
List
<
DeviceBO
>>
getDevices
()
async
{
final
result
=
await
_repository
.
getDeviceList
();
if
(
result
.
success
&&
result
.
data
!=
null
)
{
return
result
.
data
!;
}
throw
Exception
(
result
.
msg
);
}
Future
<
DeviceBO
>
getDeviceDetail
(
String
id
)
async
{
final
result
=
await
_repository
.
getDeviceDetail
(
id
);
if
(
result
.
success
&&
result
.
data
!=
null
)
{
return
result
.
data
!;
}
throw
Exception
(
result
.
msg
);
}
}
```
### 4.4 在 Cubit 中调用 Service
```
dart
class
DeviceCubit
extends
Cubit
<
DeviceState
>
{
final
DeviceRepository
_repository
=
DeviceRepository
();
final
DeviceService
_service
;
DeviceCubit
({
required
DeviceService
service
})
:
_service
=
service
,
super
(
const
DeviceState
())
{
loadDevices
();
}
Future
<
void
>
loadDevices
()
async
{
emit
(
state
.
copyWith
(
isLoading:
true
));
try
{
final
result
=
await
_repository
.
getDeviceList
();
if
(
result
.
success
)
{
// 处理 result.data
}
final
devices
=
await
_service
.
getDevices
();
emit
(
state
.
copyWith
(
isLoading:
false
,
devices:
devices
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
error:
e
.
toString
()));
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
}
}
}
```
### 4.
3
API 响应格式约定
### 4.
5
API 响应格式约定
后端接口返回标准 JSON 格式:
...
...
技术方案.md
View file @
a1177508
# 智慧酒
店 App 技术方案
# 智慧酒
店 App 技术方案
...
...
@@ -149,6 +149,58 @@ lib/
---
## 3.1 数据模型(BO)规范
所有业务数据模型统一定义在
`models/bo/`
目录下,按模块分文件。
**当前状态**
:大部分数据模型散落在各页面的
`state.dart`
文件中(如
`AlarmItem`
、
`InspectionDevice`
、
`RoomInfo`
等),需要在对接时迁移到
`models/bo/`
。
### 建议的 BO 文件组织
```
models/bo/
├── user_info_bo.dart # 用户信息(已有)
├── alarm_bo.dart # 告警相关:AlarmItem, AlarmInfo, AlarmDetail
├── device_bo.dart # 设备相关:DeviceInfo, EquipmentItem, RoomDevice
├── inspection_bo.dart # 巡检相关:InspectionDevice, InspectionRecord, InspectionItem
├── topology_bo.dart # 拓扑相关:TopologyNode
├── energy_bo.dart # 能耗相关:ZoneData, EnergyOverview, MeterData
├── room_bo.dart # 房间相关:RoomInfo, RoomDeviceStatus, RoomStatus
├── rule_bo.dart # 规则相关:RuleInfo
└── report_bo.dart # 报告相关:ReportMetrics, WeeklyPowerData
```
### BO 模型定义规范
```
dart
// 必须使用 Equatable,提供 copyWith
class
SomeBO
extends
Equatable
{
final
String
id
;
final
String
name
;
// ... 字段
const
SomeBO
({
required
this
.
id
,
required
this
.
name
});
// 从 JSON 反序列化(用于 API 响应解析)
factory
SomeBO
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
SomeBO
(
id:
json
[
'id'
]
as
String
,
name:
json
[
'name'
]
as
String
,
);
}
// 序列化为 JSON(用于 API 请求体)
Map
<
String
,
dynamic
>
toJson
()
=>
{
'id'
:
id
,
'name'
:
name
};
// copyWith(用于状态更新)
SomeBO
copyWith
({
String
?
id
,
String
?
name
})
=>
SomeBO
(
id:
id
??
this
.
id
,
name:
name
??
this
.
name
);
@override
List
<
Object
?>
get
props
=>
[
id
,
name
];
}
```
---
## 4. 核心技术说明
### 4.1 状态管理:BLoC + Cubit
...
...
@@ -343,6 +395,47 @@ Token 过期:
| 房间能耗 |
`ReportRoomDetailRoute`
| 楼层选择、房间网格、详情卡片 |
| 规则管理 |
`ReportRuleManagementRoute`
| 规则卡片管理 |
### 5.4 当前状态:Mock 数据阶段
**页面 View 层已完成开发**
,所有页面 UI 可正常渲染,但数据全部为硬编码 mock 数据。
**现状分析**
:
| 层级 | 状态 | 说明 |
|------|------|------|
| View 层 | 已完成 | 所有页面 UI 组件已完成 |
| State 层 | 已完成 | 各页面 State + copyWith 已定义 |
| Cubit 层 | Mock 阶段 |
`initData()`
中直接塞入 mock 数据,未调用 Service |
| Service 层 | 仅 Auth | 只有
`AuthService`
存在,页面级 Service 全部缺失 |
| Repository 层 | 仅 Auth | 只有
`AuthRepository`
存在,页面级 Repository 全部缺失 |
| BO 模型 | 散落 | 数据模型定义在 state 文件中,未统一到
`models/bo/`
|
### 5.5 对接改造步骤(View 层不变)
每个页面模块的改造遵循统一流程,
**View 层代码无需修改**
(因为 Cubit/State 接口不变):
```
第一步:将 State 中的数据模型提取到 models/bo/ 目录
第二步:新增 Repository(API 调用)
第三步:新增 Service(业务逻辑)
第四步:修改 Cubit:将 initData() 中的 mock 数据替换为 Service 调用
第五步:在 main.dart 中注入 Service/Repository(如需要全局单例)
```
**改造示例**
(以告警列表页为例):
```
改造前:
AbnormalListCubit.initData() → 直接 emit(mockData)
改造后:
AbnormalListCubit.loadData() → AbnormalListService.getAlarms()
→ AbnormalListRepository.getAlarms() → DioRequest.get('/api/alarms')
→ ResponseModel.data → AlarmListBO.fromJson() → emit(realData)
```
详细对接规范见
[
对接方案.md
](
./对接方案.md
)
。
---
## 6. 依赖库清单
...
...
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