1. 程式人生 > 實用技巧 >vue+echarts+datav大屏資料展示及實現中國地圖省市縣下鑽

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