随着 AI 开发工具实现一键生成代码的能力,不少零基础新手小白都开始尝试 AI 开发。但在制作数据大屏页面时,单纯依靠 AI 生成成品不仅流程繁琐,实操效果也不尽如人意,基于这个痛点,便有了本文的分享。
在数据大屏、API管理系统、可视化后台、政务统计系统等场景中,中国地图可视化几乎是标配功能。但实际开发中,很多同学会遇到:
这篇文章我会手把手带你实现一个完整的数据可视化大屏,效果包括:
本文带你从零搭建一个酷炫的数据可视化大屏,使用Vue3+ECharts 5实现中国地图热力渲染、城市散点涟漪、交互拖拽缩放等效果,附带完整源码和踩坑指南。
整体布局为经典的三栏式数据大屏:左侧数据面板 + 中央地图 + 右侧数据面板,深色科技风格。
| 技术 | 说明 |
|---|---|
| Vue 3 | 组件化开发,Composition API |
| ECharts 5 | 百度开源可视化库,地图渲染引擎 |
| Vite | 极速前端构建工具 |
| GeoJson | 阿里云 DataV 官方行政区边界数据 |
npm create vite@latest china-map -- --template vue
cd china-map
npm installnpm install echarts --save阿里云 DataV 提供了最新完整版中国地图数据,地址:
https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json下载后保存为 public/map/china.json。使用 public 目录的好处是可以通过 fetch 异步加载,避免打包体积过大。
mkdir public/map
curl -o public/map/china.json https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json为什么用 _full 版本? 精简版会丢失部分省份边界,完整版包含所有省级行政区坐标数据,渲染效果完整无缺失。china-map/
├── public/
│ └── map/
│ └── china.json # 中国地图 GeoJson 数据
├── src/
│ ├── components/
│ │ └── ChinaMap.vue # 地图核心组件
│ ├── App.vue # 大屏主布局
│ ├── style.css # 全局样式
│ └── main.js # 入口文件
├── index.html
└── package.jsonChinaMap.vue这是整个项目最核心的组件,负责 ECharts 地图的初始化、渲染和交互。
<script setup>
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
import * as echarts from 'echarts'
const mapRef = ref(null)
const myChart = shallowRef(null)
const isLoading = ref(true)
// 各省模拟数据
const mapData = [
{ name: '北京市', value: 2820 },
{ name: '上海市', value: 3150 },
{ name: '广东省', value: 4980 },
{ name: '江苏省', value: 3720 },
{ name: '浙江省', value: 3480 },
{ name: '山东省', value: 3210 },
{ name: '河南省', value: 2890 },
{ name: '四川省', value: 2740 },
{ name: '湖北省', value: 2420 },
{ name: '湖南省', value: 2180 },
// ... 更多省份数据
]
// 热门城市散点数据(经纬度 + 数值)
const scatterData = [
{ name: '北京', value: [116.46, 39.92, 2820] },
{ name: '上海', value: [121.48, 31.22, 3150] },
{ name: '广州', value: [113.23, 23.16, 4980] },
{ name: '深圳', value: [114.07, 22.62, 3620] },
{ name: '成都', value: [104.06, 30.67, 2740] },
{ name: '杭州', value: [120.19, 30.26, 3480] },
{ name: '武汉', value: [114.31, 30.52, 2420] },
{ name: '南京', value: [118.78, 32.04, 3720] },
{ name: '重庆', value: [106.54, 29.59, 1850] },
{ name: '西安', value: [108.95, 34.27, 1960] },
]
const resizeHandler = () => {
myChart.value?.resize()
}
onMounted(async () => {
try {
// 异步加载 GeoJson 数据
const response = await fetch('/map/china.json')
const chinaJson = await response.json()
// 注册中国地图
echarts.registerMap('china', chinaJson)
myChart.value = echarts.init(mapRef.value)
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
borderColor: 'rgba(59, 130, 246, 0.6)',
borderWidth: 1,
padding: [12, 16],
textStyle: { color: '#e2e8f0', fontSize: 14 },
formatter: (params) => {
const val = params.value
? (Array.isArray(params.value)
? params.value[2].toLocaleString()
: params.value.toLocaleString())
: '暂无数据'
return `<div style="font-weight:700">${params.name}</div>
<div>访问量:<span style="color:#60a5fa">${val}</span></div>`
},
},
// 颜色渐变映射
visualMap: {
min: 0,
max: 5000,
left: 20,
bottom: 20,
text: ['高', '低'],
textStyle: { color: '#94a3b8' },
calculable: true,
inRange: {
color: ['#0c2d5a', '#0e4f8a', '#1a7fd4', '#3b9ff5', '#7cc4fa'],
},
},
// 地理坐标系配置
geo: {
map: 'china',
roam: true, // 开启拖拽缩放
zoom: 1.15,
center: [104, 36],
scaleLimit: { min: 0.8, max: 5 },
itemStyle: {
areaColor: '#0e2a47',
borderColor: '#1a5291',
borderWidth: 1,
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 10,
},
emphasis: {
itemStyle: {
areaColor: '#1a7fd4',
borderColor: '#60a5fa',
borderWidth: 2,
},
label: {
show: true,
color: '#fff',
fontSize: 13,
fontWeight: 600,
},
},
},
series: [
// 地图热力层
{
name: '访问量',
type: 'map',
map: 'china',
geoIndex: 0,
data: mapData,
},
// 城市散点涟漪层
{
name: '热门城市',
type: 'effectScatter',
coordinateSystem: 'geo',
data: scatterData,
symbolSize: (val) => Math.max(val[2] / 400, 6),
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
scale: 3,
period: 4,
},
label: {
show: true,
position: 'right',
formatter: '{b}',
color: '#e2e8f0',
fontSize: 11,
},
itemStyle: {
color: '#f59e0b',
shadowColor: 'rgba(245, 158, 11, 0.5)',
shadowBlur: 10,
},
},
],
}
myChart.value.setOption(option)
window.addEventListener('resize', resizeHandler)
} catch (error) {
console.error('地图加载失败:', error)
} finally {
isLoading.value = false
}
})
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
myChart.value?.dispose()
})
</script>
<template>
<div class="map-wrapper">
<div v-if="isLoading" class="map-loading">
<div class="loading-spinner"></div>
<span>地图数据加载中...</span>
</div>
<div ref="mapRef" class="map-chart"></div>
</div>
</template>1. 异步加载 GeoJson
const response = await fetch('/map/china.json')
const chinaJson = await response.json()
echarts.registerMap('china', chinaJson)使用 fetch 从 public 目录加载 GeoJson,而不是 import 静态引入,这样不会把 ~580KB 的 json 打包进 JS bundle,首屏加载更快。
2. 双层 series 叠加
type: 'map' — 地图热力填充层,根据数值深浅渐变type: 'effectScatter' — 城市涟漪散点层,在 geo 坐标系上标注重点城市通过 geoIndex: 0 让 map series 复用 geo 坐标系,两层叠加在同一个地图上。
3. 涟漪散点效果
rippleEffect: {
brushType: 'stroke', // 描边涟漪(比 fill 更精致)
scale: 3, // 涟漪扩散倍数
period: 4, // 动画周期(秒)
}effectScatter 会自动在散点位置播放涟漪动画,非常适合标注重点城市。
4. shallowRef 管理 ECharts 实例
const myChart = shallowRef(null)使用 shallowRef 而非 ref,避免 Vue 对 ECharts 实例进行深度响应式代理,提升性能。
App.vue大屏采用经典三栏布局,左右面板 + 中央地图:
<template>
<div class="dashboard">
<header class="dash-header">
<h1>全国数据可视化大屏</h1>
<span>{{ currentTime }}</span>
</header>
<main class="dash-body">
<aside class="panel-left">
<!-- 数据概览 + 省份排名 -->
</aside>
<section class="panel-center">
<ChinaMap />
</section>
<aside class="panel-right">
<!-- 热门城市 + 区域分布 -->
</aside>
</main>
<footer class="dash-footer">...</footer>
</div>
</template>布局的核心 CSS:
.dash-body {
display: flex;
flex: 1;
padding: 12px;
gap: 12px;
}
.panel-left, .panel-right {
width: 280px;
flex-shrink: 0;
}
.panel-center {
flex: 1;
min-width: 0;
}用 flex: 1 让地图区域自适应填满中间空间,左右面板固定宽度。
风格完全可以自定义,目前的风格是基于市面上的大部分系统的实例,吸取的颜色。
整体采用深蓝色背景(#0a1628),配合蓝色系渐变:
.panel-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #3b82f6, transparent);
}每个面板卡片顶部加一条渐变发光线,增加科技感。
排名列表用进度条可视化:
.rank-bar {
background: linear-gradient(90deg, #3b82f6, #60a5fa);
border-radius: 4px;
transition: width 0.6s ease;
}.header-title {
background: linear-gradient(90deg, #60a5fa, #34d399);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}原因:GeoJson 使用了精简版,缺少部分省份坐标。
解决:使用 _full 后缀的完整版数据:
https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json原因:ref() 会对对象深度代理,ECharts 实例非常复杂,代理后性能差且可能报错。
解决:使用 shallowRef():
const myChart = shallowRef(null)原因:容器大小变化后 ECharts 没有重绘。
解决:监听 resize 事件:
window.addEventListener('resize', () => myChart.resize())原因:ECharts 实例和事件监听器未清理。
解决:在 onUnmounted 中清理:
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
myChart.value?.dispose()
})原因:import china from './china.json' 会把 ~580KB 的数据打进 JS bundle。
解决:放在 public 目录,用 fetch 异步加载:
const res = await fetch('/map/china.json')
const data = await res.json()基于当前实现,可以继续拓展:
| 功能 | 实现思路 |
|---|---|
| 省份下钻 | 点击省份加载对应 adcode_full.json,重新 registerMap |
| 飞线动画 | 使用 type: 'lines' + effect 实现城市间飞线 |
| 自动轮播 | 用 dispatchAction 循环高亮各省份 |
| 暗黑/浅色切换 | 切换 option 中的配色方案 |
| 实时数据 | 对接 WebSocket,setOption 动态更新数据 |
项目已开源,完整源码可直接运行:
git clone <your-repo-url>
cd china-map
npm install
npm run dev访问 http://localhost:5173 即可看到效果。
本文实现了一个完整的 Vue3 + ECharts 5 中国地图可视化大屏,关键要点回顾:
_full 完整版,避免边界缺失fetch 加载 GeoJson,避免打包体积过大shallowRef 管理 ECharts 实例、组件卸载时清理资源希望这篇教程对你有帮助,如果觉得不错,欢迎点赞分享!
—— 评论区 ——