更新巡检线路编辑功能,同步地图上点击设备的点位到巡检点位表
This commit is contained in:
14
UpdateLog.md
14
UpdateLog.md
@@ -1,5 +1,19 @@
|
||||
# 仓库管理操作端项目更新记录
|
||||
|
||||
## 2024年4月18日 - 路径管理功能优化
|
||||
|
||||
### 路径管理模块
|
||||
* 📝**[修改]**:为路径点增加地图模型ID属性支持
|
||||
* 📝**[修改]**:在PathManager.vue中更新模拟数据,为每个路径点添加modelId字段
|
||||
* 📝**[修改]**:在PathEditor.vue中增加modelId字段的显示和处理
|
||||
* 📝**[修改]**:实现设备选择时自动同步modelId的功能
|
||||
* 📝**[修改]**:优化PathEditor.vue中地图实例获取和应用方式
|
||||
* 📝**[修改]**:重构地图初始化逻辑,将其与组件生命周期解耦
|
||||
* 📝**[修改]**:实现基于Intersection Observer的组件可见性监测
|
||||
* 📝**[修改]**:确保地图只在组件实际显示时才初始化,避免DOM未渲染问题
|
||||
* 📝**[修改]**:为dialog组件添加ID标识,便于可见性监测
|
||||
* 📝**[修改]**:添加降级方案,确保即使在特殊情况下也能初始化地图
|
||||
|
||||
## 2024年4月17日 - 巡检路径管理功能开发
|
||||
|
||||
### 巡检路径模块
|
||||
|
@@ -1,4 +1,27 @@
|
||||
2024-6-7
|
||||
## 2024年6月8日 - 路径管理功能增强
|
||||
|
||||
### 状态管理
|
||||
- ➕**[新增]**:添加pathStore.js,用于管理全局路径点信息
|
||||
- 实现了pathPoints数组,存储{modelId, deviceName}结构的数据
|
||||
- 提供syncPathPoints方法,同步路径点数据
|
||||
- 提供clearPathPoints方法,清空路径点
|
||||
- 提供getPathPoints方法,获取路径点列表
|
||||
|
||||
### 路径编辑功能
|
||||
- 📝**[修改]**:更新useEditNavi.js
|
||||
- 导入pathStore实现全局数据同步
|
||||
- 在pathIdList变化时自动同步到全局store
|
||||
- 实现了从deviceList查找对应设备名称的功能
|
||||
|
||||
- 📝**[修改]**:更新PathEditor.vue
|
||||
- 导入pathStore并获取实例
|
||||
- 添加对话框可见性监听器,在打开时清空pathPoints数组
|
||||
- 根据编辑/新增状态,动态同步pathForm.points到pathStore
|
||||
- 新增对pathStore.pathPoints数组的深度监听,实现双向数据同步
|
||||
- 添加变更检测机制,避免循环更新问题
|
||||
- 实现全局状态到表单数据的智能转换和映射
|
||||
|
||||
## 2024-6-7
|
||||
|
||||
- 修复CarManager组件多个显示问题
|
||||
- 移除了formRef和applyFormRef的冗余初始化定义
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { inject, ref, watch, computed } from "vue"
|
||||
import { useDisplayColor } from "../hooks/useDisplayColor"
|
||||
import { useDisplayColor } from "@/hooks/useDisplayColor"
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("map")
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { inject } from "vue"
|
||||
import { useNavi } from "../hooks/useNavi"
|
||||
import { useNavi } from "@/hooks/useNavi"
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("map")
|
||||
|
61
src/components/EditDisplayRouteLine.vue
Normal file
61
src/components/EditDisplayRouteLine.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { inject } from "vue"
|
||||
import { useNavi } from "@/hooks/useEditNavi"
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("editMap")
|
||||
|
||||
const {
|
||||
pathIdList,
|
||||
addPath,
|
||||
startNavi,
|
||||
pauseNavi,
|
||||
restoreNavi,
|
||||
stopNavi,
|
||||
} = useNavi(map)
|
||||
|
||||
map.value.on('click', (e) => {
|
||||
const polygon = e.object?.userData?.polygonData // 获取点位数据
|
||||
if(!polygon?.isNavi) return
|
||||
|
||||
addPath(polygon)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
addPath,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navi">
|
||||
<div class="path-list-status">
|
||||
<div>点击具有分类的标签或模型生成线路:</div>
|
||||
<div class="selected">{{ pathIdList.join(",") }}</div>
|
||||
<button @click="() => pathIdList.pop()">删除末尾</button>
|
||||
<button @click="() => pathIdList = []">清空</button>
|
||||
<button @click="() => startNavi()" :disabled="pathIdList.length < 2">开始导航</button>
|
||||
<button @click="() => pauseNavi()" :disabled="pathIdList.length < 2">暂停</button>
|
||||
<button @click="() => restoreNavi()" :disabled="pathIdList.length < 2">继续</button>
|
||||
<button @click="() => stopNavi()" :disabled="pathIdList.length < 2">结束导航</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.path-list-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
min-height: 1.5em;
|
||||
width: 300px;
|
||||
padding: 0 5px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #767676;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
125
src/components/EditorFilter.vue
Normal file
125
src/components/EditorFilter.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<script setup>
|
||||
import { inject, ref, watch, onBeforeUnmount } from "vue"
|
||||
import { useClassPolygon } from "@/hooks/useClassPolygon"
|
||||
import { useDeviceStore } from "@/stores/device" // 导入设备store
|
||||
|
||||
const { THREE } = VgoMap
|
||||
|
||||
// 获取设备store实例
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const editMap = inject("editMap")
|
||||
const editPolygonDataAll = inject("editPolygonDataAll")
|
||||
const editRouteLineRef = inject("editRouteLineRef")
|
||||
console.log(editMap.value,'editorFilter.editMap.value')
|
||||
// 仅取有分类的点位
|
||||
const { classPoiList, classDict } = useClassPolygon(editPolygonDataAll)
|
||||
console.log(classPoiList.value,'classPoiList.value')
|
||||
console.log(classDict.value,'classDict.value')
|
||||
const markerGroup = new THREE.Group()
|
||||
editMap.value.scene.add(markerGroup)
|
||||
onBeforeUnmount(() => {
|
||||
markerGroup.dispose()
|
||||
})
|
||||
|
||||
// 根据有分类点位,创建marker
|
||||
|
||||
console.log(editMap.value,'editorFilter.editMap.value')
|
||||
console.log(editMap.value.status.floor,'editMap.value.status.floor')
|
||||
console.log(editMap.value.outerFloor,'editMap.value?.outerFloor')
|
||||
const markerStatus = ref({});
|
||||
(editMap.value.status?.floor ?? editMap.value?.outerFloor).on('floorModelAllLoaded', () => {
|
||||
console.log('lkasdfjalksdjfklasjdf')
|
||||
// 清理所有marker
|
||||
while (markerGroup.children.length) {
|
||||
markerGroup.children?.[0]?.dispose()
|
||||
}
|
||||
markerStatus.value = {}
|
||||
|
||||
// 创建新的marker
|
||||
const listGroup = Object.entries(classPoiList.value)
|
||||
listGroup.forEach(([key, list]) => {
|
||||
list.forEach((item) => {
|
||||
const model = editMap.value.getModelById(item.extrasModelId) // 获取点位关联的模型信息
|
||||
const modelName = model.userData.modelData.name
|
||||
const modelId=item.id
|
||||
console.log(modelName, ' ', item.id)
|
||||
|
||||
// 将设备信息添加到全局store
|
||||
//deviceStore.addDevice(modelId, modelName)
|
||||
|
||||
const marker = createDivMarker(modelName, item)
|
||||
|
||||
if (model) {
|
||||
marker.position.copy(model.position)
|
||||
} else {
|
||||
marker.position.copy(item.center).setZ(10)
|
||||
}
|
||||
markerGroup.add(marker)
|
||||
|
||||
if (!markerStatus.value?.[key]) {
|
||||
markerStatus.value[key] = []
|
||||
}
|
||||
markerStatus.value[key].push(marker)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 根据选中状态,控制marker显示
|
||||
const checkedStatus = ref({})
|
||||
watch(checkedStatus, (checked) => {
|
||||
for (let key in checked) {
|
||||
markerStatus.value?.[key]?.forEach((marker) => {
|
||||
marker.visible = checked[key]
|
||||
})
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
function createDivMarker(content, polygon) {
|
||||
const wrapper = document.createElement("div")
|
||||
const contentNode = document.createElement("div")
|
||||
contentNode.className = "marker"
|
||||
contentNode.textContent = content
|
||||
contentNode.onclick = (e) => {
|
||||
if(!polygon?.isNavi) return
|
||||
editRouteLineRef.value.addPath(polygon)
|
||||
}
|
||||
|
||||
wrapper.appendChild(contentNode)
|
||||
const floorId = editMap.status?.floor?.data?.id || '1'
|
||||
return editMap.value.addDomMarker(floorId, wrapper) // 之前记错了,这边直接传dom元素也是可以的
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="filter">
|
||||
<li v-for="key in Object.keys(classPoiList)" :key="key">
|
||||
<label :for="key">{{ classDict[key].name }}</label>
|
||||
<input type="checkbox" :id="key" v-model="checkedStatus[key]">
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter {
|
||||
margin: 0;
|
||||
|
||||
li,
|
||||
label,
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.marker {
|
||||
background-color: #fff;
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
</style>
|
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { inject, onBeforeUnmount } from "vue"
|
||||
import { useFence } from "../hooks/useFence"
|
||||
import { useFence } from "@/hooks/useFence"
|
||||
|
||||
const { THREE } = VgoMap
|
||||
|
||||
|
@@ -1,14 +1,18 @@
|
||||
<script setup>
|
||||
import { inject, ref, watch, onBeforeUnmount } from "vue"
|
||||
import { useClassPolygon } from "../hooks/useClassPolygon"
|
||||
import { useClassPolygon } from "@/hooks/useClassPolygon"
|
||||
import { useDeviceStore } from "@/stores/device" // 导入设备store
|
||||
|
||||
const { THREE } = VgoMap
|
||||
|
||||
// 获取设备store实例
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("map")
|
||||
const polygonDataAll = inject("polygonDataAll")
|
||||
const routeLineRef = inject("routeLineRef")
|
||||
|
||||
console.log(map.value,'Filter.map.value')
|
||||
// 仅取有分类的点位
|
||||
const { classPoiList, classDict } = useClassPolygon(polygonDataAll)
|
||||
|
||||
@@ -18,10 +22,13 @@ onBeforeUnmount(() => {
|
||||
markerGroup.dispose()
|
||||
})
|
||||
|
||||
console.log(map.value.status.floor,'editMap.value.status.floor')
|
||||
console.log(map.value.outerFloor,'editMap.value?.outerFloor')
|
||||
// 根据有分类点位,创建marker
|
||||
const markerStatus = ref({});
|
||||
|
||||
(map.value.status?.floor ?? map.value?.outerFloor).on('floorModelAllLoaded', () => {
|
||||
console.log('lkasdfjalksdjfklasjdf')
|
||||
// 清理所有marker
|
||||
while (markerGroup.children.length) {
|
||||
markerGroup.children?.[0]?.dispose()
|
||||
@@ -32,8 +39,16 @@ const markerStatus = ref({});
|
||||
const listGroup = Object.entries(classPoiList.value)
|
||||
listGroup.forEach(([key, list]) => {
|
||||
list.forEach((item) => {
|
||||
const marker = createDivMarker(classDict[key].name, item)
|
||||
const model = map.value.getModelById(item.extrasModelId) // 获取点位关联的模型信息
|
||||
const modelName = model.userData.modelData.name
|
||||
const modelId=item.id
|
||||
console.log(modelName, ' ', item.id)
|
||||
|
||||
// 将设备信息添加到全局store
|
||||
deviceStore.addDevice(modelId, modelName)
|
||||
|
||||
const marker = createDivMarker(modelName, item)
|
||||
|
||||
if (model) {
|
||||
marker.position.copy(model.position)
|
||||
} else {
|
||||
@@ -54,7 +69,7 @@ const checkedStatus = ref({})
|
||||
watch(checkedStatus, (checked) => {
|
||||
for (let key in checked) {
|
||||
markerStatus.value?.[key]?.forEach((marker) => {
|
||||
marker.visible = !checked[key]
|
||||
marker.visible = checked[key]
|
||||
})
|
||||
}
|
||||
}, {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { inject, ref, watch, onBeforeUnmount } from "vue"
|
||||
import { useSkyLight } from "../hooks/useSkyLight"
|
||||
import { useSkyLight } from "@/hooks/useSkyLight"
|
||||
|
||||
const map = inject("map")
|
||||
const { skyGroup, init, setTime } = useSkyLight(map.value)
|
||||
|
96
src/hooks/useEditNavi.js
Normal file
96
src/hooks/useEditNavi.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { ref, watch } from "vue"
|
||||
import { usePathStore } from "@/stores/pathStore"
|
||||
|
||||
export function useNavi(map) {
|
||||
const pathIdList = ref([])
|
||||
|
||||
function addPath(polygon) {
|
||||
if(polygon?.id && pathIdList.value[pathIdList.value.length - 1] === polygon?.id) return // 不连续添加相同的项
|
||||
|
||||
const modelId = polygon.extrasModelId;
|
||||
const model = map.value.getModelById(modelId)
|
||||
const modelName = model.userData.modelData.name
|
||||
console.log(modelName, 'model.name', polygon.id,'polygon.id')
|
||||
|
||||
addPathId(polygon.id)
|
||||
}
|
||||
|
||||
function addPathId(id) {
|
||||
if(id && pathIdList.value[pathIdList.value.length - 1] === id) return
|
||||
|
||||
pathIdList.value.push(id)
|
||||
}
|
||||
|
||||
function startNavi() {
|
||||
if(pathIdList.value.length < 2) return
|
||||
map.value.navi.simulate()
|
||||
map.value.navi.setSimulateSpeed(10)
|
||||
}
|
||||
|
||||
function pauseNavi () {
|
||||
// 判断当前是否处于导航中
|
||||
if (map.value.navi.status.isSimulate) {
|
||||
map.value.navi.pauseSimulate()
|
||||
}
|
||||
}
|
||||
|
||||
function restoreNavi () {
|
||||
// 判断当前是否处于暂停中
|
||||
if (!map.value.navi.isSimulatePause) {
|
||||
map.value.navi.resumeSimulate()
|
||||
}
|
||||
}
|
||||
|
||||
function stopNavi () {
|
||||
// 退出模拟导航
|
||||
map.value.navi.stopSimulate()
|
||||
removePathLine()
|
||||
pathIdList.value = []
|
||||
}
|
||||
|
||||
function removePathLine() {
|
||||
// 销毁箭头
|
||||
map.value.navi.removeNaviArrow()
|
||||
// 销毁线路
|
||||
map.value.navi.removeNaviLine()
|
||||
//移除途径点
|
||||
map.value.navi.removeAllWaypoint()
|
||||
//移除起点
|
||||
map.value.navi.removeStart()
|
||||
//移除终点
|
||||
map.value.navi.removeEnd()
|
||||
}
|
||||
|
||||
watch(pathIdList, (pathIdList) => {
|
||||
// 同步路径点到全局store
|
||||
const pathStore = usePathStore()
|
||||
pathStore.syncPathPoints(pathIdList)
|
||||
|
||||
if(pathIdList?.length < 2) {
|
||||
removePathLine()
|
||||
return
|
||||
}
|
||||
const end = pathIdList[pathIdList.length - 1]
|
||||
const [start, ...passPoints]= pathIdList.slice(0, -1);
|
||||
|
||||
map.value.navi.removeAllWaypoint()
|
||||
|
||||
map.value.navi.setStart(start)
|
||||
map.value.navi.setWaypoints(passPoints)
|
||||
map.value.navi.setEnd(end)
|
||||
|
||||
map.value.navi.find()
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
return {
|
||||
pathIdList,
|
||||
addPath,
|
||||
addPathId,
|
||||
startNavi,
|
||||
pauseNavi,
|
||||
restoreNavi,
|
||||
stopNavi,
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ export function useNavi(map) {
|
||||
const modelId = polygon.extrasModelId;
|
||||
const model = map.value.getModelById(modelId)
|
||||
const modelName = model.userData.modelData.name
|
||||
console.log(modelName, 'model.name')
|
||||
console.log(modelName, 'model.name', polygon.id,'polygon.id')
|
||||
|
||||
addPathId(polygon.id)
|
||||
}
|
||||
|
37
src/stores/device.js
Normal file
37
src/stores/device.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
// 定义全局设备数组的store
|
||||
export const useDeviceStore = defineStore('device', {
|
||||
state: () => ({
|
||||
// 全局设备数组:包含{ modelId, modelName }结构的对象
|
||||
deviceList: []
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// 添加设备到全局数组
|
||||
addDevice(modelId, modelName) {
|
||||
// 检查是否已存在相同的modelId
|
||||
const existingIndex = this.deviceList.findIndex(device => device.modelId === modelId)
|
||||
|
||||
if (existingIndex === -1 && modelId && modelName) {
|
||||
this.deviceList.push({
|
||||
modelId,
|
||||
modelName
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 清空设备列表
|
||||
clearDevices() {
|
||||
this.deviceList = []
|
||||
},
|
||||
|
||||
// 获取用于Select组件的选项格式
|
||||
getSelectOptions() {
|
||||
return this.deviceList.map(device => ({
|
||||
value: device.modelId, // 设备id作为value
|
||||
label: device.modelName // 设备名称作为label
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
49
src/stores/pathStore.js
Normal file
49
src/stores/pathStore.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { useDeviceStore } from './device'
|
||||
|
||||
// 定义路径点store
|
||||
export const usePathStore = defineStore('path', {
|
||||
state: () => ({
|
||||
// 路径点数组:包含{ modelId, deviceName }结构的对象
|
||||
pathPoints: []
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// 同步路径点数据
|
||||
syncPathPoints(pathIdList) {
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
// 清空当前路径点数组
|
||||
this.pathPoints = []
|
||||
|
||||
// 根据pathIdList中的id顺序,查找对应的设备信息并同步
|
||||
pathIdList.forEach(modelId => {
|
||||
// 从deviceList中查找对应的设备信息
|
||||
const device = deviceStore.deviceList.find(d => d.modelId === modelId)
|
||||
|
||||
if (device) {
|
||||
this.pathPoints.push({
|
||||
modelId: modelId,
|
||||
deviceName: device.modelName
|
||||
})
|
||||
} else {
|
||||
// 如果没有找到对应的设备信息,仍添加modelId但deviceName为空或默认值
|
||||
this.pathPoints.push({
|
||||
modelId: modelId,
|
||||
deviceName: `未知设备(${modelId})`
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 清空路径点
|
||||
clearPathPoints() {
|
||||
this.pathPoints = []
|
||||
},
|
||||
|
||||
// 获取路径点列表
|
||||
getPathPoints() {
|
||||
return this.pathPoints
|
||||
}
|
||||
}
|
||||
})
|
87
src/views/EditMap.vue
Normal file
87
src/views/EditMap.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, computed, provide, useTemplateRef } from "vue"
|
||||
import Filter from "@/components/EditorFilter.vue"
|
||||
import DisplayRouteLine from "@/components/EditDisplayRouteLine.vue"
|
||||
|
||||
// 在Map.vue中添加props定义
|
||||
const props = defineProps({
|
||||
elId: {
|
||||
type: String,
|
||||
default: 'mapContainer'
|
||||
}
|
||||
})
|
||||
|
||||
const { VgoMap } = window
|
||||
let mapId = "1977947221534052352"
|
||||
const isLoaded = ref(false)
|
||||
|
||||
const map = ref()
|
||||
console.log(map.value)
|
||||
const routeLineRef = useTemplateRef('routeLineRef')
|
||||
|
||||
provide("editMap", computed(() => map.value))
|
||||
provide('editRouteLineRef', computed(() => routeLineRef.value))
|
||||
provide('editPolygonDataAll', computed(() => {
|
||||
const outDoor = map.value?.mapData?.polygonData ?? []
|
||||
|
||||
const inDoor = map?.mapData?.build?.reduce((result, build) => {
|
||||
build.floor.forEach(fItem => {
|
||||
result.push(...fItem.polygonData)
|
||||
})
|
||||
return result
|
||||
}, []) ?? []
|
||||
|
||||
return [...outDoor, ...inDoor]
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
map.value = new VgoMap.Map({
|
||||
el: props.elId, // 使用传入的elId,
|
||||
id: mapId,
|
||||
})
|
||||
|
||||
map.value.on("loaded", () => {
|
||||
window.$editmap = map.value
|
||||
isLoaded.value = true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div :id="elId"></div>
|
||||
<div v-if="isLoaded" class="ui">
|
||||
<!-- <Fence/> -->
|
||||
<DisplayRouteLine ref="routeLineRef"/>
|
||||
<!-- <DisplayColor/> -->
|
||||
<Filter/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
html,
|
||||
body,
|
||||
.app,
|
||||
.wrapper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mapContainer {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ui {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 30px;
|
||||
}
|
||||
</style>
|
@@ -6,6 +6,14 @@ import Sky from "../components/Sky.vue"
|
||||
import DisplayColor from "../components/DisplayColor.vue"
|
||||
import DisplayRouteLine from "../components/DisplayRouteLine.vue"
|
||||
|
||||
// 在Map.vue中添加props定义
|
||||
const props = defineProps({
|
||||
elId: {
|
||||
type: String,
|
||||
default: 'mapContainer'
|
||||
}
|
||||
})
|
||||
|
||||
const { VgoMap } = window
|
||||
let mapId = "1977947221534052352"
|
||||
const isLoaded = ref(false)
|
||||
@@ -30,7 +38,7 @@ provide('polygonDataAll', computed(() => {
|
||||
|
||||
onMounted(() => {
|
||||
map.value = new VgoMap.Map({
|
||||
el: "mapContainer",
|
||||
el: props.elId, // 使用传入的elId,
|
||||
id: mapId,
|
||||
})
|
||||
|
||||
@@ -43,7 +51,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div id="mapContainer"></div>
|
||||
<div :id="elId"></div>
|
||||
<div v-if="isLoaded" class="ui">
|
||||
<!-- <Fence/> -->
|
||||
<DisplayRouteLine ref="routeLineRef"/>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<!-- 巡检路径编辑组件 -->
|
||||
<el-dialog
|
||||
id="path-editor-root"
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑巡检路径' : '新增巡检路径'"
|
||||
width="90%"
|
||||
@@ -47,7 +48,7 @@
|
||||
<div class="border border-blue-400 rounded-lg overflow-hidden relative">
|
||||
<!-- 地图容器,设置固定高度 -->
|
||||
<div class="path-map-container">
|
||||
<Map el-id="etsfs"/>
|
||||
<EditMap el-id="etsfs"/>
|
||||
</div>
|
||||
<!-- 地图操作提示 -->
|
||||
<div class="map-tip">
|
||||
@@ -78,8 +79,9 @@
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.deviceName"
|
||||
placeholder="请选择设备"
|
||||
placeholder="请输入设备名称"
|
||||
style="width: 100%"
|
||||
@change="(value) => handleDeviceSelect(value, scope.row)"
|
||||
>
|
||||
<el-option
|
||||
v-for="device in deviceList"
|
||||
@@ -90,6 +92,7 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="modelId" label="地图模型ID" min-width="150" align="center" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<div class="flex gap-2 justify-center">
|
||||
@@ -133,9 +136,13 @@
|
||||
* 巡检路径编辑组件
|
||||
* 用于新增和编辑巡检路径信息,包含地图展示和路径点配置
|
||||
*/
|
||||
import { ref, reactive, watch, computed, onMounted } from 'vue'
|
||||
// 导入inject用于从Map组件获取map实例
|
||||
import { ref, reactive, computed, onMounted, watch, inject } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import Map from '../Map.vue' // 导入地图组件
|
||||
import EditMap from '../EditMap.vue' // 导入地图组件
|
||||
import { useDeviceStore } from '@/stores/device' // 导入设备store
|
||||
import { usePathStore } from '@/stores/pathStore' // 导入路径store
|
||||
import { useNavi } from '@/hooks/useNavi' // 导入导航hook
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
@@ -180,26 +187,15 @@ const formRules = reactive({
|
||||
]
|
||||
})
|
||||
|
||||
// 模拟设备列表
|
||||
const deviceList = ref([
|
||||
{ value: '入口门禁', label: '入口门禁' },
|
||||
{ value: '出口门禁', label: '出口门禁' },
|
||||
{ value: '1号仓库温度传感器', label: '1号仓库温度传感器' },
|
||||
{ value: '1号仓库湿度传感器', label: '1号仓库湿度传感器' },
|
||||
{ value: '2号仓库温度传感器', label: '2号仓库温度传感器' },
|
||||
{ value: '2号仓库湿度传感器', label: '2号仓库湿度传感器' },
|
||||
{ value: '消防设施检查点', label: '消防设施检查点' },
|
||||
{ value: 'UPS电源', label: 'UPS电源' },
|
||||
{ value: '服务器机架1', label: '服务器机架1' },
|
||||
{ value: '服务器机架2', label: '服务器机架2' },
|
||||
{ value: '空调系统', label: '空调系统' },
|
||||
{ value: '网络设备柜', label: '网络设备柜' },
|
||||
{ value: '机房环境监控', label: '机房环境监控' },
|
||||
{ value: '西区大门', label: '西区大门' },
|
||||
{ value: '安全监控室', label: '安全监控室' },
|
||||
{ value: '应急出口', label: '应急出口' },
|
||||
{ value: '出口监控摄像头', label: '出口监控摄像头' }
|
||||
])
|
||||
// 获取设备store实例
|
||||
const deviceStore = useDeviceStore()
|
||||
// 获取路径store实例
|
||||
const pathStore = usePathStore()
|
||||
|
||||
// 使用computed从store获取设备列表选项
|
||||
const deviceList = computed(() => {
|
||||
return deviceStore.getSelectOptions()
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const isEdit = computed(() => !!props.pathData)
|
||||
@@ -218,6 +214,51 @@ const resetForm = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 监听对话框可见性变化
|
||||
watch(() => props.visible, (isVisible) => {
|
||||
if (isVisible) {
|
||||
// 对话框打开时,清空pathStore中的pathPoints数组
|
||||
pathStore.clearPathPoints()
|
||||
|
||||
// 判断是否为编辑状态
|
||||
if (isEdit.value && pathForm.points.length > 0) {
|
||||
// 编辑状态:将pathForm.points同步到pathStore的pathPoints数组
|
||||
const pathPointsData = pathForm.points.map(point => ({
|
||||
modelId: point.modelId,
|
||||
deviceName: point.deviceName
|
||||
}))
|
||||
// 直接设置pathStore的pathPoints数组
|
||||
pathStore.pathPoints = pathPointsData
|
||||
}
|
||||
// 新增状态:保持pathStore中的pathPoints数组为空(已通过clearPathPoints实现)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听pathStore中pathPoints数组的变更,实现从全局到局部的同步
|
||||
watch(() => pathStore.pathPoints, (newPathPoints) => {
|
||||
if (newPathPoints && newPathPoints.length > 0) {
|
||||
// 确保不重复同步(避免循环更新)
|
||||
const currentModelIds = pathForm.points.map(p => p.modelId)
|
||||
const newModelIds = newPathPoints.map(p => p.modelId)
|
||||
|
||||
// 检查是否有变化需要同步
|
||||
const hasChanges = currentModelIds.length !== newModelIds.length ||
|
||||
!currentModelIds.every((id, index) => id === newModelIds[index])
|
||||
|
||||
if (hasChanges) {
|
||||
// 将全局pathPoints同步到表单的pathPoints中
|
||||
const synchronizedPoints = newPathPoints.map((point, index) => ({
|
||||
id: `${Date.now()}-${index}`, // 生成新的唯一ID
|
||||
index: index + 1, // 序号从1开始
|
||||
modelId: point.modelId,
|
||||
deviceName: point.deviceName
|
||||
}))
|
||||
|
||||
pathForm.points = synchronizedPoints
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.pathData, (newVal) => {
|
||||
if (newVal) {
|
||||
@@ -226,6 +267,15 @@ watch(() => props.pathData, (newVal) => {
|
||||
pathForm.name = newVal.name || ''
|
||||
pathForm.description = newVal.description || ''
|
||||
pathForm.points = newVal.points ? JSON.parse(JSON.stringify(newVal.points)) : []
|
||||
|
||||
// 如果对话框已经可见且是编辑状态,同步到pathStore
|
||||
if (props.visible) {
|
||||
const pathPointsData = pathForm.points.map(point => ({
|
||||
modelId: point.modelId,
|
||||
deviceName: point.deviceName
|
||||
}))
|
||||
pathStore.pathPoints = pathPointsData
|
||||
}
|
||||
} else {
|
||||
// 新增模式:重置表单
|
||||
resetForm()
|
||||
@@ -247,7 +297,8 @@ const handleAddPathPoint = () => {
|
||||
pathForm.points.push({
|
||||
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
index: newIndex,
|
||||
deviceName: ''
|
||||
deviceName: '',
|
||||
modelId: ''
|
||||
})
|
||||
|
||||
// 更新序号
|
||||
@@ -305,6 +356,17 @@ const handleMoveDown = (row, index) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理设备选择变化
|
||||
*/
|
||||
const handleDeviceSelect = (value, row) => {
|
||||
// 查找对应的modelId
|
||||
const selectedDevice = deviceList.value.find(device => device.value === value)
|
||||
if (selectedDevice) {
|
||||
row.modelId = selectedDevice.value // 设备的value已经是modelId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新路径点序号
|
||||
*/
|
||||
@@ -377,6 +439,25 @@ const handleSave = async () => {
|
||||
// 模拟API调用延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 将路径点信息添加到导航系统
|
||||
console.log(map.value)
|
||||
console.log(pathForm.points)
|
||||
if (map.value && pathForm.points.length > 0) {
|
||||
// 先清空现有路径
|
||||
//stopNavi()
|
||||
// 按顺序添加所有路径点的modelId到导航系统
|
||||
pathForm.points.forEach(point => {
|
||||
if (point.modelId) {
|
||||
addPathId(point.modelId)
|
||||
}
|
||||
})
|
||||
// 如果路径点数量足够,可以自动启动导航
|
||||
if (pathForm.points.length >= 2) {
|
||||
// 这里可以根据需要决定是否自动启动导航
|
||||
// startNavi()
|
||||
}
|
||||
}
|
||||
|
||||
// 触发保存事件
|
||||
emit('save', saveData)
|
||||
|
||||
|
@@ -151,48 +151,40 @@ const generateMockPaths = () => {
|
||||
return [
|
||||
{
|
||||
id: '1',
|
||||
name: '东区仓库巡检路径',
|
||||
description: '覆盖东区所有仓库区域的标准巡检路径',
|
||||
pointCount: 5,
|
||||
name: '新库区巡检路径',
|
||||
description: '覆盖新库区所有仓库区域的标准巡检路径',
|
||||
pointCount: 12,
|
||||
points: [
|
||||
{ id: '1-1', index: 1, deviceName: '入口门禁' },
|
||||
{ id: '1-2', index: 2, deviceName: '1号仓库温度传感器' },
|
||||
{ id: '1-3', index: 3, deviceName: '消防设施检查点' },
|
||||
{ id: '1-4', index: 4, deviceName: '2号仓库湿度传感器' },
|
||||
{ id: '1-5', index: 5, deviceName: '出口监控摄像头' }
|
||||
{ id: '1-1', index: 1, deviceName: '摄像头1', modelId: 'mgt1aol3kro' },
|
||||
{ id: '1-2', index: 2, deviceName: '摄像头2', modelId: 'mgt1gz9s54s' },
|
||||
{ id: '1-3', index: 3, deviceName: '摄像头3', modelId: 'mgt1ho9dzhn' },
|
||||
{ id: '1-4', index: 4, deviceName: '摄像头4', modelId: 'mgt1n0wylmd' },
|
||||
{ id: '1-5', index: 5, deviceName: '摄像头5', modelId: 'mgt1nboirj' },
|
||||
{ id: '1-6', index: 6, deviceName: '摄像头6', modelId: 'mguoz4gy4x' },
|
||||
{ id: '1-7', index: 7, deviceName: '摄像头7', modelId: 'mgt2cbv7k2d' },
|
||||
{ id: '1-8', index: 8, deviceName: '摄像头8', modelId: 'mgt1njgpgpt' },
|
||||
{ id: '1-9', index: 9, deviceName: '摄像头9', modelId: 'mgt1nuwisti' },
|
||||
{ id: '1-10', index: 10, deviceName: '摄像头10', modelId: 'mgt1o6aqek' },
|
||||
{ id: '1-11', index: 11, deviceName: '摄像头11', modelId: 'mgulf1e5mi' },
|
||||
{ id: '1-12', index: 12, deviceName: '摄像头14', modelId: 'mgt1oylmwuf' }
|
||||
],
|
||||
createTime: '2024-04-01 10:30:00',
|
||||
updateTime: '2024-04-10 14:20:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '西区安全巡检路线',
|
||||
description: '西区安全检查专用路线',
|
||||
pointCount: 3,
|
||||
name: '新库区入口巡检路径',
|
||||
description: '新库区入口安全检查专用路线',
|
||||
pointCount: 5,
|
||||
points: [
|
||||
{ id: '2-1', index: 1, deviceName: '西区大门' },
|
||||
{ id: '2-2', index: 2, deviceName: '安全监控室' },
|
||||
{ id: '2-3', index: 3, deviceName: '应急出口' }
|
||||
{ id: '2-1', index: 1, deviceName: '摄像头18', modelId: 'mgt24lo3m4e' },
|
||||
{ id: '2-2', index: 2, deviceName: '摄像头17', modelId: 'mgt24kocdw' },
|
||||
{ id: '2-3', index: 3, deviceName: '摄像头16', modelId: 'mgt24gkj4on' },
|
||||
{ id: '2-4', index: 4, deviceName: '摄像头12', modelId: 'mgt1oi96hsf' },
|
||||
{ id: '2-5', index: 5, deviceName: '摄像头13', modelId: 'mgt1op9e61d' }
|
||||
],
|
||||
createTime: '2024-04-05 09:15:00',
|
||||
updateTime: '2024-04-05 09:15:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '设备机房巡检路线',
|
||||
description: '针对设备机房的详细巡检路线,包括所有关键设备',
|
||||
pointCount: 7,
|
||||
points: [
|
||||
{ id: '3-1', index: 1, deviceName: 'UPS电源' },
|
||||
{ id: '3-2', index: 2, deviceName: '服务器机架1' },
|
||||
{ id: '3-3', index: 3, deviceName: '服务器机架2' },
|
||||
{ id: '3-4', index: 4, deviceName: '空调系统' },
|
||||
{ id: '3-5', index: 5, deviceName: '消防系统' },
|
||||
{ id: '3-6', index: 6, deviceName: '网络设备柜' },
|
||||
{ id: '3-7', index: 7, deviceName: '机房环境监控' }
|
||||
],
|
||||
createTime: '2024-04-08 16:45:00',
|
||||
updateTime: '2024-04-12 11:30:00'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user