From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目
---
src/views/iot/device/device/DeviceForm.vue | 325 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 325 insertions(+), 0 deletions(-)
diff --git a/src/views/iot/device/device/DeviceForm.vue b/src/views/iot/device/device/DeviceForm.vue
new file mode 100644
index 0000000..dfed0c6
--- /dev/null
+++ b/src/views/iot/device/device/DeviceForm.vue
@@ -0,0 +1,325 @@
+<template>
+ <Dialog :title="dialogTitle" v-model="dialogVisible">
+ <el-form
+ ref="formRef"
+ :model="formData"
+ :rules="formRules"
+ label-width="100px"
+ v-loading="formLoading"
+ >
+ <el-form-item label="浜у搧" prop="productId">
+ <el-select
+ v-model="formData.productId"
+ placeholder="璇烽�夋嫨浜у搧"
+ :disabled="formType === 'update'"
+ clearable
+ @change="handleProductChange"
+ >
+ <el-option
+ v-for="product in products"
+ :key="product.id"
+ :label="product.name"
+ :value="product.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="DeviceName" prop="deviceName">
+ <el-input
+ v-model="formData.deviceName"
+ placeholder="璇疯緭鍏� DeviceName"
+ :disabled="formType === 'update'"
+ />
+ </el-form-item>
+ <el-form-item
+ v-if="formData.deviceType === DeviceTypeEnum.GATEWAY_SUB"
+ label="缃戝叧璁惧"
+ prop="gatewayId"
+ >
+ <el-select v-model="formData.gatewayId" placeholder="瀛愯澶囧彲閫夋嫨鐖惰澶�" clearable>
+ <el-option
+ v-for="gateway in gatewayDevices"
+ :key="gateway.id"
+ :label="gateway.nickname || gateway.deviceName"
+ :value="gateway.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-collapse>
+ <el-collapse-item title="鏇村閰嶇疆">
+ <el-form-item label="澶囨敞鍚嶇О" prop="nickname">
+ <el-input v-model="formData.nickname" placeholder="璇疯緭鍏ュ娉ㄥ悕绉�" />
+ </el-form-item>
+ <el-form-item label="璁惧鍥剧墖" prop="picUrl">
+ <UploadImg v-model="formData.picUrl" :height="'120px'" :width="'120px'" />
+ </el-form-item>
+ <el-form-item label="璁惧鍒嗙粍" prop="groupIds">
+ <el-select v-model="formData.groupIds" placeholder="璇烽�夋嫨璁惧鍒嗙粍" multiple clearable>
+ <el-option
+ v-for="group in deviceGroups"
+ :key="group.id"
+ :label="group.name"
+ :value="group.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁惧搴忓垪鍙�" prop="serialNumber">
+ <el-input v-model="formData.serialNumber" placeholder="璇疯緭鍏ヨ澶囧簭鍒楀彿" />
+ </el-form-item>
+ <el-form-item label="瀹氫綅绫诲瀷" prop="locationType">
+ <el-radio-group v-model="formData.locationType">
+ <el-radio
+ v-for="dict in getIntDictOptions(DICT_TYPE.IOT_LOCATION_TYPE)"
+ :key="dict.value"
+ :label="dict.value"
+ >
+ {{ dict.label }}
+ </el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <!-- LocationTypeEnum.MANUAL锛氭墜鍔ㄥ畾浣� -->
+ <template v-if="LocationTypeEnum.MANUAL === formData.locationType">
+ <el-form-item label="璁惧缁忓害" prop="longitude" type="number">
+ <el-input
+ v-model="formData.longitude"
+ placeholder="璇疯緭鍏ヨ澶囩粡搴�"
+ @blur="updateLocationFromCoordinates"
+ />
+ </el-form-item>
+ <el-form-item label="璁惧缁村害" prop="latitude" type="number">
+ <el-input
+ v-model="formData.latitude"
+ placeholder="璇疯緭鍏ヨ澶囩淮搴�"
+ @blur="updateLocationFromCoordinates"
+ />
+ </el-form-item>
+ <div class="pl-0 h-[400px] w-full ml-[-18px]" v-if="showMap">
+ <Map
+ :isWrite="true"
+ :clickMap="true"
+ :center="formData.location"
+ @locate-change="handleLocationChange"
+ ref="mapRef"
+ class="h-full w-full"
+ />
+ </div>
+ </template>
+ </el-collapse-item>
+ </el-collapse>
+ </el-form>
+ <template #footer>
+ <el-button @click="submitForm" type="primary" :disabled="formLoading">纭� 瀹�</el-button>
+ <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+ </template>
+ </Dialog>
+</template>
+<script setup lang="ts">
+import { DeviceApi, DeviceVO } from '@/api/iot/device/device'
+import { DeviceGroupApi } from '@/api/iot/device/group'
+import { DeviceTypeEnum, LocationTypeEnum, ProductApi, ProductVO } from '@/api/iot/product/product'
+import { UploadImg } from '@/components/UploadFile'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import Map from '@/components/Map/index.vue'
+import { ref } from 'vue'
+
+/** IoT 璁惧琛ㄥ崟 */
+defineOptions({ name: 'IoTDeviceForm' })
+
+const { t } = useI18n() // 鍥介檯鍖�
+const message = useMessage() // 娑堟伅绐�
+
+const dialogVisible = ref(false) // 寮圭獥鐨勬槸鍚﹀睍绀�
+const dialogTitle = ref('') // 寮圭獥鐨勬爣棰�
+const formLoading = ref(false) // 琛ㄥ崟鐨勫姞杞戒腑锛�1锛変慨鏀规椂鐨勬暟鎹姞杞斤紱2锛夋彁浜ょ殑鎸夐挳绂佺敤
+const formType = ref('') // 琛ㄥ崟鐨勭被鍨嬶細create - 鏂板锛泆pdate - 淇敼
+const showMap = ref(false) // 鏄惁鏄剧ず鍦板浘缁勪欢
+const mapRef = ref(null)
+
+const formData = ref({
+ id: undefined,
+ productId: undefined,
+ deviceName: undefined,
+ nickname: undefined,
+ picUrl: undefined,
+ gatewayId: undefined,
+ deviceType: undefined as number | undefined,
+ serialNumber: undefined,
+ locationType: undefined as number | undefined,
+ longitude: undefined,
+ latitude: undefined,
+ location: '', // 鏍煎紡: "缁忓害,绾害"
+ groupIds: [] as number[]
+})
+
+/** 鐩戝惉缁忕含搴﹀彉鍖栵紝鏇存柊location */
+watch([() => formData.value.longitude, () => formData.value.latitude], ([newLong, newLat]) => {
+ if (newLong && newLat) {
+ formData.value.location = `${newLong},${newLat}`
+ // 鏈変簡缁忕含搴︽暟鎹悗鏄剧ず鍦板浘
+ showMap.value = true
+ }
+})
+
+const formRules = reactive({
+ productId: [{ required: true, message: '浜у搧涓嶈兘涓虹┖', trigger: 'blur' }],
+ deviceName: [
+ { required: true, message: 'DeviceName 涓嶈兘涓虹┖', trigger: 'blur' },
+ {
+ pattern: /^[a-zA-Z0-9_.\-:@]{4,32}$/,
+ message:
+ '鏀寔鑻辨枃瀛楁瘝銆佹暟瀛椼�佷笅鍒掔嚎锛坃锛夈�佷腑鍒掔嚎锛�-锛夈�佺偣鍙凤紙.锛夈�佸崐瑙掑啋鍙凤紙:锛夊拰鐗规畩瀛楃@锛岄暱搴﹂檺鍒朵负 4~32 涓瓧绗�',
+ trigger: 'blur'
+ }
+ ],
+ nickname: [
+ {
+ validator: (_rule, value: any, 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'
+ }
+ ],
+ serialNumber: [
+ {
+ pattern: /^[a-zA-Z0-9-_]+$/,
+ message: '搴忓垪鍙峰彧鑳藉寘鍚瓧姣嶃�佹暟瀛椼�佷腑鍒掔嚎鍜屼笅鍒掔嚎',
+ trigger: 'blur'
+ }
+ ]
+})
+const formRef = ref() // 琛ㄥ崟 Ref
+const products = ref<ProductVO[]>([]) // 浜у搧鍒楄〃
+const gatewayDevices = ref<DeviceVO[]>([]) // 缃戝叧璁惧鍒楄〃
+const deviceGroups = ref<any[]>([])
+
+/** 鎵撳紑寮圭獥 */
+const open = async (type: string, id?: number) => {
+ dialogVisible.value = true
+ dialogTitle.value = t('action.' + type)
+ formType.value = type
+ resetForm()
+
+ // 榛樿涓嶆樉绀哄湴鍥撅紝绛夊緟鏁版嵁鍔犺浇
+ showMap.value = false
+
+ // 淇敼鏃讹紝璁剧疆鏁版嵁
+ if (id) {
+ formLoading.value = true
+ try {
+ formData.value = await DeviceApi.getDevice(id)
+
+ // 濡傛灉鏈夌粡绾害锛岃缃� location 瀛楁鐢ㄤ簬鍦板浘鏄剧ず
+ if (formData.value.longitude && formData.value.latitude) {
+ formData.value.location = `${formData.value.longitude},${formData.value.latitude}`
+ }
+ } finally {
+ formLoading.value = false
+ }
+ }
+ // 濡傛灉鏈夌粡绾俊鎭紝鍒欐暟鎹姞杞藉畬鎴愬悗锛屾樉绀哄湴鍥�
+ showMap.value = true
+
+ // 鍔犺浇缃戝叧璁惧鍒楄〃
+ gatewayDevices.value = await DeviceApi.getSimpleDeviceList(DeviceTypeEnum.GATEWAY)
+ // 鍔犺浇浜у搧鍒楄〃
+ products.value = await ProductApi.getSimpleProductList()
+ // 鍔犺浇璁惧鍒嗙粍鍒楄〃
+ deviceGroups.value = await DeviceGroupApi.getSimpleDeviceGroupList()
+}
+defineExpose({ open }) // 鎻愪緵 open 鏂规硶锛岀敤浜庢墦寮�寮圭獥
+
+/** 鎻愪氦琛ㄥ崟 */
+const emit = defineEmits(['success']) // 瀹氫箟 success 浜嬩欢锛岀敤浜庢搷浣滄垚鍔熷悗鐨勫洖璋�
+const submitForm = async () => {
+ // 鏍¢獙琛ㄥ崟
+ await formRef.value.validate()
+ // 鎻愪氦璇锋眰
+ formLoading.value = true
+ try {
+ const data = formData.value as unknown as DeviceVO
+ // 濡傛灉闈炴墜鍔ㄥ畾浣嶏紝涓嶈繘琛屾彁浜よ瀛楁
+ if (data.locationType !== LocationTypeEnum.MANUAL) {
+ data.longitude = undefined
+ data.latitude = undefined
+ }
+ // TODO @瀹楄秴锛氥�愯澶囧畾浣嶃�慳ddress 鍜� areaId 涔熻澶勭悊锛�
+ // 1. 鎵嬪姩瀹氫綅鏃讹細longitude + latitude + areaId + address锛氳绋嶅井娉ㄦ剰锛宎ddress 鍙兘瑕佸幓鎺夌渷甯傚尯閮ㄥ垎锛燂紒
+ // 2. IP 瀹氫綅鏃讹細IotDeviceMessage 鐨� buildStateUpdateOnline 鏃讹紝澧炲姞 ip 瀛楁銆傝繖鏍凤紝瑙f瀽鍒� areaId锛涘彟澶栫湅鐪嬭兘涓嶈兘閫氳繃 https://lbsyun.baidu.com/faq/api?title=webapi/ip-api-base锛堝彧鑾峰彇 location 灏� ok 鍟︼級
+ // 3. 璁惧瀹氫綅鏃讹細闂棶 haohao锛屼竴鑸�庝箞鍋氥��
+
+ if (formType.value === 'create') {
+ await DeviceApi.createDevice(data)
+ message.success(t('common.createSuccess'))
+ } else {
+ await DeviceApi.updateDevice(data)
+ message.success(t('common.updateSuccess'))
+ }
+ dialogVisible.value = false
+ // 鍙戦�佹搷浣滄垚鍔熺殑浜嬩欢
+ emit('success')
+ } finally {
+ formLoading.value = false
+ }
+}
+
+/** 閲嶇疆琛ㄥ崟 */
+const resetForm = () => {
+ formData.value = {
+ id: undefined,
+ productId: undefined,
+ deviceName: undefined,
+ nickname: undefined,
+ picUrl: undefined,
+ gatewayId: undefined,
+ deviceType: undefined,
+ serialNumber: undefined,
+ locationType: undefined,
+ longitude: undefined,
+ latitude: undefined,
+ // TODO @瀹楄秴锛氥�愯澶囧畾浣嶃�憀ocation 鏄笉鏄嬁鍑烘潵锛屼笉鏀惧湪 formData 閲�
+ location: '',
+ groupIds: []
+ }
+ formRef.value?.resetFields()
+ // 閲嶇疆琛ㄥ崟鏃讹紝闅愯棌鍦板浘
+ showMap.value = false
+}
+
+/** 浜у搧閫夋嫨鍙樺寲 */
+const handleProductChange = (productId: number) => {
+ if (!productId) {
+ formData.value.deviceType = undefined
+ return
+ }
+ const product = products.value?.find((item) => item.id === productId)
+ formData.value.deviceType = product?.deviceType
+ formData.value.locationType = product?.locationType
+}
+
+/** 澶勭悊浣嶇疆鍙樺寲 */
+const handleLocationChange = (lnglat) => {
+ formData.value.longitude = lnglat[0]
+ formData.value.latitude = lnglat[1]
+}
+
+/** 鏍规嵁缁忕含搴︽洿鏂板湴鍥句綅缃� */
+const updateLocationFromCoordinates = () => {
+ // 楠岃瘉缁忕含搴︽槸鍚︽湁鏁�
+ if (formData.value.longitude && formData.value.latitude) {
+ // 鏇存柊 location 瀛楁锛屽湴鍥剧粍浠朵細鏍规嵁姝ゅ瓧娈垫洿鏂�
+ formData.value.location = `${formData.value.longitude},${formData.value.latitude}`
+ mapRef.value.regeoCode(formData.value.location)
+ }
+}
+</script>
--
Gitblit v1.8.0