vue+echarts+datav大屏資料展示及實現中國地圖省市縣下鑽
隨著前端技術的飛速發展,大資料時代的來臨,我們在開發專案時越來越多的客戶會要求我們做一個數據展示的大屏,可以直觀的展示使用者想要的資料,同時炫酷的介面也會深受客戶的喜歡。
大屏展示其實就是一堆的圖表能夠讓人一目瞭然地看到該系統下的一些基本資料資訊的彙總,也會有一些實時資料重新整理,資訊預警之類的。筆者在之前也做過一些大屏類的資料展示,但是由於都是一些圖表類的,覺得沒什麼可說的,加之資料也都牽扯到公司,所以沒有沉澱下來什麼。
最近有朋友要做一個大屏,問了自己一個問題,自己也剛好做了一個簡單的大屏資料展示,趁此機會做一個小總結。
先看一下效果:
由於資料牽扯到公司內部資訊,所以將一些複雜的切換邏輯都去掉類,但保留了一些資料間但相互聯動。
專案採用的是Vue+Echanrts+datav寫的,結構目錄如下:
由於只是一個單一頁面,資料處理也不是複雜,沒有涉及到router和vuex,從結構目錄上看就是一個很典型的vue-cli專案,在之前我也講過關於vue-cli專案的一些操作和目錄結構解釋,這裡就不做多做說明了,在文章最後會提供該專案的原始碼地址庫。
大屏主要的炫酷效果本人引用的是datav元件,地址:http://datav.jiaminghi.com/,這簡直就是資料視覺化的一款神器,神奇之處我就不多說了,大家可以自己去它的網站上自行體會。它也提供瞭如何在vue 中使用該元件。
datav可以全域性注入,也可以按需注入,本人省事就直接在main.js中進行了全域性注入。
所有的頁面程式碼都放在了views檔案目錄下:
其中index.vue檔案為主檔案入口,其他都是其子元件,元件名稱以方位的形式命名,如centerForm.vue就是中間的表單控制元件。
本專案引入了中國地圖並實現省市縣下鑽,最初採用的是阿里旗下的高德地圖,後來因為種種原因改為了百度提供的Echarts來實現,但兩種使用方法都保留了下來,大家可以根據自己的需求進行選擇。
其中Echarts中國地圖的程式碼如下:
1 <template> 2 <div id="china_map_box"> 3 <el-button type="primary" size="mini" class="back" @click="back" v-if="deepTree.length > 1">返回</el-button> 4 <div class="echarts"> 5 <div id="map"></div> 6 </div> 7 </div> 8 </template> 9 10 <script> 11 12 import {getChinaJson, getProvinceJSON, getCityJSON} from "../api/get-json"; 13 import {cityProvincesMap} from '../config/cityProvincesMap' 14 import {mapOption} from '../config/mapOption' 15 16 17 export default { 18 name: "china", 19 components: {}, 20 data() { 21 return { 22 chart: null, // 例項化echarts 23 provincesMap: cityProvincesMap.provincesMap, // 省拼音,用於查詢對應json 24 provincesCode: cityProvincesMap.provincesCode, // 市行政區劃,用於查詢對應json 25 areaMap: cityProvincesMap.areaMap, // 省行政區劃,用於資料的查詢,按行政區劃查資料 26 special: ["北京市", "天津市", "上海市", "重慶市", "香港", "澳門"],//直轄市和特別行政區-只有二級地圖,沒有三級地圖 27 mapData: [], // 當前地圖上的地區 28 option: {...mapOption.basicOption}, // map的相關配置 29 deepTree: [],// 點選地圖時push,點返回時pop 30 areaName: '中國', // 當前地名 31 areaCode: '000000', // 當前行政區劃 32 areaLevel: 'country', // 當前級別 33 } 34 }, 35 mounted() { 36 this.$nextTick(() => { 37 this.initEcharts(); 38 this.chart.on('click', this.echartsMapClick); 39 }); 40 }, 41 methods: { 42 // 初次載入繪製地圖 43 initEcharts() { 44 //地圖容器 45 this.chart = this.$echarts.init(document.getElementById('map')); 46 if (this.areaCode === '000000') { 47 this.requestGetChinaJson(); 48 } else { 49 this.requestGetProvinceJSON({areaName: this.areaName, areaCode: this.areaCode}) 50 } 51 }, 52 // 地圖點選 53 echartsMapClick(params) { 54 // console.log(params); 55 this.areaName = params.areaName; 56 if (params.name in this.provincesMap) { 57 this.areaCode = params.data.areaCode; 58 this.areaLevel = params.data.areaLevel; 59 //如果點選的是34個省、市、自治區,繪製選中地區的二級地圖 60 this.requestGetProvinceJSON(params.data); 61 } else if (params.seriesName in this.provincesMap) { 62 //如果是【直轄市/特別行政區】只有二級下鑽 63 if (this.special.indexOf(params.seriesName) >= 0) { 64 return; 65 } else { 66 this.areaCode = this.areaMap[params.name]; 67 this.areaLevel = params.data.areaLevel; 68 //顯示縣級地圖 69 this.requestGetCityJSON(params.data) 70 } 71 } else { 72 return; 73 } 74 this.$emit('map-change', params.data); 75 }, 76 //繪製全國地圖 77 requestGetChinaJson() { 78 getChinaJson().then(res => { 79 let arr = []; 80 for (let i = 0; i < res.features.length; i++) { 81 let obj = { 82 name: res.features[i].properties.name, 83 areaName: res.features[i].properties.name, 84 areaCode: res.features[i].id, 85 areaLevel: 'province', 86 value: Math.round(Math.random()), 87 }; 88 arr.push(obj) 89 } 90 this.mapData = arr; 91 this.deepTree.push({ 92 mapData: arr, 93 params: {name: 'china', areaName: 'china', areaLevel: 'country', areaCode: '000000'} 94 }); 95 //註冊地圖 96 this.$echarts.registerMap('china', res); 97 //繪製地圖 98 this.renderMap('china', arr); 99 }); 100 }, 101 // 載入省級地圖 102 requestGetProvinceJSON(params) { 103 getProvinceJSON(params.areaCode).then(res => { 104 this.$echarts.registerMap(params.areaName, res); 105 let arr = []; 106 for (let i = 0; i < res.features.length; i++) { 107 let obj = { 108 name: res.features[i].properties.name, 109 areaName: res.features[i].properties.name, 110 areaCode: res.features[i].id, 111 areaLevel: 'city', 112 value: Math.round(Math.random()), 113 }; 114 arr.push(obj) 115 } 116 this.mapData = arr; 117 this.deepTree.push({ 118 mapData: arr, 119 params: params, 120 }); 121 this.renderMap(params.areaName, arr); 122 }); 123 }, 124 // 載入市級地圖 125 requestGetCityJSON(params) { 126 this.areaLevel = params.areaLevel; 127 getCityJSON(params.areaCode).then(res => { 128 this.$echarts.registerMap(params.areaName, res); 129 let arr = []; 130 for (let i = 0; i < res.features.length; i++) { 131 let obj = { 132 name: res.features[i].properties.name, 133 areaName: res.features[i].properties.areaName, 134 areaCode: res.features[i].id, 135 areaLevel: 'districts', 136 value: Math.round(Math.random()), 137 }; 138 arr.push(obj) 139 } 140 this.mapData = arr; 141 this.deepTree.push({mapData: arr, params: params}); 142 this.renderMap(params.areaName, arr); 143 }) 144 }, 145 renderMap(map, data) { 146 this.option.series = [ 147 { 148 name: map, 149 mapType: map, 150 ...mapOption.seriesOption, 151 data: data 152 } 153 ]; 154 //渲染地圖 155 this.chart.setOption(this.option); 156 }, 157 // 返回 158 back() { 159 // console.log(this.deepTree); 160 if (this.deepTree.length > 1) { 161 this.deepTree.pop(); 162 let areaName = this.deepTree[this.deepTree.length - 1].params.areaName; 163 let mapData = this.deepTree[this.deepTree.length - 1].mapData; 164 this.$emit('back-change', this.deepTree[this.deepTree.length - 1].params); 165 this.renderMap(areaName, mapData); 166 } 167 } 168 } 169 } 170 171 </script> 172 173 <style lang="scss" scoped> 174 #china_map_box { 175 display: flex; 176 width: 100%; 177 height: 100%; 178 position: relative; 179 .echarts { 180 width: 0; 181 flex: 1; 182 background-size: 100% 100%; 183 #map { 184 height: 100%; 185 } 186 } 187 .back { 188 position: absolute; 189 top: .8rem; 190 right: .5rem; 191 z-index: 999; 192 padding-left: .12rem; 193 padding-right: .12rem; 194 195 } 196 } 197 198 </style>
在呼叫省市地圖時本人採用的是將地圖資訊的json存放在了本地,這是由於本人的專案中很多地市的行政區劃很多需要變動,這也是放棄高德地圖的原因之一。json檔案放在了public檔案目錄下,如下圖:
裡面有一些自己沒用到的json資料本人進行了刪除,關於中國詳細的json資料大家可以去https://datav.aliyun.com/tools/atlas/#&lat=30.332329214580188&lng=106.72278672066881&zoom=3.5下載,內容由高德開放平臺提供。
高德地圖chinaGaode.vue程式碼如下:
1 <template> 2 <div id="china_map_box"> 3 <el-button type="primary" size="mini" class="back" @click="back">返回</el-button> 4 <div class="map" > 5 <map-range @change="search"></map-range> 6 </div> 7 <div class="echarts"> 8 <div id="map"></div> 9 </div> 10 </div> 11 </template> 12 13 <script> 14 import mapRange from "./mapRange"; 15 16 export default { 17 name: "chinaGaode", 18 components: { 19 mapRange 20 }, 21 data() { 22 return { 23 provinceSelect: null, 24 citySelect: null, 25 districtSelect: null, 26 areaName: '中國', 27 geoJsonData: '', 28 echartsMap: null, 29 map: null, 30 district: null, 31 polygons: [], 32 areaCode: 100000, 33 opts: {}, 34 areaData: {}, 35 mapData: [], 36 deepTree:[], 37 } 38 }, 39 mounted() { 40 this.provinceSelect = document.getElementById('province'); 41 this.citySelect = document.getElementById('city'); 42 this.districtSelect = document.getElementById('district'); 43 this.deepTree = [{mapData: this.mapData,code: 100000}]; 44 this.echartsMap = this.$echarts.init(document.getElementById('map')); 45 this.echartsMap.on('click', this.echartsMapClick); 46 this.map = new AMap.Map('container', { 47 resizeEnable: true, 48 center: [116.30946, 39.937629], 49 zoom: 3 50 }); 51 this.opts = { 52 subdistrict: 1, //返回下一級行政區 53 showbiz: false //最後一級返回街道資訊 54 }; 55 this.district = new AMap.DistrictSearch(this.opts);//注意:需要使用外掛同步下發功能才能這樣直接使用 56 this.district.search('中國', (status, result) => { 57 if (status == 'complete') { 58 this.getData(result.districtList[0], '', 100000); 59 } 60 }); 61 }, 62 methods: { 63 //地圖點選事件 64 echartsMapClick(params) { 65 if (params.data.level == 'street') return; 66 //清除地圖上所有覆蓋物 67 for (var i = 0, l = this.polygons.length; i < l; i++) { 68 this.polygons[i].setMap(null); 69 } 70 this.areaName = params.data.name; 71 this.areaCode = params.data.areaCode; 72 this.district.setLevel(params.data.level); //行政區級別 73 this.district.setExtensions('all'); 74 //行政區查詢 75 //按照adcode進行查詢可以保證資料返回的唯一性 76 this.district.search(this.areaCode, (status, result) => { 77 if (status === 'complete') { 78 this.deepTree.push({mapData: this.mapData,code: params.data.areaCode}); 79 this.getData(result.districtList[0], params.data.level, this.areaCode); 80 } 81 }); 82 this.$emit('map-change', params.data); 83 }, 84 loadMapData(areaCode) { 85 AMapUI.loadUI(['geo/DistrictExplorer'], DistrictExplorer => { 86 //建立一個例項 87 var districtExplorer = window.districtExplorer = new DistrictExplorer({ 88 eventSupport: true, //開啟事件支援 89 map: this.map 90 }); 91 districtExplorer.loadAreaNode(areaCode, (error, areaNode) => { 92 if (error) { 93 console.error(error); 94 return; 95 } 96 let mapJson = {}; 97 mapJson.type = "FeatureCollection"; 98 mapJson.features = areaNode.getSubFeatures(); 99 this.loadMap(this.areaName, mapJson); 100 this.geoJsonData = mapJson; 101 }); 102 }); 103 }, 104 loadMap(mapName, data) { 105 if (data) { 106 this.$echarts.registerMap(mapName, data); 107 var option = { 108 109 visualMap: { 110 type: 'piecewise', 111 pieces: [ 112 {max: 1, label: '稽核完成', color: '#2c9a42'}, 113 {min: -1, max: 1, label: '未完成', color: '#d08a00'}, 114 // {min: 60, label: '危險', color: '#c23c33'}, 115 ], 116 color: '#fff', 117 textStyle: { 118 color: '#fff', 119 }, 120 visibility: 'off', 121 top:50, 122 left:30, 123 }, 124 series: [{ 125 name: '資料名稱', 126 type: 'map', 127 roam: false, 128 mapType: mapName, 129 selectedMode: 'single', 130 showLegendSymbol: false, 131 visibility: 'off', 132 itemStyle: { 133 normal: { 134 color: '#ccc', 135 areaColor: '#fff', 136 borderColor: '#fff', 137 borderWidth: 0.5, 138 label: { 139 show: true, 140 textStyle: { 141 color: "rgb(249, 249, 249)", 142 fontSize: '1rem' 143 } 144 } 145 }, 146 emphasis: { 147 areaColor: false, 148 borderColor: '#fff', 149 areaStyle: { 150 color: '#fff' 151 }, 152 label: { 153 show: true, 154 textStyle: { 155 color: "rgb(249, 249, 249)" 156 } 157 } 158 } 159 }, 160 data: this.mapData, 161 }] 162 }; 163 this.echartsMap.setOption(option); 164 } 165 }, 166 getData(data, level, adcode) { 167 var bounds = data.boundaries; 168 if (bounds) { 169 for (var i = 0, l = bounds.length; i < l; i++) { 170 var polygon = new AMap.Polygon({ 171 map: this.map, 172 strokeWeight: 1, 173 strokeColor: '#0091ea', 174 fillColor: '#80d8ff', 175 fillOpacity: 0.2, 176 path: bounds[i] 177 }); 178 this.polygons.push(polygon); 179 } 180 this.map.setFitView();//地圖自適應 181 } 182 183 //清空下一級別的下拉列表 184 if (level === 'province') { 185 this.citySelect.innerHTML = ''; 186 this.districtSelect.innerHTML = ''; 187 } else if (level === 'city') { 188 this.districtSelect.innerHTML = ''; 189 } 190 var subList = data.districtList; 191 if (subList) { 192 let optionName = '--請選擇--'; 193 var contentSub = new Option(optionName); 194 var curlevel = subList[0].level; 195 if (curlevel === 'street') { 196 let mapJsonList = this.geoJsonData.features; 197 let mapJson = {}; 198 for (let i in mapJsonList) { 199 if (mapJsonList[i].properties.name == this.areaName) { 200 mapJson.type = "FeatureCollection"; 201 mapJson.features = [].concat(mapJsonList[i]); 202 } 203 } 204 this.mapData = []; 205 this.mapData.push({name: this.areaName, value: 0, level: curlevel}); 206 this.loadMap(this.areaName, mapJson); 207 return; 208 } 209 210 var curList = document.querySelector('#' + curlevel); 211 curList.add(contentSub); 212 this.mapData = []; 213 for (var i = 0, l = subList.length; i < l; i++) { 214 var name = subList[i].name; 215 var areaCode = subList[i].adcode; 216 this.mapData.push({ 217 name: name, 218 value: Math.round(Math.random()), 219 areaCode: areaCode, 220 level: curlevel 221 }); 222 var levelSub = subList[i].level; 223 contentSub = new Option(name); 224 contentSub.setAttribute("value", levelSub); 225 contentSub.center = subList[i].center; 226 contentSub.adcode = subList[i].adcode; 227 curList.add(contentSub); 228 } 229 this.loadMapData(adcode); 230 this.areaData[curlevel] = curList; 231 } 232 233 }, 234 search(area) { 235 let obj = this.areaData[area]; 236 //清除地圖上所有覆蓋物 237 for (var i = 0, l = this.polygons.length; i < l; i++) { 238 this.polygons[i].setMap(null); 239 } 240 var option = obj[obj.options.selectedIndex]; 241 242 var keyword = option.text; //關鍵字 243 var adcode = option.adcode; 244 this.areaName = keyword; 245 this.areaCode = adcode; 246 this.district.setLevel(option.value); //行政區級別 247 this.district.setExtensions('all'); 248 //行政區查詢 249 //按照adcode進行查詢可以保證資料返回的唯一性 250 this.district.search(adcode, (status, result) => { 251 if (status === 'complete') { 252 this.deepTree.push({mapData: this.mapData,code:adcode}); 253 this.getData(result.districtList[0], obj.id, adcode); 254 } 255 }); 256 var params = { 257 areaCode: adcode, 258 level: area, 259 name: keyword, 260 value: '', 261 }; 262 this.$emit('map-change', params); 263 }, 264 back() { 265 // console.log(this.deepTree) 266 if (this.deepTree.length > 1) { 267 this.mapData = this.deepTree[this.deepTree.length - 1].mapData; 268 this.deepTree.pop(); 269 // console.log(this.deepTree[this.deepTree.length - 1], 'back'); 270 this.loadMapData(this.deepTree[this.deepTree.length - 1].code) 271 } 272 } 273 } 274 } 275 </script> 276 277 <style lang="scss" scoped> 278 #china_map_box { 279 display: flex; 280 width: 100%; 281 height: 100%; 282 position: relative; 283 .echarts { 284 width: 0; 285 flex: 1; 286 background-size: 100% 100%; 287 #map { 288 height: 100%; 289 } 290 } 291 .back { 292 position: absolute; 293 top: .8rem; 294 right: .5rem; 295 z-index: 999; 296 } 297 298 } 299 300 </style>
在網上有很多下夥伴都在查詢如何使用中國地圖並實現下鑽,在實際使用地圖時其實並不難,以上是本人提供的一些解決方案和程式碼提供。
由於程式碼是從本人的一個專案中剝離而來,程式碼的質量可能欠佳,有些邏輯處理和傅子元件間的資料聯動也都有所減少,但並不影響該專案demo的使用,如果有需要大家可以去以下地址下載原始碼學習,也歡迎star。
gitee原始碼地址:https://gitee.com/vijtor/vue-map-datav