2025-03-15 18:38:27 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div class="map-container">
|
|
|
|
|
<!-- 地图容器 -->
|
|
|
|
|
<div id="container"></div>
|
|
|
|
|
|
2025-04-26 18:41:39 +08:00
|
|
|
|
<!-- 地址搜索组件 -->
|
|
|
|
|
<address-search :map="map" :AMap="AMap" v-if="map && enableEdit" />
|
|
|
|
|
|
2025-03-15 18:38:27 +08:00
|
|
|
|
<!-- 地图工具栏 -->
|
|
|
|
|
<div class="map-tools">
|
|
|
|
|
<el-button-group>
|
2025-04-28 16:34:03 +08:00
|
|
|
|
<el-button size="mini" id="add-sub" type="primary" icon="el-icon-plus" @click="startBoundaryEdit(null)" v-if="enableEdit" :disabled="isEditing">子区域</el-button>
|
|
|
|
|
<el-button size="mini" id="area-edit" type="warning" icon="el-icon-edit" @click="startAreaBoundaryEdit" v-if="enableEdit" :disabled="isEditing">电子围栏</el-button>
|
2025-03-15 18:38:27 +08:00
|
|
|
|
<el-button size="mini" icon="el-icon-full-screen" @click="setFitView">全局查看</el-button>
|
|
|
|
|
<el-button size="mini" icon="el-icon-picture" @click="toggleMapStyle">切换样式</el-button>
|
2025-04-26 18:41:39 +08:00
|
|
|
|
<el-button size="mini" icon="el-icon-document" @click="toggleLabels">{{ showLabels ? '隐藏' : '显示' }}标签</el-button>
|
2025-04-08 16:18:38 +08:00
|
|
|
|
<el-button size="mini" icon="el-icon-video-play" v-if="locationLogList && locationLogList.length > 0" @click="togglePlaybackPanel">{{ isPlaybackVisible ? '隐藏' : '显示' }}轨迹控制台</el-button>
|
2025-03-15 18:38:27 +08:00
|
|
|
|
</el-button-group>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-04-03 20:54:17 +08:00
|
|
|
|
<!-- 轨迹回放控制面板 -->
|
2025-04-10 19:56:40 +08:00
|
|
|
|
<playback-panel
|
2025-04-11 15:35:32 +08:00
|
|
|
|
v-show="isPlaybackVisible && locationLogList.length > 0"
|
2025-04-10 19:56:40 +08:00
|
|
|
|
:current-log="currentLog"
|
|
|
|
|
:current-speed="currentSpeed"
|
|
|
|
|
:max-index="sortedLocationLogs.length - 1"
|
|
|
|
|
:is-playing="isPlaying"
|
|
|
|
|
:playback-speed="playbackSpeed"
|
2025-04-11 15:35:32 +08:00
|
|
|
|
:current-index="currentLogIndex"
|
2025-04-10 19:56:40 +08:00
|
|
|
|
@slider-change="handleSliderChange"
|
|
|
|
|
@toggle-play="togglePlay"
|
|
|
|
|
@speed-change="setPlaybackSpeed"
|
|
|
|
|
/>
|
2025-04-03 20:54:17 +08:00
|
|
|
|
|
2025-03-15 18:38:27 +08:00
|
|
|
|
<!-- 编辑工具栏 -->
|
|
|
|
|
<div class="boundary-tools" v-if="isEditing">
|
2025-04-10 14:07:21 +08:00
|
|
|
|
<el-alert type="warning" :closable="false">
|
2025-03-15 18:38:27 +08:00
|
|
|
|
<template slot="title">
|
|
|
|
|
<div>{{ editSubArea != null ? '编辑' : '新增' }}提示:</div>
|
|
|
|
|
<div>1. 左键点击添加边界点</div>
|
2025-03-17 14:35:07 +08:00
|
|
|
|
<div>2. 点击顶点可删除该点</div>
|
|
|
|
|
<div>3. 双击完成绘制</div>
|
2025-03-15 18:38:27 +08:00
|
|
|
|
</template>
|
|
|
|
|
</el-alert>
|
|
|
|
|
<div class="tool-buttons">
|
|
|
|
|
<el-button type="success" size="mini" @click="saveBoundary">保存</el-button>
|
|
|
|
|
<el-button size="mini" @click="cancelEdit">取消</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import AMapLoader from "@amap/amap-jsapi-loader";
|
|
|
|
|
import globalConfig from "@/utils/config/globalConfig";
|
2025-04-26 18:41:39 +08:00
|
|
|
|
import { mapMethods } from "@/views/bst/areaSub/components/AreaMap";
|
2025-04-11 15:35:32 +08:00
|
|
|
|
import PlaybackPanel from '@/views/bst/areaSub/components/PlaybackPanel.vue';
|
2025-04-26 18:41:39 +08:00
|
|
|
|
import AddressSearch from '@/views/bst/areaSub/components/AddressSearch.vue';
|
|
|
|
|
import OperationArea from './mixins/OperationArea';
|
|
|
|
|
import SubArea from './mixins/SubArea';
|
|
|
|
|
import LocationLog from './mixins/LocationLog';
|
2025-03-15 18:38:27 +08:00
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'AreaMap',
|
2025-04-10 19:56:40 +08:00
|
|
|
|
components: {
|
2025-04-26 18:41:39 +08:00
|
|
|
|
PlaybackPanel,
|
|
|
|
|
AddressSearch
|
2025-04-10 19:56:40 +08:00
|
|
|
|
},
|
2025-04-26 18:41:39 +08:00
|
|
|
|
mixins: [OperationArea, SubArea, LocationLog],
|
2025-04-03 20:54:17 +08:00
|
|
|
|
dicts: ['device_status', 'device_lock_status', 'device_quality'],
|
2025-03-15 18:38:27 +08:00
|
|
|
|
props: {
|
|
|
|
|
// 运营区域信息
|
|
|
|
|
area: {
|
|
|
|
|
type: Object,
|
2025-04-03 20:54:17 +08:00
|
|
|
|
default: () => {}
|
2025-03-15 18:38:27 +08:00
|
|
|
|
},
|
|
|
|
|
// 子区域列表
|
|
|
|
|
areaSubList: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => []
|
2025-04-03 20:54:17 +08:00
|
|
|
|
},
|
|
|
|
|
// 定位日志列表
|
|
|
|
|
locationLogList: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => []
|
|
|
|
|
},
|
|
|
|
|
// 开启编辑
|
|
|
|
|
enableEdit: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true
|
2025-03-15 18:38:27 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
map: null,
|
|
|
|
|
AMap: null,
|
|
|
|
|
mapStyle: 'normal',
|
|
|
|
|
showLabels: true,
|
|
|
|
|
isEditing: false,
|
|
|
|
|
editingPolygon: null,
|
|
|
|
|
isEditingArea: false,
|
2025-04-26 18:41:39 +08:00
|
|
|
|
polygonEditor: null
|
2025-03-15 18:38:27 +08:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
async mounted() {
|
|
|
|
|
await this.initMap();
|
|
|
|
|
this.renderAll();
|
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
2025-04-03 20:54:17 +08:00
|
|
|
|
this.pausePlayback();
|
2025-04-12 18:00:14 +08:00
|
|
|
|
if (this.currentPolylines.length > 0) {
|
|
|
|
|
this.currentPolylines.forEach(polyline => {
|
|
|
|
|
this.map.remove(polyline);
|
|
|
|
|
});
|
2025-04-03 20:54:17 +08:00
|
|
|
|
}
|
|
|
|
|
if (this.currentDeviceMarker) {
|
|
|
|
|
if (this.currentDeviceMarker.snLabel) {
|
|
|
|
|
this.map.remove(this.currentDeviceMarker.snLabel);
|
|
|
|
|
}
|
|
|
|
|
this.map.remove(this.currentDeviceMarker);
|
|
|
|
|
}
|
2025-03-15 18:38:27 +08:00
|
|
|
|
this.destroyMap();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
methods: {
|
2025-04-26 18:41:39 +08:00
|
|
|
|
...mapMethods,
|
|
|
|
|
|
2025-03-15 18:38:27 +08:00
|
|
|
|
// 初始化地图
|
|
|
|
|
async initMap() {
|
|
|
|
|
try {
|
|
|
|
|
this.AMap = await AMapLoader.load({
|
|
|
|
|
key: globalConfig.aMap.key,
|
|
|
|
|
version: '2.0',
|
2025-04-26 18:41:39 +08:00
|
|
|
|
plugins: [
|
|
|
|
|
'AMap.PolygonEditor',
|
|
|
|
|
'AMap.InfoWindow',
|
|
|
|
|
'AMap.AutoComplete',
|
|
|
|
|
'AMap.PlaceSearch'
|
|
|
|
|
],
|
2025-03-15 18:38:27 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 使用 area 的经纬度作为中心点
|
|
|
|
|
const center = [
|
2025-04-03 20:54:17 +08:00
|
|
|
|
this.area?.longitude || 120.35218, // 如果没有经度,使用默认值
|
|
|
|
|
this.area?.latitude || 26.944335 // 如果没有纬度,使用默认值
|
2025-03-15 18:38:27 +08:00
|
|
|
|
];
|
2025-04-12 18:00:14 +08:00
|
|
|
|
|
2025-03-15 18:38:27 +08:00
|
|
|
|
this.map = new this.AMap.Map('container', {
|
|
|
|
|
zoom: 13,
|
|
|
|
|
center: center,
|
|
|
|
|
touchZoom: false,
|
|
|
|
|
dragEnable: true,
|
|
|
|
|
contextMenu: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 初始化多边形编辑器
|
|
|
|
|
this.polygonEditor = new this.AMap.PolygonEditor(this.map);
|
2025-04-12 18:00:14 +08:00
|
|
|
|
|
2025-03-15 18:38:27 +08:00
|
|
|
|
// 绑定编辑器事件
|
|
|
|
|
this.polygonEditor.on('add', this.handlePolygonAdd);
|
|
|
|
|
this.polygonEditor.on('end', this.handlePolygonEnd);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('地图初始化失败:', error);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
handlePolygonAdd({target}) {
|
|
|
|
|
this.editingPolygon = target;
|
|
|
|
|
},
|
|
|
|
|
handlePolygonEnd({target}) {
|
|
|
|
|
this.editingPolygon = target;
|
|
|
|
|
},
|
|
|
|
|
// 渲染所有内容
|
|
|
|
|
renderAll() {
|
|
|
|
|
this.clearEditor();
|
|
|
|
|
this.renderAreaBoundary();
|
|
|
|
|
this.renderSubAreas();
|
2025-04-03 20:54:17 +08:00
|
|
|
|
this.initPlayback();
|
2025-03-15 18:38:27 +08:00
|
|
|
|
},
|
|
|
|
|
// 清除编辑器
|
|
|
|
|
clearEditor() {
|
|
|
|
|
// 关闭编辑器
|
|
|
|
|
if (this.polygonEditor) {
|
|
|
|
|
// 清除编辑器的目标对象
|
|
|
|
|
this.polygonEditor.setTarget();
|
|
|
|
|
}
|
|
|
|
|
// 移除编辑中的多边形
|
|
|
|
|
if (this.editingPolygon) {
|
|
|
|
|
this.map.remove(this.editingPolygon);
|
2025-04-26 18:41:39 +08:00
|
|
|
|
this.editingPolygon = null
|
2025-03-15 18:38:27 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 保存边界
|
|
|
|
|
saveBoundary() {
|
|
|
|
|
if (!this.editingPolygon) {
|
|
|
|
|
this.$message.warning('请先绘制边界');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const path = this.editingPolygon.getPath();
|
|
|
|
|
const boundary = path.map(point => [point.getLng(), point.getLat()]);
|
|
|
|
|
const center = this.calculateCenter(boundary);
|
2025-04-12 18:00:14 +08:00
|
|
|
|
|
2025-03-15 18:38:27 +08:00
|
|
|
|
if (!center) {
|
|
|
|
|
this.$message.error('计算中心点失败');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.isEditingArea) {
|
|
|
|
|
// 更新运营区边界
|
|
|
|
|
this.$emit('update-area-boundary', {
|
2025-03-17 14:35:07 +08:00
|
|
|
|
area: this.area,
|
2025-03-15 18:38:27 +08:00
|
|
|
|
boundary: JSON.stringify(boundary),
|
|
|
|
|
longitude: center[0],
|
|
|
|
|
latitude: center[1]
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2025-03-17 14:35:07 +08:00
|
|
|
|
if (this.editSubArea == null) {
|
|
|
|
|
// 新增子区域
|
|
|
|
|
this.$emit('add-boundary', {
|
|
|
|
|
boundary: JSON.stringify(boundary),
|
|
|
|
|
longitude: center[0],
|
|
|
|
|
latitude: center[1]
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 更新子区域边界
|
|
|
|
|
this.$emit('update-boundary', {
|
|
|
|
|
areaSub: this.editSubArea,
|
|
|
|
|
boundary: JSON.stringify(boundary),
|
|
|
|
|
longitude: center[0],
|
|
|
|
|
latitude: center[1]
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-03-15 18:38:27 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 取消编辑
|
|
|
|
|
cancelEdit() {
|
|
|
|
|
// 重置编辑状态
|
|
|
|
|
this.isEditing = false;
|
|
|
|
|
this.isEditingArea = false;
|
2025-04-12 18:00:14 +08:00
|
|
|
|
|
2025-03-15 18:38:27 +08:00
|
|
|
|
// 移除地图事件监听
|
|
|
|
|
this.map.clearEvents('click');
|
|
|
|
|
this.map.clearEvents('rightclick');
|
|
|
|
|
|
|
|
|
|
// 重新渲染所有区域,确保显示正常
|
2025-04-26 18:41:39 +08:00
|
|
|
|
this.clearEditor();
|
|
|
|
|
if (this.isEditingArea) {
|
|
|
|
|
this.renderAreaBoundary();
|
|
|
|
|
} else {
|
|
|
|
|
this.renderSubAreas();
|
|
|
|
|
}
|
2025-03-15 18:38:27 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 工具方法
|
|
|
|
|
setFitView() {
|
2025-03-17 14:35:07 +08:00
|
|
|
|
this.map.setFitView();
|
2025-03-15 18:38:27 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 切换地图样式
|
|
|
|
|
toggleMapStyle() {
|
|
|
|
|
if (this.mapStyle === 'normal') {
|
|
|
|
|
// 切换到卫星图
|
|
|
|
|
this.mapStyle = 'satellite';
|
|
|
|
|
const satelliteLayer = new this.AMap.TileLayer.Satellite();
|
|
|
|
|
const roadNetLayer = new this.AMap.TileLayer.RoadNet();
|
|
|
|
|
this.map.setLayers([satelliteLayer, roadNetLayer]);
|
|
|
|
|
} else {
|
|
|
|
|
// 切换到普通地图
|
|
|
|
|
this.mapStyle = 'normal';
|
|
|
|
|
const normalLayer = new this.AMap.TileLayer();
|
|
|
|
|
this.map.setLayers([normalLayer]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
destroyMap() {
|
|
|
|
|
if (this.map) {
|
|
|
|
|
this.clearHighlight();
|
|
|
|
|
this.clearOverlays();
|
|
|
|
|
this.map.destroy();
|
|
|
|
|
this.map = null;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.map-container {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
|
|
#container {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.map-tools {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 15px;
|
|
|
|
|
right: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.boundary-tools {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 15px;
|
|
|
|
|
left: 15px;
|
|
|
|
|
z-index: 100;
|
|
|
|
|
width: 200px;
|
|
|
|
|
|
|
|
|
|
.tool-buttons {
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-03 20:54:17 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
@import "@/views/bst/areaSub/components/AreaMap.scss";
|
2025-03-15 18:38:27 +08:00
|
|
|
|
</style>
|