From 4aea2eaed59742807e339962901f5b4537b102ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Sat, 7 Sep 2024 09:46:52 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9AIOT=20?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/product/index.ts | 50 +++++ src/router/modules/remaining.ts | 21 ++ src/utils/dict.ts | 10 +- src/views/iot/product/ProductForm.vue | 203 +++++++++++++++++ .../product/detail/ProductDetailsHeader.vue | 45 ++++ .../iot/product/detail/ProductDetailsInfo.vue | 37 ++++ src/views/iot/product/detail/index.vue | 51 +++++ src/views/iot/product/index.vue | 206 ++++++++++++++++++ 8 files changed, 622 insertions(+), 1 deletion(-) create mode 100644 src/api/iot/product/index.ts create mode 100644 src/views/iot/product/ProductForm.vue create mode 100644 src/views/iot/product/detail/ProductDetailsHeader.vue create mode 100644 src/views/iot/product/detail/ProductDetailsInfo.vue create mode 100644 src/views/iot/product/detail/index.vue create mode 100644 src/views/iot/product/index.vue diff --git a/src/api/iot/product/index.ts b/src/api/iot/product/index.ts new file mode 100644 index 00000000..4c18426b --- /dev/null +++ b/src/api/iot/product/index.ts @@ -0,0 +1,50 @@ +import request from '@/config/axios' + +// iot 产品 VO +export interface ProductVO { + name: string // 产品名称 + id: number // 产品ID + productKey: string // 产品标识 + protocolId: number // 协议编号(脚本解析 id) + categoryId: number // 产品所属品类标识符 + description: string // 产品描述 + validateType: number // 数据校验级别, 0: 强校验, 1: 弱校验, 2: 免校验 + status: number // 产品状态, 0: DEVELOPMENT_STATUS, 1: RELEASE_STATUS + deviceType: number // 设备类型, 0: 直连设备, 1: 网关子设备, 2: 网关设备 + netType: number // 联网方式, 0: Wi-Fi, 1: Cellular, 2: Ethernet, 3: 其他 + protocolType: number // 接入网关协议, 0: modbus, 1: opc-ua, 2: customize, 3: ble, 4: zigbee + dataFormat: number // 数据格式, 0: 透传模式, 1: Alink JSON +} + +// iot 产品 API +export const ProductApi = { + // 查询iot 产品分页 + getProductPage: async (params: any) => { + return await request.get({ url: `/iot/product/page`, params }) + }, + + // 查询iot 产品详情 + getProduct: async (id: number) => { + return await request.get({ url: `/iot/product/get?id=` + id }) + }, + + // 新增iot 产品 + createProduct: async (data: ProductVO) => { + return await request.post({ url: `/iot/product/create`, data }) + }, + + // 修改iot 产品 + updateProduct: async (data: ProductVO) => { + return await request.put({ url: `/iot/product/update`, data }) + }, + + // 删除iot 产品 + deleteProduct: async (id: number) => { + return await request.delete({ url: `/iot/product/delete?id=` + id }) + }, + + // 导出iot 产品 Excel + exportProduct: async (params) => { + return await request.download({ url: `/iot/product/export-excel`, params }) + } +} \ No newline at end of file diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index f19d93ec..6b6acbcf 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -594,6 +594,27 @@ const remainingRouter: AppRouteRecordRaw[] = [ } } ] + }, + { + path: '/iot', + component: Layout, + name: 'IOT', + meta: { + hidden: true + }, + children: [ + { + path: 'product/detail/:id', + name: 'IotProductDetail', + meta: { + title: '产品详情', + noCache: true, + hidden: true, + activeMenu: '/iot/product' + }, + component: () => import('@/views/iot/product/detail/index.vue') + } + ] } ] diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 7fbcfebc..4a8c017c 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -226,5 +226,13 @@ export enum DICT_TYPE { AI_WRITE_LENGTH = 'ai_write_length', // AI 写作长度 AI_WRITE_FORMAT = 'ai_write_format', // AI 写作格式 AI_WRITE_TONE = 'ai_write_tone', // AI 写作语气 - AI_WRITE_LANGUAGE = 'ai_write_language' // AI 写作语言 + AI_WRITE_LANGUAGE = 'ai_write_language', // AI 写作语言 + + // ========== IOT - 物联网模块 ========== + IOT_NET_TYPE = 'iot_net_type', // IOT 联网方式 + IOT_VALIDATE_TYPE = 'iot_validate_type', // IOT 数据校验级别 + IOT_PRODUCT_STATUS = 'iot_product_status', // IOT 产品状态 + IOT_PRODUCT_DEVICE_TYPE = 'iot_product_device_type', // IOT 产品设备类型 + IOT_DATA_FORMAT = 'iot_data_format', // IOT 数据格式 + IOT_PROTOCOL_TYPE = 'iot_protocol_type' // IOT 接入网关协议 } diff --git a/src/views/iot/product/ProductForm.vue b/src/views/iot/product/ProductForm.vue new file mode 100644 index 00000000..ca7a4134 --- /dev/null +++ b/src/views/iot/product/ProductForm.vue @@ -0,0 +1,203 @@ + + + + + + + + + + {{ dict.label }} + + + + + + + + + + + + + + + + + + + + + + {{ dict.label }} + + + + + + + + + 确 定 + 取 消 + + + + diff --git a/src/views/iot/product/detail/ProductDetailsHeader.vue b/src/views/iot/product/detail/ProductDetailsHeader.vue new file mode 100644 index 00000000..4314652b --- /dev/null +++ b/src/views/iot/product/detail/ProductDetailsHeader.vue @@ -0,0 +1,45 @@ + + + + + + + {{ product.name }} + + + + + + + 编辑 + + + + + + + {{ product.name }} + {{ product.identification }} + + + + + + + + + + + + diff --git a/src/views/iot/product/detail/ProductDetailsInfo.vue b/src/views/iot/product/detail/ProductDetailsInfo.vue new file mode 100644 index 00000000..ced4a98e --- /dev/null +++ b/src/views/iot/product/detail/ProductDetailsInfo.vue @@ -0,0 +1,37 @@ + + + + + + 基本信息 + + + {{ product.name }} + {{ product.identification }} + + + + + + + + + + + + + {{ product.description }} + + + + + + diff --git a/src/views/iot/product/detail/index.vue b/src/views/iot/product/detail/index.vue new file mode 100644 index 00000000..0a3bda5b --- /dev/null +++ b/src/views/iot/product/detail/index.vue @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + diff --git a/src/views/iot/product/index.vue b/src/views/iot/product/index.vue new file mode 100644 index 00000000..9e6d06a7 --- /dev/null +++ b/src/views/iot/product/index.vue @@ -0,0 +1,206 @@ + + + + + + + + + + + + 搜索 + 重置 + + 新增 + + + 导出 + + + + + + + + + + + + + + + + + + + + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + From e5de6a5cbd3fa30326ffe14ad85dde32d4961495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Sat, 7 Sep 2024 16:38:36 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=9AIOT=20?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/iot/product/ProductForm.vue | 45 ++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/views/iot/product/ProductForm.vue b/src/views/iot/product/ProductForm.vue index ca7a4134..f5a33cd4 100644 --- a/src/views/iot/product/ProductForm.vue +++ b/src/views/iot/product/ProductForm.vue @@ -10,23 +10,32 @@ + - - + - {{ dict.label }} - - + :label="dict.label" + :value="dict.value" + /> + + - + + - + + - + + - + + + 确 定 取 消 + diff --git a/src/views/iot/product/detail/ProductDetailsInfo.vue b/src/views/iot/product/detail/ProductDetailsInfo.vue index ced4a98e..e3b5483a 100644 --- a/src/views/iot/product/detail/ProductDetailsInfo.vue +++ b/src/views/iot/product/detail/ProductDetailsInfo.vue @@ -1,34 +1,41 @@ - - - 基本信息 - - - {{ product.name }} - {{ product.identification }} - - - - - - - - - - - - - {{ product.description }} - - + + {{ product.name }} + + + + {{ + formatDate(product.createTime) + }} + + + + + + + + + + + + + + + + {{ product.description }} + diff --git a/src/views/iot/device/index.vue b/src/views/iot/device/index.vue new file mode 100644 index 00000000..e50819bd --- /dev/null +++ b/src/views/iot/device/index.vue @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 搜索 + 重置 + + 新增 + + + 导出 + + + + + + + + + + + + + + + + + + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + diff --git a/src/views/iot/product/ProductForm.vue b/src/views/iot/product/ProductForm.vue index 21cee6c3..7d254a03 100644 --- a/src/views/iot/product/ProductForm.vue +++ b/src/views/iot/product/ProductForm.vue @@ -103,7 +103,7 @@ import { ProductApi, ProductVO } from '@/api/iot/product' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -defineOptions({ name: 'ProductForm' }) +defineOptions({ name: 'IoTProductForm' }) const { t } = useI18n() const message = useMessage() diff --git a/src/views/iot/product/detail/index.vue b/src/views/iot/product/detail/index.vue index 8652e240..c77fe176 100644 --- a/src/views/iot/product/detail/index.vue +++ b/src/views/iot/product/detail/index.vue @@ -20,7 +20,7 @@ import { ProductApi, ProductVO } from '@/api/iot/product' import ProductDetailsHeader from '@/views/iot/product/detail/ProductDetailsHeader.vue' import ProductDetailsInfo from '@/views/iot/product/detail/ProductDetailsInfo.vue' -defineOptions({ name: 'IotProductDetail' }) +defineOptions({ name: 'IoTProductDetail' }) const route = useRoute() const message = useMessage() diff --git a/src/views/iot/product/index.vue b/src/views/iot/product/index.vue index a1f5ae4a..d3196ac4 100644 --- a/src/views/iot/product/index.vue +++ b/src/views/iot/product/index.vue @@ -44,7 +44,11 @@ - + + + {{ scope.row.name }} + + @@ -106,7 +110,7 @@ import ProductForm from './ProductForm.vue' import { DICT_TYPE } from '@/utils/dict' /** iot 产品 列表 */ -defineOptions({ name: 'Product' }) +defineOptions({ name: 'IoTProduct' }) const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -166,7 +170,7 @@ const openForm = (type: string, id?: number) => { /** 打开详情 */ const { currentRoute, push } = useRouter() const openDetail = (id: number) => { - push({ name: 'IotProductDetail', params: { id } }) + push({ name: 'IoTProductDetail', params: { id } }) } /** 删除按钮操作 */ From 63a0e5dc3d3463caffd56eceac5defd854941288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Sun, 22 Sep 2024 13:17:12 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91=20IO?= =?UTF-8?q?T=20=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/device/index.ts | 28 +++++++--- src/views/iot/device/DeviceForm.vue | 42 +++++++++++++- src/views/iot/device/index.vue | 86 +++++++++++++---------------- 3 files changed, 95 insertions(+), 61 deletions(-) diff --git a/src/api/iot/device/index.ts b/src/api/iot/device/index.ts index 4ee6d8d9..39a2e5a8 100644 --- a/src/api/iot/device/index.ts +++ b/src/api/iot/device/index.ts @@ -1,6 +1,6 @@ import request from '@/config/axios' -// IoT 设备 VO +// 设备 VO export interface DeviceVO { id: number // 设备 ID,主键,自增 deviceKey: string // 设备唯一标识符,全局唯一,用于识别设备 @@ -29,35 +29,45 @@ export interface DeviceVO { serialNumber: string // 设备序列号 } -// IoT 设备 API +export interface DeviceUpdateStatusVO { + id: number // 设备 ID,主键,自增 + status: number // 设备状态:0 - 未激活,1 - 在线,2 - 离线,3 - 已禁用 +} + +// 设备 API export const DeviceApi = { - // 查询IoT 设备分页 + // 查询设备分页 getDevicePage: async (params: any) => { return await request.get({ url: `/iot/device/page`, params }) }, - // 查询IoT 设备详情 + // 查询设备详情 getDevice: async (id: number) => { return await request.get({ url: `/iot/device/get?id=` + id }) }, - // 新增IoT 设备 + // 新增设备 createDevice: async (data: DeviceVO) => { return await request.post({ url: `/iot/device/create`, data }) }, - // 修改IoT 设备 + // 修改设备 updateDevice: async (data: DeviceVO) => { return await request.put({ url: `/iot/device/update`, data }) }, - // 删除IoT 设备 + // 修改设备状态 + updateDeviceStatus: async (data: DeviceUpdateStatusVO) => { + return await request.put({ url: `/iot/device/update-status`, data }) + }, + + // 删除设备 deleteDevice: async (id: number) => { return await request.delete({ url: `/iot/device/delete?id=` + id }) }, - // 导出IoT 设备 Excel + // 导出设备 Excel exportDevice: async (params) => { return await request.download({ url: `/iot/device/export-excel`, params }) } -} \ No newline at end of file +} diff --git a/src/views/iot/device/DeviceForm.vue b/src/views/iot/device/DeviceForm.vue index a77045c9..f926b6d4 100644 --- a/src/views/iot/device/DeviceForm.vue +++ b/src/views/iot/device/DeviceForm.vue @@ -8,7 +8,12 @@ v-loading="formLoading" > - + - + @@ -52,7 +61,34 @@ const formData = ref({ serialNumber: undefined }) const formRules = reactive({ - productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }] + productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }], + deviceName: [ + { + pattern: /^[a-zA-Z0-9_.\-:@]{4,32}$/, + message: + '支持英文字母、数字、下划线(_)、中划线(-)、点号(.)、半角冒号(:)和特殊字符@,长度限制为4~32个字符', + trigger: 'blur' + } + ], + nickname: [ + { + validator: (rule, value, callback) => { + if (value === undefined || value === null) { + callback() + return + } + const length = value.replace(/[\u4e00-\u9fa5\u3040-\u30ff]/g, 'aa').length + if (length < 4 || length > 64) { + callback(new Error('备注名称长度限制为4~64个字符,中文及日文算2个字符')) + } else if (!/^[\u4e00-\u9fa5\u3040-\u30ff_a-zA-Z0-9]+$/.test(value)) { + callback(new Error('备注名称只能包含中文、英文字母、日文、数字和下划线(_)')) + } else { + callback() + } + }, + trigger: 'blur' + } + ] }) const formRef = ref() // 表单 Ref diff --git a/src/views/iot/device/index.vue b/src/views/iot/device/index.vue index e50819bd..bb733250 100644 --- a/src/views/iot/device/index.vue +++ b/src/views/iot/device/index.vue @@ -72,24 +72,22 @@ - 搜索 - 重置 + + + 搜索 + + + + 重置 + - 新增 - - - 导出 + + 新增 @@ -100,9 +98,21 @@ - - - + + + {{ productMap[scope.row.productId] }} + + + + + + + + + + + + - - - - - { loading.value = true @@ -212,6 +193,13 @@ const getList = async () => { const data = await DeviceApi.getDevicePage(queryParams) list.value = data.list total.value = data.total + // 获取产品ID列表 + const productIds = [...new Set(data.list.map((device) => device.productId))] + // 获取产品名称 + const products = await Promise.all(productIds.map((id) => ProductApi.getProduct(id))) + products.forEach((product) => { + productMap[product.id] = product.name + }) } finally { loading.value = false } From 93a0789e34280c3607a80c5ebfdb3da5894b5e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Sun, 22 Sep 2024 15:42:20 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91=20IO?= =?UTF-8?q?T=20=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86=EF=BC=8C=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/device/index.ts | 1 + src/router/modules/remaining.ts | 11 ++ .../iot/device/detail/DeviceDetailsHeader.vue | 114 ++++++++++++ .../iot/device/detail/DeviceDetailsInfo.vue | 175 ++++++++++++++++++ src/views/iot/device/detail/index.vue | 66 +++++++ src/views/iot/device/index.vue | 38 ++-- 6 files changed, 387 insertions(+), 18 deletions(-) create mode 100644 src/views/iot/device/detail/DeviceDetailsHeader.vue create mode 100644 src/views/iot/device/detail/DeviceDetailsInfo.vue create mode 100644 src/views/iot/device/detail/index.vue diff --git a/src/api/iot/device/index.ts b/src/api/iot/device/index.ts index 39a2e5a8..2a1951de 100644 --- a/src/api/iot/device/index.ts +++ b/src/api/iot/device/index.ts @@ -15,6 +15,7 @@ export interface DeviceVO { lastOnlineTime: Date // 最后上线时间 lastOfflineTime: Date // 最后离线时间 activeTime: Date // 设备激活时间 + createTime: Date // 创建时间 ip: string // 设备的 IP 地址 firmwareVersion: string // 设备的固件版本 deviceSecret: string // 设备密钥,用于设备认证,需安全存储 diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 4595d563..cba8359c 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -622,6 +622,17 @@ const remainingRouter: AppRouteRecordRaw[] = [ activeMenu: '/iot/product' }, component: () => import('@/views/iot/product/detail/index.vue') + }, + { + path: 'device/detail/:id', + name: 'IoTDeviceDetail', + meta: { + title: '设备详情', + noCache: true, + hidden: true, + activeMenu: '/iot/device' + }, + component: () => import('@/views/iot/device/detail/index.vue') } ] } diff --git a/src/views/iot/device/detail/DeviceDetailsHeader.vue b/src/views/iot/device/detail/DeviceDetailsHeader.vue new file mode 100644 index 00000000..cc585f4a --- /dev/null +++ b/src/views/iot/device/detail/DeviceDetailsHeader.vue @@ -0,0 +1,114 @@ + + + + + + + {{ device.deviceName }} + + + + + + + 编辑 + + + + + + + + {{ product.name }} + + + {{ product.productKey }} + 复制 + + + + + + + diff --git a/src/views/iot/device/detail/DeviceDetailsInfo.vue b/src/views/iot/device/detail/DeviceDetailsInfo.vue new file mode 100644 index 00000000..2acbaabb --- /dev/null +++ b/src/views/iot/device/detail/DeviceDetailsInfo.vue @@ -0,0 +1,175 @@ + + + + + {{ product.name }} + {{ product.productKey }} + 复制 + + + + + {{ device.deviceName }} + 复制 + + {{ device.nickname }} + {{ + formatDate(device.createTime) + }} + {{ + formatDate(device.activeTime) + }} + {{ + formatDate(device.lastOnlineTime) + }} + + + + {{ + formatDate(device.lastOfflineTime) + }} + + 查看 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 关闭 + + + + + diff --git a/src/views/iot/device/detail/index.vue b/src/views/iot/device/detail/index.vue new file mode 100644 index 00000000..2db16bbc --- /dev/null +++ b/src/views/iot/device/detail/index.vue @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + diff --git a/src/views/iot/device/index.vue b/src/views/iot/device/index.vue index bb733250..566c78a6 100644 --- a/src/views/iot/device/index.vue +++ b/src/views/iot/device/index.vue @@ -96,7 +96,11 @@ - + + + {{ scope.row.deviceName }} + + @@ -122,6 +126,14 @@ /> + + 查看 + import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' -import download from '@/utils/download' -import { DeviceApi, DeviceUpdateStatusVO, DeviceVO } from '@/api/iot/device' +import { DeviceApi, DeviceVO } from '@/api/iot/device' import DeviceForm from './DeviceForm.vue' import { ProductApi } from '@/api/iot/product' @@ -223,6 +234,12 @@ const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } +/** 打开详情 */ +const { currentRoute, push } = useRouter() +const openDetail = (id: number) => { + push({ name: 'IoTDeviceDetail', params: { id } }) +} + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { @@ -235,21 +252,6 @@ const handleDelete = async (id: number) => { await getList() } catch {} } - -/** 导出按钮操作 */ -const handleExport = async () => { - try { - // 导出的二次确认 - await message.exportConfirm() - // 发起导出 - exportLoading.value = true - const data = await DeviceApi.exportDevice(queryParams) - download.excel(data, '设备.xls') - } catch { - } finally { - exportLoading.value = false - } -} /** 查询字典下拉列表 */ const products = ref() const getProducts = async () => { From 6aaf6112916e7ac3a6410a944558d257ae3cba91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Mon, 23 Sep 2024 23:34:21 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91=20IO?= =?UTF-8?q?T=20=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86=EF=BC=8CTopic=20?= =?UTF-8?q?=E7=B1=BB=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/device/index.ts | 6 +- src/api/iot/product/index.ts | 1 + .../iot/device/detail/DeviceDetailsHeader.vue | 47 +--- .../iot/device/detail/DeviceDetailsInfo.vue | 47 +--- .../product/detail/ProductDetailsHeader.vue | 14 +- src/views/iot/product/detail/ProductTopic.vue | 229 ++++++++++++++++++ src/views/iot/product/detail/index.vue | 31 ++- 7 files changed, 275 insertions(+), 100 deletions(-) create mode 100644 src/views/iot/product/detail/ProductTopic.vue diff --git a/src/api/iot/device/index.ts b/src/api/iot/device/index.ts index 2a1951de..24d48789 100644 --- a/src/api/iot/device/index.ts +++ b/src/api/iot/device/index.ts @@ -67,8 +67,8 @@ export const DeviceApi = { return await request.delete({ url: `/iot/device/delete?id=` + id }) }, - // 导出设备 Excel - exportDevice: async (params) => { - return await request.download({ url: `/iot/device/export-excel`, params }) + // 获取设备数量 + getDeviceCount: async (productId: number) => { + return await request.get({ url: `/iot/device/count?productId=` + productId }) } } diff --git a/src/api/iot/product/index.ts b/src/api/iot/product/index.ts index d4de1e5b..2a8d430f 100644 --- a/src/api/iot/product/index.ts +++ b/src/api/iot/product/index.ts @@ -14,6 +14,7 @@ export interface ProductVO { netType: number // 联网方式, 0: Wi-Fi, 1: Cellular, 2: Ethernet, 3: 其他 protocolType: number // 接入网关协议, 0: modbus, 1: opc-ua, 2: customize, 3: ble, 4: zigbee dataFormat: number // 数据格式, 0: 透传模式, 1: Alink JSON + deviceCount: number // 设备数量 } // iot 产品 API diff --git a/src/views/iot/device/detail/DeviceDetailsHeader.vue b/src/views/iot/device/detail/DeviceDetailsHeader.vue index cc585f4a..4360cab1 100644 --- a/src/views/iot/device/detail/DeviceDetailsHeader.vue +++ b/src/views/iot/device/detail/DeviceDetailsHeader.vue @@ -58,49 +58,10 @@ const emit = defineEmits(['refresh']) * * @param text 需要复制的文本 */ -const copyToClipboard = async (text: string) => { - if (!navigator.clipboard) { - // 浏览器不支持 Clipboard API,使用回退方法 - const textarea = document.createElement('textarea') - textarea.value = text - // 防止页面滚动 - textarea.style.position = 'fixed' - textarea.style.top = '0' - textarea.style.left = '0' - textarea.style.width = '2em' - textarea.style.height = '2em' - textarea.style.padding = '0' - textarea.style.border = 'none' - textarea.style.outline = 'none' - textarea.style.boxShadow = 'none' - textarea.style.background = 'transparent' - document.body.appendChild(textarea) - textarea.focus() - textarea.select() - - try { - const successful = document.execCommand('copy') - if (successful) { - message.success('复制成功!') - } else { - message.error('复制失败,请手动复制') - } - } catch (err) { - console.error('Fallback: Oops, unable to copy', err) - message.error('复制失败,请手动复制') - } - - document.body.removeChild(textarea) - return - } - - try { - await navigator.clipboard.writeText(text) - message.success('复制成功!') - } catch (err) { - console.error('Async: Could not copy text: ', err) - message.error('复制失败,请手动复制') - } +const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text).then(() => { + message.success('复制成功') + }) } /** diff --git a/src/views/iot/device/detail/DeviceDetailsInfo.vue b/src/views/iot/device/detail/DeviceDetailsInfo.vue index 2acbaabb..77084625 100644 --- a/src/views/iot/device/detail/DeviceDetailsInfo.vue +++ b/src/views/iot/device/detail/DeviceDetailsInfo.vue @@ -103,49 +103,10 @@ const emit = defineEmits(['refresh']) const activeNames = ref(['basicInfo']) // 复制到剪贴板方法 -const copyToClipboard = async (text: string) => { - if (!navigator.clipboard) { - // 浏览器不支持 Clipboard API,使用回退方法 - const textarea = document.createElement('textarea') - textarea.value = text - // 防止页面滚动 - textarea.style.position = 'fixed' - textarea.style.top = '0' - textarea.style.left = '0' - textarea.style.width = '2em' - textarea.style.height = '2em' - textarea.style.padding = '0' - textarea.style.border = 'none' - textarea.style.outline = 'none' - textarea.style.boxShadow = 'none' - textarea.style.background = 'transparent' - document.body.appendChild(textarea) - textarea.focus() - textarea.select() - - try { - const successful = document.execCommand('copy') - if (successful) { - message.success('复制成功!') - } else { - message.error('复制失败,请手动复制') - } - } catch (err) { - console.error('Fallback: Oops, unable to copy', err) - message.error('复制失败,请手动复制') - } - - document.body.removeChild(textarea) - return - } - - try { - await navigator.clipboard.writeText(text) - message.success('复制成功!') - } catch (err) { - console.error('Async: Could not copy text: ', err) - message.error('复制失败,请手动复制') - } +const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text).then(() => { + message.success('复制成功') + }) } // 定义 MQTT 弹框的可见性 diff --git a/src/views/iot/product/detail/ProductDetailsHeader.vue b/src/views/iot/product/detail/ProductDetailsHeader.vue index b50001e8..db0849ed 100644 --- a/src/views/iot/product/detail/ProductDetailsHeader.vue +++ b/src/views/iot/product/detail/ProductDetailsHeader.vue @@ -45,8 +45,8 @@ - 0 - 前往管理 + {{ product.deviceCount }} + 前往管理 @@ -63,8 +63,11 @@ const copyToClipboard = (text: string) => { message.success('复制成功') }) } -const goToManagement = (productKey: string) => { - message.warning('暂未开放') + +// 路由跳转到设备管理 +const { currentRoute, push } = useRouter() +const goToManagement = (productId: string) => { + push({ name: 'IoTDevice', query: { productId } }) } // 操作修改 @@ -93,6 +96,9 @@ const confirmUnpublish = async (id: number) => { } } +// 定义 Props const { product } = defineProps<{ product: ProductVO }>() + +// 定义 Emits const emit = defineEmits(['refresh']) diff --git a/src/views/iot/product/detail/ProductTopic.vue b/src/views/iot/product/detail/ProductTopic.vue new file mode 100644 index 00000000..071a5236 --- /dev/null +++ b/src/views/iot/product/detail/ProductTopic.vue @@ -0,0 +1,229 @@ + + + + + + + + + + + + + diff --git a/src/views/iot/product/detail/index.vue b/src/views/iot/product/detail/index.vue index c77fe176..6510ff3f 100644 --- a/src/views/iot/product/detail/index.vue +++ b/src/views/iot/product/detail/index.vue @@ -1,11 +1,13 @@ - + getProductData(id)" /> - + + + @@ -15,10 +17,10 @@ From 0e3ebbd2b8b314057a01c6d9b30682d2c07d0fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Tue, 24 Sep 2024 23:18:42 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=91=20IOT=20=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=8CTopic=20=E7=B1=BB=E5=88=97=E8=A1=A8=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/iot/product/detail/ProductTopic.vue | 413 +++++++++--------- src/views/iot/product/detail/index.vue | 3 +- 2 files changed, 215 insertions(+), 201 deletions(-) diff --git a/src/views/iot/product/detail/ProductTopic.vue b/src/views/iot/product/detail/ProductTopic.vue index 071a5236..79ca2808 100644 --- a/src/views/iot/product/detail/ProductTopic.vue +++ b/src/views/iot/product/detail/ProductTopic.vue @@ -2,10 +2,24 @@ - + - + @@ -13,215 +27,214 @@ diff --git a/src/views/iot/product/detail/ThinkModelFunctionForm.vue b/src/views/iot/product/detail/ThinkModelFunctionForm.vue new file mode 100644 index 00000000..976ca717 --- /dev/null +++ b/src/views/iot/product/detail/ThinkModelFunctionForm.vue @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ~ + + + + + + + + + + + + + + 读写 + 只读 + + + + + + + + + 确 定 + 取 消 + + + + + diff --git a/src/views/iot/product/detail/index.vue b/src/views/iot/product/detail/index.vue index 6ff97bb8..f57fc4d6 100644 --- a/src/views/iot/product/detail/index.vue +++ b/src/views/iot/product/detail/index.vue @@ -1,18 +1,18 @@ getProductData(id)" /> - - - + + + - - + + - - + + - - + + @@ -22,6 +22,7 @@ import { DeviceApi } from '@/api/iot/device' import ProductDetailsHeader from '@/views/iot/product/detail/ProductDetailsHeader.vue' import ProductDetailsInfo from '@/views/iot/product/detail/ProductDetailsInfo.vue' import ProductTopic from '@/views/iot/product/detail/ProductTopic.vue' +import ThinkModelFunction from '@/views/iot/product/detail/ThinkModelFunction.vue' defineOptions({ name: 'IoTProductDetail' }) @@ -30,6 +31,7 @@ const message = useMessage() const id = Number(route.params.id) // 编号 const loading = ref(true) // 加载中 const product = ref({} as ProductVO) // 详情 +const activeTab = ref('info') // 默认激活的标签页 /** 获取详情 */ const getProductData = async (id: number) => { From 4b2800f723a3c39dbc8efe496ab78920120d5a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Mon, 30 Sep 2024 12:19:55 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E3=80=90=E4=BF=AE=E6=94=B9=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=91=20IOT=20=E7=89=A9=E6=A8=A1=E5=9E=8B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=9F=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/product/detail/ThinkModelFunction.vue | 6 +- .../product/detail/ThinkModelFunctionForm.vue | 106 ++++++++++++------ 2 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/views/iot/product/detail/ThinkModelFunction.vue b/src/views/iot/product/detail/ThinkModelFunction.vue index 95089c62..cc2119c5 100644 --- a/src/views/iot/product/detail/ThinkModelFunction.vue +++ b/src/views/iot/product/detail/ThinkModelFunction.vue @@ -40,7 +40,11 @@ - + + + + + diff --git a/src/views/iot/product/detail/ThinkModelFunctionForm.vue b/src/views/iot/product/detail/ThinkModelFunctionForm.vue index 976ca717..0ea2a756 100644 --- a/src/views/iot/product/detail/ThinkModelFunctionForm.vue +++ b/src/views/iot/product/detail/ThinkModelFunctionForm.vue @@ -8,35 +8,40 @@ v-loading="formLoading" > - - - + + 属性 + 服务 + 事件 + - + - - + + - - - - - - + + + + + + - + ~ @@ -46,14 +51,7 @@ - - - + @@ -61,8 +59,12 @@ 只读 - - + + @@ -96,11 +98,11 @@ const formData = ref({ identifier: undefined, name: undefined, description: undefined, - type: undefined, + type: '1', property: { identifier: undefined, name: undefined, - accessMode: undefined, + accessMode: 'rw', required: true, dataType: { type: undefined, @@ -110,13 +112,44 @@ const formData = ref({ step: undefined, unit: undefined } - } + }, + description: undefined // 添加这一行 } }) const formRules = reactive({ - name: [{ required: true, message: '功能名称不能为空', trigger: 'blur' }], + name: [ + { required: true, message: '功能名称不能为空', trigger: 'blur' }, + { + pattern: /^[\u4e00-\u9fa5a-zA-Z0-9][\u4e00-\u9fa5a-zA-Z0-9\-_/\.]{0,29}$/, + message: + '支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符', + trigger: 'blur' + } + ], type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }], - identifier: [{ required: true, message: '标识符不能为空', trigger: 'blur' }], + identifier: [ + { required: true, message: '标识符不能为空', trigger: 'blur' }, + { + pattern: /^[a-zA-Z0-9_]{1,50}$/, + message: '支持大小写字母、数字和下划线,不超过 50 个字符', + trigger: 'blur' + }, + { + validator: (rule, value, callback) => { + const reservedKeywords = ['set', 'get', 'post', 'property', 'event', 'time', 'value'] + if (reservedKeywords.includes(value)) { + callback( + new Error( + 'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义' + ) + ) + } else { + callback() + } + }, + trigger: 'blur' + } + ], property: { dataType: { type: [{ required: true, message: '数据类型不能为空', trigger: 'blur' }] @@ -172,11 +205,11 @@ const resetForm = () => { identifier: undefined, name: undefined, description: undefined, - type: undefined, + type: '1', property: { identifier: undefined, name: undefined, - accessMode: undefined, + accessMode: 'rw', required: true, dataType: { type: undefined, @@ -186,7 +219,8 @@ const resetForm = () => { step: undefined, unit: undefined } - } + }, + description: undefined // 确保重置 description 字段 } } formRef.value?.resetFields() From 6d641177b824c7c44fed9f6d005105e2d186ce4e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 1 Oct 2024 19:29:24 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91IoT=EF=BC=9A=E4=BA=A7=E5=93=81=E3=80=81?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E3=80=81=E7=89=A9=E6=A8=A1=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/device/index.ts | 28 ++++---- src/api/iot/product/index.ts | 35 ++++----- src/api/iot/thinkmodelfunction/index.ts | 26 +++---- src/views/iot/device/DeviceForm.vue | 7 +- .../iot/device/detail/DeviceDetailsHeader.vue | 1 + .../iot/device/detail/DeviceDetailsInfo.vue | 71 ++++++++----------- src/views/iot/device/index.vue | 7 +- src/views/iot/product/ProductForm.vue | 28 ++------ .../product/detail/ProductDetailsHeader.vue | 17 +++-- .../iot/product/detail/ProductDetailsInfo.vue | 6 +- src/views/iot/product/detail/ProductTopic.vue | 1 + .../iot/product/detail/ThinkModelFunction.vue | 3 +- .../product/detail/ThinkModelFunctionForm.vue | 7 +- src/views/iot/product/detail/index.vue | 7 +- src/views/iot/product/index.vue | 19 +---- 15 files changed, 114 insertions(+), 149 deletions(-) diff --git a/src/api/iot/device/index.ts b/src/api/iot/device/index.ts index 24d48789..903874b7 100644 --- a/src/api/iot/device/index.ts +++ b/src/api/iot/device/index.ts @@ -1,16 +1,16 @@ import request from '@/config/axios' -// 设备 VO +// IoT 设备 VO export interface DeviceVO { id: number // 设备 ID,主键,自增 - deviceKey: string // 设备唯一标识符,全局唯一,用于识别设备 - deviceName: string // 设备名称,在产品内唯一,用于标识设备 - productId: number // 产品 ID,关联 iot_product 表的 id - productKey: string // 产品 Key,关联 iot_product 表的 product_key - deviceType: number // 设备类型:0 - 直连设备,1 - 网关子设备,2 - 网关设备 - nickname: string // 设备备注名称,供用户自定义备注 - gatewayId: number // 网关设备 ID,子设备需要关联的网关设备 ID - status: number // 设备状态:0 - 未激活,1 - 在线,2 - 离线,3 - 已禁用 + deviceKey: string // 设备唯一标识符 + deviceName: string // 设备名称 + productId: number // 产品编号 + productKey: string // 产品标识 + deviceType: number // 设备类型 + nickname: string // 设备备注名称 + gatewayId: number // 网关设备 ID + status: number // 设备状态 statusLastUpdateTime: Date // 设备状态最后更新时间 lastOnlineTime: Date // 最后上线时间 lastOfflineTime: Date // 最后离线时间 @@ -22,17 +22,17 @@ export interface DeviceVO { mqttClientId: string // MQTT 客户端 ID mqttUsername: string // MQTT 用户名 mqttPassword: string // MQTT 密码 - authType: string // 认证类型(如一机一密、动态注册) - latitude: number // 设备位置的纬度,范围 -90.000000 ~ 90.000000 - longitude: number // 设备位置的经度,范围 -180.000000 ~ 180.000000 - areaId: number // 地区编码,符合国家地区编码标准,关联地区表 + authType: string // 认证类型 + latitude: number // 设备位置的纬度 + longitude: number // 设备位置的经度 + areaId: number // 地区编码 address: string // 设备详细地址 serialNumber: string // 设备序列号 } export interface DeviceUpdateStatusVO { id: number // 设备 ID,主键,自增 - status: number // 设备状态:0 - 未激活,1 - 在线,2 - 离线,3 - 已禁用 + status: number // 设备状态 } // 设备 API diff --git a/src/api/iot/product/index.ts b/src/api/iot/product/index.ts index 2a8d430f..1ffa490d 100644 --- a/src/api/iot/product/index.ts +++ b/src/api/iot/product/index.ts @@ -1,50 +1,51 @@ import request from '@/config/axios' -// iot 产品 VO +// IoT 产品 VO export interface ProductVO { + id: number // 产品编号 name: string // 产品名称 - id: number // 产品ID productKey: string // 产品标识 - protocolId: number // 协议编号(脚本解析 id) + protocolId: number // 协议编号 categoryId: number // 产品所属品类标识符 description: string // 产品描述 - validateType: number // 数据校验级别, 0: 强校验, 1: 弱校验, 2: 免校验 - status: number // 产品状态, 0: DEVELOPMENT_STATUS, 1: RELEASE_STATUS - deviceType: number // 设备类型, 0: 直连设备, 1: 网关子设备, 2: 网关设备 - netType: number // 联网方式, 0: Wi-Fi, 1: Cellular, 2: Ethernet, 3: 其他 - protocolType: number // 接入网关协议, 0: modbus, 1: opc-ua, 2: customize, 3: ble, 4: zigbee - dataFormat: number // 数据格式, 0: 透传模式, 1: Alink JSON + validateType: number // 数据校验级别 + status: number // 产品状态 + deviceType: number // 设备类型 + netType: number // 联网方式 + protocolType: number // 接入网关协议 + dataFormat: number // 数据格式 deviceCount: number // 设备数量 + createTime: Date // 创建时间 } -// iot 产品 API +// IoT 产品 API export const ProductApi = { - // 查询iot 产品分页 + // 查询产品分页 getProductPage: async (params: any) => { return await request.get({ url: `/iot/product/page`, params }) }, - // 查询iot 产品详情 + // 查询产品详情 getProduct: async (id: number) => { return await request.get({ url: `/iot/product/get?id=` + id }) }, - // 新增iot 产品 + // 新增产品 createProduct: async (data: ProductVO) => { return await request.post({ url: `/iot/product/create`, data }) }, - // 修改iot 产品 + // 修改产品 updateProduct: async (data: ProductVO) => { return await request.put({ url: `/iot/product/update`, data }) }, - // 删除iot 产品 + // 删除产品 deleteProduct: async (id: number) => { return await request.delete({ url: `/iot/product/delete?id=` + id }) }, - // 导出iot 产品 Excel + // 导出产品 Excel exportProduct: async (params) => { return await request.download({ url: `/iot/product/export-excel`, params }) }, @@ -54,7 +55,7 @@ export const ProductApi = { return await request.put({ url: `/iot/product/update-status?id=` + id + `&status=` + status }) }, - // 查询产品(精简)列表 + // 查询产品(精简)列表 getSimpleProductList() { return request.get({ url: '/iot/product/list-all-simple' }) } diff --git a/src/api/iot/thinkmodelfunction/index.ts b/src/api/iot/thinkmodelfunction/index.ts index 29f06a78..bd2e2d0f 100644 --- a/src/api/iot/thinkmodelfunction/index.ts +++ b/src/api/iot/thinkmodelfunction/index.ts @@ -6,21 +6,21 @@ export interface ThinkModelFunctionVO { identifier: string // 功能标识 name: string // 功能名称 description: string // 功能描述 - productId: number // 产品ID(关联 IotProductDO 的 id) - productKey: string // 产品Key(关联 IotProductDO 的 productKey) - type: number // 功能类型(1 - 属性,2 - 服务,3 - 事件) - property: string // 属性(存储 ThingModelProperty 的 JSON 数据) - event: string // 事件(存储 ThingModelEvent 的 JSON 数据) - service: string // 服务(存储服务的 JSON 数据) + productId: number // 产品编号 + productKey: string // 产品标识 + type: number // 功能类型 + property: string // 属性 + event: string // 事件 + service: string // 服务 } // IoT 产品物模型 API export const ThinkModelFunctionApi = { - // 查询IoT 产品物模型分页 + // 查询产品物模型分页 getThinkModelFunctionPage: async (params: any) => { return await request.get({ url: `/iot/think-model-function/page`, params }) }, - // 获得IoT 产品物模型 + // 获得产品物模型 getThinkModelFunctionListByProductId: async (params: any) => { return await request.get({ url: `/iot/think-model-function/list-by-product-id`, @@ -28,27 +28,27 @@ export const ThinkModelFunctionApi = { }) }, - // 查询IoT 产品物模型详情 + // 查询产品物模型详情 getThinkModelFunction: async (id: number) => { return await request.get({ url: `/iot/think-model-function/get?id=` + id }) }, - // 新增IoT 产品物模型 + // 新增产品物模型 createThinkModelFunction: async (data: ThinkModelFunctionVO) => { return await request.post({ url: `/iot/think-model-function/create`, data }) }, - // 修改IoT 产品物模型 + // 修改产品物模型 updateThinkModelFunction: async (data: ThinkModelFunctionVO) => { return await request.put({ url: `/iot/think-model-function/update`, data }) }, - // 删除IoT 产品物模型 + // 删除产品物模型 deleteThinkModelFunction: async (id: number) => { return await request.delete({ url: `/iot/think-model-function/delete?id=` + id }) }, - // 导出IoT 产品物模型 Excel + // 导出产品物模型 Excel exportThinkModelFunction: async (params) => { return await request.download({ url: `/iot/think-model-function/export-excel`, params }) } diff --git a/src/views/iot/device/DeviceForm.vue b/src/views/iot/device/DeviceForm.vue index f926b6d4..cb026012 100644 --- a/src/views/iot/device/DeviceForm.vue +++ b/src/views/iot/device/DeviceForm.vue @@ -57,8 +57,7 @@ const formData = ref({ id: undefined, productId: undefined, deviceName: undefined, - nickname: undefined, - serialNumber: undefined + nickname: undefined }) const formRules = reactive({ productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }], @@ -66,7 +65,7 @@ const formRules = reactive({ { pattern: /^[a-zA-Z0-9_.\-:@]{4,32}$/, message: - '支持英文字母、数字、下划线(_)、中划线(-)、点号(.)、半角冒号(:)和特殊字符@,长度限制为4~32个字符', + '支持英文字母、数字、下划线(_)、中划线(-)、点号(.)、半角冒号(:)和特殊字符@,长度限制为 4~32 个字符', trigger: 'blur' } ], @@ -79,7 +78,7 @@ const formRules = reactive({ } const length = value.replace(/[\u4e00-\u9fa5\u3040-\u30ff]/g, 'aa').length if (length < 4 || length > 64) { - callback(new Error('备注名称长度限制为4~64个字符,中文及日文算2个字符')) + callback(new Error('备注名称长度限制为 4~64 个字符,中文及日文算 2 个字符')) } else if (!/^[\u4e00-\u9fa5\u3040-\u30ff_a-zA-Z0-9]+$/.test(value)) { callback(new Error('备注名称只能包含中文、英文字母、日文、数字和下划线(_)')) } else { diff --git a/src/views/iot/device/detail/DeviceDetailsHeader.vue b/src/views/iot/device/detail/DeviceDetailsHeader.vue index 4360cab1..62960529 100644 --- a/src/views/iot/device/detail/DeviceDetailsHeader.vue +++ b/src/views/iot/device/detail/DeviceDetailsHeader.vue @@ -59,6 +59,7 @@ const emit = defineEmits(['refresh']) * @param text 需要复制的文本 */ const copyToClipboard = (text: string) => { + // TODO @haohao:可以考虑用 await 异步转同步哈 navigator.clipboard.writeText(text).then(() => { message.success('复制成功') }) diff --git a/src/views/iot/device/detail/DeviceDetailsInfo.vue b/src/views/iot/device/detail/DeviceDetailsInfo.vue index 77084625..59b9d254 100644 --- a/src/views/iot/device/detail/DeviceDetailsInfo.vue +++ b/src/views/iot/device/detail/DeviceDetailsInfo.vue @@ -3,33 +3,33 @@ {{ product.name }} - {{ product.productKey }} + + {{ product.productKey }} 复制 - {{ device.deviceName }} + + {{ device.deviceName }} 复制 {{ device.nickname }} - {{ - formatDate(device.createTime) - }} - {{ - formatDate(device.activeTime) - }} - {{ - formatDate(device.lastOnlineTime) - }} + + {{ formatDate(device.createTime) }} + + + {{ formatDate(device.activeTime) }} + + + {{ formatDate(device.lastOnlineTime) }} + - {{ - formatDate(device.lastOfflineTime) - }} + + {{ formatDate(device.lastOfflineTime) }} + 查看 @@ -53,7 +53,6 @@ - @@ -63,7 +62,6 @@ - @@ -87,39 +85,28 @@ import { ProductVO } from '@/api/iot/product' import { formatDate } from '@/utils/formatTime' import { DeviceVO } from '@/api/iot/device' -// 消息提示 -const message = useMessage() +const message = useMessage() // 消息提示 -// 路由实例 -const router = useRouter() +const { product, device } = defineProps<{ product: ProductVO; device: DeviceVO }>() // 定义 Props -// 定义 Props -const { product, device } = defineProps<{ product: ProductVO; device: DeviceVO }>() +const emit = defineEmits(['refresh']) // 定义 Emits -// 定义 Emits -const emit = defineEmits(['refresh']) +const activeNames = ref(['basicInfo']) // 展示的折叠面板 +const mqttDialogVisible = ref(false) // 定义 MQTT 弹框的可见性 +const mqttParams = ref({ + mqttClientId: '', + mqttUsername: '', + mqttPassword: '' +}) // 定义 MQTT 参数对象 -// 展示的折叠面板 -const activeNames = ref(['basicInfo']) - -// 复制到剪贴板方法 +/** 复制到剪贴板方法 */ const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text).then(() => { message.success('复制成功') }) } -// 定义 MQTT 弹框的可见性 -const mqttDialogVisible = ref(false) - -// 定义 MQTT 参数对象 -const mqttParams = ref({ - mqttClientId: '', - mqttUsername: '', - mqttPassword: '' -}) - -// 打开 MQTT 参数弹框的方法 +/** 打开 MQTT 参数弹框的方法 */ const openMqttParams = () => { mqttParams.value = { mqttClientId: device.mqttClientId || 'N/A', @@ -129,7 +116,7 @@ const openMqttParams = () => { mqttDialogVisible.value = true } -// 关闭 MQTT 弹框的方法 +/** 关闭 MQTT 弹框的方法 */ const handleCloseMqttDialog = () => { mqttDialogVisible.value = false } diff --git a/src/views/iot/device/index.vue b/src/views/iot/device/index.vue index 566c78a6..784b1482 100644 --- a/src/views/iot/device/index.vue +++ b/src/views/iot/device/index.vue @@ -192,9 +192,8 @@ const queryParams = reactive({ status: undefined }) const queryFormRef = ref() // 搜索的表单 -const exportLoading = ref(false) // 导出的加载中 -/** 产品ID到名称的映射 */ +/** 产品标号和名称的映射 */ const productMap = reactive({}) /** 查询列表 */ @@ -207,6 +206,7 @@ const getList = async () => { // 获取产品ID列表 const productIds = [...new Set(data.list.map((device) => device.productId))] // 获取产品名称 + // TODO @haohao:最好后端拼接哈 const products = await Promise.all(productIds.map((id) => ProductApi.getProduct(id))) products.forEach((product) => { productMap[product.id] = product.name @@ -235,7 +235,7 @@ const openForm = (type: string, id?: number) => { } /** 打开详情 */ -const { currentRoute, push } = useRouter() +const { push } = useRouter() const openDetail = (id: number) => { push({ name: 'IoTDeviceDetail', params: { id } }) } @@ -252,6 +252,7 @@ const handleDelete = async (id: number) => { await getList() } catch {} } + /** 查询字典下拉列表 */ const products = ref() const getProducts = async () => { diff --git a/src/views/iot/product/ProductForm.vue b/src/views/iot/product/ProductForm.vue index 7d254a03..75d6efcc 100644 --- a/src/views/iot/product/ProductForm.vue +++ b/src/views/iot/product/ProductForm.vue @@ -128,15 +128,10 @@ const formData = ref({ }) const formRules = reactive({ name: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }], - deviceType: [ - { - required: true, - message: '设备类型不能为空', - trigger: 'change' - } - ], + deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'change' }], netType: [ { + // TODO @haohao:0、1、/2 最好前端也枚举下;另外,这里的 required 可以直接设置为 true。然后表单那些 v-if。只要不存在,它自动就不校验了哈 required: formData.deviceType === 0 || formData.deviceType === 2, message: '联网方式不能为空', trigger: 'change' @@ -145,23 +140,12 @@ const formRules = reactive({ protocolType: [ { required: formData.deviceType === 1, message: '接入网关协议不能为空', trigger: 'change' } ], - dataFormat: [ - { - required: true, - message: '数据格式不能为空', - trigger: 'change' - } - ], - validateType: [ - { - required: true, - message: '数据校验级别不能为空', - trigger: 'change' - } - ] + dataFormat: [{ required: true, message: '数据格式不能为空', trigger: 'change' }], + validateType: [{ required: true, message: '数据校验级别不能为空', trigger: 'change' }] }) const formRef = ref() +/** 打开弹窗 */ const open = async (type: string, id?: number) => { dialogVisible.value = true dialogTitle.value = t('action.' + type) @@ -178,6 +162,7 @@ const open = async (type: string, id?: number) => { } defineExpose({ open, close: () => (dialogVisible.value = false) }) +/** 提交表单 */ const emit = defineEmits(['success']) const submitForm = async () => { await formRef.value.validate() @@ -198,6 +183,7 @@ const submitForm = async () => { } } +/** 重置表单 */ const resetForm = () => { formData.value = { name: undefined, diff --git a/src/views/iot/product/detail/ProductDetailsHeader.vue b/src/views/iot/product/detail/ProductDetailsHeader.vue index db0849ed..ba009516 100644 --- a/src/views/iot/product/detail/ProductDetailsHeader.vue +++ b/src/views/iot/product/detail/ProductDetailsHeader.vue @@ -58,19 +58,24 @@ import ProductForm from '@/views/iot/product/ProductForm.vue' import { ProductApi, ProductVO } from '@/api/iot/product' const message = useMessage() + +const { product } = defineProps<{ product: ProductVO }>() // 定义 Props + +/** 处理复制 */ const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text).then(() => { message.success('复制成功') }) } -// 路由跳转到设备管理 -const { currentRoute, push } = useRouter() +/** 路由跳转到设备管理 */ +const { push } = useRouter() const goToManagement = (productId: string) => { push({ name: 'IoTDevice', query: { productId } }) } -// 操作修改 +/** 操作修改 */ +const emit = defineEmits(['refresh']) // 定义 Emits const formRef = ref() const openForm = (type: string, id?: number) => { formRef.value.open(type, id) @@ -95,10 +100,4 @@ const confirmUnpublish = async (id: number) => { message.error('撤销发布失败') } } - -// 定义 Props -const { product } = defineProps<{ product: ProductVO }>() - -// 定义 Emits -const emit = defineEmits(['refresh']) diff --git a/src/views/iot/product/detail/ProductDetailsInfo.vue b/src/views/iot/product/detail/ProductDetailsInfo.vue index e3b5483a..5153045e 100644 --- a/src/views/iot/product/detail/ProductDetailsInfo.vue +++ b/src/views/iot/product/detail/ProductDetailsInfo.vue @@ -6,9 +6,9 @@ - {{ - formatDate(product.createTime) - }} + + {{ formatDate(product.createTime) }} + diff --git a/src/views/iot/product/detail/ProductTopic.vue b/src/views/iot/product/detail/ProductTopic.vue index 79ca2808..c327bb65 100644 --- a/src/views/iot/product/detail/ProductTopic.vue +++ b/src/views/iot/product/detail/ProductTopic.vue @@ -44,6 +44,7 @@ const columns2 = reactive([ { label: '描述', field: 'description' } ]) +// TODO @haohao:这个,有没可能写到一个枚举里,方便后续维护? /Users/yunai/Java/yudao-ui-admin-vue3/src/views/ai/utils/constants.ts const data1 = computed(() => { if (!props.product || !props.product.productKey) return [] return [ diff --git a/src/views/iot/product/detail/ThinkModelFunction.vue b/src/views/iot/product/detail/ThinkModelFunction.vue index cc2119c5..5a75d109 100644 --- a/src/views/iot/product/detail/ThinkModelFunction.vue +++ b/src/views/iot/product/detail/ThinkModelFunction.vue @@ -88,6 +88,7 @@ import ThinkModelFunctionForm from '@/views/iot/product/detail/ThinkModelFunctio const props = defineProps<{ product: ProductVO }>() +const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 const loading = ref(true) // 列表的加载中 @@ -97,7 +98,7 @@ const queryParams = reactive({ pageNo: 1, pageSize: 10, type: undefined, - productId: undefined + productId: -1 }) const queryFormRef = ref() // 搜索的表单 diff --git a/src/views/iot/product/detail/ThinkModelFunctionForm.vue b/src/views/iot/product/detail/ThinkModelFunctionForm.vue index 0ea2a756..895692e0 100644 --- a/src/views/iot/product/detail/ThinkModelFunctionForm.vue +++ b/src/views/iot/product/detail/ThinkModelFunctionForm.vue @@ -46,7 +46,6 @@ ~ - @@ -77,7 +76,6 @@