0 需求
依次点击区-镇-小区,展示当前位置的数据列表 ;移动地图(movestart事件),隐藏详情。
1 准备工作
1.1 引入百度地图并配置ak
jspopularGL | 百度地图API SDK
1.2 根据IP定位获取当前定位
import axios from 'axios';export const getCurrentCity = () => {const localCity=JSON.parse(localStorage.getItem('hkzf_city'))if (!localCity) {//localStorage 中是否有定位城市,没有,保存并返回return new Promise((resolve, reject) => {const curCity = new window.BMapGL.LocalCity();curCity.get(async res => {try {//成功const result = await axios.get(`http://localhost:8080/area/info?name=${res.name}`)localStorage.setItem('hkzf_city', JSON.stringify(result.body))resolve(res.body)//返回结果为promise:如果直接return,外层函数无法接收到} catch(e) {//失败reject(e)}})})}return Promise.resolve(localCity)//有,直接返回:此处的 Promise 不会失败
}
2 代码实现
import React from 'react'
import styles from './index.module.scss'
import NavHeader from '../../components/NavHeader'
import axios from 'axios'
import { Link } from 'react-router-dom'
import { Toast } from 'antd-mobile'export default class Map extends React.Component {// 覆盖物样式labelStyle = {cursor: 'pointer',border: '0px solid rgb(255, 0, 0)',padding: '0px',whiteSpace: 'nowrap',fontSize: '12px',color: 'rgb(255, 255, 255)',textAlign: 'center'}state = {housesList: [],isShowList: false}componentDidMount () {this.initMap();}initMap () {// 1初始化地图实例const { label, value } = JSON.parse(localStorage.getItem('hkzf_city'));//当前城市name,idvar map = new window.BMapGL.Map("container");//在 react 脚手架中全局对象需要使用 window 来访问,否则,会造成 ESLint 校验错误this.map = map;// 2将地址转换为坐标var myGeo = new window.BMapGL.Geocoder(); //创建地址解析器实例myGeo.getPoint(label, (point) => {// 将地址解析结果显示在地图上,并调整地图视野if (point) {// 2.1设置中心点map.centerAndZoom(point, 11);// 2.2添加地图控件map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放var scaleCtrl = new window.BMapGL.ScaleControl();// 添加比例尺控件map.addControl(scaleCtrl);var zoomCtrl = new window.BMapGL.ZoomControl();// 添加缩放控件map.addControl(zoomCtrl);// 2.3渲染覆盖物this.renderOverlays(value)} else {alert('您选择的地址没有解析到结果!');}}, label)// 给地图绑定移动事件map.addEventListener('movestart', () => {console.log('movestart')if (this.state.isShowList) {this.setState({isShowList: false})}})}// 渲染覆盖物async renderOverlays (id) {try {Toast.show({icon: 'loading',content: '加载中…',duration: 0,})// 获取数据const res = await axios.get(`http://localhost:8080/area/map?id=${id}`)Toast.clear()const data = res.data.body// 获取类型及缩放级别const { nextZoom, type } = this.getTypeAndZoom()// 渲染数据this.map.clearOverlays();data.forEach(item => {this.createOverlays(item, nextZoom, type)})} catch (e) {Toast.clear()}}// 计算要绘制的覆盖物类型和下一个缩放级别getTypeAndZoom () {const zoom = this.map.getZoom();let nextZoom, typeif (zoom >= 10 && zoom < 12) {// 区nextZoom = 13type = 'circle'} else if (zoom >= 12 && zoom < 14) {// 镇nextZoom = 15type = 'circle'} else if (zoom >= 14 && zoom < 16) {// 小区type = 'rect'}return { nextZoom, type }}// 创建覆盖物createOverlays (item, nextZoom, type) {const {coord: { longitude, latitude },label: areaName,count, value} = item;// 将经纬度转化为坐标var pointArea = new window.BMapGL.Point(longitude, latitude);if (type === 'circle') {this.createCircle(pointArea, areaName, count, value, nextZoom)} else {this.createRect(pointArea, areaName, count, value)}}// 创建区、镇覆盖物createCircle (pointArea, areaName, count, value, nextZoom) {// 1创建文本标注覆盖物实例var content = "文本覆盖物";var labelTXT = new window.BMapGL.Label(content, {position: pointArea,// 设置标注的地理位置offset: new window.BMapGL.Size(-35, -35)// 设置标注的偏移量})// 2设置结构labelTXT.setContent(`<div class="${styles.bubble}"><p class="${styles.name}">${areaName}</p><p>${count}</p></div>`)// 3设置样式labelTXT.setStyle(this.labelStyle)this.map.addOverlay(labelTXT);// 将标注添加到地图中// 4监听标注事件labelTXT.addEventListener('click', (e) => {this.renderOverlays(value)this.map.centerAndZoom(pointArea, nextZoom);});}// 创建小区覆盖物createRect (pointArea, areaName, count, value) {var content = "";var labelTXT = new window.BMapGL.Label(content, {position: pointArea,offset: new window.BMapGL.Size(-50, -28)})labelTXT.setContent(`<div class="${styles.rect}"><span class="${styles.housename}">${areaName}</span><span class="${styles.housenum}">${count}套</span><i class="${styles.arrow}"></i></div>`)labelTXT.setStyle(this.labelStyle)this.map.addOverlay(labelTXT);labelTXT.addEventListener('click', (e) => {// 获取小区房源数据this.getHouseList(value);const { x, y, width, height } = e.target.domElement.getBoundingClientRect()console.log(x, y, width, height)// 将被点击的房源移动到中心位置this.map.panBy(window.innerWidth / 2 - x,(window.innerHeight - 330) / 2 - y)});}// 获取小区房源数据async getHouseList (id) {try {Toast.show({icon: 'loading',content: '加载中…',duration: 0,})const res = await axios.get(`http://localhost:8080/houses?cityId=${id}`)Toast.clear()this.setState({housesList: res.data.body.list,isShowList: true})} catch (e) {Toast.clear()}}// 渲染小区房源数据renderHousesList () {return this.state.housesList.map(item => (<div className={styles.house} key={item.houseCode}><div className={styles.imgWrap}><imgclassName={styles.img}src={`http://localhost:8080${item.houseImg}`}alt=""/></div><div className={styles.content}><h3 className={styles.title}>{item.title}</h3><div className={styles.desc}>{item.desc}</div><div>{item.tags.map(tag => (<spanclassName={[styles.tag, styles.tag1].join(' ')}key={tag}>{tag}</span>))}</div><div className={styles.price}><span className={styles.priceNum}>{item.price}</span> 元/月</div></div></div>))}render () {return (<div className={styles.map}><NavHeader>地图找房</NavHeader><div id="container" className={styles.container}></div>{/* 房源列表 */}<divclassName={[styles.houseList,this.state.isShowList ? styles.show : ''].join(' ')}><div className={styles.titleWrap}><h1 className={styles.listTitle}>房屋列表</h1><Link className={styles.titleMore} to="/home/list">更多房源</Link></div><div className={styles.houseItems}>{/* 房屋结构 */}{this.renderHousesList()}</div></div></div>)}
}
3 代码分析
3.1 封装流程
3.2 代码逻辑
初始化渲染:创建map实例,以当前定位为中心点渲染地图(使用IP定位),并添加控件;
渲染覆盖物:
获取数据:接口需返回的数据信息包括:位置id,位置name,当前位置的经纬度;
渲染数据:创建文本标注实例,设置结构和样式后添加到地图上。
点击覆盖物:
圆形:放大地图;渲染下一级数据。
矩形:移动地图;渲染列表数据。
注意:事件对象中e的clientX=undefined,需要借助getBoundingClientRect()将数据移动到中心点位置。