F003疫情传染病数据可视化vue+flask+mysql

发布于:2025-08-20 ⋅ 阅读:(17) ⋅ 点赞:(0)

编号:F003

文章结尾有CSDN官方提供的学长的联系方式!!
欢迎关注B站

✅ vue + flask 前后端分离架构
✅ 实现中国地图、柱状图、折线图、水地图、环图等多种图形的echarts可视化分析

视频

vue+flask+爬虫 新冠疫情大屏实现 python 可视化分析项目源码

1 系统功能

  1. 读取腾讯新闻api数据解析后,存储到mysql数据库
    2.利用Flask的模板引擎,通过Echats 设计了中国疫情地图、折线图、柱状图等来分析疫情数据,进行数据的可视化

2 功能图 & 架构图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3 开发环境和关键技术

  • WebStorm
  • pymysql
  • flask
  • request 、 json 等

源码

<!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="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.2/dist/echarts.min.js"></script>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #0c1b33, #1a3a5f);
            color: #e0f0ff;
            overflow: hidden;
        }
        
        #app {
            padding: 20px;
            position: relative;
            height: 100vh;
            display: flex;
            flex-direction: column;
        }
        
        .dashboard-header {
            position: relative;
            margin-bottom: 20px;
            padding: 15px 0;
            border-bottom: 1px solid rgba(64, 158, 255, 0.3);
        }
        
        .dashboard-title {
            text-align: center;
            font-size: 36px;
            font-weight: bold;
            letter-spacing: 4px;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            -webkit-background-clip: text;
            background-clip: text;
            -webkit-text-fill-color: transparent;
            text-shadow: 0 0 10px rgba(79, 172, 254, 0.3);
            margin-bottom: 10px;
        }
        
        .last-update {
            text-align: center;
            font-size: 14px;
            color: #a0d8ff;
        }
        
        .dashboard-grid {
            display: grid;
            grid-template-columns: 1.5fr 1fr;
            grid-template-rows: 1fr 1.5fr 1fr;
            gap: 20px;
            height: calc(100vh - 140px);
            grid-template-areas:
                "map kpi"
                "map trend"
                "dist data";
        }
        
        .dashboard-card {
            background: rgba(16, 42, 88, 0.45);
            border: 1px solid rgba(64, 158, 255, 0.2);
            border-radius: 8px;
            padding: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
            backdrop-filter: blur(4px);
            position: relative;
            overflow: hidden;
        }
        
        .card-title {
            font-size: 18px;
            font-weight: bold;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid rgba(64, 158, 255, 0.2);
            display: flex;
            align-items: center;
        }
        
        .card-title::before {
            content: '';
            display: inline-block;
            width: 4px;
            height: 18px;
            background: #409eff;
            margin-right: 8px;
            border-radius: 2px;
        }
        
        /* 指标卡片样式 */
        .kpi-container {
            grid-area: kpi;
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            grid-template-rows: repeat(2, 1fr);
            gap: 15px;
        }
        
        .kpi-card {
            background: rgba(16, 42, 88, 0.7);
            border-radius: 6px;
            padding: 15px;
            display: flex;
            flex-direction: column;
            position: relative;
            overflow: hidden;
            transition: all 0.3s ease;
        }
        
        .kpi-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
        }
        
        .kpi-label {
            font-size: 14px;
            color: #a0d8ff;
            margin-bottom: 10px;
        }
        
        .kpi-value {
            font-size: 32px;
            font-weight: bold;
            margin-bottom: 5px;
        }
        
        .kpi-change {
            font-size: 13px;
            display: flex;
            align-items: center;
        }
        
        .kpi-change.up {
            color: #f56c6c;
        }
        
        .kpi-change.down {
            color: #67c23a;
        }
        
        /* 地图区域 */
        .map-container {
            grid-area: map;
            position: relative;
        }
        
        /* 趋势图表 */
        .trend-container {
            grid-area: trend;
        }
        
        /* 分布图表 */
        .distribution-container {
            grid-area: dist;
        }
        
        /* 实时数据 */
        .realtime-container {
            grid-area: data;
            display: flex;
            flex-direction: column;
        }
        
        .data-list {
            flex: 1;
            overflow-y: auto;
        }
        
        .data-item {
            padding: 12px 15px;
            border-bottom: 1px solid rgba(64, 158, 255, 0.1);
            display: flex;
            justify-content: space-between;
            transition: background 0.2s;
        }
        
        .data-item:hover {
            background: rgba(64, 158, 255, 0.1);
        }
        
        .province {
            font-weight: 600;
        }
        
        .number {
            color: #f56c6c;
            font-weight: bold;
        }
        
        /* 图表容器 */
        .chart-container {
            width: 100%;
            height: calc(100% - 40px);
            position: relative;
        }
        
        /* 装饰元素 */
        .corner-decoration {
            position: absolute;
            width: 15px;
            height: 15px;
        }
        
        .corner-tl {
            top: 0;
            left: 0;
            border-top: 2px solid #409eff;
            border-left: 2px solid #409eff;
            border-top-left-radius: 4px;
        }
        
        .corner-tr {
            top: 0;
            right: 0;
            border-top: 2px solid #409eff;
            border-right: 2px solid #409eff;
            border-top-right-radius: 4px;
        }
        
        .corner-bl {
            bottom: 0;
            left: 0;
            border-bottom: 2px solid #409eff;
            border-left: 2px solid #409eff;
            border-bottom-left-radius: 4px;
        }
        
        .corner-br {
            bottom: 0;
            right: 0;
            border-bottom: 2px solid #409eff;
            border-right: 2px solid #409eff;
            border-bottom-right-radius: 4px;
        }
        
        /* 响应式调整 */
        @media (max-width: 1200px) {
            .dashboard-grid {
                grid-template-columns: 1fr 1fr;
                grid-template-areas:
                    "map kpi"
                    "trend trend"
                    "dist data";
            }
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="dashboard-header">
            <h1 class="dashboard-title">传染病数据监控大屏</h1>
            <p class="last-update">最后更新: {{ updateTime }}</p>
        </div>
        
        <div class="dashboard-grid">
            <!-- 关键指标区域 -->
            <div class="kpi-container dashboard-card">
                <div class="kpi-card">
                    <div class="corner-decoration corner-tl"></div>
                    <div class="corner-decoration corner-tr"></div>
                    <div class="corner-decoration corner-bl"></div>
                    <div class="corner-decoration corner-br"></div>
                    <div class="kpi-label">累计确诊病例</div>
                    <div class="kpi-value">{{ kpiData.totalCases | formatNumber }}</div>
                    <div class="kpi-change up">
                        <i class="el-icon-caret-top" style="margin-right: 4px;"></i>
                        {{ kpiData.newCases | formatNumber }} 较昨日
                    </div>
                </div>
                
                <div class="kpi-card">
                    <div class="corner-decoration corner-tl"></div>
                    <div class="corner-decoration corner-tr"></div>
                    <div class="corner-decoration corner-bl"></div>
                    <div class="corner-decoration corner-br"></div>
                    <div class="kpi-label">现有确诊病例</div>
                    <div class="kpi-value">{{ kpiData.currentCases | formatNumber }}</div>
                    <div class="kpi-change down">
                        <i class="el-icon-caret-bottom" style="margin-right: 4px;"></i>
                        {{ kpiData.currentChange | formatNumber }} 较昨日
                    </div>
                </div>
                
                <div class="kpi-card">
                    <div class="corner-decoration corner-tl"></div>
                    <div class="corner-decoration corner-tr"></div>
                    <div class="corner-decoration corner-bl"></div>
                    <div class="corner-decoration corner-br"></div>
                    <div class="kpi-label">累计治愈病例</div>
                    <div class="kpi-value">{{ kpiData.totalRecovered | formatNumber }}</div>
                    <div class="kpi-change up">
                        <i class="el-icon-caret-top" style="margin-right: 4px;"></i>
                        {{ kpiData.newRecovered | formatNumber }} 较昨日
                    </div>
                </div>
                
                <div class="kpi-card">
                    <div class="corner-decoration corner-tl"></div>
                    <div class="corner-decoration corner-tr"></div>
                    <div class="corner-decoration corner-bl"></div>
                    <div class="corner-decoration corner-br"></div>
                    <div class="kpi-label">累计死亡病例</div>
                    <div class="kpi-value">{{ kpiData.totalDeaths | formatNumber }}</div>
                    <div class="kpi-change up">
                        <i class="el-icon-caret-top" style="margin-right: 4px;"></i>
                        {{ kpiData.newDeaths | formatNumber }} 较昨日
                    </div>
                </div>
            </div>
            
            <!-- 疫情地图区域 -->
            <div class="map-container dashboard-card">
                <div class="card-title">全国疫情分布地图</div>
                <div class="chart-container" id="map-chart"></div>
            </div>
            
            <!-- 趋势图表区域 -->
            <div class="trend-container dashboard-card">
                <div class="card-title">疫情发展趋势</div>
                <div class="chart-container" id="trend-chart"></div>
            </div>
            
            <!-- 分布图表区域 -->
            <div class="distribution-container dashboard-card">
                <div class="card-title">风险地区分布</div>
                <div class="chart-container" id="distribution-chart"></div>
            </div>
            
            <!-- 实时数据区域 -->
            <div class="realtime-container dashboard-card">
                <div class="card-title">高风险地区实时数据</div>
                <div class="data-list">
                    <div v-for="(item, index) in realtimeData" :key="index" class="data-item">
                        <span class="province">{{ item.province }}</span>
                        <span class="city">{{ item.city }}</span>
                        <span class="number">{{ item.cases }}例</span>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // 初始化Vue应用
        new Vue({
            el: '#app',
            data() {
                return {
                    updateTime: this.getCurrentTime(),
                    kpiData: {
                        totalCases: 985265,
                        newCases: 3426,
                        currentCases: 124823,
                        currentChange: -1584,
                        totalRecovered: 846321,
                        newRecovered: 4852,
                        totalDeaths: 14121,
                        newDeaths: 28
                    },
                    realtimeData: [
                        { province: '北京市', city: '朝阳区', cases: 256 },
                        { province: '上海市', city: '浦东新区', cases: 184 },
                        { province: '广东省', city: '深圳市', cases: 152 },
                        { province: '江苏省', city: '南京市', cases: 98 },
                        { province: '浙江省', city: '杭州市', cases: 87 },
                        { province: '湖北省', city: '武汉市', cases: 76 },
                        { province: '四川省', city: '成都市', cases: 65 },
                        { province: '陕西省', city: '西安市', cases: 58 },
                        { province: '天津市', city: '滨海新区', cases: 49 },
                        { province: '辽宁省', city: '沈阳市', cases: 42 }
                    ],
                    mapChart: null,
                    trendChart: null,
                    distributionChart: null
                }
            },
            filters: {
                formatNumber(value) {
                    return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
                }
            },
            mounted() {
                this.initCharts();
                this.updateTimeInterval = setInterval(() => {
                    this.updateTime = this.getCurrentTime();
                }, 60000);
                
                // 模拟实时数据更新
                setInterval(this.updateRealtimeData, 5000);
            },
            beforeDestroy() {
                clearInterval(this.updateTimeInterval);
            },
            methods: {
                getCurrentTime() {
                    const now = new Date();
                    return now.getFullYear() + '-' + 
                           (now.getMonth()+1).toString().padStart(2, '0') + '-' + 
                           now.getDate().toString().padStart(2, '0') + ' ' + 
                           now.getHours().toString().padStart(2, '0') + ':' + 
                           now.getMinutes().toString().padStart(2, '0');
                },
                initCharts() {
                    // 初始化地图图表
                    this.mapChart = echarts.init(document.getElementById('map-chart'));
                    this.renderMap();
                    
                    // 初始化趋势图表
                    this.trendChart = echarts.init(document.getElementById('trend-chart'));
                    this.renderTrendChart();
                    
                    // 初始化分布图表
                    this.distributionChart = echarts.init(document.getElementById('distribution-chart'));
                    this.renderDistributionChart();
                },
                renderMap() {
                    // 地图配置 - 使用模拟数据
                    const option = {
                        tooltip: {
                            trigger: 'item',
                            formatter: '{b}: {c} 例'
                        },
                        visualMap: {
                            min: 0,
                            max: 5000,
                            text: ['高', '低'],
                            realtime: false,
                            calculable: true,
                            inRange: {
                                color: ['#1d7dc2', '#1a7fd5', '#1a6aef', '#1a54ff', '#313bff', '#5c32ff', '#8529ff', '#af1fff', '#d815ff', '#ff0be5']
                            }
                        },
                        series: [
                            {
                                name: '确诊病例',
                                type: 'map',
                                map: 'china',
                                roam: true,
                                label: {
                                    show: true,
                                    fontSize: 10
                                },
                                data: [
                                    {name: '北京', value: 4521},
                                    {name: '天津', value: 1420},
                                    {name: '上海', value: 3845},
                                    {name: '重庆', value: 2641},
                                    {name: '河北', value: 1847},
                                    {name: '河南', value: 2715},
                                    {name: '云南', value: 1218},
                                    {name: '辽宁', value: 1975},
                                    {name: '黑龙江', value: 2140},
                                    {name: '湖南', value: 2310},
                                    {name: '安徽', value: 1645},
                                    {name: '山东', value: 2950},
                                    {name: '新疆', value: 780},
                                    {name: '江苏', value: 3540},
                                    {name: '浙江', value: 3985},
                                    {name: '江西', value: 1470},
                                    {name: '湖北', value: 3760},
                                    {name: '广西', value: 1065},
                                    {name: '甘肃', value: 885},
                                    {name: '山西', value: 1267},
                                    {name: '内蒙古', value: 985},
                                    {name: '陕西', value: 1740},
                                    {name: '吉林', value: 1124},
                                    {name: '福建', value: 2310},
                                    {name: '贵州', value: 1080},
                                    {name: '广东', value: 4210},
                                    {name: '青海', value: 320},
                                    {name: '西藏', value: 85},
                                    {name: '四川', value: 2870},
                                    {name: '宁夏', value: 630},
                                    {name: '海南', value: 780},
                                ]
                            }
                        ]
                    };
                    
                    // 使用中国地图
                    fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
                        .then(response => response.json())
                        .then(chinaJson => {
                            echarts.registerMap('china', chinaJson);
                            this.mapChart.setOption(option);
                        });
                },
                renderTrendChart() {
                    // 趋势图表配置
                    const option = {
                        tooltip: {
                            trigger: 'axis'
                        },
                        legend: {
                            data: ['新增确诊', '现有确诊', '治愈病例'],
                            textStyle: {
                                color: '#e0f0ff'
                            },
                            right: 10,
                            top: 10
                        },
                        grid: {
                            left: '3%',
                            right: '4%',
                            bottom: '3%',
                            containLabel: true
                        },
                        xAxis: {
                            type: 'category',
                            boundaryGap: false,
                            data: ['8/1', '8/2', '8/3', '8/4', '8/5', '8/6', '8/7'],
                            axisLine: {
                                lineStyle: {
                                    color: '#e0f0ff'
                                }
                            }
                        },
                        yAxis: {
                            type: 'value',
                            axisLine: {
                                lineStyle: {
                                    color: '#e0f0ff'
                                }
                            },
                            splitLine: {
                                lineStyle: {
                                    color: 'rgba(255, 255, 255, 0.1)'
                                }
                            }
                        },
                        series: [
                            {
                                name: '新增确诊',
                                type: 'line',
                                data: [4250, 4870, 4520, 3920, 3560, 3420, 3426],
                                smooth: true,
                                lineStyle: {
                                    width: 3,
                                    color: '#f56c6c'
                                },
                                symbol: 'circle',
                                symbolSize: 8,
                                itemStyle: {
                                    color: '#f56c6c'
                                },
                                areaStyle: {
                                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                                        { offset: 0, color: 'rgba(245, 108, 108, 0.5)' },
                                        { offset: 1, color: 'rgba(245, 108, 108, 0.1)' }
                                    ])
                                }
                            },
                            {
                                name: '现有确诊',
                                type: 'line',
                                data: [154200, 148600, 142500, 135800, 129500, 126400, 124800],
                                smooth: true,
                                lineStyle: {
                                    width: 3,
                                    color: '#e6a23c'
                                },
                                symbol: 'circle',
                                symbolSize: 8,
                                itemStyle: {
                                    color: '#e6a23c'
                                }
                            },
                            {
                                name: '治愈病例',
                                type: 'line',
                                data: [785240, 795680, 805420, 815650, 825900, 836580, 846320],
                                smooth: true,
                                lineStyle: {
                                    width: 3,
                                    color: '#67c23a'
                                },
                                symbol: 'circle',
                                symbolSize: 8,
                                itemStyle: {
                                    color: '#67c23a'
                                }
                            }
                        ]
                    };
                    this.trendChart.setOption(option);
                },
                renderDistributionChart() {
                    // 分布图表配置
                    const option = {
                        tooltip: {
                            trigger: 'item',
                            formatter: '{a} <br/>{b}: {c} ({d}%)'
                        },
                        legend: {
                            orient: 'vertical',
                            left: 10,
                            top: 'center',
                            data: ['高风险地区', '中风险地区', '低风险地区'],
                            textStyle: {
                                color: '#e0f0ff'
                            }
                        },
                        series: [
                            {
                                name: '风险地区分布',
                                type: 'pie',
                                radius: ['50%', '70%'],
                                center: ['65%', '50%'],
                                avoidLabelOverlap: false,
                                itemStyle: {
                                    borderRadius: 10,
                                    borderColor: '#0c1b33',
                                    borderWidth: 2
                                },
                                label: {
                                    show: false,
                                    position: 'center'
                                },
                                emphasis: {
                                    label: {
                                        show: true,
                                        fontSize: '16',
                                        fontWeight: 'bold',
                                        color: '#fff'
                                    }
                                },
                                labelLine: {
                                    show: false
                                },
                                data: [
                                    { value: 25, name: '高风险地区', itemStyle: { color: '#f56c6c' } },
                                    { value: 45, name: '中风险地区', itemStyle: { color: '#e6a23c' } },
                                    { value: 30, name: '低风险地区', itemStyle: { color: '#67c23a' } }
                                ]
                            }
                        ]
                    };
                    this.distributionChart.setOption(option);
                },
                updateRealtimeData() {
                    // 模拟实时数据更新
                    const provinces = ['北京市', '上海市', '广东省', '江苏省', '浙江省', '湖北省', '四川省', '陕西省', '天津市', '辽宁省'];
                    const cities = {
                        '北京市': ['朝阳区', '海淀区', '丰台区', '西城区', '东城区'],
                        '上海市': ['浦东新区', '闵行区', '徐汇区', '黄浦区', '虹口区'],
                        '广东省': ['深圳市', '广州市', '东莞市', '佛山市', '珠海市'],
                        '江苏省': ['南京市', '苏州市', '无锡市', '徐州市', '常州市'],
                        '浙江省': ['杭州市', '宁波市', '温州市', '嘉兴市', '绍兴市'],
                        '湖北省': ['武汉市', '黄冈市', '孝感市', '荆州市', '宜昌市'],
                        '四川省': ['成都市', '绵阳市', '德阳市', '泸州市', '南充市'],
                        '陕西省': ['西安市', '咸阳市', '宝鸡市', '渭南市', '汉中市'],
                        '天津市': ['滨海新区', '南开区', '河西区', '河东区', '河北区'],
                        '辽宁省': ['沈阳市', '大连市', '鞍山市', '抚顺市', '锦州市']
                    };
                    
                    const newData = [];
                    for (let i = 0; i < 10; i++) {
                        const province = provinces[Math.floor(Math.random() * provinces.length)];
                        const cityArr = cities[province];
                        const city = cityArr[Math.floor(Math.random() * cityArr.length)];
                        const cases = Math.floor(Math.random() * 100) + 20;
                        newData.push({ province, city, cases });
                    }
                    
                    this.realtimeData = newData;
                }
            }
        });
    </script>
</body>
</html>


网站公告

今日签到

点亮在社区的每一天
去签到