[F9/F10] 子系统管理 + 告警正交 5 态列表(UI 骨架 + mock)

两任务并行完成,因同动 router/iot.ts 与 locales/page.json 合并 commit。

F9 子系统管理 + 设备批量绑定:
- apps/web-antd/src/api/iot/subsystem/index.ts
- apps/web-antd/src/views/iot/subsystem/{list,form,devices}.vue
- apps/web-antd/src/views/iot/subsystem/__tests__/subsystem-utils.test.ts (18 用例)
- Known Pitfalls: 评审 A6 Redis stats 降级 / A7 403 拦截器 / 批量 100 台 / code regex snake_case
- 后端 B10/B11 API 契约: /iot/subsystem/{page,get,create,update,delete,simple-list,device-count}
  + /iot/device/{batchBindSubsystem,bindSubsystem} + /iot/device/page 加 subsystemId 过滤

F10 告警记录 (评审 C1 正交 5 态):
- apps/web-antd/src/views/iot/alarm/record/{list,detail}.vue
- apps/web-antd/src/views/iot/alarm/record/{alarm-state,api}.ts
- apps/web-antd/src/views/iot/alarm/record/components/{AlarmStateTag,AlarmOperations}.vue
- apps/web-antd/src/views/iot/alarm/record/__tests__/AlarmStateTag.spec.ts (12 用例)
- 5 态: 活跃·未确认(red) / 活跃·已确认(orange) / 已清除·未确认(gold醒目) / 已清除·已确认(green) / 已归档(default)
- Known Pitfalls: C1 5 态必展示 / archived 优先判断 / 30s 轮询 / 严重度颜色映射
- 后端 B12 API 契约: /iot/alarm-record/{page,get,ack,unack,clear,archive,batch-*,history,remark}

共同:
- apps/web-antd/src/router/routes/modules/iot.ts 追加 subsystem + alarm 路由
- locales/langs/{zh-CN,en-US}/page.json 追加 iot.subsystem.* + iot.alarm.*

note: 路由用顶级路径;项目采用动态菜单机制,菜单注册需后端 menu 表配置(运维侧工作),
      前端路由可通过 URL 直接访问。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-23 22:39:47 +08:00
parent 8613641d1d
commit ba459aa1d7
15 changed files with 2443 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace IotSubsystemApi {
/** 子系统 */
export interface Subsystem {
id?: number; // 子系统编号
name: string; // 子系统名称
code: string; // 子系统编码(小写+下划线,如 security / energy / clean
description?: string; // 子系统描述
icon?: string; // 子系统图标 URL 或 iconify 名称
status: number; // 状态0=禁用 1=启用)
sort?: number; // 排序
projectId?: number; // 所属项目 ID预留字段当前版本不启用
deviceCount?: number; // 设备数量(来自 /iot/subsystem/device-count 聚合,非实时 count
createTime?: Date; // 创建时间
updateTime?: Date; // 更新时间
}
/** 子系统分页查询参数 */
export interface SubsystemPageParam extends PageParam {
keyword?: string; // 关键字(名称/编码模糊搜索)
status?: number; // 状态过滤
}
/** 子系统精简信息(下拉列表) */
export interface SubsystemSimple {
id: number;
name: string;
code: string;
}
/** 设备统计 map 中单条数据 */
export interface SubsystemDeviceCount {
subsystemId: number;
total: number;
online: number;
alarm: number;
}
/** 批量绑定子系统请求体 */
export interface BatchBindSubsystemReqVO {
deviceIds: number[]; // 设备编号列表(每批 ≤ 100
subsystemId: number; // 目标子系统 ID
}
/** 单设备绑定/解绑子系统请求体 */
export interface BindSubsystemReqVO {
deviceId: number;
subsystemId: null | number; // null = 解绑(移除归属)
}
}
// ─── 子系统 CRUD ────────────────────────────────────────────────────────────
/** 创建子系统 */
export function createSubsystem(data: IotSubsystemApi.Subsystem) {
return requestClient.post<number>('/iot/subsystem/create', data);
}
/** 更新子系统 */
export function updateSubsystem(data: IotSubsystemApi.Subsystem) {
return requestClient.put<boolean>('/iot/subsystem/update', data);
}
/**
* 删除子系统
* 后端会校验子系统下是否仍有设备,有则返回业务异常(前端 deleteCount 检查后禁用按钮)
*/
export function deleteSubsystem(id: number) {
return requestClient.delete<boolean>(`/iot/subsystem/delete?id=${id}`);
}
/** 获取子系统详情 */
export function getSubsystem(id: number) {
return requestClient.get<IotSubsystemApi.Subsystem>(
`/iot/subsystem/get?id=${id}`,
);
}
/** 子系统分页查询 */
export function getSubsystemPage(params: IotSubsystemApi.SubsystemPageParam) {
return requestClient.get<PageResult<IotSubsystemApi.Subsystem>>(
'/iot/subsystem/page',
{ params },
);
}
/**
* 子系统精简列表(下拉)
* 注意:⚠️ [评审 A7] 此接口已加权限403 由请求拦截器统一处理
*/
export function getSubsystemSimpleList() {
return requestClient.get<IotSubsystemApi.SubsystemSimple[]>(
'/iot/subsystem/simple-list',
);
}
// ─── 设备统计B10 需提供)────────────────────────────────────────────────────
/**
* 各子系统设备统计Redis 聚合)
* ⚠️ [评审 A6] 统计数据来自 Redis 缓存,不做实时 count
* B10 需要实现GET /iot/subsystem/device-count
* 返回格式:{ [subsystemId]: { total, online, alarm } }
*/
export function getSubsystemDeviceCount() {
return requestClient.get<
Record<string, IotSubsystemApi.SubsystemDeviceCount>
>('/iot/subsystem/device-count');
}
// ─── 设备归属操作B11 需提供)───────────────────────────────────────────────
/**
* 批量绑定设备到子系统
* ⚠️ 每批 ≤ 100 台,调用方需自行分批
* B11 需要实现PUT /iot/device/batchBindSubsystem
*/
export function batchBindSubsystem(
data: IotSubsystemApi.BatchBindSubsystemReqVO,
) {
return requestClient.put<boolean>('/iot/device/batchBindSubsystem', data);
}
/**
* 单设备解绑子系统(移除归属)
* B11 需要实现PUT /iot/device/bindSubsystemsubsystemId=null 表示解绑)
*/
export function unbindDeviceSubsystem(deviceId: number) {
return requestClient.put<boolean>('/iot/device/bindSubsystem', {
deviceId,
subsystemId: null,
} satisfies IotSubsystemApi.BindSubsystemReqVO);
}