作為一名開發人員,每次接到開發任務,我們首先應該先分析需求,然後再思考技術方案和解決方案。三思而後行,這是一個好的習慣。
需求:本項目是采用vue元件化開發的單頁應用項目,現需要在項目中引入百度的地圖展示功能,用于展示所有項目的分布情況。搜尋功能(省略,不是這裡介紹的内容).......
互動:選中左側的項目,選中項高亮,自動定位到右側地圖中項目所在位置,并彈出項目的基本資訊。點選右側的項目,自動高亮顯示左側的項目,并滾動到項目所在位置。地圖支援聚合和縮放。
項目運作效果如下圖所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SOycjMwIjN4kTMtEDNwgDM4MTMxcDMzATOxAjMtETN4MTM08CXzATOxAjMvwVM1gzMxQzLcd2bsJ2Lc12bj5ycn9Gbi52YugTMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
在vue中引入百度地圖
百度開發者平台已經封裝了基于vue的地圖元件,詳細使用,請參考官網:
https://dafrok.github.io/vue-baidu-map/#/zh/start/installation
網上有一些是直接在index.html頁面全部引用的,本人強烈反對此種使用方式,因為我們項目是元件化的單頁應用,強行引入多頁應用的開發方式,會破壞整個項目的架構,嚴重影響性能。有些甚至還在vue單頁應用中引入jquery,感覺這都是一些反人類的騷操作,不到萬不得已,不建議使用。
使用方式
我這裡隻示範單頁應用的開發方式。
1.安裝元件
$ npm install vue-baidu-map --save
2.注冊元件
元件的注冊可以分為全局注冊和局部注冊,我這裡采用的是局部注冊。因為整個項目中僅此一個界面使用。引入官方的說明:
如果有按需引入元件的需要,可以選擇局部注冊百度地圖元件,這将減少工程打包後的容量尺寸。局部注冊的元件必須聲明
BaiduMap
屬性。 所有的獨立元件均存放在
ak
檔案夾下,按需引用即可。 由于未編譯的 ES 子產品不能在大多數浏覽器中直接運作,如果引入元件時發生運作時錯誤,請檢查 webpack 的 loader 配置,确認
vue-baidu-map/components
和
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>
View Code
說明:本界面所有功能是一天之内趕出來的,是以代碼就湊合吧。其它的一些封裝的元件代碼沒有貼出來,因為本篇重點是介紹地圖的使用。我見過一些所謂前端工程師的代碼,也就那樣,有些還時不時的弄一些騷操作,請允許我自戀一下,O(∩_∩)O哈哈~。雖然我不是專業的前端,但是不管是寫後端代碼還是前端代碼,多少要有點追求,有時候為了趕進度,快得了一時,其實誤了後面更多時間,很多項目都是被怎麼快怎麼來玩死的,每天想着以後再重構,以後再重構,到後面就不了了之了..........
明天是三八節,今天給公司每一位女同胞寫賀卡,已經快忘記怎麼寫字了,中學時代,我可是專業情書槍手呢?O(∩_∩)O哈哈~
部落格位址: | http://www.cnblogs.com/jiekzou/ |
部落格版權: | 本文以學習、研究和分享為主,歡迎轉載,但必須在文章頁面明顯位置給出原文連接配接。 如果文中有不妥或者錯誤的地方還望高手的你指出,以免誤人子弟。如果覺得本文對你有所幫助不如【推薦】一下!如果你有更好的建議,不如留言一起讨論,共同進步! 再次感謝您耐心的讀完本篇文章。 |
其它: | .net-QQ群4:612347965 java-QQ群:805741535 H5-QQ群:773766020 我的拙作《ASP.NET MVC企業級實戰》《H5+移動應用實戰開發》 《Vue.js 2.x實踐指南》 《JavaScript實用教程 》 《Node+MongoDB+React 項目實戰開發》 已經出版,希望大家多多支援! |