This component provides a flexible way to embed interactive maps within a Vue 3 application, supporting various map providers like Tianditu, Baidu Maps, Tencent Maps, and Amap. It includes geocoding functionality to find coordinates from addresses and supports marker dragging for point selection.
The component can be integrated into your Vue project as follows:
<template>
<div class="map-container">
<div id="map-instance" :style="{ width: mapWidth, height: mapHeight }"></div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, onMounted } from 'vue';
export default defineComponent({
props: {
// The address to initially display or search for
modelValue: {
type: String,
default: '上海市黄浦区上海中心大厦',
},
// Whether the marker should be draggable
draggable: {
type: Boolean,
default: true,
},
// Width of the map container
mapWidth: {
type: String,
default: '100%',
},
// Height of the map container
mapHeight: {
type: String,
default: '350px',
},
// Map provider type: 'tianditu', 'baidu', 'tencent', 'amap'
mapProvider: {
type: String,
default: 'baidu',
},
},
emits: ['coordinate-selected'],
setup(props, { emit }) {
// --- API Keys (Replace with your actual keys) ---
const API_KEYS = {
tianditu: 'YOUR_TIANDITU_KEY',
baidu: 'YOUR_BAIDU_KEY',
tencent: 'YOUR_TENCENT_KEY',
amap: 'YOUR_AMAP_KEY',
amap_security: 'YOUR_AMAP_SECURITY_KEY',
};
const mapInstance = ref<any>(null);
const longitude = ref(121.47894); // Default longitude (e.g., Shanghai Center)
const latitude = ref(31.223); // Default latitude
// Watch for changes in the address prop to re-geocode
watch(() => props.modelValue, (newAddress) => {
if (newAddress && mapInstance.value) {
geocodeAddress(newAddress);
}
});
onMounted(() => {
loadMapScript();
});
// Dynamically loads the appropriate map script
const loadMapScript = () => {
const existingScript = document.querySelector('.map-loader-script');
if (existingScript) {
document.body.removeChild(existingScript);
}
const script = document.createElement('script');
script.type = 'text/javascript';
script.className = 'map-loader-script';
let scriptUrl = '';
let callbackFunctionName = 'initializeMap'; // Default callback
switch (props.mapProvider) {
case 'tianditu':
scriptUrl = `https://api.tianditu.gov.cn/api?v=4.0&tk=${API_KEYS.tianditu}`;
break;
case 'baidu':
scriptUrl = `https://api.map.baidu.com/getscript?v=3.0&ak=${API_KEYS.baidu}`;
break;
case 'tencent':
// Tencent maps requires a global callback function
callbackFunctionName = 'initializeTencentMap';
scriptUrl = `https://map.qq.com/api/js?v=2.exp&key=${API_KEYS.tencent}&callback=${callbackFunctionName}`;
break;
case 'amap':
scriptUrl = `https://webapi.amap.com/maps?v=2.0&key=${API_KEYS.amap}`;
break;
default:
console.error('Unsupported map provider:', props.mapProvider);
return;
}
script.src = scriptUrl;
// Handle initialization callback
if (props.mapProvider === 'tencent') {
window[callbackFunctionName] = () => {
loadTencentGLMapScript(callbackFunctionName);
};
} else {
script.onload = () => {
initializeMap();
};
}
document.body.appendChild(script);
// Special handling for Amap security code
if (props.mapProvider === 'amap') {
(window as any)._AMapSecurityConfig = {
securityJsCode: API_KEYS.amap_security,
};
}
};
// Loads the Tencent Maps GL JS SDK if needed
const loadTencentGLMapScript = (callbackName: string) => {
const glScript = document.createElement('script');
glScript.type = 'text/javascript';
glScript.className = 'map-loader-script-gl'; // Use a different class for GL script
glScript.src = `https://map.qq.com/api/gljs?v=1.exp&key=${API_KEYS.tencent}&libraries=service`;
const existingGlScript = document.querySelector('.map-loader-script-gl');
if (existingGlScript) {
document.body.removeChild(existingGlScript);
}
glScript.onload = () => {
// Re-assign the original callback to ensure it runs after GL script loads
window[callbackName] = initializeMap;
};
document.body.appendChild(glScript);
};
// Initializes the map based on the provider
const initializeMap = () => {
switch (props.mapProvider) {
case 'tianditu':
initializeTiandituMap();
break;
case 'baidu':
initializeBaiduMap();
break;
case 'tencent':
initializeTencentMap(); // This is called via callback
break;
case 'amap':
initializeAmap();
break;
}
};
const initializeTiandituMap = () => {
const T = (window as any).T;
if (!T) return;
const initialPoint = new T.LngLat(longitude.value, latitude.value);
mapInstance.value = new T.Map('map-instance');
mapInstance.value.centerAndZoom(initialPoint, 10);
mapInstance.value.disableScrollWheelZoom();
mapInstance.value.addControl(new T.Control.Zoom());
let marker = new T.Marker(initialPoint);
mapInstance.value.addOverLay(marker);
if (props.draggable) {
marker.enableDragging();
marker.addEventListener('dragend', (event: any) => {
updateCoordinatesFromMarker(event.lnglat.lng, event.lnglat.lat, T.LngLat);
});
}
};
const initializeBaiduMap = () => {
const BMap = (window as any).BMap;
if (!BMap) return;
mapInstance.value = new BMap.Map('map-instance', { enableMapClick: false });
const initialPoint = new BMap.Point(longitude.value, latitude.value);
mapInstance.value.centerAndZoom(initialPoint, 10);
mapInstance.value.addControl(new BMap.NavigationControl({ anchor: window.BMAP_ANCHOR_TOP_LEFT, type: window.BMAP_NAVIGATION_CONTROL_LARGE }));
let marker = new BMap.Marker(initialPoint);
mapInstance.value.addOverlay(marker);
if (props.draggable) {
marker.enableDragging();
marker.addEventListener('dragend', (event: any) => {
updateCoordinatesFromMarker(event.point.lng, event.point.lat, BMap.Point);
});
const copyrightCtrl = new BMap.CopyrightControl({ anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT });
mapInstance.value.addControl(copyrightCtrl);
copyrightCtrl.addCopyright({ id: 1, content: '<div class="map-dragging-tips">Drag the marker to set location</div>', bounds: mapInstance.value.getBounds() });
}
};
const initializeTencentMap = () => {
const qq_maps = (window as any).qq.maps;
if (!qq_maps) return;
const initialPoint = new qq_maps.LatLng(latitude.value, longitude.value);
mapInstance.value = new qq_maps.Map('map-instance', {
center: initialPoint,
zoom: 10,
});
const marker = new qq_maps.Marker({
map: mapInstance.value,
position: initialPoint,
draggable: props.draggable,
});
if (props.draggable) {
qq_maps.event.addListener(marker, 'dragend', (event: any) => {
updateCoordinatesFromMarker(event.latLng.lng, event.latLng.lat, qq_maps.LatLng);
});
}
};
const initializeAmap = () => {
const AMap = (window as any).AMap;
if (!AMap) return;
mapInstance.value = new AMap.Map('map-instance', {
zoomEnable: true,
resizeEnable: false,
scrollWheel: false,
zoom: 10,
center: [longitude.value, latitude.value],
});
AMap.plugin(['AMap.ToolBar'], () => {
mapInstance.value.addControl(new AMap.ToolBar());
});
const marker = new AMap.Marker({
position: mapInstance.value.getCenter(),
draggable: props.draggable,
});
marker.setMap(mapInstance.value);
if (props.draggable) {
marker.on('dragend', (event: any) => {
updateCoordinatesFromMarker(event.lnglat.lng, event.lnglat.lat, AMap.LngLat);
});
}
mapInstance.value.add(marker);
};
// Geocodes an address and updates the map
const geocodeAddress = (address: string) => {
switch (props.mapProvider) {
case 'tianditu':
const T = (window as any).T;
new T.Geocoder().getPoint(address, (result: any) => {
if (result && result.getStatus() === 0) {
const location = result.getLocationPoint();
updateCoordinates(location.lng, location.lat, T.LngLat);
} else {
console.warn('Tianditu geocoding failed:', result);
// Consider using a fallback or showing a message
}
});
break;
case 'baidu':
const BMap = (window as any).BMap;
new BMap.Geocoder().getPoint(address, (point: any) => {
if (point) {
updateCoordinates(point.lng, point.lat, BMap.Point);
} else {
console.warn('Baidu geocoding failed for address:', address);
}
}, '全国');
break;
case 'tencent':
// Ensure TMap.service is available after GL script load
const TMap = (window as any).TMap;
if (TMap && TMap.service) {
new TMap.service.Geocoder().getLocation({ address: address }).then((result: any) => {
if (result && result.result && result.result.location) {
const coords = result.result.location;
updateCoordinates(coords.lng, coords.lat, TMap.LatLng);
} else {
console.warn('Tencent geocoding failed:', result);
}
});
} else {
console.error('Tencent Maps service not loaded.');
}
break;
case 'amap':
const AMap = (window as any).AMap;
AMap.plugin('AMap.Geocoder', () => {
new AMap.Geocoder().getLocation(address, (status: string, result: any) => {
if (status === 'complete' && result.geocodes.length) {
const coords = result.geocodes[0].location;
updateCoordinates(coords.lng, coords.lat, AMap.LngLat);
} else {
console.warn('Amap geocoding failed:', result);
}
});
});
break;
}
};
// Updates coordinates and emits event
const updateCoordinates = (lng: number, lat: number, CoordinateClass: any) => {
longitude.value = lng;
latitude.value = lat;
if (mapInstance.value) {
const newPoint = new CoordinateClass(lng, lat);
mapInstance.value.setCenter(newPoint);
// Optionally update marker position if it exists
// This part might need refinement based on how markers are managed
if (props.mapProvider === 'amap') { // Example for AMAP
if (mapInstance.value.getMarker) { // Assuming you have a way to get the marker
mapInstance.value.getMarker().setPosition([lng, lat]);
}
} else if (mapInstance.value.getOverlay) { // Example for Baidu/Tianditu
const overlays = mapInstance.value.getOverlays ? mapInstance.value.getOverlays() : (mapInstance.value.getComponents ? mapInstance.value.getComponents().markers : []);
const marker = overlays.find((o: any) => o.type === 'Marker'); // This logic needs adjustment per provider
if(marker) marker.setPosition(newPoint);
}
}
emit('coordinate-selected', { lng, lat });
};
// Updates coordinates when marker is dragged
const updateCoordinatesFromMarker = (lng: number, lat: number, CoordinateClass: any) => {
longitude.value = lng;
latitude.value = lat;
if (mapInstance.value) {
mapInstance.value.panTo(new CoordinateClass(lng, lat)); // Pan to the new location
}
emit('coordinate-selected', { lng, lat });
};
return {
mapInstance,
longitude,
latitude,
mapWidth: props.mapWidth,
mapHeight: props.mapHeight,
loadMapScript,
initializeMap,
geocodeAddress,
};
},
});
</script>
<style scoped>
.map-container {
width: 100%;
height: 350px; /* Default height, can be overridden by props */
border-radius: 8px; /* Example styling */
}
#map-instance {
width: 100%;
height: 100%;
}
.map-dragging-tips {
padding: 5px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 3px;
}
</style>
Component Usage Example
To use the component, you would include it in another Vue component and bind props and event listeners:
<template>
<div>
<MapComponent
v-model="addressInput"
:mapProvider="selectedMapType"
@coordinate-selected="handleCoordinateSelection"
mapHeight="400px"
/>
<input v-model="addressInput" placeholder="Enter address"/>
<select v-model="selectedMapType">
<option value="tianditu">Tianditu</option>
<option value="baidu">Baidu Maps</option>
<option value="tencent">Tencent Maps</option>
<option value="amap">Amap</option>
</select>
</div>
</template>
<script lang="ts">
import { ref } from 'vue';
import MapComponent from './MapComponent.vue'; // Adjust path as needed
export default {
components: {
MapComponent,
},
setup() {
const addressInput = ref('上海市黄浦区上海中心大厦');
const selectedMapType = ref('baidu'); // Default map type
// Callback function to receive selected coordinates
const handleCoordinateSelection = (coords: { lng: number; lat: number }) => {
console.log('Selected Coordinates:', coords);
// Update your form or state with coords.lng and coords.lat
};
return {
addressInput,
selectedMapType,
handleCoordinateSelection,
};
},
};
</script>
Props
v-model(String): Binds to the address input. Changes to this value will trigger a geocoding search.mapProvider(String, default: 'baidu'): Specifies which map service to use ('tianditu', 'baidu', 'tencent', 'amap').draggable(Booleen, default: true): Determines if the map marker can be dragged.mapHeight(String, default: '350px'): Sets the height of the map container.mapWidth(String, default: '100%'): Sets the width of the map container.
Events
coordinate-selected: Emitted when the map view is updated (either by goecoding or marker drag). It passes an object containing thelng(longitude) andlat(latitude) of the selected point.