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

