
简介中国大学生计算机设计大赛数据可视化赛道国奖项目的完整源码包面向备赛学生、课程设计开发者以及对前端可视化感兴趣的入门与进阶人群可用于快速理解并复刻一套可运行的数据可视化作品。压缩包共69个文件以53个JavaScript脚本、4个HTML页面、3个CSS样式文件为主另含Python数据处理脚本、PyCharm工程配置和静态图片资源整体仅683KB目录按templates、static等模块划分清晰易读。HTML负责页面结构CSS负责视觉风格JS结合ECharts完成图表渲染与交互Python脚本承担数据读取与预处理组成一个从数据到界面的完整链路。资源内含中国、世界、省份等多维度可视化页面既能学习多页面数据看板的组织方式也可借鉴获奖项目的配色、布局与交互细节较快提升作品完整度和竞赛竞争力。目前已有1249人学习下载值得数据可视化赛道备赛者细细拆解。1. 国奖疫情可视化源码拆解Flask ECharts 的完整数据链路很多学生作品在比赛答辩时能撑住页面好看但一被评委追问数据是怎么更新的是写死在 HTML 里还是接口拉的就语塞。这个拿到国三的疫情可视化项目答案非常明确页面是模板数据走接口。压缩包里的05计算机设计大赛.py是 Flask 后端templates/下四个 HTML 是前端页面static/里放着 ECharts 和地图 GeoJSON。整套代码把采集-存储-接口-渲染串成了一条完整的链路。适合准备中国大学生计算机设计大赛数据可视化赛道、想从静态 HTML 跳转到前后端动态加载的选手。想改成大学生消费行为数据可视化或者做成企业级数据可视化看板这套源码结构也能直接套。2. Flask 后端主程序路由、模板渲染与数据接口的设计2.1 为什么是这个技术栈比赛中很多作品用的是HTML 一个 JSON 文件 ECharts这能出图但撑不起交互和更新。这个项目选择 Flask 做后端核心原因是轻。Django 的 ORM、Admin 和中间件在单机演示场景里全是负担而 Flask 用一个入口文件就能描述完整的数据流。一个.py文件管路由、管模板渲染、管 API 返回压缩包体积小答辩时也好讲。前端可以只通过fetch拿数据HTML 里不写死任何数值这也是答辩时能讲出动态加载的关键依据。2.2 解压后的目录结构与 Flask 约定拿到 zip 解压后你会看到这样的结构我先标出和 Flask 关系最大的部分. ├── 05计算机设计大赛.py # Flask 主程序整个后端 ├── templates/ │ ├── home.html # 首页项目说明 导航 │ ├── China_Web.html # 中国地图可视化 │ ├── World_Web.html # 世界地图可视化 │ └── Province_Web.html # 省份下钻可视化 ├── static/ │ ├── js/ # echarts.js、自定义脚本 │ ├── css/ # 页面样式 │ └── images/ # 背景和封面图 └── .gitignoretemplates/和static/是 Flask 的两个约定目录前者放render_template要渲染的 HTML后者放浏览器直接访问的静态资源。你不需要配置任何东西Flask 默认就会去这两个目录找文件。数据 JSON 如果没出现在目录树里通常在static/data/下或者在项目根目录你解压后全局搜一下.json文件就能定位到。2.3 主程序路由与数据接口实现05计算机设计大赛.py的核心逻辑可以收敛成下面这段骨架代码注释对应原项目里最基础的四个页面from flask import Flask, render_template, jsonify import json app Flask(__name__) # 页面路由渲染模板 app.route(/) def home(): return render_template(home.html) app.route(/china) def china(): return render_template(China_Web.html) app.route(/world) def world(): return render_template(World_Web.html) app.route(/province) def province(): return render_template(Province_Web.html) # 数据接口前端从这份 JSON 里动态取数 app.route(/api/covid/data) def covid_data(): with open(static/data/covid_data.json, r, encodingutf-8) as f: data json.load(f) return jsonify(data) if __name__ __main__: app.run(host127.0.0.1, port5050, debugTrue)这里值得讲清楚三件事。第一render_template(China_Web.html)会自动去templates/目录拼接路径所以你写相对路径时不需要带templates/前缀。第二jsonify(data)会把 Python 的列表和字典转成 JSON 响应并且自动设置Content-Type: application/json前端res.json()拿到的就是结构化数据。第三debugTrue能在开发时热重载代码但比赛现场演示如果开启一旦代码有改动服务会重启可能打断演示节奏我一般会在展示前把它改成False。主程序里还有一处细节app.run的host和port参数。调试时127.0.0.1:5050就够了但如果想把屏幕投到另一台设备、或者让评委用手机访问看板就要把host改成0.0.0.0。参数作用建议值host监听地址本机调试用127.0.0.1局域网演示用0.0.0.0port服务端口5000 或 5050避开 80 和 8080 的常见占用debug热重载与错误页开发True演示False2.4 数据量变大以后从 JSON 切到 MySQL当数据从几百条变成几万条继续用json.load读文件会越来越慢而且每次服务重启都要重新加载。常见做法是换成 MySQL用 PyMySQL 做查询。如果原项目代码里带了数据库部分写法大致是这样的import pymysql from pymysql.cursors import DictCursor def query_covid_data(): conn pymysql.connect( hostlocalhost, userroot, password123456, databasecovid_db, charsetutf8mb4 ) with conn.cursor(DictCursor) as cursor: cursor.execute( SELECT province, confirmed FROM covid_stats WHERE date (SELECT MAX(date) FROM covid_stats) ) rows cursor.fetchall() conn.close() return rowsDictCursor让每行结果以字典形式返回rows直接就能被jsonify序列化。把查询结果塞进接口后前端代码一行都不用改这就是接口层带来的解耦价值。你切换数据源时只需要保证返回结构里的name和value字段名不变ECharts 那边就能稳定渲染。3. ECharts 数据可视化实现三个模板如何共用一套数据接口3.1 三张页面的定位差异templates/里三个可视化页面不是三个独立项目是同一套数据接口的三种展示角度。China_Web.html是全局视角展示全国各省疫情分布World_Web.html换成世界地图展示各国数据Province_Web.html是省域下钻点击某个省看到市级数据。三者的 ECharts 配置高度相似最核心的差异在map参数。China_Web.html里map: chinaWorld_Web.html里map: worldProvince_Web.html里则是map: 北京或map: huBei这类注册名。地图名不是随便起的它必须等于你用registerMap注册时给的名字所以先看代码里地图是怎么注册的再决定页面里写什么值。3.2 数据接口调用与 setOption 参数页面里的取数和渲染逻辑我整理成一份可复用的写法async function renderMap(containerId, mapName) { const res await fetch(/api/covid/data); const data await res.json(); const chart echarts.init(document.getElementById(containerId)); chart.setOption({ tooltip: { trigger: item, formatter: (params) ${params.name}${params.value ?? 0} 例 }, visualMap: { min: 0, max: data.maxConfirmed || 100, text: [高, 低], inRange: { color: [#e0f3f8, #fee08b, #d73027] } }, series: [{ type: map, map: mapName, roam: true, data: data.list }] }); } renderMap(chart-main, china);这段代码让三个页面可以共用一个函数差别只是传入的mapName。重点说几个参数tooltip.formatter控制了鼠标悬停时的信息格式params.value ?? 0防止数据缺失时显示undefinedvisualMap的min和max直接决定颜色深浅的映射区间如果最大值是 100 但你写成 1000整个地图全部是浅色看不出梯度inRange.color是一个颜色数组低值到高值从左到右渐变series.roam开启缩放拖拽这个在大屏演示时很实用。fetch 返回的data.list必须是[{ name: 湖北, value: 67800 }, ...]的结构name要跟地图 GeoJSON 里的行政区域名完全一致。3.3 地图注册顺序这个坑比赛现场最容易踩ECharts 5 以后官方包不再内置中国地图和世界地图 GeoJSON只引入echarts.min.js还不够必须先把地图数据注册进去fetch(./static/js/map/china.json) .then(res res.json()) .then(geoJson { echarts.registerMap(china, geoJson); });registerMap的两个参数第一个是你在series.map里引用的名字第二个是 GeoJSON 对象。这个调用是异步的一个常见报错是map china not exists原因就是setOption跑在了registerMap前面。我一般会包一层async函数先 fetch 地图再 fetch 数据最后setOption保证时序。提示registerMap的名字一旦定下来页面map参数和注册名必须完全一致大小写都不能错。比赛现场如果地图空白优先检查这一步。3.4 三个页面的配置差异与复用建议把三个页面的差异放在一起对比方便你改自己的项目页面map名称数据维度附加组件China_Web.htmlchina各省累计确诊visualMap、tooltipWorld_Web.htmlworld各国累计确诊按大洲过滤、dataZoomProvince_Web.html省名注册的地图各市/区县确诊返回上一级按钮如果你要复用建议把renderMap抽到static/js/common.js三个页面各自只保留初始化容器和地图名。静态资源走static/js路径浏览器缓存命中率也会更高。这一层抽完整个项目的页面代码量能砍掉一半答辩时讲组件复用也更有说服力。4. 从 zip 源码到可运行看板本地复现与高频报错排查4.1 搭建运行环境拿到zip之后第一步不是双击打开 HTML而是先把后端跑起来。这个项目依赖 Python 3.8从压缩包里的cpython-38缓存能看出来建议用虚拟环境隔离依赖避免和你本机其他项目的包打架unzip XX可视化项目源码.zip cd 项目目录 python3 -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install flask pymysql python 05计算机设计大赛.py启动后终端会出现Running on http://127.0.0.1:5050浏览器直接访问这个地址即可。Windows 下解压用右键菜单或Expand-Archive -Path XX.zip -DestinationPath .后面命令除虚拟环境激活脚本路径不同外其余一致。这里有几个容易忽略的点。第一PyMySQL 只在代码里真的写了数据库连接时才需要装如果原项目纯读 JSON装 Flask 就够了。第二__pycache__目录和05计算机设计大赛.cpython-38.pyc是 Python 运行时的字节码缓存删除不影响任何功能提交 zip 前建议清掉体积更小。第三.idea/是 PyCharm 的工作区配置用 VS Code 或别的 IDE 不必保留。注意不要直接双击打开templates/下的 HTML页面里的fetch(/api/...)会因为没有后端服务而全部失败必须通过 Flask 启动后访问http://127.0.0.1:5050。4.2 高频报错定位表复现和比赛阶段我遇到频率最高的报错集中在这几类现象直接原因处理动作TemplateNotFound: home.htmlHTML 不在templates/目录下检查文件路径Flask 只认templates/JSONDecodeError数据 JSON 编码不是 UTF-8用open(path, encodingutf-8)读文件map china not existsGeoJSON 没注册或注册顺序靠后先registerMap再setOption端口被占用OSError: [Errno 48]5050 已被其他进程占换端口或lsof -i:5050看占用进程地图显示空白数据name与 GeoJSON 行政名不一致打印前后端 name 字段逐项对齐表格里第一行其实是目录约定问题很多选手把 zip 解压后直接改了 HTML 文件名或者把home.html移出了templates/Flask 就会立刻报TemplateNotFound。解决方式也很简单保持render_template里的名字和你实际文件一致目录层级别动。第二行 JSON 编码问题在 Windows 上特别常见记事本另存为时默认可能是 GBK保存成 UTF-8 就能规避。Windows 上查看端口占用用netstat -ano | findstr :5050拿到 PID 后在任务管理器里结束进程。4.3 把项目数据换成你自己的数据替换数据是最常见的改造难点往往不是爬虫或导入而是格式对齐。后端接口要返回的是[ { name: 湖北, value: 67800 }, { name: 广东, value: 1500 } ]其中name必须和地图 GeoJSON 里的name完全一致。比如中国地图的 GeoJSON 里通常是湖北而非湖北省如果 JSON 文件里写的是全称地图上这个区域就会空着。遇到这种情况我一般先写一段 Python 脚本做名称映射import csv import json name_map {湖北省: 湖北, 广东省: 广东} # 按实际数据补全 result [] with open(data.csv, r, encodingutf-8) as f: for row in csv.DictReader(f): std_name name_map.get(row[province], row[province]) result.append({name: std_name, value: int(row[confirmed])}) with open(static/data/covid_data.json, w, encodingutf-8) as f: json.dump({list: result, maxConfirmed: max(x[value] for x in result)}, f, ensure_asciiFalse)这个脚本同时算出了maxConfirmed前端visualMap.max就可以直接引用不用担心颜色区间失真。运行完刷新页面新数据就生效了后端代码一行没动。这就是接口层设计带来的好处数据源替换被限制在 JSON 文件和转换脚本之间不会波及渲染层。5. 进阶把比赛作品改造成企业级数据可视化看板5.1 从一次性渲染到定时增量刷新比赛页面的一次setOption在真实业务里不够用因为数据会持续变化。企业级数据可视化看板的常见做法是前端轮询后端保持接口稳定不变前端每隔一个固定周期拉取最新数据async function refreshChart() { const res await fetch(/api/covid/data); const data await res.json(); myChart.setOption({ series: [{ data: data.list }] }); } setInterval(refreshChart, 60000);这里有一个多数教程不会讲清的细节setOption默认是 merge 模式你只传series.data它会自动对比新旧数据并更新图表不会闪也不会打断用户正在进行的缩放。千万不要加true作为第二个参数那是notMerge模式会清空地图状态导致缩放位置重置体验很差。5.2 接口聚合与首屏加载另外可以把分散的接口合并成一个减少请求次数。比如返回一个data对象里面同时包含中国看板、世界看板的字段前端一次请求拿回全部。接口合并之后首屏加载时可以进一步减小白屏时间。验证刷新是否生效最直接的办法是打开浏览器控制台的 Network 面板筛选 XHR 请求观察/api/covid/data这条请求是否每 60 秒出现一次。把setInterval(refreshChart, 60000)改成setInterval(refreshChart, 30000)刷新频率会立刻翻倍——你可以用这个实验直观感受轮询间隔对后端接口压力的影响。本文还有配套的精品资源点击获取