简介:面向需要完成毕业设计或系统学习Web开发的计算机专业学生,压缩包内是一套基于Python与Django框架的疫情数据可视化分析系统,覆盖数据录入、管理、统计与图表展示等核心功能,支持直接运行与二次扩展。资源共含745个文件,总大小15.81MB,文件类型以Python源码、Vue组件、JavaScript脚本、HTML页面、CSS样式及SQL数据库文件为主。后端代码配合MySQL实现数据存储与查询,前端借助Vue、JavaScript和SVG资源完成交互页面与可视化展示,多个bat脚本则为安装与启动提供了简便入口。压缩包内另附配置教程与开发说明文档,从环境搭建、系统运行到原理实现均有讲解,有助于快速理解Django项目结构及Pandas、NumPy在数据处理中的实际应用。目前已有61人学习,对毕业设计答辩、项目实战练手与简历项目积累都具有不错的参考价值。
1. 打开这份 Django 疫情可视化源码前,先想清楚毕设怎么做
每年三、四月,后台最常出现的提问就是:数据可视化的毕设,数据从哪里来、图表怎么做、Django 怎么和 MySQL 连起来,这三关里至少翻一关。这份 Python + Django 的疫情数据可视化分析系统,就是来解决这套组合拳的。源码里内置了处理好的疫情历史数据,前端用 ECharts 画地图和趋势图,后端用 Django ORM 操作 MySQL,zip 里还附带配置教程和开发说明,照着走就能在本机完整跑起来。接下来我按拿到源码后的实际顺序拆一遍:先看架构,再配环境,然后跑通数据和页面,最后把最容易翻车的几个点提前摆到台面上。适合正在赶毕设的学生、做期末大作业的同学,以及想快速体验 Django 数据可视化完整链路的人。
2. 技术选型与源码结构:Django + MySQL + ECharts 这套组合怎么组织
疫情可视化这个题目,核心词有两个:后端框架和数据可视化。选型是否合理,直接决定了后面开发顺畅程度和答辩时能讲出多少东西。
2.1 为什么选 Django + MySQL + ECharts:答辩有得讲,开发不踩空
Django 属于重量级框架,自带 Admin 后台、ORM 数据库映射、表单校验和 CSRF 防护。对毕设场景来说,它的优势在于「该有的都有」:登录注册、后台管理、数据库操作都不需要从零造轮子。中期检查被问到系统架构,你可以很自然地讲出 MTV 分层,每个模块的职责边界是清晰的,这比 Flask 拼装出来的项目在评分时更有话可说。
MySQL 在存储层配合 Django ORM,基本不需要手写原生 SQL,对于平时不常写复杂查询的同学来说很友好。疫情数据是典型的时间序列加空间维度数据,按省份、日期做聚合统计,MySQL 的索引和分组查询完全扛得住,数据量在十万级以内时性能不会成为瓶颈。
数据可视化前端,ECharts 是当前毕业设计里出现频率最高的方案。它对中文地图支持完善,疫情按省份展示这种需求,用 ECharts 的 map 类型配合 GeoJSON 就能实现;折线图、柱状图、饼图的配置项也直观,照着官方示例替换数据源就能出图。相比 d3 的高门槛和 Highcharts 的授权问题,ECharts 的开源免费与上手成本低,让它在毕设场景里几乎没有替代品。
这套组合还有一层隐藏优势:市场上同类资源最多。遇到「Django 查数据传给前端」「ECharts 地图数据格式怎么组织」这类问题,搜索引擎随便一翻就有大量案例,不至于困在某个细节上两周出不来。
2.2 解压 zip 后的目录结构:先认文件,再动代码
拿到 zip 包后,先不要急着 runserver。打开目录看一遍结构,知道每个文件是干什么的,后面改配置时才不会迷路。源码包里的 Django 项目结构大致是这样组织的:
epidemic_analysis/ ├── manage.py # Django 管理入口 ├── requirements.txt # 第三方依赖清单 ├── config/ # 项目配置模块 │ ├── settings.py # 全局配置 │ ├── urls.py # 根 URL 路由 │ └── wsgi.py ├── apps/ │ ├── epidemic/ # 疫情数据核心应用 │ │ ├── models.py # 数据模型定义 │ │ ├── views.py # 视图函数 / 接口 │ │ ├── urls.py # 应用级路由 │ │ └── services.py # 数据抓取与导入服务 │ └── user/ # 用户登录注册模块 ├── templates/ # HTML 模板 ├── static/ │ ├── css/ js/ │ └── data/ # 疫情历史数据 / 中国地图 GeoJSON └── sql/ # 初始化 SQL 脚本几个关键位置的说明:
| 路径 | 作用 | 通常需要动吗 |
|---|---|---|
| config/settings.py | 数据库连接、静态文件、时区配置 | 需要,改成自己的 MySQL 账号密码 |
| apps/epidemic/models.py | 疫情数据表结构 | 一般不动,除非要加字段 |
| static/data/ | 前端图表要用的数据和地图 GeoJSON | 可选,换成你要展示的区域 |
| sql/ | 初始化数据脚本 | 用命令导入,属于捷径 |
| requirements.txt | Python 依赖版本清单 | 严格按它安装 |
第一次看源码,我建议按这个顺序读:先打开 config/urls.py 看根路由挂载了哪些 app,再进 apps/epidemic/models.py 看表结构,最后回到 views.py 看接口怎么查数据。把这三层串起来,整个项目的请求链路就清楚了。
2.3 数据模型设计:核心表结构与建表语句
疫情可视化系统的数据模型是整个项目的地基。打开 apps/epidemic/models.py,核心模型长这样:
from django.db import models class EpidemicRecord(models.Model): province = models.CharField('省份', max_length=50, db_index=True) city = models.CharField('城市', max_length=50, blank=True) confirmed = models.IntegerField('累计确诊', default=0) suspected = models.IntegerField('疑似', default=0) cured = models.IntegerField('治愈', default=0) dead = models.IntegerField('死亡', default=0) date = models.DateField('统计日期', db_index=True) class Meta: db_table = 'epidemic_record' unique_together = (('province', 'city', 'date'),) def __str__(self): return f'{self.province} {self.date} {self.confirmed}'这段模型的设计逻辑:province 和 city 定位区域层级,confirmed、suspected、cured、dead 对应四个核心指标,date 标记统计日期。db_index=True 是为 province 和 date 加上数据库索引,后续频繁按省份筛选、按日期排序的查询会走索引而不是全表扫描。
unique_together是最值得注意的设计。它声明了「省份 + 城市 + 日期」三者组合唯一,也就是说同一天同一个城市只允许一条记录。数据导入脚本重复执行时,这条约束能挡住重复数据,避免页面上的数字莫名翻倍。
对应 MySQL 里的建表语句,如果是从 sql/ 目录手动导库,看到的应该是这样:
CREATE TABLE epidemic_record ( id INT AUTO_INCREMENT PRIMARY KEY, province VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, confirmed INT DEFAULT 0, suspected INT DEFAULT 0, cured INT DEFAULT 0, dead INT DEFAULT 0, date DATE NOT NULL, UNIQUE KEY uk_province_city_date (province, city, date), KEY idx_province (province), KEY idx_date (date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;手写 SQL 和 Django 模型一一对应,唯一索引、字段类型、字符集都保持同步。这样做的价值在于:当你需要用原生 SQL 做批量导入、或者用可视化工具直接查数时,不会因为两边表结构不一致而报表对不上。
另外还会有一张用户表,用来支撑登录注册功能。这个表结构比较简单,就是 Django 默认的 auth_user 基础上扩展一个昵称字段,不涉及复杂业务,毕设演示时能登录、能区分普通用户和管理员就够用了。
3. 环境搭建与数据导入:把配置教程跑通的最短路径
这一章是动手环节。我先按顺序拆解从解压到首页出现图表完整过程,中途把最容易出错的地方提前标出来。
3.1 Python 虚拟环境与依赖安装:先把版本锁住
源码包在 Python 3.8/3.9 环境下开发,建议先创建独立虚拟环境,避免和机器上其他项目共用依赖导致版本冲突。
# 进入项目目录后创建虚拟环境 python -m venv venv # Windows 激活虚拟环境 venv\Scripts\activate # Linux / macOS 激活虚拟环境 source venv/bin/activate # 安装项目依赖 pip install -r requirements.txt为什么强制走虚拟环境?这不算玄学,是实打实的血泪经验。Django 版本之间不兼容的情况很常见,机器上如果已有别的项目装的是 Django 4.x,而源码包按 Django 3.2 写的,混着用会出现各种莫名其妙的报错。venv 把依赖隔离在项目内部,pip install 装的包只对当前项目生效。
requirements.txt 里常见的依赖清单是这样的:
Django==3.2.* mysqlclient==2.1.* requests==2.28.* pandas==1.5.*Django 锁大版本,mysqlclient 是连接 MySQL 的驱动。这里有一个高频坑:Windows 下直接装 mysqlclient 经常失败,报错信息一般是Microsoft Visual C++ 14.0 is required。遇到这种情况不要死磕,换 pymysql 驱动:
pip install pymysql然后在项目同名目录下的__init__.py里加两行兼容代码:
import pymysql pymysql.install_as_MySQLdb()这段代码的作用是让 Django 的 MySQL 后端在调用 MySQLdb 时自动改用 pymysql。纯 Python 实现的驱动不需要本地编译工具链,安装即用,是 Windows 环境下的标准替代方案。
提示:如果你的系统是 Linux,直接装 mysqlclient 一般没问题,优先用它,性能比 pymysql 略好。
3.2 MySQL 建库与 settings.py 配置:字符集和时区一次设好
依赖装完后,进入数据库配置环节。先用命令行建一个空库:
CREATE DATABASE epidemic DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意字符集必须显式写成 utf8mb4。疫情数据从网页抓取时偶尔会混入特殊符号,如果建库时用了默认的 latin1 或 utf8,写入时轻则告警,重则该行数据直接报错。utf8mb4 是 MySQL 对完整 Unicode 的支持,能容纳四字节字符,是这类带文本数据的系统最稳妥的选择。
然后修改 config/settings.py 里的 DATABASES 配置:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'epidemic', 'USER': 'root', 'PASSWORD': '你的数据库密码', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', }, } }每个参数对应 MySQL 连接信息:NAME 是刚才建好的库名,USER 和 PASSWORD 换成你自己的数据库账号,HOST 和 PORT 保持默认即可。OPTIONS 里的 charset 再设一次 utf8mb4,是双保险——就算建库时没注意字符集,连接层面的字符集对了也能降低乱码概率。
同一份 settings.py 里还有两个配置项需要顺势确认:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_TZ = False时区设置影响数据写入时间的计算。如果 USE_TZ 保持默认的 True,Django 会用 UTC 时间做序列化,页面显示的时间可能和你本地时间差八小时。做国内项目,把这个值改成 False 是最省心的方案。
3.3 数据迁移与初始化数据:让首页在十分钟内出图
配置完成后,执行 Django 的迁移命令把模型同步到 MySQL:
python manage.py makemigrations python manage.py migrate python manage.py createsuperusermakemigrations 根据 models.py 生成迁移文件,migrate 把这些文件应用到数据库。createsuperuser 是创建后台管理账号,用户名、邮箱、密码按提示输入即可,用于登录 Django Admin 后台查看和管理数据。
数据导入有两条路。第一条是直接用源码包里的 SQL 脚本:
mysql -uroot -p epidemic < sql/epidemic_init.sql这个命令把 sql/epidemic_init.sql 里的建表语句和 INSERT 数据一次性灌进库。执行后可以在 MySQL 里确认一下记录数:
SELECT COUNT(*) FROM epidemic_record;第二条路是通过 Django 自带的导入脚本。源码包里通常内置了一份历史快照,比如 static/data/history.json,结构如下:
[ { "date": "2020-02-01", "province": "湖北", "city": "武汉", "confirmed": 9074, "cured": 215, "dead": 294 }, { "date": "2020-02-01", "province": "广东", "city": "广州", "confirmed": 535, "cured": 112, "dead": 4 } ]这个 JSON 是标准的数据交换格式,每个对象对应数据库里的一条记录。导入脚本会遍历数组,逐条写入表里;遇到唯一约束冲突时先查重再决定更新还是跳过。这种 upsert 逻辑保证了脚本重复执行不会产生重复数据。
导入命令一般是项目里自定义的 management command:
python manage.py import_data --file static/data/history.json执行完成后,启动开发服务器:
python manage.py runserver浏览器访问http://127.0.0.1:8000,首页大屏如果正常渲染出疫情地图和趋势折线图,说明整套链路已经通了。如果页面有错,先看终端控制台的报错日志,大多数问题在这一步就能定位到具体模块。
4. 核心功能拆解:后端接口与 ECharts 大屏如何配合工作
环境跑通只是开始,关键是理解数据从 MySQL 到前端图表的完整流转链路。这也是答辩时最容易被追问的部分。
4.1 后端视图:用 ORM 聚合查询,把统计结果转成 JSON
疫情数据的查询集中在 apps/epidemic/views.py。核心接口之一是按省份返回每日趋势,实现方式如下:
from django.http import JsonResponse from django.db.models import Sum from .models import EpidemicRecord def province_trend_api(request): province = request.GET.get('province', '湖北') rows = ( EpidemicRecord.objects .filter(province=province) .order_by('date') .values('date') .annotate( confirmed_total=Sum('confirmed'), cured_total=Sum('cured'), dead_total=Sum('dead'), ) ) data = list(rows) return JsonResponse({'code': 0, 'data': data})这段代码的逻辑分四步:filter 按省份筛选记录,order_by 让结果按日期升序排列,values 指定要返回的字段,annotate 配合 Sum 把同一天内多城市的数据聚合到省份维度。annotate 是 Django ORM 里的分组聚合操作,对应 SQL 里的 GROUP BY,用它可以在 Python 层不写一行原生 SQL。
JSON 响应的结构是标准格式:code 为 0 表示业务成功,data 是前端要的数组,每个元素包含 date、confirmed_total、cured_total、dead_total。项目里其他接口沿用同一套响应格式,前端处理起来统一。
对应的路由配置在 apps/epidemic/urls.py:
from django.urls import path from . import views urlpatterns = [ path('api/epidemic/trend/', views.province_trend_api, name='province_trend'), ]4.2 前端页面:ECharts 折线图、柱状图与地图的接入
前端模板放在 templates/ 下,图表库通过静态文件引入。以省份趋势折线图为例,页面里的核心 JS 逻辑如下:
fetch('/api/epidemic/trend/?province=湖北') .then(res => res.json()) .then(json => { const dates = json.data.map(item => item.date); const confirmed = json.data.map(item => item.confirmed_total); const cured = json.data.map(item => item.cured_total); const chart = echarts.init(document.getElementById('trendChart')); chart.setOption({ title: { text: '湖北省疫情趋势' }, tooltip: { trigger: 'axis' }, legend: { data: ['累计确诊', '治愈'] }, xAxis: { type: 'category', data: dates }, yAxis: { type: 'value' }, series: [ { name: '累计确诊', type: 'line', data: confirmed }, { name: '治愈', type: 'line', data: cured } ] }); });这段代码的逻辑:fetch 请求后端接口,拿到 JSON 后用 map 把日期和指标拆成两个平行数组,建一个折线图实例,setOption 渲染。series 数组里可放多条线,一条对应累计确诊,一条对应治愈,天然适合做趋势对比。
比例图用饼图和柱状图,逻辑相同,区别只在 series 里的 type 字段。三种图表类型的数据接入方式基本一致,学会一种就能举一反三。
4.3 地图可视化:ECharts 5 里需要显式注册 GeoJSON
疫情展示最核心的页面是「中国地图 + 各省数据着色」。这里有个重点:ECharts 5 开始不再内置中国地图数据,需要显式引入 GeoJSON 文件并注册。
fetch('/static/data/china.json') .then(res => res.json()) .then(mapJson => { echarts.registerMap('china', mapJson); fetch('/api/epidemic/latest/') .then(res => res.json()) .then(json => { const mapChart = echarts.init(document.getElementById('mapChart')); mapChart.setOption({ series: [{ type: 'map', map: 'china', roam: true, label: { show: true, fontSize: 10 }, data: json.data }], visualMap: { min: 0, max: 10000, text: ['高', '低'], realtime: false, calculable: true } }); }); });这里踩坑概率最高的地方是时机。registerMap 必须在 setOption 之前完成,如果 fetch 地图 JSON 的请求还没返回就开始初始化图表,画布上只有空白和报错。上面代码用嵌套 fetch 保证了顺序,地图数据加载完才注册,注册完才渲染。
visualMap 是地图着色的关键配置项。min 和 max 定义了颜色映射的数据范围,小于 min 的显示最浅色,超过 max 的显示最深色。疫情数据全国各省差异可能上千倍,max 值需要根据实际数据调整,否则绝大多数省份都会因为数值远低于 max 而呈现同一种浅色,地图看起来缺乏层次。
大屏布局方面,页面通常用 CSS Grid 或 Flexbox 排布多个图表容器。每个图表是一个独立 div,高度要显式设置,否则 echarts.init 初始化的容器高度为 0,图表直接渲染不出来。
5. 部署与使用避坑:五个高频翻车点实录
这一章是资源落地时最常见的五类问题,每条都是「现象 → 原因 → 解决」的排查路径,照着对号入座能省下大量查日志时间。
5.1 现象:页面渲染出来了,但图表区域一片空白
首页能正常打开,但 ECharts 图表区域显示空白,控制台报Cannot read properties of undefined。
原因大概率有两个。第一,echarts.init 在 DOM 还没有完成布局时执行,容器高度为 0,图表就画不上去;第二,fetch 地图 GeoJSON 是异步请求,registerMap 还没执行就 setOption,地图无法渲染。前者常见于把初始化脚本直接写在 body 顶部而没有等待 DOM ready,后者常见于忘记嵌套请求顺序。
解决方式:把图表初始化逻辑放到页面底部或 DOMContentLoaded 回调中,确保容器存在;地图则严格按 fetch 完成后再 registerMap 再 setOption 的顺序执行。同时检查 CSS 里图表容器是否设置了明确的 height,不要依赖内容撑开。
document.addEventListener('DOMContentLoaded', function() { const chart = echarts.init(document.getElementById('mapChart')); // 后续渲染逻辑 });5.2 现象:mysqlclient 安装失败,pip 报编译错误
Windows 环境下执行pip install -r requirements.txt,卡在 mysqlclient 这一步,报error: Microsoft Visual C++ 14.0 is required。
原因:mysqlclient 需要本地编译 C 扩展,Windows 上没有对应的编译器工具链就装不过去。这不是代码问题,是环境问题。
解决:放弃在 Windows 上硬编 mysqlclient,改用 pymysql。先执行pip install pymysql,然后在项目同名目录的__init__.py中补充pymysql.install_as_MySQLdb(),最后在 requirements.txt 里把 mysqlclient 那一行删掉或注释即可。如果服务器是 Linux 环境,可以直接用 apt 装python3-dev default-libmysqlclient-dev build-essential后再装 mysqlclient,成功率极高。
5.3 现象:中文数据显示为问号或乱码
页面表格里汉字正常,但写入 MySQL 后通过命令行查询显示成???,或者从数据库读回来变成乱码。
原因:建库时没有指定 utf8mb4,MySQL 使用了默认的 latin1 字符集;或者在连接阶段的 charset 没对上。这是一个连锁问题:建库字符集、连接字符集、表字符集三层中任何一层不是 utf8mb4,中文写入就会出问题。
解决:第一,建库时显式加上CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;第二,在 settings.py 的 DATABASES 配置里补上'OPTIONS': {'charset': 'utf8mb4'};第三,如果表已经建好且数据已乱,需要对表执行转换:
ALTER TABLE epidemic_record CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;转换后已写入的脏数据可能无法自动修复,最稳妥的做法是删掉表重新导入一遍。
5.4 现象:数据重复导入后,接口返回的数字翻倍
运行导入脚本两次后,首页的总确诊数变成实际值的两倍,前端图表数据明显异常。
原因:导入脚本是逐条 INSERT 而不是 upsert,第二次执行时没有检查该条记录是否已存在,导致同一天同一个地区出现了两行记录。虽然模型里的 unique_together 设置了联合唯一约束,但如果导入脚本用的是get_or_create拿不到记录时的create逻辑不够严谨,或者绕过了 ORM 直接执行原生 SQL,唯一约束就不生效。
解决:确认导入逻辑是否正确处理了重复记录。推荐的写法是用update_or_create:
record, created = EpidemicRecord.objects.update_or_create( province=item['province'], city=item['city'], date=item['date'], defaults={ 'confirmed': item['confirmed'], 'suspected': item['suspected'], 'cured': item['cured'], 'dead': item['dead'], } )这段代码按省份 + 城市 + 日期三个字段查找记录,存在则更新 defaults 里的指标字段,不存在才插入新行。重复执行再多次,数据也不会翻倍。
5.5 现象:打开页面报 TemplateDoesNotExist
访问首页时 Django 抛出TemplateDoesNotExist: index.html,终端里能看到完整的模板加载路径列表。
原因:模板文件不在 Django 搜索路径中。Django 默认会按 app 目录下的 templates/ 子目录查找模板,但项目的 templates/ 放在项目根目录,需要在 settings.py 里显式注册。
解决:在 settings.py 中找到 TEMPLATES 配置,确认 DIRS 里已加入模板目录:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, # 其余配置保持不变 }, ]注意 BASE_DIR / 'templates' 使用的是 pathlib 语法,Django 3.1 之后支持这种写法。检查完配置后重启 runserver,模板路径立刻生效。
6. 答辩前加一层:定时更新疫情数据与接口耗时自查
毕设演示时,如果只是打开一个静态页面,评委可能会问「数据怎么更新」。这个问题答好了是加分项。我给源码包补一个轻量方案:用 Django 自定义 management command 做数据抓取,再用系统定时任务触发。
在 apps/epidemic/management/commands/ 目录下新建 update_data.py:
from django.core.management.base import BaseCommand from ...services import fetch_latest_data class Command(BaseCommand): help = '拉取最新疫情数据并写入 MySQL' def handle(self, *args, **options): count = fetch_latest_data() self.stdout.write(self.style.SUCCESS(f'更新完成,共写入 {count} 条新记录'))这样数据更新就不再依赖手动点击页面,而是可以通过命令行维护。Windows 上用任务计划程序,Linux 上用 crontab,每天定时执行一次python manage.py update_data。答辩现场演示时,先跑一次这个命令再刷新页面,比任何口头解释「我会更新数据」都有说服力。
接口性能自查同样很关键。评委或老师可能会问:「如果数据量翻十倍,接口还扛得住吗?」回答这个问题之前,先自己看一眼 MySQL 的查询计划:
EXPLAIN SELECT province, SUM(confirmed) FROM epidemic_record WHERE date = '2022-04-01' GROUP BY province;如果 EXPLAIN 结果里 type 是 ref 或 const,并且用到了 idx_date 索引,说明查询是健康的;如果出现全表扫描(type=ALL),就该考虑在 date 字段上建索引。项目里 models.py 已经给 date 加了 db_index=True,正常是命中索引的,你可以现场验证给评委看。
我一般还会在后端接口里加一个简单的耗时打印,方便自查:
import time def province_trend_api(request): start = time.time() # ... 原有查询逻辑 elapsed = (time.time() - start) * 1000 print(f'province_trend_api 耗时: {elapsed:.1f}ms') return JsonResponse({'code': 0, 'data': data})这个不是为了炫技,是给答辩准备一组真实数字:访问某个接口耗时多少毫秒,数据库查询占了大头,前端渲染又占多少。能讲出这层数据,说明你对系统运行状态有实际掌控,而不是只会跑通 demo。
从那以后,我每次拿到别人的 Django 项目,都强制自己先看 models 再跑 migrate,看完确认数据表结构没问题再调页面的图表初始化,这个顺序帮我少踩了无数查日志的坑。这份源码包里的开发说明也建议按同样的节奏走,先结构后功能,先数据后界面。希望帮到你。
本文还有配套的精品资源,点击获取