Vue3中封装高德地图使用

Vue3 + TypeScript 中高德地图的完整封装与使用

从依赖安装到业务组件封装,完整梳理一个 Vue3 项目中高德地图 SDK 的分层架构与实践方法,涵盖地图加载、位置展示、地址搜索、PC/移动端定位等核心场景。


目录


一、整体架构概览

项目中高德地图的封装采用分层架构设计,从底层配置到上层业务组件,职责清晰、易于复用。整体分为四层:

复制代码
┌─────────────────────────────────────────────────────┐
│  配置层: sdk_config.ts  │  类型声明: amap.d.ts       │
├─────────────────────────────────────────────────────┤
│              核心 Hook: useAmap.ts                    │
│         (SDK加载 / 实例化 / 插件注册 / 销毁)           │
├─────────────────────────────────────────────────────┤
│  位置展示组件    │  地址搜索组件  │  PC定位预览  │  移动端定位  │
│ amap_location   │ amap_navigation│  _pc.vue    │  _mobile.vue│
├─────────────────────────────────────────────────────┤
│        基类组件: base_map_*.vue (SDK切换层)           │
│              ↓ 业务调用(表单 / 页面)                 │
└─────────────────────────────────────────────────────┘
  • 配置层:统一管理 API Key、安全密钥、标记图标等,支持高德/百度地图切换
  • Hook 层:封装 SDK 加载、地图实例化、销毁等通用逻辑
  • 组件层:针对不同业务场景封装独立组件(展示、搜索、定位)
  • 基类层:通过配置动态切换地图 SDK(AMAP/BMAP),对业务透明

二、依赖安装

使用高德地图 JS API 需要安装官方提供的 Loader 和类型定义包:

bash 复制代码
# 高德地图 JS API Loader(动态加载 SDK)
npm install @amap/amap-jsapi-loader

# 高德地图 TypeScript 类型定义
npm install @amap/amap-jsapi-types

# 或使用 pnpm
pnpm add @amap/amap-jsapi-loader @amap/amap-jsapi-types
包名 作用 项目版本
@amap/amap-jsapi-loader 动态加载高德地图 JS API 2.0,避免在 index.html 中硬编码 script 标签 ^1.0.1
@amap/amap-jsapi-types 提供 AMap 命名空间下的 TypeScript 类型声明 ^0.0.15

三、配置管理

将所有地图相关的配置集中管理,包括 API Key、安全密钥、标记图标 URL 等。这样做的好处是统一维护、方便切换环境

typescript 复制代码
// src/config/sdk_config.ts

export const MAP_CONF = {
  BASE_MAP_TYPE: 'AMAP',                        // AMAP | BMAP,控制使用高德还是百度
  AMAP_API_KEY: 'your_amap_api_key',            // 高德地图 API Key
  AMAP_SECRET_KEY: 'your_amap_secret_key',      // 安全密钥(JS API 2.0 必填)
  AMAP_API_WEBSERVICE_KEY: 'your_webservice_key', // Web 服务 Key
  AMAP_MARKER_ICON: 'https://xxx.com/location_marker.svg', // 标记图标
  BMAP_API_KEY: 'your_bmap_key',                // 百度地图 Key(可选)
};

注意事项 :高德地图 JS API 2.0 要求配置安全密钥 (securityJsCode),否则地图无法正常加载。安全密钥需在高德开放平台控制台获取。实际项目中建议将 Key 通过环境变量(.env)注入,不要硬编码到源码中。


四、类型声明

在项目中引入高德地图的 TypeScript 类型定义,让 IDE 能正确提示 AMap 命名空间下的所有类型:

typescript 复制代码
// types/amap.d.ts

import '@amap/amap-jsapi-types';

引入后,在 .vue.ts 文件中即可使用 AMap.MapAMap.Marker 等类型标注,享受完整的类型检查。


五、核心 Hook:useAmap

这是整个封装的核心层。将高德 SDK 的加载、地图实例化、插件注册和销毁逻辑统一封装为一个 Composition API Hook,业务组件只需传入容器 ID 和回调函数即可使用。

typescript 复制代码
// src/hooks/web/useAmap.ts

import AMapLoader from '@amap/amap-jsapi-loader';
import { MAP_CONF } from '@/config/sdk_config';

// 地图初始化参数
export interface initOptions {
  zoom?: number;
  center?: number[];
  resizeEnable?: boolean;
  dragEnable?: boolean;
  zoomEnable?: boolean;
}

// Hook 入参
interface initOpts {
  container: string;             // 地图容器 DOM 的 id
  initCallbackFn: Function;      // 初始化完成后的回调
  mapOpts?: initOptions;         // 地图配置项
}

export const useAmap = (options: initOpts) => {
  const mapObj = ref();

  if (!options.container) {
    options?.initCallbackFn?.call(null, false);
    return;
  }

  try {
    // 1. 设置安全密钥(JS API 2.0 必须配置)
    (window as any)._AMapSecurityConfig = {
      securityJsCode: MAP_CONF.AMAP_SECRET_KEY,
    };

    // 2. 加载高德地图 SDK 及所需插件
    AMapLoader.load({
      key: MAP_CONF.AMAP_API_KEY,
      version: '2.0',
      plugins: [
        'AMap.Scale',        // 比例尺
        'AMap.Geolocation',   // 定位
        'AMap.PlaceSearch',   // 地点搜索
        'AMap.Geocoder',      // 地理编码
        'AMap.Marker',        // 标记点
        'AMap.CitySearch',    // 城市查询
        'AMap.ToolBar',       // 工具条
      ],
    }).then((amap) => {
      // 3. 创建地图实例
      const mapObj = new amap.Map(
        options.container,
        options.mapOpts ?? { resizeEnable: true },
      );
      // 4. 回调通知业务层,传入 AMap 命名空间和地图实例
      options?.initCallbackFn?.call(null, true, amap, mapObj);
    });
  } catch (error) {
    options?.initCallbackFn?.call(null, false);
    console.error(error);
  }

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

  const destroyMap = () => {
    mapObj.value && mapObj.value.destroy();
  };
};

💡 设计要点 :回调函数 initCallbackFn 接收三个参数:(isSuccess, aMap, mapObj)aMap 是 AMap 命名空间(用于创建 Marker、Geolocation 等对象),mapObj 是地图实例(用于 add、setZoomAndCenter 等操作)。这种设计让业务组件能灵活使用所有 SDK 能力。


六、位置展示组件

场景:只读展示一个地理位置,在地图上打标记点,点击可跳转高德地图网页版查看详情。

vue 复制代码
<!-- src/components/iForm/src/map_sdk/amap/amap_location.vue -->

<template>
  <div class="amap_location" :class="{ isMobile: !isPC }">
    <div class="address_wrap">
      <p class="address_title" v-if="mapLoactionInfo.location?.length">
        {{ mapLoactionInfo.location
        }}{{ mapLoactionInfo.address?.length ? `, ${mapLoactionInfo.address}` : '' }}
      </p>
      <p class="address_location" v-if="mapLoactionInfo.lng && mapLoactionInfo.lat">
        经度: {{ mapLoactionInfo.lng }}, 纬度: {{ mapLoactionInfo.lat }}
      </p>
    </div>
    <div class="map_wrap">
      <div :id="mapId" class="map_container" ref="mapContainerRef"></div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { MAP_CONF } from '@/config/sdk_config';
import { useAmap } from '@/hooks/web/useAmap';
import { isMobile } from '@/utils/is';

const props = defineProps<{ data: any }>();
const itemConf = ref(props.data);
const isPC = ref(!isMobile());

// 从配置中解析位置信息
const mapLoactionInfo = computed(() => {
  const info = itemConf.value?.customConf?.mapLoactionInfo;
  if (!info) return {};
  const lnglat = (info.lnglat || '').split(',');
  return {
    location: info.location || '',
    address: info.address || '',
    lng: lnglat[0],
    lat: lnglat[1],
  };
});

// 容器 ID 必须唯一
const mapId = `${itemConf.value.formItemKey}_${Date.now()}`;
const markerIcon = MAP_CONF.AMAP_MARKER_ICON;

useAmap({
  container: mapId,
  mapOpts: { zoom: 15, resizeEnable: false },
  initCallbackFn: (isSuccess: boolean, aMap: any, mapObj: AMap.Map) => {
    if (!isSuccess) return;
    const { lng, lat } = mapLoactionInfo.value;
    if (lng == undefined || lat == undefined) return;

    // 创建标记点
    const marker = new aMap.Marker({
      position: new aMap.LngLat(+lng, +lat),
      offset: new aMap.Pixel(-15, -20),
      icon: markerIcon,
      title: mapLoactionInfo.value.location,
    });
    mapObj.add(marker);
    // 设置缩放级别和中心点
    mapObj.setZoomAndCenter(15, [+lng, +lat]);

    // 点击地图跳转高德网页版
    mapObj.on('click', () => {
      const url = `https://ditu.amap.com/regeo?lng=${+lng}&lat=${+lat}&name=${
        mapLoactionInfo.value.location
      }&src=uriapi&callnative=1&innersrc=uriapi`;
      window.open(url, '_blank');
    });
  },
});
</script>

七、地址搜索导航组件

场景:用户输入地址关键词,通过高德 PlaceSearch 插件搜索地点列表,选择后在地图上打标记并回传位置数据。

vue 复制代码
<!-- src/components/iForm/src/map_sdk/amap/amap_navigation.vue -->

<template>
  <div class="base_location_amap">
    <!-- 地址搜索输入框(Element Plus 自动补全) -->
    <ElAutocomplete
      v-model="inputLocation"
      class="input_location"
      placeholder="请输入您的地址"
      :fetch-suggestions="querySearch"
      :trigger-on-focus="false"
      @select="handleSelect"
      @blur="handleChange"
    >
      <template #default="{ item }">
        <div class="value">{{ item.name }}</div>
      </template>
      <template #suffix>
        <Icon :icon="`svg-icon:search_icon`" :size="18" />
      </template>
    </ElAutocomplete>
    <!-- 详细地址输入(选填) -->
    <ElInput
      class="input_address"
      placeholder="详细地址:如楼层、门牌号等(选填)"
      v-model="inputAddress"
      @change="handleChange"
    />
    <!-- 地图容器 -->
    <div :id="mapId" class="map_container" ref="mapContainerRef"></div>
  </div>
</template>

<script setup lang="ts">
import { MAP_CONF } from '@/config/sdk_config';
import { useAmap } from '@/hooks/web/useAmap';

const props = defineProps<{ data: any }>();
const emit = defineEmits(['update']);

let AMapController: any;
let AMapObj: AMap.Map;
let markers: any[] = [];

const mapId = `${props.data.formItemKey}_${Date.now()}`;
const markerIcon = MAP_CONF.AMAP_MARKER_ICON;
const placeSearch = ref();
const inputLocation = ref('');
const inputAddress = ref('');
const lnglat = ref('');

useAmap({
  container: mapId,
  mapOpts: { zoom: 11, resizeEnable: true },
  initCallbackFn: (isSuccess: boolean, aMap: any, mapObj: AMap.Map) => {
    if (!isSuccess) return;
    AMapController = aMap;
    AMapObj = mapObj;

    // 添加工具条和比例尺
    mapObj.addControl(new AMapController.ToolBar());
    mapObj.addControl(new AMapController.Scale());

    // 初始化 PlaceSearch 搜索插件
    placeSearch.value = new AMapController.PlaceSearch({
      city: '全国',
      pageSize: 10,       // 每页结果数,默认10
      pageIndex: 1,       // 请求页码,默认1
      extensions: 'base', // 返回信息详略,默认为base(基本信息)
    });
  },
});

// 搜索地址
const querySearch = (queryString, cb) => {
  if (!queryString) return;
  placeSearch.value.search(queryString, (status, result) => {
    if (status != 'complete') {
      cb([]);
      return;
    }
    cb(result?.poiList?.pois || []);
  });
};

// 选中搜索结果
const handleSelect = (item) => {
  inputLocation.value = item.name;
  addMarker(item.name, item.location['lng'], item.location['lat']);
};

// 添加/更新标记点
const addMarker = (name: string, lng: number, lat: number) => {
  if (lng == undefined || lat == undefined) return;
  const marker = new AMapController.Marker({
    position: new AMapController.LngLat(lng, lat),
    offset: new AMapController.Pixel(-15, -20),
    icon: markerIcon,
    title: name,
  });
  AMapObj.remove(markers);  // 移除旧标记
  AMapObj.add(marker);
  markers.push(marker);
  AMapObj.setZoomAndCenter(15, [lng, lat]);
  lnglat.value = `${lng},${lat}`;
  emitUpdate();
};

const emitUpdate = () => {
  emit('update', {
    location: inputLocation.value,
    address: inputAddress.value,
    lnglat: lnglat.value,
  });
};

const handleChange = () => {
  emitUpdate();
};
</script>

💡 核心逻辑 :使用 ElAutocompletefetch-suggestions 属性接入高德 PlaceSearch 插件,实现输入即搜索。每次选择新地址时,先 mapObj.remove(markers) 移除旧标记,再 mapObj.add(marker) 添加新标记,保证地图上始终只有一个标记点。


八、PC 端定位预览组件

场景:用户点击「获取地理位置」按钮,通过浏览器 Geolocation API + 高德逆地理编码获取当前定位,同时支持手动搜索地址。适用于 PC 端表单填写。

vue 复制代码
<!-- src/components/iForm/src/map_sdk/amap/amap_preview_location_pc.vue -->
<!-- 此处展示核心逻辑,模板部分省略 -->

<script setup lang="ts">
import { MAP_CONF } from '@/config/sdk_config';
import { useAmap } from '@/hooks/web/useAmap';

const props = defineProps<{ data: any; isReview: boolean }>();
const emit = defineEmits(['update']);

let AMapController: any;
let AMapObj: AMap.Map;
let markers: any[] = [];
let defaultLocation: [number, number] = [116.406606, 39.943691]; // 默认北京

const showMap = ref(false);
const mapLoactionInfo = ref();
const submitLoactionInfo = ref();
const placeSearch = ref();
const mapId = `${props.data.formItemKey}_${Date.now()}`;

// 初始化地图
const initMap = () => {
  showMap.value = true;
  useAmap({
    container: mapId,
    mapOpts: { zoom: 15, resizeEnable: false },
    initCallbackFn: (isSuccess: boolean, aMap: any, mapObj: AMap.Map) => {
      if (!isSuccess) return;
      AMapController = aMap;
      AMapObj = mapObj;

      if (!props.isReview) {
        getUserPermission();  // 非查看模式:自动获取定位
        placeSearch.value = new AMapController.PlaceSearch({
          city: '全国',
          pageSize: 10,
          extensions: 'base',
        });
      }
    },
  });
};

// 获取浏览器定位(高德 Geolocation 插件)
const getUserPermission = () => {
  const geolocation = new AMapController.Geolocation({
    enableHighAccuracy: true,   // 高精度定位
    timeout: 5 * 1000,          // 超时 5 秒
    position: 'RB',             // 定位按钮停靠位置
    offset: [10, 20],           // 偏移量
    zoomToAccuracy: true,       // 定位成功自动调整视野
    needAddress: true,          // 返回地址信息
    extensions: 'all',          // 返回详细信息
  });

  AMapObj.addControl(new AMapController.ToolBar());
  AMapObj.addControl(new AMapController.Scale());

  geolocation.getCurrentPosition((status, result) => {
    if (status == 'complete') {
      localeComplete(result);
    } else {
      localeError(result, geolocation);  // 定位失败走 IP 定位兜底
    }
  });
};

// 定位成功
const localeComplete = (result: any) => {
  const lng = result?.position?.lng;
  const lat = result?.position?.lat;
  const address = result?.formattedAddress;
  if (address && lng && lat) {
    mapLoactionInfo.value = submitLoactionInfo.value = {
      location: address,
      lng,
      lat,
    };
    addMarker(address, lng, lat);
    handleUpdate();
  } else {
    // 数据不完整,走兜底
    localeError('Locate failed');
  }
};

// 定位失败:通过 IP 城市定位兜底
const localeError = (err: any, geolocation?: any) => {
  geolocation?.getCityInfo((status, result) => {
    if (status == 'complete') {
      const longitude = result?.position?.[0] || defaultLocation[0];
      const latitude = result?.position?.[1] || defaultLocation[1];
      mapLoactionInfo.value = {
        location: result?.city ?? '北京市',
        lng: longitude,
        lat: latitude,
      };
      AMapObj.setZoomAndCenter(11, [longitude, latitude]);
    } else {
      console.log('IP定位失败');
    }
  });
};

// 添加标记
const addMarker = (name: string, lng: number, lat: number) => {
  if (lng == undefined || lat == undefined) return;
  const marker = new AMapController.Marker({
    position: new AMapController.LngLat(lng, lat),
    offset: new AMapController.Pixel(-15, -40),
    icon: MAP_CONF.AMAP_MARKER_ICON,
    title: name,
  });
  AMapObj.remove(markers);
  AMapObj.add(marker);
  markers.push(marker);
  AMapObj.setZoomAndCenter(15, [lng, lat]);
};

// 手动搜索选择地址
const handleSelect = (item) => {
  const data = {
    location: item.name,
    lng: item?.location['lng'],
    lat: item?.location['lat'],
  };
  mapLoactionInfo.value = submitLoactionInfo.value = data;
  addMarker(item.name, item.location['lng'], item.location['lat']);
  handleUpdate();
};

// 重置
const handleReset = () => {
  showMap.value = false;
  mapLoactionInfo.value = submitLoactionInfo.value = undefined;
};

// 重新定位
const handleLoaction = () => {
  AMapObj.remove(markers);
  getUserPermission();
};

const handleUpdate = () => {
  emit('update', submitLoactionInfo.value);
};

// 暴露给父组件的方法
defineExpose({
  refreshModel: (valueData: any) => {
    mapLoactionInfo.value = Object.assign({}, valueData?.mapLocationValue ?? {});
    submitLoactionInfo.value = mapLoactionInfo.value;
    initMap();
  },
  resetModel: (_: any) => {
    mapLoactionInfo.value = undefined;
    submitLoactionInfo.value = undefined;
    initMap();
  },
});
</script>

💡 定位降级策略 :PC 端定位采用三级降级 策略:① 浏览器高精度 Geolocation 定位 → ② 失败后调用 getCityInfo 进行 IP 城市级定位 → ③ 仍失败则使用默认北京坐标。这保证了用户始终能看到地图和位置信息。


九、移动端定位预览组件

场景 :移动端由于屏幕限制,不展示完整地图,仅显示定位结果文本(地址 + 经纬度)。通过 rem 单位适配移动端屏幕。

vue 复制代码
<!-- src/components/iForm/src/map_sdk/amap/amap_preview_location_mobile.vue -->
<!-- 此处展示核心逻辑,模板部分省略 -->

<script setup lang="ts">
import { useAmap } from '@/hooks/web/useAmap';

const props = defineProps<{ data: any; isReview: boolean }>();
const emit = defineEmits(['update']);

let AMapController: any;
let defaultLocation: [number, number] = [116.406606, 39.943691];

const showMap = ref(false);
const mapLoactionInfo = ref();
const submitLoactionInfo = ref();
const mapId = `${props.data.formItemKey}_${Date.now()}`;

const initMap = () => {
  showMap.value = true;
  useAmap({
    container: mapId,
    mapOpts: { zoom: 15, resizeEnable: false },
    initCallbackFn: (isSuccess: boolean, aMap: any, _: AMap.Map) => {
      if (!isSuccess) return;
      AMapController = aMap;
      if (!props.isReview) {
        getUserPermission();
      }
    },
  });
};

// 移动端定位逻辑与 PC 端类似,但地图容器宽高设为 0(不展示地图)
const getUserPermission = () => {
  const geolocation = new AMapController.Geolocation({
    enableHighAccuracy: true,
    timeout: 5 * 1000,
    position: 'RB',
    offset: [10, 20],
    zoomToAccuracy: true,
    needAddress: true,
    extensions: 'all',
  });
  geolocation.getCurrentPosition((status, result) => {
    if (status == 'complete') {
      localeComplete(result);
    } else {
      localeError(result, geolocation);
    }
  });
};

const localeComplete = (result: any) => {
  const lng = result?.position?.lng;
  const lat = result?.position?.lat;
  const address = result?.formattedAddress;
  if (address && lng && lat) {
    mapLoactionInfo.value = submitLoactionInfo.value = {
      location: address,
      lng,
      lat,
    };
    handleUpdate();
  } else {
    localeError('Locate failed');
  }
};

const localeError = (_: any, geolocation?: any) => {
  geolocation?.getCityInfo((status, result) => {
    if (status == 'complete') {
      const longitude = result?.position?.[0] || defaultLocation[0];
      const latitude = result?.position?.[1] || defaultLocation[1];
      mapLoactionInfo.value = {
        location: result?.city ?? '北京市',
        lng: longitude,
        lat: latitude,
      };
    } else {
      mapLoactionInfo.value = {
        location: 'IP定位失败',
        lng: defaultLocation[0],
        lat: defaultLocation[1],
      };
    }
    submitLoactionInfo.value = mapLoactionInfo.value;
    handleUpdate();
  });
};

const handleReset = () => {
  showMap.value = false;
  mapLoactionInfo.value = submitLoactionInfo.value = undefined;
};

const handleLoaction = () => {
  getUserPermission();
};

const handleUpdate = () => {
  emit('update', submitLoactionInfo.value);
};

defineExpose({
  refreshModel: (valueData: any) => {
    mapLoactionInfo.value = Object.assign({}, valueData?.mapLocationValue ?? {});
    submitLoactionInfo.value = mapLoactionInfo.value;
    initMap();
  },
  resetModel: (_: any) => {
    mapLoactionInfo.value = undefined;
    submitLoactionInfo.value = undefined;
    initMap();
  },
});
</script>

💡 PC 端 vs 移动端差异 :移动端组件将地图容器 .map_container 的宽高设为 0,仅利用 SDK 的定位能力获取经纬度,不渲染可视地图。UI 上以文本卡片形式展示地址信息和「重新获取定位」按钮,所有尺寸使用 rem 单位适配移动端。


十、基类组件(SDK 切换层)

为了支持高德/百度地图的灵活切换,项目封装了基类组件作为统一入口 。通过配置 MAP_CONF.BASE_MAP_TYPE 决定渲染哪个 SDK 的组件,业务层无需感知底层差异。

vue 复制代码
<!-- src/components/iForm/src/map_sdk/base_map_location.vue -->

<template>
  <div class="base_map_location">
    <!-- 动态组件:根据配置切换高德/百度 -->
    <component :is="sdkType == 'AMAP' ? AmapLocation : BmapLocation" :data="itemConf" />
  </div>
</template>

<script setup lang="ts">
import { MAP_CONF } from '@/config/sdk_config';
import AmapLocation from './amap/amap_location.vue';
import BmapLocation from './bmap/bmap_location.vue';

const props = defineProps<{ data: any }>();
const itemConf = ref(props.data);
const sdkType = MAP_CONF.BASE_MAP_TYPE;  // 'AMAP' 或 'BMAP'
</script>
vue 复制代码
<!-- src/components/iForm/src/map_sdk/base_map_navigation.vue -->

<template>
  <div class="base_location">
    <component
      :is="sdkType == 'AMAP' ? AMapNavigation : BMapNavigation"
      :data="itemConf"
      @update="(res) => emit('update', res)"
    />
  </div>
</template>

<script setup lang="ts">
import { MAP_CONF } from '@/config/sdk_config';
import AMapNavigation from './amap/amap_navigation.vue';
import BMapNavigation from './bmap/bmap_navigation.vue';

const props = defineProps<{ data: any }>();
const emit = defineEmits(['update']);
const itemConf = ref(props.data);
const sdkType = MAP_CONF.BASE_MAP_TYPE;
</script>

十一、使用方法总结

使用步骤

  1. 安装依赖 @amap/amap-jsapi-loader@amap/amap-jsapi-types
  2. sdk_config.ts 中配置 API Key 和安全密钥
  3. types/amap.d.ts 中引入类型声明
  4. 在业务组件中调用 useAmap Hook 初始化地图
  5. 在回调函数中使用 aMap(命名空间)和 mapObj(实例)操作地图

最小化使用示例

vue 复制代码
<template>
  <div :id="mapId" style="width: 100%; height: 400px"></div>
</template>

<script setup lang="ts">
import { useAmap } from '@/hooks/web/useAmap';
import { MAP_CONF } from '@/config/sdk_config';

const mapId = 'my_map_container';

useAmap({
  container: mapId,
  mapOpts: {
    zoom: 12,
    center: [116.406606, 39.943691],  // 初始中心点
    resizeEnable: true,
  },
  initCallbackFn: (isSuccess, aMap, mapObj) => {
    if (!isSuccess) return;

    // 添加标记点
    const marker = new aMap.Marker({
      position: new aMap.LngLat(116.406606, 39.943691),
      icon: MAP_CONF.AMAP_MARKER_ICON,
    });
    mapObj.add(marker);

    // 点击事件
    mapObj.on('click', (e) => {
      console.log('点击坐标:', e.lnglat.getLng(), e.lnglat.getLat());
    });
  },
});
</script>

通过基类组件使用

vue 复制代码
<template>
  <!-- 位置展示 -->
  <BaseMapLocation :data="formItemConf" />

  <!-- 地址搜索(带 update 事件) -->
  <BaseMapNavigation
    :data="formItemConf"
    @update="handleLocationUpdate"
  />
</template>

<script setup lang="ts">
import BaseMapLocation from '@/components/iForm/src/map_sdk/base_map_location.vue';
import BaseMapNavigation from '@/components/iForm/src/map_sdk/base_map_navigation.vue';

const handleLocationUpdate = (data) => {
  // data = { location: '地址名', address: '详细地址', lnglat: '经度,纬度' }
  console.log('位置更新:', data);
};
</script>

十二、关键设计要点

设计点 实现方式 收益
SDK 动态加载 使用 @amap/amap-jsapi-loader 而非 index.html 硬编码 script 按需加载,减少首屏体积
配置集中管理 统一在 sdk_config.ts 维护 Key、密钥、图标 修改一处即可,支持环境切换
Hook 封装 useAmap 统一处理加载、实例化、销毁 业务组件无需关心 SDK 初始化细节
回调传参设计 initCallbackFn(isSuccess, aMap, mapObj) 业务层获得完整 SDK 能力,灵活扩展
SDK 切换层 基类组件通过 BASE_MAP_TYPE 动态切换高德/百度 业务层无感知,一键切换地图供应商
定位降级策略 高精度定位 → IP 城市定位 → 默认坐标 保证用户始终可见位置信息
容器 ID 唯一性 ${formItemKey}_${Date.now()} 避免多实例冲突
自动销毁 onUnmounted 中调用 mapObj.destroy() 防止内存泄漏
PC/移动端分离 独立组件,移动端隐藏地图仅用定位能力 适配不同终端的交互差异

总结 :整个封装的核心思路是分层 + 回调 :配置层管 Key,Hook 层管加载和销毁,组件层管业务逻辑,基类层管 SDK 切换。useAmap 的回调设计让业务组件能拿到完整的 AMap 命名空间和地图实例,在保持封装性的同时不失灵活性。

相关推荐
敲代码的玉米C2 小时前
我修的那个 bug,制造了另一个 bug
前端·人工智能·架构
gis开发之家2 小时前
《Vue3 从入门到大神50篇》Vue3 源码详解(二十):生命周期钩子源码解析 —— onMounted / onUpdated 如何实现?
前端·javascript·前端框架·vue3·vue3源码
Narrastory2 小时前
我用 Claude Code 写代码不到 2 小时,却花了 3 天做完这个软件—Vibe Coding 的正确姿势,是设计不是生成
前端·人工智能·github
Enaium2 小时前
我实现了KMP的SDL绑定并编写了高性能软光追
前端·kotlin
古夕2 小时前
本地正常线上白屏:一次路由切换后刷新恢复问题的排查与修复
前端·ai编程
栀鸢ouo2 小时前
解决Element Plus表格展开行横向溢出、滚动截断问题(项目实战方案)
前端·vue.js
用户39051332192883 小时前
写了5年JS,才发现这10个方法能少写一半代码
前端
平凡的阿泽3 小时前
我用TRAE Work手搓了一个「谁是卧底」小游戏
前端·javascript
一心只读圣贤书3 小时前
AI 辅助前端视觉回归治理:从截图基线到变更解释
前端·人工智能
嘟嘟07173 小时前
用单例模式管理弹窗:从一段原生 JS 理解 Singlet
前端·javascript·代码规范