324-基于Python的中国传染病数据可视化分析系统
Infectious Disease Data Visualization and Analysis System
目录
项目概述
项目背景
中国传染病数据可视化分析系统是一个用于展示和分析2018-2024年中国各省份传染病数据的综合性平台。系统通过现代化的前端技术和强大的后端分析能力,为疾控中心、研究人员和公众提供直观的数据洞察。
核心功能
- 多维度数据分析: 支持按时间、地区、疾病类型等多维度交叉分析
- 全国地图可视化: 基于GeoJSON的省份级别数据展示
- 趋势预测: 基于历史数据的简单趋势预测
- 用户系统: 完整的注册、登录、个人中心功能
- 数据收藏: 用户可收藏感兴趣的数据记录
- 响应式设计: 适配桌面端和移动端
数据规模
| 指标 | 数值 |
|---|---|
| 数据年份 | 2018-2024 (7年) |
| 省级行政区 | 34个 |
| 数据记录 | 10,000+ 条 |
| 分析维度 | 28+ 字段 |
| 疾病类型 | 10种 |























技术栈详解
后端技术栈
Django 4.2.29
Django 是一个高级 Python Web 框架,鼓励快速开发和简洁实用的设计。
核心组件:
| 组件 | 版本 | 说明 |
|---|---|---|
| Django | 4.2.29 | Web 框架 |
| djangorestframework | 3.16.1 | REST API 框架 |
| djangorestframework-simplejwt | 5.3.1 | JWT 认证 |
| django-cors-headers | 4.9.0 | 跨域资源共享 |
| django-simpleui | 2024.4.1 | Admin 美化主题 |
| PyMySQL | 1.1.1 | MySQL 驱动 |
| python-dotenv | 1.0.1 | 环境变量管理 |
Django Apps 架构:
backend/
├── core/ # 核心配置 (settings, urls, wsgi)
├── analytics/ # 数据分析服务
├── datasets/ # 数据模型与管理
└── accounts/ # 用户认证系统
Django REST Framework
基于 Django 的强大灵活的 REST API 工具包。
核心特性:
- 序列化器 (Serializers) - 对象与JSON互转
- 视图集 (ViewSets) - CRUD操作的快速实现
- 路由 (Routers) - 自动生成URL路由
- 认证 (Authentication) - 多种认证方式支持
- 权限 (Permissions) - 细粒度权限控制
- 限流 (Throttling) - API请求频率限制
JWT 认证 (djangorestframework-simplejwt)
使用 JSON Web Tokens 实现无状态认证。
Token 结构:
python
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
配置参数:
| 参数 | 值 | 说明 |
|---|---|---|
| ACCESS_TOKEN_LIFETIME | 1小时 | 访问令牌有效期 |
| REFRESH_TOKEN_LIFETIME | 7天 | 刷新令牌有效期 |
| ROTATE_REFRESH_TOKENS | True | 刷新时轮换令牌 |
| BLACKLIST_AFTER_ROTATION | True | 旧刷新令牌加入黑名单 |
前端技术栈
Vue 3
渐进式 JavaScript 框架,采用 Composition API。
核心特性:
- 响应式数据绑定
- 组件化开发
- 组合式 API (Composition API)
- 单文件组件 (.vue)
依赖包:
| 包 | 版本 | 说明 |
|---|---|---|
| vue | ^3.x | 核心框架 |
| vue-router | ^4.x | 路由管理 |
| pinia | ^2.x | 状态管理 |
| echarts | ^5.x | 数据可视化 |
| axios | ^1.x | HTTP 客户端 |
Pinia 状态管理
Vue 3 推荐的状态管理解决方案。
Store 结构:
javascript
// stores/auth.js
export const useAuthStore = defineStore('auth', {
state: () => ({ user: null, token: null }),
getters: { isLoggedIn: (state) => !!state.token },
actions: { login(), logout(), fetchProfile() }
})
ECharts 5
百度开源的数据可视化图表库。
支持的图表类型:
- 折线图 (line)
- 柱状图 (bar)
- 饼图 (pie)
- 散点图 (scatter)
- 地图 (map)
- 雷达图 (radar)
- 热力图 (heatmap)
- K线图 (candlestick)
Vite 5
新一代前端构建工具。
核心优势:
- 极快的热更新 (HMR)
- 基于 ESM 的开发服务器
- 智能化依赖预构建
- 插件化的构建机制
数据库
MySQL 8.x
关系型数据库,存储系统核心数据。
数据库配置:
python
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "design_324_infectious",
"USER": "root",
"PASSWORD": "123456",
"HOST": "127.0.0.1",
"PORT": 3306,
"OPTIONS": {
"charset": "utf8mb4",
"init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
SQLite (开发环境)
轻量级数据库,开发环境下可选。
python
DB_ENGINE = os.getenv("DB_ENGINE", "mysql")
if DB_ENGINE == "sqlite":
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
系统架构
整体架构图
┌─────────────────────────────────────────────────────────────────┐
│ 用户端浏览器 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Vue 3 前端应用 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ AppShell.vue │ │
│ │ ┌───────────┬───────────┬───────────┬─────────────┐ │ │
│ │ │ 总览页 │ 疾病洞察 │ 区域研判 │ 数据检索 │ │ │
│ │ └───────────┴───────────┴───────────┴─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │Auth Store │ │Filters │ │Favorites │ │
│ │(认证状态) │ │Store │ │Store │ │
│ └────────────┘ │(筛选状态) │ │(收藏状态) │ │
│ └────────────┘ └────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ API Service │ │
│ │ (Axios) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ HTTP/JWT
▼
┌─────────────────────────────────────────────────────────────────┐
│ Django 后端 API │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Django REST Framework │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Analytics│ │Datasets │ │Accounts │ │ SimpleUI│ │ │
│ │ │ Views │ │ Models │ │ Views │ │ Admin │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └─────────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────┴───────────┘ │ │
│ │ │ │ │
│ │ Services Layer │ │
│ │ (build_overview_response, build_disease_response...) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ MySQL / SQLite │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ infectious_ │ │ auth_user │ │ accounts_ │ │ │
│ │ │ disease_ │ │ │ │ favorites │ │ │
│ │ │ records │ │ │ │ user_profiles│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
前端路由架构
/ (根路由)
├── /login # 登录页 (公开)
├── /register # 注册页 (公开)
└── /app # 应用壳 (需认证)
├── /app/overview # 总览页面
├── /app/disease # 疾病洞察页面
├── /app/region # 区域研判页面
├── /app/records # 数据检索页面
└── /app/profile # 个人中心页面
数据流向
用户操作 → Vue组件 → Pinia Store → API Service → Django View
│
▼
用户界面 ← ECharts ← Serializer ← Services ← Database
功能模块
1. 数据管理模块
1.1 数据模型
InfectiousDiseaseRecord 模型:
python
class InfectiousDiseaseRecord(models.Model):
# 时间维度
year = models.PositiveSmallIntegerField(db_index=True) # 年份
month = models.PositiveSmallIntegerField(db_index=True) # 月份
season = models.CharField(max_length=8, db_index=True) # 季节
# 地理维度
province = models.CharField(max_length=16, db_index=True) # 省份
city_tier = models.CharField(max_length=8, db_index=True) # 城市等级
# 疾病维度
disease_type = models.CharField(max_length=24, db_index=True) # 疾病类型
# 人口指标
population_density = models.PositiveIntegerField() # 人口密度
urbanization_rate = models.FloatField() # 城市化率
elderly_population_rate = models.FloatField() # 老年人口比例
children_population_rate = models.FloatField() # 儿童人口比例
# 环境指标
avg_temperature = models.FloatField() # 平均温度
monthly_rainfall = models.FloatField() # 月降水量
relative_humidity = models.PositiveSmallIntegerField() # 相对湿度
# 医疗指标
vaccination_rate = models.FloatField() # 疫苗接种率
medical_beds_per_10k = models.FloatField() # 每万人床位数
doctors_per_1k = models.FloatField() # 每千人医生数
hospital_count = models.PositiveIntegerField() # 医院数量
# 流动指标
mobility_index = models.FloatField() # 人口流动指数
# 疫情指标
hygiene_level = models.PositiveSmallIntegerField() # 卫生等级
response_level = models.PositiveSmallIntegerField() # 响应等级
monthly_cases = models.PositiveIntegerField() # 月度病例数
monthly_deaths = models.PositiveIntegerField() # 月度死亡数
monthly_recoveries = models.PositiveIntegerField() # 月度康复数
recovery_rate = models.FloatField() # 康复率
mortality_rate = models.FloatField() # 死亡率
transmission_speed = models.FloatField() # 传播速度
is_high_risk = models.BooleanField(default=False, db_index=True) # 高风险标记
# 系统字段
record_hash = models.CharField(max_length=64, unique=True) # 数据哈希
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
1.2 数据导入机制
CSV导入命令:
bash
python manage.py import_infectious_csv --truncate
导入流程:
1. 读取CSV文件 (指定编码UTF-8)
│
▼
2. 解析表头映射 (CSV_FIELD_MAPPING)
│
▼
3. 类型转换 (FIELD_CASTERS)
│
▼
4. 生成数据哈希 (SHA256)
│
▼
5. 批量插入数据库 (batch_size=2000)
│
▼
6. 去重处理 (unique_together约束)
核心代码实现:
python
# 数据哈希生成
record_hash = hashlib.sha256(
f"{year}{month}{province}{disease_type}".encode()
).hexdigest()
# 批量插入优化
InfectiousDiseaseRecord.objects.bulk_create(
records, ignore_conflicts=True
)
1.3 数据常量配置
省份名称映射 (解决与GeoJSON不匹配问题):
python
PROVINCE_GEOJSON_NAMES = {
# 直辖市
"北京": "北京市",
"上海": "上海市",
"天津": "天津市",
"重庆": "重庆市",
# 自治区
"内蒙古": "内蒙古自治区",
"广西": "广西壮族自治区",
"新疆": "新疆维吾尔自治区",
"西藏": "西藏自治区",
"宁夏": "宁夏回族自治区",
}
2. 数据分析模块
2.1 分析服务架构
analytics/services.py
│
├── get_filter_metadata() # 筛选器元数据
├── build_overview_response() # 总览数据
├── build_disease_response() # 疾病洞察数据
├── build_region_response() # 区域分析数据
└── build_records_response() # 数据检索数据
2.2 核心分析算法
风险评分算法
python
WEIGHT_CONFIG = {
"cases": 0.30, # 病例数权重
"deaths": 0.17, # 死亡数权重
"transmission": 0.16, # 传播速度权重
"density": 0.11, # 人口密度权重
"mobility": 0.10, # 流动指数权重
"vaccination": 0.09, # 疫苗接种率权重 (反向)
"response": 0.07, # 响应等级权重 (反向)
}
def attach_risk_scores(items):
# 1. 归一化处理 (Min-Max)
normalized_values[metric] = (value - min) / (max - min)
# 2. 反向处理 (疫苗、响应等级越高风险越低)
if metric in {"vaccination", "response"}:
score = 1 - score
# 3. 加权求和
risk_score = sum(normalized * weight for weight in WEIGHT_CONFIG)
# 4. 风险分层
if risk_score >= 70: tier = "极高风险"
elif risk_score >= 50: tier = "高风险"
elif risk_score >= 30: tier = "中风险"
else: tier = "低风险"
皮尔逊相关系数
python
def pearson_correlation(pairs):
if len(pairs) < 2:
return 0
xs, ys = zip(*pairs)
mean_x = sum(xs) / len(xs)
mean_y = sum(ys) / len(ys)
numerator = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys))
denominator_x = sqrt(sum((x - mean_x) ** 2 for x in xs))
denominator_y = sqrt(sum((y - mean_y) ** 2 for y in ys))
denominator = denominator_x * denominator_y
return 0 if not denominator else numerator / denominator
简单趋势预测
python
def predict_next_year(yearly_series):
# 1. 取最近3年数据
recent = yearly_series[-3:]
# 2. 计算年均增长率
for i in range(1, len(recent)):
growth = (curr - prev) / prev
avg_growth += growth
avg_growth_rate = avg_growth / (len(recent) - 1)
# 3. 预测下一年
last_cases = yearly_series[-1]["cases"]
predicted = last_cases * (1 + avg_growth_rate)
return {
"year": last_year + 1,
"predictedCases": max(0, int(predicted)),
"growthRate": round(avg_growth_rate * 100, 2),
"trend": "上升" if avg_growth_rate > 0 else "下降",
"confidence": "高" if len(recent) >= 3 else "中",
}
2.3 总览页面 (OverviewView)
API 返回数据结构:
javascript
{
kpis: {
recordCount: 10000, // 记录总数
totalCases: 500000, // 累计病例
totalDeaths: 5000, // 累计死亡
totalRecoveries: 450000, // 累计康复
avgRecoveryRate: 85.5, // 平均康复率
avgVaccinationRate: 72.3, // 平均接种率
highRiskShare: 23.5, // 高风险占比
},
mapData: [ // 地图数据
{ name: "北京市", shortName: "北京", value: 15000, riskScore: 85.3 },
// ...
],
yearlyTrend: [ // 年度趋势
{ year: 2018, cases: 60000, deaths: 600, recoveries: 54000 },
// ...
],
diseaseRanking: [ // 疾病排名
{ disease_type: "新冠肺炎", cases: 150000, deaths: 1500 },
// ...
],
mortalityTrend: [ // 死亡率趋势
{ year: 2018, mortality_rate: 1.2 },
// ...
],
responseDistribution: [ // 响应等级分布
{ response_level: 1, cases: 10000, count: 200 },
// ...
],
riskTiers: { // 风险分层
"极高风险": ["广东", "浙江", ...],
"高风险": [...],
// ...
},
prediction: { // 趋势预测
year: 2025,
predictedCases: 520000,
growthRate: 4.2,
trend: "上升",
confidence: "高"
}
}
2.4 疾病洞察页面 (DiseaseInsightsView)
核心图表:
- 月度趋势图 - 折线图展示病例时间变化
- 疾病特征雷达图 - 多维度疾病特征对比
- 疾病-省份矩阵 - 热力图展示跨维度分析
- 省份死亡率对比 - 横向柱状图
2.5 区域研判页面 (RegionAnalysisView)
核心图表:
- 风险地图 - GeoJSON地图按风险评分着色
- 省份排名 - 多维度综合评分排名
- 城市等级对比 - 按城市等级分组分析
- 医疗资源散点 - 病床数/医生数与病例相关性
- 人口结构分析 - 城市化率、老年/儿童比例
3. 用户认证模块
3.1 用户模型扩展
python
# accounts/models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
nickname = models.CharField(max_length=50, blank=True)
avatar = models.URLField(max_length=500, blank=True)
phone = models.CharField(max_length=20, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Favorite(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
record = models.ForeignKey("datasets.InfectiousDiseaseRecord")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ["user", "record"] # 防止重复收藏
3.2 JWT 认证流程
1. 用户注册/登录
│
▼
2. 服务器验证凭据
│
▼
3. 生成 Access Token (1小时) + Refresh Token (7天)
│
▼
4. 前端存储 Token (localStorage)
│
▼
5. 请求时附加 Token (Authorization: Bearer xxx)
│
▼
6. Access Token 过期时,使用 Refresh Token 刷新
3.3 前端认证状态管理
javascript
// stores/auth.js
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
accessToken: localStorage.getItem('accessToken'),
refreshToken: localStorage.getItem('refreshToken'),
}),
getters: {
isLoggedIn: (state) => !!state.accessToken,
},
actions: {
async login(username, password) {
const data = await login(username, password)
this.accessToken = data.tokens.access
this.refreshToken = data.tokens.refresh
this.user = data.user
},
async logout() {
await logout()
this.accessToken = null
this.refreshToken = null
this.user = null
}
}
})
4. 后台管理模块
4.1 Django Admin 配置
使用 SimpleUI 美化后台界面。
python
# core/settings.py
INSTALLED_APPS = [
"simpleui", # 必须放在第一位
"django.contrib.admin",
# ...
]
4.2 模型中文配置
python
# accounts/models.py
class Meta:
verbose_name = "用户资料"
verbose_name_plural = "用户资料"
class Favorite(models.Model):
class Meta:
verbose_name = "收藏"
verbose_name_plural = "收藏"
4.3 Admin 站点配置
python
# accounts/admin.py
@admin.register(Favorite)
class FavoriteAdmin(admin.ModelAdmin):
list_display = ["user", "record", "created_at"]
search_fields = ["user__username"]
技术实现
1. 前端 ECharts 图表配置
地图配置 (GeoJSON)
javascript
// OverviewView.vue
const mapOption = computed(() => ({
series: [{
type: 'map',
map: 'china',
data: payload.mapData.map(item => ({
name: item.name, // "北京市"
value: item.value,
})),
label: { show: true },
itemStyle: {
areaColor: {
// 根据风险评分着色
color: getRiskColor(item.riskScore)
}
}
}]
}))
风险评分颜色映射
javascript
function getRiskColor(score) {
if (score >= 70) return '#c23531' // 极高风险 - 红色
if (score >= 50) return '#d48265' // 高风险 - 橙色
if (score >= 30) return '#e6b600' // 中风险 - 黄色
return '#50a3ba' // 低风险 - 蓝色
}
2. 省份名称转换
数据库中使用短名称(如"北京"),GeoJSON中使用全称(如"北京市")。
python
# services.py
def to_geojson_name(province_name):
return PROVINCE_GEOJSON_NAMES.get(province_name, province_name)
# 使用示例
mapData = [
{"name": to_geojson_name(item["province"]), "value": item["cases"]}
for item in province_groups
]
3. 全局筛选联动
使用 Pinia 实现全局状态同步。
javascript
// stores/filters.js
export const useFiltersStore = defineStore('filters', {
state: () => ({
filters: {
year: [2024],
month: [],
province: [],
disease: [],
season: [],
cityTier: [],
highRiskOnly: false,
}
}),
getters: {
activeCount: (state) =>
Object.values(state.filters).filter(v => v.length > 0).length
},
actions: {
setFilter(key, value) {
this.filters[key] = value
}
}
})
4. 分页查询
python
# services.py
def build_records_response(filters, params):
page = max(int(params.get("page", 1)), 1)
page_size = min(max(int(params.get("pageSize", 12)), 1), 100)
paginator = Paginator(queryset, page_size)
page_obj = paginator.get_page(page)
return {
"items": [record_to_dict(record) for record in page_obj],
"pagination": {
"page": page_obj.number,
"pageSize": page_size,
"pages": paginator.num_pages,
"total": paginator.count,
}
}
API 接口文档
基础信息
| 项目 | 说明 |
|---|---|
| Base URL | http://localhost:8000/api/ |
| 数据格式 | JSON |
| 认证方式 | JWT Bearer Token |
认证接口
POST /api/auth/register - 用户注册
请求:
json
{
"username": "testuser",
"password": "password123",
"password_confirm": "password123",
"email": "test@example.com"
}
响应 (201 Created):
json
{
"user": {
"id": 1,
"username": "testuser",
"email": "test@example.com"
},
"tokens": {
"refresh": "eyJ0eXAiOiJKV1...",
"access": "eyJ0eXAiOiJKV1..."
}
}
POST /api/auth/login - 用户登录
请求:
json
{
"username": "admin",
"password": "admin123"
}
响应 (200 OK):
json
{
"user": {
"id": 1,
"username": "admin",
"email": "admin@example.com"
},
"tokens": {
"refresh": "eyJ0eXAiOiJKV1...",
"access": "eyJ0eXAiOiJKV1..."
}
}
POST /api/auth/logout - 用户登出
请求头 : Authorization: Bearer <access_token>
请求:
json
{
"refresh": "eyJ0eXAiOiJKV1..."
}
响应 (200 OK):
json
{
"message": "登出成功"
}
GET /api/auth/profile - 获取个人信息
请求头 : Authorization: Bearer <access_token>
响应 (200 OK):
json
{
"id": 1,
"username": "admin",
"email": "admin@example.com",
"profile": {
"nickname": "",
"avatar": "",
"phone": ""
}
}
PUT /api/auth/profile - 更新个人信息
请求头 : Authorization: Bearer <access_token>
请求:
json
{
"nickname": "新昵称",
"phone": "13800138000"
}
响应 (200 OK):
json
{
"id": 1,
"username": "admin",
"profile": {
"nickname": "新昵称",
"phone": "13800138000"
}
}
POST /api/auth/change-password - 修改密码
请求:
json
{
"old_password": "oldpass123",
"new_password": "newpass123"
}
响应 (200 OK):
json
{
"message": "密码修改成功"
}
数据接口
GET /api/filters - 获取筛选器元数据
响应:
json
{
"filters": {
"years": [2018, 2019, 2020, 2021, 2022, 2023, 2024],
"months": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
"provinces": ["北京", "天津", "河北", ...],
"diseases": ["新冠肺炎", "流感", "肺结核", ...],
"seasons": ["春季", "夏季", "秋季", "冬季"],
"cityTiers": ["一线", "新一线", "二线", "三线", "四线", "五线"]
},
"fieldLabels": { ... },
"valueAliases": { ... }
}
GET /api/overview - 总览数据
查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| year | int[] | 年份筛选 |
| province | string[] | 省份筛选 |
| disease | string[] | 疾病类型筛选 |
响应 : 参见 总览页面数据结构
GET /api/disease-insights - 疾病洞察数据
查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| disease | string | 疾病类型(必填) |
响应 : 参见 疾病洞察数据结构
GET /api/region-analysis - 区域分析数据
响应 : 参见 区域分析数据结构
GET /api/records - 数据检索
查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| page | int | 页码(默认1) |
| pageSize | int | 每页条数(默认12,最大100) |
| sortBy | string | 排序字段 |
| order | string | 排序方向 (asc/desc) |
响应:
json
{
"items": [
{
"id": 1,
"year": 2024,
"month": 1,
"province": "北京",
"diseaseType": "新冠肺炎",
"monthlyCases": 5000,
...
}
],
"pagination": {
"page": 1,
"pageSize": 12,
"pages": 100,
"total": 1200
}
}
收藏接口
GET /api/favorites - 获取收藏列表
响应:
json
[
{
"id": 1,
"record_id": 10,
"created_at": "2024-04-03T10:00:00Z",
"record": { ... }
}
]
POST /api/favorites/add - 添加收藏
请求:
json
{
"record_id": 10
}
响应 (201 Created):
json
{
"id": 1,
"record_id": 10
}
DELETE /api/favorites/{record_id} - 删除收藏
响应 (204 No Content)
数据模型
模型关系图
User
├── UserProfile (1:1)
└── Favorite (1:N)
└── InfectiousDiseaseRecord (N:1)
数据库表结构
infectious_disease_records
| 字段 | 类型 | 索引 | 说明 |
|---|---|---|---|
| id | BIGINT | PK | 主键 |
| year | SMALLINT | idx | 年份 |
| month | SMALLINT | idx | 月份 |
| province | VARCHAR(16) | idx | 省份 |
| city_tier | VARCHAR(8) | idx | 城市等级 |
| disease_type | VARCHAR(24) | idx | 疾病类型 |
| population_density | INT | - | 人口密度 |
| avg_temperature | FLOAT | - | 平均温度 |
| monthly_rainfall | FLOAT | - | 月降水量 |
| vaccination_rate | FLOAT | - | 疫苗接种率 |
| medical_beds_per_10k | FLOAT | - | 每万人床位 |
| monthly_cases | INT | - | 月度病例 |
| monthly_deaths | INT | - | 月度死亡 |
| mortality_rate | FLOAT | - | 死亡率 |
| is_high_risk | BOOL | idx | 高风险标记 |
| record_hash | VARCHAR(64) | UNIQUE | 数据哈希 |
复合索引:
- (year, month)
- (province, disease_type)
- (season, city_tier)
- (is_high_risk, year)
auth_user
Django 内置用户表。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT | 主键 |
| username | VARCHAR(150) | 用户名 |
| password | VARCHAR(128) | 密码哈希 |
| VARCHAR(254) | 邮箱 | |
| is_staff | BOOL | 是否为员工 |
| is_superuser | BOOL | 是否为超级用户 |
accounts_userprofile
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键 |
| user_id | INT | 外键 -> auth_user |
| nickname | VARCHAR(50) | 昵称 |
| avatar | VARCHAR(500) | 头像URL |
| phone | VARCHAR(20) | 电话 |
accounts_favorite
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键 |
| user_id | INT | 外键 -> auth_user |
| record_id | BIGINT | 外键 -> infectious_disease_records |
| created_at | DATETIME | 创建时间 |
约束: UNIQUE(user_id, record_id)
项目结构
infectious/
├── backend/ # Django 后端项目
│ ├── core/ # 核心配置模块
│ │ ├── __init__.py
│ │ ├── settings.py # Django 设置文件
│ │ │ ├── INSTALLED_APPS # 已安装应用
│ │ │ ├── DATABASES # 数据库配置
│ │ │ ├── REST_FRAMEWORK # DRF 配置
│ │ │ ├── SIMPLE_JWT # JWT 配置
│ │ │ └── CORS_ALLOWED_ORIGINS # CORS 配置
│ │ ├── urls.py # 根路由配置
│ │ ├── wsgi.py # WSGI 入口
│ │ └── asgi.py # ASGI 入口
│ │
│ ├── analytics/ # 数据分析应用
│ │ ├── __init__.py
│ │ ├── apps.py # 应用配置
│ │ ├── models.py # 模型定义(本应用为空)
│ │ ├── services.py # ★ 核心分析服务
│ │ │ ├── parse_filters() # 筛选参数解析
│ │ │ ├── apply_filters() # 筛选条件应用
│ │ │ ├── attach_risk_scores() # 风险评分计算
│ │ │ ├── pearson_correlation() # 相关系数
│ │ │ ├── build_overview_response() # 总览数据
│ │ │ ├── build_disease_response() # 疾病洞察
│ │ │ ├── build_region_response() # 区域分析
│ │ │ └── build_records_response() # 数据检索
│ │ ├── views.py # API 视图
│ │ ├── urls.py # 路由配置
│ │ ├── admin.py # Admin 配置
│ │ └── tests.py # 单元测试
│ │
│ ├── datasets/ # 数据管理应用
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ ├── models.py # ★ 数据模型
│ │ ├── constants.py # ★ 常量配置
│ │ │ ├── CSV_FIELD_MAPPING # CSV字段映射
│ │ │ ├── FIELD_LABELS # 字段中英文标签
│ │ │ ├── PROVINCE_ALIASES # 省份英文别名
│ │ │ ├── DISEASE_ALIASES # 疾病英文别名
│ │ │ ├── SEASON_ORDER # 季节顺序
│ │ │ └── CITY_TIER_ORDER # 城市等级顺序
│ │ ├── views.py
│ │ ├── admin.py # Admin 配置
│ │ ├── management/
│ │ │ └── commands/
│ │ │ └── import_infectious_csv.py # ★ CSV导入命令
│ │ └── migrations/
│ │
│ ├── accounts/ # 用户账户应用
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ ├── models.py # ★ 用户扩展模型
│ │ │ ├── UserProfile # 用户资料
│ │ │ └── Favorite # 收藏
│ │ ├── serializers.py # ★ DRF 序列化器
│ │ │ ├── UserSerializer
│ │ │ ├── RegisterSerializer
│ │ │ ├── UserProfileSerializer
│ │ │ ├── FavoriteSerializer
│ │ │ └── ChangePasswordSerializer
│ │ ├── views.py # ★ API 视图
│ │ │ ├── RegisterView # 注册
│ │ │ ├── LoginView # 登录
│ │ │ ├── LogoutView # 登出
│ │ │ ├── ProfileView # 个人信息
│ │ │ ├── ChangePasswordView # 修改密码
│ │ │ ├── FavoriteListView # 收藏列表
│ │ │ ├── FavoriteCreateView # 添加收藏
│ │ │ └── FavoriteDeleteView # 删除收藏
│ │ ├── urls.py # 路由配置
│ │ └── admin.py # Admin 配置
│ │
│ ├── staticfiles/ # 收集的静态文件
│ │ └── admin/
│ │ └── simpleui-x/ # SimpleUI 静态资源
│ │
│ ├── manage.py # Django 管理脚本
│ └── requirements.txt # Python 依赖
│
├── frontend/ # Vue 前端项目
│ ├── public/
│ │ └── index.html # HTML 入口
│ │
│ ├── src/
│ │ ├── main.js # ★ Vue 入口
│ │ ├── App.vue # 根组件
│ │ ├── style.css # 全局样式
│ │ │
│ │ ├── views/ # ★ 页面组件
│ │ │ ├── LoginView.vue # 登录页
│ │ │ ├── RegisterView.vue # 注册页
│ │ │ ├── OverviewView.vue # 总览页
│ │ │ ├── DiseaseInsightsView.vue # 疾病洞察
│ │ │ ├── RegionAnalysisView.vue # 区域研判
│ │ │ ├── RecordsView.vue # 数据检索
│ │ │ └── ProfileView.vue # 个人中心
│ │ │
│ │ ├── components/ # 公共组件
│ │ │ ├── AppShell.vue # 应用壳组件
│ │ │ ├── GlobalFilterBar.vue # 全局筛选栏
│ │ │ └── DataTable.vue # 数据表格
│ │ │
│ │ ├── stores/ # ★ Pinia 状态管理
│ │ │ ├── auth.js # 认证状态
│ │ │ ├── filters.js # 筛选状态
│ │ │ └── favorites.js # 收藏状态
│ │ │
│ │ ├── services/ # API 服务层
│ │ │ └── api.js # ★ Axios 封装
│ │ │ # - 请求拦截器
│ │ │ # - 响应拦截器
│ │ │ # - Token 管理
│ │ │
│ │ ├── router/ # 路由配置
│ │ │ └── index.js # ★ 路由定义
│ │ │ # - 路由守卫
│ │ │ # - 权限控制
│ │ │
│ │ ├── utils/ # 工具函数
│ │ └── assets/ # 静态资源
│ │
│ ├── package.json # npm 依赖
│ ├── vite.config.js # Vite 配置
│ └── .env.example # 环境变量示例
│
├── data/ # 数据文件
│ └── 中国传染病数据.csv # 原始数据
│
├── scripts/ # 脚本目录
│ └── init_mysql.sql # MySQL 初始化脚本
│
├── .env.example # 环境变量示例
├── .gitignore # Git 忽略配置
└── README.md # 项目文档
环境配置
后端环境变量
bash
# .env 文件 (位于 infectious/ 目录)
# Django 核心配置
DJANGO_SECRET_KEY=your-secret-key-change-in-production
DJANGO_DEBUG=true
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost
# 数据库配置 (MySQL)
DB_ENGINE=mysql
DB_NAME=design_324_infectious
DB_USER=root
DB_PASSWORD=123456
DB_HOST=127.0.0.1
DB_PORT=3306
# 或使用 SQLite (开发环境)
# DB_ENGINE=sqlite
# SQLITE_NAME=backend/db.sqlite3
# CORS 配置
CORS_ALLOWED_ORIGINS=http://127.0.0.1:5173,http://localhost:5173
# 数据文件路径
DATA_CSV_PATH=./data/中国传染病数据.csv
前端环境变量
bash
# .env 文件 (位于 frontend/ 目录)
VITE_API_BASE_URL=http://localhost:8000/api
部署指南
1. 环境准备
Python 环境:
bash
# 推荐使用虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# 安装依赖
cd backend
pip install -r requirements.txt
Node.js 环境:
bash
# 推荐使用 nvm 管理 Node 版本
nvm install 18
nvm use 18
# 安装依赖
cd frontend
npm install
2. 数据库初始化
创建 MySQL 数据库:
sql
-- 登录 MySQL
mysql -u root -p
-- 执行 SQL
CREATE DATABASE IF NOT EXISTS `design_324_infectious`
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
或使用初始化脚本:
bash
mysql -u root -p < scripts/init_mysql.sql
3. 后端部署
bash
cd backend
# 1. 执行数据库迁移
python manage.py migrate
# 2. 导入初始数据
python manage.py import_infectious_csv --truncate
# 3. 创建管理员账户
python manage.py createsuperuser
# Username: admin
# Email: admin@example.com
# Password: admin123
# 4. 收集静态文件
python manage.py collectstatic
# 5. 启动开发服务器
python manage.py runserver
4. 前端部署
bash
cd frontend
# 1. 安装依赖
npm install
# 2. 启动开发服务器
npm run dev
# 3. 构建生产版本 (可选)
npm run build
5. 访问系统
| 服务 | 地址 |
|---|---|
| 前端应用 | http://localhost:5173 |
| 后端 API | http://localhost:8000/api/ |
| Admin 后台 | http://localhost:8000/admin/ |
| API 文档 | http://localhost:8000/api/docs/ (需安装 drf-spectacular) |
开发指南
添加新的分析图表
1. 后端 - 添加数据服务
python
# analytics/services.py
def build_new_chart_data(filters):
"""新图表数据构建"""
queryset = apply_filters(InfectiousDiseaseRecord.objects.all(), filters)
# 数据处理逻辑
chart_data = list(
queryset.values('field')
.annotate(...)
.order_by(...)
)
return {
"chartData": chart_data,
"metadata": { ... }
}
2. 后端 - 添加 API 端点
python
# analytics/views.py
class NewChartView(APIView):
permission_classes = [AllowAny]
def get(self, request):
filters = parse_filters(request.query_params)
return Response(build_new_chart_data(filters))
python
# analytics/urls.py
urlpatterns = [
# ... existing routes
path('new-chart/', NewChartView.as_view(), name='new-chart'),
]
3. 前端 - 添加图表组件
vue
<!-- OverviewView.vue -->
<script setup>
import { ref, computed, onMounted } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref(null)
const newChartOption = computed(() => ({
title: { text: '新图表' },
series: [{
type: 'bar',
data: payload.value.chartData
}]
}))
onMounted(() => {
// 初始化图表
const chart = echarts.init(chartRef.value)
chart.setOption(newChartOption.value)
})
</script>
<template>
<div ref="chartRef" class="chart-container"></div>
</template>
添加新的筛选条件
1. 数据库 - 添加字段
python
# datasets/models.py
class InfectiousDiseaseRecord(models.Model):
# ... existing fields
new_field = models.CharField(max_length=50, blank=True)
2. 常量 - 添加映射
python
# datasets/constants.py
CSV_FIELD_MAPPING['新CSV字段'] = 'new_field'
FIELD_LABELS['new_field'] = {'zh': '新字段', 'en': 'New Field'}
3. 服务 - 添加筛选
python
# analytics/services.py
QUERY_TO_FIELD['newFilter'] = 'new_field'
def parse_filters(params):
return {
# ... existing filters
'newFilter': parse_multi_value(params, 'newFilter'),
}
4. 前端 - 添加筛选组件
vue
<!-- GlobalFilterBar.vue -->
<template>
<div class="filter-bar">
<!-- 现有筛选器 -->
<select v-model="filtersStore.filters.newFilter">
<option value="">新字段</option>
</select>
</div>
</template>
生产环境部署建议
Nginx 配置
nginx
upstream backend {
server 127.0.0.1:8000;
}
upstream frontend {
server 127.0.0.1:5173;
}
server {
listen 80;
server_name yourdomain.com;
# 前端静态文件
location / {
proxy_pass http://frontend;
}
# API 代理
location /api {
proxy_pass http://backend;
}
# Django 静态文件
location /static {
alias /path/to/staticfiles;
}
}
Gunicorn 配置
bash
# 安装 gunicorn
pip install gunicorn
# 启动 (8 workers)
gunicorn core.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 8 \
--timeout 120
系统截图预览
登录页面
- 现代化玻璃拟态设计
- 左右分栏布局
- 动态背景光晕
- 实时表单验证
总览页面
- 4x3 KPI 卡片网格
- 全国地图可视化
- 多图表联动
- 风险等级分层展示
疾病洞察页面
- 月度趋势分析
- 疾病特征雷达图
- 跨维度矩阵热力图
区域研判页面
- 省份风险排名
- 医疗资源散点分析
- 人口结构可视化
常见问题
Q: 地图省份无法显示?
A : 检查 services.py 中的 PROVINCE_GEOJSON_NAMES 映射,确保数据库省份名称与 GeoJSON 文件中的名称一致。
Q: 登录失败,提示 Token 无效?
A:
- 确认后端服务器正在运行
- 检查浏览器控制台是否有 CORS 错误
- 清除 localStorage 重新登录
Q: 数据导入失败?
A:
- 检查 CSV 文件编码(需 UTF-8)
- 确认 CSV 表头与
CSV_FIELD_MAPPING一致 - 查看 Django 控制台错误日志
Q: SimpleUI 主题不生效?
A:
- 确认
simpleui在INSTALLED_APPS第一位 - 执行
python manage.py collectstatic - 重启 Django 服务器
- 清除浏览器缓存
版本历史
| 版本 | 日期 | 主要更新 |
|---|---|---|
| v2.0 | 2024.04 | 全新UI设计、用户认证、数据收藏 |
| v1.0 | 2024.03 | 初始版本、基础数据导入、可视化展示 |
许可证
本项目仅供学习和研究使用。
致谢
- Django - Python Web 框架
- Vue.js - 渐进式 JavaScript 框架
- ECharts - 数据可视化图表库
- Django REST Framework - REST API 工具包
- Element Plus - Vue 3 UI 组件库
- SimpleUI - Django Admin 美化主题