如何在vue单页应用中使用百度地图
作为一名开发人员,每次接到开发任务,我们首先应该先分析需求,然后再思考技术方案和解决方案。三思而后行,这是一个好的习惯。
需求:本项目是采用vue组件化开发的单页应用项目,现需要在项目中引入百度的地图展示功能,用于展示所有项目的分布情况。搜索功能(省略,不是这里介绍的内容).......
交互:选中左侧的项目,选中项高亮,自动定位到右侧地图中项目所在位置,并弹出项目的基本信息。点击右侧的项目,自动高亮显示左侧的项目,并滚动到项目所在位置。地图支持聚合和缩放。
项目运行效果如下图所示:
接下来开始开发:
在vue中引入百度地图
百度开发者平台已经封装了基于vue的地图组件,详细使用,请参考官网:
网上有一些是直接在index.html页面全部引用的,本人强烈反对此种使用方式,因为我们项目是组件化的单页应用,强行引入多页应用的开发方式,会破坏整个项目的框架,严重影响性能。有些甚至还在vue单页应用中引入jquery,感觉这都是一些反人类的骚操作,不到万不得已,不建议使用。
使用方式
我这里只演示单页应用的开发方式。
1.安装组件
$ npm install vue-baidu-map --save
2.注册组件
组件的注册可以分为全局注册和局部注册,我这里采用的是局部注册。因为整个项目中仅此一个界面使用。引入官方的说明:
如果有按需引入组件的需要,可以选择局部注册百度地图组件,这将减少工程打包后的容量尺寸。局部注册的
baidumap
组件必须声明ak
属性。 所有的独立组件均存放在vue-baidu-map/components
文件夹下,按需引用即可。 由于未编译的 es 模块不能在大多数浏览器中直接运行,如果引入组件时发生运行时错误,请检查 webpack 的 loader 配置,确认include
和exclude
选项命中了组件库。
引入组件代码如下:
//百度地图 import baidumap from 'vue-baidu-map/components/map/map.vue' import bmscale from 'vue-baidu-map/components/controls/scale' import bmnavigation from 'vue-baidu-map/components/controls/navigation' import bmmarkerclusterer from 'vue-baidu-map/components/extra/markerclusterer' import bmmarker from 'vue-baidu-map/components/overlays/marker' import bminfowindow from 'vue-baidu-map/components/overlays/infowindow'
组件注册:
export default { name: "pm-distribution", components: { baidumap, bmscale, bmnavigation, bmmarkerclusterer, bmmarker, bminfowindow }, ......
3.html部分:
<baidu-map :style="{width:map.width,height:map.height}" class="map" ak="你的百度地图秘钥" :zoom="map.zoom" :center="{lng: map.center.lng, lat: map.center.lat}"
@ready="handler" :scroll-wheel-zoom="true"> <!--比例尺控件--> <bm-scale anchor="bmap_anchor_top_right"></bm-scale> <!--缩放控件--> <bm-navigation anchor="bmap_anchor_bottom_right" ></bm-navigation> <!--聚合动态添加的点坐标--> <bm-marker-clusterer :averagecenter="true"> <bm-marker v-for="marker of markers" :key="marker.code" :position="{lng: marker.lng, lat: marker.lat}" @click="lookdetail(marker)"></bm-marker> </bm-marker-clusterer> <!--信息窗体--> <bm-info-window :position="{lng: infowindow.info.lng, lat: infowindow.info.lat}" :title="infowindow.info.name" :show="infowindow.show" @close="infowindowclose" @open="infowindowopen"> <p><span class="left">单位面积能耗:</span><span class="right">{{infowindow.info.areaenergy}}kwh/㎡</span></p> <p><span class="left">建筑面积:</span><span class="right">{{infowindow.info.area}}㎡</span></p> <p><span class="left">电耗:</span><span class="right">{{infowindow.info.energy}}kwh</span></p> <p><span class="left">水耗:</span><span class="right">{{infowindow.info.water}}m³</span></p> <p><span class="left">气耗:</span><span class="right">{{infowindow.info.air}}m³</span></p> </bm-info-window> </baidu-map>
寻找共性,提取公共部分,左侧点击项目和地图中点击项目,效果大体一致,都是弹出一个信息框,提取方法:
//查看详情 lookdetail(data, target){ this.infowindow.show =true; this.infowindow.info=data; this.activename = data.name; //为弹窗口标题添加title this.$nexttick(()=>{ var win=document.queryselector(".bmap_bubble_title"); win.title = this.activename; }) if(target=='left'){ //点击的是左侧列表项,则不需要滚动 this.map.center={lng: data.lng, lat: data.lat}; this.map.zoom = 15; return; } //滚动到指定元素位置 this.$nexttick(()=>{ var obj=document.queryselector(".active"); var scrolltop=obj.offsettop; this.$refs.box.scrolltop=scrolltop-180; }) },
注意看上述代码,用到了this.$nexttick,这是在vue中如果要对dom进行操作,在此方法中可以保证dom节点已加载完成,vue中是以数据驱动的形式来渲染dom的,也就是说数据修改后,dom不会马上改变,它会排队等待修改。再演示某些程序员的骚操作:
settimeout(function () { var win=document.queryselector(".bmap_bubble_title"); win.title = this.activename; },300);
注意看:上述代码使用了settimeout进行延时,来避免数据改变了,但是获取的dom中数据不是最新的情况,虽然大部分情况下可以解决问题,但是存在缺陷。1、你如何确定刚好300毫秒就可以读取到最新的dom数据了。(经验值)2.万一你设置的这个延时时间小了呢?或者数据变更得慢了一些呢?可能你设置了300ms,可实际100ms就已经变更了呢?是不是存在200ms的效率差?等等.....
左侧项目名称超出自动显示省略号
单行文本的溢出显示省略号同学们应该都知道用text-overflow:ellipsis属性来,当然还需要加宽度width属来兼容部分浏览。
实现方法,直接通过css样式控制:
overflow: hidden; text-overflow:ellipsis; white-space: nowrap;
左侧项目列表选中项高亮,其它项正常显示
通过定义一个变量activename ,记录当前选中的项目名称(此处项目名称不可能重复),如果当前项目的名称和activename 的值一致时,添加一个css样式名称active,然后设置这个样式active高亮(设置背景色啊等等)
如下代码所示:
<div class="row" v-for="marker of markers" :key="marker.code" @click="lookdetail(marker,'left')" :class="{active: activename == marker.name}">
点击地图中项目,自动定位到左侧项目选中位置
左侧选中的dom,有一个激活样式active,获取到它的offsettop属性,然后设置列表容器的scrolltop属性即可。
//滚动到指定元素位置 this.$nexttick(()=>{ var obj=document.queryselector(".active"); var scrolltop=obj.offsettop; this.$refs.box.scrolltop=scrolltop-180; })
注意:点击左侧项目,不需要滚动,只有点击地图中的项目才去滚动。
关于项目信息框中标题超出添加省略号,添加title完整提示
通常我们添加了超出部分省略号,一般都会给其添加一个完整的title显示。在现有的百度提供的infowindow组件中是没有封装这个属性的,所以我们通常有两种办法:1.扩展组件源码(耗时)2.直接dom操作。
这里我选择第二种,因为快。浏览器中按f12,可以发现这个标题的html代码部分:
<div class="bmap_bubble_title" style="overflow: hidden; height: auto; line-height: 24px; white-space: nowrap; width: auto;" title="南京高新区管委会行政办公大楼">南京高新区管委会行政办公大楼</div>
我们看到有一个bmap_bubble_title样式,我们可以直接操作这个dom。
代码如下:
//为弹窗口标题添加title this.$nexttick(()=>{ var win=document.queryselector(".bmap_bubble_title"); win.title = this.activename; })
从左侧树点击项目要修改zoom,直接定位到项目信息
百度地图中zoom的详细说明:
地图自动铺满右侧,并且高度全屏且和左侧高度基本一致
但凡这种情况,首先就考虑要计算浏览器的宽高了,当然你也可以使用一些自适应的ui库,我这里直接自己计算的。这个也很简单,获取浏览器可是部分高度,减去一些固定px的长度部分即可。
关于单页应用中的样式问题
我发现一些以前做惯了多页应用开发的人,现在来做单页应用,他会很迷糊,因为在多页应用的世界,每个界面是独立的,每个界面中的样式是互不影响的。而单页应用,通常是一个入口,其它组件(页面)都是按需加载,样式命名相同就冲突了,也就是合并覆盖。避免的方式呢,组件中只自己用的,添加scoped,如果需要覆盖其它的,就在组件容器的最外层添加一个class或者id(这个在项目中唯一命名),然后覆盖的样式都写在这个容器样式之下。
如局部样式:
<style lang="scss" scoped>
全局样式:
<style lang="scss"> .pm-distribution{ .bmap_bubble_title { ......
完整代码:
<template> <div class="pm-distribution"> <h3 class="title">项目分布</h3> <div class="container" id="container" :style="{height:containerheight}"> <div class="left"> <div class="top"> <!--行政区域--> <div class="item fl"> <el-select filterable clearable v-model="districttype" style="width: 140px;margin-left: 5px;"> <el-option v-for="item in districttypeoptions" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </div> <!--项目类型--> <div class="item fl"> <el-select filterable clearable v-model="buildtype" style="width: 140px;"> <el-option v-for="item in buildtypeoptions" :key="item.id" :label="item.name" :value="item.id"> </el-option> </el-select> </div> <div class="item fl" style="margin-left: 10px"> <el-select filterable clearable v-model="buildid" style="width: 190px;"> <el-option v-for="item in buildoptions" :key="item.code" :label="item.name" :value="item.code"> </el-option> </el-select> </div> <!--查询--> <div class="item query-submit fl" @click="search"> <i class="el-icon-search"></i> 查 询 </div> </div> <div class="header">共{{markers.length}}个项目<span style="float: right;margin-right: 15px;">今日能耗</span></div> <div class="list" :style="{height:leftheight}"> <div class="list-context"> <div ref="box" class="list-scroll bmr-y-scroll":style="{height:leftheight}"> <!-- 项目列表--> <div class="listitemdiv"> <div class="row" v-for="marker of markers" :key="marker.code" @click="lookdetail(marker,'left')" :class="{active: activename == marker.name}"> <div class="head-title" v-text="marker.name" :title="marker.name"></div> <div class="row-content"> <span class="item fl"><i class="iconfont nhjc-dianli electricity"></i>{{marker.energy}}kwh</span> <span class="item fl"><i class="iconfont nhjc-shui water"></i>{{marker.water}}m³</span> <span class="item fl"><i class="iconfont nhjc-qi air"></i>{{marker.air}}m³</span> </div> </div> <div style="clear:both;"></div> </div> </div> </div> </div> </div> <div class="right-context" id="right-context"> <baidu-map :style="{width:map.width,height:map.height}" class="map" ak="这里填你的百度秘钥" :zoom="map.zoom" :center="{lng: map.center.lng, lat: map.center.lat}" @ready="handler" :scroll-wheel-zoom="true"> <!--比例尺控件--> <bm-scale anchor="bmap_anchor_top_right"></bm-scale> <!--缩放控件--> <bm-navigation anchor="bmap_anchor_bottom_right" ></bm-navigation> <!--聚合动态添加的点坐标--> <bm-marker-clusterer :averagecenter="true"> <bm-marker v-for="marker of markers" :key="marker.code" :position="{lng: marker.lng, lat: marker.lat}" @click="lookdetail(marker)"></bm-marker> </bm-marker-clusterer> <!--信息窗体--> <bm-info-window :position="{lng: infowindow.info.lng, lat: infowindow.info.lat}" :title="infowindow.info.name" :show="infowindow.show" @close="infowindowclose" @open="infowindowopen"> <p><span class="left">单位面积能耗:</span><span class="right">{{infowindow.info.areaenergy}}kwh/㎡</span></p> <p><span class="left">建筑面积:</span><span class="right">{{infowindow.info.area}}㎡</span></p> <p><span class="left">电耗:</span><span class="right">{{infowindow.info.energy}}kwh</span></p> <p><span class="left">水耗:</span><span class="right">{{infowindow.info.water}}m³</span></p> <p><span class="left">气耗:</span><span class="right">{{infowindow.info.air}}m³</span></p> </bm-info-window> </baidu-map> </div> </div> </div> </template> <script> import { buildtypeoptionsdata, districttypeoptionsdata, buildoptionsdata } from "../views/energyanalysis/energyconsumptionranking/energyconsumptionranking" import { getprogramstype } from "../assets/js/bmr-request"; import globalutil from "../utils/globalutil"; import pmdistributionservice from "../services/pmdistributionservice" //百度地图 import baidumap from 'vue-baidu-map/components/map/map.vue' import bmscale from 'vue-baidu-map/components/controls/scale' import bmnavigation from 'vue-baidu-map/components/controls/navigation' import bmmarkerclusterer from 'vue-baidu-map/components/extra/markerclusterer' import bmmarker from 'vue-baidu-map/components/overlays/marker' import bminfowindow from 'vue-baidu-map/components/overlays/infowindow' export default { name: "pm-distribution", components: { baidumap, bmscale, bmnavigation, bmmarkerclusterer, bmmarker, bminfowindow }, data() { return { districttype: 0,//行政区域 districttypeoptions: globalutil.deepcopy(districttypeoptionsdata),//行政区域选项 buildtype: 0,//项目类型 buildtypeoptions: buildtypeoptionsdata,//项目类型选项 buildid: '',//项目id buildoptions: buildoptionsdata, //项目列表 searchparams:{ regions:0,//区域编号 protype:0,//项目类型 procode:'',//项目编号 }, map:{ center: {lng: 118.802422,lat:32.065631},//'南京市', zoom: 12, width:'1000px', height:'710px' }, markers:[], infowindow: { lng: 0, lat: 0, show: false, info:{ air: 0, area: 12313, areaenergy: 0.64, code: "440300a055", energy: 7922.66, lat: "32.043433", lng: "118.784107", name: "市人民检察院", water: 0 }, }, activename: '', leftheight:'540px', containerheight:'700px' } }, created() { this.districttypeoptions[0].label = '请选择行政区域'; this.getbuildtypes(); this.getselectpro(); }, mounted() { this.leftheight=document.body.clientheight-300+'px'; this.containerheight=document.body.clientheight-150+'px'; }, methods: { //查询 search() { this.searchparams.regions=this.districttype; this.searchparams.protype=this.buildtype; this.searchparams.procode=this.buildid; this.getpropositionmap(); }, infowindowclose (e) { this.infowindow.show = false }, infowindowopen (e) { this.infowindow.show = true }, //查看详情 lookdetail(data, target){ // console.log('data',data) this.infowindow.show =true; this.infowindow.info=data; this.activename = data.name; // let this=this; //为弹窗口标题添加title this.$nexttick(()=>{ var win=document.queryselector(".bmap_bubble_title"); win.title = this.activename; }) if(target=='left'){ //点击的是左侧列表项,则不需要滚动 this.map.center={lng: data.lng, lat: data.lat}; this.map.zoom = 15; return; } //滚动到指定元素位置 this.$nexttick(()=>{ var obj=document.queryselector(".active"); var scrolltop=obj.offsettop; this.$refs.box.scrolltop=scrolltop-180; }) }, //地图初始化 handler ({bmap, map}) { console.log(bmap, map) // this.map.center.lng = 118.802422 // this.map.center.lat = 32.065631 // this.map.zoom = 12; this.map.width= document.getelementbyid("container").clientwidth-330+'px'; this.map.height=document.body.clientheight -160+'px'; this.getpropositionmap(); }, //获取项目列表 getselectpro(){ pmdistributionservice.instance().getselectpro({}).then((res) => { if (res.code === 200) { console.log('res',res) res.data.list.unshift({code:'',name:'请输入项目名称'}) this.buildoptions=res.data.list; } else { this.$message({ message: "获取数据失败", type: "error" }); } }) }, //项目分布地图 getpropositionmap(){ pmdistributionservice.instance().getpropositionmap(this.searchparams).then((res) => { if (res.code === 200) { // console.log('res',res) this.markers=res.data.list; } else { this.$message({ message: "获取数据失败", type: "error" }); } }) }, //获取项目类型 getbuildtypes() { getprogramstype().then(res => { //获取楼建筑下拉框 let temarr = new array(); temarr.push({name: '请选择项目类型', id: 0, type: 10}) res.array.foreach(function (item, index) { temarr.push(item); }) console.log('res.array', res.array) this.buildtypeoptions = temarr; }); }, } } </script> <style lang="scss" scoped> $bgbluecolor: #1881bf; h3.title { box-sizing: border-box; height: 34px; line-height: 34px; font-size: 16px; font-weight: 600; padding: 0 0 0 30px; border: 1px #e5eef3 solid; color: #2274a4; background: #f5f9f9; width: 100%; text-align: left; } .container { margin: 0 auto; min-width: 1366px; padding: 0px; min-height: 710px; .left { width: 320px; float: left; .header { width: 300px; clear: left; height: 34px; line-height: 34px; color: black; background: #f5f9f9; padding-left: 20px; } .top { height: 70px; padding: 8px 5px 12px 5px; } } .right-context { float: left; } .item { margin: 5px; height: 28px; line-height: 28px; .electricity{ color: #ffd670; } .water{ color:#4eb9db; } .air{ color:#f39795; } .iconfont{ font-size: 22px; } } .query-submit { width: 90px; border-radius: 28px; background: $bgbluecolor; color: #fff; text-align: center; cursor: pointer; } .list{ .item{ width: 93px; height: 30px; line-height: 30px; display: block; } .item.fl{ float: left; } .list-context{ border-radius:5px;margin-top:10px;background:white; .list-scroll{ margin-top:10px;overflow-y:auto;min-height:537px;overflow-x: hidden; .row{ float:left;width:320px; cursor: pointer; .head-title{ overflow: hidden;text-overflow: ellipsis;white-space: nowrap;font-size:16px;color:#1781bf;font-weight:400; padding-left: 20px; height: 30px; line-height: 30px; } .row-content{ overflow: hidden;text-overflow: ellipsis;white-space: nowrap;padding-bottom:10px;font-size:12px;color:rgb(128, 128, 128); border-bottom: 1px solid #eee; padding-left: 10px; } } .row.active{ background-color: #cfddf3; } } } } .map{ min-width: 800px; width:1000px;height:710px;float:left;background:white;border-radius:5px;margin-left:10px; .right{ text-align: left; } .left{ width: 100px; } } } </style> <style lang="scss"> .pm-distribution{ .bmap_bubble_title { color: #2db7f5 !important; font-size: 16px; font-weight: 400; margin-bottom: 8px; overflow: hidden; text-overflow:ellipsis; white-space: nowrap; width: 220px !important; } } </style>
说明:本界面所有功能是一天之内赶出来的,所以代码就凑合吧。其它的一些封装的组件代码没有贴出来,因为本篇重点是介绍地图的使用。我见过一些所谓前端工程师的代码,也就那样,有些还时不时的弄一些骚操作,请允许我自恋一下,o(∩_∩)o哈哈~。虽然我不是专业的前端,但是不管是写后端代码还是前端代码,多少要有点追求,有时候为了赶进度,快得了一时,其实误了后面更多时间,很多项目都是被怎么快怎么来玩死的,每天想着以后再重构,以后再重构,到后面就不了了之了..........
明天是三八节,今天给公司每一位女同胞写贺卡,已经快忘记怎么写字了,中学时代,我可是专业情书枪手呢?o(∩_∩)o哈哈~
上一篇: experss 做小型服务器出现跨域问题
下一篇: 课时9.HTML发展史(了解)