全网稀缺,完整链家地图找房的实现(二)

3,106 阅读6分钟

全网稀缺,完整链家地图找房的实现(一)章节中,我们已经实现了地图找房中地图的初始化、区域气泡和区域边界的显示及交互,那么在本文中我们要做的就是地图周边房源的显示及交互。

项目已上线,猛戳我体验~

周边房源的显示

对于周边房源的显示逻辑大致如下,

  • 用户点击某个区域气泡,地图缩放层级增大(此时地图的 zoom 更新为区域气泡消失的临界值 ZOOMBOUNDARY 加1,this.zoom = ZOOMBOUNDARY + 1),此时周边房源气泡和周边房源总数提示(dataToast)出现,区域气泡和区域边界消失

    效果演示图

  • 用户放大地图,当缩放层级大于临界值的时候,同上;小于临界值的时候区域气泡出现,周边房源和周边房源总数提示消失

    效果演示图

  • 在地图缩放层级大于临界值时,用户拖动地图,拖动结束后更新周边房源(也就是发送 get请求,参数需要和后端同学沟通好,我这里是通过地图当前的中心点坐标查询)

    效果演示图
    在分析完周边房源显示的场景之后,我们就分为三步来逐步实现。

step1 -- 用户点击某个区域气泡

首先我们需要抽象一个周边房源组件 -- aroundOverlay,该组件和区域气泡组件(zoneOverlay)差不多,样式和内容稍作修改即可,

<template>
  <bm-overlay
    ref="customOverlay"
    class="around"
    pane="labelPane"
    @draw="draw">
    <div>
      <!-- div中的内容可自己调整 -->
      <p>{{text.title}}</p>
      <p>{{Math.ceil(text.initialPrice / 10000)}}万</p>
    </div>
  </bm-overlay>
</template>

<script>
export default {
  props: ['text', 'position', 'active'],
  watch: {
    position: {
      handler () {
        this.$refs.customOverlay.reload()
      },
      deep: true
    }
  },
  methods: {
    draw ({el, BMap, map}) {
      const {lng, lat} = this.position
      const pixel = map.pointToOverlayPixel(new BMap.Point(lng, lat))
      // 让周边房源气泡的中心和坐标对应上
      el.style.left = pixel.x - 61 + 'px'
      el.style.top = pixel.y - 18 + 'px'
    }
  }
}
</script>

<style lang="stylus" scoped>
  .around
    overflow: hidden
    width: 122px
    height: 36px
    padding: 5px
    box-shadow: 0 0 1px #bbb
    background-color: rgba(58,126,255,0.9)
    color: #fff
    text-align: center
    position: absolute
    font-size: 12px
    line-height: 13px
    border-radius: 5px
    box-sizing: border-box
    div
      display: flex
      flex-wrap: wrap
      overflow: hidden
      text-overflow: ellipsis
      white-space: nowrap
      justify-content: space-between
      p
        overflow: hidden
        text-overflow: ellipsis
        white-space: nowrap
        width: 100%
        text-align: center
</style>

以及周边房源提示组件 -- dataToast

<template>
  <div class="toast">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'dataToast'
}
</script>

<style lang="scss" scoped>
  .toast{
    position: fixed;
    left: 0;
    right: 0;
    bottom: 20px;
    width: 340px;
    height: 26px;
    margin: auto;
    padding: 3px 10px;
    background-color: rgba(0,0,0,.7);
    color: #fff;
    font-size: 12px;
    line-height: 20px;
    box-sizing: border-box;
  }
</style>

在地图容器中使用 aroundOverlay 组件,

<baidu-map id="bm-view" class="bm-view" :center="center" :zoom="zoom" :scroll-wheel-zoom="true" :inertial-dragging="true" @ready="handler" @zoomend="syncCenterAndZoom">
  <bm-boundary v-if="showBoundary" :name="zoneBoundary" :strokeWeight="2" strokeColor="blue" fillColor="skyblue" :fillOpacity="0.4"></bm-boundary>
  <div v-if="showZone">
    <zone-overlay
      v-for="(item, index) in zoneGeoPoints" :key="index"
      :position="{lng: item.lng, lat: item.lat}"
      :text="item"
      @mouseover.native="selectZone(item, index)"
      @mouseleave.native="cancelZone"
      @click.native="selZone(item, index)">
    </zone-overlay>
  </div>
  <div v-if="!showZone">
    <around-overlay
      v-for="(item, index) in aroundGeoPoints" :key="index"
      :position="{lng: item.lng, lat: item.lat}"
      :text="item">
    </around-overlay>
  </div>
</baidu-map>

用户点击某个区域气泡,也就是对应的selZine事件,此时区域气泡和区域边界消失,而周边房源出现,设置地图缩放层级 zoom为临界值ZOOMBOUNDARY加1,并且设置地图中心点的经纬度 center,最后发送请求获取周边房源数据,

selZone (item, index) {
  this.showZone = false
  this.$set(this.center, 'lng', item.lng)
  this.$set(this.center, 'lat', item.lat)
  this.zoneBoundary = ''
  this.showBoundary = false
  this.zoom = ZOOMBOUNDARY + 1
  // updateHouseList 获取周边房源,使用vuex存储部分数据,这里需要自己设计
  this.updateHouseList(item.lng, item.lat)
},

对于 dataToast 组件,我是放在地图容器外面,这里需要把和地图相关的部分放在一个组件里(我抽象成了 searchMap 组件),然后和其他的内容放在一个容器div里面,

<div>
    <searchMap></searchMap>
    <!-- 在上面的updateHouseList方法中将缩放层级存储在vuex中,消失和显示与周边房源组件同步,aroundCnt对应周边房源总数 -->
    <dataToast v-if="this.$store.state.showAround > ZOOMBOUNDARY ">视野内共有{{aroundCnt}}个房源,拖动地图查看更多~</dataToast>
</div>

searchMap 组件

<div>
    <!-- 把之前的baidu-map搬过来就行了,下面是简写 -->
    <baidu-map></baidu-map>
</div>

step2 -- 用户缩放地图

在这个过程中我们需要双向绑定地图的缩放层级更新 zoom,并且获取当前经纬度,再比较 zoom 和临界值 ZOOMBOUNDARY 显示和隐藏周边房源气泡,对 @zoomend="syncCenterAndZoom" 作如下修改

syncCenterAndZoom (e) {
  this.zoom = e.target.getZoom() // 更新 zoom
  if (this.zoom > ZOOMBOUNDARY) {
    this.showZone = false
    // 获取周边房源数据,仅供参考
    const {lng, lat} = e.target.getCenter()
    this.updateHouseList(lng, lat)
  } else {
    this.showZone = true
  }
}

step3 -- 用户移动地图

这个过程需要双向绑定地图当前经纬度,并根据经纬度更新周边房源

<baidu-map id="bm-view" class="bm-view" :center="center" :zoom="zoom" :scroll-wheel-zoom="true" :inertial-dragging="true" @ready="handler" @zoomend="syncCenterAndZoom" @moveend="syncCenterAndZoom_"></baidu-map>

@moveend="syncCenterAndZoom_" 表示在地图移动结束时触发 syncCenterAndZoom_,我们在此方法中双向当前的绑定经纬度

syncCenterAndZoom_ (e) {
  const zoom = e.target.getZoom()
  if (zoom > ZOOMBOUNDARY) {
    // 更新周边房源数据,仅供参考
    const {lng, lat} = e.target.getCenter()
    this.updateHouseList(lng, lat)
  }
}

周边房源的交互

css :hover伪类

同区域气泡一样,当鼠标滑入某个周边房源时,该气泡高亮,鼠标滑出时,该气泡恢复,由于气泡的onmouseover 和 onmouseleave 没有缓动效果,所以换用css的hover伪类,修改 aroundOverlay 的stylus

.around
    transition: background-color .15s ease-in-out /* 缓动效果,可自己调整 */
    overflow: hidden
    width: 122px
    height: 36px
    padding: 5px
    box-shadow: 0 0 1px #bbb
    background-color: rgba(58,126,255,0.9)
    color: #fff
    text-align: center
    position: absolute
    font-size: 12px
    line-height: 13px
    border-radius: 5px
    box-sizing: border-box
    &:hover
      z-index: 1
      background-color: rgba(240,65,52,.9)
      color: #fff
    div
      display: flex
      flex-wrap: wrap
      overflow: hidden
      text-overflow: ellipsis
      white-space: nowrap
      justify-content: space-between
      p
        overflow: hidden
        text-overflow: ellipsis
        white-space: nowrap
        width: 100%
        text-align: center
/* .active是动态的类名,在后面的鼠标点击事件会用到,主要用于高亮显示 */  
.around.active
    z-index: 1
    background-color: rgba(240,65,52,.9)
    color: #fff

气泡 hover 时 z-index 为1是为了让高亮的气泡不会被其他气泡遮盖住,因为层叠上下文的原因,所以只需设置 z-index 为1即可,如果还不知道层叠上下文的话,可以去看张鑫旭老师的深入理解CSS中的层叠上下文和层叠顺序

鼠标点击事件 @click.native

我们希望不仅要在鼠标滑入某个周边房源气泡时,该气泡高亮,而且希望鼠标点击也同样高亮,这里就要用到 @click.native (native是修饰符,会把组件当成普通的html标签),动态绑定一个类名,作如下修改

<div v-if="!showZone && this.$store.state.toggleAround">
    <around-overlay
      v-for="(item, index) in aroundGeoPoints" :key="index"
      v-show="!item.isShow"
      :class="curAround === item.id?'active':''"
      :position="{lng: item.lng, lat: item.lat}"
      :text="item"
      @click.native="selAround(item, index)">
    </around-overlay>
</div>
selAround (item, index) {
  this.curAround = item.id
}

这里我踩了一个坑,一开始我是根据 index 来动态绑定类名,

:class="curAround === index?'active':''"

selAround (item, index) {
  this.curAround = index
}

这样是不行的,因为数组 aroundGeoPoints 更新之后(拖动地图结束后发送请求更新周边房源),会出现原来在 aroundGeoPoints 下标为5的元素下标变成了8,原来下标为5的元素是高亮的,数组更新之后就不高亮了,因为他的下标变成了8,因此换成了根据元素的 id(从后台获取) 来绑定类名,因为元素的 id 是唯一的。

房源详情气泡 -- detailOverlay

最后就是房源详情气泡了,类似于一张卡片,展示房源的信息,抽象 detailOverlay 组件

<template>
  <bm-overlay
    ref="customOverlay"
    class="detail"
    pane="labelPane"
    @draw="draw">
    <slot></slot>
  </bm-overlay>
</template>

<script>
export default {
  props: ['text', 'position', 'active'],
  watch: {
    position: {
      handler () {
        this.$refs.customOverlay.reload()
      },
      deep: true
    }
  },
  methods: {
    draw ({el, BMap, map}) {
      const {lng, lat} = this.position
      const pixel = map.pointToOverlayPixel(new BMap.Point(lng, lat))
      el.style.left = pixel.x - 200 + 'px'
      el.style.top = pixel.y - 180 + 'px' // 详情气泡底端距房源气泡顶端的距离,可自己调整
    }
  }
}
</script>

<style lang="stylus" scoped>
  .detail
    z-index: 9
    transition: background-color .15s ease-in-out
    width: 400px
    height: 140px
    padding: 20px
    box-shadow: 0 0 10px #bbb
    background-color: #fff
    color: #fff
    text-align: center
    position: absolute
    font-size: 12px
    line-height: 13px
    border-radius: 5px
    box-sizing: border-box
    div
      display: flex
      flex-wrap: wrap
      overflow: hidden
      text-overflow: ellipsis
      white-space: nowrap
      justify-content: space-between
      p
        overflow: hidden
        text-overflow: ellipsis
        white-space: nowrap
        width: 100%
        text-align: center
    &:after
      content: ''
      display: block
      position: absolute
      left: 50%
      bottom: -20px
      width: 0
      height: 0
      margin-left: -10px
      border: 10px solid transparent
      border-top-color: #fff
</style>

在 baidu-map 中使用

<baidu-map>
    <div v-if="toggleDetail">
        <detail-overlay
          :detailGeo="detailGeo"
          :position="{lng: detailGeo.lng, lat: detailGeo.lat}">
          <!-- info-wrapper 中的内容需要自己替换,仅供参考 -->
          <div class="info-wrapper" v-if="toggleDetail">
            <div class="img">
              <img :src="detailGeo.pic_url" alt="房源图片">
            </div>
            <div class="info">
              <h4 class="title">{{detailGeo.title}}</h4>
              <div class="price">
                <div>
                  <p>当前价</p>
                  <p>{{parseInt(detailGeo.currentPrice / 100) / 100}}万</p>
                </div>
                <div>
                  <p>法院评估价</p>
                  <p>{{parseInt(detailGeo.consultPrice / 100) / 100}}万</p>
                </div>
                <div>
                  <p>市场评估价</p>
                  <p>{{parseInt(detailGeo.marketPrice / 100) / 100}}万</p>
                </div>
              </div>
            </div>
          </div>
        </detail-overlay>
    </div>
</baidu-map>

当鼠标滑入房源气泡时,详情气泡出现,鼠标滑出时,详情气泡消失,所以在 aroundOverlay 组件加上 onmouseover 和 onmouseleave

<around-overlay
  v-for="(item, index) in aroundGeoPoints" :key="index"
  v-show="!item.isShow"
  :class="curAround === item.id?'active':''"
  :position="{lng: item.lng, lat: item.lat}"
  :text="item"
  @click.native="selAround(item, index)"
  @mouseover.native="showCurInfo(item, index)"
  @mouseleave.native="hideCurInfo">
</around-overlay>
// 下面我做了防抖处理, deBounce 是引入的防抖函数
showCurInfo (item, index) {
  this.infoPromise = new Promise(resolve => {
    deBounce(() => {
      this.detailGeo = item
      this.toggleDetail = true
      resolve()
    })
  })
  return this.infoPromise
},
hideCurInfo () {
  this.infoPromise.then(() => {
    this.toggleDetail = false
  })
}

现在我们已经实现了区域气泡、区域边界、周边房源及其详情、周边房源数量提示的展示和交互,效果如下

最后就剩下画圈找房还没实现了,感谢阅读,to be continued~