在全网稀缺,完整链家地图找房的实现(一)章节中,我们已经实现了地图找房中地图的初始化、区域气泡和区域边界的显示及交互,那么在本文中我们要做的就是地图周边房源的显示及交互。
周边房源的显示
对于周边房源的显示逻辑大致如下,
-
用户点击某个区域气泡,地图缩放层级增大(此时地图的 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~