From 09970d89a46e27f5229f84f2308ea14988a86979 Mon Sep 17 00:00:00 2001 From: XuZhiqiang Date: Thu, 4 Jun 2026 16:17:45 +0800 Subject: [PATCH] feat(web-antdv-next): sync IoT module --- .../src/api/iot/alert/config/index.ts | 57 +- .../src/api/iot/alert/record/index.ts | 48 +- .../src/api/iot/device/device/index.ts | 9 +- .../src/api/iot/ota/firmware/index.ts | 58 +- .../src/api/iot/ota/task/index.ts | 72 +-- .../src/api/iot/ota/task/record/index.ts | 78 +-- .../src/api/iot/product/category/index.ts | 3 +- .../src/api/iot/product/product/index.ts | 9 +- .../src/api/iot/rule/data/rule/index.ts | 55 +- .../src/api/iot/rule/data/sink/index.ts | 244 ++++---- .../src/api/iot/rule/scene/index.ts | 81 +-- .../src/api/iot/statistics/index.ts | 12 - .../src/api/iot/thingmodel/index.ts | 296 +++++---- .../src/api/system/mail/template/index.ts | 14 + .../src/api/system/notify/template/index.ts | 14 + .../src/api/system/sms/template/index.ts | 14 + .../src/assets/imgs/iot/device.png | Bin 0 -> 10007 bytes .../src/assets/svgs/iot/cube.svg | 1 + .../src/router/routes/modules/iot.ts | 9 +- .../src/views/iot/alert/config/data.ts | 86 ++- .../src/views/iot/alert/config/index.vue | 81 +-- .../modules/form.vue} | 16 +- .../src/views/iot/alert/record/data.ts | 41 +- .../src/views/iot/alert/record/index.vue | 185 +----- .../src/views/iot/device/device/data.ts | 24 +- .../views/iot/device/device/detail/index.vue | 48 +- .../device/device/detail/modules/config.vue | 62 +- .../device/device/detail/modules/header.vue | 10 +- .../iot/device/device/detail/modules/info.vue | 64 +- .../device/device/detail/modules/message.vue | 33 +- .../detail/modules/modbus-config-form.vue | 33 +- .../device/detail/modules/modbus-config.vue | 26 +- .../detail/modules/modbus-point-form.vue | 32 +- .../device/detail/modules/simulator.vue | 266 ++++++-- .../device/detail/modules/sub-device.vue | 13 +- .../detail/modules/thing-model-event.vue | 44 +- .../modules/thing-model-property-history.vue | 104 +--- .../detail/modules/thing-model-property.vue | 18 +- .../detail/modules/thing-model-service.vue | 44 +- .../device/detail/modules/thing-model.vue | 18 +- .../src/views/iot/device/device/index.vue | 61 +- .../iot/device/device/modules/card-view.vue | 287 +++------ .../views/iot/device/device/modules/form.vue | 38 +- .../iot/device/device/modules/group-form.vue | 4 +- .../iot/device/device/modules/import-form.vue | 17 +- .../src/views/iot/device/group/data.ts | 14 +- .../src/views/iot/device/group/index.vue | 8 +- .../views/iot/device/group/modules/form.vue | 3 + .../src/views/iot/home/chart-options.ts | 42 +- .../web-antdv-next/src/views/iot/home/data.ts | 24 +- .../src/views/iot/home/index.vue | 13 +- .../iot/home/modules/device-count-card.vue | 8 +- .../iot/home/modules/device-map-card.vue | 90 ++- .../home/modules/device-state-count-card.vue | 4 +- .../iot/home/modules/message-trend-card.vue | 26 +- apps/web-antdv-next/src/views/iot/ota/data.ts | 173 ------ .../src/views/iot/ota/firmware/data.ts | 68 ++- .../views/iot/ota/firmware/detail/index.vue | 74 +++ .../iot/ota/firmware/detail/modules/info.vue | 26 + .../src/views/iot/ota/firmware/index.vue | 38 +- .../modules/form.vue} | 25 +- .../src/views/iot/ota/index.vue | 129 ---- .../views/iot/ota/modules/detail/index.vue | 196 ------ .../iot/ota/modules/firmware-detail/index.vue | 196 ------ .../iot/ota/modules/task/ota-task-detail.vue | 411 ------------- .../iot/ota/modules/task/ota-task-form.vue | 173 ------ .../iot/ota/modules/task/ota-task-list.vue | 257 -------- .../src/views/iot/ota/task/data.ts | 163 +++++ .../src/views/iot/ota/task/modules/detail.vue | 96 +++ .../src/views/iot/ota/task/modules/form.vue | 89 +++ .../src/views/iot/ota/task/modules/info.vue | 26 + .../src/views/iot/ota/task/modules/list.vue | 159 +++++ .../views/iot/ota/task/modules/statistics.vue | 84 +++ .../src/views/iot/ota/task/record/data.ts | 59 ++ .../iot/ota/task/record/modules/list.vue | 129 ++++ .../src/views/iot/product/category/data.ts | 9 +- .../src/views/iot/product/category/index.vue | 7 +- .../iot/product/category/modules/form.vue | 2 +- .../iot/product/product/components/select.vue | 15 +- .../src/views/iot/product/product/data.ts | 66 +- .../iot/product/product/detail/index.vue | 23 +- .../product/product/detail/modules/header.vue | 99 +-- .../product/product/detail/modules/info.vue | 55 +- .../src/views/iot/product/product/index.vue | 12 +- .../iot/product/product/modules/card-view.vue | 354 ++++------- .../iot/product/product/modules/form.vue | 16 +- .../src/views/iot/rule/data/data.ts | 103 ---- .../src/views/iot/rule/data/index.vue | 131 +--- .../rule/components/source-config-form.vue | 311 ---------- .../src/views/iot/rule/data/rule/data.ts | 60 +- .../src/views/iot/rule/data/rule/index.vue | 18 +- .../{data-rule-form.vue => modules/form.vue} | 42 +- .../data/rule/modules/source-config-form.vue | 301 +++++++++ .../config/components/key-value-editor.vue | 23 +- .../data/sink/config/database-config-form.vue | 141 +++++ .../data/sink/config/http-config-form.vue | 143 +++-- .../views/iot/rule/data/sink/config/index.ts | 3 + .../data/sink/config/kafka-mq-config-form.vue | 69 ++- .../data/sink/config/mqtt-config-form.vue | 75 ++- .../sink/config/rabbit-mq-config-form.vue | 129 ++-- .../sink/config/redis-stream-config-form.vue | 118 ++-- .../sink/config/rocket-mq-config-form.vue | 88 ++- .../rule/data/sink/config/tcp-config-form.vue | 145 +++++ .../sink/config/websocket-config-form.vue | 160 +++++ .../iot/rule/data/sink/data-sink-form.vue | 149 ----- .../src/views/iot/rule/data/sink/data.ts | 55 +- .../src/views/iot/rule/data/sink/index.vue | 18 +- .../views/iot/rule/data/sink/modules/form.vue | 193 ++++++ .../src/views/iot/rule/scene/data.ts | 93 +-- .../rule/scene/form/configs/alert-config.vue | 43 +- .../scene/form/configs/condition-config.vue | 104 ++-- .../configs/current-time-condition-config.vue | 79 ++- .../form/configs/device-control-config.vue | 147 ++--- .../form/configs/device-trigger-config.vue | 121 ++-- .../configs/main-condition-inner-config.vue | 143 +++-- .../configs/sub-condition-group-config.vue | 36 +- .../configs/timer-condition-group-config.vue | 192 ++++++ .../scene/form/inputs/json-params-input.vue | 226 ++++--- .../rule/scene/form/inputs/value-input.vue | 102 +++- .../iot/rule/scene/form/rule-scene-form.vue | 360 ----------- .../scene/form/sections/action-section.vue | 133 ++-- .../form/sections/basic-info-section.vue | 52 +- .../scene/form/sections/trigger-section.vue | 152 ++--- .../scene/form/selectors/device-selector.vue | 63 +- .../form/selectors/operator-selector.vue | 19 +- .../scene/form/selectors/product-selector.vue | 23 +- .../form/selectors/property-selector.vue | 572 ++++++++---------- .../src/views/iot/rule/scene/index.vue | 274 ++++++++- .../src/views/iot/rule/scene/modules/form.vue | 198 ++++-- .../src/views/iot/thingmodel/data.ts | 18 +- .../src/views/iot/thingmodel/index.vue | 162 +++-- .../modules/components/data-definition.vue | 129 ++-- .../thingmodel/modules/data-specs/array.vue | 69 +++ .../thingmodel/modules/data-specs/enum.vue | 140 +++++ .../thingmodel/modules/data-specs/index.ts | 4 + .../thingmodel/modules/data-specs/number.vue | 154 +++++ .../thingmodel/modules/data-specs/struct.vue | 178 ++++++ .../iot/thingmodel/modules/dataSpecs/index.ts | 4 - .../thing-model-array-data-specs.vue | 64 -- .../dataSpecs/thing-model-enum-data-specs.vue | 67 -- .../thing-model-number-data-specs.vue | 78 --- .../thing-model-struct-data-specs.vue | 168 ----- .../{thing-model-event.vue => event.vue} | 23 +- .../src/views/iot/thingmodel/modules/form.vue | 249 ++++++++ .../thingmodel/modules/input-output-param.vue | 163 +++++ ...{thing-model-property.vue => property.vue} | 180 +++--- .../{thing-model-service.vue => service.vue} | 40 +- .../thingmodel/modules/thing-model-form.vue | 298 --------- .../thing-model-input-output-param.vue | 162 ----- .../thingmodel/modules/thing-model-tsl.vue | 118 ---- .../src/views/iot/thingmodel/modules/tsl.vue | 83 +++ .../src/views/iot/utils/scene-rule.ts | 401 ++++++++++++ .../system/mail/template/components/index.ts | 1 + .../mail/template/components/select.vue | 82 +++ .../notify/template/components/index.ts | 1 + .../notify/template/components/select.vue | 82 +++ .../system/sms/template/components/index.ts | 1 + .../system/sms/template/components/select.vue | 82 +++ 158 files changed, 7620 insertions(+), 7378 deletions(-) create mode 100644 apps/web-antdv-next/src/assets/imgs/iot/device.png create mode 100644 apps/web-antdv-next/src/assets/svgs/iot/cube.svg rename apps/web-antdv-next/src/views/iot/alert/{modules/alert-config-form.vue => config/modules/form.vue} (85%) delete mode 100644 apps/web-antdv-next/src/views/iot/ota/data.ts create mode 100644 apps/web-antdv-next/src/views/iot/ota/firmware/detail/index.vue create mode 100644 apps/web-antdv-next/src/views/iot/ota/firmware/detail/modules/info.vue rename apps/web-antdv-next/src/views/iot/ota/{modules/ota-firmware-form.vue => firmware/modules/form.vue} (77%) delete mode 100644 apps/web-antdv-next/src/views/iot/ota/index.vue delete mode 100644 apps/web-antdv-next/src/views/iot/ota/modules/detail/index.vue delete mode 100644 apps/web-antdv-next/src/views/iot/ota/modules/firmware-detail/index.vue delete mode 100644 apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-detail.vue delete mode 100644 apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-form.vue delete mode 100644 apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-list.vue create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/data.ts create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/modules/detail.vue create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/modules/info.vue create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/modules/list.vue create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/modules/statistics.vue create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/record/data.ts create mode 100644 apps/web-antdv-next/src/views/iot/ota/task/record/modules/list.vue delete mode 100644 apps/web-antdv-next/src/views/iot/rule/data/data.ts delete mode 100644 apps/web-antdv-next/src/views/iot/rule/data/rule/components/source-config-form.vue rename apps/web-antdv-next/src/views/iot/rule/data/rule/{data-rule-form.vue => modules/form.vue} (72%) create mode 100644 apps/web-antdv-next/src/views/iot/rule/data/rule/modules/source-config-form.vue create mode 100644 apps/web-antdv-next/src/views/iot/rule/data/sink/config/database-config-form.vue create mode 100644 apps/web-antdv-next/src/views/iot/rule/data/sink/config/tcp-config-form.vue create mode 100644 apps/web-antdv-next/src/views/iot/rule/data/sink/config/websocket-config-form.vue delete mode 100644 apps/web-antdv-next/src/views/iot/rule/data/sink/data-sink-form.vue create mode 100644 apps/web-antdv-next/src/views/iot/rule/data/sink/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/iot/rule/scene/form/configs/timer-condition-group-config.vue delete mode 100644 apps/web-antdv-next/src/views/iot/rule/scene/form/rule-scene-form.vue create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/data-specs/array.vue create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/data-specs/enum.vue create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/data-specs/index.ts create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/data-specs/number.vue create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/data-specs/struct.vue delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/dataSpecs/index.ts delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/dataSpecs/thing-model-array-data-specs.vue delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/dataSpecs/thing-model-enum-data-specs.vue delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/dataSpecs/thing-model-number-data-specs.vue delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/dataSpecs/thing-model-struct-data-specs.vue rename apps/web-antdv-next/src/views/iot/thingmodel/modules/{thing-model-event.vue => event.vue} (71%) create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/form.vue create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/input-output-param.vue rename apps/web-antdv-next/src/views/iot/thingmodel/modules/{thing-model-property.vue => property.vue} (51%) rename apps/web-antdv-next/src/views/iot/thingmodel/modules/{thing-model-service.vue => service.vue} (59%) delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/thing-model-form.vue delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/thing-model-input-output-param.vue delete mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/thing-model-tsl.vue create mode 100644 apps/web-antdv-next/src/views/iot/thingmodel/modules/tsl.vue create mode 100644 apps/web-antdv-next/src/views/iot/utils/scene-rule.ts create mode 100644 apps/web-antdv-next/src/views/system/mail/template/components/index.ts create mode 100644 apps/web-antdv-next/src/views/system/mail/template/components/select.vue create mode 100644 apps/web-antdv-next/src/views/system/notify/template/components/index.ts create mode 100644 apps/web-antdv-next/src/views/system/notify/template/components/select.vue create mode 100644 apps/web-antdv-next/src/views/system/sms/template/components/index.ts create mode 100644 apps/web-antdv-next/src/views/system/sms/template/components/select.vue 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 0000000000000000000000000000000000000000..79339cdf486e64bff9f5c3f89f16a7c20a8aadeb GIT binary patch literal 10007 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 \ 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, }, ]" /> - - -