两任务并行完成,因同动 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>
137 lines
4.6 KiB
TypeScript
137 lines
4.6 KiB
TypeScript
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/bindSubsystem(subsystemId=null 表示解绑)
|
||
*/
|
||
export function unbindDeviceSubsystem(deviceId: number) {
|
||
return requestClient.put<boolean>('/iot/device/bindSubsystem', {
|
||
deviceId,
|
||
subsystemId: null,
|
||
} satisfies IotSubsystemApi.BindSubsystemReqVO);
|
||
}
|