uniapp地图组件map+nvue案例分享,包括地图标点、动态缩放、nvue开发注意事项等
一、概要说明&效果图
1、概要说明
需求:
1、实现地图显示标记点
markers
,点击标记点弹出详情框
2、缩放地图时,如果缩小到一定层级只显示地区的店铺数量,反之则显示店铺标记点。模拟点聚合效果
3、实现按钮
功能:返回原位、放大地图、缩小地图
4、视野范围内没有店铺marker点时,顶部弹出提示
所用技术:
1、
uniapp
的map
地图组件
2、UI框架采用uview
3、由于对地图的自定义程度较高(弹出详情页、按钮等),所以采用nvue
,解决map的组件层级问题
2、效果图(仅作demo学习使用):
1、进入页面的初始状态(左),和点击marker标记点弹出详情的效果(右)
2、将地图缩小到一定层级的效果(左),当前视野内没有marker点时弹出提示的效果(右)
二、nvue下iconfont的引入和使用方法,及nvue开发注意事项
1、iconfont引入和使用
在script
的顶部添加如下代码:
<script>
// #ifdef APP-NVUE
// nvue通过weex的dom模块引入字体,相关文档地址如下:
// https://weex.apache.org/zh/docs/modules/dom.html#addrule
const fontUrl = '/static/iconfont/iconfont.ttf'
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
'fontFamily': "iconfont",
'src': `url('${fontUrl}')`
})
// #endif
</script>
在css
中加入以下代码:
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
// 非nvue下加载字体
@font-face {
font-family: 'iconfont';
src: url('/static/iconfont/iconfont.ttf') format('truetype');
}
/* #endif */
.nvue-iconfont{
font-family: iconfont;
}
</style>
在DOM
中使用如下:
<text class="nvue-iconfont"></text>
2、nvue开发注意事项
- nvue只支持
类选择器
,也就是说标签选择器、ID选择器都不适用,DOM里要写很多的class - nvue不支持
/deep/
、::v-deep
等任何的组件穿透样式,只能通过组件传参的方式修改子组件样式 - nvue为
flex
布局,且默认flex-direction: column
; - nvue对文字的
color
和字体大小fontsize
的修改,只在text
标签下有效。所以要养成好习惯,涉及到文字的要用text标签。且text标签不能再嵌套view标签和text标签。 - 涉及到滚动要用
<scroll-view>
标签,overflow-y: scroll和auto都是不支持的 - 不能使用
百分比
、vh
、calc
、auto
等宽高的计算样式,尽量使用px
、rpx
三、在地图组件上标记点markers,点击弹出详情框;实现按钮:回到原位、放大、缩小
DOM代码:
<!-- 页面返回按钮 -->
<view @click="navToBack" class="nav-back" :style="{top: `${navTop}px`}">
<text class="nvue-iconfont"></text>
</view>
<map
:style="{
height: windowHeight + 'px',
top: '-' + statusBarHeight + 'px'
}"
id="map"
class="map"
:show-location="true"
theme="satellite"
:markers="markers"
:scale="scale"
:latitude="latitude"
:longitude="longitude"
:min-scale="minScale"
@regionchange="mapRegionchange"
@callouttap="markertap"
@markertap="markertap">
</map>
<!-- 按钮-返回原位 -->
<view class="tool-btn" @click="toLocation">
<text class="nvue-iconfont"></text>
</view>
<!-- 按钮-放大 -->
<view class="tool-btn scale-up-btn" @click="mapScaleUp">
<text class="nvue-iconfont"></text>
</view>
<!-- 按钮-缩小 -->
<view class="tool-btn scale-down-btn" @click="mapScaleDown">
<text class="nvue-iconfont"></text>
</view>
<!-- 店铺详情 -->
<u-popup :show="shopPopupShow" @close="closePopup" :round="15">
<view class="shop-detail-box">
<view class="shop-detail-header">
<image :src="shopData.logoUrl" class="shop-logo"></image>
<view class="shop-header-right">
<text class="shop-title">{{shopData.shopName}}</text>
<view class="shop-sub-title">
<view class="shop-rate">
<u-rate v-model="shopData.mark" :size="13" :gutter="2" readonly></u-rate>
<text class="rate-text">{{`${shopData.mark}分`}}</text>
</view>
<view class="shop-evaluate">
<text class="evaluate-text">{{`${shopData.evaluateNum}人评`}}</text>
<text class="nvue-iconfont icon-arrow-right"></text>
</view>
</view>
</view>
</view>
<text class="hours-text">营业时间:{{shopData.businessHours}}</text>
<view class="shop-addree-box" @click="openLocation">
<text class="nvue-iconfont"></text>
<text class="address-text">{{shopData.address}}</text>
<text class="nvue-iconfont icon-arrow-right"></text>
</view>
</view>
</u-popup>
data代码:
data() {
return {
// 计算高度属性
windowHeight: 800,
statusBarHeight: 30,
navTop: 30,
// 地图
_mapContext: null,
// 默认位置坐标
latitude: 28.682976,
longitude: 115.857972,
scale: 12,
minScale: 4.5,
// 地图标点
markers: [],
// 店铺标记点数据
shopMarkers: [
{
id: 0,
latitude: 28.657005,
longitude: 115.876998,
iconPath: '/static/img/deptIcon.png',
width: 18,
height: 23,
alpha: 1, //透明度
callout: { //自定义标记点上方的气泡窗口 点击有效
content: '西湖区分店1#',//文本
color: '#ffffff',//文字颜色
fontSize: 12,//文本大小
borderRadius: 5,//边框圆角
padding: 5,
bgColor: '#3f94fd',//背景颜色
display: 'ALWAYS',//常显
},
},
{
id: 1,
latitude: 28.62182,
longitude: 115.925709,
iconPath: '/static/img/deptIcon.png',
width: 18,
height: 23,
alpha: 1, //透明度
callout: { //自定义标记点上方的气泡窗口 点击有效
content: '青云谱分店2#',//文本
color: '#ffffff',//文字颜色
fontSize: 12,//文本大小
borderRadius: 5,//边框圆角
padding: 5,
bgColor: '#3f94fd',//背景颜色
display: 'ALWAYS',//常显
},
},
{
id: 2,
latitude: 28.681356,
longitude: 115.881141,
iconPath: '/static/img/deptIcon.png',
width: 18,
height: 23,
alpha: 1, //透明度
callout: { //自定义标记点上方的气泡窗口 点击有效
content: '滕王阁分店3#',//文本
color: '#ffffff',//文字颜色
fontSize: 12,//文本大小
borderRadius: 5,//边框圆角
padding: 5,
bgColor: '#3f94fd',//背景颜色
display: 'ALWAYS',//常显
},
},
// 更多数据在此不展示了,这里只做样例demo
],
// 店铺详情
shopPopupShow: false,
shopId: null,
shopData: {
id: 2,
logoUrl: '/static/img/logo.png',
shopName: '滕王阁分店3#',
address: '南昌市东湖区仿古街58号',
latitude: 28.681356,
longitude: 115.881141,
telPhone: '0791-XXXXXXX',
businessHours: '9:00-14:30, 16:30-22:00',
mark: 4.7,
evaluateNum: 1356,
averageAmount: 21,
},
}
}
js代码:
created() {
let that = this;
uni.getSystemInfo({
success(res) {
// #ifdef H5
that.windowHeight = res.windowHeight;
// #endif
// #ifndef H5
that.windowHeight = res.windowHeight + res.statusBarHeight;
// #endif
that.statusBarHeight = res.statusBarHeight;
that.navTop = res.statusBarHeight + 10;
}
})
this.getLocation();
this.getShopAllList();
},
onReady() {
this._mapContext = uni.createMapContext("map", this);
},
methods: {
// 返回按钮
navToBack(){
uni.navigateBack();
},
// 获取当前定位
getLocation(){
let that = this;
uni.getLocation({
type: 'gcj02',
success(res){
that.longitude = res.longitude;
that.latitude = res.latitude;
},
fail:function(e){
console.log("获取位置信息失败", e.errMsg);
}
})
},
// 获取所有店铺列表数据
getShopAllList(){
let that = this;
// 模拟向后端获取数据
setTimeout(()=>{
this.markers = this.shopMarkers;
this._mapContext.addMarkers({
markers: this.markers,
clear: true
})
}, 1000);
},
// 点击地图marker点
markertap(e){
this.shopId = e.detail.markerId;
// 此处根据拿到的id向后端发送请求,获取详情数据
this.shopPopupShow = true;
},
// 回到原位,恢复默认缩放
toLocation(){
this._mapContext.moveToLocation({
longitude: this.longitude,
latitude: this.latitude
});
this.updateMapScale(0, 12);
},
// 放大地图
mapScaleUp(){
this.updateMapScale(1);
},
// 缩小地图
mapScaleDown(){
this.updateMapScale(2);
},
//设置地图缩放等级
updateMapScale(type, level){
this._mapContext.getScale({
success: res=>{
this.scale = res.scale;
this.$nextTick(()=>{
// 指定缩放级别
if(type == 0){
this.scale = level
}
// 放大
else if(type == 1 && this.scale<20){
this.scale = this.scale+1>20 ? 20 : this.scale+1
}
// 缩小
else if(type == 2 && this.scale>this.minScale){
this.scale = this.scale-1<this.minScale ? this.minScale : this.scale-1
}
})
}
})
},
}
css代码:
$boxShadow: 0 0 8px #ccc;
.nav-back {
position: fixed;
z-index: 9;
left: 10px;
padding: 6px;
background-color: #fff;
width: 38px;
height: 38px;
align-items: center;
justify-content: center;
border-radius: 8px;
box-shadow: $boxShadow;
.nvue-iconfont{
font-size: 20px;
font-weight: bold;
}
}
.map{
width: 750rpx;
position: relative;
z-index: 0;
}
// 右侧按钮组
.tool-btn{
position: fixed;
right: 15px;
bottom: 350px;
background-color: #ffffff;
border-radius: 8px;
width: 38px;
height: 38px;
align-items: center;
justify-content: center;
box-shadow: $boxShadow;
.nvue-iconfont{
color: #222;
font-size: 22px;
}
&.scale-up-btn{
bottom: 300px;
.nvue-iconfont{
font-size: 17px;
}
}
&.scale-down-btn{
bottom: 250px;
.nvue-iconfont{
font-size: 17px;
}
}
}
// 店铺详情的样式这里就不放了,不是重点
四、不同缩放层级,地图展示的markers点不同,模拟点聚合的效果
1、增加一组地区的
markers
数据,记录地区的经纬度、当前地区的店铺数量
2、通过视野变化事件,实时判断当前地图的缩放层级,决定显示哪一组markers数据
data数据,增加以下属性:
scaleLevel: 0, //0-店铺级别;1-地区级别
// 地区标记点数据
cityMarkers: [
{
id: 'nc',
latitude: 28.682976,
longitude: 115.857972,
iconPath: '/static/img/transparent.png',
width: 10,
height: 10,
alpha: 1, //透明度
callout: { //自定义标记点上方的气泡窗口 点击有效
content: '南昌(7)',//文本
color: '#ffffff',//文字颜色
fontSize: 13,//文本大小
borderRadius: 5,//边框圆角
padding: 10,
bgColor: '#3f94fd',//背景颜色
display: 'ALWAYS',//常显
},
},
{
id: 'fz',
latitude: 26.074286,
longitude: 119.296411,
iconPath: '/static/img/transparent.png',
width: 10,
height: 10,
alpha: 1, //透明度
callout: { //自定义标记点上方的气泡窗口 点击有效
content: '福州(10)',//文本
color: '#ffffff',//文字颜色
fontSize: 13,//文本大小
borderRadius: 5,//边框圆角
padding: 10,
bgColor: '#3f94fd',//背景颜色
display: 'ALWAYS',//常显
},
},
{
id: 'xm',
latitude: 24.479627,
longitude: 118.08891,
iconPath: '/static/img/transparent.png',
width: 10,
height: 10,
alpha: 1, //透明度
callout: { //自定义标记点上方的气泡窗口 点击有效
content: '厦门(12)',//文本
color: '#ffffff',//文字颜色
fontSize: 13,//文本大小
borderRadius: 5,//边框圆角
padding: 10,
bgColor: '#3f94fd',//背景颜色
display: 'ALWAYS',//常显
},
},
// 更多数据在此不展示
]
js里,增加对视野变化事件的处理:
// 地图视野发生变化
mapRegionchange(e){
let that = this;
// 监听当前地图的缩放级别,根据不同缩放级别展示不同的marker数据点
this._mapContext.getScale({
success: res=>{
// 缩放到地区级别
if(res.scale<=8 && that.scaleLevel==0){
that.markers = that.cityMarkers;
that._mapContext.addMarkers({
markers: that.markers,
clear: true
})
that.scaleLevel = 1;
}
// 缩放到店铺级别
else if(res.scale>8 && that.scaleLevel==1){
that.markers = that.shopMarkers;
that._mapContext.addMarkers({
markers: that.markers,
clear: true
})
that.scaleLevel = 0;
}
}
})
},
修改点击marker的事件,当缩放级别为地区级时,点击地区,地图将会放大移动到当前地区:
// 点击地图marker点
markertap(e){
let that = this;
// 1、当前缩放为店铺级别,查看店铺详情
if(this.scaleLevel == 0){
this.shopId = e.detail.markerId;
this.shopPopupShow = true;
}
// 2、当前缩放为地区级别,将地图中心移到当前地区,缩放放大
else if(this.scaleLevel == 1){
let city = this.cityMarkers.filter(item=>item.id==e.detail.markerId)[0];
this._mapContext.moveToLocation({
longitude: Number(city.longitude),
latitude: Number(city.latitude),
success(res){
setTimeout(()=>{
that.updateMapScale(0, 12);
}, 500)
}
});
}
},
五、视野范围内没有marker点时进行提示
地图里没有marker点的时候,应该给用户提示,不然光溜溜的不好看嘿嘿
DOM代码:
<!-- 提示 -->
<u-transition
mode="fade-down"
:show="notifyShow"
:custom-style="notifyStyle"
:style="{top: `${navTop}px`}"
>
<text class="notify-text">可视范围没找到店铺,请移动或缩放地图</text>
</u-transition>
data代码增加以下属性:
// 提示相关属性
notifyShow: false,
notifyTimer: null,
notifyStyle: {
position: "fixed",
left: '60px',
width: "300px",
height: "38px",
backgroundColor: "#ffffff",
borderRadius: "8px",
alignItems: "center",
justifyContent: "center",
},
同样也是通过视野变化事件来判断的,修改上面的mapRegionchange
方法
// 地图视野发生变化
mapRegionchange(e){
let that = this;
// 1、检查是否有marker点在视野范围内
this._mapContext.getRegion({
success: res=>{
let northeast = res.northeast;
let southwest = res.southwest;
let flag = false;
for(let i=0; i<that.markers.length; i++){
let item = that.markers[i];
if(item.latitude>southwest.latitude
&& item.longitude>southwest.longitude
&& item.latitude<northeast.latitude
&& item.longitude<northeast.longitude){
flag = true;
break
}
}
// 视野范围内没有marker,弹出提示框
if(!flag){
// 定时器初始化之前,要执行清除操作,
that.clearTimer()
that.notifyShow = true;
this.notifyTimer = setTimeout(() => {
that.notifyShow = false;
// 倒计时结束,清除定时器,隐藏toast组件
that.clearTimer()
}, 1500);
}else{
that.clearTimer();
}
}
})
// 2、监听当前地图的缩放级别,根据不同缩放级别展示不同的marker数据点
// 同上面的代码
},
clearTimer() {
this.notifyShow = false
// 清除定时器
clearTimeout(this.notifyTimer)
this.notifyTimer = null
},
小结
OK,到此为止,就把实现目标需求的代码全部完成啦~
关于标记点的聚合,一开始想用joinCluster
实现,但是它是根据点之间的距离进行聚合,没办法对于地区进行聚合,不是我所希望的效果。所以另辟蹊径,也不知道这么对不对。
第一次做map地图,以上纯为个人思路,如果有更好的实现方式欢迎大家一起讨论哈