From e816288b828ed4d70c7fc152ceb85561c50a2dc7 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 20 May 2026 13:31:27 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=88iot=EF=BC=89:=20=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=20ele=20=E7=9A=84=20alert=E3=80=81device=E3=80=81product?= =?UTF-8?q?=E3=80=81ota=E3=80=81home=E3=80=81thingmodel=20=E7=9A=84?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/iot/alert/config/data.ts | 205 ++++++ .../src/views/iot/alert/config/index.vue | 136 ++++ .../views/iot/alert/config/modules/form.vue | 88 +++ .../src/views/iot/alert/record/data.ts | 165 +++++ .../src/views/iot/alert/record/index.vue | 136 ++++ .../src/views/iot/device/device/data.ts | 336 +++++++++ .../views/iot/device/device/detail/index.vue | 162 +++++ .../device/device/detail/modules/config.vue | 202 ++++++ .../device/device/detail/modules/header.vue | 105 +++ .../iot/device/device/detail/modules/info.vue | 195 ++++++ .../device/device/detail/modules/message.vue | 248 +++++++ .../detail/modules/modbus-config-form.vue | 222 ++++++ .../device/detail/modules/modbus-config.vue | 358 ++++++++++ .../detail/modules/modbus-point-form.vue | 314 +++++++++ .../device/detail/modules/simulator.vue | 566 +++++++++++++++ .../device/detail/modules/sub-device.vue | 355 ++++++++++ .../detail/modules/thing-model-event.vue | 260 +++++++ .../modules/thing-model-property-history.vue | 512 ++++++++++++++ .../detail/modules/thing-model-property.vue | 421 +++++++++++ .../detail/modules/thing-model-service.vue | 285 ++++++++ .../device/detail/modules/thing-model.vue | 47 ++ .../src/views/iot/device/device/index.vue | 517 ++++++++++++++ .../iot/device/device/modules/card-view.vue | 437 ++++++++++++ .../views/iot/device/device/modules/form.vue | 205 ++++++ .../iot/device/device/modules/group-form.vue | 73 ++ .../iot/device/device/modules/import-form.vue | 109 +++ .../src/views/iot/device/group/data.ts | 124 ++++ .../src/views/iot/device/group/index.vue | 125 ++++ .../views/iot/device/group/modules/form.vue | 88 +++ .../src/views/iot/home/chart-options.ts | 196 ++++++ apps/web-ele/src/views/iot/home/data.ts | 20 + apps/web-ele/src/views/iot/home/index.vue | 108 +++ .../iot/home/modules/device-count-card.vue | 78 +++ .../iot/home/modules/device-map-card.vue | 220 ++++++ .../home/modules/device-state-count-card.vue | 113 +++ .../iot/home/modules/message-trend-card.vue | 177 +++++ .../src/views/iot/ota/firmware/data.ts | 181 +++++ .../views/iot/ota/firmware/detail/index.vue | 74 ++ .../iot/ota/firmware/detail/modules/info.vue | 27 + .../src/views/iot/ota/firmware/index.vue | 164 +++++ .../views/iot/ota/firmware/modules/form.vue | 90 +++ apps/web-ele/src/views/iot/ota/task/data.ts | 165 +++++ .../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 | 160 +++++ .../views/iot/ota/task/modules/statistics.vue | 86 +++ .../src/views/iot/ota/task/record/data.ts | 59 ++ .../iot/ota/task/record/modules/list.vue | 132 ++++ .../src/views/iot/product/category/data.ts | 137 ++++ .../src/views/iot/product/category/index.vue | 128 ++++ .../iot/product/category/modules/form.vue | 89 +++ .../iot/product/product/components/index.ts | 1 + .../iot/product/product/components/select.vue | 63 ++ .../src/views/iot/product/product/data.ts | 255 +++++++ .../iot/product/product/detail/index.vue | 89 +++ .../product/product/detail/modules/header.vue | 168 +++++ .../product/product/detail/modules/info.vue | 115 +++ .../src/views/iot/product/product/index.vue | 309 +++++++++ .../iot/product/product/modules/card-view.vue | 425 ++++++++++++ .../iot/product/product/modules/form.vue | 157 +++++ apps/web-ele/src/views/iot/thingmodel/data.ts | 67 ++ .../src/views/iot/thingmodel/index.vue | 158 +++++ .../modules/components/data-definition.vue | 87 +++ .../thingmodel/modules/components/index.ts | 1 + .../thingmodel/modules/data-specs/array.vue | 71 ++ .../thingmodel/modules/data-specs/enum.vue | 131 ++++ .../thingmodel/modules/data-specs/index.ts | 4 + .../thingmodel/modules/data-specs/number.vue} | 58 +- .../thingmodel/modules/data-specs/struct.vue} | 50 +- .../views/iot/thingmodel/modules/event.vue | 64 ++ .../src/views/iot/thingmodel/modules/form.vue | 265 +++++++ .../thingmodel/modules/input-output-param.vue | 158 +++++ .../views/iot/thingmodel/modules/property.vue | 227 ++++++ .../views/iot/thingmodel/modules/service.vue | 70 ++ .../src/views/iot/thingmodel/modules/tsl.vue | 83 +++ apps/web-ele/src/views/iot/utils/constants.ts | 653 ++++++++++++++++++ apps/web-ele/src/views/mes/utils/constants.ts | 39 ++ 78 files changed, 13325 insertions(+), 54 deletions(-) create mode 100644 apps/web-ele/src/views/iot/alert/config/data.ts create mode 100644 apps/web-ele/src/views/iot/alert/config/index.vue create mode 100644 apps/web-ele/src/views/iot/alert/config/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/alert/record/data.ts create mode 100644 apps/web-ele/src/views/iot/alert/record/index.vue create mode 100644 apps/web-ele/src/views/iot/device/device/data.ts create mode 100644 apps/web-ele/src/views/iot/device/device/detail/index.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/config.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/header.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/info.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/message.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config-form.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/modbus-point-form.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/simulator.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/sub-device.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-event.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property-history.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-service.vue create mode 100644 apps/web-ele/src/views/iot/device/device/detail/modules/thing-model.vue create mode 100644 apps/web-ele/src/views/iot/device/device/index.vue create mode 100644 apps/web-ele/src/views/iot/device/device/modules/card-view.vue create mode 100644 apps/web-ele/src/views/iot/device/device/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/device/device/modules/group-form.vue create mode 100644 apps/web-ele/src/views/iot/device/device/modules/import-form.vue create mode 100644 apps/web-ele/src/views/iot/device/group/data.ts create mode 100644 apps/web-ele/src/views/iot/device/group/index.vue create mode 100644 apps/web-ele/src/views/iot/device/group/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/home/chart-options.ts create mode 100644 apps/web-ele/src/views/iot/home/data.ts create mode 100644 apps/web-ele/src/views/iot/home/index.vue create mode 100644 apps/web-ele/src/views/iot/home/modules/device-count-card.vue create mode 100644 apps/web-ele/src/views/iot/home/modules/device-map-card.vue create mode 100644 apps/web-ele/src/views/iot/home/modules/device-state-count-card.vue create mode 100644 apps/web-ele/src/views/iot/home/modules/message-trend-card.vue create mode 100644 apps/web-ele/src/views/iot/ota/firmware/data.ts create mode 100644 apps/web-ele/src/views/iot/ota/firmware/detail/index.vue create mode 100644 apps/web-ele/src/views/iot/ota/firmware/detail/modules/info.vue create mode 100644 apps/web-ele/src/views/iot/ota/firmware/index.vue create mode 100644 apps/web-ele/src/views/iot/ota/firmware/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/ota/task/data.ts create mode 100644 apps/web-ele/src/views/iot/ota/task/modules/detail.vue create mode 100644 apps/web-ele/src/views/iot/ota/task/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/ota/task/modules/info.vue create mode 100644 apps/web-ele/src/views/iot/ota/task/modules/list.vue create mode 100644 apps/web-ele/src/views/iot/ota/task/modules/statistics.vue create mode 100644 apps/web-ele/src/views/iot/ota/task/record/data.ts create mode 100644 apps/web-ele/src/views/iot/ota/task/record/modules/list.vue create mode 100644 apps/web-ele/src/views/iot/product/category/data.ts create mode 100644 apps/web-ele/src/views/iot/product/category/index.vue create mode 100644 apps/web-ele/src/views/iot/product/category/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/product/product/components/index.ts create mode 100644 apps/web-ele/src/views/iot/product/product/components/select.vue create mode 100644 apps/web-ele/src/views/iot/product/product/data.ts create mode 100644 apps/web-ele/src/views/iot/product/product/detail/index.vue create mode 100644 apps/web-ele/src/views/iot/product/product/detail/modules/header.vue create mode 100644 apps/web-ele/src/views/iot/product/product/detail/modules/info.vue create mode 100644 apps/web-ele/src/views/iot/product/product/index.vue create mode 100644 apps/web-ele/src/views/iot/product/product/modules/card-view.vue create mode 100644 apps/web-ele/src/views/iot/product/product/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/data.ts create mode 100644 apps/web-ele/src/views/iot/thingmodel/index.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/components/data-definition.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/components/index.ts create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/data-specs/array.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/data-specs/enum.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/data-specs/index.ts rename apps/{web-antd/src/views/iot/thingmodel/modules/data-specs/thing-model-number-data-specs.vue => web-ele/src/views/iot/thingmodel/modules/data-specs/number.vue} (52%) rename apps/{web-antd/src/views/iot/thingmodel/modules/data-specs/thing-model-struct-data-specs.vue => web-ele/src/views/iot/thingmodel/modules/data-specs/struct.vue} (79%) create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/event.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/form.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/input-output-param.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/property.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/service.vue create mode 100644 apps/web-ele/src/views/iot/thingmodel/modules/tsl.vue create mode 100644 apps/web-ele/src/views/iot/utils/constants.ts diff --git a/apps/web-ele/src/views/iot/alert/config/data.ts b/apps/web-ele/src/views/iot/alert/config/data.ts new file mode 100644 index 000000000..421b16aa5 --- /dev/null +++ b/apps/web-ele/src/views/iot/alert/config/data.ts @@ -0,0 +1,205 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { AlertConfigApi } from '#/api/iot/alert/config'; + +import { CommonStatusEnum, DICT_TYPE } 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'; + +/** 新增/修改告警配置的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '配置名称', + component: 'Input', + componentProps: { + placeholder: '请输入配置名称', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '配置描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入配置描述', + rows: 3, + }, + }, + { + fieldName: 'level', + label: '告警级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_ALERT_LEVEL, 'number'), + placeholder: '请选择告警级别', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '配置状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + defaultValue: CommonStatusEnum.ENABLE, + rules: 'required', + }, + { + fieldName: 'sceneRuleIds', + label: '关联场景联动规则', + component: 'ApiSelect', + componentProps: { + api: getSimpleRuleSceneList, + labelField: 'name', + valueField: 'id', + multiple: true, + placeholder: '请选择关联的场景联动规则', + }, + defaultValue: [], + rules: 'required', + }, + { + fieldName: 'receiveUserIds', + label: '接收的用户', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + multiple: true, + placeholder: '请选择接收的用户', + }, + defaultValue: [], + rules: 'required', + }, + { + fieldName: 'receiveTypes', + label: '接收类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_ALERT_RECEIVE_TYPE, 'number'), + multiple: true, + placeholder: '请选择接收类型', + }, + defaultValue: [], + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '配置名称', + component: 'Input', + componentProps: { + placeholder: '请输入配置名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '配置状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择配置状态', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '配置编号', + minWidth: 80, + }, + { + field: 'name', + title: '配置名称', + minWidth: 150, + }, + { + field: 'description', + title: '配置描述', + minWidth: 200, + }, + { + field: 'level', + title: '告警级别', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_ALERT_LEVEL }, + }, + }, + { + field: 'status', + title: '配置状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'sceneRuleIds', + title: '关联场景联动规则', + minWidth: 150, + formatter: ({ cellValue }) => `${cellValue?.length || 0} 条`, + }, + { + field: 'receiveUserNames', + title: '接收人', + minWidth: 150, + }, + { + field: 'receiveTypes', + title: '接收类型', + minWidth: 150, + slots: { default: 'receiveTypes' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/alert/config/index.vue b/apps/web-ele/src/views/iot/alert/config/index.vue new file mode 100644 index 000000000..198d7d0db --- /dev/null +++ b/apps/web-ele/src/views/iot/alert/config/index.vue @@ -0,0 +1,136 @@ + + + diff --git a/apps/web-ele/src/views/iot/alert/config/modules/form.vue b/apps/web-ele/src/views/iot/alert/config/modules/form.vue new file mode 100644 index 000000000..42da09ba6 --- /dev/null +++ b/apps/web-ele/src/views/iot/alert/config/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/iot/alert/record/data.ts b/apps/web-ele/src/views/iot/alert/record/data.ts new file mode 100644 index 000000000..1645c1d6c --- /dev/null +++ b/apps/web-ele/src/views/iot/alert/record/data.ts @@ -0,0 +1,165 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { AlertRecordApi } from '#/api/iot/alert/record'; +import type { IotDeviceApi } from '#/api/iot/device/device'; +import type { IotProductApi } from '#/api/iot/product/product'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleAlertConfigList } from '#/api/iot/alert/config'; +import { getSimpleDeviceList } from '#/api/iot/device/device'; +import { getSimpleProductList } from '#/api/iot/product/product'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let productList: IotProductApi.Product[] = []; +let deviceList: IotDeviceApi.Device[] = []; +getSimpleProductList().then((data) => (productList = data)); +getSimpleDeviceList().then((data) => (deviceList = data)); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'configId', + label: '告警配置', + component: 'ApiSelect', + componentProps: { + api: getSimpleAlertConfigList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择告警配置', + clearable: true, + filterable: true, + }, + }, + { + fieldName: 'configLevel', + label: '告警级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_ALERT_LEVEL, 'number'), + placeholder: '请选择告警级别', + clearable: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + clearable: true, + filterable: true, + }, + }, + { + fieldName: 'deviceId', + label: '设备', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceList, + labelField: 'deviceName', + valueField: 'id', + placeholder: '请选择设备', + clearable: true, + filterable: true, + }, + }, + { + fieldName: 'processStatus', + label: '是否处理', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING), + placeholder: '请选择是否处理', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '记录编号', + minWidth: 80, + }, + { + field: 'configName', + title: '告警名称', + minWidth: 150, + }, + { + field: 'configLevel', + title: '告警级别', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_ALERT_LEVEL }, + }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 120, + formatter: ({ cellValue }) => + productList.find((p) => p.id === cellValue)?.name || '-', + }, + { + field: 'deviceId', + title: '设备名称', + minWidth: 120, + formatter: ({ cellValue }) => + deviceList.find((d) => d.id === cellValue)?.deviceName || '-', + }, + { + field: 'deviceMessage', + title: '触发的设备消息', + minWidth: 150, + slots: { default: 'deviceMessage' }, + }, + { + field: 'processStatus', + title: '是否处理', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'processRemark', + title: '处理结果', + minWidth: 150, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 100, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/alert/record/index.vue b/apps/web-ele/src/views/iot/alert/record/index.vue new file mode 100644 index 000000000..5356009ec --- /dev/null +++ b/apps/web-ele/src/views/iot/alert/record/index.vue @@ -0,0 +1,136 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/device/data.ts b/apps/web-ele/src/views/iot/device/device/data.ts new file mode 100644 index 000000000..277c2e31c --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/data.ts @@ -0,0 +1,336 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { IotDeviceApi } from '#/api/iot/device/device'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleDeviceGroupList } from '#/api/iot/device/group'; +import { getSimpleProductList } from '#/api/iot/product/product'; + +/** 基础表单字段 */ +export function useBasicFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + }, + dependencies: { + triggerFields: ['id'], + disabled: (values: any) => !!values?.id, + }, + rules: 'required', + }, + { + component: 'Input', + fieldName: 'deviceType', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'deviceName', + label: 'DeviceName', + component: 'Input', + componentProps: { + placeholder: '请输入 DeviceName', + }, + dependencies: { + triggerFields: ['id'], + disabled: (values: any) => !!values?.id, + }, + rules: z + .string() + .min(4, 'DeviceName 长度不能少于 4 个字符') + .max(32, 'DeviceName 长度不能超过 32 个字符') + .regex( + /^[\w.\-:@]{4,32}$/, + '支持英文字母、数字、下划线(_)、中划线(-)、点号(.)、半角冒号(:)和特殊字符@', + ), + }, + ]; +} + +/** 高级设置表单字段(更多设置) */ +export function useAdvancedFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'nickname', + label: '备注名称', + component: 'Input', + componentProps: { + placeholder: '请输入备注名称', + }, + rules: z + .string() + .min(4, '备注名称长度限制为 4~64 个字符') + .max(64, '备注名称长度限制为 4~64 个字符') + .regex( + /^[\u4E00-\u9FA5\u3040-\u30FF\w]+$/, + '备注名称只能包含中文、英文字母、日文、数字和下划线(_)', + ) + .optional() + .or(z.literal('')), + }, + { + fieldName: 'picUrl', + label: '设备图片', + component: 'ImageUpload', + }, + { + fieldName: 'groupIds', + label: '设备分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceGroupList, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择设备分组', + }, + }, + { + fieldName: 'serialNumber', + label: '设备序列号', + component: 'Input', + componentProps: { + placeholder: '请输入设备序列号', + }, + rules: z + .string() + .regex(/^[\w-]+$/, '序列号只能包含字母、数字、中划线和下划线') + .optional() + .or(z.literal('')), + }, + { + fieldName: 'longitude', + label: '设备经度', + component: 'InputNumber', + componentProps: { + class: '!w-full', + placeholder: '请输入设备经度', + min: -180, + max: 180, + precision: 6, + }, + rules: z + .number() + .min(-180, '经度范围为 -180 到 180') + .max(180, '经度范围为 -180 到 180') + .optional() + .nullable(), + }, + { + fieldName: 'latitude', + label: '设备纬度', + component: 'InputNumber', + componentProps: { + class: '!w-full', + placeholder: '请输入设备纬度', + min: -90, + max: 90, + precision: 6, + }, + rules: z + .number() + .min(-90, '纬度范围为 -90 到 90') + .max(90, '纬度范围为 -90 到 90') + .optional() + .nullable(), + }, + ]; +} + +/** 设备分组表单 */ +export function useGroupFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'groupIds', + label: '设备分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceGroupList, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择设备分组', + }, + rules: 'required', + }, + ]; +} + +/** 设备导入表单 */ +export function useImportFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'file', + label: '设备数据', + component: 'Upload', + rules: 'required', + help: '仅允许导入 xls、xlsx 格式文件', + }, + { + fieldName: 'updateSupport', + label: '是否覆盖', + component: 'Switch', + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + rules: z.boolean().default(false), + help: '是否更新已经存在的设备数据', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + clearable: true, + }, + }, + { + fieldName: 'deviceName', + label: 'DeviceName', + component: 'Input', + componentProps: { + placeholder: '请输入 DeviceName', + clearable: true, + }, + }, + { + fieldName: 'nickname', + label: '备注名称', + component: 'Input', + componentProps: { + placeholder: '请输入备注名称', + clearable: true, + }, + }, + { + fieldName: 'deviceType', + label: '设备类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number'), + placeholder: '请选择设备类型', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '设备状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_DEVICE_STATE, 'number'), + placeholder: '请选择设备状态', + clearable: true, + }, + }, + { + fieldName: 'groupId', + label: '设备分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceGroupList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择设备分组', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'deviceName', + title: 'DeviceName', + minWidth: 150, + }, + { + field: 'nickname', + title: '备注名称', + minWidth: 120, + }, + { + field: 'picUrl', + title: '设备图片', + width: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'productId', + title: '所属产品', + minWidth: 120, + slots: { default: 'product' }, + }, + { + field: 'deviceType', + title: '设备类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE }, + }, + }, + { + field: 'groupIds', + title: '所属分组', + minWidth: 150, + slots: { default: 'groups' }, + }, + { + field: 'state', + title: '设备状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_DEVICE_STATE }, + }, + }, + { + field: 'onlineTime', + title: '最后上线时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/device/device/detail/index.vue b/apps/web-ele/src/views/iot/device/device/detail/index.vue new file mode 100644 index 000000000..7173de87b --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/index.vue @@ -0,0 +1,162 @@ + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/config.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/config.vue new file mode 100644 index 000000000..0d61a0cfe --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/config.vue @@ -0,0 +1,202 @@ + + + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/header.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/header.vue new file mode 100644 index 000000000..fcb1587e0 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/header.vue @@ -0,0 +1,105 @@ + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/info.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/info.vue new file mode 100644 index 000000000..769eea473 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/info.vue @@ -0,0 +1,195 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/message.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/message.vue new file mode 100644 index 000000000..f2a4d973e --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/message.vue @@ -0,0 +1,248 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config-form.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config-form.vue new file mode 100644 index 000000000..237ba570b --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config-form.vue @@ -0,0 +1,222 @@ + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config.vue new file mode 100644 index 000000000..2bb693afe --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-config.vue @@ -0,0 +1,358 @@ + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-point-form.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-point-form.vue new file mode 100644 index 000000000..090261724 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/modbus-point-form.vue @@ -0,0 +1,314 @@ + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/simulator.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/simulator.vue new file mode 100644 index 000000000..81e0f3cb9 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/simulator.vue @@ -0,0 +1,566 @@ + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/sub-device.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/sub-device.vue new file mode 100644 index 000000000..e5ab5c716 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/sub-device.vue @@ -0,0 +1,355 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-event.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-event.vue new file mode 100644 index 000000000..ef2b84dfe --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-event.vue @@ -0,0 +1,260 @@ + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property-history.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property-history.vue new file mode 100644 index 000000000..ac91deba5 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property-history.vue @@ -0,0 +1,512 @@ + + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property.vue new file mode 100644 index 000000000..ff9814631 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-property.vue @@ -0,0 +1,421 @@ + + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-service.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-service.vue new file mode 100644 index 000000000..b0e972f51 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model-service.vue @@ -0,0 +1,285 @@ + + + + diff --git a/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model.vue b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model.vue new file mode 100644 index 000000000..cf520a98e --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/detail/modules/thing-model.vue @@ -0,0 +1,47 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/device/index.vue b/apps/web-ele/src/views/iot/device/device/index.vue new file mode 100644 index 000000000..9bc71cea7 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/index.vue @@ -0,0 +1,517 @@ + + + + + diff --git a/apps/web-ele/src/views/iot/device/device/modules/card-view.vue b/apps/web-ele/src/views/iot/device/device/modules/card-view.vue new file mode 100644 index 000000000..de4d003f8 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/modules/card-view.vue @@ -0,0 +1,437 @@ + + + + + diff --git a/apps/web-ele/src/views/iot/device/device/modules/form.vue b/apps/web-ele/src/views/iot/device/device/modules/form.vue new file mode 100644 index 000000000..27fa235af --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/modules/form.vue @@ -0,0 +1,205 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/device/modules/group-form.vue b/apps/web-ele/src/views/iot/device/device/modules/group-form.vue new file mode 100644 index 000000000..a6a2d1242 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/modules/group-form.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/device/modules/import-form.vue b/apps/web-ele/src/views/iot/device/device/modules/import-form.vue new file mode 100644 index 000000000..82fe188a6 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/device/modules/import-form.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/group/data.ts b/apps/web-ele/src/views/iot/device/group/data.ts new file mode 100644 index 000000000..5463262c9 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/group/data.ts @@ -0,0 +1,124 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { IotDeviceGroupApi } from '#/api/iot/device/group'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +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: z + .string() + .min(1, '分组名称不能为空') + .max(64, '分组名称长度不能超过 64 个字符'), + }, + { + fieldName: 'status', + label: '分组状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'description', + label: '分组描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入分组描述', + rows: 3, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分组名称', + component: 'Input', + componentProps: { + placeholder: '请输入分组名称', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + minWidth: 100, + }, + { + field: 'name', + title: '分组名称', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + width: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'description', + title: '分组描述', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'deviceCount', + title: '设备数量', + minWidth: 100, + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/device/group/index.vue b/apps/web-ele/src/views/iot/device/group/index.vue new file mode 100644 index 000000000..ad64bdad5 --- /dev/null +++ b/apps/web-ele/src/views/iot/device/group/index.vue @@ -0,0 +1,125 @@ + + + diff --git a/apps/web-ele/src/views/iot/device/group/modules/form.vue b/apps/web-ele/src/views/iot/device/group/modules/form.vue new file mode 100644 index 000000000..ca0edc48b --- /dev/null +++ b/apps/web-ele/src/views/iot/device/group/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/iot/home/chart-options.ts b/apps/web-ele/src/views/iot/home/chart-options.ts new file mode 100644 index 000000000..902181505 --- /dev/null +++ b/apps/web-ele/src/views/iot/home/chart-options.ts @@ -0,0 +1,196 @@ +/** 消息趋势图表配置 */ +export function getMessageTrendChartOptions( + times: string[], + upstreamData: number[], + downstreamData: number[], +): any { + return { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + backgroundColor: '#6a7985', + }, + }, + }, + legend: { + data: ['上行消息', '下行消息'], + top: '5%', + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + top: '15%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + boundaryGap: false, + data: times, + }, + ], + yAxis: [ + { + type: 'value', + name: '消息数量', + }, + ], + series: [ + { + name: '上行消息', + type: 'line', + smooth: true, + areaStyle: { + opacity: 0.3, + }, + emphasis: { + focus: 'series', + }, + data: upstreamData, + itemStyle: { + color: '#1890ff', + }, + }, + { + name: '下行消息', + type: 'line', + smooth: true, + areaStyle: { + opacity: 0.3, + }, + emphasis: { + focus: 'series', + }, + data: downstreamData, + itemStyle: { + color: '#52c41a', + }, + }, + ], + }; +} + +/** + * 设备状态仪表盘图表配置 + */ +export function getDeviceStateGaugeChartOptions( + value: number, + max: number, + color: string, + title: string, +): any { + return { + series: [ + { + type: 'gauge', + startAngle: 225, + endAngle: -45, + min: 0, + max, + center: ['50%', '50%'], + radius: '80%', + progress: { + show: true, + width: 12, + itemStyle: { + color, + }, + }, + axisLine: { + lineStyle: { + width: 12, + color: [[1, '#E5E7EB']] as [number, string][], + }, + }, + axisTick: { show: false }, + splitLine: { show: false }, + axisLabel: { show: false }, + pointer: { show: false }, + title: { + show: true, + offsetCenter: [0, '80%'], + fontSize: 14, + color: '#666', + }, + detail: { + valueAnimation: true, + fontSize: 32, + fontWeight: 'bold', + color, + offsetCenter: [0, '10%'], + formatter: (val: number) => `${val} 个`, + }, + data: [{ value, name: title }], + }, + ], + }; +} + +/** + * 设备数量饼图配置 + */ +export function getDeviceCountPieChartOptions( + data: Array<{ name: string; value: number }>, +): any { + return { + tooltip: { + trigger: 'item', + formatter: '{b}: {c} 个 ({d}%)', + }, + legend: { + type: 'scroll', + orient: 'horizontal', + bottom: '10px', + left: 'center', + icon: 'circle', + itemWidth: 10, + itemHeight: 10, + itemGap: 12, + textStyle: { + fontSize: 12, + }, + pageButtonPosition: 'end', + pageIconSize: 12, + pageTextStyle: { + fontSize: 12, + }, + pageFormatter: '{current}/{total}', + }, + series: [ + { + name: '设备数量', + type: 'pie', + radius: ['35%', '55%'], + center: ['50%', '40%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 8, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + }, + emphasis: { + label: { + show: true, + fontSize: 16, + fontWeight: 'bold', + }, + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, + }, + labelLine: { + show: false, + }, + data, + }, + ], + }; +} diff --git a/apps/web-ele/src/views/iot/home/data.ts b/apps/web-ele/src/views/iot/home/data.ts new file mode 100644 index 000000000..433db75c7 --- /dev/null +++ b/apps/web-ele/src/views/iot/home/data.ts @@ -0,0 +1,20 @@ +import type { IotStatisticsApi } from '#/api/iot/statistics'; + +/** 统计数据 */ +export type StatsData = IotStatisticsApi.StatisticsSummaryRespVO; + +/** 默认统计数据;用 -1 作为「未加载」哨兵,避免与「真 0 设备」混淆 */ +export const defaultStatsData: StatsData = { + productCategoryCount: -1, + productCount: -1, + deviceCount: -1, + deviceMessageCount: -1, + productCategoryTodayCount: 0, + productTodayCount: 0, + deviceTodayCount: 0, + deviceMessageTodayCount: 0, + deviceOnlineCount: 0, + deviceOfflineCount: 0, + deviceInactiveCount: 0, + productCategoryDeviceCounts: {}, +}; diff --git a/apps/web-ele/src/views/iot/home/index.vue b/apps/web-ele/src/views/iot/home/index.vue new file mode 100644 index 000000000..601c89dde --- /dev/null +++ b/apps/web-ele/src/views/iot/home/index.vue @@ -0,0 +1,108 @@ + + + diff --git a/apps/web-ele/src/views/iot/home/modules/device-count-card.vue b/apps/web-ele/src/views/iot/home/modules/device-count-card.vue new file mode 100644 index 000000000..5ec3a5606 --- /dev/null +++ b/apps/web-ele/src/views/iot/home/modules/device-count-card.vue @@ -0,0 +1,78 @@ + + + diff --git a/apps/web-ele/src/views/iot/home/modules/device-map-card.vue b/apps/web-ele/src/views/iot/home/modules/device-map-card.vue new file mode 100644 index 000000000..dd2ff62ad --- /dev/null +++ b/apps/web-ele/src/views/iot/home/modules/device-map-card.vue @@ -0,0 +1,220 @@ + + + diff --git a/apps/web-ele/src/views/iot/home/modules/device-state-count-card.vue b/apps/web-ele/src/views/iot/home/modules/device-state-count-card.vue new file mode 100644 index 000000000..b27dc2ac5 --- /dev/null +++ b/apps/web-ele/src/views/iot/home/modules/device-state-count-card.vue @@ -0,0 +1,113 @@ + + + diff --git a/apps/web-ele/src/views/iot/home/modules/message-trend-card.vue b/apps/web-ele/src/views/iot/home/modules/message-trend-card.vue new file mode 100644 index 000000000..9e66e2523 --- /dev/null +++ b/apps/web-ele/src/views/iot/home/modules/message-trend-card.vue @@ -0,0 +1,177 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/firmware/data.ts b/apps/web-ele/src/views/iot/ota/firmware/data.ts new file mode 100644 index 000000000..c3a854768 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/firmware/data.ts @@ -0,0 +1,181 @@ +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)); + +/** 固件详情的描述字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { field: 'name', label: '固件名称' }, + { field: 'productName', label: '所属产品' }, + { field: 'version', label: '固件版本' }, + { + field: 'createTime', + label: '创建时间', + render: (val) => (val ? (formatDateTime(val) as string) : '-'), + }, + { field: 'description', label: '固件描述', span: 2 }, + ]; +} + +/** 新增/修改固件的表单 */ +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: 'FileUpload', + componentProps: { + maxNumber: 1, + accept: ['bin', 'hex', 'zip'], + maxSize: 50, + helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '固件名称', + component: 'Input', + componentProps: { + placeholder: '请输入固件名称', + clearable: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '固件编号', + minWidth: 80, + }, + { + field: 'name', + title: '固件名称', + minWidth: 150, + }, + { + field: 'version', + title: '版本号', + minWidth: 120, + }, + { + field: 'description', + title: '固件描述', + minWidth: 200, + }, + { + field: 'productId', + title: '所属产品', + minWidth: 150, + formatter: ({ cellValue }) => + productList.find((p) => p.id === cellValue)?.name || '-', + }, + { + field: 'fileUrl', + title: '固件文件', + minWidth: 120, + slots: { default: 'fileUrl' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/ota/firmware/detail/index.vue b/apps/web-ele/src/views/iot/ota/firmware/detail/index.vue new file mode 100644 index 000000000..7f9ff9a36 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/firmware/detail/index.vue @@ -0,0 +1,74 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/firmware/detail/modules/info.vue b/apps/web-ele/src/views/iot/ota/firmware/detail/modules/info.vue new file mode 100644 index 000000000..5647ab610 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/firmware/detail/modules/info.vue @@ -0,0 +1,27 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/firmware/index.vue b/apps/web-ele/src/views/iot/ota/firmware/index.vue new file mode 100644 index 000000000..e3479cfc1 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/firmware/index.vue @@ -0,0 +1,164 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/firmware/modules/form.vue b/apps/web-ele/src/views/iot/ota/firmware/modules/form.vue new file mode 100644 index 000000000..1d09ff7cc --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/firmware/modules/form.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/task/data.ts b/apps/web-ele/src/views/iot/ota/task/data.ts new file mode 100644 index 000000000..a487fafc5 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/data.ts @@ -0,0 +1,165 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictLabel, getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { IoTOtaTaskDeviceScopeEnum } from '#/views/iot/utils/constants'; + +/** 任务详情的描述字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { field: 'id', label: '任务编号' }, + { field: 'name', label: '任务名称' }, + { + field: 'deviceScope', + label: '升级范围', + render: (val) => getDictLabel(DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE, val), + }, + { + field: 'status', + label: '任务状态', + render: (val) => getDictLabel(DICT_TYPE.IOT_OTA_TASK_STATUS, val), + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => (val ? (formatDateTime(val) as string) : '-'), + }, + { field: 'description', label: '任务描述', span: 3 }, + ]; +} + +/** 新增升级任务的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'firmwareId', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '任务名称', + component: 'Input', + componentProps: { + placeholder: '请输入任务名称', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '任务描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入任务描述', + rows: 3, + }, + }, + { + fieldName: 'deviceScope', + label: '升级范围', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE, 'number'), + placeholder: '请选择升级范围', + }, + defaultValue: IoTOtaTaskDeviceScopeEnum.ALL.value, + rules: 'required', + }, + { + fieldName: 'deviceIds', + label: '选择设备', + component: 'Select', + componentProps: { + mode: 'multiple', + placeholder: '请选择设备', + showSearch: true, + filterOption: true, + optionFilterProp: 'label', + }, + defaultValue: [], + dependencies: { + triggerFields: ['deviceScope'], + show: (values) => + values.deviceScope === IoTOtaTaskDeviceScopeEnum.SELECT.value, + rules: (values) => + values.deviceScope === IoTOtaTaskDeviceScopeEnum.SELECT.value + ? 'required' + : null, + }, + }, + ]; +} + +/** 任务列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '任务编号', + width: 80, + align: 'center', + }, + { + field: 'name', + title: '任务名称', + minWidth: 150, + align: 'center', + }, + { + field: 'deviceScope', + title: '升级范围', + width: 110, + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE }, + }, + }, + { + field: 'progress', + title: '升级进度', + width: 110, + align: 'center', + formatter: ({ row }) => + `${row.deviceSuccessCount || 0}/${row.deviceTotalCount || 0}`, + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + align: 'center', + formatter: 'formatDateTime', + }, + { + field: 'description', + title: '任务描述', + minWidth: 150, + align: 'center', + showOverflow: 'tooltip', + }, + { + field: 'status', + title: '任务状态', + width: 110, + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_OTA_TASK_STATUS }, + }, + }, + { + title: '操作', + width: 120, + fixed: 'right', + align: 'center', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/ota/task/modules/detail.vue b/apps/web-ele/src/views/iot/ota/task/modules/detail.vue new file mode 100644 index 000000000..9e0492c1c --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/modules/detail.vue @@ -0,0 +1,96 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/task/modules/form.vue b/apps/web-ele/src/views/iot/ota/task/modules/form.vue new file mode 100644 index 000000000..2d7cf85c9 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/task/modules/info.vue b/apps/web-ele/src/views/iot/ota/task/modules/info.vue new file mode 100644 index 000000000..fd3504678 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/modules/info.vue @@ -0,0 +1,26 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/task/modules/list.vue b/apps/web-ele/src/views/iot/ota/task/modules/list.vue new file mode 100644 index 000000000..a79dffe99 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/modules/list.vue @@ -0,0 +1,160 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/task/modules/statistics.vue b/apps/web-ele/src/views/iot/ota/task/modules/statistics.vue new file mode 100644 index 000000000..38c8cd68b --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/modules/statistics.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/iot/ota/task/record/data.ts b/apps/web-ele/src/views/iot/ota/task/record/data.ts new file mode 100644 index 000000000..a07093b09 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/record/data.ts @@ -0,0 +1,59 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 升级记录的列表字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'deviceName', + title: '设备名称', + minWidth: 150, + align: 'center', + }, + { + field: 'fromFirmwareVersion', + title: '当前版本', + width: 120, + align: 'center', + }, + { + field: 'status', + title: '升级状态', + width: 120, + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_OTA_TASK_RECORD_STATUS }, + }, + }, + { + field: 'progress', + title: '升级进度', + width: 120, + align: 'center', + formatter: ({ row }) => `${row.progress || 0}%`, + }, + { + field: 'description', + title: '状态描述', + minWidth: 150, + align: 'center', + showOverflow: 'tooltip', + }, + { + field: 'updateTime', + title: '更新时间', + width: 180, + align: 'center', + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + align: 'center', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/ota/task/record/modules/list.vue b/apps/web-ele/src/views/iot/ota/task/record/modules/list.vue new file mode 100644 index 000000000..eda196e04 --- /dev/null +++ b/apps/web-ele/src/views/iot/ota/task/record/modules/list.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/category/data.ts b/apps/web-ele/src/views/iot/product/category/data.ts new file mode 100644 index 000000000..cb972ae0b --- /dev/null +++ b/apps/web-ele/src/views/iot/product/category/data.ts @@ -0,0 +1,137 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { IotProductCategoryApi } from '#/api/iot/product/category'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +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: z + .string() + .min(1, '分类名字不能为空') + .max(64, '分类名字长度不能超过 64 个字符'), + }, + { + fieldName: 'sort', + label: '分类排序', + component: 'InputNumber', + componentProps: { + class: '!w-full', + placeholder: '请输入分类排序', + min: 0, + precision: 0, + }, + defaultValue: 0, + rules: z.number().min(0, '分类排序不能小于 0'), + }, + { + fieldName: 'status', + label: '分类状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'description', + label: '描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入分类描述', + rows: 3, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分类名字', + component: 'Input', + componentProps: { + placeholder: '请输入分类名字', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + width: 80, + }, + { + field: 'name', + title: '名字', + minWidth: 200, + }, + { + field: 'sort', + title: '排序', + width: 100, + }, + { + field: 'status', + title: '状态', + width: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'description', + title: '描述', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/product/category/index.vue b/apps/web-ele/src/views/iot/product/category/index.vue new file mode 100644 index 000000000..cb246d716 --- /dev/null +++ b/apps/web-ele/src/views/iot/product/category/index.vue @@ -0,0 +1,128 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/category/modules/form.vue b/apps/web-ele/src/views/iot/product/category/modules/form.vue new file mode 100644 index 000000000..688abb982 --- /dev/null +++ b/apps/web-ele/src/views/iot/product/category/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/product/components/index.ts b/apps/web-ele/src/views/iot/product/product/components/index.ts new file mode 100644 index 000000000..5ddac0c31 --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/components/index.ts @@ -0,0 +1 @@ +export { default as ProductSelect } from './select.vue'; diff --git a/apps/web-ele/src/views/iot/product/product/components/select.vue b/apps/web-ele/src/views/iot/product/product/components/select.vue new file mode 100644 index 000000000..73a478f6b --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/components/select.vue @@ -0,0 +1,63 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/product/data.ts b/apps/web-ele/src/views/iot/product/product/data.ts new file mode 100644 index 000000000..2d07b11b1 --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/data.ts @@ -0,0 +1,255 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { IotProductApi } from '#/api/iot/product/product'; + +import { h } from 'vue'; + +import { DeviceTypeEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { ElButton } from 'element-plus'; + +import { z } from '#/adapter/form'; +import { getSimpleProductCategoryList } from '#/api/iot/product/category'; + +/** 基础表单字段(不含图标、图片、描述) */ +export function useBasicFormSchema( + formApi?: any, + generateProductKey?: () => string, +): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'productKey', + label: 'ProductKey', + component: 'Input', + componentProps: { + placeholder: '请输入 ProductKey', + }, + dependencies: { + triggerFields: ['id'], + if(values) { + return !values.id; + }, + }, + rules: z + .string() + .min(1, 'ProductKey 不能为空') + .max(32, 'ProductKey 长度不能超过 32 个字符'), + suffix: () => { + return h( + ElButton, + { + onClick: () => { + if (generateProductKey) { + formApi?.setFieldValue('productKey', generateProductKey()); + } + }, + }, + { default: () => '重新生成' }, + ); + }, + }, + { + fieldName: 'productKey', + label: 'ProductKey', + component: 'Input', + componentProps: { + placeholder: '请输入 ProductKey', + disabled: true, + }, + dependencies: { + triggerFields: ['id'], + if(values) { + return !!values.id; + }, + }, + rules: z + .string() + .min(1, 'ProductKey 不能为空') + .max(32, 'ProductKey 长度不能超过 32 个字符'), + }, + { + fieldName: 'name', + label: '产品名称', + component: 'Input', + componentProps: { + placeholder: '请输入产品名称', + }, + rules: z + .string() + .min(1, '产品名称不能为空') + .max(64, '产品名称长度不能超过 64 个字符'), + }, + { + fieldName: 'categoryId', + label: '产品分类', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductCategoryList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品分类', + }, + rules: 'required', + }, + { + fieldName: 'deviceType', + label: '设备类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number'), + }, + defaultValue: DeviceTypeEnum.DEVICE, + dependencies: { + triggerFields: ['id'], + componentProps: (values) => ({ + // 编辑时设备类型不可改 + disabled: !!values.id, + }), + }, + rules: 'required', + }, + { + fieldName: 'netType', + label: '联网方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_NET_TYPE, 'number'), + placeholder: '请选择联网方式', + }, + // 网关子设备走网关联网,不需要联网方式 + dependencies: { + triggerFields: ['deviceType'], + // TODO DONE @AI:枚举值。或者这里不要枚举值?(也看看 vben 里,其它是不是也漏了枚举值。) + show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY, + }, + rules: 'required', + }, + { + fieldName: 'protocolType', + label: '协议类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'string'), + placeholder: '请选择协议类型', + }, + defaultValue: 'mqtt', + rules: 'required', + }, + { + fieldName: 'serializeType', + label: '序列化类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_SERIALIZE_TYPE, 'string'), + placeholder: '请选择序列化类型', + }, + defaultValue: 'json', + help: 'iot-gateway-server 默认根据接入的协议类型确定数据格式,仅 MQTT、EMQX 协议支持自定义序列化类型', + rules: 'required', + }, + ]; +} + +/** 高级设置表单字段(图标、图片、产品描述、动态注册) */ +export function useAdvancedFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'registerEnabled', + label: '动态注册', + component: 'Switch', + componentProps: { + checkedChildren: '开', + unCheckedChildren: '关', + }, + defaultValue: false, + help: '设备动态注册无需一一烧录设备证书(DeviceSecret),每台设备烧录相同的产品证书,即 ProductKey 和 ProductSecret ,云端鉴权通过后下发设备证书,您可以根据需要开启或关闭动态注册,保障安全性。', + }, + { + fieldName: 'icon', + label: '产品图标', + component: 'ImageUpload', + }, + { + fieldName: 'picUrl', + label: '产品图片', + component: 'ImageUpload', + }, + { + fieldName: 'description', + label: '产品描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入产品描述', + rows: 3, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + width: 80, + }, + { + field: 'productKey', + title: 'ProductKey', + minWidth: 150, + }, + { + field: 'categoryName', + title: '品类', + minWidth: 120, + formatter: ({ row }) => row.categoryName || '未分类', + }, + { + field: 'deviceType', + title: '设备类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE }, + }, + }, + { + field: 'icon', + title: '产品图标', + width: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'picUrl', + title: '产品图片', + width: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/product/product/detail/index.vue b/apps/web-ele/src/views/iot/product/product/detail/index.vue new file mode 100644 index 000000000..85a7866ea --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/detail/index.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/product/detail/modules/header.vue b/apps/web-ele/src/views/iot/product/product/detail/modules/header.vue new file mode 100644 index 000000000..521d9e083 --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/detail/modules/header.vue @@ -0,0 +1,168 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/product/detail/modules/info.vue b/apps/web-ele/src/views/iot/product/product/detail/modules/info.vue new file mode 100644 index 000000000..7d17c994f --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/detail/modules/info.vue @@ -0,0 +1,115 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/product/index.vue b/apps/web-ele/src/views/iot/product/product/index.vue new file mode 100644 index 000000000..d34f1f5fd --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/index.vue @@ -0,0 +1,309 @@ + + + diff --git a/apps/web-ele/src/views/iot/product/product/modules/card-view.vue b/apps/web-ele/src/views/iot/product/product/modules/card-view.vue new file mode 100644 index 000000000..3158acae8 --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/modules/card-view.vue @@ -0,0 +1,425 @@ + + + + + diff --git a/apps/web-ele/src/views/iot/product/product/modules/form.vue b/apps/web-ele/src/views/iot/product/product/modules/form.vue new file mode 100644 index 000000000..58c9186a5 --- /dev/null +++ b/apps/web-ele/src/views/iot/product/product/modules/form.vue @@ -0,0 +1,157 @@ + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/data.ts b/apps/web-ele/src/views/iot/thingmodel/data.ts new file mode 100644 index 000000000..0f1a53e6f --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/data.ts @@ -0,0 +1,67 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { ThingModelApi } from '#/api/iot/thingmodel'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getDataTypeOptionsLabel } from '#/views/iot/utils/constants'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'type', + label: '功能类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE, 'number'), + placeholder: '请选择功能类型', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'type', + title: '功能类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_THING_MODEL_TYPE }, + }, + }, + { + field: 'name', + title: '功能名称', + minWidth: 150, + }, + { + field: 'identifier', + title: '标识符', + minWidth: 120, + }, + { + title: '数据类型', + minWidth: 100, + formatter: ({ row }) => + getDataTypeOptionsLabel(row.property?.dataType ?? '') || '-', + }, + { + title: '数据定义', + minWidth: 200, + slots: { default: 'dataDefinition' }, + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/iot/thingmodel/index.vue b/apps/web-ele/src/views/iot/thingmodel/index.vue new file mode 100644 index 000000000..6c9127f85 --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/index.vue @@ -0,0 +1,158 @@ + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/components/data-definition.vue b/apps/web-ele/src/views/iot/thingmodel/modules/components/data-definition.vue new file mode 100644 index 000000000..a5c4cee7c --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/components/data-definition.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/components/index.ts b/apps/web-ele/src/views/iot/thingmodel/modules/components/index.ts new file mode 100644 index 000000000..d6316d657 --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/components/index.ts @@ -0,0 +1 @@ +export { default as DataDefinition } from './data-definition.vue'; diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/array.vue b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/array.vue new file mode 100644 index 000000000..ef946764f --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/array.vue @@ -0,0 +1,71 @@ + + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/enum.vue b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/enum.vue new file mode 100644 index 000000000..22030d2ce --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/enum.vue @@ -0,0 +1,131 @@ + + + + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/index.ts b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/index.ts new file mode 100644 index 000000000..5b75f0a7f --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/index.ts @@ -0,0 +1,4 @@ +export { default as ThingModelArrayDataSpecs } from './array.vue'; +export { default as ThingModelEnumDataSpecs } from './enum.vue'; +export { default as ThingModelNumberDataSpecs } from './number.vue'; +export { default as ThingModelStructDataSpecs } from './struct.vue'; diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/data-specs/thing-model-number-data-specs.vue b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/number.vue similarity index 52% rename from apps/web-antd/src/views/iot/thingmodel/modules/data-specs/thing-model-number-data-specs.vue rename to apps/web-ele/src/views/iot/thingmodel/modules/data-specs/number.vue index 024d91c55..0a8706e69 100644 --- a/apps/web-antd/src/views/iot/thingmodel/modules/data-specs/thing-model-number-data-specs.vue +++ b/apps/web-ele/src/views/iot/thingmodel/modules/data-specs/number.vue @@ -2,16 +2,13 @@ diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/form.vue b/apps/web-ele/src/views/iot/thingmodel/modules/form.vue new file mode 100644 index 000000000..c6c1f8cdd --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/form.vue @@ -0,0 +1,265 @@ + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/input-output-param.vue b/apps/web-ele/src/views/iot/thingmodel/modules/input-output-param.vue new file mode 100644 index 000000000..30fd91f78 --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/input-output-param.vue @@ -0,0 +1,158 @@ + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/property.vue b/apps/web-ele/src/views/iot/thingmodel/modules/property.vue new file mode 100644 index 000000000..81b4aab3c --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/property.vue @@ -0,0 +1,227 @@ + + + + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/service.vue b/apps/web-ele/src/views/iot/thingmodel/modules/service.vue new file mode 100644 index 000000000..727678ced --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/service.vue @@ -0,0 +1,70 @@ + + + + + + diff --git a/apps/web-ele/src/views/iot/thingmodel/modules/tsl.vue b/apps/web-ele/src/views/iot/thingmodel/modules/tsl.vue new file mode 100644 index 000000000..630ed86ef --- /dev/null +++ b/apps/web-ele/src/views/iot/thingmodel/modules/tsl.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web-ele/src/views/iot/utils/constants.ts b/apps/web-ele/src/views/iot/utils/constants.ts new file mode 100644 index 000000000..f4b415f0c --- /dev/null +++ b/apps/web-ele/src/views/iot/utils/constants.ts @@ -0,0 +1,653 @@ +// TODO @AI:感觉这块,放到 biz-iot-enum 里好点。 + +import { isEmpty } from '@vben/utils'; + +/** IoT 依赖注入 KEY */ +export const IOT_PROVIDE_KEY = { + PRODUCT: 'IOT_PRODUCT', +}; + +/** IoT 设备状态枚举 */ +export enum DeviceStateEnum { + INACTIVE = 0, // 未激活 + ONLINE = 1, // 在线 + OFFLINE = 2, // 离线 +} + +/** IoT 产品物模型类型枚举类 */ +export const IoTThingModelTypeEnum = { + PROPERTY: 1, // 属性 + SERVICE: 2, // 服务 + EVENT: 3, // 事件 +}; + +// IoT 产品物模型服务调用方式枚举 +export const IoTThingModelServiceCallTypeEnum = { + ASYNC: { + label: '异步', + value: 'async', + }, + SYNC: { + label: '同步', + value: 'sync', + }, +}; +export const getThingModelServiceCallTypeLabel = ( + value: string, +): string | undefined => + Object.values(IoTThingModelServiceCallTypeEnum).find( + (type) => type.value === value, + )?.label; + +// IoT 产品物模型事件类型枚举 +export const IoTThingModelEventTypeEnum = { + INFO: { + label: '信息', + value: 'info', + }, + ALERT: { + label: '告警', + value: 'alert', + }, + ERROR: { + label: '故障', + value: 'error', + }, +}; +export const getEventTypeLabel = (value: string): string | undefined => + Object.values(IoTThingModelEventTypeEnum).find((type) => type.value === value) + ?.label; + +// IoT 产品物模型参数是输入参数还是输出参数 +export const IoTThingModelParamDirectionEnum = { + INPUT: 'input', // 输入参数 + OUTPUT: 'output', // 输出参数 +}; + +// IoT 产品物模型访问模式枚举类 +export const IoTThingModelAccessModeEnum = { + READ_WRITE: { + label: '读写', + value: 'rw', + }, + READ_ONLY: { + label: '只读', + value: 'r', + }, + WRITE_ONLY: { + label: '只写', + value: 'w', + }, +}; + +/** 获取访问模式标签 */ +export const getAccessModeLabel = (value: string): string => { + const mode = Object.values(IoTThingModelAccessModeEnum).find( + (mode) => mode.value === value, + ); + return mode?.label || value; +}; + +/** 属性值的数据类型 */ +export const IoTDataSpecsDataTypeEnum = { + INT: 'int', + FLOAT: 'float', + DOUBLE: 'double', + ENUM: 'enum', + BOOL: 'bool', + TEXT: 'text', + DATE: 'date', + STRUCT: 'struct', + ARRAY: 'array', +}; + +const DATA_TYPE_OPTIONS = Object.freeze([ + { value: IoTDataSpecsDataTypeEnum.INT, label: '整数型' }, + { value: IoTDataSpecsDataTypeEnum.FLOAT, label: '单精度浮点型' }, + { value: IoTDataSpecsDataTypeEnum.DOUBLE, label: '双精度浮点型' }, + { value: IoTDataSpecsDataTypeEnum.ENUM, label: '枚举型' }, + { value: IoTDataSpecsDataTypeEnum.BOOL, label: '布尔型' }, + { value: IoTDataSpecsDataTypeEnum.TEXT, label: '文本型' }, + { value: IoTDataSpecsDataTypeEnum.DATE, label: '时间型' }, + { value: IoTDataSpecsDataTypeEnum.STRUCT, label: '结构体' }, + { value: IoTDataSpecsDataTypeEnum.ARRAY, label: '数组' }, +]); + +export const getDataTypeOptions = () => DATA_TYPE_OPTIONS; + +/** 获得物体模型数据类型配置项名称 */ +export const getDataTypeOptionsLabel = (value: string) => { + if (isEmpty(value)) { + return value; + } + const dataType = getDataTypeOptions().find( + (option) => option.value === value, + ); + return dataType && `${dataType.value}(${dataType.label})`; +}; + +/** 获取数据类型显示名称(用于属性选择器) */ +export const getDataTypeName = (dataType: string): string => { + const typeMap: Record = { + [IoTDataSpecsDataTypeEnum.INT]: '整数', + [IoTDataSpecsDataTypeEnum.FLOAT]: '浮点数', + [IoTDataSpecsDataTypeEnum.DOUBLE]: '双精度', + [IoTDataSpecsDataTypeEnum.TEXT]: '字符串', + [IoTDataSpecsDataTypeEnum.BOOL]: '布尔值', + [IoTDataSpecsDataTypeEnum.ENUM]: '枚举', + [IoTDataSpecsDataTypeEnum.DATE]: '日期', + [IoTDataSpecsDataTypeEnum.STRUCT]: '结构体', + [IoTDataSpecsDataTypeEnum.ARRAY]: '数组', + }; + return typeMap[dataType] || dataType; +}; + +/** 获取数据类型标签颜色(antd Tag `color`) */ +export const getDataTypeTagColor = ( + dataType: string, +): 'default' | 'error' | 'processing' | 'success' | 'warning' => { + const tagMap: Record< + string, + 'default' | 'error' | 'processing' | 'success' | 'warning' + > = { + [IoTDataSpecsDataTypeEnum.INT]: 'processing', + [IoTDataSpecsDataTypeEnum.FLOAT]: 'success', + [IoTDataSpecsDataTypeEnum.DOUBLE]: 'success', + [IoTDataSpecsDataTypeEnum.TEXT]: 'default', + [IoTDataSpecsDataTypeEnum.BOOL]: 'warning', + [IoTDataSpecsDataTypeEnum.ENUM]: 'error', + [IoTDataSpecsDataTypeEnum.DATE]: 'processing', + [IoTDataSpecsDataTypeEnum.STRUCT]: 'default', + [IoTDataSpecsDataTypeEnum.ARRAY]: 'warning', + }; + return tagMap[dataType] || 'default'; +}; + +/** 物模型组标签常量 */ +export const THING_MODEL_GROUP_LABELS = { + PROPERTY: '设备属性', + EVENT: '设备事件', + SERVICE: '设备服务', +}; + +// IoT OTA 任务设备范围枚举 +export const IoTOtaTaskDeviceScopeEnum = { + ALL: { + label: '全部设备', + value: 1, + }, + SELECT: { + label: '指定设备', + value: 2, + }, +}; + +// IoT OTA 任务状态枚举 +export const IoTOtaTaskStatusEnum = { + IN_PROGRESS: { + label: '进行中', + value: 10, + }, + END: { + label: '已结束', + value: 20, + }, + CANCELED: { + label: '已取消', + value: 30, + }, +}; + +// IoT OTA 升级记录状态枚举 +export const IoTOtaTaskRecordStatusEnum = { + PENDING: { + label: '待推送', + value: 0, + }, + PUSHED: { + label: '已推送', + value: 10, + }, + UPGRADING: { + label: '升级中', + value: 20, + }, + SUCCESS: { + label: '升级成功', + value: 30, + }, + FAILURE: { + label: '升级失败', + value: 40, + }, + CANCELED: { + label: '升级取消', + value: 50, + }, +}; + +// ========== 场景联动规则相关常量 ========== + +/** IoT 场景联动触发器类型枚举 */ +export const IotRuleSceneTriggerTypeEnum = { + DEVICE_STATE_UPDATE: 1, // 设备上下线变更 + DEVICE_PROPERTY_POST: 2, // 物模型属性上报 + DEVICE_EVENT_POST: 3, // 设备事件上报 + DEVICE_SERVICE_INVOKE: 4, // 设备服务调用 + TIMER: 100, // 定时触发 +}; + +/** 触发器类型选项配置 */ +export const triggerTypeOptions = [ + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE, + label: '设备状态变更', + }, + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, + label: '设备属性上报', + }, + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST, + label: '设备事件上报', + }, + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE, + label: '设备服务调用', + }, + { + value: IotRuleSceneTriggerTypeEnum.TIMER, + label: '定时触发', + }, +]; + +/** 判断是否为设备触发器类型 */ +export function isDeviceTrigger(type: number): boolean { + const deviceTriggerTypes = [ + IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE, + IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, + IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST, + IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE, + ] as number[]; + return deviceTriggerTypes.includes(type); +} + +// ========== 场景联动规则执行器相关常量 ========== + +/** IoT 场景联动执行器类型枚举 */ +export const IotRuleSceneActionTypeEnum = { + DEVICE_PROPERTY_SET: 1, // 设备属性设置 + DEVICE_SERVICE_INVOKE: 2, // 设备服务调用 + ALERT_TRIGGER: 100, // 告警触发 + ALERT_RECOVER: 101, // 告警恢复 +}; + +/** 执行器类型选项配置 */ +export const getActionTypeOptions = () => [ + { + value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, + label: '设备属性设置', + }, + { + value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE, + label: '设备服务调用', + }, + { + value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER, + label: '触发告警', + }, + { + value: IotRuleSceneActionTypeEnum.ALERT_RECOVER, + label: '恢复告警', + }, +]; + +/** 获取执行器类型标签 */ +export const getActionTypeLabel = (type: number): string => { + const option = getActionTypeOptions().find((opt) => opt.value === type); + return option?.label || '未知类型'; +}; + +/** IoT 场景联动触发条件参数操作符枚举 */ +export const IotRuleSceneTriggerConditionParameterOperatorEnum = { + EQUALS: { name: '等于', value: '=' }, // 等于 + NOT_EQUALS: { name: '不等于', value: '!=' }, // 不等于 + GREATER_THAN: { name: '大于', value: '>' }, // 大于 + GREATER_THAN_OR_EQUALS: { name: '大于等于', value: '>=' }, // 大于等于 + LESS_THAN: { name: '小于', value: '<' }, // 小于 + LESS_THAN_OR_EQUALS: { name: '小于等于', value: '<=' }, // 小于等于 + IN: { name: '在...之中', value: 'in' }, // 在...之中 + NOT_IN: { name: '不在...之中', value: 'not in' }, // 不在...之中 + BETWEEN: { name: '在...之间', value: 'between' }, // 在...之间 + NOT_BETWEEN: { name: '不在...之间', value: 'not between' }, // 不在...之间 + LIKE: { name: '字符串匹配', value: 'like' }, // 字符串匹配 + NOT_NULL: { name: '非空', value: 'not null' }, // 非空 +}; + +/** IoT 场景联动触发条件类型枚举 */ +export const IotRuleSceneTriggerConditionTypeEnum = { + DEVICE_STATUS: 1, // 设备状态 + DEVICE_PROPERTY: 2, // 设备属性 + CURRENT_TIME: 3, // 当前时间 +}; + +/** 获取条件类型选项 */ +export const getConditionTypeOptions = () => [ + { + value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS, + label: '设备状态', + }, + { + value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, + label: '设备属性', + }, + { + value: IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME, + label: '当前时间', + }, +]; + +/** 设备状态枚举 - 统一的设备状态管理 */ +export const IoTDeviceStatusEnum = { + // 在线状态 + ONLINE: { + label: '在线', + value: 'online', + tagType: 'success', + }, + OFFLINE: { + label: '离线', + value: 'offline', + tagType: 'danger', + }, + // 启用状态 + ENABLED: { + label: '正常', + value: 0, + value2: 'enabled', + tagType: 'success', + }, + DISABLED: { + label: '禁用', + value: 1, + value2: 'disabled', + tagType: 'danger', + }, + // 激活状态 + ACTIVATED: { + label: '已激活', + value2: 'activated', + tagType: 'success', + }, + NOT_ACTIVATED: { + label: '未激活', + value2: 'not_activated', + tagType: 'info', + }, +}; + +/** 设备选择器特殊选项 */ +export const DEVICE_SELECTOR_OPTIONS = { + ALL_DEVICES: { + id: 0, + deviceName: '全部设备', + }, +}; + +/** IoT 场景联动触发时间操作符枚举 */ +export const IotRuleSceneTriggerTimeOperatorEnum = { + BEFORE_TIME: { name: '在时间之前', value: 'before_time' }, // 在时间之前 + AFTER_TIME: { name: '在时间之后', value: 'after_time' }, // 在时间之后 + BETWEEN_TIME: { name: '在时间之间', value: 'between_time' }, // 在时间之间 + AT_TIME: { name: '在指定时间', value: 'at_time' }, // 在指定时间 + BEFORE_TODAY: { name: '在今日之前', value: 'before_today' }, // 在今日之前 + AFTER_TODAY: { name: '在今日之后', value: 'after_today' }, // 在今日之后 + TODAY: { name: '在今日之间', value: 'today' }, // 在今日之间 +}; + +/** 获取触发器类型标签 */ +export const getTriggerTypeLabel = (type: number): string => { + const option = triggerTypeOptions.find((item) => item.value === type); + return option?.label || '未知类型'; +}; + +// ========== JSON 参数输入组件相关常量 ========== + +/** JSON 参数输入组件类型枚举 */ +export const JsonParamsInputTypeEnum = { + SERVICE: 'service', + EVENT: 'event', + PROPERTY: 'property', + CUSTOM: 'custom', +}; + +/** JSON 参数输入组件类型 */ +export type JsonParamsInputType = + (typeof JsonParamsInputTypeEnum)[keyof typeof JsonParamsInputTypeEnum]; + +/** JSON 参数输入组件文本常量 */ +export const JSON_PARAMS_INPUT_CONSTANTS = { + // 基础文本 + PLACEHOLDER: '请输入JSON格式的参数', + JSON_FORMAT_CORRECT: 'JSON 格式正确', + QUICK_FILL_LABEL: '快速填充:', + EXAMPLE_DATA_BUTTON: '示例数据', + CLEAR_BUTTON: '清空', + VIEW_EXAMPLE_TITLE: '查看参数示例', + COMPLETE_JSON_FORMAT: '完整 JSON 格式:', + REQUIRED_TAG: '必填', + + // 错误信息 + PARAMS_MUST_BE_OBJECT: '参数必须是一个有效的 JSON 对象', + PARAM_REQUIRED_ERROR: (paramName: string) => `参数 ${paramName} 为必填项`, + JSON_FORMAT_ERROR: (error: string) => `JSON格式错误: ${error}`, + UNKNOWN_ERROR: '未知错误', + + // 类型相关标题 + TITLES: { + SERVICE: (name?: string) => `${name || '服务'} - 输入参数示例`, + EVENT: (name?: string) => `${name || '事件'} - 输出参数示例`, + PROPERTY: '属性设置 - 参数示例', + CUSTOM: (name?: string) => `${name || '自定义'} - 参数示例`, + DEFAULT: '参数示例', + }, + + // 参数标签 + PARAMS_LABELS: { + SERVICE: '输入参数', + EVENT: '输出参数', + PROPERTY: '属性参数', + CUSTOM: '参数列表', + DEFAULT: '参数', + }, + + // 空状态消息 + EMPTY_MESSAGES: { + SERVICE: '此服务无需输入参数', + EVENT: '此事件无输出参数', + PROPERTY: '无可设置的属性', + CUSTOM: '无参数配置', + DEFAULT: '无参数', + }, + + // 无配置消息 + NO_CONFIG_MESSAGES: { + SERVICE: '请先选择服务', + EVENT: '请先选择事件', + PROPERTY: '请先选择产品', + CUSTOM: '请先进行配置', + DEFAULT: '请先进行配置', + }, +}; + +/** JSON 参数输入组件图标常量 */ +export const JSON_PARAMS_INPUT_ICONS = { + // 标题图标 + TITLE_ICONS: { + SERVICE: 'ep:service', + EVENT: 'ep:bell', + PROPERTY: 'ep:edit', + CUSTOM: 'ep:document', + DEFAULT: 'ep:document', + }, + + // 参数图标 + PARAMS_ICONS: { + SERVICE: 'ep:edit', + EVENT: 'ep:upload', + PROPERTY: 'ep:setting', + CUSTOM: 'ep:list', + DEFAULT: 'ep:edit', + }, + + // 状态图标 + STATUS_ICONS: { + ERROR: 'ep:warning', + SUCCESS: 'ep:circle-check', + }, +}; + +/** JSON 参数输入组件示例值常量 */ +export const JSON_PARAMS_EXAMPLE_VALUES: Record = { + [IoTDataSpecsDataTypeEnum.INT]: { display: '25', value: 25 }, + [IoTDataSpecsDataTypeEnum.FLOAT]: { display: '25.5', value: 25.5 }, + [IoTDataSpecsDataTypeEnum.DOUBLE]: { display: '25.5', value: 25.5 }, + [IoTDataSpecsDataTypeEnum.BOOL]: { display: 'false', value: false }, + [IoTDataSpecsDataTypeEnum.TEXT]: { display: '"auto"', value: 'auto' }, + [IoTDataSpecsDataTypeEnum.ENUM]: { display: '"option1"', value: 'option1' }, + [IoTDataSpecsDataTypeEnum.STRUCT]: { display: '{}', value: {} }, + [IoTDataSpecsDataTypeEnum.ARRAY]: { display: '[]', value: [] }, + DEFAULT: { display: '""', value: '' }, +}; + +// ========== Modbus 通用常量 ========== + +/** Modbus 模式枚举 */ +export const ModbusModeEnum = { + POLLING: 1, // 云端轮询 + ACTIVE_REPORT: 2, // 主动上报 +} as const; + +/** Modbus 帧格式枚举 */ +export const ModbusFrameFormatEnum = { + MODBUS_TCP: 1, // Modbus TCP + MODBUS_RTU: 2, // Modbus RTU +} as const; + +/** Modbus 功能码枚举 */ +export const ModbusFunctionCodeEnum = { + READ_COILS: 1, // 读线圈 + READ_DISCRETE_INPUTS: 2, // 读离散输入 + READ_HOLDING_REGISTERS: 3, // 读保持寄存器 + READ_INPUT_REGISTERS: 4, // 读输入寄存器 +} as const; + +/** Modbus 功能码选项 */ +export const ModbusFunctionCodeOptions = [ + { value: 1, label: '01 - 读线圈 (Coils)', description: '可读写布尔值' }, + { + value: 2, + label: '02 - 读离散输入 (Discrete Inputs)', + description: '只读布尔值', + }, + { + value: 3, + label: '03 - 读保持寄存器 (Holding Registers)', + description: '可读写 16 位数据', + }, + { + value: 4, + label: '04 - 读输入寄存器 (Input Registers)', + description: '只读 16 位数据', + }, +]; + +/** Modbus 原始数据类型枚举 */ +export const ModbusRawDataTypeEnum = { + INT16: 'INT16', + UINT16: 'UINT16', + INT32: 'INT32', + UINT32: 'UINT32', + FLOAT: 'FLOAT', + DOUBLE: 'DOUBLE', + BOOLEAN: 'BOOLEAN', + STRING: 'STRING', +} as const; + +/** Modbus 原始数据类型选项 */ +export const ModbusRawDataTypeOptions = [ + { + value: 'INT16', + label: 'INT16', + description: '有符号16位整数', + registerCount: 1, + }, + { + value: 'UINT16', + label: 'UINT16', + description: '无符号16位整数', + registerCount: 1, + }, + { + value: 'INT32', + label: 'INT32', + description: '有符号32位整数', + registerCount: 2, + }, + { + value: 'UINT32', + label: 'UINT32', + description: '无符号32位整数', + registerCount: 2, + }, + { + value: 'FLOAT', + label: 'FLOAT', + description: '32位浮点数', + registerCount: 2, + }, + { + value: 'DOUBLE', + label: 'DOUBLE', + description: '64位浮点数', + registerCount: 4, + }, + { + value: 'BOOLEAN', + label: 'BOOLEAN', + description: '布尔值', + registerCount: 1, + }, + { + value: 'STRING', + label: 'STRING', + description: '字符串', + registerCount: 0, + }, +]; + +/** Modbus 字节序选项 - 16位 */ +export const ModbusByteOrder16Options = [ + { value: 'AB', label: 'AB', description: '大端序' }, + { value: 'BA', label: 'BA', description: '小端序' }, +]; + +/** Modbus 字节序选项 - 32位 */ +export const ModbusByteOrder32Options = [ + { value: 'ABCD', label: 'ABCD', description: '大端序' }, + { value: 'CDAB', label: 'CDAB', description: '大端字交换' }, + { value: 'DCBA', label: 'DCBA', description: '小端序' }, + { value: 'BADC', label: 'BADC', description: '小端字交换' }, +]; + +/** 根据数据类型获取字节序选项 */ +export const getByteOrderOptions = (rawDataType: string) => { + if (['FLOAT', 'INT32', 'UINT32'].includes(rawDataType)) { + return ModbusByteOrder32Options; + } + if (rawDataType === 'DOUBLE') { + // 64 位暂时复用 32 位字节序 + return ModbusByteOrder32Options; + } + return ModbusByteOrder16Options; +}; diff --git a/apps/web-ele/src/views/mes/utils/constants.ts b/apps/web-ele/src/views/mes/utils/constants.ts index 1e28f3d91..32f282f22 100644 --- a/apps/web-ele/src/views/mes/utils/constants.ts +++ b/apps/web-ele/src/views/mes/utils/constants.ts @@ -13,4 +13,43 @@ export const MesItemOrProductEnum = { /** MES 自动编码规则 Code 枚举 */ export const MesAutoCodeRuleCode = { MD_ITEM_TYPE_CODE: 'MD_ITEM_TYPE_CODE', + MD_ITEM_CODE: 'MD_ITEM_CODE', } as const; + +/** MES 条码格式枚举 */ +export enum BarcodeFormatEnum { + QR_CODE = 1, + EAN13 = 2, + CODE39 = 3, + UPC_A = 4, +} + +/** 条码格式映射表(枚举值 -> JsBarcode 格式名) */ +export const BARCODE_FORMAT_MAP: Record = { + [BarcodeFormatEnum.QR_CODE]: 'QR_CODE', + [BarcodeFormatEnum.EAN13]: 'EAN13', + [BarcodeFormatEnum.CODE39]: 'CODE39', + [BarcodeFormatEnum.UPC_A]: 'UPC_A', +}; + +/** MES 条码业务类型枚举 */ +export enum BarcodeBizTypeEnum { + WAREHOUSE = 102, + LOCATION = 103, + AREA = 104, + PACKAGE = 105, + STOCK = 106, + BATCH = 107, + PROCARD = 300, + WORKORDER = 301, + TRANSORDER = 302, + TASK = 303, + MACHINERY = 400, + TOOL = 500, + ITEM = 600, + VENDOR = 601, + WORKSTATION = 602, + WORKSHOP = 603, + USER = 604, + CLIENT = 605, +}