一、项目概述
技术栈:
- 可视化库:Apache ECharts 5.5(CDN 引入)
- 页面骨架:原生 HTML + CSS Grid + Flexbox
- 边框装饰:手写 CSS/SVG(仿 DataV 边框盒,无需引入 DataV 依赖)
- 部署方式:单文件,双击即可运行,无需 Node 环境
为什么不用 Vue3 + DataV?
原博客用 Vue3 +@kjgl77/datav-vue3搭建,工程化更规范。但如果只是要一张能跑起来的大屏,单文件 HTML 有三个优势:
- 双击就能打开,演示、截图、交作业零门槛;
- 没有
npm install和构建流程,改一行刷新即生效; - DataV 的边框盒本质上是 SVG 装饰,用 CSS 二十几行就能仿个七成像。
展示效果:
二、数据准备
大屏的灵魂是数据。我全部采用可追溯的公开数据,并在页面页脚标注来源——这一点在课程作业和正式汇报里都非常加分。
| 指标 | 数据 | 来源 |
|---|---|---|
| 在线外卖市场规模 | 2020:7463 亿 → 2025:22850 亿元(+24.47%) | 网经社《2025 年度中国数字生活市场数据报告》 |
| 外卖用户规模 | 2020:4.72 亿 → 2025:6.30 亿人 | CNNIC《中国互联网络发展状况统计报告》 |
| 2025 全行业补贴 | 1730 亿元(三大平台累计) | 公开报道 |
| 日均订单 | 战前 8000 万单 → 峰值 2.5 亿单 | 公开报道 |
| 堂食/外卖占比 | 外卖 12.9%(2019)→ 24.1%(2025E) | 红餐产业研究院 |
两点诚信声明(建议大家都写上):
- 2026 年为预测值,在图例和页脚明确标注“2026E”;
- 省份“优惠力度指数”为示意数据,页面页脚写明“非官方统计”,避免把演示数据当成真实统计。
对应到代码里,就是把数据集中定义在一个区块,方便替换成自己的数据:
/* ---------- 通用常量 ---------- */ const YEARS = ['2020', '2021', '2022', '2023', '2024', '2025', '2026E']; const AXIS_C = '#00bfff'; // 霓虹蓝坐标轴 const SPLIT_C = 'rgba(0, 150, 255, 0.12)'; const PALETTE = ['#00DDFF', '#37A2FF', '#80FFA5', '#FFBF00', '#FF0087', '#9B6BFF']; /* 核心数据集(替换成自己的业务数据即可) */ const MARKET = [7463, 9740, 12470, 15263, 18357, 22850, 24800]; // 市场规模 亿元 const GROWTH = [30.51, 30.51, 28.02, 22.39, 20.27, 24.47, 8.53]; // 同比增速 % const USERS = [4.72, 5.44, 5.21, 5.45, 5.92, 6.30, 6.55]; // 用户规模 亿人 const ORDERS = [4200, 5200, 6100, 7000, 8000, 16500, 12000]; // 日均订单 万单三、环境搭建
不需要脚手架,新建一个文件夹放两个文件即可:
takeout-dashboard/ ├── index.html # 大屏本体(全部代码都在这一个文件里) └── preview.png # 效果截图在<body>末尾引入 ECharts。这里做了双 CDN 容错——jsdelivr 挂了自动切 unpkg,避免演示现场白屏:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script> <script> if (typeof echarts === 'undefined') { document.write('<script src="https://unpkg.com/echarts@5.5.0/dist/echarts.min.js"><\/script>'); } </script>四、代码展示
4.1 页面骨架:三栏布局
版式完全对齐原博客:顶部标题栏 +3fr : 6fr : 3fr三栏,左右各叠 3 个面板,中间是“指标卡 + 中国地图”。
<div id="app"> <header class="top"> ... </header> <main class="bottom"> <div class="col left"> <section class="panel"> <span class="c c-tl"></span><span class="c c-tr"></span> <span class="c c-bl"></span><span class="c c-br"></span> <div class="panel-hd"><i></i>在线外卖市场规模(亿元)<span class="tag">网经社口径</span></div> <div class="panel-bd"><div id="c_market" class="chart"></div></div> </section> </div> <div class="col center"> <div class="kpis"> ... </div> <section class="panel"> <div class="panel-hd"><i></i>各省外卖消费热度与优惠力度指数</div> <div class="panel-bd"><div id="c_map" class="chart"></div></div> </section> </div> <div class="col right"> ... </div> </main> <div class="foot"> ... </div> </div>对应的 CSS 只需要几行 Grid:
#app { display: flex; flex-direction: column; width: 100vw; height: 100vh; } /* 主体三栏 3fr : 6fr : 3fr / .bottom { flex: 1; display: grid; grid-template-columns: 3fr 6fr 3fr; gap: 10px; min-height: 0; } .col { display: grid; gap: 10px; min-height: 0; } .left, .right { grid-template-rows: repeat(3, 1fr); } / 左右各叠 3 块 / .center { grid-template-rows: 118px 1fr; } / 指标卡固定高,地图占满剩余 */关键点:
min-height: 0。Grid 子项默认min-height: auto,内容一撑开就溢出容器,图表会被压扁或撑破布局。加上它,面板高度才能被严格约束。
4.2 科技风样式:手写仿 DataV 边框盒
DataV 的dv-border-box-1本质就是“四角亮色 L 形 + 半透明面板 + 内发光”。用 CSS 伪元素 + 4 个角标 span 即可复刻:
.panel { position: relative; background: linear-gradient(180deg, rgba(8,40,96,.42), rgba(3,16,44,.62)); box-shadow: inset 0 0 22px rgba(0,110,255,.28), 0 0 0 1px rgba(0,160,255,.16); border-radius: 4px; display: flex; flex-direction: column; } /* 四角 L 形亮边 */ .c { position: absolute; width: 13px; height: 13px; z-index: 2; } .c-tl { top: -1px; left: -1px; border-top: 2px solid #23d8ff; border-left: 2px solid #23d8ff; box-shadow: -1px -1px 8px rgba(35,216,255,.6); } .c-tr { top: -1px; right: -1px; border-top: 2px solid #23d8ff; border-right: 2px solid #23d8ff; box-shadow: 1px -1px 8px rgba(35,216,255,.6); } .c-bl { bottom: -1px; left: -1px; border-bottom: 2px solid #23d8ff; border-left: 2px solid #23d8ff; box-shadow: -1px 1px 8px rgba(35,216,255,.6); } .c-br { bottom: -1px; right: -1px; border-bottom: 2px solid #23d8ff; border-right: 2px solid #23d8ff; box-shadow: 1px 1px 8px rgba(35,216,255,.6); }页面背景用网格底纹 + 顶部径向光晕,是大屏“科技感”的来源:
body { background: #000; background-image: radial-gradient(ellipse at 50% 0%, rgba(0,110,255,.18), transparent 60%), linear-gradient(rgba(0,178,255,.045) 1px, transparent 1px), linear-gradient(90deg, rgba(0,178,255,.045) 1px, transparent 1px); background-size: 100% 100%, 40px 40px, 40px 40px; }标题的“渐变字 + 霓虹发光”:
.top-title { font-size: 26px; font-weight: 700; letter-spacing: 4px; background: linear-gradient(180deg, #fff 10%, #7ce7ff 55%, #00a2ff 100%); -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: 0 0 18px rgba(0, 200, 255, .55); } .top-title .hl { color: #ffd873; -webkit-text-fill-color: #ffd873; } /* 高亮“外卖优惠” */顶部标题栏效果:
4.3 实时时钟与全屏按钮
原博客在标题栏放了实时时钟,这里用 10 行原生 JS 实现,另加一个全屏按钮方便演示:
function pad(n) { return String(n).padStart(2, '0'); } function tick() { const now = new Date(); document.getElementById('clock').textContent = `${now.getFullYear()}-${pad(now.getMonth()+1)}-${pad(now.getDate())} ` + `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`; } tick(); setInterval(tick, 1000); document.getElementById('fsBtn').addEventListener('click', () => { if (!document.fullscreenElement) document.documentElement.requestFullscreen(); else document.exitFullscreen(); });4.4 图表通用配置:统一配色与 Tooltip
七个图表共用一套视觉语言,抽成公共对象,改主题色只动一处:
const TOOLTIP = { backgroundColor: 'rgba(2, 16, 40, 0.92)', borderColor: '#00bfff', borderWidth: 1, textStyle: { color: '#e6f6ff', fontSize: 12 }, extraCssText: 'box-shadow:0 0 16px rgba(0,180,255,.45);' }; /* 坐标轴统一样式 */ function axis(name) { return { axisLine: { lineStyle: { color: AXIS_C } }, axisLabel: { color: AXIS_C, fontSize: 10 }, axisTick: { show: false }, splitLine: { lineStyle: { color: SPLIT_C } }, name, nameTextStyle: { color: '#6fa8d4', fontSize: 10 } }; } /* 统一注册 + 统一 resize */ const charts = {}; function reg(id, opt) { const inst = echarts.init(document.getElementById(id)); inst.setOption(opt); charts[id] = inst; return inst; } window.addEventListener('resize', () => Object.values(charts).forEach(c => c.resize()));4.5 左上:市场规模(柱状图 + 折线双轴)
一个图讲两个维度:柱子看规模,黄线看增速。柱子用线性渐变,这是大屏质感的关键。
reg('c_market', { color: PALETTE, tooltip: Object.assign({ trigger: 'axis', axisPointer: { type: 'shadow' } }, TOOLTIP), grid: { left: 6, right: 6, top: 34, bottom: 4, containLabel: true }, legend: { data: ['市场规模', '同比增速'], right: 8, top: 0, itemWidth: 12, itemHeight: 7, textStyle: { color: AXIS_C, fontSize: 10 } }, xAxis: Object.assign({ type: 'category', data: YEARS }, axis()), yAxis: [ Object.assign({ type: 'value', name: '亿元' }, axis()), Object.assign({ type: 'value', name: '%', max: 60 }, axis()) ], series: [ { name: '市场规模', type: 'bar', data: MARKET, barWidth: '46%', itemStyle: { borderRadius: [3, 3, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#4dfbff' }, { offset: 0.5, color: '#12b3ff' }, { offset: 1, color: 'rgba(0,80,200,0.25)' } ]) }, label: { show: true, position: 'top', color: '#7fe3ff', fontSize: 9.5 } }, { name: '同比增速', type: 'line', yAxisIndex: 1, data: GROWTH, smooth: true, symbol: 'circle', symbolSize: 6, lineStyle: { width: 2, color: '#FFBF00', shadowColor: '#FFBF00', shadowBlur: 10 }, itemStyle: { color: '#FFBF00', borderColor: '#fff', borderWidth: 1 }, label: { show: true, position: 'top', color: '#FFBF00', fontSize: 9, formatter: '{c}%' }