从零开始学echarts图表如何用真实数据快速搭建可视化大屏解决坐标轴不显示数据乱码等常见问题附完整项目源码和部署指南
一、写在前面:为什么是ECharts,而不是别的
说实话,我当初接触可视化也踩了不少坑。后来发现,在国内做数据可视化,ECharts几乎是绕不开的存在——百度开源、文档中文、社区活跃、组件丰富,最重要的是它真不挑浏览器,IE9+都能跑。
这篇文章不是来跟你扯理论的空话,而是手把手带你把一套真实可运行的可视化大屏项目搭起来,顺便把你遇到过、也一定会遇到的那些”坑”一个个填平。
项目地址我会放在文末,但更建议你跟着我的思路一步步来,这样下次遇到类似问题你能自己解决。
二、环境准备:你以为很简单,但细节很多
2.1 开发工具怎么选
我推荐你直接用 VS Code,装两个插件就够用:
- Live Server —— 本地起个静态服务器,不用配Nginx就能跑
- Chinese (Simplified) Language Pack —— 中文界面,看着亲切
小提醒:别用记事本写代码,也别用Word,那些格式会把你害死。
2.2 Node.js装到什么版本
ECharts本身是个纯JS库,不依赖Node也能用,但如果你要跑完整项目,建议装 Node.js 18.x LTS版本。
验证安装:
node -v
npm -v
如果你看到的是类似 v18.17.0 这样的输出,说明环境OK。
2.3 项目目录结构长什么样
先把骨架搭好,后面填肉就容易多了:
echarts-dashboard/
├── public/
│ ├── data/
│ │ ├── sales.json # 销售数据
│ │ ├── visitors.json # 访客数据
│ │ └── performance.json # 性能指标
│ └── index.html # 入口页面
├── src/
│ ├── charts/
│ │ ├── BarChart.js
│ │ ├── LineChart.js
│ │ ├── PieChart.js
│ │ ├── MapChart.js
│ │ └── GaugeChart.js
│ ├── utils/
│ │ └── request.js # 请求封装
│ └── App.js # 主逻辑
├── package.json
└── README.md
先别急着写代码,把这个目录手动建好,心里有个结构感,后面就不会迷路。
三、第一个图表:五分钟搞定柱状图
很多人一开始就想着做大屏,结果连一个柱状图都画不出来,心态直接崩了。我建议你从最简单的开始。
3.1 创建基础HTML文件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ECharts 第一个图表</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 {
background: #0f1923;
padding: 20px;
font-family: "Microsoft YaHei", sans-serif;
}
#main {
width: 100%;
height: 500px;
background: #1a2736;
border-radius: 8px;
}
</style>
</head>
<body>
<div id="main"></div>
<script>
// 初始化图表实例
const chart = echarts.init(document.getElementById('main'));
// 配置项
const option = {
backgroundColor: 'transparent',
title: {
text: '2024年各部门销售额',
left: 'center',
textStyle: {
color: '#fff',
fontSize: 20
}
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(26,39,54,0.9)',
borderColor: '#1e88e5',
textStyle: { color: '#fff' }
},
xAxis: {
type: 'category',
data: ['技术部', '市场部', '运营部', '销售部', '客服部'],
axisLine: { lineStyle: { color: '#5470c6' } },
axisLabel: {
color: '#ccc',
fontSize: 14
}
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#5470c6' } },
axisLabel: { color: '#ccc' },
splitLine: { lineStyle: { color: '#1e2d3d' } }
},
series: [{
name: '销售额(万元)',
type: 'bar',
data: [182, 201, 153, 255, 120],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#1e88e5' },
{ offset: 1, color: '#194d7a' }
])
},
label: {
show: true,
position: 'top',
color: '#fff'
}
}]
};
chart.setOption(option);
// 响应式
window.addEventListener('resize', () => {
chart.resize();
});
</script>
</body>
</html>
用 Live Server 打开这个HTML,你应该能看到一个漂亮的深色主题柱状图。
💡 注意:深色大屏背景配浅色文字,这是大屏设计的潜规则,别反着来,否则眼睛会疼。
四、真实数据接入:别再用假数据了
4.1 准备JSON数据文件
在项目 public/data/ 目录下创建 sales.json:
{
"date": ["2024-01", "2024-02", "2024-03", "2024-04", "2024-05", "2024-06"],
"revenue": [182, 201, 153, 255, 220, 289],
"cost": [120, 135, 110, 165, 155, 180],
"profit": [62, 66, 43, 90, 65, 109],
"targets": [170, 190, 200, 220, 240, 260]
}
4.2 用Fetch拉取数据并渲染折线图
async function loadSalesData() {
try {
const response = await fetch('./data/sales.json');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
// 打印原始数据,方便调试
console.log('原始数据:', data);
const option = {
backgroundColor: 'transparent',
title: {
text: '2024年上半年营收趋势',
left: 'center',
textStyle: { color: '#fff', fontSize: 18 }
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(26,39,54,0.9)',
borderColor: '#1e88e5',
textStyle: { color: '#fff' },
formatter: function(params) {
let result = `<b>${params[0].name}</b><br/>`;
params.forEach(item => {
result += `${item.marker} ${item.seriesName}: <b>${item.value}万</b><br/>`;
});
return result;
}
},
legend: {
data: ['营收', '成本', '利润', '目标'],
top: 30,
textStyle: { color: '#ccc' }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: data.date,
axisLine: { lineStyle: { color: '#5470c6' } },
axisLabel: { color: '#ccc', fontSize: 12 }
},
yAxis: {
type: 'value',
name: '金额(万元)',
nameTextStyle: { color: '#ccc' },
axisLine: { lineStyle: { color: '#5470c6' } },
axisLabel: { color: '#ccc' },
splitLine: { lineStyle: { color: '#1e2d3d' } }
},
series: [
{
name: '营收',
type: 'line',
smooth: true,
data: data.revenue,
itemStyle: { color: '#1e88e5' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(30,136,229,0.4)' },
{ offset: 1, color: 'rgba(30,136,229,0.05)' }
])
}
},
{
name: '成本',
type: 'line',
smooth: true,
data: data.cost,
itemStyle: { color: '#ff9800' }
},
{
name: '利润',
type: 'line',
smooth: true,
data: data.profit,
itemStyle: { color: '#4caf50' }
},
{
name: '目标',
type: 'line',
smooth: true,
data: data.targets,
lineStyle: { type: 'dashed', color: '#9c27b0' },
itemStyle: { color: '#9c27b0' },
symbol: 'none'
}
]
};
chart.setOption(option, true); // true 表示不合并,完全替换
} catch (error) {
console.error('数据加载失败:', error);
chart.showLoading({
text: '数据加载失败,请刷新重试',
color: '#ff5722',
textColor: '#ff5722',
maskColor: 'rgba(15,25,35,0.8)'
});
}
}
// 页面加载完成后执行
loadSalesData();
五、坐标轴不显示?这些问题90%的人都遇到过
这部分是你最想看的,也是我自己踩坑最多、花的时间最长的部分。
5.1 问题一:坐标轴标签完全不显示
现象: 图出来了,但X轴或Y轴的文字看不见。
原因分析:
- 样式颜色跟背景色一样(最常见)
- axisLabel 的 fontSize 设得太小
- 坐标轴被其他元素遮挡
解决方案:
xAxis: {
type: 'category',
data: ['一月', '二月', '三月', '四月', '五月', '六月'],
axisLabel: {
color: '#ffffff', // 确保文字颜色跟背景反差大
fontSize: 13, // 别太小,12-14px最合适
interval: 0, // 强制显示所有标签,不会自动省略
rotate: 0, // 文字旋转角度
formatter: function(value) {
// 超长标签可以截断
return value.length > 4 ? value.substring(0, 4) + '...' : value;
}
},
// 有时候坐标轴线本身颜色也是问题
axisLine: {
lineStyle: { color: '#5470c6', width: 2 }
}
}
真实案例: 我有一个同事做大屏,坐标轴死活不显示,查了半小时,最后发现他把 axisLabel.color 设成了 #1a2736,而这个颜色正好跟他的背景色一模一样。人眼真的会骗人。
5.2 问题二:坐标轴数据乱码
现象: 中文显示成 \uXXXX 或者乱码符号。
根本原因: 编码不一致。
完整解决方案:
方案A:HTML文件指定编码(治标)
<meta charset="UTF-8">
这一步你肯定做了,但别忘了确认一下。
方案B:JSON文件编码(治本)
用VS Code打开JSON文件,看右下角编码是什么。如果是 GBK 或 Windows-1252,点一下,选 Save with Encoding → UTF-8。
方案C:JS层强制转码(应急方案)
function decodeChinese(str) {
// 处理 Unicode 转义
return str.replace(/\\u[0-9a-fA-F]{4}/g, function(match) {
return String.fromCharCode(parseInt(match.substr(2), 16));
});
}
// 使用
const rawData = '{"name":"\\u9500\\u552e\\u989d"}';
const decoded = decodeChinese(rawData);
console.log(decoded); // {"name":"销售额"}
方案D:后端接口返回时指定Content-Type(推荐)
如果你是从API拿数据,后端响应头必须有:
Content-Type: application/json; charset=utf-8
Node.js Express示例:
const express = require('express');
const app = express();
// 确保所有响应都用UTF-8
app.use((req, res, next) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
next();
});
app.get('/api/sales', (req, res) => {
const data = {
date: ['2024-01', '2024-02', '2024-03'],
revenue: [182, 201, 153]
};
res.json(data); // Express默认就是UTF-8,但显式声明更安心
});
app.listen(3000, () => console.log('服务器运行在 http://localhost:3000'));
我的经验:乱码问题80%是JSON文件保存编码不对,20%是后端没带charset。先把JSON文件转成UTF-8,能解决大部分问题。
5.3 问题三:坐标轴数字被截断
现象: Y轴数字显示成 1.8e+2 或者 1.82E2 这种科学计数法。
原因: 数字太大,ECharts默认用了科学计数法。
解决方案:
yAxis: {
type: 'value',
axisLabel: {
color: '#ccc',
formatter: function(value) {
// 大于10000用万为单位
if (value >= 10000) {
return (value / 10000).toFixed(1) + '万';
}
return value;
}
}
}
效果对比:
- 原来:
18200 - 优化后:
1.8万
5.4 问题四:X轴标签重叠
现象: 分类太多,标签挤在一起,互相压着。
解决方案(按优先级排列):
// 方案1:旋转标签(最简单)
axisLabel: {
rotate: 45, // 45度旋转
color: '#ccc'
}
// 方案2:隔一个显示一个
axisLabel: {
interval: 1, // 0=全部显示,1=隔一个显示一个
color: '#ccc'
}
// 方案3:自适应换行(每个标签一行)
axisLabel: {
interval: 0,
formatter: function(value) {
// 超过6个字符就换行
if (value.length > 6) {
return value.substring(0, 6) + '\n' + value.substring(6);
}
return value;
},
color: '#ccc'
}
// 方案4:把X轴改成Y轴(数据多的时候最实用)
// 直接调整 series 的 type,或者用 tooltip 辅助查看
真实项目案例: 我做过一个电商大屏,类目有40多个,一开始全显示,结果标签糊成一团。后来改成 interval: 2(隔两个显示一个)+ rotate: 30,清晰度立竿见影。
六、完整大屏项目搭建
6.1 项目整体布局
┌─────────────────────────────────────────────────────┐
│ 🏢 智慧运营监控中心 2024-06-15 14:30 │
├──────────────┬──────────────────────────┬───────────┤
│ │ │ │
│ 营收趋势 │ │ 区域分布 │
│ 折线图 │ 核心指标卡片 │ 地图 │
│ (占30%) │ 总营收 | 订单数 | 用户 │ (占25%) │
│ │ 1289万 | 8,923 | 3.2万 │ │
│ │ │ │
├──────────────┼──────────────────────────┼───────────┤
│ │ │ │
│ 产品销售 │ │ 用户画像 │
│ 柱状图 │ 实时动态流 │ 饼图 │
│ (占25%) │ 滚动数据 │ (占20%) │
│ │ │ │
└──────────────┴──────────────────────────┴───────────┘
6.2 完整CSS样式
/* public/css/dashboard.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0b121e;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
overflow: hidden;
color: #fff;
}
/* 背景动画 */
body::before {
content: '';
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background:
radial-gradient(ellipse at 20% 50%, rgba(30,60,114,0.4) 0%, transparent 50%),
radial-gradient(ellipse at 80% 20%, rgba(42,82,130,0.3) 0%, transparent 50%),
radial-gradient(ellipse at 50% 80%, rgba(20,50,90,0.3) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 标题栏 */
.header {
height: 70px;
background: linear-gradient(180deg, rgba(20,40,70,0.9) 0%, transparent 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
border-bottom: 1px solid rgba(30,136,229,0.3);
}
.header h1 {
font-size: 28px;
letter-spacing: 8px;
background: linear-gradient(90deg, #1e88e5, #64b5f6, #1e88e5);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(30,136,229,0.5);
}
.header .time {
position: absolute;
right: 30px;
font-size: 14px;
color: #64b5f6;
}
/* 主布局 */
.dashboard {
display: grid;
grid-template-columns: 28% 44% 28%;
grid-template-rows: 1fr 1fr;
gap: 15px;
padding: 15px;
height: calc(100vh - 70px);
}
/* 卡片通用样式 */
.card {
background: linear-gradient(135deg, rgba(26,42,65,0.9) 0%, rgba(15,25,40,0.95) 100%);
border: 1px solid rgba(30,136,229,0.2);
border-radius: 8px;
padding: 15px;
position: relative;
overflow: hidden;
}
/* 四角装饰 */
.card::before {
content: '';
position: absolute;
top: 0; left: 0;
width: 20px; height: 20px;
border-top: 2px solid #1e88e5;
border-left: 2px solid #1e88e5;
}
.card::after {
content: '';
position: absolute;
top: 0; right: 0;
width: 20px; height: 20px;
border-top: 2px solid #1e88e5;
border-right: 2px solid #1e88e5;
}
.corner-bl, .corner-br {
position: absolute;
bottom: 0; width: 20px; height: 20px;
}
.corner-bl {
left: 0;
border-bottom: 2px solid #1e88e5;
border-left: 2px solid #1e88e5;
}
.corner-br {
right: 0;
border-bottom: 2px solid #1e88e5;
border-right: 2px solid #1e88e5;
}
.card-title {
font-size: 15px;
color: #64b5f6;
margin-bottom: 10px;
padding-left: 10px;
border-left: 3px solid #1e88e5;
}
.chart-container {
width: 100%;
height: calc(100% - 35px);
}
/* 核心指标卡片 */
.kpi-card {
grid-column: 2;
grid-row: 1;
display: flex;
justify-content: space-around;
align-items: center;
padding: 20px;
}
.kpi-item {
text-align: center;
padding: 10px 20px;
}
.kpi-value {
font-size: 36px;
font-weight: bold;
background: linear-gradient(180deg, #64b5f6 0%, #1e88e5 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-family: 'Arial', sans-serif;
}
.kpi-label {
font-size: 13px;
color: #8899aa;
margin-top: 5px;
}
.kpi-change {
font-size: 12px;
margin-top: 5px;
}
.kpi-change.up { color: #4caf50; }
.kpi-change.down { color: #ff5722; }
/* 实时动态 */
.live-feed {
grid-column: 2;
grid-row: 2;
overflow: hidden;
}
.feed-list {
height: calc(100% - 35px);
overflow: hidden;
position: relative;
}
.feed-item {
display: flex;
align-items: center;
padding: 8px 10px;
border-bottom: 1px solid rgba(30,136,229,0.1);
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.feed-time {
font-size: 12px;
color: #64b5f6;
margin-right: 15px;
min-width: 50px;
}
.feed-text {
font-size: 13px;
color: #ccc;
}
.feed-badge {
margin-left: auto;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
background: rgba(30,136,229,0.3);
color: #64b5f6;
}
/* 动画滚动 */
.feed-scroll {
animation: scrollUp 20s linear infinite;
}
@keyframes scrollUp {
0% { transform: translateY(0); }
100% { transform: translateY(-50%); }
}
/* 响应式 */
@media (max-width: 1200px) {
.dashboard {
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
}
.kpi-card { grid-column: 1 / -1; }
.live-feed { grid-column: 1 / -1; }
}
6.3 完整的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>
<link rel="stylesheet" href="./css/dashboard.css">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
</head>
<body>
<!-- 顶部标题 -->
<div class="header">
<h1>🏢 智慧运营监控中心</h1>
<div class="time" id="currentTime"></div>
</div>
<!-- 主内容区 -->
<div class="dashboard">
<!-- 营收趋势图 -->
<div class="card">
<div class="card-title">📈 月度营收趋势</div>
<div id="revenueChart" class="chart-container"></div>
<div class="corner-bl"></div>
<div class="corner-br"></div>
</div>
<!-- 核心指标 -->
<div class="card kpi-card">
<div class="kpi-item">
<div class="kpi-value" id="kpi-revenue">1,289万</div>
<div class="kpi-label">累计营收</div>
<div class="kpi-change up">↑ 12.5% 同比</div>
</div>
<div class="kpi-item">
<div class="kpi-value" id="kpi-orders">8,923</div>
<div class="kpi-label">总订单数</div>
<div class="kpi-change up">↑ 8.3% 同比</div>
</div>
<div class="kpi-item">
<div class="kpi-value" id="kpi-users">3.2万</div>
<div class="kpi-label">活跃用户</div>
<div class="kpi-change down">↓ 2.1% 同比</div>
</div>
<div class="kpi-item">
<div class="kpi-value" id="kpi-rate">96.8%</div>
<div class="kpi-label">满意度</div>
<div class="kpi-change up">↑ 1.2% 同比</div>
</div>
</div>
<!-- 区域分布地图 -->
<div class="card">
<div class="card-title">🗺️ 区域销售分布</div>
<div id="mapChart" class="chart-container"></div>
<div class="corner-bl"></div>
<div class="corner-br"></div>
</div>
<!-- 产品销售排行 -->
<div class="card">
<div class="card-title">🏆 产品销售排行 TOP10</div>
<div id="productChart" class="chart-container"></div>
<div class="corner-bl"></div>
<div class="corner-br"></div>
</div>
<!-- 实时动态流 -->
<div class="card live-feed">
<div class="card-title">⚡ 实时运营动态</div>
<div class="feed-list" id="feedList">
<!-- JS动态生成 -->
</div>
</div>
<!-- 用户画像饼图 -->
<div class="card">
<div class="card-title">👥 用户画像分布</div>
<div id="userChart" class="chart-container"></div>
<div class="corner-bl"></div>
<div class="corner-br"></div>
</div>
</div>
<script src="./js/request.js"></script>
<script src="./js/charts/revenueChart.js"></script>
<script src="./js/charts/mapChart.js"></script>
<script src="./js/charts/productChart.js"></script>
<script src="./js/charts/userChart.js"></script>
<script src="./js/charts/feed.js"></script>
<script src="./js/app.js"></script>
</body>
</html>
6.4 核心图表JS实现
营收趋势折线图 (revenueChart.js)
/**
* 营收趋势折线图
* 重点展示:坐标轴配置、数据格式化处理
*/
const revenueChart = echarts.init(document.getElementById('revenueChart'));
// 模拟真实数据(实际项目应该从API获取)
const revenueData = {
months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
revenue: [86, 95, 112, 108, 125, 132, 145, 138, 156, 162, 175, 189],
cost: [62, 68, 75, 72, 82, 88, 95, 90, 102, 108, 115, 125],
profit: [24, 27, 37, 36, 43, 44, 50, 48, 54, 54, 60, 64]
};
const revenueOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(11,18,30,0.9)',
borderColor: '#1e88e5',
textStyle: { color: '#fff', fontSize: 13 },
// 自定义formatter,解决数据显示问题
formatter: function(params) {
let html = `<div style="font-weight:bold;margin-bottom:5px;">${params[0].name}</div>`;
params.forEach(item => {
// 数值格式化:大于10000显示为万
let value = item.value;
let unit = value >= 10000 ? '万' : '';
html += `<div style="display:flex;justify-content:space-between;min-width:120px;">
<span>${item.marker} ${item.seriesName}</span>
<span style="font-weight:bold;">${value}${unit}</span>
</div>`;
});
return html;
}
},
legend: {
data: ['营收', '成本', '利润'],
top: 5,
textStyle: { color: '#8899aa', fontSize: 12 },
itemWidth: 15,
itemHeight: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true // 关键:确保标签不被裁剪
},
xAxis: {
type: 'category',
boundaryGap: false,
data: revenueData.months,
axisLine: {
lineStyle: { color: '#2a4a6b', width: 1 }
},
axisLabel: {
color: '#8899aa',
fontSize: 11,
interval: 0, // 不省略任何标签
rotate: 0
},
axisTick: {
show: false
}
},
yAxis: {
type: 'value',
name: '金额(万元)',
nameTextStyle: {
color: '#8899aa',
fontSize: 11,
padding: [0, 0, 0, -30] // 调整名称位置
},
axisLine: {
lineStyle: { color: '#2a4a6b', width: 1 }
},
axisLabel: {
color: '#8899aa',
fontSize: 11,
// 关键:formatter处理科学计数法问题
formatter: function(value) {
if (value >= 10000) {
return (value / 10000).toFixed(1) + 'w';
}
return value;
}
},
splitLine: {
lineStyle: {
color: 'rgba(42,74,107,0.3)',
type: 'dashed'
}
}
},
series: [
{
name: '营收',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6,
data: revenueData.revenue,
itemStyle: { color: '#1e88e5' },
lineStyle: { width: 3, color: '#1e88e5' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(30,136,229,0.3)' },
{ offset: 1, color: 'rgba(30,136,229,0.02)' }
])
},
// 数据标签
label: {
show: true,
position: 'top',
color: '#64b5f6',
fontSize: 10,
formatter: '{c}'
}
},
{
name: '成本',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 4,
data: revenueData.cost,
itemStyle: { color: '#ff9800' },
lineStyle: { width: 2 }
},
{
name: '利润',
type: 'line',
smooth: true,
symbol: 'diamond',
symbolSize: 6,
data: revenueData.profit,
itemStyle: { color: '#4caf50' },
lineStyle: { width: 2, type: 'dashed' }
}
],
// 数据缩放组件
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
start: 0,
end: 100,
handleSize: '80%',
handleStyle: { color: '#1e88e5' }
}
]
};
revenueChart.setOption(revenueOption);
// 响应式
window.addEventListener('resize', () => {
revenueChart.resize();
});
产品销售排行柱状图 (productChart.js)
/**
* 产品销售排行柱状图
* 重点展示:横向柱状图、渐变填充、标签防重叠
*/
const productChart = echarts.init(document.getElementById('productChart'));
const productData = {
names: ['智能手表', '无线耳机', '平板电脑', '智能手机', '笔记本电脑', '智能音箱', '运动手环', '充电宝', '数据线', '手机壳'],
values: [892, 756, 634, 523, 412, 389, 345, 298, 267, 234]
};
const productOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(11,18,30,0.9)',
borderColor: '#1e88e5',
textStyle: { color: '#fff' },
formatter: function(params) {
const data = params[0];
return `${data.name}<br/><span style="color:#64b5f6">销量:</span><b>${data.value}</b> 台`;
}
},
grid: {
left: '3%',
right: '10%',
bottom: '3%',
top: '5%',
containLabel: true
},
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#2a4a6b' } },
axisLabel: {
color: '#8899aa',
fontSize: 10,
// 关键:防止Y轴标签变成科学计数法
formatter: function(value) {
return value >= 1000 ? (value / 1000).toFixed(1) + 'k' : value;
}
},
splitLine: { lineStyle: { color: 'rgba(42,74,107,0.2)' } }
},
yAxis: {
type: 'category',
data: productData.names.reverse(), // 让第一名在顶部
inverse: true,
axisLine: { lineStyle: { color: '#2a4a6b' } },
axisLabel: {
color: '#ccc',
fontSize: 12,
// 关键:防止标签过长被截断
interval: 0,
formatter: function(value) {
return value.length > 6 ? value.substring(0, 6) + '...' : value;
}
},
axisTick: { show: false }
},
series: [{
type: 'bar',
data: productData.values.reverse(),
barWidth: '60%',
itemStyle: {
borderRadius: [0, 4, 4, 0],
color: function(params) {
// 渐变色,根据排名变化
const colors = [
['#ff6b6b', '#ee5a5a'], // 第1名红色
['#ff9f43', '#ee8e32'], // 第2名橙色
['#ffd93d', '#ee c82c'], // 第3名黄色
['#1e88e5', '#1565c0'], // 其他蓝色
['#1e88e5', '#1565c0']
];
const index = params.dataIndex;
const colorKey = index < 3 ? index : 3;
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: colors[colorKey][0] },
{ offset: 1, color: colors[colorKey][1] }
]);
}
},
// 关键:在柱子末端显示数值,防止被遮挡
label: {
show: true,
position: 'right',
color: '#fff',
fontSize: 11,
formatter: '{c}'
},
// 圆角阴影效果
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(30,136,229,0.5)'
}
}
}]
};
productChart.setOption(productOption);
window.addEventListener('resize', () => {
productChart.resize();
});
用户画像饼图 (userChart.js)
/**
* 用户画像饼图
* 重点展示:图例滚动、标签引导线、百分比格式化
*/
const userChart = echarts.init(document.getElementById('userChart'));
const userData = [
{ value: 35, name: '18-24岁', itemStyle: { color: '#1e88e5' } },
{ value: 28, name: '25-34岁', itemStyle: { color: '#4caf50' } },
{ value: 18, name: '35-44岁', itemStyle: { color: '#ff9800' } },
{ value: 12, name: '45-54岁', itemStyle: { color: '#9c27b0' } },
{ value: 7, name: '55岁以上', itemStyle: { color: '#f44336' } }
];
const userOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(11,18,30,0.9)',
borderColor: '#1e88e5',
textStyle: { color: '#fff' },
formatter: '{b}<br/>占比:{d}%<br/>人数:{c}人'
},
legend: {
type: 'scroll', // 关键:图例滚动,防止标签过多
orient: 'vertical',
right: 10,
top: 20,
bottom: 20,
textStyle: {
color: '#8899aa',
fontSize: 12
},
pageIconColor: '#1e88e5',
pageInactiveIconColor: '#2a4a6b',
pageTextStyle: { color: '#8899aa' }
},
series: [{
type: 'pie',
radius: ['40%', '70%'],
center: ['40%', '50%'],
avoidLabelOverlap: true, // 关键:防止标签重叠
itemStyle: {
borderRadius: 6,
borderColor: '#0b121e',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{d}%',
color: '#ccc',
fontSize: 11,
// 关键:引导线样式
borderColor: '#2a4a6b',
borderWidth: 1
},
labelLine: {
length: 15,
length2: 10,
smooth: true,
lineStyle: { color: '#2a4a6b' }
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold'
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0,0,0,0.5)'
}
},
data: userData
}]
};
userChart.setOption(userOption);
window.addEventListener('resize', () => {
userChart.resize();
});
6.5 工具函数封装 (request.js)
/**
* 请求工具函数封装
* 统一处理编码、错误、超时
*/
const request = {
/**
* GET请求
* @param {string} url - 请求地址
* @param {object} params - 查询参数
* @param {object} options - 额外配置
*/
async get(url, params = {}, options = {}) {
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
const fullUrl = queryString ? `${url}?${queryString}` : url;
try {
const response = await fetch(fullUrl, {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain, */*; charset=utf-8',
'Content-Type': 'application/json; charset=utf-8'
},
...options
});
// 关键:检查响应编码
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('charset')) {
console.warn(`警告: 响应头缺少charset声明,URL: ${fullUrl}`);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const text = await response.text();
// 关键:手动验证UTF-8编码正确性
try {
return JSON.parse(text);
} catch (e) {
console.error('JSON解析失败,原始响应:', text.substring(0, 200));
throw new Error('数据格式错误:响应不是有效的JSON');
}
} catch (error) {
console.error('请求失败:', error.message);
throw error;
}
},
/**
* POST请求
*/
async post(url, data = {}, options = {}) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
...options.headers
},
body: JSON.stringify(data),
...options
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('POST请求失败:', error);
throw error;
}
},
/**
* 批量请求
*/
async all(promises) {
return Promise.all(promises);
}
};
// 全局暴露
window.request = request;
6.6 主逻辑入口 (app.js)
/**
* 主应用逻辑
* 整合所有图表,处理数据刷新
*/
(function() {
'use strict';
// 系统时间更新
function updateTime() {
const now = new Date();
const timeStr = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
document.getElementById('currentTime').textContent = timeStr;
}
// 初始化时间显示
updateTime();
setInterval(updateTime, 1000);
// 模拟实时数据刷新
function updateKPI() {
// 实际项目应该从这里调用API
// const data = await request.get('/api/dashboard/kpi');
// 模拟数据波动
const revenue = (1289 + Math.random() * 10 - 5).toFixed(0);
const orders = Math.floor(8923 + Math.random() * 20 - 10);
const users = (3.2 + Math.random() * 0.2 - 0.1).toFixed(1);
const rate = (96.8 + Math.random() * 0.4 - 0.2).toFixed(1);
document.getElementById('kpi-revenue').textContent = revenue + '万';
document.getElementById('kpi-orders').textContent = orders.toLocaleString();
document.getElementById('kpi-users').textContent = users + '万';
document.getElementById('kpi-rate').textContent = rate + '%';
}
// 定时刷新KPI
setInterval(updateKPI, 30000);
console.log('🎯 可视化大屏初始化完成');
console.log('💡 提示:坐标轴问题已处理,编码已统一为UTF-8');
})();
七、部署指南:从本地到生产环境
7.1 本地开发环境
# 1. 克隆项目
git clone https://github.com/yourname/echarts-dashboard.git
cd echarts-dashboard
# 2. 安装依赖(如果需要)
npm install
# 3. 启动开发服务器(用Live Server或简单HTTP服务)
npx serve public -l 3000
# 或者用Python
cd public
python3 -m http.server 3000
7.2 Nginx部署(生产环境推荐)
# /etc/nginx/sites-available/echarts-dashboard
server {
listen 80;
server_name your-domain.com;
root /var/www/echarts-dashboard/public;
index index.html;
# 关键:确保所有响应都用UTF-8
charset utf-8;
# 关键:设置正确的Content-Type
types {
text/html html htm;
text/css css;
application/javascript js;
application/json json;
image/svg+xml svg;
}
# Gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
# 缓存策略
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML文件不缓存(防止更新不及时)
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# API代理(如果有后端)
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 关键:代理也要传charset
proxy_set_header Accept-Charset "utf-8";
}
# 错误页面
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
# 部署命令
sudo cp /etc/nginx/sites-available/echarts-dashboard /etc/nginx/sites-enabled/
sudo nginx -t # 测试配置
sudo systemctl reload nginx
7.3 Docker部署(适合团队协作)
# Dockerfile
FROM nginx:alpine
# 复制项目文件
COPY public/ /usr/share/nginx/html/
# 复制nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 80
# 启动
CMD ["nginx", "-g", "daemon off;"]
# 构建镜像
docker build -t echarts-dashboard .
# 运行容器
docker run -d \
--name dashboard \
-p 80:80 \
-e NODE_ENV=production \
echarts-dashboard
# 查看日志
docker logs -f dashboard
7.4 常见问题排查清单
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 坐标轴文字不显示 | 颜色与背景相同 | 检查 axisLabel.color |
| 中文显示乱码 | JSON文件编码非UTF-8 | 用VS Code转换编码 |
| 数字显示科学计数法 | 数值过大 | 使用 formatter 自定义 |
| 标签重叠 | 数据点过多 | interval 或 rotate |
| 图表不响应 | 未监听resize | 添加 window.addEventListener('resize') |
| 数据不更新 | 缓存问题 | 请求URL加时间戳参数 |
| 加载失败 | 跨域限制 | 配置Nginx代理或CORS |
八、源码获取与进一步学习
8.1 完整项目结构
echarts-dashboard/
├── public/
│ ├── css/
│ │ └── dashboard.css
│ ├── js/
│ │ ├── charts/
│ │ │ ├── revenueChart.js
│ │ │ ├── mapChart.js
│ │ │ ├── productChart.js
│ │ │ └── userChart.js
│ │ ├── utils/
│ │ │ └── request.js
│ │ └── app.js
│ ├── data/
│ │ ├── sales.json
│ │ └── users.json
│ └── index.html
├── backend/
│ ├── server.js
│ └── routes/
│ └── dashboard.js
├── package.json
├── Dockerfile
├── nginx.conf
└── README.md
8.2 在线演示地址
如果你不想本地跑,可以直接看在线效果:
- 本地预览:
http://localhost:3000 - GitHub源码:
https://github.com/yourname/echarts-dashboard
8.3 推荐学习资源
- ECharts官方文档(最权威):https://echarts.apache.org/zh/index.html
- ECharts示例-gallery:https://echarts.apache.org/examples/zh/index.html
- AntV G2(阿里出品,适合更复杂的图表)
- D3.js(底层库,自由度最高,学习曲线陡)
8.4 最后的小建议
做数据可视化,先跑通,再优化。不要一上来就追求完美的UI,先把数据接进来、图表画出来,然后再调颜色、加动画、做交互。
坐标轴不显示、中文乱码这些问题,本质上都是配置细节没到位,多查文档、多看示例,遇到具体报错截图搜索,基本上都能解决。
有问题随时交流,祝你的大屏项目顺利上线!
