electripper-v2-ui/src/views/bst/areaSub/components/AreaMap.vue

824 lines
22 KiB
Vue
Raw Normal View History

2025-03-15 18:38:27 +08:00
<template>
<div class="map-container">
<!-- 地图容器 -->
<div id="container"></div>
<!-- 地图工具栏 -->
<div class="map-tools">
<el-button-group>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="startBoundaryEdit" :disabled="isEditing">新增区域</el-button>
<el-button size="mini" type="warning" icon="el-icon-edit" @click="startAreaBoundaryEdit" :disabled="isEditing">修改运营区</el-button>
<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>
<el-button size="mini" icon="el-icon-document" @click="toggleLabels">{{ showLabels ? '隐藏' : '显示' }}文字</el-button>
</el-button-group>
</div>
<!-- 编辑工具栏 -->
<div class="boundary-tools" v-if="isEditing">
<el-alert type="info" :closable="false">
<template slot="title">
<div>{{ editSubArea != null ? '编辑' : '新增' }}提示</div>
<div>1. 左键点击添加边界点</div>
<div>2. 右键点击空白位置完成绘制</div>
<div>3. 右键点击顶点可删除该点</div>
</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 { SubAreaType, SubAreaStatus } from "@/utils/enums";
import { debounce } from "@/utils/index";
import globalConfig from "@/utils/config/globalConfig";
// 区域样式配置
const AREA_STYLES = {
[SubAreaType.PARKING]: {
strokeColor: '#1890ff',
strokeWeight: 2,
fillColor: '#1890ff',
fillOpacity: 0.3,
strokeStyle: 'solid'
},
[SubAreaType.NO_PARKING]: {
strokeColor: '#ff4d4f',
strokeWeight: 2,
fillColor: '#ff4d4f',
fillOpacity: 0.3,
strokeStyle: 'solid'
},
[SubAreaType.NO_RIDE]: {
strokeColor: '#faad14',
strokeWeight: 2,
fillColor: '#faad14',
fillOpacity: 0.3,
strokeStyle: 'solid'
}
};
// 高亮样式
const HIGHLIGHT_STYLES = {
hover: {
strokeWeight: 3,
fillOpacity: 0.5
},
selected: {
strokeWeight: 4,
fillOpacity: 0.6
}
};
// 图标配置
const MARKER_ICONS = {
[SubAreaType.PARKING]: 'https://lxnapi.ccttiot.com/FqcYf6ecsnbC0OT6YYAF5npgu-kh',
[SubAreaType.NO_PARKING]: 'https://lxnapi.ccttiot.com/FjKE5PWbnEnZUq3k-wVIvV4lv8Ab',
[SubAreaType.NO_RIDE]: 'https://lxnapi.ccttiot.com/FmX1diEPPbFYe1vcUfKp6qbKzzh2'
};
// 标签样式配置
const LABEL_STYLES = {
[SubAreaType.PARKING]: {
backgroundColor: '#1890ff',
color: '#fff'
},
[SubAreaType.NO_PARKING]: {
backgroundColor: '#ff4d4f',
color: '#fff'
},
[SubAreaType.NO_RIDE]: {
backgroundColor: '#faad14',
color: '#fff'
}
};
export default {
name: 'AreaMap',
props: {
// 运营区域信息
area: {
type: Object,
required: true
},
// 子区域列表
areaSubList: {
type: Array,
default: () => []
}
},
data() {
return {
map: null,
AMap: null,
mapStyle: 'normal',
showLabels: true,
isEditing: false,
editingPolygon: null,
editSubArea: null,
isEditingArea: false,
polygonEditor: null,
overlays: {
polygons: new Map(),
markers: new Map(),
labels: new Map()
},
currentInfoWindow: null,
highlightedAreaId: null, // 当前鼠标悬停的区域ID
selectedAreaId: null // 当前选中的区域ID
};
},
watch: {
// 监听区域数据变化
area: {
handler: debounce(function() {
this.renderAreaBoundary();
}, 300),
deep: true
},
// 监听子区域列表变化
areaSubList: {
handler: debounce(function() {
this.renderSubAreas();
}, 300),
deep: true
},
// 监听高亮状态变化
highlightedAreaId: {
handler(newVal, oldVal) {
if (oldVal) {
this.updateAreaStyle(oldVal);
}
if (newVal) {
this.updateAreaStyle(newVal);
}
}
},
// 监听选中状态变化
selectedAreaId: {
handler(newVal, oldVal) {
if (oldVal) {
this.updateAreaStyle(oldVal);
}
if (newVal) {
this.updateAreaStyle(newVal);
}
}
}
},
async mounted() {
await this.initMap();
this.renderAll();
},
beforeDestroy() {
this.destroyMap();
},
methods: {
// 初始化地图
async initMap() {
try {
this.AMap = await AMapLoader.load({
key: globalConfig.aMap.key,
version: '2.0',
plugins: ['AMap.PolygonEditor', 'AMap.InfoWindow']
});
// 使用 area 的经纬度作为中心点
const center = [
this.area.longitude || 120.35218, // 如果没有经度,使用默认值
this.area.latitude || 26.944335 // 如果没有纬度,使用默认值
];
this.map = new this.AMap.Map('container', {
zoom: 13,
center: center,
touchZoom: false,
dragEnable: true,
contextMenu: true
});
// 添加地图点击事件,关闭信息窗体和清除选中状态
this.map.on('click', () => {
if (this.currentInfoWindow) {
this.currentInfoWindow.close();
}
if (!this.isEditing) {
this.selectedAreaId = null;
}
});
// 初始化多边形编辑器
this.polygonEditor = new this.AMap.PolygonEditor(this.map);
// 绑定编辑器事件
this.polygonEditor.on('add', this.handlePolygonAdd);
this.polygonEditor.on('end', this.handlePolygonEnd);
} catch (error) {
console.error('地图初始化失败:', error);
this.$message.error('地图加载失败');
}
},
handlePolygonAdd({target}) {
console.log("handlePolygonAdd", target);
this.editingPolygon = target;
},
handlePolygonEnd({target}) {
console.log("handlePolygonEnd", target);
this.editingPolygon = target;
},
// 渲染所有内容
renderAll() {
this.clearEditor();
this.clearHighlight();
this.clearOverlays();
this.renderAreaBoundary();
this.renderSubAreas();
},
// 清除编辑器
clearEditor() {
// 关闭编辑器
if (this.polygonEditor) {
// 清除编辑器的目标对象
this.polygonEditor.setTarget();
}
// 移除编辑中的多边形
if (this.editingPolygon) {
this.map.remove(this.editingPolygon);
this.editingPolygon = null;
}
},
// 渲染运营区边界
renderAreaBoundary() {
if (!this.map || !this.area.boundaryStr) {
return;
};
try {
const boundary = JSON.parse(this.area.boundaryStr);
const polygon = new this.AMap.Polygon({
path: boundary,
strokeColor: '#2b8cbe',
strokeWeight: 2,
fillColor: '#ccebc5',
fillOpacity: 0.3,
strokeStyle: 'dashed',
strokeDasharray: [5, 5]
});
this.map.add(polygon);
this.overlays.polygons.set('area', polygon);
} catch (error) {
console.error('渲染运营区边界失败:', error);
}
},
// 渲染子区域
renderSubAreas() {
if (!this.map || !this.areaSubList.length) return;
this.clearSubAreas();
this.areaSubList.forEach(subArea => {
if (subArea.status === SubAreaStatus.DISABLED) return;
try {
// 渲染边界
if (subArea.boundaryStr) {
const boundary = JSON.parse(subArea.boundaryStr);
const polygon = new this.AMap.Polygon({
path: boundary,
...AREA_STYLES[subArea.type],
extData: {
id: subArea.id,
type: subArea.type
}
});
// 添加事件监听
polygon.on('mouseover', () => {
if (!this.isEditing) {
this.highlightedAreaId = subArea.id;
}
});
polygon.on('mouseout', () => {
if (!this.isEditing) {
this.highlightedAreaId = null;
}
});
polygon.on('click', (e) => {
if (!this.isEditing) {
// 如果点击的是当前选中的区域,不做任何操作
if (this.selectedAreaId !== subArea.id) {
this.selectedAreaId = subArea.id;
this.showInfoWindow(subArea);
}
}
});
this.map.add(polygon);
this.overlays.polygons.set(subArea.id, polygon);
}
// 渲染标记点
const marker = new this.AMap.Marker({
position: [subArea.longitude, subArea.latitude],
icon: new this.AMap.Icon({
image: MARKER_ICONS[subArea.type],
size: new this.AMap.Size(25, 36),
imageSize: new this.AMap.Size(25, 36)
}),
offset: new this.AMap.Pixel(-12.5, -36)
});
marker.on('click', (e) => {
if (!this.isEditing) {
this.showInfoWindow(subArea);
}
});
this.map.add(marker);
this.overlays.markers.set(subArea.id, marker);
// 渲染文字标签
const label = new this.AMap.Text({
text: subArea.name,
position: [subArea.longitude, subArea.latitude],
offset: new this.AMap.Pixel(0, -60),
anchor: 'center',
cursor: 'pointer',
style: {
...LABEL_STYLES[subArea.type],
border: 'none',
fontSize: '12px',
padding: '2px 8px',
borderRadius: '4px',
whiteSpace: 'nowrap',
textAlign: 'center',
position: 'absolute',
transform: 'translateX(-50%)',
minWidth: '50px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
});
label.on('click', (e) => {
if (!this.isEditing) {
this.showInfoWindow(subArea);
}
});
this.map.add(label);
this.overlays.labels.set(subArea.id, label);
label.setMap(this.showLabels ? this.map : null);
} catch (error) {
console.error(`渲染子区域失败 [${subArea.id}]:`, error);
}
});
},
// 显示信息窗体
showInfoWindow(subArea, focus = false) {
console.log("showInfoWindow", this.isEditing);
if (this.currentInfoWindow) {
this.currentInfoWindow.close();
}
const position = [subArea.longitude, subArea.latitude];
// 聚焦效果
if (focus) {
const polygon = this.overlays.polygons.get(subArea.id);
if (polygon) {
const bounds = polygon.getBounds();
const padding = [150, 300, 150, 300];
this.map.setBounds(bounds, false, padding);
} else {
this.map.setZoomAndCenter(15, position, false, 300);
}
} else {
this.map.setCenter(position);
}
// 创建自定义内容使用Vue的事件处理方式
const content = document.createElement('div');
content.className = 'map-info-window';
content.innerHTML = `
<h4>${subArea.name}</h4>
<div class="info-actions">
<a class="info-action" data-action="edit">编辑信息</a>
<a class="info-action" data-action="boundary">修改边界</a>
<a class="info-action" data-action="delete">删除</a>
</div>
`;
// 直接绑定事件
content.addEventListener('click', (e) => {
const target = e.target;
if (target.classList.contains('info-action')) {
const action = target.getAttribute('data-action');
this.handleInfoAction(action, subArea);
if (this.currentInfoWindow) {
this.currentInfoWindow.close();
}
}
});
this.currentInfoWindow = new this.AMap.InfoWindow({
content: content,
offset: new this.AMap.Pixel(0, -30),
closeWhenClickMap: true
});
// 添加关闭事件监听
this.currentInfoWindow.on('close', () => {
if (!this.isEditing) {
this.selectedAreaId = null;
}
});
this.currentInfoWindow.open(this.map, position);
},
// 处理信息窗体操作
handleInfoAction(action, subArea) {
console.log("action", action);
switch (action) {
case 'edit':
this.$emit('edit', subArea);
break;
case 'boundary':
this.startBoundaryEdit(subArea);
break;
case 'delete':
this.$emit('delete', subArea.id);
break;
}
},
// 计算多边形中心点
calculateCenter(path) {
if (!path || path.length === 0) return null;
let total = path.length;
let X = 0, Y = 0, Z = 0;
path.forEach(point => {
let lng = (point[0] * Math.PI) / 180;
let lat = (point[1] * Math.PI) / 180;
let x = Math.cos(lat) * Math.cos(lng);
let y = Math.cos(lat) * Math.sin(lng);
let z = Math.sin(lat);
X += x;
Y += y;
Z += z;
});
X = X / total;
Y = Y / total;
Z = Z / total;
let Lng = Math.atan2(Y, X);
let Hyp = Math.sqrt(X * X + Y * Y);
let Lat = Math.atan2(Z, Hyp);
return [
(Lng * 180) / Math.PI,
(Lat * 180) / Math.PI
];
},
// 开始边界编辑
startBoundaryEdit(subArea) {
this.clearEditor();
if (subArea && subArea.boundaryStr) {
console.log("subArea", subArea);
try {
const boundary = JSON.parse(subArea.boundaryStr);
this.editingPolygon = new this.AMap.Polygon({
path: boundary,
...AREA_STYLES[subArea.type]
});
this.map.add(this.editingPolygon);
this.polygonEditor.setTarget(this.editingPolygon);
} catch (error) {
console.error('加载边界数据失败:', error);
this.$message.error('加载边界数据失败');
}
}
this.polygonEditor.open();
this.startEditArea();
},
// 保存边界
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);
if (!center) {
this.$message.error('计算中心点失败');
return;
}
console.log(boundary);
if (this.isEditingArea) {
// 更新运营区边界
this.$emit('update-area-boundary', {
boundary: JSON.stringify(boundary),
longitude: center[0],
latitude: center[1]
});
} else if (this.editSubArea == null) {
// 新增子区域
this.$emit('add-boundary', {
boundary: JSON.stringify(boundary),
longitude: center[0],
latitude: center[1]
});
} else {
// 更新子区域边界
this.$emit('update-boundary', {
area: this.editSubArea,
boundary: JSON.stringify(boundary),
longitude: center[0],
latitude: center[1]
});
}
this.cancelEdit();
},
// 取消编辑
cancelEdit() {
// 重置编辑状态
this.isEditing = false;
this.editSubArea = null;
this.isEditingArea = false;
// 移除地图事件监听
this.map.clearEvents('click');
this.map.clearEvents('rightclick');
// 重新渲染所有区域,确保显示正常
this.renderAll();
},
// 工具方法
setFitView() {
this.map.setFitView(null, false, [150, 60, 100, 60]);
},
// 切换地图样式
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]);
}
},
toggleLabels() {
this.showLabels = !this.showLabels;
this.overlays.labels.forEach(label => {
label.setMap(this.showLabels ? this.map : null);
});
},
// 清理方法优化
clearOverlays() {
Object.values(this.overlays).forEach(overlayMap => {
overlayMap.forEach(overlay => {
this.map.remove(overlay);
});
overlayMap.clear();
});
},
clearSubAreas() {
['polygons', 'markers', 'labels'].forEach(type => {
this.overlays[type].forEach((overlay, key) => {
if (key !== 'area') {
this.map.remove(overlay);
this.overlays[type].delete(key);
}
});
});
},
// 更新区域样式
updateAreaStyle(areaId) {
const polygon = this.overlays.polygons.get(areaId);
if (!polygon) return;
const type = polygon.getExtData().type;
let style = { ...AREA_STYLES[type] };
// 根据状态叠加高亮样式
if (areaId === this.selectedAreaId) {
style = { ...style, ...HIGHLIGHT_STYLES.selected };
} else if (areaId === this.highlightedAreaId) {
style = { ...style, ...HIGHLIGHT_STYLES.hover };
}
polygon.setOptions(style);
},
// 清除所有高亮状态
clearHighlight() {
this.highlightedAreaId = null;
this.selectedAreaId = null;
},
destroyMap() {
if (this.map) {
this.clearHighlight();
this.clearOverlays();
this.map.destroy();
this.map = null;
}
},
// 开始编辑运营区边界
startAreaBoundaryEdit() {
this.clearEditor();
if (this.area.boundaryStr) {
try {
const boundary = JSON.parse(this.area.boundaryStr);
this.editingPolygon = new this.AMap.Polygon({
path: boundary,
strokeColor: '#2b8cbe',
strokeWeight: 2,
fillColor: '#ccebc5',
fillOpacity: 0.3,
strokeStyle: 'dashed',
strokeDasharray: [5, 5]
});
this.map.add(this.editingPolygon);
this.polygonEditor.setTarget(this.editingPolygon);
} catch (error) {
console.error('加载运营区边界数据失败:', error);
this.$message.error('加载运营区边界数据失败');
}
} else {
// 创建一个新的运营区多边形
this.editingPolygon = new this.AMap.Polygon({
path: [],
strokeColor: '#2b8cbe',
strokeWeight: 2,
fillColor: '#ccebc5',
fillOpacity: 0.3,
strokeStyle: 'dashed',
strokeDasharray: [5, 5]
});
this.map.add(this.editingPolygon);
this.polygonEditor.addAdsorbPolygons(this.editingPolygon)
}
this.polygonEditor.open();
this.startEditArea();
},
// 开始绘制(不管是那种边界)
startEditArea() {
this.isEditing = true;
this.isEditingArea = true;
// 关闭当前信息窗口
if (this.currentInfoWindow) {
this.currentInfoWindow.close();
}
},
// 设置为新建模式
setAddMode() {
// 添加地图点击事件监听,用于绘制多边形
const clickHandler = (e) => {
const path = this.editingPolygon.getPath();
path.push([e.lnglat.getLng(), e.lnglat.getLat()]);
this.editingPolygon.setPath(path);
};
// 添加右键点击事件监听,用于完成绘制
const rightClickHandler = () => {
this.map.off('click', clickHandler);
this.map.off('rightclick', rightClickHandler);
if (this.editingPolygon.getPath().length < 3) {
this.$message.warning('请至少绘制3个点形成多边形');
return;
}
this.polygonEditor.setTarget(this.editingPolygon);
this.polygonEditor.open();
};
this.map.on('click', clickHandler);
this.map.on('rightclick', rightClickHandler);
}
}
};
</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;
z-index: 100;
}
.boundary-tools {
position: absolute;
top: 15px;
left: 15px;
z-index: 100;
width: 200px;
.tool-buttons {
margin-top: 10px;
text-align: left;
}
}
}
</style>
<style lang="scss">
// 信息窗体样式
.map-info-window {
padding: 5px;
font-size: 12px;
h4 {
margin: 0 0 10px;
font-size: 14px;
}
.info-actions {
display: flex;
gap: 10px;
}
.info-action {
color: #409eff;
cursor: pointer;
text-decoration: none;
&:hover {
color: #66b1ff;
}
}
}
</style>