Commit a1177508 authored by 张宏's avatar 张宏

.

parent b95e2acc
......@@ -50,3 +50,9 @@ app.*.map.json
# ai相关
ai_work/
## 文档相关
测试方案.md
对接方案.md
技术方案.md
上手帮助.md
\ No newline at end of file
# 智能酒店管理系统 - 架构迁移方案
## 一、现有架构分析
### 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
# 智慧酒店 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 格式:
......
# 智慧酒店 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. 依赖库清单
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment