vue3中封装基础高德地图组件

152 阅读6分钟
  1. 安装依赖

pnpm install @amap/amap-jsapi-loader --save

  1. 实现组件 ReAMap, 由useAMap.tsindex.vue构成

功能:通过传输经纬度,渲染展示地图。 代码如下

useAMap.ts

import { shallowRef } from "vue";
import AMapLoader from "@amap/amap-jsapi-loader";

export function useAMap() {
  const map = shallowRef(null);

  const initMap = async (container: string | HTMLElement, options?: object) => {
    try {
      const AMap = await AMapLoader.load({
        key: "xxxx", // 生产环境建议将 Key 存储在配置文件中
        version: "2.0",
        plugins: []
      });
      map.value = new AMap.Map(container, {
        zoom: 11,
        center: [116.39, 39.9],
        ...options
      });
    } catch (error) {
      console.error("地图加载失败:", error);
    }
  };

  const destroyMap = () => {
    if (map.value) {
      map.value.destroy();
      map.value = null;
    }
  };

  return { map, initMap, destroyMap };
}

index.vue


<template>
  <div ref="mapContainer" class="re-amap-container" />
</template>

<script setup lang="ts">
import {
  ref,
  onMounted,
  onUnmounted,
  defineProps,
  withDefaults,
  defineExpose,
  watch
} from "vue";
import { useAMap } from "./useAMap";

// 定义 emits
const emit = defineEmits(["map-click"]);

// 定义 props
interface Props {
  options?: object;
  markerOptions?: object;
  infoWindow?: object;
}
const props = withDefaults(defineProps<Props>(), {
  options: () => ({}),
  markerOptions: null,
  infoWindow: null
});

const mapContainer = ref<HTMLElement | null>(null);
const { map, initMap, destroyMap } = useAMap();

// 监听地图实例创建
watch(map, newMap => {
  if (newMap) {
    // 绑定地图点击事件
    newMap.on("click", (e: any) => {
      emit("map-click", e.lnglat);
    });

    // 处理标记点和信息窗
    if (props.markerOptions) {
      const AMap = (window as any).AMap;
      const marker = new AMap.Marker(props.markerOptions);
      newMap.add(marker);

      if (props.infoWindow) {
        const infoWindow = new AMap.InfoWindow(props.infoWindow);
        marker.on("click", () => {
          infoWindow.open(newMap, marker.getPosition());
        });
      }
    }
  }
});

onMounted(() => {
  if (mapContainer.value) {
    initMap(mapContainer.value, props.options);
  }
});

onUnmounted(() => {
  destroyMap();
});

// 将 map 实例暴露给父组件
defineExpose({
  map
});
</script>

<style scoped>
.re-amap-container {
  width: 100%;
  height: 100%;
}
</style>

  1. 使用组件

省略了一部分代码,只贴主要的关键代码,对地图组件有什么操作,通过ref去控制即可

<script lang="ts" setup>
import { ref } from "vue";

const amapRef = ref(null);

const mapOptions = {
  center: [116.39, 39.9],
  zoom: 16
};

const markerOptions = {
  position: [116.39, 39.9],
  title: "xxx"
};

const infoWindowOptions = {
  isCustom: false, // 使用高德默认样式
  autoMove: true, // 点击标记点后自动平移地图,使信息窗完整显示
  offset: [11, -60], // 设置信息窗的偏移量
  content:
    '<div style="color: #333; font-size: 14px; padding: 10px;">地址:xxxx</div>'
};

const handleMapClick = lnglat => {
  console.log("地图点击位置:", lnglat);
};

</script>

<template>
    <div class="h-[173px] w-[526px]">
        <ReAMap
          ref="amapRef"
          :options="mapOptions"
          :marker-options="markerOptions"
          :info-window="infoWindowOptions"
          @map-click="handleMapClick"
        />
      </div>
</template>

实现效果可参考下图

image.png


背景介绍

铛铛~ 今天有一版优化,之前是通过传输经纬度,进行渲染展示地图。

今天的需求是,只给地址,根据地址去获取地理编码。

除了pnpm install @amap/amap-jsapi-loader --save 外,还要用到安全密钥去使用AMap.Geocoder,(新版高德 JS API 都强制要求)。

AMap.Geocoder地理编码与逆地理编码服务,用于地址描述与经纬度坐标之间的转换,可以通过回调函数获取查询结果。

正向地理编码: 比如在搜索页面,用户输入的是某个地址描述信息,然后调用正向地理编码接口,将地址描述信息转换成地理坐标(经纬度)

逆向地理编码:比如在某个地图页面,用户可以通过点选地图选择点,然后调用头逆地理编码接口,将地理坐标(经纬度)转换成地址描述信息

new AMap.Geocoder(opts: GeocoderOptions)

GeocoderOptions参数介绍 参数名 默认值 描述 city "全国" 城市,地理编码时,设置地址描述所在城市可选值:城市名(中文或中文全拼)、citycode、adcode radius 1000 逆地理编码时,以给定坐标为中心点,单位:米,取值范围:0 - 3000 lang "zh_cn" 设置语言类型 可选值:zh_cn(中文)、en(英文) batch - 是否批量查询,batch 设置为 false 时,只返回第一条记录 extensions "base" 逆地理编码时,返回信息的详略 base:返回基本地址信息,all:返回地址信息及附近poi、道路、道路交叉口等信息

GeocoderOptions参数介绍

参数名默认值描述
city"全国"城市,地理编码时,设置地址描述所在城市,可选值:城市名(中文或中文全拼)、citycode、adcode
radius1000逆地理编码时,以给定坐标为中心点,单位:米,取值范围:0 - 3000
lang"zh_cn"设置语言类型,可选值:zh_cn(中文)、en(英文)
batch-是否批量查询,batch 设置为 false 时,只返回第一条记录
extensions"base"逆地理编码时,返回信息的详略:base 返回基本地址信息;all 返回地址信息及附近 poi、道路、道路交叉口等信息

方法介绍

方法名说明参数值描述
getLocation(keyword, cbk)将地址信息转化为高德经纬度坐标信息keyword (String):关键字;cbk (GeocoderCallback):回调函数
getAddress(location, cbk)将高德经纬度坐标信息转化为结构化的地址信息location (LngLat | Array):给定坐标;cbk (ReGeocoderCallback):回调函数

安全密钥在高德控制台复制,和key是配套的,见下图。

image.png

引入安全密钥

一定要在使用import AMapLoader from "@amap/amap-jsapi-loader";之前引入

① 可以在main.ts (src/main.ts)中引入

// 高德地图安全密钥配置 (重要:必须在 AMapLoader.load 之前设置)

(window as any)._AMapSecurityConfig = {
  securityJsCode: "xxx" // 安全密钥
};

② 也可以在自己封装的方法顶部引入。

实现组件 ReAMap, 依然由useAMap.tsindex.vue构成

useAMap.ts

// 高德地图安全密钥配置 (重要:必须在 AMapLoader.load 之前设置)
(window as any)._AMapSecurityConfig = {
  securityJsCode: "xxx"
};

import { shallowRef } from "vue";
import AMapLoader from "@amap/amap-jsapi-loader";

/**
 * @description 高德地图组合式函数
 */
export function useAMap() {
  // shallowRef 用于存储地图实例,避免深层响应式带来的性能问题
  const map = shallowRef(null);

  /**
   * @description 初始化地图
   * @param container 地图容器的 DOM 元素或 ID
   * @param options 地图配置项
   */
  const initMap = async (container: string | HTMLElement, options?: object) => {
    try {
      // 异步加载高德地图 JSAPI
      const AMap = await AMapLoader.load({
        key: "xxx", // Web端开发者Key
        version: "2.0", // 指定要加载的 JSAPI 的版本
        plugins: ["AMap.Geocoder"] // 需要使用的插件列表,例如:地理编码插件
      });
      // 创建地图实例
      map.value = new AMap.Map(container, {
        zoom: 11, // 默认缩放级别
        center: [116.39, 39.9], // 默认中心点
        ...options // 合并外部传入的配置
      });
    } catch (error) {
      console.error("地图加载失败:", error);
    }
  };

  /**
   * @description 销毁地图实例
   */
  const destroyMap = () => {
    if (map.value) {
      map.value.destroy();
      map.value = null;
    }
  };

  return { map, initMap, destroyMap };
}

index.vue

<template>
  <div ref="mapContainer" class="re-amap-container" />
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, defineExpose } from "vue";
import { useAMap } from "./useAMap";

// 定义组件接收的 props
const props = defineProps({
  // 用于地址解析模式的地址字符串
  address: String,
  // 地图初始化时的高级配置项
  options: Object,
  // (模式1) 手动传入的标记点配置
  markerOptions: Object,
  // (模式2) 手动传入的信息窗体配置
  infoWindow: Object
});

// 用于挂载地图容器的 DOM 引用
const mapContainer = ref<HTMLElement | null>(null);
// 从组合式函数中获取地图实例和控制方法
const { map, initMap, destroyMap } = useAMap();

/**
 * @description 根据地址字符串渲染地图标记点
 * @param address 详细地址
 */
const renderAddressMarker = (address: string) => {
  const AMap = (window as any).AMap;
  // 确保 AMap 和地图实例都已加载
  if (!AMap || !map.value) return;

  // 渲染前清除旧的覆盖物
  map.value.clearMap();

  // 实例化地理编码服务
  const geocoder = new AMap.Geocoder({ city: "全国" });
  // 调用 getLocation 将地址转换为经纬度
  geocoder.getLocation(address, (status, result) => {
    if (status === "complete" && result.info === "OK") {
      const loc = result.geocodes[0].location;
      const pos = [loc.lng, loc.lat];
      // 设置地图中心点
      map.value.setCenter(pos);
      // 创建并添加标记点
      const marker = new AMap.Marker({ position: pos });
      map.value.add(marker);

      // 创建并配置信息窗体
      const infoWindow = new AMap.InfoWindow({
        isCustom: false,
        autoMove: true,
        offset: [0, -30],
        content: `<div style="color: #333; font-size: 14px; padding: 10px;">地址:${address}</div>`
      });
      // 绑定标记点点击事件,打开信息窗体
      marker.on("click", () => infoWindow.open(map.value, marker.getPosition()));
      // 默认直接打开信息窗体
      infoWindow.open(map.value, marker.getPosition());
    } else {
      console.error("高德地图地址解析失败:", { address, status, result });
    }
  });
};

/**
 * @description 根据手动传入的 markerOptions 渲染标记点 (兼容旧模式)
 */
const renderOptionsMarker = () => {
  const AMap = (window as any).AMap;
  if (!AMap || !map.value || !props.markerOptions) return;

  map.value.clearMap();
  const marker = new AMap.Marker(props.markerOptions);
  map.value.add(marker);
  if (props.infoWindow) {
    const infoWindow = new AMap.InfoWindow(props.infoWindow);
    marker.on("click", () => infoWindow.open(map.value, marker.getPosition()));
  }
};

// 组件挂载后初始化地图,并根据 props 执行首次渲染
onMounted(async () => {
  if (mapContainer.value) {
    await initMap(mapContainer.value, props.options);
    // 优先使用 address 模式
    if (props.address) {
      renderAddressMarker(props.address);
    } else if (props.markerOptions) {
      // 其次使用 markerOptions 兼容模式
      renderOptionsMarker();
    }
  }
});

// 监听 address prop 的变化,以动态更新地图
watch(
  () => props.address,
  newAddr => {
    if (map.value && newAddr) renderAddressMarker(newAddr);
  }
);

// 监听 markerOptions prop 的变化 (兼容旧模式)
watch(
  () => props.markerOptions,
  () => {
    // 仅在 address 模式不生效时,才启用此 watch
    if (map.value && props.markerOptions && !props.address) {
      renderOptionsMarker();
    }
  },
  { deep: true }
);

// 组件卸载时销毁地图实例,防止内存泄漏
onUnmounted(() => {
  destroyMap();
});

// 将 map 实例暴露给父组件,允许父组件直接操作地图
defineExpose({
  map
});
</script>

<style scoped>
.re-amap-container {
  width: 100%;
  height: 100%;
}
</style>

使用组件
<script setup lang="ts">
import ReAMap from "@/components/ReAMap/index.vue";

const amapRef = ref(null);
const companyAddressForMap = ref("");
// 自己写方法获取要的地址
</script>

<template>
    <ReAMap ref="amapRef" :address="companyAddressForMap" />
</template>