diff --git a/apps/web-antdv-next/src/api/iot/alert/config/index.ts b/apps/web-antdv-next/src/api/iot/alert/config/index.ts index 26e114c0c..a1d3bdfcc 100644 --- a/apps/web-antdv-next/src/api/iot/alert/config/index.ts +++ b/apps/web-antdv-next/src/api/iot/alert/config/index.ts @@ -3,37 +3,24 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace AlertConfigApi { - /** IoT 告警配置 VO */ + /** IoT 告警配置 */ export interface AlertConfig { id?: number; - name: string; + name?: string; description?: string; level?: number; status?: number; sceneRuleIds?: number[]; receiveUserIds?: number[]; - receiveUserNames?: string; + receiveUserNames?: string[]; receiveTypes?: number[]; + smsTemplateCode?: string; + mailTemplateCode?: string; + notifyTemplateCode?: string; createTime?: Date; - updateTime?: Date; } } -/** IoT 告警配置 */ -export interface AlertConfig { - id?: number; - name?: string; - description?: string; - level?: number; - status?: number; - sceneRuleIds?: number[]; - receiveUserIds?: number[]; - receiveUserNames?: string; - receiveTypes?: number[]; - createTime?: Date; - updateTime?: Date; -} - /** 查询告警配置分页 */ export function getAlertConfigPage(params: PageParam) { return requestClient.get>( @@ -49,20 +36,20 @@ export function getAlertConfig(id: number) { ); } -/** 查询所有告警配置列表 */ -export function getAlertConfigList() { +/** 获取告警配置简单列表 */ +export function getSimpleAlertConfigList() { return requestClient.get( - '/iot/alert-config/list', + '/iot/alert-config/simple-list', ); } /** 新增告警配置 */ -export function createAlertConfig(data: AlertConfig) { +export function createAlertConfig(data: AlertConfigApi.AlertConfig) { return requestClient.post('/iot/alert-config/create', data); } /** 修改告警配置 */ -export function updateAlertConfig(data: AlertConfig) { +export function updateAlertConfig(data: AlertConfigApi.AlertConfig) { return requestClient.put('/iot/alert-config/update', data); } @@ -70,25 +57,3 @@ export function updateAlertConfig(data: AlertConfig) { export function deleteAlertConfig(id: number) { return requestClient.delete(`/iot/alert-config/delete?id=${id}`); } - -/** 批量删除告警配置 */ -export function deleteAlertConfigList(ids: number[]) { - return requestClient.delete('/iot/alert-config/delete-list', { - params: { ids: ids.join(',') }, - }); -} - -/** 启用/禁用告警配置 */ -export function toggleAlertConfig(id: number, enabled: boolean) { - return requestClient.put(`/iot/alert-config/toggle`, { - id, - enabled, - }); -} - -/** 获取告警配置简单列表 */ -export function getSimpleAlertConfigList() { - return requestClient.get( - '/iot/alert-config/simple-list', - ); -} diff --git a/apps/web-antdv-next/src/api/iot/alert/record/index.ts b/apps/web-antdv-next/src/api/iot/alert/record/index.ts index 6d2afcfcf..fea6e17a4 100644 --- a/apps/web-antdv-next/src/api/iot/alert/record/index.ts +++ b/apps/web-antdv-next/src/api/iot/alert/record/index.ts @@ -3,41 +3,21 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace AlertRecordApi { - /** IoT 告警记录 VO */ + /** IoT 告警记录 */ export interface AlertRecord { id?: number; configId?: number; configName?: string; configLevel?: number; deviceId?: number; - deviceName?: string; productId?: number; - productName?: string; - deviceMessage?: string; + deviceMessage?: any; processStatus?: boolean; processRemark?: string; - processTime?: Date; createTime?: Date; } } -/** IoT 告警记录 */ -export interface AlertRecord { - id?: number; - configId?: number; - configName?: string; - configLevel?: number; - deviceId?: number; - deviceName?: string; - productId?: number; - productName?: string; - deviceMessage?: string; - processStatus?: boolean; - processRemark?: string; - processTime?: Date; - createTime?: Date; -} - /** 查询告警记录分页 */ export function getAlertRecordPage(params: PageParam) { return requestClient.get>( @@ -54,29 +34,9 @@ export function getAlertRecord(id: number) { } /** 处理告警记录 */ -export function processAlertRecord(id: number, remark?: string) { +export function processAlertRecord(id: number, processRemark?: string) { return requestClient.put('/iot/alert-record/process', { id, - remark, - }); -} - -/** 批量处理告警记录 */ -export function batchProcessAlertRecord(ids: number[], remark?: string) { - return requestClient.put('/iot/alert-record/batch-process', { - ids, - remark, - }); -} - -/** 删除告警记录 */ -export function deleteAlertRecord(id: number) { - return requestClient.delete(`/iot/alert-record/delete?id=${id}`); -} - -/** 批量删除告警记录 */ -export function deleteAlertRecordList(ids: number[]) { - return requestClient.delete('/iot/alert-record/delete-list', { - params: { ids: ids.join(',') }, + processRemark, }); } diff --git a/apps/web-antdv-next/src/api/iot/device/device/index.ts b/apps/web-antdv-next/src/api/iot/device/device/index.ts index ca2a17ada..a55b017d5 100644 --- a/apps/web-antdv-next/src/api/iot/device/device/index.ts +++ b/apps/web-antdv-next/src/api/iot/device/device/index.ts @@ -150,11 +150,8 @@ export function importDeviceTemplate() { /** 导入设备 */ export function importDevice(file: File, updateSupport: boolean) { return requestClient.upload( - '/iot/device/import', - { - file, - updateSupport, - }, + `/iot/device/import?updateSupport=${updateSupport}`, + { file }, ); } @@ -168,7 +165,7 @@ export function getLatestDeviceProperties(params: any) { /** 获取设备属性历史数据 */ export function getHistoryDevicePropertyList(params: any) { - return requestClient.get>( + return requestClient.get( '/iot/device/property/history-list', { params }, ); diff --git a/apps/web-antdv-next/src/api/iot/ota/firmware/index.ts b/apps/web-antdv-next/src/api/iot/ota/firmware/index.ts index 1b8ae5b69..7fd7469ca 100644 --- a/apps/web-antdv-next/src/api/iot/ota/firmware/index.ts +++ b/apps/web-antdv-next/src/api/iot/ota/firmware/index.ts @@ -3,39 +3,22 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace IoTOtaFirmwareApi { - /** IoT OTA 固件 VO */ + /** IoT OTA 固件信息 */ export interface Firmware { id?: number; - name: string; - version: string; - productId: number; - productName?: string; + name?: string; description?: string; + version?: string; + productId?: number; + productName?: string; fileUrl?: string; - fileMd5?: string; fileSize?: number; - status?: number; + fileDigestAlgorithm?: string; + fileDigestValue?: string; createTime?: Date; - updateTime?: Date; } } -/** IoT OTA 固件 */ -export interface IoTOtaFirmware { - id?: number; - name?: string; - version?: string; - productId?: number; - productName?: string; - description?: string; - fileUrl?: string; - fileMd5?: string; - fileSize?: number; - status?: number; - createTime?: Date; - updateTime?: Date; -} - /** 查询 OTA 固件分页 */ export function getOtaFirmwarePage(params: PageParam) { return requestClient.get>( @@ -52,12 +35,12 @@ export function getOtaFirmware(id: number) { } /** 新增 OTA 固件 */ -export function createOtaFirmware(data: IoTOtaFirmware) { +export function createOtaFirmware(data: IoTOtaFirmwareApi.Firmware) { return requestClient.post('/iot/ota/firmware/create', data); } /** 修改 OTA 固件 */ -export function updateOtaFirmware(data: IoTOtaFirmware) { +export function updateOtaFirmware(data: IoTOtaFirmwareApi.Firmware) { return requestClient.put('/iot/ota/firmware/update', data); } @@ -65,26 +48,3 @@ export function updateOtaFirmware(data: IoTOtaFirmware) { export function deleteOtaFirmware(id: number) { return requestClient.delete(`/iot/ota/firmware/delete?id=${id}`); } - -/** 批量删除 OTA 固件 */ -export function deleteOtaFirmwareList(ids: number[]) { - return requestClient.delete('/iot/ota/firmware/delete-list', { - params: { ids: ids.join(',') }, - }); -} - -/** 更新 OTA 固件状态 */ -export function updateOtaFirmwareStatus(id: number, status: number) { - return requestClient.put(`/iot/ota/firmware/update-status`, { - id, - status, - }); -} - -/** 根据产品 ID 查询固件列表 */ -export function getOtaFirmwareListByProductId(productId: number) { - return requestClient.get( - '/iot/ota/firmware/list-by-product-id', - { params: { productId } }, - ); -} diff --git a/apps/web-antdv-next/src/api/iot/ota/task/index.ts b/apps/web-antdv-next/src/api/iot/ota/task/index.ts index 25ef2429f..7f4f5d6fb 100644 --- a/apps/web-antdv-next/src/api/iot/ota/task/index.ts +++ b/apps/web-antdv-next/src/api/iot/ota/task/index.ts @@ -3,45 +3,21 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace IoTOtaTaskApi { - /** IoT OTA 升级任务 VO */ + /** IoT OTA 升级任务 */ export interface Task { id?: number; - name: string; + name?: string; description?: string; - firmwareId: number; - firmwareName?: string; - productId?: number; - productName?: string; + firmwareId?: number; + status?: number; deviceScope?: number; deviceIds?: number[]; - status?: number; - successCount?: number; - failureCount?: number; - pendingCount?: number; + deviceTotalCount?: number; + deviceSuccessCount?: number; createTime?: Date; - updateTime?: Date; } } -/** IoT OTA 升级任务 */ -export interface OtaTask { - id?: number; - name?: string; - description?: string; - firmwareId?: number; - firmwareName?: string; - productId?: number; - productName?: string; - deviceScope?: number; - deviceIds?: number[]; - status?: number; - successCount?: number; - failureCount?: number; - pendingCount?: number; - createTime?: Date; - updateTime?: Date; -} - /** 查询 OTA 升级任务分页 */ export function getOtaTaskPage(params: PageParam) { return requestClient.get>( @@ -56,43 +32,11 @@ export function getOtaTask(id: number) { } /** 新增 OTA 升级任务 */ -export function createOtaTask(data: OtaTask) { +export function createOtaTask(data: IoTOtaTaskApi.Task) { return requestClient.post('/iot/ota/task/create', data); } -/** 修改 OTA 升级任务 */ -export function updateOtaTask(data: OtaTask) { - return requestClient.put('/iot/ota/task/update', data); -} - -/** 删除 OTA 升级任务 */ -export function deleteOtaTask(id: number) { - return requestClient.delete(`/iot/ota/task/delete?id=${id}`); -} - -/** 批量删除 OTA 升级任务 */ -export function deleteOtaTaskList(ids: number[]) { - return requestClient.delete('/iot/ota/task/delete-list', { - params: { ids: ids.join(',') }, - }); -} - /** 取消 OTA 升级任务 */ export function cancelOtaTask(id: number) { - return requestClient.put(`/iot/ota/task/cancel?id=${id}`); -} - -/** 启动 OTA 升级任务 */ -export function startOtaTask(id: number) { - return requestClient.put(`/iot/ota/task/start?id=${id}`); -} - -/** 暂停 OTA 升级任务 */ -export function pauseOtaTask(id: number) { - return requestClient.put(`/iot/ota/task/pause?id=${id}`); -} - -/** 恢复 OTA 升级任务 */ -export function resumeOtaTask(id: number) { - return requestClient.put(`/iot/ota/task/resume?id=${id}`); + return requestClient.post(`/iot/ota/task/cancel?id=${id}`); } diff --git a/apps/web-antdv-next/src/api/iot/ota/task/record/index.ts b/apps/web-antdv-next/src/api/iot/ota/task/record/index.ts index 2d66a422f..29eada8b6 100644 --- a/apps/web-antdv-next/src/api/iot/ota/task/record/index.ts +++ b/apps/web-antdv-next/src/api/iot/ota/task/record/index.ts @@ -3,44 +3,24 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace IoTOtaTaskRecordApi { - /** IoT OTA 升级任务记录 VO */ + /** IoT OTA 升级任务记录 */ export interface TaskRecord { id?: number; - taskId: number; - taskName?: string; - deviceId: number; - deviceName?: string; firmwareId?: number; - firmwareName?: string; firmwareVersion?: string; + taskId?: number; + deviceId?: string; + deviceName?: string; + currentVersion?: string; + fromFirmwareId?: number; + fromFirmwareVersion?: string; status?: number; progress?: number; - errorMessage?: string; - startTime?: Date; - endTime?: Date; - createTime?: Date; + description?: string; + updateTime?: Date; } } -// TODO @AI:这里应该拿到 IoTOtaTaskRecordApi 里 -/** IoT OTA 升级任务记录 */ -export interface OtaTaskRecord { - id?: number; - taskId?: number; - taskName?: string; - deviceId?: number; - deviceName?: string; - firmwareId?: number; - firmwareName?: string; - firmwareVersion?: string; - status?: number; - progress?: number; - errorMessage?: string; - startTime?: Date; - endTime?: Date; - createTime?: Date; -} - /** 查询 OTA 升级任务记录分页 */ export function getOtaTaskRecordPage(params: PageParam) { return requestClient.get>( @@ -49,48 +29,12 @@ export function getOtaTaskRecordPage(params: PageParam) { ); } -/** 查询 OTA 升级任务记录详情 */ -export function getOtaTaskRecord(id: number) { - return requestClient.get( - `/iot/ota/task/record/get?id=${id}`, - ); -} - -/** 根据任务 ID 查询记录列表 */ -export function getOtaTaskRecordListByTaskId(taskId: number) { - return requestClient.get( - '/iot/ota/task/record/list-by-task-id', - { params: { taskId } }, - ); -} - -/** 根据设备 ID 查询记录列表 */ -export function getOtaTaskRecordListByDeviceId(deviceId: number) { - return requestClient.get( - '/iot/ota/task/record/list-by-device-id', - { params: { deviceId } }, - ); -} - -/** 根据固件 ID 查询记录列表 */ -export function getOtaTaskRecordListByFirmwareId(firmwareId: number) { - return requestClient.get( - '/iot/ota/task/record/list-by-firmware-id', - { params: { firmwareId } }, - ); -} - -/** 重试升级任务记录 */ -export function retryOtaTaskRecord(id: number) { - return requestClient.put(`/iot/ota/task/record/retry?id=${id}`); -} - -/** 取消升级任务记录 */ +/** 取消 OTA 升级任务记录 */ export function cancelOtaTaskRecord(id: number) { return requestClient.put(`/iot/ota/task/record/cancel?id=${id}`); } -/** 获取升级任务记录状态统计 */ +/** 获取 OTA 升级任务记录状态统计 */ export function getOtaTaskRecordStatusStatistics( firmwareId?: number, taskId?: number, diff --git a/apps/web-antdv-next/src/api/iot/product/category/index.ts b/apps/web-antdv-next/src/api/iot/product/category/index.ts index 24d02ed3b..45ce4ccdb 100644 --- a/apps/web-antdv-next/src/api/iot/product/category/index.ts +++ b/apps/web-antdv-next/src/api/iot/product/category/index.ts @@ -7,11 +7,10 @@ export namespace IotProductCategoryApi { export interface ProductCategory { id?: number; // 分类 ID name: string; // 分类名称 - parentId?: number; // 父级分类 ID sort?: number; // 分类排序 status?: number; // 分类状态 description?: string; // 分类描述 - createTime?: string; // 创建时间 + createTime?: Date; // 创建时间 } } diff --git a/apps/web-antdv-next/src/api/iot/product/product/index.ts b/apps/web-antdv-next/src/api/iot/product/product/index.ts index f77255f4d..9b4a88ed6 100644 --- a/apps/web-antdv-next/src/api/iot/product/product/index.ts +++ b/apps/web-antdv-next/src/api/iot/product/product/index.ts @@ -20,8 +20,6 @@ export namespace IotProductApi { deviceType?: number; // 设备类型 netType?: number; // 联网方式 serializeType?: string; // 序列化类型 - dataFormat?: number; // 数据格式 - validateType?: number; // 认证方式 registerEnabled?: boolean; // 是否开启动态注册 deviceCount?: number; // 设备数量 createTime?: Date; // 创建时间 @@ -103,3 +101,10 @@ export function getProductByKey(productKey: string) { params: { productKey }, }); } + +/** 同步产品物模型 TDengine 超级表结构 */ +export function syncProductPropertyTable(productId: number) { + return requestClient.post( + `/iot/product/sync-property-table?productId=${productId}`, + ); +} diff --git a/apps/web-antdv-next/src/api/iot/rule/data/rule/index.ts b/apps/web-antdv-next/src/api/iot/rule/data/rule/index.ts index 55e50184a..e9ba120b2 100644 --- a/apps/web-antdv-next/src/api/iot/rule/data/rule/index.ts +++ b/apps/web-antdv-next/src/api/iot/rule/data/rule/index.ts @@ -3,45 +3,21 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace DataRuleApi { - /** IoT 数据流转规则 VO */ - export interface Rule { + /** IoT 数据流转规则 */ + export interface DataRule { id?: number; - name: string; + name?: string; description?: string; status?: number; - productId?: number; - productKey?: string; - sourceConfigs?: SourceConfig[]; + sourceConfigs?: any[]; sinkIds?: number[]; createTime?: Date; } - - /** IoT 数据源配置 */ - export interface SourceConfig { - productId?: number; - productKey?: string; - deviceId?: number; - type?: string; - topic?: string; - } -} - -/** IoT 数据流转规则 */ -export interface DataRule { - id?: number; - name?: string; - description?: string; - status?: number; - productId?: number; - productKey?: string; - sourceConfigs?: any[]; - sinkIds?: number[]; - createTime?: Date; } /** 查询数据流转规则分页 */ export function getDataRulePage(params: PageParam) { - return requestClient.get>( + return requestClient.get>( '/iot/data-rule/page', { params }, ); @@ -49,16 +25,16 @@ export function getDataRulePage(params: PageParam) { /** 查询数据流转规则详情 */ export function getDataRule(id: number) { - return requestClient.get(`/iot/data-rule/get?id=${id}`); + return requestClient.get(`/iot/data-rule/get?id=${id}`); } /** 新增数据流转规则 */ -export function createDataRule(data: DataRule) { +export function createDataRule(data: DataRuleApi.DataRule) { return requestClient.post('/iot/data-rule/create', data); } /** 修改数据流转规则 */ -export function updateDataRule(data: DataRule) { +export function updateDataRule(data: DataRuleApi.DataRule) { return requestClient.put('/iot/data-rule/update', data); } @@ -66,18 +42,3 @@ export function updateDataRule(data: DataRule) { export function deleteDataRule(id: number) { return requestClient.delete(`/iot/data-rule/delete?id=${id}`); } - -/** 批量删除数据流转规则 */ -export function deleteDataRuleList(ids: number[]) { - return requestClient.delete('/iot/data-rule/delete-list', { - params: { ids: ids.join(',') }, - }); -} - -/** 更新数据流转规则状态 */ -export function updateDataRuleStatus(id: number, status: number) { - return requestClient.put(`/iot/data-rule/update-status`, { - id, - status, - }); -} diff --git a/apps/web-antdv-next/src/api/iot/rule/data/sink/index.ts b/apps/web-antdv-next/src/api/iot/rule/data/sink/index.ts index 4614c2a6e..3c26df91d 100644 --- a/apps/web-antdv-next/src/api/iot/rule/data/sink/index.ts +++ b/apps/web-antdv-next/src/api/iot/rule/data/sink/index.ts @@ -2,101 +2,147 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; +interface BaseConfig { + type: string; +} + export namespace DataSinkApi { /** IoT 数据流转目的 VO */ - export interface Sink { + export interface DataSink { id?: number; - name: string; + name?: string; description?: string; status?: number; - type: string; - config?: any; + direction?: number; + type?: number; + config?: + | DatabaseConfig + | HttpConfig + | KafkaMQConfig + | MqttConfig + | RabbitMQConfig + | RedisStreamMQConfig + | RocketMQConfig + | TcpConfig + | WebSocketConfig; createTime?: Date; } + + /** HTTP 配置 */ + export interface HttpConfig extends BaseConfig { + url: string; + method: string; + headers: Record; + query: Record; + body: string; + } + + /** TCP 配置 */ + export interface TcpConfig extends BaseConfig { + host: string; + port: number; + connectTimeoutMs: number; + readTimeoutMs: number; + ssl: boolean; + sslCertPath: string; + dataFormat: string; + heartbeatIntervalMs: number; + reconnectIntervalMs: number; + maxReconnectAttempts: number; + } + + /** WebSocket 配置 */ + export interface WebSocketConfig extends BaseConfig { + serverUrl: string; + connectTimeoutMs: number; + sendTimeoutMs: number; + heartbeatIntervalMs: number; + heartbeatMessage: string; + subprotocols: string; + customHeaders: string; + verifySslCert: boolean; + dataFormat: string; + reconnectIntervalMs: number; + maxReconnectAttempts: number; + enableCompression: boolean; + sendRetryCount: number; + sendRetryIntervalMs: number; + } + + /** MQTT 配置 */ + export interface MqttConfig extends BaseConfig { + url: string; + username: string; + password: string; + clientId: string; + topic: string; + } + + /** Database 配置 */ + export interface DatabaseConfig extends BaseConfig { + jdbcUrl: string; + username: string; + password: string; + tableName: string; + } + + /** RocketMQ 配置 */ + export interface RocketMQConfig extends BaseConfig { + nameServer: string; + accessKey: string; + secretKey: string; + group: string; + topic: string; + tags: string; + } + + /** Kafka 配置 */ + export interface KafkaMQConfig extends BaseConfig { + bootstrapServers: string; + username: string; + password: string; + ssl: boolean; + topic: string; + } + + /** RabbitMQ 配置 */ + export interface RabbitMQConfig extends BaseConfig { + host: string; + port: number; + virtualHost: string; + username: string; + password: string; + exchange: string; + routingKey: string; + queue: string; + } + + /** Redis Stream MQ 配置 */ + export interface RedisStreamMQConfig extends BaseConfig { + host: string; + port: number; + password: string; + database: number; + topic: string; + } } -/** IoT 数据流转目的 */ -export interface DataSinkVO { - id?: number; - name?: string; - description?: string; - status?: number; - type?: string; - config?: any; - createTime?: Date; -} - -/** IoT 数据目的类型枚举 */ -export enum IotDataSinkTypeEnum { - HTTP = 'HTTP', - KAFKA = 'KAFKA', - MQTT = 'MQTT', - RABBITMQ = 'RABBITMQ', - REDIS_STREAM = 'REDIS_STREAM', - ROCKETMQ = 'ROCKETMQ', -} - -/** HTTP 配置 */ -export interface HttpConfig { - url?: string; - method?: string; - headers?: Record; - timeout?: number; -} - -/** MQTT 配置 */ -export interface MqttConfig { - broker?: string; - port?: number; - topic?: string; - username?: string; - password?: string; - clientId?: string; - qos?: number; -} - -/** Kafka 配置 */ -export interface KafkaMQConfig { - bootstrapServers?: string; - topic?: string; - acks?: string; - retries?: number; - batchSize?: number; -} - -/** RabbitMQ 配置 */ -export interface RabbitMQConfig { - host?: string; - port?: number; - virtualHost?: string; - username?: string; - password?: string; - exchange?: string; - routingKey?: string; - queue?: string; -} - -/** RocketMQ 配置 */ -export interface RocketMQConfig { - nameServer?: string; - topic?: string; - tag?: string; - producerGroup?: string; -} - -/** Redis Stream 配置 */ -export interface RedisStreamMQConfig { - host?: string; - port?: number; - password?: string; - database?: number; - streamKey?: string; - maxLen?: number; -} +/** 数据流转目的类型 */ +export const IotDataSinkTypeEnum = { + HTTP: 1, + TCP: 2, + WEBSOCKET: 3, + MQTT: 10, + DATABASE: 20, + REDIS_STREAM: 21, + ROCKETMQ: 30, + RABBITMQ: 31, + KAFKA: 32, +} as const; /** 查询数据流转目的分页 */ export function getDataSinkPage(params: PageParam) { - return requestClient.get>( + return requestClient.get>( '/iot/data-sink/page', { params }, ); @@ -104,26 +150,23 @@ export function getDataSinkPage(params: PageParam) { /** 查询数据流转目的详情 */ export function getDataSink(id: number) { - return requestClient.get(`/iot/data-sink/get?id=${id}`); + return requestClient.get(`/iot/data-sink/get?id=${id}`); } -/** 查询所有数据流转目的列表 */ -export function getDataSinkList() { - return requestClient.get('/iot/data-sink/list'); -} - -/** 查询数据流转目的简单列表 */ +/** 查询数据流转目的(精简)列表 */ export function getDataSinkSimpleList() { - return requestClient.get('/iot/data-sink/simple-list'); + return requestClient.get( + '/iot/data-sink/simple-list', + ); } /** 新增数据流转目的 */ -export function createDataSink(data: DataSinkVO) { +export function createDataSink(data: DataSinkApi.DataSink) { return requestClient.post('/iot/data-sink/create', data); } /** 修改数据流转目的 */ -export function updateDataSink(data: DataSinkVO) { +export function updateDataSink(data: DataSinkApi.DataSink) { return requestClient.put('/iot/data-sink/update', data); } @@ -131,18 +174,3 @@ export function updateDataSink(data: DataSinkVO) { export function deleteDataSink(id: number) { return requestClient.delete(`/iot/data-sink/delete?id=${id}`); } - -/** 批量删除数据流转目的 */ -export function deleteDataSinkList(ids: number[]) { - return requestClient.delete('/iot/data-sink/delete-list', { - params: { ids: ids.join(',') }, - }); -} - -/** 更新数据流转目的状态 */ -export function updateDataSinkStatus(id: number, status: number) { - return requestClient.put(`/iot/data-sink/update-status`, { - id, - status, - }); -} diff --git a/apps/web-antdv-next/src/api/iot/rule/scene/index.ts b/apps/web-antdv-next/src/api/iot/rule/scene/index.ts index 9a8acf758..23549f1ad 100644 --- a/apps/web-antdv-next/src/api/iot/rule/scene/index.ts +++ b/apps/web-antdv-next/src/api/iot/rule/scene/index.ts @@ -11,25 +11,20 @@ export namespace RuleSceneApi { status?: number; triggers?: Trigger[]; actions?: Action[]; + lastTriggeredTime?: Date; createTime?: Date; } /** 场景联动规则的触发器 */ export interface Trigger { - type?: string; + type?: number; productId?: number; deviceId?: number; identifier?: string; operator?: string; value?: any; cronExpression?: string; - conditionGroups?: TriggerConditionGroup[]; - } - - /** 场景联动规则的触发条件组 */ - export interface TriggerConditionGroup { - conditions?: TriggerCondition[]; - operator?: string; + conditionGroups?: TriggerCondition[][]; // 后端结构:List>;外层「或」、组内「且」 } /** 场景联动规则的触发条件 */ @@ -39,72 +34,22 @@ export namespace RuleSceneApi { identifier?: string; operator?: string; value?: any; - type?: string; + type?: number; + param?: string; } /** 场景联动规则的动作 */ export interface Action { - type?: string; + type?: number; productId?: number; deviceId?: number; identifier?: string; value?: any; alertConfigId?: number; + params?: string; } } -// TODO @haohao:貌似下面的,和 RuleSceneApi 重复了。 -/** IoT 场景联动规则 */ -export interface IotSceneRule { - id?: number; - name?: string; - description?: string; - status?: number; - triggers?: Trigger[]; - actions?: Action[]; - createTime?: Date; -} - -/** IoT 场景联动规则触发器 */ -export interface Trigger { - type?: string; - productId?: number; - deviceId?: number; - identifier?: string; - operator?: string; - value?: any; - cronExpression?: string; - conditionGroups?: TriggerConditionGroup[]; -} - -/** IoT 场景联动规则触发条件组 */ -export interface TriggerConditionGroup { - conditions?: TriggerCondition[]; - operator?: string; -} - -/** IoT 场景联动规则触发条件 */ -export interface TriggerCondition { - productId?: number; - deviceId?: number; - identifier?: string; - operator?: string; - value?: any; - type?: string; - param?: string; -} - -/** IoT 场景联动规则动作 */ -export interface Action { - type?: string; - productId?: number; - deviceId?: number; - identifier?: string; - value?: any; - alertConfigId?: number; - params?: string; -} - /** 查询场景联动规则分页 */ export function getSceneRulePage(params: PageParam) { return requestClient.get>( @@ -121,12 +66,12 @@ export function getSceneRule(id: number) { } /** 新增场景联动规则 */ -export function createSceneRule(data: IotSceneRule) { +export function createSceneRule(data: RuleSceneApi.SceneRule) { return requestClient.post('/iot/scene-rule/create', data); } /** 修改场景联动规则 */ -export function updateSceneRule(data: IotSceneRule) { +export function updateSceneRule(data: RuleSceneApi.SceneRule) { return requestClient.put('/iot/scene-rule/update', data); } @@ -135,14 +80,6 @@ export function deleteSceneRule(id: number) { return requestClient.delete(`/iot/scene-rule/delete?id=${id}`); } -/** 批量删除场景联动规则 */ -// TODO @haohao:貌似用上。 -export function deleteSceneRuleList(ids: number[]) { - return requestClient.delete('/iot/scene-rule/delete-list', { - params: { ids: ids.join(',') }, - }); -} - /** 更新场景联动规则状态 */ export function updateSceneRuleStatus(id: number, status: number) { return requestClient.put(`/iot/scene-rule/update-status`, { diff --git a/apps/web-antdv-next/src/api/iot/statistics/index.ts b/apps/web-antdv-next/src/api/iot/statistics/index.ts index a02b342d2..dc7971eb6 100644 --- a/apps/web-antdv-next/src/api/iot/statistics/index.ts +++ b/apps/web-antdv-next/src/api/iot/statistics/index.ts @@ -17,18 +17,6 @@ export namespace IotStatisticsApi { productCategoryDeviceCounts: Record; // 按品类统计的设备数量 } - /** 时间戳-数值的键值对类型 */ - export interface TimeValueItem { - [key: string]: number; - } - - /** 消息统计数据类型 */ - export interface DeviceMessageSummary { - statType: number; - upstreamCounts: TimeValueItem[]; - downstreamCounts: TimeValueItem[]; - } - /** 设备消息数量统计(按日期) */ export interface DeviceMessageSummaryByDateRespVO { time: string; // 时间轴 diff --git a/apps/web-antdv-next/src/api/iot/thingmodel/index.ts b/apps/web-antdv-next/src/api/iot/thingmodel/index.ts index 9e89b869c..002ccd9c2 100644 --- a/apps/web-antdv-next/src/api/iot/thingmodel/index.ts +++ b/apps/web-antdv-next/src/api/iot/thingmodel/index.ts @@ -1,126 +1,209 @@ +import type { Rule } from 'antdv-next/es/form'; + import type { PageParam, PageResult } from '@vben/request'; +import { isEmpty } from '@vben/utils'; + import { requestClient } from '#/api/request'; export namespace ThingModelApi { - /** IoT 物模型数据 VO */ + /** IoT 物模型数据 */ export interface ThingModel { id?: number; productId?: number; productKey?: string; - identifier: string; - name: string; - desc?: string; - type: string; - property?: ThingModelProperty; - event?: ThingModelEvent; - service?: ThingModelService; + identifier?: string; + name?: string; + description?: string; + dataType?: string; + type?: number; // 参见 IoTThingModelTypeEnum 枚举类 + property?: Property; + event?: Event; + service?: Service; } /** IoT 物模型属性 */ export interface Property { - identifier: string; - name: string; - accessMode: string; - dataType: string; + identifier?: string; + name?: string; + accessMode?: string; + required?: boolean; + dataType?: string; + description?: string; dataSpecs?: any; dataSpecsList?: any[]; - desc?: string; } /** IoT 物模型服务 */ export interface Service { - identifier: string; - name: string; - callType: string; - inputData?: any[]; - outputData?: any[]; - desc?: string; + identifier?: string; + name?: string; + required?: boolean; + callType?: string; + description?: string; + inputParams?: Param[]; + outputParams?: Param[]; + method?: string; } /** IoT 物模型事件 */ export interface Event { - identifier: string; + identifier?: string; + name?: string; + required?: boolean; + type?: string; + description?: string; + outputParams?: Param[]; + method?: string; + } + + /** IoT 物模型参数 */ + export interface Param { + identifier?: string; + name?: string; + direction?: string; + paraOrder?: number; + dataType?: string; + dataSpecs?: any; + dataSpecsList?: any[]; + } + + /** IoT 物模型 TSL(树形)响应 */ + export interface ThingModelTSL { + productId?: number; + productKey?: string; + properties?: Property[]; + events?: Event[]; + services?: Service[]; + } + + /** IoT 数据定义(数值型) */ + export interface DataSpecsNumberData { + min?: number | string; + max?: number | string; + step?: number | string; + unit?: string; + unitName?: string; + } + + /** IoT 数据定义(枚举/布尔型) */ + export interface DataSpecsEnumOrBoolData { + value: number | string; name: string; - type: string; - outputData?: any[]; - desc?: string; } } -/** IoT 物模型数据 */ -export interface ThingModelData { - id?: number; - productId?: number; - productKey?: string; - identifier?: string; - name?: string; - desc?: string; - type?: string; - dataType?: string; - property?: ThingModelProperty; - event?: ThingModelEvent; - service?: ThingModelService; +/** 生成「必填 + 数字」类校验器:拼到 size / length / 枚举值上 */ +function buildRequiredNumberValidator(label: string) { + return (_rule: any, value: any, callback: any) => { + if (isEmpty(value)) { + callback(new Error(`${label}不能为空`)); + return; + } + if (Number.isNaN(Number(value))) { + callback(new Error(`${label}必须是数字`)); + return; + } + callback(); + }; } -/** IoT 物模型属性 */ -export interface ThingModelProperty { - identifier?: string; - name?: string; - accessMode?: string; - dataType?: string; - dataSpecs?: any; - dataSpecsList?: any[]; - desc?: string; -} - -/** IoT 物模型服务 */ -export interface ThingModelService { - identifier?: string; - name?: string; - callType?: string; - inputData?: any[]; - outputData?: any[]; - desc?: string; -} - -/** IoT 物模型事件 */ -export interface ThingModelEvent { - identifier?: string; - name?: string; - type?: string; - outputData?: any[]; - desc?: string; -} - -/** IoT 数据定义(数值型) */ -export interface DataSpecsNumberData { - min?: number | string; - max?: number | string; - step?: number | string; - unit?: string; - unitName?: string; -} - -/** IoT 数据定义(枚举/布尔型) */ -export interface DataSpecsEnumOrBoolData { - value: number | string; - name: string; +/** 生成「标识符样式」名称校验器:开头需为中文 / 英文 / 数字,整体仅允许中文、英文、数字、下划线、短划线,长度 ≤ 20 */ +export function buildIdentifierLikeNameValidator(label: string) { + return (_rule: any, value: string, callback: any) => { + if (isEmpty(value)) { + callback(new Error(`${label}不能为空`)); + return; + } + if (!/^[一-龥A-Za-z0-9]/.test(value)) { + callback(new Error(`${label}必须以中文、英文字母或数字开头`)); + return; + } + if (!/^[一-龥A-Za-z0-9][\w一-龥-]*$/.test(value)) { + callback( + new Error(`${label}只能包含中文、英文字母、数字、下划线和短划线`), + ); + return; + } + if (value.length > 20) { + callback(new Error(`${label}长度不能超过 20 个字符`)); + return; + } + callback(); + }; } /** IoT 物模型表单校验规则 */ -export interface ThingModelFormRules { - [key: string]: any; -} +export const ThingModelFormRules: Record = { + name: [ + { required: true, message: '功能名称不能为空', trigger: 'blur' }, + { + pattern: /^[一-龥A-Za-z0-9][一-龥A-Za-z0-9\-_/.]{0,29}$/, + message: + '支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符', + trigger: 'blur', + }, + ], + type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }], + identifier: [ + { required: true, message: '标识符不能为空', trigger: 'blur' }, + { + pattern: /^[a-zA-Z][a-zA-Z0-9_]{0,31}$/, + message: '支持大小写字母、数字和下划线,必须以字母开头,不超过 32 个字符', + trigger: 'blur', + }, + { + validator: (_rule: any, value: string, callback: any) => { + const reservedKeywords = [ + 'set', + 'get', + 'post', + 'property', + 'event', + 'time', + 'value', + ]; + if (reservedKeywords.includes(value)) { + callback( + new Error( + 'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义', + ), + ); + return; + } + if (/^\d+$/.test(value)) { + callback(new Error('标识符不能是纯数字')); + return; + } + callback(); + }, + trigger: 'blur', + }, + ], + childDataType: [{ required: true, message: '元素类型不能为空' }], + size: [ + { + required: true, + validator: buildRequiredNumberValidator('元素个数'), + trigger: 'blur', + }, + ], + length: [ + { + required: true, + validator: buildRequiredNumberValidator('文本长度'), + trigger: 'blur', + }, + ], + accessMode: [ + { required: true, message: '请选择读写类型', trigger: 'change' }, + ], + callType: [{ required: true, message: '请选择调用方式', trigger: 'change' }], + eventType: [{ required: true, message: '请选择事件类型', trigger: 'change' }], +}; -/** 验证布尔型名称 */ -export function validateBoolName(_rule: any, value: any, callback: any) { - if (value) { - callback(); - } else { - callback(new Error('枚举描述不能为空')); - } -} +/** 校验布尔值名称 */ +export const validateBoolName = buildIdentifierLikeNameValidator('布尔值名称'); /** 查询产品物模型分页 */ export function getThingModelPage(params: PageParam) { @@ -141,17 +224,19 @@ export function getThingModel(id: number) { export function getThingModelListByProductId(productId: number) { return requestClient.get( '/iot/thing-model/list', - { params: { productId } }, + { + params: { productId }, + }, ); } /** 新增物模型 */ -export function createThingModel(data: ThingModelData) { +export function createThingModel(data: ThingModelApi.ThingModel) { return requestClient.post('/iot/thing-model/create', data); } /** 修改物模型 */ -export function updateThingModel(data: ThingModelData) { +export function updateThingModel(data: ThingModelApi.ThingModel) { return requestClient.put('/iot/thing-model/update', data); } @@ -161,26 +246,11 @@ export function deleteThingModel(id: number) { } /** 获取物模型 TSL */ -export function getThingModelTSL(productId: number) { - return requestClient.get( +export function getThingModelTSLByProductId(productId: number) { + return requestClient.get( '/iot/thing-model/get-tsl', - { params: { productId } }, + { + params: { productId }, + }, ); } - -/** 导入物模型 TSL -export function importThingModelTSL(productId: number, tslData: any) { - return requestClient.post('/iot/thing-model/import-tsl', { - productId, - tslData, - }); -} - */ - -/** 导出物模型 TSL -export function exportThingModelTSL(productId: number) { - return requestClient.get('/iot/thing-model/export-tsl', { - params: { productId }, - }); -} - */ diff --git a/apps/web-antdv-next/src/api/system/mail/template/index.ts b/apps/web-antdv-next/src/api/system/mail/template/index.ts index 57f722cf5..fffd5d62b 100644 --- a/apps/web-antdv-next/src/api/system/mail/template/index.ts +++ b/apps/web-antdv-next/src/api/system/mail/template/index.ts @@ -17,6 +17,13 @@ export namespace SystemMailTemplateApi { createTime: Date; } + /** 邮件模版精简信息 */ + export interface MailTemplateSimple { + id: number; + name: string; + code: string; + } + /** 邮件发送信息 */ export interface MailSendReqVO { toMails: string[]; @@ -35,6 +42,13 @@ export function getMailTemplatePage(params: PageParam) { ); } +/** 查询邮件模版精简列表 */ +export function getSimpleMailTemplateList() { + return requestClient.get( + '/system/mail-template/simple-list', + ); +} + /** 查询邮件模版详情 */ export function getMailTemplate(id: number) { return requestClient.get( diff --git a/apps/web-antdv-next/src/api/system/notify/template/index.ts b/apps/web-antdv-next/src/api/system/notify/template/index.ts index dd19f4b8f..92c7d7bcc 100644 --- a/apps/web-antdv-next/src/api/system/notify/template/index.ts +++ b/apps/web-antdv-next/src/api/system/notify/template/index.ts @@ -16,6 +16,13 @@ export namespace SystemNotifyTemplateApi { remark: string; } + /** 站内信模板精简信息 */ + export interface NotifyTemplateSimple { + id: number; + name: string; + code: string; + } + /** 发送站内信请求 */ export interface NotifySendReqVO { userId: number; @@ -33,6 +40,13 @@ export function getNotifyTemplatePage(params: PageParam) { ); } +/** 查询站内信模板精简列表 */ +export function getSimpleNotifyTemplateList() { + return requestClient.get( + '/system/notify-template/simple-list', + ); +} + /** 查询站内信模板详情 */ export function getNotifyTemplate(id: number) { return requestClient.get( diff --git a/apps/web-antdv-next/src/api/system/sms/template/index.ts b/apps/web-antdv-next/src/api/system/sms/template/index.ts index eccfb911e..5cfc5ca9b 100644 --- a/apps/web-antdv-next/src/api/system/sms/template/index.ts +++ b/apps/web-antdv-next/src/api/system/sms/template/index.ts @@ -19,6 +19,13 @@ export namespace SystemSmsTemplateApi { createTime?: Date; } + /** 短信模板精简信息 */ + export interface SmsTemplateSimple { + id: number; + name: string; + code: string; + } + /** 发送短信请求 */ export interface SmsSendReqVO { mobile: string; @@ -35,6 +42,13 @@ export function getSmsTemplatePage(params: PageParam) { ); } +/** 查询短信模板精简列表 */ +export function getSimpleSmsTemplateList() { + return requestClient.get( + '/system/sms-template/simple-list', + ); +} + /** 查询短信模板详情 */ export function getSmsTemplate(id: number) { return requestClient.get( diff --git a/apps/web-antdv-next/src/assets/imgs/iot/device.png b/apps/web-antdv-next/src/assets/imgs/iot/device.png new file mode 100644 index 000000000..79339cdf4 Binary files /dev/null and b/apps/web-antdv-next/src/assets/imgs/iot/device.png differ diff --git a/apps/web-antdv-next/src/assets/svgs/iot/cube.svg b/apps/web-antdv-next/src/assets/svgs/iot/cube.svg new file mode 100644 index 000000000..200ac1b1c --- /dev/null +++ b/apps/web-antdv-next/src/assets/svgs/iot/cube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web-antdv-next/src/router/routes/modules/iot.ts b/apps/web-antdv-next/src/router/routes/modules/iot.ts index 2bc6d32b2..a1d2203b9 100644 --- a/apps/web-antdv-next/src/router/routes/modules/iot.ts +++ b/apps/web-antdv-next/src/router/routes/modules/iot.ts @@ -12,7 +12,7 @@ const routes: RouteRecordRaw[] = [ }, children: [ { - path: 'product/detail/:id', + path: 'product/product/detail/:id', name: 'IoTProductDetail', meta: { title: '产品详情', @@ -30,14 +30,13 @@ const routes: RouteRecordRaw[] = [ component: () => import('#/views/iot/device/device/detail/index.vue'), }, { - path: 'ota/firmware/detail/:id', + path: 'ota/operation/firmware/detail/:id', name: 'IoTOtaFirmwareDetail', meta: { title: '固件详情', - activePath: '/iot/ota', + activePath: '/iot/operation/ota/firmware', }, - component: () => - import('#/views/iot/ota/modules/firmware-detail/index.vue'), + component: () => import('#/views/iot/ota/firmware/detail/index.vue'), }, ], }, diff --git a/apps/web-antdv-next/src/views/iot/alert/config/data.ts b/apps/web-antdv-next/src/views/iot/alert/config/data.ts index 37fc38ed1..86dab96cc 100644 --- a/apps/web-antdv-next/src/views/iot/alert/config/data.ts +++ b/apps/web-antdv-next/src/views/iot/alert/config/data.ts @@ -1,12 +1,26 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { AlertConfigApi } from '#/api/iot/alert/config'; -import { DICT_TYPE } from '@vben/constants'; +import { markRaw } from 'vue'; + +import { + CommonStatusEnum, + DICT_TYPE, + IotAlertReceiveTypeEnum, +} from '@vben/constants'; import { getDictOptions } from '@vben/hooks'; import { getSimpleRuleSceneList } from '#/api/iot/rule/scene'; import { getSimpleUserList } from '#/api/system/user'; import { getRangePickerDefaultProps } from '#/utils'; +import { MailTemplateSelect } from '#/views/system/mail/template/components'; +import { NotifyTemplateSelect } from '#/views/system/notify/template/components'; +import { SmsTemplateSelect } from '#/views/system/sms/template/components'; + +function hasReceiveType(values: Partial>, type: number) { + return Array.isArray(values.receiveTypes) && values.receiveTypes.includes(type); +} /** 新增/修改告警配置的表单 */ export function useFormSchema(): VbenFormSchema[] { @@ -31,7 +45,7 @@ export function useFormSchema(): VbenFormSchema[] { { fieldName: 'description', label: '配置描述', - component: 'TextArea', + component: 'Textarea', componentProps: { placeholder: '请输入配置描述', rows: 3, @@ -56,6 +70,7 @@ export function useFormSchema(): VbenFormSchema[] { buttonStyle: 'solid', optionType: 'button', }, + defaultValue: CommonStatusEnum.ENABLE, rules: 'required', }, { @@ -69,6 +84,7 @@ export function useFormSchema(): VbenFormSchema[] { mode: 'multiple', placeholder: '请选择关联的场景联动规则', }, + defaultValue: [], rules: 'required', }, { @@ -82,6 +98,7 @@ export function useFormSchema(): VbenFormSchema[] { mode: 'multiple', placeholder: '请选择接收的用户', }, + defaultValue: [], rules: 'required', }, { @@ -93,8 +110,63 @@ export function useFormSchema(): VbenFormSchema[] { mode: 'multiple', placeholder: '请选择接收类型', }, + defaultValue: [], rules: 'required', }, + { + fieldName: 'smsTemplateCode', + label: '短信模板', + component: markRaw(SmsTemplateSelect), + dependencies: { + triggerFields: ['receiveTypes'], + show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.SMS), + trigger: async (values, formApi) => { + if ( + !hasReceiveType(values, IotAlertReceiveTypeEnum.SMS) && + values.smsTemplateCode + ) { + await formApi.setFieldValue('smsTemplateCode', undefined); + } + }, + }, + rules: 'selectRequired', + }, + { + fieldName: 'mailTemplateCode', + label: '邮件模板', + component: markRaw(MailTemplateSelect), + dependencies: { + triggerFields: ['receiveTypes'], + show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.MAIL), + trigger: async (values, formApi) => { + if ( + !hasReceiveType(values, IotAlertReceiveTypeEnum.MAIL) && + values.mailTemplateCode + ) { + await formApi.setFieldValue('mailTemplateCode', undefined); + } + }, + }, + rules: 'selectRequired', + }, + { + fieldName: 'notifyTemplateCode', + label: '站内信模板', + component: markRaw(NotifyTemplateSelect), + dependencies: { + triggerFields: ['receiveTypes'], + show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY), + trigger: async (values, formApi) => { + if ( + !hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY) && + values.notifyTemplateCode + ) { + await formApi.setFieldValue('notifyTemplateCode', undefined); + } + }, + }, + rules: 'selectRequired', + }, ]; } @@ -133,9 +205,8 @@ export function useGridFormSchema(): VbenFormSchema[] { } /** 列表的字段 */ -export function useGridColumns(): VxeTableGridOptions['columns'] { +export function useGridColumns(): VxeTableGridOptions['columns'] { return [ - { type: 'checkbox', width: 40 }, { field: 'id', title: '配置编号', @@ -155,7 +226,10 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { field: 'level', title: '告警级别', minWidth: 100, - slots: { default: 'level' }, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_ALERT_LEVEL }, + }, }, { field: 'status', @@ -170,7 +244,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { field: 'sceneRuleIds', title: '关联场景联动规则', minWidth: 150, - slots: { default: 'sceneRules' }, + formatter: ({ cellValue }) => `${cellValue?.length || 0} 条`, }, { field: 'receiveUserNames', diff --git a/apps/web-antdv-next/src/views/iot/alert/config/index.vue b/apps/web-antdv-next/src/views/iot/alert/config/index.vue index bba84f73f..137a16875 100644 --- a/apps/web-antdv-next/src/views/iot/alert/config/index.vue +++ b/apps/web-antdv-next/src/views/iot/alert/config/index.vue @@ -1,22 +1,22 @@ - - + - + @@ -595,7 +745,7 @@ async function handleServiceInvoke(row: ThingModelData) { > (); const router = useRouter(); /** 子设备列表表格列配置 */ -function useGridColumns(): VxeTableGridOptions['columns'] { +function useGridColumns(): VxeTableGridOptions['columns'] { return [ { type: 'checkbox', width: 40 }, { @@ -126,7 +126,7 @@ function handleRowCheckboxChange({ /** 解绑单个设备 */ async function handleUnbind(row: IotDeviceApi.Device) { - await confirm({ content: `确定要解绑子设备【${row.deviceName}】吗?` }); + await confirm(`确定要解绑子设备【${row.deviceName}】吗?`); const hideLoading = message.loading({ content: `正在解绑【${row.deviceName}】...`, duration: 0, @@ -142,9 +142,7 @@ async function handleUnbind(row: IotDeviceApi.Device) { /** 批量解绑 */ async function handleUnbindBatch() { - await confirm({ - content: `确定要解绑选中的 ${checkedIds.value.length} 个子设备吗?`, - }); + await confirm(`确定要解绑选中的 ${checkedIds.value.length} 个子设备吗?`); const hideLoading = message.loading({ content: '正在批量解绑...', duration: 0, @@ -190,7 +188,7 @@ function useAddGridFormSchema(): VbenFormSchema[] { ]; } -function useAddGridColumns(): VxeTableGridOptions['columns'] { +function useAddGridColumns(): VxeTableGridOptions['columns'] { return [ { type: 'checkbox', width: 40 }, { @@ -317,6 +315,7 @@ watch( label: '添加子设备', type: 'primary', icon: ACTION_ICON.ADD, + auth: ['iot:device:update'], onClick: openAddModal, }, { @@ -324,6 +323,7 @@ watch( type: 'primary', danger: true, icon: ACTION_ICON.DELETE, + auth: ['iot:device:update'], disabled: isEmpty(checkedIds), onClick: handleUnbindBatch, }, @@ -342,6 +342,7 @@ watch( label: '解绑', type: 'link', danger: true, + auth: ['iot:device:update'], onClick: () => handleUnbind(row), }, ]" diff --git a/apps/web-antdv-next/src/views/iot/device/device/detail/modules/thing-model-event.vue b/apps/web-antdv-next/src/views/iot/device/device/detail/modules/thing-model-event.vue index 3ddd21000..97d50cdba 100644 --- a/apps/web-antdv-next/src/views/iot/device/device/detail/modules/thing-model-event.vue +++ b/apps/web-antdv-next/src/views/iot/device/device/detail/modules/thing-model-event.vue @@ -1,45 +1,48 @@ @@ -242,187 +277,3 @@ onMounted(() => { - - diff --git a/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue b/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue index a0839970c..922b39bdd 100644 --- a/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue +++ b/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue @@ -1,4 +1,4 @@ - diff --git a/apps/web-antdv-next/src/views/iot/ota/data.ts b/apps/web-antdv-next/src/views/iot/ota/data.ts deleted file mode 100644 index a873d4d85..000000000 --- a/apps/web-antdv-next/src/views/iot/ota/data.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type { VbenFormSchema } from '#/adapter/form'; -import type { VxeTableGridOptions } from '#/adapter/vxe-table'; - -import { getSimpleProductList } from '#/api/iot/product/product'; -import { getRangePickerDefaultProps } from '#/utils'; - -/** 新增/修改固件的表单 */ -export function useFormSchema(): VbenFormSchema[] { - return [ - { - component: 'Input', - fieldName: 'id', - dependencies: { - triggerFields: [''], - show: () => false, - }, - }, - { - fieldName: 'name', - label: '固件名称', - component: 'Input', - componentProps: { - placeholder: '请输入固件名称', - }, - rules: 'required', - }, - { - fieldName: 'productId', - label: '所属产品', - component: 'ApiSelect', - componentProps: { - api: getSimpleProductList, - labelField: 'name', - valueField: 'id', - placeholder: '请选择产品', - }, - rules: 'required', - }, - { - fieldName: 'version', - label: '版本号', - component: 'Input', - componentProps: { - placeholder: '请输入版本号', - }, - rules: 'required', - }, - { - fieldName: 'description', - label: '固件描述', - component: 'TextArea', - componentProps: { - placeholder: '请输入固件描述', - rows: 3, - }, - }, - { - fieldName: 'fileUrl', - label: '固件文件', - component: 'Upload', - componentProps: { - maxCount: 1, - accept: '.bin,.hex,.zip', - }, - rules: 'required', - help: '支持上传 .bin、.hex、.zip 格式的固件文件', - }, - ]; -} - -/** 列表的搜索表单 */ -export function useGridFormSchema(): VbenFormSchema[] { - return [ - { - fieldName: 'name', - label: '固件名称', - component: 'Input', - componentProps: { - placeholder: '请输入固件名称', - allowClear: true, - }, - }, - { - fieldName: 'productId', - label: '产品', - component: 'ApiSelect', - componentProps: { - api: getSimpleProductList, - labelField: 'name', - valueField: 'id', - placeholder: '请选择产品', - allowClear: true, - }, - }, - { - fieldName: 'createTime', - label: '创建时间', - component: 'RangePicker', - componentProps: { - ...getRangePickerDefaultProps(), - allowClear: true, - }, - }, - ]; -} - -/** 列表的字段 */ -export function useGridColumns(): VxeTableGridOptions['columns'] { - return [ - { - type: 'checkbox', - width: 50, - fixed: 'left', - }, - { - field: 'id', - title: '固件编号', - width: 100, - }, - { - field: 'name', - title: '固件名称', - minWidth: 150, - }, - { - field: 'version', - title: '版本号', - width: 120, - }, - { - field: 'productName', - title: '所属产品', - minWidth: 150, - }, - { - field: 'description', - title: '固件描述', - minWidth: 200, - showOverflow: 'tooltip', - }, - { - field: 'fileSize', - title: '文件大小', - width: 120, - formatter: ({ cellValue }) => { - if (!cellValue) return '-'; - const kb = cellValue / 1024; - if (kb < 1024) return `${kb.toFixed(2)} KB`; - return `${(kb / 1024).toFixed(2)} MB`; - }, - }, - { - field: 'status', - title: '状态', - width: 100, - formatter: ({ cellValue }) => { - return cellValue === 1 ? '启用' : '禁用'; - }, - }, - { - field: 'createTime', - title: '创建时间', - width: 180, - formatter: 'formatDateTime', - }, - { - title: '操作', - width: 160, - fixed: 'right', - slots: { default: 'actions' }, - }, - ]; -} diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts b/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts index 298cd340b..a8d52702f 100644 --- a/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts +++ b/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts @@ -1,9 +1,44 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { IotProductApi } from '#/api/iot/product/product'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { formatDateTime } from '@vben/utils'; import { getSimpleProductList } from '#/api/iot/product/product'; import { getRangePickerDefaultProps } from '#/utils'; +/** 关联数据 */ +let productList: IotProductApi.Product[] = []; +getSimpleProductList().then((data) => (productList = data)); + +/** 根据产品 ID 取产品名称 */ +export function getProductName(productId?: number): string { + if (!productId) { + return '-'; + } + return productList.find((product) => product.id === productId)?.name || '-'; +} + +/** 固件详情的描述字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { field: 'name', label: '固件名称' }, + { + field: 'productName', + label: '所属产品', + render: (val) => val || '-', + }, + { field: 'version', label: '固件版本' }, + { + field: 'createTime', + label: '创建时间', + render: (val) => (val ? (formatDateTime(val) as string) : '-'), + }, + { field: 'description', label: '固件描述', span: 2 }, + ]; +} + /** 新增/修改固件的表单 */ export function useFormSchema(): VbenFormSchema[] { return [ @@ -34,7 +69,13 @@ export function useFormSchema(): VbenFormSchema[] { valueField: 'id', placeholder: '请选择产品', }, - rules: 'required', + dependencies: { + triggerFields: ['id'], + componentProps: (values) => ({ + disabled: !!values.id, + }), + rules: (values) => (values.id ? null : 'required'), + }, }, { fieldName: 'version', @@ -43,12 +84,18 @@ export function useFormSchema(): VbenFormSchema[] { componentProps: { placeholder: '请输入版本号', }, - rules: 'required', + dependencies: { + triggerFields: ['id'], + componentProps: (values) => ({ + disabled: !!values.id, + }), + rules: (values) => (values.id ? null : 'required'), + }, }, { fieldName: 'description', label: '固件描述', - component: 'TextArea', + component: 'Textarea', componentProps: { placeholder: '请输入固件描述', rows: 3, @@ -60,11 +107,17 @@ export function useFormSchema(): VbenFormSchema[] { component: 'FileUpload', componentProps: { maxNumber: 1, - accept: ['bin', 'hex', 'zip'], + accept: ['bin', 'zip', 'pdf'], maxSize: 50, - helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB', + helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB', + }, + dependencies: { + triggerFields: ['id'], + componentProps: (values) => ({ + disabled: !!values.id, + }), + rules: (values) => (values.id ? null : 'required'), }, - rules: 'required', }, ]; } @@ -108,7 +161,6 @@ export function useGridFormSchema(): VbenFormSchema[] { /** 列表的字段 */ export function useGridColumns(): VxeTableGridOptions['columns'] { return [ - { type: 'checkbox', width: 40 }, { field: 'id', title: '固件编号', @@ -133,7 +185,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { field: 'productId', title: '所属产品', minWidth: 150, - slots: { default: 'product' }, + slots: { default: 'productName' }, }, { field: 'fileUrl', diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/detail/index.vue b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/index.vue new file mode 100644 index 000000000..7f9ff9a36 --- /dev/null +++ b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/index.vue @@ -0,0 +1,74 @@ + + + diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/detail/modules/info.vue b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/modules/info.vue new file mode 100644 index 000000000..6f40f3e45 --- /dev/null +++ b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/modules/info.vue @@ -0,0 +1,26 @@ + + + diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue b/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue index f39d4ae67..13f331313 100644 --- a/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue +++ b/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue @@ -13,10 +13,12 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware'; import { $t } from '#/locales'; -import OtaFirmwareForm from '../modules/ota-firmware-form.vue'; -import { useGridColumns, useGridFormSchema } from './data'; - -defineOptions({ name: 'IoTOtaFirmware' }); +import { + getProductName, + useGridColumns, + useGridFormSchema, +} from './data'; +import OtaFirmwareForm from './modules/form.vue'; const { push } = useRouter(); @@ -47,7 +49,7 @@ async function handleDelete(row: IoTOtaFirmwareApi.Firmware) { duration: 0, }); try { - await deleteOtaFirmware(row.id as number); + await deleteOtaFirmware(row.id!); message.success({ content: $t('ui.actionMessage.deleteSuccess', [row.name]), }); @@ -62,6 +64,11 @@ function handleDetail(row: IoTOtaFirmwareApi.Firmware) { push({ name: 'IoTOtaFirmwareDetail', params: { id: row.id } }); } +/** 跳转到产品详情 */ +function handleOpenProductDetail(productId: number) { + push({ name: 'IoTProductDetail', params: { id: productId } }); +} + const [Grid, gridApi] = useVbenVxeGrid({ formOptions: { schema: useGridFormSchema(), @@ -104,17 +111,23 @@ const [Grid, gridApi] = useVbenVxeGrid({ label: $t('ui.actionTitle.create', ['固件']), type: 'primary', icon: ACTION_ICON.ADD, + auth: ['iot:ota-firmware:create'], onClick: handleCreate, }, ]" /> - - -