手把手教你用ECharts从零搭建企业级数据仪表盘完整实战指南
嘿,朋友,欢迎来到数据可视化的世界!
你是不是也曾盯着密密麻麻的Excel表格发呆,心想”如果这些数据能变成直观易懂的图表该多好”?别急,今天我就带你一步步用ECharts搭建一个真正能用、好看、专业级企业仪表盘。不管你是前端新手还是想提升可视化能力,这篇文章都会让你收获满满。
先说说ECharts是个啥——它是百度开源的一个纯JavaScript图表库,简单说就是能让你在网页上轻松画出各种图表。支持柱状图、折线图、饼图、散点图、地图、热力图……应有尽有,而且文档写得特别友好,对新手极其友好。
第一步:环境准备,让ECharts跑起来
开始写代码之前,你得有个能运行JavaScript的环境。最简单的方式就是直接在HTML文件里引入CDN,或者用npm安装后打包。我们先从最简单的方式开始。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>企业级数据仪表盘</title>
<!-- 引入ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d1117;
color: #e6edf3;
min-height: 100vh;
}
</style>
</head>
<body>
<!-- 仪表盘容器 -->
<div id="dashboard">
<div class="chart-container" id="mainChart"></div>
<div class="chart-container" id="pieChart"></div>
</div>
<script>
// 初始化图表
const chartDom = document.getElementById('mainChart');
const myChart = echarts.init(chartDom);
</script>
</body>
</html>
你看,只要引入那行CDN,ECharts就准备好了。这里我还顺手加了点深色主题的基础样式,因为企业仪表盘一般都喜欢深色背景,看起来更专业。
第二步:搭建仪表盘整体布局
一个企业级仪表盘不是把图表随便堆上去就行,得有清晰的结构。我们来设计一个典型的布局:顶部是标题和关键指标卡片,中间是核心图表区域,底部是一些辅助图表。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>企业数据仪表盘</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d1117;
color: #e6edf3;
min-height: 100vh;
padding: 20px;
}
/* 顶部标题栏 */
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid #21262d;
margin-bottom: 24px;
}
.dashboard-header h1 {
font-size: 24px;
font-weight: 600;
background: linear-gradient(135deg, #58a6ff, #3fb950);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header-info {
font-size: 14px;
color: #8b949e;
}
/* 关键指标卡片 */
.kpi-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.kpi-card {
background: #161b22;
border: 1px solid #21262d;
border-radius: 12px;
padding: 20px;
transition: transform 0.2s, box-shadow 0.2s;
}
.kpi-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.kpi-card .label {
font-size: 13px;
color: #8b949e;
margin-bottom: 8px;
}
.kpi-card .value {
font-size: 32px;
font-weight: 700;
color: #f0f6fc;
}
.kpi-card .change {
font-size: 13px;
margin-top: 8px;
}
.kpi-card .change.up {
color: #3fb950;
}
.kpi-card .change.down {
color: #f85149;
}
/* 图表网格布局 */
.chart-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.chart-card {
background: #161b22;
border: 1px solid #21262d;
border-radius: 12px;
padding: 20px;
}
.chart-card.large {
grid-column: span 8;
}
.chart-card.medium {
grid-column: span 6;
}
.chart-card.small {
grid-column: span 4;
}
.chart-card h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: #c9d1d9;
}
.chart-container {
width: 100%;
height: 320px;
}
.chart-container.tall {
height: 400px;
}
</style>
</head>
<body>
<div class="dashboard-wrapper">
<!-- 顶部标题 -->
<div class="dashboard-header">
<h1>📊 企业运营数据仪表盘</h1>
<div class="header-info">
<span>数据更新时间:2024-01-15 14:30</span>
</div>
</div>
<!-- 关键指标卡片 -->
<div class="kpi-cards">
<div class="kpi-card">
<div class="label">总营收</div>
<div class="value">¥1,284,500</div>
<div class="change up">↑ 12.5% 较上月</div>
</div>
<div class="kpi-card">
<div class="label">活跃用户</div>
<div class="value">58,432</div>
<div class="change up">↑ 8.3% 较上月</div>
</div>
<div class="kpi-card">
<div class="label">订单量</div>
<div class="value">23,891</div>
<div class="change down">↓ 3.2% 较上月</div>
</div>
<div class="kpi-card">
<div class="label">转化率</div>
<div class="value">4.7%</div>
<div class="change up">↑ 0.8% 较上月</div>
</div>
</div>
<!-- 图表区域 -->
<div class="chart-grid">
<div class="chart-card large">
<h3>📈 月度营收趋势</h3>
<div class="chart-container tall" id="trendChart"></div>
</div>
<div class="chart-card medium">
<h3>🥧 收入构成分析</h3>
<div class="chart-container" id="pieChart"></div>
</div>
<div class="chart-card medium">
<h3>📊 各渠道用户增长</h3>
<div class="chart-container" id="barChart"></div>
</div>
<div class="chart-card small">
<h3>🌡️ 地域热力分布</h3>
<div class="chart-container" id="heatmapChart"></div>
</div>
<div class="chart-card small">
<h3>📉 用户留存率</h3>
<div class="chart-container" id="lineChart"></div>
</div>
<div class="chart-card small">
<h3>🎯 KPI完成度</h3>
<div class="chart-container" id="gaugeChart"></div>
</div>
</div>
</div>
<script>
// 这里会放置所有图表的配置代码
</script>
</body>
</html>
布局搭好了,看起来是不是很有感觉?深色背景、卡片式布局、清晰的层级关系——这些都是企业级仪表盘的标准配置。注意我用的是CSS Grid布局,responsive的同时也保证了各个图表位置固定,不会因为屏幕变化而乱掉。
第三步:核心图表——营收趋势折线图
折线图是仪表盘里最常见的图表,用来展示数据随时间的变化趋势。下面这个例子展示了过去12个月的营收走势,还加上了两条均线做对比,让数据更有参考价值。
// 月度营收趋势图
const trendChart = echarts.init(document.getElementById('trendChart'));
const trendOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' },
formatter: function(params) {
let result = `<strong>${params[0].axisValue}</strong><br/>`;
params.forEach(param => {
result += `${param.marker} ${param.seriesName}: ¥${param.value.toLocaleString()}<br/>`;
});
return result;
}
},
legend: {
data: ['实际营收', '目标营收', '去年同期'],
top: 10,
textStyle: { color: '#8b949e' }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: 60,
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
axisLine: { lineStyle: { color: '#30363d' } },
axisLabel: { color: '#8b949e' }
},
yAxis: {
type: 'value',
name: '金额(元)',
nameTextStyle: { color: '#8b949e' },
axisLine: { show: false },
axisLabel: {
color: '#8b949e',
formatter: '{value}'
},
splitLine: {
lineStyle: { color: '#21262d', type: 'dashed' }
}
},
series: [
{
name: '实际营收',
type: 'line',
data: [820000, 932000, 901000, 1054000, 1189000, 1256000, 1178000, 1342000, 1289000, 1423000, 1356000, 1284500],
smooth: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: { width: 3, color: '#58a6ff' },
itemStyle: { color: '#58a6ff' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(88, 166, 255, 0.3)' },
{ offset: 1, color: 'rgba(88, 166, 255, 0.02)' }
])
}
},
{
name: '目标营收',
type: 'line',
data: [850000, 900000, 950000, 1000000, 1100000, 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000],
smooth: true,
symbol: 'none',
lineStyle: { width: 2, color: '#f0883e', type: 'dashed' },
itemStyle: { color: '#f0883e' }
},
{
name: '去年同期',
type: 'line',
data: [720000, 810000, 834000, 920000, 1012000, 1089000, 1023000, 1156000, 1102000, 1234000, 1178000, 1145000],
smooth: true,
symbol: 'none',
lineStyle: { width: 2, color: '#3fb950', type: 'dotted' },
itemStyle: { color: '#3fb950' }
}
]
};
trendChart.setOption(trendOption);
这里有几个值得注意的细节:areaStyle 用了渐变填充,让折线图看起来更有层次感;tooltip 里我自定义了 formatter,这样鼠标悬停时显示的信息更清晰,不只是简单的数字;三条线用不同的样式区分——实线表示实际数据,虚线是目标,点线是去年同期,这样一眼就能看出今年的表现是超预期还是未达标。
第四步:饼图——收入构成可视化
饼图适合展示各部分的占比关系。在企业仪表盘里,我们经常用它来展示收入来源、成本构成等。这个例子展示了公司五大收入板块的占比。
// 收入构成饼图
const pieChart = echarts.init(document.getElementById('pieChart'));
const pieOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' },
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
textStyle: { color: '#8b949e', fontSize: 12 },
itemWidth: 10,
itemHeight: 10
},
series: [
{
name: '收入构成',
type: 'pie',
radius: ['40%', '70%'], // 环形图,中间空心更好看
center: ['35%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 8,
borderColor: '#161b22',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{d}%',
color: '#c9d1d9',
fontSize: 11
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold'
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
data: [
{ value: 456000, name: '产品销售', itemStyle: { color: '#58a6ff' } },
{ value: 298000, name: '服务收入', itemStyle: { color: '#3fb950' } },
{ value: 186000, name: '广告收入', itemStyle: { color: '#f0883e' } },
{ value: 156000, name: '订阅费用', itemStyle: { color: '#a371f7' } },
{ value: 188500, name: '其他收入', itemStyle: { color: '#8b949e' } }
]
}
]
};
pieChart.setOption(pieOption);
我把饼图做成了环形图(donut chart),radius: ['40%', '70%'] 这个设置让中间是空的,视觉上更轻盈。每个扇区之间加了 borderColor 和 borderWidth,这样各部分之间有清晰的间隔,不会黏在一起。鼠标悬停时的 emphasis 效果让当前项会浮起来,体验很好。
第五步:柱状图——渠道对比分析
柱状图适合做横向对比,比如不同渠道、不同产品、不同地区的业绩比较。下面这个例子用堆叠柱状图展示了各渠道的用户增长情况。
// 各渠道用户增长柱状图
const barChart = echarts.init(document.getElementById('barChart'));
const barOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' }
},
legend: {
data: ['新增用户', '回流用户', '流失用户'],
top: 10,
textStyle: { color: '#8b949e' }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: 50,
containLabel: true
},
xAxis: {
type: 'category',
data: ['微信公众号', '抖音', '小红书', '知乎', 'B站', '线下活动'],
axisLine: { lineStyle: { color: '#30363d' } },
axisLabel: { color: '#8b949e', rotate: 15 } // 标签旋转防止重叠
},
yAxis: {
type: 'value',
axisLine: { show: false },
axisLabel: { color: '#8b949e' },
splitLine: {
lineStyle: { color: '#21262d', type: 'dashed' }
}
},
series: [
{
name: '新增用户',
type: 'bar',
stack: 'total',
data: [12000, 28000, 15000, 8000, 22000, 5000],
itemStyle: { color: '#58a6ff', borderRadius: [4, 4, 0, 0] },
barWidth: '50%'
},
{
name: '回流用户',
type: 'bar',
stack: 'total',
data: [3000, 8000, 4000, 2000, 6000, 1500],
itemStyle: { color: '#3fb950' }
},
{
name: '流失用户',
type: 'bar',
stack: 'total',
data: [1500, 2000, 800, 500, 1800, 300],
itemStyle: { color: '#f85149', borderRadius: [0, 0, 4, 4] }
}
]
};
barChart.setOption(barOption);
堆叠柱状图(stack: 'total')的好处是一眼就能看到总量差异,同时各部分的占比关系也很清晰。注意我在 xAxis 上加了 rotate: 15,因为渠道名称比较长,不旋转的话会挤在一起看不清。borderRadius 给柱子的顶部和底部做了圆角处理,细节决定品质。
第六步:热力图——地域数据展示
热力图在展示地域分布、时间规律时特别好用。这里我模拟了一个电商企业各省份订单量的热力分布。
// 地域热力分布
const heatmapChart = echarts.init(document.getElementById('heatmapChart'));
// 模拟省份数据
const provinces = ['广东', '浙江', '江苏', '山东', '河南', '四川', '湖北', '湖南', '安徽', '福建'];
const hours = ['1月', '2月', '3月', '4月', '5月', '6月'];
// 生成热力图数据
const heatmapData = [];
for (let i = 0; i < provinces.length; i++) {
for (let j = 0; j < hours.length; j++) {
heatmapData.push([
j,
i,
Math.floor(Math.random() * 10000) + 1000
]);
}
}
const heatmapOption = {
backgroundColor: 'transparent',
tooltip: {
position: 'top',
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' },
formatter: function(params) {
return `${provinces[params.value[1]]} ${hours[params.value[0]]}<br/>订单量: ${params.value[2].toLocaleString()}`;
}
},
grid: {
height: '70%',
top: 30,
left: 60,
right: 40
},
xAxis: {
type: 'category',
data: hours,
splitArea: { show: true, areaStyle: { color: ['transparent', 'rgba(33, 38, 45, 0.5)'] } },
axisLabel: { color: '#8b949e' },
axisLine: { lineStyle: { color: '#30363d' } }
},
yAxis: {
type: 'category',
data: provinces,
splitArea: { show: true, areaStyle: { color: ['transparent', 'rgba(33, 38, 45, 0.5)'] } },
axisLabel: { color: '#8b949e' },
axisLine: { lineStyle: { color: '#30363d' } }
},
visualMap: {
min: 0,
max: 15000,
show: false,
calculable: true,
inRange: {
color: ['#0d1117', '#1a5276', '#2e86c1', '#5dade2', '#aed6f1']
}
},
series: [{
type: 'heatmap',
data: heatmapData,
label: {
show: true,
color: '#e6edf3',
formatter: function(params) {
return params.value[2] > 5000 ? params.value[2] : '';
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};
heatmapChart.setOption(heatmapOption);
热力图的配色我用了蓝色渐变系,从深色到亮蓝,代表订单量从低到高。visualMap 里的 show: false 是把图例隐藏了,因为仪表盘空间有限。当数值超过5000时才显示具体数字,避免格子太多太密。这种”少即是多”的思路在仪表盘设计里很重要。
第七步:折线图——用户留存分析
留存率是衡量产品健康度的核心指标。这个线图展示了用户从注册后第1天到第30天的留存情况,用多个线条对比不同批次的留存曲线。
// 用户留存率折线图
const lineChart = echarts.init(document.getElementById('lineChart'));
const lineOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' }
},
legend: {
data: ['1月批次', '2月批次', '3月批次'],
top: 0,
textStyle: { color: '#8b949e', fontSize: 11 },
itemWidth: 12,
itemHeight: 12
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: 35,
containLabel: true
},
xAxis: {
type: 'category',
data: ['第1天', '第3天', '第7天', '第14天', '第30天'],
axisLine: { lineStyle: { color: '#30363d' } },
axisLabel: { color: '#8b949e', fontSize: 10 }
},
yAxis: {
type: 'value',
name: '留存率(%)',
nameTextStyle: { color: '#8b949e', fontSize: 10 },
max: 100,
axisLine: { show: false },
axisLabel: { color: '#8b949e', formatter: '{value}%' },
splitLine: {
lineStyle: { color: '#21262d', type: 'dashed' }
}
},
series: [
{
name: '1月批次',
type: 'line',
data: [85, 62, 45, 32, 22],
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#58a6ff', width: 2 },
itemStyle: { color: '#58a6ff' }
},
{
name: '2月批次',
type: 'line',
data: [82, 58, 41, 29, 19],
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#3fb950', width: 2 },
itemStyle: { color: '#3fb950' }
},
{
name: '3月批次',
type: 'line',
data: [88, 65, 48, 35, 25],
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#f0883e', width: 2 },
itemStyle: { color: '#f0883e' }
}
]
};
lineChart.setOption(lineOption);
留存曲线是产品运营最关心的图表之一。三条线分别代表不同月份的用户批次,可以看出3月批次的留存率最高,说明那个月的产品改进或者运营活动效果不错。smooth: true 让折线变得圆润,比尖角的折线更易读。
第八步:仪表盘——KPI完成度展示
仪表盘(gauge)是展示进度、完成率的专业图表。这里用来展示季度KPI完成情况和各项关键指标的达成率。
// KPI完成度仪表盘
const gaugeChart = echarts.init(document.getElementById('gaugeChart'));
const gaugeOption = {
backgroundColor: 'transparent',
series: [
{
name: '总KPI完成度',
type: 'gauge',
radius: '90%',
center: ['50%', '55%'],
startAngle: 220,
endAngle: -40,
min: 0,
max: 100,
splitNumber: 5,
itemStyle: {
color: '#58a6ff',
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.3)'
},
progress: {
show: true,
width: 12
},
pointer: {
show: true,
length: '60%',
width: 4,
itemStyle: { color: '#e6edf3' }
},
axisLine: {
lineStyle: {
width: 12,
color: [
[0.3, '#f85149'],
[0.7, '#f0883e'],
[1, '#3fb950']
]
}
},
splitLine: {
length: 15,
lineStyle: { color: '#30363d', width: 2 }
},
axisTick: {
length: 8,
lineStyle: { color: '#8b949e', width: 1 }
},
axisLabel: {
color: '#8b949e',
fontSize: 10,
distance: -15
},
title: {
show: true,
offsetCenter: [0, '30%'],
fontSize: 12,
color: '#8b949e'
},
detail: {
valueAnimation: true,
fontSize: 24,
fontWeight: 'bold',
offsetCenter: [0, '-5%'],
color: '#e6edf3',
formatter: '{value}%'
},
data: [
{ value: 78.5, name: '季度目标达成' }
]
},
{
name: '营收指标',
type: 'gauge',
radius: '45%',
center: ['25%', '72%'],
startAngle: 220,
endAngle: -40,
min: 0,
max: 100,
progress: { show: true, width: 8 },
pointer: { show: false },
axisLine: {
lineStyle: { width: 8, color: [[1, '#30363d']] }
},
splitLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false },
title: { show: false },
detail: {
fontSize: 14,
fontWeight: 'bold',
offsetCenter: [0, '0%'],
color: '#58a6ff',
formatter: '{value}%'
},
data: [{ value: 85, name: '营收' }]
},
{
name: '用户指标',
type: 'gauge',
radius: '45%',
center: ['75%', '72%'],
startAngle: 220,
endAngle: -40,
min: 0,
max: 100,
progress: { show: true, width: 8 },
pointer: { show: false },
axisLine: {
lineStyle: { width: 8, color: [[1, '#30363d']] }
},
splitLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false },
title: { show: false },
detail: {
fontSize: 14,
fontWeight: 'bold',
offsetCenter: [0, '0%'],
color: '#3fb950',
formatter: '{value}%'
},
data: [{ value: 72, name: '用户' }]
}
]
};
gaugeChart.setOption(gaugeOption);
这个仪表盘设计了一个主仪表盘加两个小仪表盘的布局。主仪表盘显示总KPI完成度(78.5%),背景色从红到绿渐变,直观反映健康程度。下面两个小仪表盘分别显示营收和用户的单项完成度,紧凑且信息量大。valueAnimation: true 让指针有动画效果,打开页面时数字会滚动增长,体验很好。
第九步:响应式适配——让图表在任何屏幕上都好看
企业仪表盘要在不同尺寸的屏幕上都能正常显示,不能在小笔记本上挤成一团,也不能在大屏上稀稀拉拉。ECharts提供了 resize 方法,配合 window.resize 事件即可实现自适应。
// 所有图表响应式适配
window.addEventListener('resize', function() {
trendChart.resize();
pieChart.resize();
barChart.resize();
heatmapChart.resize();
lineChart.resize();
gaugeChart.resize();
});
// 更优雅的写法:封装一个通用方法
function resizeAllCharts() {
echarts.getInstanceByDom(document.getElementById('trendChart'))?.resize();
echarts.getInstanceByDom(document.getElementById('pieChart'))?.resize();
echarts.getInstanceByDom(document.getElementById('barChart'))?.resize();
echarts.getInstanceByDom(document.getElementById('heatmapChart'))?.resize();
echarts.getInstanceByDom(document.getElementById('lineChart'))?.resize();
echarts.getInstanceByDom(document.getElementById('gaugeChart'))?.resize();
}
window.addEventListener('resize', resizeAllCharts);
注意 resize 是在窗口大小变化时触发的,所以调整浏览器窗口大小或者在不同设备上查看时,图表会自动重新计算尺寸。用 echarts.getInstanceByDom() 的方式更安全,即使图表已经销毁也不会报错。
第十步:动态数据加载——让仪表盘”活”起来
一个真正实用的仪表盘不应该只展示静态数据,而应该能实时或定期刷新数据。下面这个例子展示了如何模拟从后端API获取数据并更新图表。
// 模拟API数据
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
revenue: [890000, 945000, 978000, 1089000, 1234000, 1298000, 1215000, 1389000, 1334000, 1456000, 1398000, 1324000],
users: [52000, 54000, 55000, 56000, 57000, 58000, 58432, 59000, 59500, 60000, 60500, 61000],
orders: [21000, 21500, 22000, 22500, 23000, 23200, 23500, 23891, 24000, 24200, 24500, 24800],
conversion: [3.8, 3.9, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.75, 4.8]
});
}, 500);
});
}
// 更新KPI卡片
async function updateKPICards() {
const data = await fetchData();
const cards = document.querySelectorAll('.kpi-card');
cards[0].querySelector('.value').textContent = '¥' + data.revenue[11].toLocaleString();
cards[1].querySelector('.value').textContent = data.users[11].toLocaleString();
cards[2].querySelector('.value').textContent = data.orders[11].toLocaleString();
cards[3].querySelector('.value').textContent = data.conversion[11].toFixed(1) + '%';
// 更新趋势图
trendChart.setOption({
series: [{ data: data.revenue }]
});
}
// 每30秒自动刷新一次
setInterval(updateKPICards, 30000);
// 页面加载时立即获取一次数据
updateKPICards();
这里用 Promise 模拟了异步API请求,实际项目中你可以换成 fetch 或者 axios 请求真实接口。setInterval 让数据每30秒自动刷新一次,这样仪表盘就能实时反映业务变化。实际使用时建议加一个加载状态和错误处理,让用户体验更好。
第十一步:主题定制——打造你的专属风格
ECharts支持自定义主题,你可以通过JSON配置或代码方式定义颜色、字体、间距等。下面是一个企业级深色主题的配置示例。
// 自定义深色主题
const customTheme = {
color: ['#58a6ff', '#3fb950', '#f0883e', '#a371f7', '#8b949e', '#f778ba', '#79c0ff'],
backgroundColor: 'transparent',
textStyle: { fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' },
title: { textStyle: { color: '#e6edf3' } },
legend: { textStyle: { color: '#8b949e' } },
tooltip: {
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' }
},
axisPointer: { lineStyle: { color: '#30363d' } },
line: { smooth: true, symbol: 'circle', symbolSize: 6 },
bar: { barWidth: '45%' },
pie: { radius: ['35%', '65%'] },
graphic: {
elements: [{
type: 'text',
style: { fill: '#8b949e', fontSize: 12 }
}]
}
};
// 应用主题
echarts.registerTheme('corporate-dark', customTheme);
// 使用: echarts.init(dom, 'corporate-dark')
有了自定义主题,你只需要在 init 时指定主题名称,所有图表就会自动应用这套配色方案。这比在每个图表配置里重复写颜色要高效得多,也方便后期统一修改。
第十二步:完整项目整合——从零到一
把前面所有部分串起来,就是一个完整的企业级仪表盘。下面给出完整可运行的代码框架,你可以直接复制到一个HTML文件里打开查看效果。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>企业数据仪表盘</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d1117;
color: #e6edf3;
min-height: 100vh;
padding: 20px;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid #21262d;
margin-bottom: 24px;
}
.dashboard-header h1 {
font-size: 24px;
font-weight: 600;
background: linear-gradient(135deg, #58a6ff, #3fb950);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header-info { font-size: 14px; color: #8b949e; }
.kpi-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.kpi-card {
background: #161b22;
border: 1px solid #21262d;
border-radius: 12px;
padding: 20px;
transition: transform 0.2s;
}
.kpi-card:hover { transform: translateY(-2px); }
.kpi-card .label { font-size: 13px; color: #8b949e; margin-bottom: 8px; }
.kpi-card .value { font-size: 32px; font-weight: 700; color: #f0f6fc; }
.kpi-card .change { font-size: 13px; margin-top: 8px; }
.kpi-card .change.up { color: #3fb950; }
.kpi-card .change.down { color: #f85149; }
.chart-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 16px;
}
.chart-card {
background: #161b22;
border: 1px solid #21262d;
border-radius: 12px;
padding: 20px;
}
.chart-card.large { grid-column: span 8; }
.chart-card.medium { grid-column: span 6; }
.chart-card.small { grid-column: span 4; }
.chart-card h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: #c9d1d9;
}
.chart-container { width: 100%; height: 320px; }
.chart-container.tall { height: 400px; }
@media (max-width: 1200px) {
.kpi-cards { grid-template-columns: repeat(2, 1fr); }
.chart-card.large,
.chart-card.medium,
.chart-card.small { grid-column: span 12; }
}
@media (max-width: 600px) {
.kpi-cards { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="dashboard-wrapper">
<div class="dashboard-header">
<h1>📊 企业运营数据仪表盘</h1>
<div class="header-info">
<span id="updateTime">数据更新时间:加载中...</span>
</div>
</div>
<div class="kpi-cards">
<div class="kpi-card">
<div class="label">总营收</div>
<div class="value" id="kpiRevenue">¥1,284,500</div>
<div class="change up" id="kpiRevenueChange">↑ 12.5% 较上月</div>
</div>
<div class="kpi-card">
<div class="label">活跃用户</div>
<div class="value" id="kpiUsers">58,432</div>
<div class="change up" id="kpiUsersChange">↑ 8.3% 较上月</div>
</div>
<div class="kpi-card">
<div class="label">订单量</div>
<div class="value" id="kpiOrders">23,891</div>
<div class="change down" id="kpiOrdersChange">↓ 3.2% 较上月</div>
</div>
<div class="kpi-card">
<div class="label">转化率</div>
<div class="value" id="kpiConversion">4.7%</div>
<div class="change up" id="kpiConversionChange">↑ 0.8% 较上月</div>
</div>
</div>
<div class="chart-grid">
<div class="chart-card large">
<h3>📈 月度营收趋势</h3>
<div class="chart-container tall" id="trendChart"></div>
</div>
<div class="chart-card medium">
<h3>🥧 收入构成分析</h3>
<div class="chart-container" id="pieChart"></div>
</div>
<div class="chart-card medium">
<h3>📊 各渠道用户增长</h3>
<div class="chart-container" id="barChart"></div>
</div>
<div class="chart-card small">
<h3>🌡️ 地域热力分布</h3>
<div class="chart-container" id="heatmapChart"></div>
</div>
<div class="chart-card small">
<h3>📉 用户留存率</h3>
<div class="chart-container" id="lineChart"></div>
</div>
<div class="chart-card small">
<h3>🎯 KPI完成度</h3>
<div class="chart-container" id="gaugeChart"></div>
</div>
</div>
</div>
<script>
// 初始化所有图表
const charts = {};
// 1. 趋势图
charts.trend = echarts.init(document.getElementById('trendChart'));
charts.trend.setOption({
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' },
formatter: function(params) {
let result = `<strong>${params[0].axisValue}</strong><br/>`;
params.forEach(p => {
result += `${p.marker} ${p.seriesName}: ¥${p.value.toLocaleString()}<br/>`;
});
return result;
}
},
legend: {
data: ['实际营收', '目标营收', '去年同期'],
top: 10,
textStyle: { color: '#8b949e' }
},
grid: { left: '3%', right: '4%', bottom: '3%', top: 60, containLabel: true },
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
axisLine: { lineStyle: { color: '#30363d' } },
axisLabel: { color: '#8b949e' }
},
yAxis: {
type: 'value',
name: '金额(元)',
nameTextStyle: { color: '#8b949e' },
axisLine: { show: false },
axisLabel: { color: '#8b949e' },
splitLine: { lineStyle: { color: '#21262d', type: 'dashed' } }
},
series: [
{ name: '实际营收', type: 'line', smooth: true, symbol: 'circle', symbolSize: 8,
lineStyle: { width: 3, color: '#58a6ff' }, itemStyle: { color: '#58a6ff' },
areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1,
[{ offset: 0, color: 'rgba(88, 166, 255, 0.3)' }, { offset: 1, color: 'rgba(88, 166, 255, 0.02)' })]
),
data: [820000,932000,901000,1054000,1189000,1256000,1178000,1342000,1289000,1423000,1356000,1284500] },
{ name: '目标营收', type: 'line', smooth: true, symbol: 'none',
lineStyle: { width: 2, color: '#f0883e', type: 'dashed' }, itemStyle: { color: '#f0883e' },
data: [850000,900000,950000,1000000,1100000,1200000,1250000,1300000,1350000,1400000,1450000,1500000] },
{ name: '去年同期', type: 'line', smooth: true, symbol: 'none',
lineStyle: { width: 2, color: '#3fb950', type: 'dotted' }, itemStyle: { color: '#3fb950' },
data: [720000,810000,834000,920000,1012000,1089000,1023000,1156000,1102000,1234000,1178000,1145000] }
]
});
// 2. 饼图
charts.pie = echarts.init(document.getElementById('pieChart'));
charts.pie.setOption({
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(22, 27, 34, 0.95)',
borderColor: '#30363d',
textStyle: { color: '#e6edf3' },
formatter: '{b}: {c} ({d}%)'
},
legend: { orient: 'vertical', right: 10, top: 'center', textStyle: { color: '#8b949e', fontSize: 12 } },
series: [{
name: '收入构成', type: 'pie', radius: ['40%', '70%'], center: ['35%', '50%'],
itemStyle: { borderRadius: 8, borderColor: '#161b22', borderWidth: 2 },
label: { show: true, formatter: '{b}\n{d}%', color: '#c9d1d9', fontSize: 11 },
emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
data: [
{ value: 456000, name: '产品销售', itemStyle: { color: '#58a6ff' } },
{ value: 298000, name: '服务收入', itemStyle: { color: '#3fb950' } },
{ value: 186000, name: '广告收入', itemStyle: { color: '#f0883e' } },
{ value: 156000, name: '订阅费用', itemStyle: { color: '#a371f7' } },
{ value: 188500, name: '其他收入', itemStyle: { color: '#8b949e' } }
]
}]
});
// 3. 柱状图
charts.bar = echarts.init(document.getElementById('barChart'));
charts.bar.setOption({
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis', axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(22, 27, 34, 0.95)', borderColor: '#30363d', textStyle: { color: '#e6edf3' }
},
legend: { data: ['新增用户','回流用户','流失用户'], top: 0, textStyle: { color: '#8b949e' } },
grid: { left: '3%', right: '4%', bottom: '3%', top: 35, containLabel: true },
xAxis: {
type: 'category', data: ['公众号','抖音','小红书','知乎','B站','线下'],
axisLine: { lineStyle: { color: '#30363d' } },
axisLabel: { color: '#8b949e', rotate: 15 }
},
yAxis: {
type: 'value', axisLine: { show: false }, axisLabel: { color: '#8b949e' },
splitLine: { lineStyle: { color: '#21262d', type: 'dashed' } }
},
series: [
{ name: '新增用户', type: 'bar', stack: 'total', barWidth: '50%',
itemStyle: { color: '#58a6ff', borderRadius: [4,4,0,0] },
data: [12000,28000,15000,8000,22000,5000] },
{ name: '回流用户', type: 'bar', stack: 'total',
itemStyle: { color: '#3fb950' }, data: [3000,8000,4000,2000,6000,1500] },
{ name: '流失用户', type: 'bar', stack: 'total',
itemStyle: { color: '#f85149', borderRadius: [0,0,4,4] }, data: [1500,2000,800,500,1800,300] }
]
});
// 4. 热力图
charts.heatmap = echarts.init(document.getElementById('heatmapChart'));
const provinces = ['广东','浙江','江苏','山东','河南','四川','湖北','湖南','安徽','福建'];
const months = ['1月','2月','3月','4月','5月','6月'];
const heatmapData = [];
for (let i = 0; i < provinces.length; i++) {
for (let j = 0; j < months.length; j++) {
heatmapData.push([j, i, Math.floor(Math.random() * 10000) + 1000]);
}
}
charts.heatmap.setOption({
backgroundColor: 'transparent',
tooltip: {
position: 'top', backgroundColor: 'rgba(22,27,34,0.95)', borderColor: '#30363d',
textStyle: { color: '#e6edf3' },
formatter: p => `${provinces[p.value[1]]} ${months[p.value[0]]}<br/>订单: ${p.value[2].toLocaleString()}`
},
grid: { height: '75%', top: 20, left: 55, right: 30 },
xAxis: {
type: 'category', data: months,
splitArea: { show: true, areaStyle: { color: ['transparent','rgba(33,38,45,0.5)'] } },
axisLabel: { color: '#8b949e', fontSize: 10 },
axisLine: { lineStyle: { color: '#30363d' } }
},
yAxis: {
type: 'category', data: provinces,
splitArea: { show: true, areaStyle: { color: ['transparent','rgba(33,38,45,0.5)'] } },
axisLabel: { color: '#8b949e', fontSize: 10 },
axisLine: { lineStyle: { color: '#30363d' } }
},
visualMap: { min: 0, max: 15000, show: false, inRange: { color: ['#0d1117','#1a5276','#2e86c1','#5dade2','#aed6f1'] } },
series: [{
type: 'heatmap', data: heatmapData,
label: { show: true, color: '#e6edf3', formatter: p => p.value[2] > 5000 ? p.value[2] : '' },
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.5)' } }
}]
});
// 5. 留存率折线图
charts.line = echarts.init(document.getElementById('lineChart'));
charts.line.setOption({
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis', backgroundColor: 'rgba(22,27,34,0.95)', borderColor: '#30363d',
textStyle: { color: '#e6edf3' }
},
legend: {
data: ['1月批次','2月批次','3月批次'], top: 0,
textStyle: { color: '#8b949e', fontSize: 11 }
},
grid: { left: '3%', right: '4%', bottom: '3%', top: 30, containLabel: true },
xAxis: {
type: 'category', data: ['第1天','第3天','第7天','第14天','第30天'],
axisLine: { lineStyle: { color: '#30363d' } },
axisLabel: { color: '#8b949e', fontSize: 10 }
},
yAxis: {
type: 'value', name: '留存率(%)', nameTextStyle: { color: '#8b949e', fontSize: 10 },
max: 100, axisLine: { show: false },
axisLabel: { color: '#8b949e', formatter: '{value}%' },
splitLine: { lineStyle: { color: '#21262d', type: 'dashed' } }
},
series: [
{ name: '1月批次', type: 'line', data: [85,62,45,32,22], smooth: true, symbol: 'circle', symbolSize: 6, lineStyle: { color: '#58a6ff', width: 2 }, itemStyle: { color: '#58a6ff' } },
{ name: '2月批次', type: 'line', data: [82,58,41,29,19], smooth: true, symbol: 'circle', symbolSize: 6, lineStyle: { color: '#3fb950', width: 2 }, itemStyle: { color: '#3fb950' } },
{ name: '3月批次', type: 'line', data: [88,65,48,35,25], smooth: true, symbol: 'circle', symbolSize: 6, lineStyle: { color: '#f0883e', width: 2 }, itemStyle: { color: '#f0883e' } }
]
});
// 6. KPI仪表盘
charts.gauge = echarts.init(document.getElementById('gaugeChart'));
charts.gauge.setOption({
backgroundColor: 'transparent',
series: [
{
name: '总KPI完成度', type: 'gauge', radius: '90%', center: ['50%','55%'],
startAngle: 220, endAngle: -40, min: 0, max: 100, splitNumber: 5,
itemStyle: { color: '#58a6ff', shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.3)' },
progress: { show: true, width: 12 },
pointer: { show: true, length: '60%', width: 4, itemStyle: { color: '#e6edf3' } },
axisLine: { lineStyle: { width: 12, color: [[0.3,'#f85149'],[0.7,'#f0883e'],[1,'#3fb950']] } },
splitLine: { length: 15, lineStyle: { color: '#30363d', width: 2 } },
axisTick: { length: 8, lineStyle: { color: '#8b949e', width: 1 } },
axisLabel: { color: '#8b949e', fontSize: 10, distance: -15 },
title: { show: true, offsetCenter: [0,'30%'], fontSize: 12, color: '#8b949e' },
detail: { valueAnimation: true, fontSize: 24, fontWeight: 'bold', offsetCenter: [0,'-5%'], color: '#e6edf3', formatter: '{value}%' },
data: [{ value: 78.5, name: '季度目标' }]
},
{
name: '营收指标', type: 'gauge', radius: '45%', center: ['25%','72%'],
startAngle: 220, endAngle: -40, min: 0, max: 100,
progress: { show: true, width: 8 }, pointer: { show: false },
axisLine: { lineStyle: { width: 8, color: [[1,'#30363d']] } },
splitLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false },
title: { show: false }, detail: { fontSize: 14, fontWeight: 'bold', offsetCenter: [0,'0%'], color: '#58a6ff', formatter: '{value}%' },
data: [{ value: 85, name: '营收' }]
},
{
name: '用户指标', type: 'gauge', radius: '45%', center: ['75%','72%'],
startAngle: 220, endAngle: -40, min: 0, max: 100,
progress: { show: true, width: 8 }, pointer: { show: false },
axisLine: { lineStyle: { width: 8, color: [[1,'#30363d']] } },
splitLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false },
title: { show: false }, detail: { fontSize: 14, fontWeight: 'bold', offsetCenter: [0,'0%'], color: '#3fb950', formatter: '{value}%' },
data: [{ value: 72, name: '用户' }]
}
]
});
// 响应式适配
function resizeAllCharts() {
Object.values(charts).forEach(chart => chart.resize());
}
window.addEventListener('resize', resizeAllCharts);
// 模拟数据刷新
function updateTime() {
const now = new Date();
const timeStr = now.getFullYear() + '-' +
String(now.getMonth()+1).padStart(2,'0') + '-' +
String(now.getDate()).padStart(2,'0') + ' ' +
String(now.getHours()).padStart(2,'0') + ':' +
String(now.getMinutes()).padStart(2,'0');
document.getElementById('updateTime').textContent = '数据更新时间:' + timeStr;
}
updateTime();
setInterval(updateTime, 60000);
</script>
</body>
</html>
把这段代码保存成 .html 文件,用浏览器打开,你就能看到一个完整的企业级数据仪表盘了。每个图表都是可交互的——鼠标悬停显示详情、拖动缩放、点击图例切换系列。
最后说几点实战经验:第一,性能方面,如果图表数量多或者数据量大,记得用 echarts.dispose() 在组件销毁时清理实例,避免内存泄漏;第二,数据更新,用 setOption 而不是重新 init,这样动画会更流畅;第三,配色方案,深色主题现在很流行,但一定要保证对比度够高,让文字和图表都清晰可读;第四,加载体验,可以加个骨架屏或者 loading 动画,等数据到来前让用户知道页面在加载。
希望这篇文章能帮你搭建出属于自己的专业仪表盘。数据可视化不只是让数据”好看”,更是让数据”好懂”——好的仪表盘应该让管理者一眼就能看出业务的健康状况,让执行者能快速定位问题所在。如果你在搭建过程中遇到具体问题,随时来问我。
