效果图
想法:点击地图生成一个marker,至少三个marker形成电子围栏,点击第四个以上改变电子围栏的形状, 点击已生成的marker,从电子围栏去掉。
这里使用的是renderjs,我原本的开发模式是vue3+ts,由于uniapp官网的map组件和ucharts无法实现定制需求。 为了高德地图和echarts的开发,不得不变成vue3+ts 与 vue2renderjs的混用,庆幸uniapp兼容这种混用的写法。
renderjs不支持小程序,所以有app+小程序适配环境的朋友们,可以写俩套文件逻辑,分别去适配app和小程序。如果有更好的方案,欢迎大佬们留言。
实现功能:电子围栏的增删改查
注意事项:除了官网的以外,本人踩坑。
1. 不支持vue-i18n, pinia等vue3插件, 所以在语言化方面和存储方面 尽可能使用uniapp内置的。
2. 逻辑层和视图层的变量 不要一样的,容易混淆并且 变量数据会融合。
话不多说 看代码,代码有注释
<!-- 地图 -->
<view id="fenceMap" style="width: 100%;height:100%;" ref="mapRef" :info="mapConfig"
:change:info="fencedetailAmap.updateData">
</view>
普通script 逻辑层代码:
data() {
return {
keyCode: amap_web_key_passwordTest,
key: amap_web_keyTest,
lange: '',
deviceId: '', // 设备id
state: '', // add edit look
fenceNumber: '', // 围栏编号
addressList: [], // 设备经纬度 lng lat
mapConfig: {},
fencePointsList: [], // 存储电子围栏的顶点坐标
}
},
onLoad(options) {
if (options && options.info) {
const info = JSON.parse(options.info)
this.state = info.state
this.deviceId = info.deviceId
this.lange = info.locale
this.addressList = info.address
if (info.iteminfo) {
this.fenceNumber = info.iteminfo.number
this.fencePointsList = info.iteminfo.coordinate_set
}
}
},
mounted() {
this.changeMapConfig()
},
methods: {
changeMapConfig() {
const WG = gcoord.transform(
[Number(this.addressList.lng), Number(this.addressList.lat)], // 经纬度坐标
gcoord.WGS84, // 当前坐标系
gcoord.GCJ02 // 目标坐标系
);
this.mapConfig = {
key: this.key,
keyCode: this.keyCode,
lange: this.lange,
state: this.state,
addressList: this.addressList,
center: [Number(WG[0].toFixed(6)), Number(WG[1].toFixed(6))],
fencePointsList: [...this.fencePointsList]
}
},
// renderjs传来的数据,
updeRenderData(data) {
console.log('renderjs传过来的', data);
this.fencePointsList = data
},
btnClick() {
if (!this.fenceNumber) {
uni.showToast({
icon: 'none',
title: this.lange === 'en' ? 'Please enter the fence number' : '请输入围栏编号'
})
return;
}
// 新增
if (this.state === 'add') {
this.addFence()
} else if (this.state === 'edit') {
this.addFence()
} else if (this.state === 'look') {
this.state = 'edit'
this.mapConfig.state = 'edit'
}
},
// 新增
async addFence() {
if (this.fencePointsList.length < 3) {
uni.showToast({
icon: 'none',
title: this.lange === 'en' ? 'At least three coordinates' : '坐标点至少三个'
})
return;
}
await addElefenceApi({}, true, {
number: this.fenceNumber,
device_id: this.deviceId,
coordinate_set: JSON.stringify(this.fencePointsList.map(item => {
return {
lng: item[0],
lat: item[1]
}
}))
})
uni.$emit('updataList')
uni.navigateBack()
},
// 提示
explainOpen() {
this.$refs.notify.show();
},
// 返回
leftClick() {
uni.navigateBack()
}
}
renderjs 视图层代码:
<script module="fencedetailAmap" lang="renderjs">
data() {
return {
map: null, // 用于存储地图对象
marker: null, // 存储marker对象 设备所在地
fenceMarkers: [], // 存储所有生成的 markers
polygon: null, // 存储电子围栏(多边形)
fencePoints: [], // 存储电子围栏的顶点坐标
amapAllInfo: {},
}
},
methods: {
updateData(newValue, oldValue, ownerInstance, instance) {
this.$nextTick(() => {
if (newValue && newValue.state) {
this.amapAllInfo = newValue
console.log('renderjs更新触发', this.amapAllInfo);
// this.$ownerInstance.callMethod('getMessage', this.amapAllInfo)
this.initAMap();
}
});
},
isApp() {
// 判断是否在 APP 环境中(根据具体的环境进行判断)
return !!(uni.getSystemInfoSync && uni.getSystemInfoSync().platform === 'app');
},
// 初始化高德地图 (在app中使用script加载方式速度更快,通过AMapLoader加载兼容性更好,首页需要加载快优先使用script方式)
initAMap() {
// if (this.isApp()) {
// 在 APP 中使用 script 标签方式加载
if (!window.AMap) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src =
`https://webapi.amap.com/maps?v=2.0&key=${this.amapAllInfo.key}&plugins=AMap.Scale,AMap.Polygon,AMap.Marker`;
script.async = true;
script.onload = () => {
this.loadAMap(); // 地图脚本加载完成后执行
};
script.onerror = () => {
console.error('加载高德地图失败');
};
document.head.appendChild(script);
} else {
this.loadAMap(); // 如果 AMap 已经加载,直接调用
}
// }
// else {
// // 在 H5 和小程序中使用 AMapLoader
// AMapLoader.load({
// key: this.amapAllInfo.key, // 高德地图 API 密钥
// version: '2.0', // 高德地图 API 的版本
// plugins: ['AMap.Scale'], // 需要加载的插件
// }).then(AMap => {
// this.loadAMap(AMap); // 地图脚本加载完成后执行
// }).catch((error) => {
// console.error('加载高德地图失败', error);
// });
// }
},
// loadAMap(AMap) {
loadAMap() {
try {
// 初始化地图
this.map = new AMap.Map('fenceMap', {
zoom: 14,
center: this.amapAllInfo.center
});
const icon = new AMap.Icon({
size: new AMap.Size(36, 36), //图标尺寸
image: "static/icon_map_mark_red.png", //Icon 的图像
imageOffset: new AMap.Pixel(0, 0), //图像相对展示区域的偏移量,适于雪碧图等
imageSize: new AMap.Size(36, 36), //根据所设置的大小拉伸或压缩图片
});
this.marker = new AMap.Marker({
position: new AMap.LngLat(this.amapAllInfo.center[0], this.amapAllInfo.center[1]), //点标记的位置
offset: new AMap.Pixel(-10, -30), //偏移量
icon: icon, //添加 Icon 实例
});
// 回显
if (['look', 'edit'].includes(this.amapAllInfo.state)) {
console.log('回显', this.amapAllInfo.fencePointsList);
// 由于传给后端格式是[{lng:'xx',lat:'xx'},{lng:'xx',lat:'xx'}],
// 这种格式在回显 marker和电子围栏时会报错, 所以需要转换下格式。
this.amapAllInfo.fencePointsList.forEach((position) => {
this.addMarker(new AMap.LngLat(parseFloat(position.lng), parseFloat(position.lat)));
});
// 创建电子围栏
if (this.fencePoints.length >= 3) {
this.createFence();
}
}
// 点击地图
this.map.on('click', (MapsEvent) => {
console.log('地图事件');
this.onMapClick(MapsEvent)
})
this.map.add(this.marker)
} catch (error) {
console.error('初始化地图失败', error);
}
},
// 点击地图
onMapClick(e) {
if (this.amapAllInfo.state === 'look') return;
const position = e.lnglat; // 获取点击的位置
console.log('点击地图position', position);
this.addMarker(position); // 添加marker
if (this.fencePoints.length >= 3) { // 如果已经有 3 个点,开始创建电子围栏
this.createFence();
}
if (this.fencePoints.length > 3) { // 如果超过 3 个点,允许改变围栏形状
this.updateFenceShape();
}
},
// 添加marker标记
addMarker(position) {
console.log('添加数组的position', position);
const icon = new AMap.Icon({
size: new AMap.Size(36, 36),
image: "static/icon_location.png",
imageOffset: new AMap.Pixel(0, 0),
imageSize: new AMap.Size(36, 36),
});
const marker = new AMap.Marker({
position, // 点标记在地图上显示的位置
offset: new AMap.Pixel(-10, -30), //偏移量
icon: icon, //添加 Icon 实例
map: this.map, // marker所在的地图对象
});
// 如果数组中已经存在,就不push进去
this.fenceMarkers.push(marker); // 将新 marker 添加到 markers 数组中
this.fencePoints.push(position); // 更新围栏顶点
// 地图使用onclick事件和marker的onlick事件会起冲突,
// 点击marker事件会被冒泡到地图事件上,使用 e.stopPropagation() 和 e.preventDefault() 均不生效, 所以删除marker改成了双击
marker.on('dblclick', () => { // 如果点击的是已存在的 marker,移除该 marker
console.log('marker事件');
if (this.amapAllInfo.state === 'look') return;
this.removeMarker(marker);
});
},
// 创建电子围栏(多边形)
createFence() {
// 只有在有 3 个以上的点时才创建围栏
if (this.fencePoints.length >= 3) {
// 清除之前的围栏
if (this.polygon) {
this.map.remove(this.polygon);
this.polygon = null;
}
}
// 使用围栏顶点创建一个多边形
this.polygon = new AMap.Polygon({
path: this.fencePoints, // 多边形轮廓线的节点坐标数组
strokeColor: '#0000FF', // 线条颜色,
strokeWeight: 3, // 轮廓线宽度
strokeOpacity: 0.5, // 轮廓线透明度,取值范围 [0,1] ,0表示完全透明,1表示不透明。默认为0.5
fillColor: '#0000FF', // 多边形填充颜色,
fillOpacity: 0.3, // 多边形填充透明度,取值范围 [0,1] ,0表示完全透明,1表示不透明。默认为0.5
map: this.map,
});
this.updeRender()
},
// 更新围栏形状(当点数大于 3 时)
updateFenceShape() {
if (this.fencePoints.length > 3) {
// 例如:可以修改为圆形或其他形状
// 或者直接将围栏更新为包含所有点的多边形
this.polygon.setPath(this.fencePoints); // 更新围栏形状
}
this.updeRender()
},
// 从围栏中移除一个 marker
removeMarker(marker) {
// 从 fenceMarkers 数组中移除该 marker
const index = this.fenceMarkers.indexOf(marker);
if (index !== -1) {
this.fenceMarkers.splice(index, 1);
}
// 从 fencePoints 中移除该点,转换 marker.getPosition() 为经纬度数组进行比较
const position = marker.getPosition();
console.log('position', position);
console.log('this.fencePoints', this.fencePoints);
const pointIndex = this.fencePoints.findIndex(point => {
return point.lng === position.lng && point.lat === position.lat;
});
console.log('pointIndex', pointIndex);
if (pointIndex !== -1) {
this.fencePoints.splice(pointIndex, 1);
}
// 更新围栏
if (this.fencePoints.length >= 3) {
this.createFence(); // 重新创建围栏
} else {
// 如果少于 3 个点,移除围栏
if (this.polygon) {
this.map.remove(this.polygon);
this.polygon = null;
}
}
// 从地图上移除 marker
this.map.remove(marker);
this.updeRender()
},
updeRender() {
console.log('向着逻辑层发送', this.fencePoints);
this.$ownerInstance.callMethod('updeRenderData', this.fencePoints)
}
}