Browse Source

【优化】 IOT 设备管理

安浩浩 11 tháng trước cách đây
mục cha
commit
63a0e5dc3d

+ 19 - 9
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 })
   }
-}
+}

+ 39 - 3
src/views/iot/device/DeviceForm.vue

@@ -8,7 +8,12 @@
       v-loading="formLoading"
     >
       <el-form-item label="产品" prop="productId">
-        <el-select v-model="formData.productId" placeholder="请选择产品" clearable>
+        <el-select
+          v-model="formData.productId"
+          placeholder="请选择产品"
+          :disabled="formType === 'update'"
+          clearable
+        >
           <el-option
             v-for="product in products"
             :key="product.id"
@@ -18,7 +23,11 @@
         </el-select>
       </el-form-item>
       <el-form-item label="DeviceName" prop="deviceName">
-        <el-input v-model="formData.deviceName" placeholder="请输入 DeviceName" />
+        <el-input
+          v-model="formData.deviceName"
+          placeholder="请输入 DeviceName"
+          :disabled="formType === 'update'"
+        />
       </el-form-item>
       <el-form-item label="备注名称" prop="nickname">
         <el-input v-model="formData.nickname" placeholder="请输入备注名称" />
@@ -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
 

+ 37 - 49
src/views/iot/device/index.vue

@@ -72,24 +72,22 @@
         </el-select>
       </el-form-item>
       <el-form-item>
-        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" />
+          重置
+        </el-button>
         <el-button
           type="primary"
           plain
           @click="openForm('create')"
           v-hasPermi="['iot:device:create']"
         >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['iot:device:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
+          <Icon icon="ep:plus" class="mr-5px" />
+          新增
         </el-button>
       </el-form-item>
     </el-form>
@@ -100,9 +98,21 @@
     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
       <el-table-column label="DeviceName" align="center" prop="deviceName" />
       <el-table-column label="备注名称" align="center" prop="nickname" />
-      <el-table-column label="设备所属产品" align="center" prop="productName" />
-      <el-table-column label="设备类型" align="center" prop="deviceType" />
-      <el-table-column label="设备状态" align="center" prop="status" />
+      <el-table-column label="设备所属产品" align="center" prop="productId">
+        <template #default="scope">
+          {{ productMap[scope.row.productId] }}
+        </template>
+      </el-table-column>
+      <el-table-column label="设备类型" align="center" prop="deviceType">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE" :value="scope.row.deviceType" />
+        </template>
+      </el-table-column>
+      <el-table-column label="设备状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.IOT_DEVICE_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
       <el-table-column
         label="最后上线时间"
         align="center"
@@ -110,18 +120,6 @@
         :formatter="dateFormatter"
         width="180px"
       />
-      <el-table-column label="启用禁用">
-        <template #default="scope">
-          <el-switch
-            v-model="scope.row.status"
-            active-value="1"
-            inactive-value="0"
-            active-text="启用"
-            inactive-text="禁用"
-            @change="handleUpdate(scope.row)"
-          />
-        </template>
-      </el-table-column>
       <el-table-column label="操作" align="center" min-width="120px">
         <template #default="scope">
           <el-button
@@ -160,7 +158,7 @@
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
-import { DeviceApi, DeviceVO } from '@/api/iot/device'
+import { DeviceApi, DeviceUpdateStatusVO, DeviceVO } from '@/api/iot/device'
 import DeviceForm from './DeviceForm.vue'
 import { ProductApi } from '@/api/iot/product'
 
@@ -176,35 +174,18 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deviceKey: undefined,
   deviceName: undefined,
   productId: undefined,
-  productKey: undefined,
   deviceType: undefined,
   nickname: undefined,
-  gatewayId: undefined,
-  status: undefined,
-  statusLastUpdateTime: [],
-  lastOnlineTime: [],
-  lastOfflineTime: [],
-  activeTime: [],
-  ip: undefined,
-  firmwareVersion: undefined,
-  deviceSecret: undefined,
-  mqttClientId: undefined,
-  mqttUsername: undefined,
-  mqttPassword: undefined,
-  authType: undefined,
-  latitude: undefined,
-  longitude: undefined,
-  areaId: undefined,
-  address: undefined,
-  serialNumber: undefined,
-  createTime: []
+  status: undefined
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
+/** 产品ID到名称的映射 */
+const productMap = reactive({})
+
 /** 查询列表 */
 const getList = async () => {
   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
   }