前言

在uniapp中实现的智能多边形绘制页面,涵盖高德地图API集成、逻辑层与RenderJS通信机制,以及移动端地图交互的完整实现方案。

核心技术栈

模块 技术 作用
跨平台框架 uniapp 一套代码多端运行(H5/小程序/APP)
地图引擎 高德地图JSAPI 提供地图渲染、定位、标记等核心能力
通信机制 RenderJS 解耦逻辑层与地图渲染层,避免DOM操作污染

逻辑架构流程图

功能实现全流程解析

页面初始化流程

// 逻辑层 - 页面加载
onLoad(option) {
  this.originPolygon = option.polygon || ""; // 接收传入的多边形数据
  this.checkLoginStatus(); // 检查登录状态
}

// RenderJS - 初始化地图
mounted() {
  this.initAMap(); // 加载高德地图
}

关键设计:通过option.polygon接收上一个页面传入的多边形数据,实现"编辑模式"。


 高德地图核心初始化

initAMap() {
  window._AMapSecurityConfig = { securityJsCode: '你的高德地图密钥' };
  
  AMapLoader.load({
    key: "你的高德地图的key",
    version: "1.4.15",
    plugins: ["AMap.PlaceSearch", "AMap.Scale", "AMap.ToolBar", "AMap.Geolocation", 'misc/PositionPicker'],
    AMapUI: { version: '1.0', plugins: ['misc/PathSimplifier'] }
  }).then(AMap => {
    // 创建地图实例
    map = new AMap.Map("container", {
      pitch: 50,
      viewMode: '3D',
      zoom: 16,
      center: [84.920629, 45.417129],
      layers: [new AMap.TileLayer.Satellite(), new AMap.TileLayer.RoadNet()]
    });
    
    // 添加地图控件
    map.addControl(new AMap.Scale());
    map.addControl(new AMap.ToolBar());
    map.addControl(geolocation); // 添加定位控件
  });
}

技术亮点

  • 使用AMapLoader安全加载地图API
  • 配置3D视角+卫星图层提升地图体验
  • 添加缩放控件和工具栏增强交互性

智能多边形绘制逻辑

addMarker(position) {
  // 保存坐标
  polygonSaveTemp.push([position.lng, position.lat]);
  
  // 创建标记点
  const marker = new AMap.Marker({ position });
  map.add(marker);
  markers.push(marker);
}

createPolygon() {
  if (markers.length >= 3) {
    const path = markers.map(marker => marker.getPosition());
    
    // 移除旧多边形
    if (polygon) map.remove(polygon);
    
    // 创建新多边形
    polygon = new AMap.Polygon({
      path,
      strokeColor: "#FF33FF",
      strokeOpacity: 0.2,
      strokeWeight: 3,
      fillColor: "#1791fc",
      fillOpacity: 0.35
    });
    map.add(polygon);
  }
}

交互设计

  1. 用户点击地图添加标记点(至少3个点)
  2. 点击"保存"按钮自动创建多边形
  3. 保留多边形样式(蓝色填充+紫色边框)

逻辑层与RenderJS通信机制

// RenderJS中保存多边形
saveDiv.addEventListener('click', () => {
  this.createPolygon();
  let result = JSON.stringify(polygonSaveTemp);
  this.$ownerInstance.callMethod('polygonSave', result);
});

// 逻辑层接收结果
polygonSave(result) {
  console.log("逻辑层多边形数据-----》", result);
  this.polygon = result;
  const eventChannel = this.getOpenerEventChannel();
  eventChannel.emit("polygonResult", { result: JSON.stringify(this.polygon) });
  uni.navigateTo({ url: "/pages/main/main" });
}

通信优势

  • 通过callMethod实现双向通信
  • 避免在逻辑层直接操作DOM
  • 保持代码职责清晰(逻辑层处理数据,RenderJS处理渲染)

完整代码:

<template>
  <view>
    <view id="container">
      <view
        :originPolygon="originPolygon"
        :change:originPolygon="renderJS.receiveOriginPolygon"
      />
    </view>
  </view>
</template>

<script>
import { useUserStore } from "../../stores/user";
export default {
  data() {
    return {
      maph: 0,
      //如果需要编辑多边形,传进来多边形字符串
      originPolygon: "", // 接收传入的多边形数据
      polygon: [], // 存储绘制的多边形坐标
      currentLocation: null, // 当前位置信息
    };
  },
  onLoad(option) {
    // 页面加载时检查用户登录状态
    this.checkLoginStatus();
    // 页面加载时获取传入的多边形参数
    this.originPolygon = option.polygon || "";
  },
  mounted() {
    //获取当前定位
    this.$nextTick(() => {
      this.getCurrentLocation();
    });
  },
  methods: {
    // 检查用户登录状态
    checkLoginStatus() {
      try {
        // 使用 Pinia store 检查登录状态
        const userStore = useUserStore();
        // 检查是否已登录
        if (!userStore.isLogin) {
          uni.showToast({
            title: "请先登录",
            icon: "none",
            duration: 2000,
          });

          // 延迟跳转以显示提示信息
          setTimeout(() => {
            uni.redirectTo({
              url: "/pages/login/login",
            });
          }, 2000);

          return false;
        }
        return true;
      } catch (error) {
        console.error("检查登录状态失败:", error);
        uni.redirectTo({
          url: "/pages/login/login",
        });
        return false;
      }
    },
    getCurrentLocation() {
      uni.getLocation({
        type: "gcj02",
        success: (res) => {
          this.currentLocation = {
            lat: res.latitude,
            lng: res.longitude,
          };
          // this.renderJS.receiveCurrentLocation(this.currentLocation)

          // this.$callMethod("receiveCurrentLocation",this.currentLocation)
          // console.log("this.$ownerInstance.callMethod--->",this.$ownerInstance.callMethod)

          // console.log("this.$callMethod--->",this.$callMethod)

          // 延迟调用,确保 renderJS 就绪
          // setTimeout(() => {
          //   if (this.$ownerInstance && this.$ownerInstance.callMethod) {
          //     this.$callMethod("receiveCurrentLocation", this.currentLocation);
          //   } else {
          //     console.warn("renderJS 未就绪,跳过调用");
          //   }
          // }, 500);
        },
        fail: (err) => {
          console.log("getCurrentLocation--fail--->", err);
        },
      });
    },

    polygonSave(result) {
      // 保存多边形数据并返回给上一个页面
      console.log("逻辑层多边形数据-----》", result);
      this.polygon = result;
      const eventChannel = this.getOpenerEventChannel();
      eventChannel.emit("polygonResult", {
        result: JSON.stringify(this.polygon),
      });
      uni.navigateTo({ url: "/pages/main/main" });
    },
  },
};
</script>

<script module="renderJS" lang="renderjs">
import AMapLoader from "@amap/amap-jsapi-loader";

let map = null;// 地图实例
let positionPicker = null;// 位置选择器
let polygon = null;// 多边形对象
let markers = [];// 标记点数组
let positionAddress = null;// 当前位置信息
let polygonSaveTemp = [];// 临时存储多边形坐标
let geolocation = null;// 地理定位对象
export default {
	name: "map-view",
	mounted() {
		// console.log("renderJS开始挂载---》",this)
		this.initAMap();
	},
	methods: {

		initAMap() {
       // 设置安全密钥
			window._AMapSecurityConfig = {
				securityJsCode: 'your keycode',
			}
       // 加载高德地图API
			AMapLoader.load({
				key: "your key",
				version: "1.4.15",
				plugins: ["AMap.PlaceSearch", "AMap.Scale", "AMap.ToolBar","AMap.Geolocation", 'misc/PositionPicker'],
				AMapUI: {
					version: '1.0',
					plugins: ['misc/PathSimplifier', 'overlay/SimpleMarker']
				}
			}).then((AMap) => {
         // 创建地图实例
				map = new AMap.Map("container", {
					pitch: 50, //地图俯仰角度
					viewMode: '3D',
					zoom: 16,
					center: [84.920629, 45.417129], // 设置初始中心点 [lng, lat]
					layers: [new AMap.TileLayer.Satellite(), new AMap.TileLayer.RoadNet()],
					visible: true,
					resizeEnable: true,
					zooms: [2, 20], //地图显示的缩放级别范围
				});
         // 初始化地理定位
				geolocation = new AMap.Geolocation({
					enableHighAccuracy: true, //是否使用高精度定位,默认:true
					timeout: 10000, //超过10秒后停止定位,默认:5s
					buttonPosition: 'RB', //定位按钮的停靠位置
					buttonOffset: new AMap.Pixel(10, 20), //定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
					zoomToAccuracy: true, //定位成功后是否自动调整地图视野到定位点

				});
				scale=new AMap.Scale({
					visible: true,
				})
				toolBar=new AMap.ToolBar({
					visible: true,
					position:'RT'
				})
				map.addControl(scale);
   				map.addControl(toolBar);
				map.addControl(geolocation);

// 				geolocation.getCurrentPosition((status, result) => {
//            console.log('geolocation.getCurrentPosition----->',status, result)

// 				})
         // 加载位置选择器
				AMapUI.loadUI(['misc/PositionPicker'], (PositionPickerClass) => {
					positionPicker = new PositionPickerClass({
						mode: 'dragMap',
						map: map,
					});
           // 监听位置选择成功事件
					positionPicker.on('success', (positionResult) => {
						console.log('positionPicker.on--->', positionResult.position)
						// 处理成功获取位置的结果,保存当前位置
						positionAddress = positionResult.position
					});

					positionPicker.on('fail', (positionResult) => {
						console.log(positionResult)
						// 处理失败获取位置的结果
					});

					map.on("click", (e) => {
						console.log("map.on---->",e.lnglat)
						// 添加标记点
                this.addMarker(e.lnglat);

					});
           // 启动位置选择器
					// positionPicker.start()

				});

         // 地图加载完成事件
				map.on("complete", (e) => {
					this.initDivs()// 初始化控制按钮
				});
			}).catch((e) => {
				console.error(e);
			});
		},
     //接收逻辑层传来的当前位置
 //     receiveCurrentLocation(newPosition){
 //       console.log("receiveCurrentLocation----->",map,newPosition);
 //       map.setCenter([newPosition.lng, newPosition.lat]);
	// map.setZoom(18);

     // },
		addMarker(position) {
       // 检查位置信息是否存在
       if (!position) {
           console.warn('位置信息为空,无法添加标记');
           uni.showToast({
               title: '请先进行定位',
               icon: 'none'
           });
           return;
       }
			console.log('position----->', position)

       // 保存坐标到临时数组
			let temp = [position.lng, position.lat]
			polygonSaveTemp.push(temp)

       // 创建并添加标记点
			const marker = new AMap.Marker({
				position: position,
			});
			map.add(marker);
			markers.push(marker);
		},
		createPolygon() {
			if (markers.length >= 3) {
				const path = markers.map(marker => marker.getPosition());// 获取所有标记点坐标
				// 如果已有旧多边形,则先移除
         if (polygon) {
					map.remove(polygon);
				}
         // 创建新多边形
				polygon = new AMap.Polygon({
					path: path,
					strokeColor: "#FF33FF", //线颜色
					strokeOpacity: 0.2, //线透明度
					strokeWeight: 3, //线宽
					fillColor: "#1791fc", //填充色
					fillOpacity: 0.35, //填充透明度
				});
				map.add(polygon);// 添加到地图
			}
		},
		initDivs() {
       // 创建底部控制栏容器
			const div = document.createElement('div');
			div.style.display = 'flex';
			div.style.flexDirection = 'row';
			div.style.width = 'calc(100% - 40px)';
			div.style.alignItems = 'center';
			div.style.height = '40px';
			div.style.justifyContent = 'space-between';
			div.style.position = 'fixed';
			div.style.bottom = '40px';
			div.style.left = '20px';
			div.style.fontSize = '30px';
			div.style.zIndex = 999
			document.body.appendChild(div);

			const clearDiv = this.createDiv('清除')
			div.appendChild(clearDiv)
			clearDiv.addEventListener('click', () => {
       // 清除所有标记
       if (markers && markers.length > 0) {
           // 逐个移除标记
           markers.forEach(marker => {
               if (marker && map) {
                   map.remove(marker);
               }
           });
           markers = [];
       }

       // 移除多边形
       if (polygon && map) {
           map.remove(polygon);
           polygon = null;
       }

       // 清空保存的坐标数据
       polygonSaveTemp = [];

       // 清除地图上的所有覆盖物(可选)
       if (map) {
           map.clearMap();
       }
   });

			const saveDiv = this.createDiv('保存')
			div.appendChild(saveDiv)
			saveDiv.addEventListener('click', () => {
				//创建多边形
				this.createPolygon();


				let result = JSON.stringify(polygonSaveTemp)
				this.$ownerInstance.callMethod('polygonSave', result)


				console.log('保存')
			});

			// const confirmDiv = this.createDiv('定位')
			// div.appendChild(confirmDiv)
			// confirmDiv.addEventListener('click', () => {
			// 	geolocation.getCurrentPosition()
			// });

		},

		//创建单个div
		createDiv(inner) {
			let div = document.createElement('div');
			div.style.width = '70px';
			div.style.height = '30px';
			div.style.background = '#FFFFFF';
			div.style.boxShadow = '0px 0px 3px 1px rgba(0, 0, 0, 0.1)';
			div.style.borderRadius = '10px';
			div.style.fontSize = '15px';
			div.style.lineHeight = '30px';
			div.style.textAlign = 'center';
			div.innerHTML = inner
			return div
		},
		receiveOriginPolygon() {
			console.log(polygonSaveTemp)
			// 处理接收到的多边形数据
			// 可以将 polygonData 解析并用于初始化地图上的多边形
		},
	},
};
</script>

<style scoped>
#container {
  width: 100vw;
  height: 90vh;
}
</style>

总结:

  1. 优先使用RenderJS:避免在逻辑层操作DOM,这是uniapp地图开发的核心原则
  2. 坐标系转换要谨慎:高德API使用[lng, lat],业务系统常用[lat, lng]
  3. 通信机制要明确callMethod + eventChannel是跨层通信的黄金组合
  4. 用户体验优先:固定底部操作按钮、3D地图视角、即时反馈都是提升体验的关键
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐