feat(iot): 新增设备地图功能并优化设备相关接口
- 新增设备地图页面,实现设备在地图上的展示和信息弹出 - 扩展设备接口,增加经纬度和图标字段 - 优化设备编辑页面,将产品名称改为所属产品下拉选择- 更新产品属性页面,调整属性类型为读写类型 - 添加高德地图相关依赖和类型定义
This commit is contained in:
parent
827bb69f22
commit
ea1df9d55f
@ -31,6 +31,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||||
|
"@amap/amap-jsapi-types": "^0.0.15",
|
||||||
"@arco-design/web-vue": "^2.44.7",
|
"@arco-design/web-vue": "^2.44.7",
|
||||||
"@vueuse/core": "^9.3.0",
|
"@vueuse/core": "^9.3.0",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
@ -58,6 +60,7 @@
|
|||||||
"@arco-plugins/vite-vue": "^1.4.5",
|
"@arco-plugins/vite-vue": "^1.4.5",
|
||||||
"@commitlint/cli": "^17.1.2",
|
"@commitlint/cli": "^17.1.2",
|
||||||
"@commitlint/config-conventional": "^17.1.0",
|
"@commitlint/config-conventional": "^17.1.0",
|
||||||
|
"@types/amap-js-api": "^1.4.16",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/lodash": "^4.14.186",
|
"@types/lodash": "^4.14.186",
|
||||||
"@types/mockjs": "^1.0.7",
|
"@types/mockjs": "^1.0.7",
|
||||||
|
@ -10,6 +10,9 @@ export interface DeviceRecord {
|
|||||||
status?: string;
|
status?: string;
|
||||||
isOnline?: boolean;
|
isOnline?: boolean;
|
||||||
pageable?: string;
|
pageable?: string;
|
||||||
|
longitude?: number;
|
||||||
|
latitude?: number;
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceCreateRecord {
|
export interface DeviceCreateRecord {
|
||||||
|
2
src/env.d.ts
vendored
2
src/env.d.ts
vendored
@ -9,3 +9,5 @@ declare module '*.vue' {
|
|||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_BASE_URL: string;
|
readonly VITE_API_BASE_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,6 +98,18 @@ const IOT: AppRouteRecordRaw = {
|
|||||||
permissions: ['iot:tsl'],
|
permissions: ['iot:tsl'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'deviceMap',
|
||||||
|
name: 'DeviceMap',
|
||||||
|
component: () => import('@/views/iot/deviceMap/index.vue'),
|
||||||
|
meta: {
|
||||||
|
locale: '设备地图',
|
||||||
|
title: '设备地图',
|
||||||
|
requiresAuth: true,
|
||||||
|
showInMenu: true,
|
||||||
|
permissions: ['iot:device'],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,22 +34,22 @@
|
|||||||
>
|
>
|
||||||
<!-- 产品名称 -->
|
<!-- 产品名称 -->
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="productName"
|
field="productId"
|
||||||
label="产品名称"
|
label="所属产品"
|
||||||
:rules="[{ required: true, message: '产品名称不能为空' }]"
|
:rules="[{ required: true, message: '产品名称不能为空' }]"
|
||||||
:validate-trigger="['change']"
|
:validate-trigger="['change']"
|
||||||
>
|
>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="formData.productName"
|
v-model="formData.productId"
|
||||||
placeholder="请选择产品名称"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:filter-option="false"
|
:filter-option="false"
|
||||||
allow-search
|
allow-search
|
||||||
|
placeholder="请选择所属产品"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
>
|
>
|
||||||
<a-option v-for="item of options" :key="item.id" :value="item.name">{{
|
<a-option v-for="item of options" :key="item.id" :value="item.id">{{
|
||||||
item.name
|
item.name
|
||||||
}}</a-option>
|
}}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- 设备名称 -->
|
<!-- 设备名称 -->
|
||||||
@ -238,17 +238,15 @@
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = (value: any) => {
|
const handleSearch = (value: string) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
options.value = [];
|
options.value = [];
|
||||||
window.setTimeout(async () => {
|
window.setTimeout(async () => {
|
||||||
const res = await queryDeviceByName({
|
const res = await queryDeviceByName({
|
||||||
name: value,
|
name: value,
|
||||||
page: 1,
|
|
||||||
size: 10,
|
|
||||||
});
|
});
|
||||||
options.value = res.data.records.map((item: any) => {
|
options.value = res.data.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
@ -298,8 +296,6 @@
|
|||||||
if (!valid) {
|
if (!valid) {
|
||||||
// 新增
|
// 新增
|
||||||
formData.value.extendParams = paramsData.value;
|
formData.value.extendParams = paramsData.value;
|
||||||
const productId = await queryDeviceByName(formData.value.productName);
|
|
||||||
formData.value.productId = productId.data.records[0].id;
|
|
||||||
|
|
||||||
if (props.isCreate) {
|
if (props.isCreate) {
|
||||||
// formData.value.username = formData.value.email;
|
// formData.value.username = formData.value.email;
|
||||||
|
@ -331,7 +331,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = (value: any) => {
|
const handleSearch = (value: string) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
options.value = [];
|
options.value = [];
|
||||||
@ -392,6 +392,7 @@
|
|||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search();
|
search();
|
||||||
|
handleSearch('');
|
||||||
});
|
});
|
||||||
watch(() => columns.value, deepClone, { deep: true, immediate: true });
|
watch(() => columns.value, deepClone, { deep: true, immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
117
src/views/iot/deviceMap/index.vue
Normal file
117
src/views/iot/deviceMap/index.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<a-layout>
|
||||||
|
<a-layout-sider :resize-directions="['right']">
|
||||||
|
<a-row>
|
||||||
|
</a-row>
|
||||||
|
</a-layout-sider>
|
||||||
|
<a-layout-content>
|
||||||
|
<div id="container"></div>
|
||||||
|
<!-- 标记点信息弹出框 -->
|
||||||
|
<a-modal v-model:visible="infoVisible" title="设备信息" :footer="null">
|
||||||
|
<p>ID: {{ selectedDevice.id }}</p>
|
||||||
|
<p>名称: {{ selectedDevice.name }}</p>
|
||||||
|
<p>状态: {{ selectedDevice.state }}</p>
|
||||||
|
<p>在线状态: {{ selectedDevice.online ? '是' : '否' }}</p>
|
||||||
|
</a-modal>
|
||||||
|
</a-layout-content>
|
||||||
|
</a-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import AMapLoader from '@amap/amap-jsapi-loader';
|
||||||
|
import { DeviceRecord, queryDeviceList } from '@/api/device';
|
||||||
|
import "@amap/amap-jsapi-types";
|
||||||
|
|
||||||
|
let map: any = null;
|
||||||
|
const selectedDevice = ref<DeviceRecord | null>(null);
|
||||||
|
const infoVisible = ref(false);
|
||||||
|
const deviceList = ref<DeviceRecord[]>([]);
|
||||||
|
const markerContent = `<div class="custom-content-marker">
|
||||||
|
<img src="//a.amap.com/jsapi_demos/static/demo-center/icons/dir-via-marker.png">
|
||||||
|
<div class="close-btn">X</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
AMapLoader.load({
|
||||||
|
key: 'a4e80eed798a56451b226dcfca81b846', // 申请好的Web端开发者Key,首次调用 load 时必填
|
||||||
|
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
|
||||||
|
plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
|
||||||
|
})
|
||||||
|
.then((AMap) => {
|
||||||
|
map = new AMap.Map('container', {
|
||||||
|
// 设置地图容器id
|
||||||
|
viewMode: '3D', // 是否为3D地图模式
|
||||||
|
zoom: 11, // 初始化地图级别
|
||||||
|
center: [116.397428, 39.90923], // 初始化地图中心点位置
|
||||||
|
});
|
||||||
|
// 创建标记点
|
||||||
|
const position = new AMap.LngLat(116.397428, 39.90923);
|
||||||
|
const marker = new AMap.Marker({
|
||||||
|
position,
|
||||||
|
content: markerContent,
|
||||||
|
offset: new AMap.Pixel(-13, -30),
|
||||||
|
});
|
||||||
|
map.add(marker);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取设备列表
|
||||||
|
queryDeviceList({
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
deviceList.value = response.data.records;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('获取设备列表失败', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
map?.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#container {
|
||||||
|
width: 100%;
|
||||||
|
height: 800px;
|
||||||
|
}
|
||||||
|
.custom-content-marker {
|
||||||
|
position: relative;
|
||||||
|
width: 25px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-content-marker img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-content-marker .close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
right: -8px;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #ccc;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 15px;
|
||||||
|
box-shadow: -1px 1px 1px rgba(10, 10, 10, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-content-marker .close-btn:hover{
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
</style>
|
@ -37,6 +37,9 @@
|
|||||||
删除
|
删除
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
|
<template #ioType="{ record }">
|
||||||
|
<div>{{ record.ioType === 'READ_WRITE' ? '读写' : '只读' }}</div>
|
||||||
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" title="服务">
|
<a-tab-pane key="2" title="服务">
|
||||||
@ -105,11 +108,11 @@
|
|||||||
:model="propertyAddData"
|
:model="propertyAddData"
|
||||||
:style="{ width: '800px', height: '420px' }"
|
:style="{ width: '800px', height: '420px' }"
|
||||||
>
|
>
|
||||||
<!-- 设备名称 -->
|
<!-- 属性名称 -->
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="name"
|
field="name"
|
||||||
label="属性名称"
|
label="属性名称"
|
||||||
:rules="[{ required: true, message: '设备名称不能为空' }]"
|
:rules="[{ required: true, message: '属性名称不能为空' }]"
|
||||||
:validate-trigger="['change']"
|
:validate-trigger="['change']"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
@ -146,8 +149,8 @@
|
|||||||
<!-- 读写类型 -->
|
<!-- 读写类型 -->
|
||||||
<a-form-item field="ioType" label="读写类型">
|
<a-form-item field="ioType" label="读写类型">
|
||||||
<a-radio-group v-model="propertyAddData.ioType">
|
<a-radio-group v-model="propertyAddData.ioType">
|
||||||
<a-radio value="1">读写</a-radio>
|
<a-radio value="READ_WRITE">读写</a-radio>
|
||||||
<a-radio value="2">只读</a-radio>
|
<a-radio value="READ_ONLY">只读</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- 备注 -->
|
<!-- 备注 -->
|
||||||
@ -439,6 +442,11 @@
|
|||||||
dataIndex: 'identifier',
|
dataIndex: 'identifier',
|
||||||
slotName: 'identifier',
|
slotName: 'identifier',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'ioType',
|
||||||
|
slotName: 'ioType',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
dataIndex: 'remark',
|
dataIndex: 'remark',
|
||||||
|
@ -136,7 +136,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 部门树模态框-->
|
<!-- 用户模态框-->
|
||||||
<a-modal width="900px" :visible="deptVisible" @cancel="deptTreeCancel">
|
<a-modal width="900px" :visible="deptVisible" @cancel="deptTreeCancel">
|
||||||
<template #title>发送用户</template>
|
<template #title>发送用户</template>
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
|
Loading…
Reference in New Issue
Block a user