# 智慧酒店 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.3 Provider 分层结构

项目通过 `MultiBlocProvider` 的嵌套实现不同作用域的状态管理：

```
MaterialApp (main.dart)
  └─ MultiBlocProvider (全局)
       ├─ AuthBloc  ← 所有页面都能访问
       │
       ├─ LoginRoute()          ← 可以 read<AuthBloc>
       │
       └─ DefaultLayoutRoute()  ← 可以 read<AuthBloc>
            └─ MultiBlocProvider (仅主页范围)
                 ├─ HomeIndexCubit    ← 仅在 tab 页面内可访问
                 ├─ ServiceIndexCubit ← 仅在 tab 页面内可访问
                 ├─ ReportIndexCubit  ← 仅在 tab 页面内可访问
                 │
                 └─ AutoTabsScaffold
                      ├─ HomeView      ← 可以 read<AuthBloc> + read<HomeIndexCubit>
                      ├─ ServiceView   ← 可以 read<AuthBloc> + read<ServiceIndexCubit>
                      ├─ ReportView    ← 可以 read<AuthBloc> + read<ReportIndexCubit>
                      └─ ProfileView   ← 可以 read<AuthBloc>（但不能读上面三个Cubit）
```

| 层级 | 定义位置 | 作用域 | 提供者 | 特点 |
|------|---------|--------|--------|------|
| 全局 Provider | `main.dart` | 整个 App | `AuthBloc`（认证） | `.value()` 复用已有实例，App 存活期间一直存在 |
| 主页 Provider | `default_view.dart` | 仅底部 Tab 页面 | `HomeIndexCubit`、`ServiceIndexCubit`、`ReportIndexCubit` | `create` 创建新实例，仅在主页存活期间存在 |

**子 `MultiBlocProvider` 可以访问父 `MultiBlocProvider` 提供的内容**（tab 页面中 `context.read<AuthBloc>()` 依然有效），反过来不行（`AuthBloc` 里不能读取业务 Cubit）。

项目未使用 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
```

---

## 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

项目混合使用 **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` | 规则卡片管理 |

### 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. 依赖库清单

| 库 | 版本 | 用途 |
|-----|------|------|
| 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 异常转换为项目内部异常类型，方便上层统一处理。