React (react-amap)高德地图使用(加标记、缩放、缩略图)

React 高德地图使用指南

本文档基于实际项目经验,详细介绍如何在 React 项目中集成和使用高德地图。

目录

  1. 环境准备
  2. 基础配置
  3. 地图组件实现
  4. 标记点功能
  5. 信息窗口
  6. 事件处理
  7. 样式定制
  8. 常见问题

环境准备

1. 安装依赖

npm install react-amap
# 或
yarn add react-amap

2. 获取高德地图 API Key

  1. 访问 高德开放平台
  2. 注册账号并创建应用
  3. 获取 Web 端 API Key

基础配置

1. 在 HTML 中引入高德地图 API

public/index.html 中添加:

<script src="https://webapi.amap.***/maps?v=1.4.15&key=YOUR_API_KEY"></script>

2. TypeScript 类型定义

interface MarkerData {
  ID: string;
  AS_NAME: string;
  AS_ADDRESS: string;
  AS_LONGITUDE_LATITUDE: string;
  AREA: string | number;
  ROOM_COUNT: number;
  IS_OPERATING?: number;
  [key: string]: any;
}

interface FilterParams {
  ***pany: string;
  department: string;
  assetType: string;
}

地图组件实现

1. 基础地图组件

import React, { useEffect, useState } from 'react';
import { Map, Marker, InfoWindow } from 'react-amap';

const ReactAmap***ponent: React.FC = () => {
  const [mapCenter, setMapCenter] = useState([116.397428, 39.90923]);
  const [zoom, setZoom] = useState(10);
  const [markers, setMarkers] = useState<MarkerData[]>([]);

  return (
    <div style={{ width: '100%', height: '600px' }}>
      <Map
        amapkey="YOUR_API_KEY"
        center={mapCenter}
        zoom={zoom}
        loading={<div>地图加载中...</div>}
        events={{
          created: (mapInstance) => {
            console.log('地图创建成功', mapInstance);
          },
          click: (e) => {
            console.log('地图点击', e.lnglat);
          }
        }}
      >
        {/* 标记点将在这里渲染 */}
      </Map>
    </div>
  );
};

export default ReactAmap***ponent;

2. 地图配置选项

const mapOptions = {
  amapkey: "YOUR_API_KEY",
  center: [116.397428, 39.90923], // 地图中心点
  zoom: 10,                       // 缩放级别
  mapStyle: 'amap://styles/normal', // 地图样式
  features: ['bg', 'point', 'road', 'building'], // 地图要素
  viewMode: '3D',                 // 地图模式
  pitch: 0,                       // 地图俯仰角度
  rotation: 0,                    // 地图旋转角度
  animateEnable: true,            // 地图平移过程中是否使用动画
  keyboardEnable: true,           // 地图是否可通过键盘控制
  dragEnable: true,               // 地图是否可通过鼠标拖拽平移
  zoomEnable: true,               // 地图是否可缩放
  doubleClickZoom: true,          // 地图是否可通过双击鼠标放大地图
  scrollWheel: true,              // 地图是否可通过鼠标滚轮缩放浏览
};

标记点功能

1. 渲染标记点

const renderMarkers = () => {
  return markers.map((marker) => {
    const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);
    
    return (
      <Marker
        key={marker.ID}
        position={[lng, lat]}
        title={marker.AS_NAME}
        events={{
          click: () => handleMarkerClick(marker)
        }}
      />
    );
  });
};

const handleMarkerClick = (marker: MarkerData) => {
  console.log('标记点击', marker);
  setActiveInfoWindow(marker.ID);
};

2. 自定义标记点图标

const getMarkerIcon = (isOperating: number) => {
  return {
    image: isOperating === 1 
      ? '/icons/marker-operating.png' 
      : '/icons/marker-non-operating.png',
    size: [32, 32],
    imageSize: [32, 32]
  };
};

<Marker
  position={[lng, lat]}
  icon={getMarkerIcon(marker.IS_OPERATING)}
  events={{
    click: () => handleMarkerClick(marker)
  }}
/>

信息窗口

1. 基础信息窗口

const [activeInfoWindow, setActiveInfoWindow] = useState<string | null>(null);

const renderInfoWindow = () => {
  if (!activeInfoWindow) return null;
  
  const activeMarker = markers.find(m => m.ID === activeInfoWindow);
  if (!activeMarker) return null;
  
  const [lng, lat] = activeMarker.AS_LONGITUDE_LATITUDE.split(',').map(Number);
  const position = [lng, lat];
  
  return (
    <InfoWindow
      position={position}
      visible={true}
      closeWhenClickMap={false}
      showShadow={false}
      isCustom={true}
      events={{
        close: () => setActiveInfoWindow(null)
      }}
    >
      <div style={{
        width: '380px',
        maxHeight: '480px',
        overflow: 'auto',
        padding: '0',
        margin: '0'
      }}>
        <CardInfo 
          assetData={activeMarker} 
          onClose={() => setActiveInfoWindow(null)} 
        />
      </div>
    </InfoWindow>
  );
};

2. 自定义信息窗口组件

interface CardInfoProps {
  assetData: MarkerData;
  onClose?: () => void;
}

const CardInfo: React.FC<CardInfoProps> = ({ assetData, onClose }) => {
  return (
    <div className="map-info-window">
      <div className="info-window-header">
        <div className="info-window-title">{assetData.AS_NAME || '资产详情'}</div>
        <button 
          className="info-window-close" 
          type="button" 
          aria-label="关闭"
          onClick={onClose}
        >
          ×
        </button>
      </div>
      
      <div className="info-window-content">
        <div className="info-section">
          <div className="section-title">基础信息</div>
          <div className="info-grid">
            <div className="info-row">
              <div className="info-label">资产编号:</div>
              <div className="info-value">{assetData.AS_CODE || '-'}</div>
            </div>
            <div className="info-row">
              <div className="info-label">建筑面积:</div>
              <div className="info-value">{assetData.AREA || '0'}</div>
            </div>
          </div>
        </div>
      </div>
      
      <style jsx>{`
        .map-info-window {
          background: white;
          border-radius: 8px;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
          overflow: hidden;
        }
        .info-window-header {
          background: #f8f9fa;
          padding: 15px;
          border-bottom: 1px solid #e9ecef;
          position: relative;
        }
        .info-window-title {
          font-size: 16px;
          font-weight: bold;
          color: #333;
          margin: 0;
        }
        .info-window-close {
          position: absolute;
          top: 18px;
          right: 15px;
          background: none;
          border: none;
          font-size: 18px;
          cursor: pointer;
          color: #999;
        }
      `}</style>
    </div>
  );
};

事件处理

1. 地图事件

const mapEvents = {
  created: (mapInstance: any) => {
    console.log('地图实例创建', mapInstance);
  },
  click: (e: any) => {
    console.log('地图点击', e.lnglat);
    setActiveInfoWindow(null); // 点击地图关闭信息窗口
  },
  zoomchange: (e: any) => {
    console.log('缩放级别改变', e.zoom);
  },
  moveend: (e: any) => {
    console.log('地图移动结束', e.target.getCenter());
  }
};

2. 标记点事件

const markerEvents = {
  click: (marker: MarkerData) => {
    handleMarkerClick(marker);
  },
  mouseover: (e: any) => {
    console.log('鼠标悬停在标记上');
  },
  mouseout: (e: any) => {
    console.log('鼠标离开标记');
  }
};

样式定制

1. 信息窗口样式

.map-info-window {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.info-window-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 15px;
  color: white;
  position: relative;
}

.info-window-title {
  font-size: 16px;
  font-weight: bold;
  margin: 0;
}

.info-window-close {
  position: absolute;
  top: 18px;
  right: 15px;
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.8);
  transition: color 0.3s;
}

.info-window-close:hover {
  color: white;
}

.info-window-content {
  padding: 18px 15px;
  max-height: 450px;
  overflow-y: auto;
}

.info-section {
  margin-bottom: 20px;
}

.section-title {
  font-size: 15px;
  font-weight: bold;
  color: #1890ff;
  margin-bottom: 15px;
}

.info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.info-row {
  display: flex;
  margin-bottom: 10px;
  font-size: 14px;
}

.info-label {
  color: #666;
  width: 85px;
  flex-shrink: 0;
}

.info-value {
  color: #333;
  flex-grow: 1;
}

2. 地图容器样式

.map-container {
  width: 100%;
  height: 600px;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.map-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  background: #f5f5f5;
  color: #666;
}

数据处理

1. 获取地图数据

const fetchMapData = async (filters: FilterParams) => {
  try {
    setLoading(true);
    
    const params = {
      tableName: 'AS_ASSET_INFO',
      condition: buildCondition(filters),
      pageSize: 1000
    };
    
    const response = await getDataByCondition(params);
    
    if (response.su***ess && response.data) {
      const validMarkers = response.data.filter((item: any) => 
        item.AS_LONGITUDE_LATITUDE && 
        item.AS_LONGITUDE_LATITUDE.includes(',')
      );
      
      setMarkers(validMarkers);
      updateMapCenter(validMarkers);
    }
  } catch (error) {
    console.error('获取地图数据失败:', error);
  } finally {
    setLoading(false);
  }
};

2. 构建查询条件

const buildCondition = (filters: FilterParams) => {
  const conditions = [];
  
  if (filters.***pany) {
    conditions.push(`***_ID = '${filters.***pany}'`);
  }
  
  if (filters.department) {
    conditions.push(`AS_CORE_ORG_ID = '${filters.department}'`);
  }
  
  if (filters.assetType) {
    conditions.push(`ASSET_TYPE = '${filters.assetType}'`);
  }
  
  return conditions.length > 0 ? conditions.join(' AND ') : '';
};

3. 更新地图中心点

const updateMapCenter = (markers: MarkerData[]) => {
  if (markers.length === 0) return;
  
  const validCoords = markers
    .filter(marker => marker.AS_LONGITUDE_LATITUDE)
    .map(marker => {
      const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);
      return [lng, lat];
    });
  
  if (validCoords.length > 0) {
    const avgLng = validCoords.reduce((sum, coord) => sum + coord[0], 0) / validCoords.length;
    const avgLat = validCoords.reduce((sum, coord) => sum + coord[1], 0) / validCoords.length;
    
    setMapCenter([avgLng, avgLat]);
  }
};

性能优化

1. 标记点聚合

import { MarkerClusterer } from 'react-amap';

const renderClusteredMarkers = () => {
  return (
    <MarkerClusterer
      gridSize={60}
      maxZoom={18}
      averageCenter={true}
      styles={[
        {
          url: '/icons/cluster-small.png',
          size: [40, 40],
          textColor: '#fff',
          textSize: 12
        },
        {
          url: '/icons/cluster-medium.png',
          size: [50, 50],
          textColor: '#fff',
          textSize: 14
        },
        {
          url: '/icons/cluster-large.png',
          size: [60, 60],
          textColor: '#fff',
          textSize: 16
        }
      ]}
    >
      {renderMarkers()}
    </MarkerClusterer>
  );
};

2. 懒加载和虚拟化

const [visibleMarkers, setVisibleMarkers] = useState<MarkerData[]>([]);

const updateVisibleMarkers = useCallback((bounds: any) => {
  const visible = markers.filter(marker => {
    const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);
    return bounds.contains([lng, lat]);
  });
  
  setVisibleMarkers(visible);
}, [markers]);

const mapEvents = {
  moveend: (e: any) => {
    const bounds = e.target.getBounds();
    updateVisibleMarkers(bounds);
  },
  zoomend: (e: any) => {
    const bounds = e.target.getBounds();
    updateVisibleMarkers(bounds);
  }
};

常见问题

1. 地图不显示

问题: 地图容器为空或显示空白

解决方案:

  • 检查 API Key 是否正确
  • 确保地图容器有明确的宽高
  • 检查网络连接和控制台错误
// 确保容器有明确尺寸
<div style={{ width: '100%', height: '600px' }}>
  <Map amapkey="YOUR_API_KEY" />
</div>

2. 标记点不显示

问题: 标记点无法在地图上显示

解决方案:

  • 检查坐标格式是否正确
  • 确保坐标在有效范围内
  • 检查标记点数据是否正确传递
// 坐标验证
const isValidCoordinate = (lngLat: string) => {
  const [lng, lat] = lngLat.split(',').map(Number);
  return !isNaN(lng) && !isNaN(lat) && 
         lng >= -180 && lng <= 180 && 
         lat >= -90 && lat <= 90;
};

3. 信息窗口样式问题

问题: 信息窗口样式不生效或显示异常

解决方案:

  • 使用 isCustom={true} 启用自定义样式
  • 确保 CSS 样式正确加载
  • 检查 z-index 层级问题
<InfoWindow
  isCustom={true}
  closeWhenClickMap={false}
  showShadow={false}
>
  {/* 自定义内容 */}
</InfoWindow>

4. React 18 兼容性

问题: React 18 中出现 ReactDOM.render 警告

解决方案:

  • 避免使用 ReactDOMServer.renderToString
  • 直接在 InfoWindow 中渲染 React 组件
  • 使用最新版本的 react-amap

5. 性能优化建议

  • 使用标记点聚合减少渲染数量
  • 实现视口内标记点的懒加载
  • 避免在 render 方法中创建新对象
  • 使用 React.memo 优化组件重渲染
const Marker***ponent = React.memo(({ marker, onClick }) => {
  const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);
  
  return (
    <Marker
      position={[lng, lat]}
      events={{ click: () => onClick(marker) }}
    />
  );
});

总结

本文档涵盖了在 React 项目中使用高德地图的主要场景和最佳实践。通过合理的组件设计、事件处理和性能优化,可以构建出功能丰富、用户体验良好的地图应用。

在实际开发中,建议根据具体需求选择合适的功能模块,并注意处理边界情况和错误状态,确保应用的稳定性和可用性。

转载请说明出处内容投诉
CSS教程网 » React (react-amap)高德地图使用(加标记、缩放、缩略图)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买