前言
在最近遇到一个新的项目需求,在我们的首页区一个模块展示一个中国地图,并特别标注指定的地区进行轮播展示~
现在给大家分享一下,我的搬砖历程...
此次需求使用 uni-app H5端实现
Part.1 效果展示
Part.2 代码构思
首先接到这个需求,我的脑海中就出现了 Echarts,这个大家肯定都很熟悉,因为这个我们做图表方面的需求应用得很多。
好的,废话不多说,我们开始整理思路。
1. 确认引用 Echarts 库
2. 使用 Echarts 实现中国地图
3. 高亮展示指定的区域
4. 高亮区域数据实现轮播展示
Part.3 代码编写
1. 引入 Echarts 库,这个不用多说,引入的方式有很多,我这里是通过 CDN 引入的。
特别需要强调的是版本问题,这个是我遇到的一个很大的问题,版本不同导致API的不同,从而导致在构思的 第3步(高亮展示指定的区域)和 第四步(高亮区域数据实现轮播展示)无法实现
我这里使用的是 @4.1.0
我的引用:
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/4.1.0/echarts.min.js"></script>
2. 实现中国地图,这里需要先引用两个文件后续会用到
chinaData.json 地址:https://gitee.com/langxiyu/china-data.json/blob/master/chinaData.json
china.js 地址: https://gitee.com/langxiyu/china-data.json/blob/master/china.js
第3步,第4步将在源码中展示
Part.4 源码
<template> <view class="site-content"> <image class="img" src="/static/index/map-bg.png" mode="aspectFill"></image> <view class="content-header"> <view class="cur-info"> <image class="info-bg" src="/static/index/local-info-bg.png"></image> <view class="info"> <view class="left"> <text class="city-name">{{ curSiteInfo.venueName }}馆</text> <text class="city-desc">{{ curSiteInfo.venueText }}</text> </view> <view class="right"> <image class="logo" :src="curSiteInfo.venueHeadUrl"></image> </view> </view> </view> </view> <view class="content-map"> <localMap @getCurSiteInfo="getCurSiteInfo"></localMap> </view> </view> </template> <script> import localMap from './component/localMap/localMap.vue' export default { components: { localMap }, data() { return { // 当前信息 curSiteInfo: {} } }, methods: { // 展示当前信息 getCurSiteInfo(e) { this.curSiteInfo = e } } } </script> <style lang="scss" scoped> .site-content { 100%; position: relative; .img { 100%; height: 706rpx; } .content-header { 690rpx; position: absolute; top: -2rpx; left: 0; text-align: center; z-index: 1; .cur-info { min- 276rpx; height: 136rpx; padding-top: 32rpx; position: relative; display: inline-block; .info-bg { 100%; height: 136rpx; position: absolute; top: 0; left: 0; } .info { padding-left: 30rpx; padding-right: 30rpx; position: relative; display: flex; align-items: center; z-index: 1; .left { display: flex; flex-direction: column; text-align: left; .city-name { font-size: 44rpx; font-family: YouSheBiaoTiHei; color: #8A4424; line-height: 58rpx; background: linear-gradient(180deg, #9C4A23 0%, #713F29 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .city-desc { font-size: 26rpx; font-weight: 400; color: #8A4424; line-height: 40rpx; } } .right { 80rpx; height: 80rpx; margin-left: 20rpx; .logo { 100%; height: 100%; border-radius: 10rpx; } } } } } .content-map { 690rpx; position: absolute; top: 80rpx; left: 0; } .content-cur-site { min- 262rpx; padding: 18rpx 16rpx; background: linear-gradient(125deg, #FFFFFF 0%, #FFFFFF 100%); box-shadow: 0 0 18rpx 0 rgba(237, 151, 0, 0.6) inset; border-radius: 20rpx; display: flex; justify-content: space-between; position: absolute; z-index: 1; transition: all 0.5s; .cur-site-left { display: flex; flex-direction: column; .left-title { margin-top: 4rpx; font-family: YouSheBiaoTiHei; font-size: 34rpx; white-space: nowrap; color: rgba(179, 135, 60, 1); line-height: 36rpx; } .left-desc { max- 140rpx; margin-top: 4rpx; font-size: 24rpx; font-weight: 400; color: #CBA86A; line-height: 36rpx; } } .cur-site-right { 80rpx; height: 80rpx; .right-img { 100%; height: 100%; } } } .content-site-position { position: absolute; .site-label { 24rpx; height: 36rpx; border-radius: 50% 50% 50% 50% / 30% 30% 70% 70%; background: linear-gradient(39deg, #C9D94F 0%, #899922 100%); position: relative; transition: all 2s; .label-white { 12rpx; height: 12rpx; position: absolute; top: 6rpx; right: 0; left: 0; margin: auto; border-radius: 50%; background-color: #FFF; } .header-sign{ 12rpx; height: 12rpx; position: absolute; top: 26rpx; left: 6rpx; border-radius: 50% } &.active { transform: scale(1.5); background: linear-gradient(39deg, rgba(250, 217, 97, 1) 0%, rgba(247, 107, 28, 1) 100%); .header-sign { animation: waterWave 1s ease-out; animation-iteration-count: infinite; } } @keyframes waterWave { form { transform: scale(1); background: rgba(248, 140, 49, 0.34); } to { transform: scale(2); background: rgba(248, 140, 49, 0.24); } 50% { transform: scale(3); background: rgba(248, 140, 49, 0.14); } 75% { transform: scale(4); background: rgba(248, 140, 49, 0.04); } 100% { transform: scale(5); background: rgba(248, 140, 49, 0); } } } } } </style>
1 <template> 2 <view class="qiun-charts"> 3 <view id="mapChart" ref="mapChart"></view> 4 5 <!-- 九段线图片 --> 6 <image class="map-section-9" 7 src="/static/index/section-9.png"></image> 8 9 <!-- 推荐场馆 --> 10 <view class="site-recommend"> 11 <view v-for="(item, index) in rankingList" 12 :key="index" 13 class="item"> 14 <text class="num">No.{{ index + 1 }}</text> 15 <text class="name">{{ item.venueName }}</text> 16 </view> 17 18 <!-- 全国地方馆入口 --> 19 <view class="item"> 20 <text class="entry">全国</text> 21 <text class="entry">地方馆</text> 22 </view> 23 </view> 24 </view> 25 </template> 26 27 <script> 28 import chinaData from '@/common/chinaData.json' 29 import defaultData from './js/china.js' 30 export default { 31 data() { 32 return { 33 mapChart: null, 34 35 // 默认全国数据 36 defaultData: defaultData, 37 38 // 已经开放地区 39 openAreasArr: [], 40 // 当前循环数据索引 41 curIndex: 0, 42 43 // 地方馆排名 44 rankingList: [], 45 46 // 场馆更换定时器 47 timer: null, 48 // 场馆重新启动定时器 49 timeOut: null 50 } 51 }, 52 mounted() { 53 // 获取地方馆地图推荐列表地方馆 54 this.getVenueOfFirstPage() 55 }, 56 methods: { 57 // 获取地图推荐列表 58 getVenueOfFirstPage() { 59 // 接口请求数据 - 示例 60 // 逻辑可自行修改 61 this.$api.getVenueOfFirstPage(null, res => { 62 if (res.code == 10000) { 63 let data = res.data; 64 let defaultDataLen = this.defaultData.length; 65 let i = 0; 66 67 if (data && data.length != 0) { 68 data.map(item => { 69 // 去除 ‘馆’ 70 item.venueName = item.venueName.split('馆')[0]; 71 72 // 判断是否存在 logo 73 if (item.venueHeadUrl == null || item.venueHeadUrl == '') { 74 item.venueHeadUrl = '/static/logo-two.png' 75 } else { 76 item.venueHeadUrl = this.$util.formatImg(item.venueHeadUrl) 77 }; 78 79 for (i = 0; i < defaultDataLen; i++) { 80 if (this.defaultData[i].name.indexOf(item.venueName) > -1) { 81 // 默认展示标识 82 this.defaultData[i].value = 1; 83 // 增加定位字段 - 用于 markPoint 84 this.defaultData[i].coord = chinaData.features[i].properties.centroid; 85 // 合并对象 86 Object.assign(this.defaultData[i], item) 87 break 88 } 89 } 90 }) 91 }; 92 93 // 初始化配置 94 this.initOptions(); 95 96 // 添加监听点击 97 this.addMouseover() 98 }; 99 100 // 获取排名列表 101 this.getVenueHeatOfFirstPage() 102 }) 103 }, 104 105 // 获取排名列表 106 getVenueHeatOfFirstPage() { 107 // 接口请求数据 - 示例 108 // 逻辑可自行修改 109 this.$api.getVenueHeatOfFirstPage(null, res => { 110 if (res.code == 10000) { 111 let data = res.data; 112 113 if (data != '' && data != null) { 114 this.rankingList = data.splice(0, 5); 115 } 116 } 117 }) 118 }, 119 120 // 初始化配置 121 initOptions() { 122 echarts.registerMap('china', chinaData); 123 this.mapChart = echarts.init(document.getElementById('mapChart')); 124 125 this.openAreasArr = []; 126 this.defaultData.map((item, index) => { 127 // 已经开馆地区 128 if (item.value > 0) { 129 this.openAreasArr.push(item) 130 } 131 }); 132 133 // 更新配置 134 this.updateOption(this.openAreasArr[this.curIndex], this.openAreasArr[this.curIndex].name); 135 136 // 开始循环展示地方馆信息 137 this.circulateSiteInfo() 138 }, 139 140 // 更新配置 141 updateOption(markPointData, name) { 142 // 初始化配置 143 let option = { 144 tooltip: { 145 triggerOn: 'click', 146 confine: true, 147 extraCssText: 'z-index: 2', 148 formatter: params => { 149 let data = params.data; 150 151 if (data.value == 0) { 152 return 153 }; 154 155 let html = `<view style="display: flex;pointer-events: all;"> 156 <view style="display: flex;flex-direction: column;"> 157 <text style="margin-top: 2px; 158 font-family: YouSheBiaoTiHei; 159 font-size: 17px; 160 white-space: nowrap; 161 color: #FFFFFF; 162 line-height: 18px;">${data.venueName}馆</text> 163 <text style="max- 75px; 164 height: 18px; 165 margin-top: 2px; 166 font-size: 12px; 167 font-weight: 400; 168 color: #FFFFFF; 169 line-height: 18px;">${data.venueText}</text> 170 </view> 171 <view style=" 40px;height: 40px;margin-left:10px"> 172 <image style=" 40px;height: 40px;" 173 src="${data.venueHeadUrl}"></image> 174 </view> 175 </view>` 176 177 return html; 178 }, 179 backgroundColor: "rgba(133, 68, 39, 0.8)",//提示标签背景颜色 180 textStyle: { 181 color: "#fff", 182 } 183 }, 184 geo: { 185 map: 'china', 186 zoom: 1.2, 187 label: { 188 normal: { 189 show: true, 190 textStyle: { 191 color: "#D49655", 192 fontSize: 5 193 } 194 }, 195 emphasis: { 196 show: false, // 197 } 198 }, 199 itemStyle: { 200 normal: { 201 borderWidth: 1, 202 borderColor: '#D49655', 203 } 204 }, 205 regions: [{ 206 name: '南海诸岛', 207 value: 0, 208 itemStyle: { 209 normal: { 210 opacity: 0, 211 label: { 212 show: false 213 } 214 } 215 } 216 }] 217 }, 218 series: [{ 219 map: "china", 220 type: 'map', 221 mapType: 'china', 222 geoIndex: 0, 223 data: this.defaultData, 224 itemStyle:{ 225 normal: { 226 label: { 227 show: true, 228 textStyle: { 229 color: "#D49655", 230 fontSize: 5 231 } 232 }, 233 color: function(parameter) { 234 if (parameter.data) { 235 let value = parameter.data.value; 236 return value == 0? 'transparent' : '#F7FFD3' 237 } 238 } 239 }, 240 emphasis: { 241 label: { 242 show: true, 243 textStyle: { 244 color: "#D49655", 245 fontSize: 5 246 } 247 }, 248 areaColor: '#FFE602' 249 } 250 }, 251 markPoint: { 252 symbol: 'image://static/index/location-ico.png', 253 symbolSize: [22, 36], 254 silent: true, 255 label: { 256 show: false 257 }, 258 data: [markPointData] 259 } 260 }] 261 }; 262 263 this.mapChart.setOption(option); 264 265 // 高亮展示某个区域 266 this.highlight(name); 267 268 // 头部展示当前高亮场馆信息 269 this.curSiteInfo(markPointData); 270 }, 271 272 // 开始循环展示地方馆信息 273 circulateSiteInfo() { 274 let len = this.openAreasArr.length; 275 276 if (len == 0) { 277 return 278 }; 279 280 let lastIndex = len - 1; // 最后一条数据的索引 281 282 if (this.timer != null) { 283 clearInterval(this.timer) 284 }; 285 286 // 启动定时器,更换展示 287 this.timer = setInterval(() => { 288 // 是否已经循环到了最后一条数据 289 if (this.curIndex >= lastIndex) { 290 this.curIndex = 0 291 } else { 292 this.curIndex++ 293 }; 294 295 // 更新配置 296 this.updateOption(this.openAreasArr[this.curIndex], this.openAreasArr[this.curIndex].name); 297 }, 4000); 298 }, 299 300 // 高亮展示某个区域 301 highlight(name) { 302 // 区域背景颜色高亮 303 this.mapChart.dispatchAction({ 304 type: 'highlight', 305 name: name 306 }); 307 308 // 提示框变化 309 this.mapChart.dispatchAction({ 310 type: 'showTip', 311 name: name, 312 seriesIndex: 0 313 }) 314 }, 315 316 // 头部展示当前高亮场馆信息 317 curSiteInfo(obj) { 318 this.$emit('getCurSiteInfo', obj) 319 }, 320 321 322 // 监听点击 323 // 只有默认选中区域才可点击 324 addMouseover() { 325 this.mapChart.on("mouseover", params => { 326 if (params.value != 0) { 327 // 取消正在循环的高亮地区 328 this.mapChart.dispatchAction({ 329 type: 'downplay', 330 name: this.openAreasArr[this.curIndex].name 331 }); 332 333 // 高亮展示当前地区 334 this.updateOption(params.data, params.name); 335 336 // 如果定时器启动,清除定时器 337 if (this.timer != null) { 338 clearInterval(this.timer) 339 }; 340 341 // 如果已经开启延时,清除延时,以最新点击为准 342 if (this.timeOut != null) { 343 clearTimeout(this.timeOut) 344 }; 345 346 // 5秒后重启定时器 347 this.timeOut = setTimeout(() => { 348 // 清除3秒延时 349 clearTimeout(this.timeOut); 350 351 // 重新开始循环场馆信息 352 this.circulateSiteInfo() 353 }, 4000) 354 } else { 355 // 取消区域背景颜色高亮 356 this.mapChart.dispatchAction({ 357 type: 'downplay', 358 name: params.name 359 }) 360 } 361 }) 362 } 363 }, 364 destroyed() { 365 clearInterval(this.timer) 366 } 367 } 368 </script> 369 370 371 <style lang="scss" scoped> 372 .qiun-charts { 373 690rpx; 374 height: 500rpx; 375 margin: 0 auto auto; 376 position: relative; 377 378 #mapChart { 379 690rpx; 380 height: 500rpx; 381 } 382 383 .map-section-9 { 384 90rpx; 385 height: 134rpx; 386 position: absolute; 387 bottom: 30rpx; 388 right: 46rpx; 389 } 390 391 .site-recommend { 392 display: flex; 393 align-items: center; 394 margin-top: 10rpx; 395 396 .item { 397 98rpx; 398 height: 98rpx; 399 margin-left: 14rpx; 400 display: flex; 401 flex-direction: column; 402 align-items: center; 403 justify-content: center; 404 background: linear-gradient(308deg, #FEEAC3 0%, #FCD090 100%); 405 border-radius: 20rpx; 406 border: 2rpx solid #F7D9A8; 407 408 .num, .name { 409 font-size: 26rpx; 410 font-family: YouSheBiaoTiHei; 411 color: #FFFFFF; 412 line-height: 34rpx; 413 background: linear-gradient(180deg, #9C4A23 0%, #713F29 100%); 414 -webkit-background-clip: text; 415 -webkit-text-fill-color: transparent; 416 } 417 418 .name { 419 max- 72rpx; 420 height: 34rpx; 421 margin-top: 4rpx; 422 overflow: hidden; 423 } 424 425 .entry { 426 font-size: 26rpx; 427 font-family: YouSheBiaoTiHei; 428 color: #F77E05; 429 line-height: 28rpx; 430 } 431 } 432 } 433 } 434 </style>