作为一名开发人员, 每次接到开发任务, 我们首先应该都是先分析需求, 然后再思考技术方案和解决方案. 三思而后行, 这是一个好的习惯.
需求: 本项目是采用 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="cYp57vhSo3DecscVK1s24TOKtt3G4eIG" :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-Windows :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-Windows>
- </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 的详细说明:
完整代码:
<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="cYp57vhSo3DecscVK1s24TOKtt3G4eIG" :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-Windows :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-Windows> </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; } } .pagination { margin-top: 0px; text-align: left; font-size: 12px; } } </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> View Code
说明: 一天之内赶出来的, 其它的一些封装的组件代码没有贴出来, 因为本篇重点是介绍地图的使用, 所以代码规范上面只能说一般, 不过我觉得也比我见过的一些所谓的前端写的代码要强些, 请允许我自恋一下. 虽然我不是专业的前端, 但是不管是写后端代码还是前端代码, 多少要有点追求, 有时候为了赶进度, 快得了一时, 其实误了后面更多时间, 很多项目都是被怎么快怎么来玩死的, 每天想着以后再重构, 以后再重构, 到后面就不了了之了..........
来源: https://www.cnblogs.com/jiekzou/p/10485604.html