feat(iot):【设备订单:50%】简化设备定位功能,支持 GeoLocation 自动更新,基于 calm-roaming-pillow.md

This commit is contained in:
YunaiV
2026-01-20 21:41:56 +08:00
parent 3821b32b03
commit 3620278360
7 changed files with 47 additions and 96 deletions

View File

@@ -21,7 +21,6 @@ export interface DeviceVO {
mqttUsername: string // MQTT 用户名
mqttPassword: string // MQTT 密码
authType: string // 认证类型
locationType: number // 定位类型
latitude?: number // 设备位置的纬度
longitude?: number // 设备位置的经度
areaId: number // 地区编码
@@ -49,7 +48,7 @@ export interface IotDevicePropertyRespVO {
updateTime: Date // 更新时间
}
// TODO @芋艿:调整到 constants
// TODO @AI:调整到 constants
// IoT 设备状态枚举
export enum DeviceStateEnum {
INACTIVE = 0, // 未激活

View File

@@ -13,7 +13,6 @@ export interface ProductVO {
description: string // 产品描述
status: number // 产品状态
deviceType: number // 设备类型
locationType: number // 设备类型
netType: number // 联网方式
codecType: string // 数据格式(编解码器类型)
deviceCount: number // 设备数量
@@ -26,12 +25,6 @@ export enum DeviceTypeEnum {
GATEWAY_SUB = 1, // 网关子设备
GATEWAY = 2 // 网关设备
}
// IOT 产品定位类型枚举类 0: 手动定位, 1: IP 定位, 2: 定位模块定位
export enum LocationTypeEnum {
IP = 1, // IP 定位
MODULE = 2, // 设备定位
MANUAL = 3 // 手动定位
}
// IOT 数据格式(编解码器类型)枚举类
export enum CodecTypeEnum {
ALINK = 'Alink' // 阿里云 Alink 协议

View File

@@ -1,5 +1,5 @@
<!-- 地图组件基于百度地图GL实现 -->
<!-- TODO @super还存在两个没解决的小bug,一个是修改手动定位时一次加载 不知道为何定位点在地图左上角 调了半天没解决 第二个是检索地址确定定位的功能参照百度的文档没也搞好 回头再解决一下 -->
<!-- 地图组件基于百度地图 GL 实现 -->
<!-- TODO @AI还存在两个没解决的小bug,一个是修改手动定位时一次加载 不知道为何定位点在地图左上角 调了半天没解决 第二个是检索地址确定定位的功能参照百度的文档没也搞好 回头再解决一下 -->
<template>
<div v-if="props.isWrite">
<el-form ref="form" label-width="120px">
@@ -73,6 +73,7 @@ const props = defineProps({
watch(
() => props.center,
// TODO @AI这里有个 linter 报错
(newVal, oldVal) => {
if (newVal) {
// 当 center 变化时 重新加载mark点
@@ -100,7 +101,7 @@ const loadMap = () => {
initGeocoder()
initAutoComplete()
// TODO @super:这里加一行注释
// TODO @AI:这里加一行注释
if (props.clickMap) {
state.map.addEventListener('click', (e: any) => {
console.log(e)
@@ -194,9 +195,10 @@ const handleAddressSelect = (value: string) => {
* 添加标记点
* @param lnglat 经纬度数组
*/
// TODO @super拼写尽量不要有 idea 绿色提醒哈
const setMarker = (lnglat: any) => {
if (!lnglat) return
if (!lnglat) {
return
}
// 如果点标记已存在则先移除原点
if (state.mapMarker !== null) {
@@ -217,14 +219,14 @@ const setMarker = (lnglat: any) => {
* 经纬度转化为地址、添加标记点
* @param lonLat 经度,纬度字符串
*/
// TODO @super拼写尽量不要有 idea 绿色提醒哈
const regeoCode = (lonLat: string) => {
if (!lonLat) return
// TODO @super拼写尽量不要有 idea 绿色提醒哈
if (!lonLat) {
return
}
const lnglat = lonLat.split(',')
if (lnglat.length !== 2) return
if (lnglat.length !== 2) {
return
}
state.longitude = lnglat[0]
state.latitude = lnglat[1]
@@ -241,7 +243,6 @@ const regeoCode = (lonLat: string) => {
getAddress(lnglat)
}
// TODO @superlnglat 拼写
/**
* 根据经纬度获取地址信息
*
@@ -266,6 +267,7 @@ onMounted(() => {
</script>
<style scoped>
// @AI尽量 unocss 简化掉;
.mapContainer {
width: 100%;
height: 400px;

View File

@@ -66,44 +66,31 @@
<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 label="设备经度" prop="longitude" type="number">
<el-input
v-model="formData.longitude"
placeholder="请输入设备经度"
@blur="updateLocationFromCoordinates"
/>
</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-form-item label="设备维度" prop="latitude" type="number">
<el-input
v-model="formData.latitude"
placeholder="请输入设备维度"
@blur="updateLocationFromCoordinates"
/>
</el-form-item>
<!-- TODO @AI然后后面有个按钮标注地图可以手动按需调整 -->
<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>
</el-collapse-item>
</el-collapse>
</el-form>
@@ -116,9 +103,8 @@
<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 { DeviceTypeEnum, 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'
@@ -144,14 +130,14 @@ const formData = ref({
gatewayId: undefined,
deviceType: undefined as number | undefined,
serialNumber: undefined,
locationType: undefined as number | undefined,
longitude: undefined,
latitude: undefined,
location: '', // 格式: "经度,纬度"
location: '', // 格式: "经度,纬度" // TODO @AI单独搞个字段出来不放在 formData 里!
groupIds: [] as number[]
})
/** 监听经纬度变化更新location */
/** 监听经纬度变化,更新 location */
// TODO @AI交互上想改成上面展示 longitude、latitude 两个地址;然后后面有个按钮【标注地图】可以手动按需调整;
watch([() => formData.value.longitude, () => formData.value.latitude], ([newLong, newLat]) => {
if (newLong && newLat) {
formData.value.location = `${newLong},${newLat}`
@@ -197,6 +183,7 @@ const formRules = reactive({
trigger: 'blur'
}
]
// TODO @AI加个校验。如果 longitude、latitude 有一个非空,必须两个都非空;
})
const formRef = ref() // 表单 Ref
const products = ref<ProductVO[]>([]) // 产品列表
@@ -248,16 +235,6 @@ const submitForm = async () => {
formLoading.value = true
try {
const data = formData.value as unknown as DeviceVO
// 如果非手动定位,不进行提交该字段
if (data.locationType !== LocationTypeEnum.MANUAL) {
data.longitude = undefined
data.latitude = undefined
}
// TODO @宗超【设备定位】address 和 areaId 也要处理;
// 1. 手动定位时longitude + latitude + areaId + address要稍微注意address 可能要去掉省市区部分?!
// 2. IP 定位时IotDeviceMessage 的 buildStateUpdateOnline 时,增加 ip 字段。这样,解析到 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'))
@@ -284,10 +261,8 @@ const resetForm = () => {
gatewayId: undefined,
deviceType: undefined,
serialNumber: undefined,
locationType: undefined,
longitude: undefined,
latitude: undefined,
// TODO @宗超【设备定位】location 是不是拿出来,不放在 formData 里
location: '',
groupIds: []
}
@@ -304,10 +279,10 @@ const handleProductChange = (productId: number) => {
}
const product = products.value?.find((item) => item.id === productId)
formData.value.deviceType = product?.deviceType
formData.value.locationType = product?.locationType
}
/** 处理位置变化 */
// todo @AI这了有 linter 报错TS7044: Parameter lnglat implicitly has an any type, but a better type may be inferred from usage.
const handleLocationChange = (lnglat) => {
formData.value.longitude = lnglat[0]
formData.value.latitude = lnglat[1]
@@ -319,6 +294,7 @@ const updateLocationFromCoordinates = () => {
if (formData.value.longitude && formData.value.latitude) {
// 更新 location 字段,地图组件会根据此字段更新
formData.value.location = `${formData.value.longitude},${formData.value.latitude}`
// TODO @AI这里有告警
mapRef.value.regeoCode(formData.value.location)
}
}

View File

@@ -20,9 +20,6 @@
<el-descriptions-item label="设备类型">
<dict-tag :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE" :value="product.deviceType" />
</el-descriptions-item>
<el-descriptions-item label="定位类型">
<dict-tag :type="DICT_TYPE.IOT_LOCATION_TYPE" :value="device.locationType" />
</el-descriptions-item>
<el-descriptions-item label="DeviceName">
{{ device.deviceName }}
</el-descriptions-item>
@@ -67,6 +64,7 @@
</div>
</template>
<div class="h-[400px] w-full">
<!-- TODO @AI是不是可以通过 getLocationString() 简化判断 -->
<Map v-if="showMap" :center="getLocationString()" class="h-full w-full" />
<div
v-else

View File

@@ -62,17 +62,6 @@
/>
</el-select>
</el-form-item>
<el-form-item label="定位类型" prop="locationType">
<el-radio-group v-model="formData.locationType" :disabled="formType === 'update'">
<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>
<el-form-item label="数据格式" prop="codecType">
<el-radio-group v-model="formData.codecType" :disabled="formType === 'update'">
<el-radio
@@ -130,7 +119,6 @@ const formData = ref({
picUrl: undefined,
description: undefined,
deviceType: undefined,
locationType: undefined,
netType: undefined,
codecType: CodecTypeEnum.ALINK
})
@@ -139,7 +127,6 @@ const formRules = reactive({
name: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
categoryId: [{ required: true, message: '产品分类不能为空', trigger: 'change' }],
deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'change' }],
locationType: [{ required: true, message: '定位类型不能为空', trigger: 'change' }],
netType: [
{
required: true,
@@ -206,7 +193,6 @@ const resetForm = () => {
picUrl: undefined,
description: undefined,
deviceType: undefined,
locationType: undefined,
netType: undefined,
codecType: CodecTypeEnum.ALINK
}

View File

@@ -6,9 +6,6 @@
<el-descriptions-item label="设备类型">
<dict-tag :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE" :value="product.deviceType" />
</el-descriptions-item>
<el-descriptions-item label="定位类型">
<dict-tag :type="DICT_TYPE.IOT_LOCATION_TYPE" :value="product.locationType" />
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ formatDate(product.createTime) }}
</el-descriptions-item>