Vue3 + TypeScript 中高德地图的完整封装与使用
从依赖安装到业务组件封装,完整梳理一个 Vue3 项目中高德地图 SDK 的分层架构与实践方法,涵盖地图加载、位置展示、地址搜索、PC/移动端定位等核心场景。
目录
- 一、整体架构概览
- 二、依赖安装
- 三、配置管理
- 四、类型声明
- [五、核心 Hook:useAmap](#五、核心 Hook:useAmap)
- 六、位置展示组件
- 七、地址搜索导航组件
- [八、PC 端定位预览组件](#八、PC 端定位预览组件)
- 九、移动端定位预览组件
- [十、基类组件(SDK 切换层)](#十、基类组件(SDK 切换层))
- 十一、使用方法总结
- 十二、关键设计要点
一、整体架构概览
项目中高德地图的封装采用分层架构设计,从底层配置到上层业务组件,职责清晰、易于复用。整体分为四层:
┌─────────────────────────────────────────────────────┐
│ 配置层: 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.Map、AMap.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>
💡 核心逻辑 :使用
ElAutocomplete的fetch-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>
十一、使用方法总结
使用步骤
- 安装依赖
@amap/amap-jsapi-loader和@amap/amap-jsapi-types - 在
sdk_config.ts中配置 API Key 和安全密钥 - 在
types/amap.d.ts中引入类型声明 - 在业务组件中调用
useAmapHook 初始化地图 - 在回调函数中使用
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 命名空间和地图实例,在保持封装性的同时不失灵活性。