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下载,内容由高德开放平台提供。
全国行政区划编码地址 http://www.mca.gov.cn/article/sj/xzqh/2020/,内容由国家统计局提供
高德地图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的使用,如果有需要大家可以去以下地址下载源码学习。
gitee源码地址:https://gitee.com/vijtor/vue-map-datav
另一优化版本地址: https://www.cnblogs.com/weijiutao/p/15990813.html