高德地图自定义marker,点击、悬停显示信息框
<div class="map-container" ref="mapRef" :style="{ height: props.height ? `${props.height}px` : '400px' }"></div>
声明变量及对象类型
const props = defineProps<{zoom?: numbermarkers?: GpsDevice[]height?: number
}>()const mapRef = ref<HTMLElement | null>(null)
let map: AMap.Map | null = null
let markerLayer: AMap.Marker[] = []
let infoWindow: AMap.InfoWindow | null = null
let userInteracted = false // ✅ 是否用户手动交互过地图
interface GpsDevice {id: stringstatus: 'online' | 'online_driving' | 'offline' | 'online_parking'licensePlate: stringts: string | numberlatitude: numberlongitude: numberaltitude: numberspeed?: numberdirection?: numbersignal?: numbercompany?:stringdepartment?:string[key: string]: any}
加载地图
const initMap = async () => {try {console.log('开始加载高德地图...')const AMap = await AMapLoader.load({key: '你的key',version: '2.0',plugins: ['AMap.Marker', 'AMap.InfoWindow']})if (mapRef.value) {map = new AMap.Map(mapRef.value, {zoom: props.zoom || 12,viewMode: '2D',})console.log('地图实例创建成功:', map)// ✅ 监听用户交互map.on('zoomstart', () => userInteracted = true)map.on('dragstart', () => userInteracted = true)// 创建信息窗口infoWindow = new AMap.InfoWindow({offset: new AMap.Pixel(0, -30),closeWhenClickMap: true,autoMove: true})if (props.markers?.length) {renderMarkers(props.markers)}}} catch (error) {console.error('地图初始化失败:', error)}
}
创建信息窗口内容
const createInfoWindowContent = (device: GpsDevice) => {const statusInfo = getDeviceDisplayInfo(device.status)const speed = device.speed ? `${device.speed} km/h` : '--'const company = device.company || '--'const department = device.department || '--'// 信号强度转换const getSignalText = (signal: number) => {if (signal === 3) return "信号优秀"if (signal === 2) return "信号良好"if (signal === 1) return "信号一般"return "信号未知"}const signal = device.signal ? getSignalText(device.signal) : '--'return `<div class="vehicle-info-window"><div class="info-header"><div class="status-indicator" style="background-color: ${statusInfo.iconBgColor};"><i class="iconfont ${statusInfo.icon}" style="color: white;"></i></div><div class="vehicle-title"><h3>${device.licensePlate}</h3><span class="status-text" style="color: ${statusInfo.dotColor};">${statusInfo.label}</span></div></div><div class="info-content"><div class="info-row"><span class="label">车辆ID:</span><span class="value">${device.id}</span></div><div class="info-row"><span class="label">所属公司:</span><span class="value">${company}</span></div><div class="info-row"><span class="label">所属部门:</span><span class="value">${department}</span></div><div class="info-row"><span class="label">当前速度:</span><span class="value">${speed}</span></div><div class="info-row"><span class="label">信号强度:</span><span class="value">${signal}</span></div><div class="info-row"><span class="label">更新时间:</span><span class="value">${new Date(device.ts).toLocaleString()}</span></div></div></div>`
}
渲染标记点
const renderMarkers = (list: GpsDevice[]) => {if (!map) returnconsole.log('📍 渲染 marker 数量:', list.length)// 移除旧的标记if (markerLayer.length > 0) {map.remove(markerLayer)markerLayer = []}list.forEach(item => {if (item.longitude && item.latitude) {const [gcjLng, gcjLat] = coordtransform.wgs84togcj02(item.longitude, item.latitude)const content = `<div class="custom-marker" style="background-color: ${getDeviceDisplayInfo(item.cdtStatus).iconBgColor}; width: 30px; height: 40px; border-radius: 50px; display:flex; justify-content:center; align-items:center; color: white; font-weight: bold; border: 2px solid white;"><i class="iconfont ${getDeviceDisplayInfo(item.cdtStatus).icon}" style="font-size: 16px;"></i></div>`const marker = new AMap.Marker({position: [gcjLng, gcjLat],content,title: item.licensePlate,offset: new AMap.Pixel(-15, -30),})// 添加鼠标悬停事件marker.on('mouseover', () => {if (infoWindow) {infoWindow.setContent(createInfoWindowContent(item))infoWindow.open(map, [gcjLng, gcjLat])}})// 添加鼠标离开事件marker.on('mouseout', () => {if (infoWindow) {infoWindow.close()}})// 添加点击事件marker.on('click', () => {if (infoWindow) {infoWindow.setContent(createInfoWindowContent(item))infoWindow.open(map, [gcjLng, gcjLat])}})markerLayer.push(marker)}})if (markerLayer.length > 0) {map.add(markerLayer)// ✅ 如果用户没手动操作过地图,则自动缩放if (!userInteracted) {map.setFitView(markerLayer)}}
}
工具函数:状态 → 显示信息的映射函数。
export type CdtStatus = 'online' | 'online_driving' | 'offline' | 'online_parking';export interface DeviceDisplayInfo {label: string;iconBgColor: string;icon: string;dotColor:string;
}const statusMap: Record<CdtStatus, DeviceDisplayInfo> = {online: {//在线label: '在线',iconBgColor: '#52c41a', icon: 'icon-cheliangguanli',dotColor:'#52c41a',},online_driving: {//在线+行驶label: '在线',iconBgColor: '#165dff', icon: 'icon-che-tianchong',dotColor:'#52c41a',},online_parking: {//在线+停车label: '在线',iconBgColor: '#36cfc9', icon: 'icon-tingcheguanli-cheweipeizhi',dotColor:'#52c41a',},offline: {//离线label: '离线',iconBgColor: '#faad14', icon: 'icon-cheliangguanli',dotColor:'#909399',},
};export function getDeviceDisplayInfo(status: CdtStatus): DeviceDisplayInfo {return statusMap[status] || {label: '未知',iconBgColor: '#d9d9d9',icon: 'icon-unknown',dotColor:'#909399',};
}