# Device Planning 快速说明

`devicePlanning` 是 `SaasDevicePlanning` 低代码物料源码，用来管理租户设备规划。当前页面覆盖工作区管理、三类设备列表、设备增删改、设备详情、智能设备与外设连接，以及外设连接预览。

## 先看哪里

- `index.tsx`：页面入口，只初始化上下文、组合模块、串联弹窗动作。
- `api/`：后端接口封装，按 `workAreas`、`devices`、`deviceModels`、`deviceConnections` 拆分。
- `model/`：领域类型、常量和后端数据到展示模型的转换。
- `modules/workAreas/`：工作区列表、创建/编辑/删除弹窗和 `useWorkAreas`。
- `modules/devices/`：设备表格、设备新增/编辑/删除弹窗和设备列表/型号 hooks。
- `modules/deviceDetail/`：详情抽屉壳层、详情信息页、侧边 tab 和 `useDeviceDetail`。
- `modules/deviceConnections/`：连接页、连接弹窗、外设预览和连接相关 hooks。
- `components/shared/`：当前模块内跨业务域复用的表单、状态、图标等 UI。
- `shared/`：跨业务域复用的展示工具和常量。
- `locales/`：静态文案，多语言字段展示不要从这里取。

根层 `serve.ts`、`types.ts`、`constants.ts`、`utils.ts` 目前只作为兼容导出保留。新增代码优先从 `api`、`model`、`shared` 或对应 `modules/*` 入口引用。

低代码引擎通过 `useEngineContext()` 注入 `appHelper.utils.request`。入口组件会在初始化时调用 `request.setRequest(utils.request)`，所以子模块不要自己创建 request client。

## 当前功能

- 左侧工作区支持列表、选中、创建、编辑、删除和拖拽排序。
- 删除工作区前会拉取 `smartDevices`、`dumbDevices`、`integratedDevices` 数量；工作区下仍有设备时由删除弹窗阻断确认。
- 右侧设备按工作区和设备种类展示，种类为 `smart`、`dumb`、`integrated`。
- 设备列表支持搜索、型号筛选、本地分页、拖拽排序和空状态快捷新增；`smart` 列表会在名称后展示 `Device Short Number`。
- 新增设备先选型号，再填写名称、描述和 profile；创建成功后刷新列表，并自动打开新设备详情。
- 编辑设备可更新工作区、名称、描述和 smart profile；删除设备成功后关闭详情并刷新列表。
- 详情抽屉包含信息页和连接页；`integrated` 设备没有连接页。
- 详情抽屉 Basic Information 会展示设备描述，后端返回 `null` 或缺失时显示为空值占位。
- 连接页支持连接已有设备、新增外设并连接、编辑连接配置、删除连接。
- 连接已有设备弹窗顶部的两个设备摘要会在型号下方展示设备描述，最多两行，超出省略。
- smart 连接页的外设卡片支持点击小眼睛打开只读预览弹窗，查看外设基础信息、在线状态和连接到它的 smart 设备列表。

## 目录结构

```text
devicePlanning/
  api/
    devices.ts
    deviceConnections.ts
    deviceModels.ts
    workAreas.ts
    index.ts
  model/
    adapters.ts
    constants.ts
    types.ts
    index.ts
  modules/
    workAreas/
    devices/
    deviceDetail/
    deviceConnections/
  components/shared/
  shared/
  locales/
  index.tsx
  index.less
```

模块依赖方向：

```mermaid
flowchart TD
  PageEntry[index.tsx] --> WorkAreas[modules/workAreas]
  PageEntry --> Devices[modules/devices]
  PageEntry --> DeviceDetail[modules/deviceDetail]
  DeviceDetail --> DeviceConnections[modules/deviceConnections]
  WorkAreas --> Api[api]
  Devices --> Api
  DeviceConnections --> Api
  WorkAreas --> Model[model]
  Devices --> Model
  DeviceDetail --> Model
  DeviceConnections --> Model
  WorkAreas --> Shared[components/shared and shared]
  Devices --> Shared
  DeviceDetail --> Shared
  DeviceConnections --> Shared
```

## 入口组件职责

`index.tsx` 保持薄入口，主要做四件事：

1. 初始化 request 和静态文案。
2. 维护当前设备 tab：`activeKind`。
3. 组合 `useWorkAreas`、`useDeviceModels`、`useDeviceList`、`useDevicePlanningDialogs`。
4. 串联创建、编辑、删除后的刷新、toast、弹窗关闭和详情打开。

切换工作区或设备种类时必须调用 `deviceList.resetDeviceView()`，清空搜索、型号筛选并回到第一页。

## 数据流

设备列表由 `selectedWorkAreaId + activeKind` 驱动：

```mermaid
flowchart TD
  WorkArea[selectedWorkAreaId] --> DeviceList[useDeviceList]
  Kind[activeKind] --> DeviceList
  Models[useDeviceModels] --> TypeOptions[typeOptions]
  DeviceList --> Rows[DeviceRow tableData]
  TypeOptions --> DeviceTable[DeviceTable]
  Rows --> DeviceTable
  DeviceTable --> Detail[DeviceDetailDrawer]
```

列表和详情使用 `DeviceRow` 作为展示模型，原始后端数据保留在 `_raw`。不要手写 `DeviceRow`，统一使用 `model.createDeviceRow()`，因为 `link/status/profileName` 等展示值在这里统一处理。

## 模块说明

### Work Areas

`modules/workAreas` 管理工作区列表、当前选中项和工作区 CRUD。

- 初次加载后默认选中排序后的第一个工作区。
- 创建或编辑成功后刷新工作区，并选中新建/编辑项。
- 拖拽排序先乐观更新，再调用 `/tenant/device/workarea/sort`；失败时重新拉取后端顺序。
- 删除前使用 `getWorkAreaDetail()` 拉取设备数量，弹窗展示计数并决定是否允许删除。

没有工作区时，右侧只展示空状态，不发设备列表请求。

### Devices

`modules/devices` 管理设备列表、型号懒加载、设备新增/编辑/删除。

- `useDeviceList` 请求 `getDevices({ workarea_id, type_kind, with: ['type', 'model', 'workarea'] })`。
- 搜索、型号筛选、分页都在前端完成。
- `smart` 设备表格列顺序为名称、设备短号、类型、型号、结构、配置方案、连接状态和在线状态。
- 表格拖拽排序会提交当前过滤后列表的 id 顺序到 `/tenant/device/sort`；失败时重新拉取。
- `useDeviceModels` 按 `DeviceKind` 懒加载型号，每类设备型号只请求一次，请求失败允许下次重试。
- 外设品牌 `getDeviceBrands()` 只在新增入口按需加载。

新增设备成功后的特殊逻辑仍在入口 `handleCreateDevice()`：关闭新增弹窗、回到第一页、刷新列表，并在有新设备 id 时打开详情。设备 `description` 是非必填多语言字段，后端可能返回 `null` 或不返回字段，表单初始化和详情展示都需要兼容空值。

### Device Detail

`modules/deviceDetail` 负责详情抽屉壳层、信息页和详情 tab 状态。

- `DeviceDetailDrawer` 由入口组件条件挂载，并用设备 id 作为 key，避免切换设备时复用旧抽屉状态。
- `useDeviceDetail` 管理当前 tab、详情补拉取、`integrated` 禁止进入连接页。
- `DeviceInfoPanel` 展示基础信息、设备描述、连接状态、在线状态和设备上报信息。
- `DrawerTabKey` 放在 `modules/deviceDetail/types.ts`，不要从具体 UI 组件文件导入。

### Device Connections

`modules/deviceConnections` 负责连接页、连接弹窗、外设预览和连接请求状态。

- `useDeviceConnections` 只在抽屉打开且 tab 为 `connections` 时加载连接列表。
- 连接变更后必须触发 `onConnectionsChanged`，父级会刷新设备列表，保证列表上的连接状态和在线状态同步。
- `DeviceConnectionsPanel` 是连接功能入口。
- `ConnectExistingDeviceModal` 支持三种模式：`existing`、`new`、`edit`。
- 连接弹窗顶部 `DeviceConnectionSummary` 展示 smart 设备和外设摘要；设备描述只展示两行，超出用省略号。
- `ConnectedDevicePreviewModal` 复用 `modules/deviceDetail/components/DeviceInfoPanel` 展示外设基础信息和状态。

连接配置规则：

- `connection_type` 必填。
- 连接方式选项按当前外设型号的 `model.data.connection_types` 过滤；如果配置为空，或过滤后没有匹配到内置连接方式，则回退展示全部连接方式。
- `ethernet` 和 `wifi` 需要 `connection_method`。
- `connection_method` 为 `manual` 时才提交 `connection_ip`。
- profile 是否必填由型号的 `profile_structure.required` 决定。

`Add New Peripheral` 不调用 `createDevice()`，而是通过创建连接接口嵌套创建外设。

## 样式约定

- `index.less`：只保留页面壳层、左右布局、右侧标题和空工作区状态。
- `modules/workAreas/components/WorkAreaList/index.less`：工作区列表样式。
- `modules/workAreas/components/DeleteWorkAreaModal/index.less`：删除工作区弹窗样式。
- `modules/devices/components/DeviceTable/index.less`：设备表格样式。
- `modules/devices/components/AddDeviceModal/index.less`：新增设备弹窗和型号选择样式。
- `modules/devices/components/EditDeviceModal/index.less`：编辑设备弹窗样式。
- `modules/deviceDetail/components/DeviceDetailDrawer/index.less`：抽屉壳层、sidebar、header、content 和 footer。
- `modules/deviceDetail/components/DeviceInfoPanel.less`：详情信息页样式。
- `modules/deviceConnections/components/DeviceConnectionsPanel.less`：连接页列表、空状态、连接项和外设预览弹窗样式。
- `modules/deviceConnections/components/ConnectExistingDeviceModal.less`：连接弹窗、选择已有设备、新增外设、连接配置和连接摘要样式。

修改样式时优先改对应组件相邻 less，不要把子组件样式重新放回页面入口 `index.less`。

## 开发注意事项

- 新增接口先放到 `api/` 对应资源文件，再从 `api/index.ts` 导出。
- 新增领域类型、展示模型转换或业务常量放到 `model/`。
- 新增跨业务域 UI 放到 `components/shared/`；纯展示工具放到 `shared/`。
- 新增业务功能优先落到对应 `modules/*`，不要继续堆到顶层 `components/` 或 `hooks/`。
- 不要绕过 `createDeviceRow()` 自己组装列表或详情展示模型。
- 修改设备连接 payload 时，同步检查 `useConnectDeviceModal.handleSubmit()`。
