Python全栈项目:数据可视化大屏设计与实现

Python全栈数据大屏实战

项目概述

本文将带你从零开始构建一个完整的Python全栈数据可视化大屏项目,涵盖前端界面设计、后端API开发、数据库设计、实时数据处理以及项目部署等全栈开发的各个环节。项目采用前后端分离架构,适合作为企业级数据监控平台的技术参考。

项目效果

  • ✨ 实时数据监控与可视化展示
  • 📊 支持多种图表类型(折线图、柱状图、饼图、热力图等)
  • 🔄 数据自动刷新,WebSocket实时推送
  • 📱 响应式设计,支持大屏展示
  • 🎨 炫酷的动画效果和交互体验
  • 🔐 用户认证与权限管理

技术架构

整体架构图

┌─────────────────────────────────────────────────┐
│                   前端层                          │
│  React/Vue + ECharts + WebSocket Client         │
└─────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────┐
│                   API网关                         │
│              Nginx/Kong                          │
└─────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────┐
│                  后端服务层                        │
│   Flask/FastAPI + SQLAlchemy + Redis           │
└─────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────┐
│                  数据层                           │
│   PostgreSQL/MySQL + MongoDB + InfluxDB        │
└─────────────────────────────────────────────────┘

技术栈选型

前端技术
  • HTML5 + CSS3 + JavaScript ES6+
  • ECharts 5.x / Plotly.js (图表库)
  • WebSocket (实时通信)
  • Bootstrap / Tailwind CSS (样式框架)
后端技术
  • Python 3.9+
  • Flask 2.x / FastAPI (Web框架)
  • SQLAlchemy (ORM)
  • Celery (异步任务队列)
  • Redis (缓存 + 消息队列)
  • Socket.IO (WebSocket服务)
数据库
  • PostgreSQL (关系型数据)
  • Redis (缓存)
  • MongoDB (日志存储,可选)
  • InfluxDB (时序数据,可选)
部署运维
  • Docker + Docker Compose
  • Nginx (反向代理)
  • Supervisor (进程管理)
  • Prometheus + Grafana (监控)

数据库设计

核心数据表结构

-- 用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    role VARCHAR(20) DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_login TIMESTAMP
);

-- 业务数据表(销售数据)
CREATE TABLE sales_data (
    id SERIAL PRIMARY KEY,
    date DATE NOT NULL,
    region VARCHAR(50),
    category VARCHAR(50),
    product_name VARCHAR(100),
    quantity INTEGER,
    amount DECIMAL(10, 2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_date (date),
    INDEX idx_region (region)
);

-- 实时指标表
CREATE TABLE metrics (
    id SERIAL PRIMARY KEY,
    metric_name VARCHAR(50) NOT NULL,
    metric_value DECIMAL(15, 2),
    metric_type VARCHAR(20),
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_timestamp (timestamp),
    INDEX idx_metric_name (metric_name)
);

-- 告警规则表
CREATE TABLE alert_rules (
    id SERIAL PRIMARY KEY,
    rule_name VARCHAR(100) NOT NULL,
    metric_name VARCHAR(50),
    condition VARCHAR(50),
    threshold DECIMAL(10, 2),
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 告警记录表
CREATE TABLE alerts (
    id SERIAL PRIMARY KEY,
    rule_id INTEGER REFERENCES alert_rules(id),
    alert_level VARCHAR(20),
    alert_message TEXT,
    is_resolved BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    resolved_at TIMESTAMP
);

后端开发

项目结构

backend/
├── app/
│   ├── __init__.py
│   ├── config.py              # 配置文件
│   ├── models/                # 数据模型
│   │   ├── __init__.py
│   │   ├── user.py
│   │   ├── sales.py
│   │   └── metrics.py
│   ├── api/                   # API路由
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   ├── dashboard.py
│   │   └── data.py
│   ├── services/              # 业务逻辑
│   │   ├── __init__.py
│   │   ├── data_service.py
│   │   ├── cache_service.py
│   │   └── alert_service.py
│   ├── utils/                 # 工具函数
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   └── decorators.py
│   └── websocket/             # WebSocket服务
│       ├── __init__.py
│       └── events.py
├── tasks/                     # Celery任务
│   ├── __init__.py
│   └── data_tasks.py
├── migrations/                # 数据库迁移
├── tests/                     # 测试文件
├── requirements.txt
└── run.py                     # 启动文件

核心代码实现

配置文件 (app/config.py)
import os
from datetime import timedelta

class Config:
    """基础配置"""
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
    
    # 数据库配置
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'postgresql://username:password@localhost:5432/dashboard_db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_POOL_SIZE = 10
    SQLALCHEMY_POOL_RECYCLE = 3600
    
    # Redis配置
    REDIS_URL = os.environ.get('REDIS_URL') or 'redis://localhost:6379/0'
    
    # JWT配置
    JWT_SECRET_KEY = SECRET_KEY
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
    JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30)
    
    # Celery配置
    CELERY_BROKER_URL = REDIS_URL
    CELERY_RESULT_BACKEND = REDIS_URL
    
    # 跨域配置
    CORS_ORIGINS = ['http://localhost:3000', 'http://localhost:8080']
    
    # 缓存配置
    CACHE_TYPE = 'redis'
    CACHE_REDIS_URL = REDIS_URL
    CACHE_DEFAULT_TIMEOUT = 300

class DevelopmentConfig(Config):
    """开发环境配置"""
    DEBUG = True
    TESTING = False

class ProductionConfig(Config):
    """生产环境配置"""
    DEBUG = False
    TESTING = False

config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}
应用初始化 (app/init.py)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from flask_socketio import SocketIO
from flask_caching import Cache
from redis import Redis
import logging

# 初始化扩展
db = SQLAlchemy()
jwt = JWTManager()
socketio = SocketIO(cors_allowed_origins="*")
cache = Cache()
redis_client = None

def create_app(config_name='default'):
    """应用工厂函数"""
    app = Flask(__name__)
    
    # 加载配置
    from app.config import config
    app.config.from_object(config[config_name])
    
    # 初始化扩展
    db.init_app(app)
    jwt.init_app(app)
    socketio.init_app(app)
    cache.init_app(app)
    CORS(app, origins=app.config['CORS_ORIGINS'])
    
    # 初始化Redis客户端
    global redis_client
    redis_client = Redis.from_url(app.config['REDIS_URL'])
    
    # 配置日志
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    # 注册蓝图
    from app.api import auth_bp, dashboard_bp, data_bp
    app.register_blueprint(auth_bp, url_prefix='/api/auth')
    app.register_blueprint(dashboard_bp, url_prefix='/api/dashboard')
    app.register_blueprint(data_bp, url_prefix='/api/data')
    
    # 注册WebSocket事件
    from app.websocket import register_socketio_events
    register_socketio_events(socketio)
    
    return app
数据模型 (app/models/sales.py)
from app import db
from datetime import datetime

class SalesData(db.Model):
    """销售数据模型"""
    __tablename__ = 'sales_data'
    
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.Date, nullable=False, index=True)
    region = db.Column(db.String(50), index=True)
    category = db.Column(db.String(50))
    product_name = db.Column(db.String(100))
    quantity = db.Column(db.Integer)
    amount = db.Column(db.Numeric(10, 2))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    def to_dict(self):
        """转换为字典"""
        return {
            'id': self.id,
            'date': self.date.isoformat(),
            'region': self.region,
            'category': self.category,
            'product_name': self.product_name,
            'quantity': self.quantity,
            'amount': float(self.amount),
            'created_at': self.created_at.isoformat()
        }

class Metrics(db.Model):
    """实时指标模型"""
    __tablename__ = 'metrics'
    
    id = db.Column(db.Integer, primary_key=True)
    metric_name = db.Column(db.String(50), nullable=False, index=True)
    metric_value = db.Column(db.Numeric(15, 2))
    metric_type = db.Column(db.String(20))
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
    
    def to_dict(self):
        return {
            'id': self.id,
            'metric_name': self.metric_name,
            'metric_value': float(self.metric_value),
            'metric_type': self.metric_type,
            'timestamp': self.timestamp.isoformat()
        }
业务服务层 (app/services/data_service.py)
from app import db, cache
from app.models.sales import SalesData, Metrics
from sqlalchemy import func, and_
from datetime import datetime, timedelta
import pandas as pd

class DataService:
    """数据服务类"""
    
    @staticmethod
    @cache.memoize(timeout=300)
    def get_dashboard_summary(date=None):
        """获取仪表盘汇总数据"""
        if date is None:
            date = datetime.utcnow().date()
        
        # 今日数据
        today_data = db.session.query(
            func.sum(SalesData.amount).label('total_sales'),
            func.sum(SalesData.quantity).label('total_quantity'),
            func.count(SalesData.id).label('order_count')
        ).filter(SalesData.date == date).first()
        
        # 昨日数据(用于计算增长率)
        yesterday = date - timedelta(days=1)
        yesterday_data = db.session.query(
            func.sum(SalesData.amount).label('total_sales')
        ).filter(SalesData.date == yesterday).first()
        
        # 计算增长率
        growth_rate = 0
        if yesterday_data.total_sales and today_data.total_sales:
            growth_rate = (
                (float(today_data.total_sales) - float(yesterday_data.total_sales)) 
                / float(yesterday_data.total_sales) * 100
            )
        
        return {
            'total_sales': float(today_data.total_sales or 0),
            'total_quantity': int(today_data.total_quantity or 0),
            'order_count': int(today_data.order_count or 0),
            'growth_rate': round(growth_rate, 2)
        }
    
    @staticmethod
    @cache.memoize(timeout=300)
    def get_sales_trend(days=30):
        """获取销售趋势数据"""
        end_date = datetime.utcnow().date()
        start_date = end_date - timedelta(days=days)
        
        results = db.session.query(
            SalesData.date,
            func.sum(SalesData.amount).label('daily_sales')
        ).filter(
            and_(
                SalesData.date >= start_date,
                SalesData.date <= end_date
            )
        ).group_by(SalesData.date).order_by(SalesData.date).all()
        
        return [
            {
                'date': result.date.isoformat(),
                'sales': float(result.daily_sales)
            }
            for result in results
        ]
    
    @staticmethod
    @cache.memoize(timeout=300)
    def get_category_distribution():
        """获取分类销售分布"""
        results = db.session.query(
            SalesData.category,
            func.sum(SalesData.amount).label('category_sales'),
            func.count(SalesData.id).label('order_count')
        ).group_by(SalesData.category).all()
        
        return [
            {
                'category': result.category,
                'sales': float(result.category_sales),
                'order_count': int(result.order_count)
            }
            for result in results
        ]
    
    @staticmethod
    @cache.memoize(timeout=300)
    def get_regional_data():
        """获取区域销售数据"""
        results = db.session.query(
            SalesData.region,
            func.sum(SalesData.amount).label('regional_sales')
        ).group_by(SalesData.region).all()
        
        return [
            {
                'region': result.region,
                'sales': float(result.regional_sales)
            }
            for result in results
        ]
    
    @staticmethod
    def get_realtime_metrics():
        """获取实时指标"""
        # 获取最新的各项指标
        metrics = Metrics.query.order_by(
            Metrics.timestamp.desc()
        ).limit(10).all()
        
        return [metric.to_dict() for metric in metrics]
API路由 (app/api/dashboard.py)
from flask import Blueprint, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from app.services.data_service import DataService
from app.utils.decorators import handle_errors
from datetime import datetime

dashboard_bp = Blueprint('dashboard', __name__)

@dashboard_bp.route('/summary', methods=['GET'])
@jwt_required()
@handle_errors
def get_summary():
    """获取仪表盘汇总数据"""
    date_str = request.args.get('date')
    date = datetime.fromisoformat(date_str).date() if date_str else None
    
    data = DataService.get_dashboard_summary(date)
    
    return jsonify({
        'code': 200,
        'message': 'success',
        'data': data
    })

@dashboard_bp.route('/sales-trend', methods=['GET'])
@jwt_required()
@handle_errors
def get_sales_trend():
    """获取销售趋势"""
    days = request.args.get('days', 30, type=int)
    
    data = DataService.get_sales_trend(days)
    
    return jsonify({
        'code': 200,
        'message': 'success',
        'data': data
    })

@dashboard_bp.route('/category-distribution', methods=['GET'])
@jwt_required()
@handle_errors
def get_category_distribution():
    """获取分类分布"""
    data = DataService.get_category_distribution()
    
    return jsonify({
        'code': 200,
        'message': 'success',
        'data': data
    })

@dashboard_bp.route('/regional-data', methods=['GET'])
@jwt_required()
@handle_errors
def get_regional_data():
    """获取区域数据"""
    data = DataService.get_regional_data()
    
    return jsonify({
        'code': 200,
        'message': 'success',
        'data': data
    })

@dashboard_bp.route('/realtime-metrics', methods=['GET'])
@jwt_required()
@handle_errors
def get_realtime_metrics():
    """获取实时指标"""
    data = DataService.get_realtime_metrics()
    
    return jsonify({
        'code': 200,
        'message': 'success',
        'data': data
    })
WebSocket事件处理 (app/websocket/events.py)
from flask_socketio import emit, join_room, leave_room
from flask_jwt_extended import decode_token
from app import socketio
import logging

logger = logging.getLogger(__name__)

def register_socketio_events(socketio):
    """注册WebSocket事件"""
    
    @socketio.on('connect')
    def handle_connect(auth):
        """客户端连接"""
        try:
            # 验证JWT Token
            token = auth.get('token')
            if token:
                decode_token(token)
                logger.info('Client connected with valid token')
                emit('connection_response', {'status': 'connected'})
            else:
                logger.warning('Client connected without token')
                return False
        except Exception as e:
            logger.error(f'Connection error: {str(e)}')
            return False
    
    @socketio.on('disconnect')
    def handle_disconnect():
        """客户端断开连接"""
        logger.info('Client disconnected')
    
    @socketio.on('join_dashboard')
    def handle_join_dashboard(data):
        """加入仪表盘房间"""
        room = data.get('dashboard_id', 'default')
        join_room(room)
        emit('joined', {'room': room})
        logger.info(f'Client joined room: {room}')
    
    @socketio.on('leave_dashboard')
    def handle_leave_dashboard(data):
        """离开仪表盘房间"""
        room = data.get('dashboard_id', 'default')
        leave_room(room)
        emit('left', {'room': room})
        logger.info(f'Client left room: {room}')
    
    @socketio.on('request_update')
    def handle_request_update():
        """请求数据更新"""
        from app.services.data_service import DataService
        data = DataService.get_realtime_metrics()
        emit('data_update', {'metrics': data})

def broadcast_metrics_update(room='default'):
    """广播指标更新(由后台任务调用)"""
    from app.services.data_service import DataService
    data = DataService.get_realtime_metrics()
    socketio.emit('metrics_update', {'data': data}, room=room)
Celery异步任务 (tasks/data_tasks.py)
from celery import Celery
from app import create_app, db
from app.models.sales import Metrics
from app.websocket.events import broadcast_metrics_update
import random
from datetime import datetime, timedelta

app = create_app()
celery = Celery(
    app.import_name,
    broker=app.config['CELERY_BROKER_URL'],
    backend=app.config['CELERY_RESULT_BACKEND']
)

@celery.task
def generate_random_metrics():
    """生成随机指标数据(模拟实时数据)"""
    with app.app_context():
        metrics_data = [
            Metrics(
                metric_name='active_users',
                metric_value=random.randint(1000, 5000),
                metric_type='gauge'
            ),
            Metrics(
                metric_name='requests_per_second',
                metric_value=random.randint(100, 1000),
                metric_type='counter'
            ),
            Metrics(
                metric_name='response_time',
                metric_value=random.uniform(10, 500),
                metric_type='gauge'
            )
        ]
        
        db.session.bulk_save_objects(metrics_data)
        db.session.commit()
        
        # 广播更新
        broadcast_metrics_update()

@celery.task
def cleanup_old_metrics():
    """清理旧的指标数据"""
    with app.app_context():
        # 删除30天前的数据
        cutoff_date = datetime.utcnow() - timedelta(days=30)
        Metrics.query.filter(Metrics.timestamp < cutoff_date).delete()
        db.session.commit()

# 配置定时任务
celery.conf.beat_schedule = {
    'generate-metrics-every-5-seconds': {
        'task': 'tasks.data_tasks.generate_random_metrics',
        'schedule': 5.0,
    },
    'cleanup-old-metrics-daily': {
        'task': 'tasks.data_tasks.cleanup_old_metrics',
        'schedule': 86400.0,  # 每天执行一次
    },
}

前端开发

前端项目结构

frontend/
├── public/
│   ├── index.html
│   └── assets/
├── src/
│   ├── api/                   # API接口
│   │   ├── auth.js
│   │   └── dashboard.js
│   ├── components/            # 组件
│   │   ├── charts/
│   │   │   ├── LineChart.js
│   │   │   ├── BarChart.js
│   │   │   ├── PieChart.js
│   │   │   └── HeatMap.js
│   │   ├── MetricCard.js
│   │   └── Header.js
│   ├── pages/                 # 页面
│   │   ├── Login.js
│   │   └── Dashboard.js
│   ├── utils/                 # 工具函数
│   │   ├── request.js
│   │   └── websocket.js
│   ├── styles/                # 样式文件
│   │   ├── global.css
│   │   └── dashboard.css
│   ├── App.js
│   └── index.js
├── package.json
└── webpack.config.js

核心前端代码

WebSocket工具类 (src/utils/websocket.js)
import io from 'socket.io-client';

class WebSocketService {
  constructor() {
    this.socket = null;
    this.listeners = {};
  }

  connect(url, token) {
    this.socket = io(url, {
      auth: { token },
      transports: ['websocket']
    });

    this.socket.on('connect', () => {
      console.log('WebSocket connected');
      this.joinDashboard('default');
    });

    this.socket.on('disconnect', () => {
      console.log('WebSocket disconnected');
    });

    this.socket.on('metrics_update', (data) => {
      this.trigger('metrics_update', data);
    });

    this.socket.on('data_update', (data) => {
      this.trigger('data_update', data);
    });
  }

  joinDashboard(dashboardId) {
    if (this.socket) {
      this.socket.emit('join_dashboard', { dashboard_id: dashboardId });
    }
  }

  requestUpdate() {
    if (this.socket) {
      this.socket.emit('request_update');
    }
  }

  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }

  off(event, callback) {
    if (this.listeners[event]) {
      this.listeners[event] = this.listeners[event].filter(
        cb => cb !== callback
      );
    }
  }

  trigger(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(callback => callback(data));
    }
  }

  disconnect() {
    if (this.socket) {
      this.socket.disconnect();
    }
  }
}

export default new WebSocketService();
仪表盘页面 (src/pages/Dashboard.js)
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import * as echarts from 'echarts';
import WebSocketService from '../utils/websocket';
import MetricCard from '../components/MetricCard';
import './Dashboard.css';

const Dashboard = () => {
  const [summary, setSummary] = useState({
    total_sales: 0,
    total_quantity: 0,
    order_count: 0,
    growth_rate: 0
  });
  const [salesTrend, setSalesTrend] = useState([]);
  const [categoryData, setCategoryData] = useState([]);

  useEffect(() => {
    // 初始化数据
    fetchDashboardData();

    // 连接WebSocket
    const token = localStorage.getItem('access_token');
    WebSocketService.connect('http://localhost:5000', token);

    // 监听实时更新
    WebSocketService.on('metrics_update', handleMetricsUpdate);

    // 初始化图表
    initCharts();

    return () => {
      WebSocketService.off('metrics_update', handleMetricsUpdate);
      WebSocketService.disconnect();
    };
  }, []);

  const fetchDashboardData = async () => {
    try {
      const token = localStorage.getItem('access_token');
      const config = {
        headers: { Authorization: `Bearer ${token}` }
      };

      // 获取汇总数据
      const summaryRes = await axios.get(
        'http://localhost:5000/api/dashboard/summary',
        config
      );
      setSummary(summaryRes.data.data);

      // 获取销售趋势
      const trendRes = await axios.get(
        'http://localhost:5000/api/dashboard/sales-trend?days=30',
        config
      );
      setSalesTrend(trendRes.data.data);

      // 获取分类数据
      const categoryRes = await axios.get(
        'http://localhost:5000/api/dashboard/category-distribution',
        config
      );
      setCategoryData(categoryRes.data.data);

      // 更新图表
      updateCharts(trendRes.data.data, categoryRes.data.data);
    } catch (error) {
      console.error('Error fetching dashboard data:', error);
    }
  };

  const handleMetricsUpdate = (data) => {
    console.log('Received metrics update:', data);
    // 更新实时指标
    fetchDashboardData();
  };

  const initCharts = () => {
    // 初始化趋势图
    const trendChart = echarts.init(document.getElementById('trend-chart'));
    window.trendChart = trendChart;

    // 初始化饼图
    const pieChart = echarts.init(document.getElementById('pie-chart'));
    window.pieChart = pieChart;

    // 响应式
    window.addEventListener('resize', () => {
      trendChart.resize();
      pieChart.resize();
    });
  };

  const updateCharts = (trendData, categoryData) => {
    // 更新趋势图
    const trendOption = {
      backgroundColor: 'transparent',
      tooltip: {
        trigger: 'axis',
        axisPointer: { type: 'cross' }
      },
      grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: trendData.map(item => item.date),
        axisLine: { lineStyle: { color: '#4a5568' } }
      },
      yAxis: {
        type: 'value',
        axisLine: { lineStyle: { color: '#4a5568' } },
        splitLine: { lineStyle: { color: '#2d3748' } }
      },
      series: [{
        name: '销售额',
        type: 'line',
        smooth: true,
        symbol: 'circle',
        symbolSize: 6,
        lineStyle: {
          width: 3,
          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
            { offset: 0, color: '#00d9ff' },
            { offset: 1, color: '#00ff88' }
          ])
        },
        areaStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(0, 217, 255, 0.3)' },
            { offset: 1, color: 'rgba(0, 217, 255, 0)' }
          ])
        },
        data: trendData.map(item => item.sales)
      }]
    };
    window.trendChart.setOption(trendOption);

    // 更新饼图
    const pieOption = {
      backgroundColor: 'transparent',
      tooltip: {
        trigger: 'item',
        formatter: '{b}: {c} ({d}%)'
      },
      legend: {
        orient: 'vertical',
        right: '10%',
        top: 'center',
        textStyle: { color: '#fff' }
      },
      series: [{
        name: '销售分布',
        type: 'pie',
        radius: ['40%', '70%'],
        avoidLabelOverlap: false,
        itemStyle: {
          borderRadius: 10,
          borderColor: '#1a202c',
          borderWidth: 2
        },
        label: {
          show: false,
          position: 'center'
        },
        emphasis: {
          label: {
            show: true,
            fontSize: '20',
            fontWeight: 'bold'
          }
        },
        labelLine: { show: false },
        data: categoryData.map((item, index) => ({
          value: item.sales,
          name: item.category,
          itemStyle: {
            color: ['#00d9ff', '#00ff88', '#ffaa00', '#ff4444', '#aa00ff'][index]
          }
        }))
      }]
    };
    window.pieChart.setOption(pieOption);
  };

  return (
    <div className="dashboard-container">
      <div className="dashboard-header">
        <h1>数据运营监控大屏</h1>
        <div className="header-actions">
          <button onClick={fetchDashboardData}>刷新数据</button>
        </div>
      </div>

      <div className="metrics-row">
        <MetricCard
          title="总销售额"
          value={`¥${summary.total_sales.toLocaleString()}`}
          change={`${summary.growth_rate > 0 ? '+' : ''}${summary.growth_rate}%`}
          changeType={summary.growth_rate >= 0 ? 'positive' : 'negative'}
        />
        <MetricCard
          title="订单数量"
          value={summary.order_count.toLocaleString()}
          change="+12.5%"
          changeType="positive"
        />
        <MetricCard
          title="销售数量"
          value={summary.total_quantity.toLocaleString()}
          change="+8.3%"
          changeType="positive"
        />
        <MetricCard
          title="转化率"
          value="24.5%"
          change="-2.1%"
          changeType="negative"
        />
      </div>

      <div className="charts-row">
        <div className="chart-container">
          <h3>销售趋势</h3>
          <div id="trend-chart" style={{ width: '100%', height: '400px' }}></div>
        </div>
        <div className="chart-container">
          <h3>分类销售占比</h3>
          <div id="pie-chart" style={{ width: '100%', height: '400px' }}></div>
        </div>
      </div>
    </div>
  );
};

export default Dashboard;
样式文件 (src/styles/dashboard.css)
.dashboard-container {
  min-height: 100vh;
  background: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
  padding: 20px;
  color: #fff;
}

.dashboard-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30px;
  padding-bottom: 20px;
  border-bottom: 2px solid rgba(0, 217, 255, 0.3);
}

.dashboard-header h1 {
  font-size: 36px;
  font-weight: bold;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
  margin: 0;
}

.header-actions button {
  background: linear-gradient(135deg, #00d9ff, #00ff88);
  border: none;
  color: #000;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;
  font-weight: bold;
  transition: transform 0.2s;
}

.header-actions button:hover {
  transform: translateY(-2px);
  box-shadow: 0 5px 15px rgba(0, 217, 255, 0.4);
}

.metrics-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
  margin-bottom: 30px;
}

.metric-card {
  background: rgba(30, 30, 30, 0.8);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  padding: 20px;
  backdrop-filter: blur(10px);
  transition: transform 0.3s, box-shadow 0.3s;
}

.metric-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 8px 20px rgba(0, 217, 255, 0.3);
}

.metric-card h4 {
  color: #8b949e;
  font-size: 14px;
  margin: 0 0 10px 0;
}

.metric-card .value {
  font-size: 32px;
  font-weight: bold;
  margin: 10px 0;
}

.metric-card .change {
  font-size: 14px;
}

.change.positive { color: #00ff88; }
.change.negative { color: #ff4444; }

.charts-row {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 20px;
}

.chart-container {
  background: rgba(30, 30, 30, 0.8);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  padding: 20px;
  backdrop-filter: blur(10px);
}

.chart-container h3 {
  color: #8b949e;
  font-size: 18px;
  margin: 0 0 15px 0;
}

@media (max-width: 1200px) {
  .metrics-row {
    grid-template-columns: repeat(2, 1fr);
  }
  
  .charts-row {
    grid-template-columns: 1fr;
  }
}

Docker部署

Docker配置文件

Dockerfile (后端)
FROM python:3.9-slim

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 5000

# 启动命令
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "--worker-class", "eventlet", "run:app"]
docker-compose.yml
version: '3.8'

services:
  # PostgreSQL数据库
  postgres:
    image: postgres:13
    environment:
      POSTGRES_DB: dashboard_db
      POSTGRES_USER: dashboard_user
      POSTGRES_PASSWORD: dashboard_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - dashboard_network

  # Redis缓存
  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"
    networks:
      - dashboard_network

  # 后端应用
  backend:
    build: ./backend
    environment:
      DATABASE_URL: postgresql://dashboard_user:dashboard_password@postgres:5432/dashboard_db
      REDIS_URL: redis://redis:6379/0
      FLASK_ENV: production
    ports:
      - "5000:5000"
    depends_on:
      - postgres
      - redis
    networks:
      - dashboard_network
    restart: unless-stopped

  # Celery Worker
  celery_worker:
    build: ./backend
    command: celery -A tasks.data_tasks.celery worker --loglevel=info
    environment:
      DATABASE_URL: postgresql://dashboard_user:dashboard_password@postgres:5432/dashboard_db
      REDIS_URL: redis://redis:6379/0
    depends_on:
      - postgres
      - redis
    networks:
      - dashboard_network
    restart: unless-stopped

  # Celery Beat
  celery_beat:
    build: ./backend
    command: celery -A tasks.data_tasks.celery beat --loglevel=info
    environment:
      DATABASE_URL: postgresql://dashboard_user:dashboard_password@postgres:5432/dashboard_db
      REDIS_URL: redis://redis:6379/0
    depends_on:
      - postgres
      - redis
    networks:
      - dashboard_network
    restart: unless-stopped

  # Nginx反向代理
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./frontend/build:/usr/share/nginx/html
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - backend
    networks:
      - dashboard_network
    restart: unless-stopped

volumes:
  postgres_data:

networks:
  dashboard_network:
    driver: bridge
Nginx配置 (nginx.conf)
events {
    worker_connections 1024;
}

http {
    upstream backend {
        server backend:5000;
    }

    server {
        listen 80;
        server_name localhost;

        # 前端静态文件
        location / {
            root /usr/share/nginx/html;
            index index.html;
            try_files $uri $uri/ /index.html;
        }

        # API代理
        location /api/ {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # WebSocket代理
        location /socket.io/ {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    }
}

项目启动

开发环境启动

# 1. 启动数据库和Redis
docker-compose up -d postgres redis

# 2. 初始化数据库
cd backend
python manage.py db init
python manage.py db migrate
python manage.py db upgrade

# 3. 启动后端
python run.py

# 4. 启动Celery Worker
celery -A tasks.data_tasks.celery worker --loglevel=info

# 5. 启动Celery Beat
celery -A tasks.data_tasks.celery beat --loglevel=info

# 6. 启动前端
cd frontend
npm install
npm start

生产环境部署

# 一键启动所有服务
docker-compose up -d

# 查看日志
docker-compose logs -f

# 停止服务
docker-compose down

# 重新构建并启动
docker-compose up -d --build

性能优化与最佳实践

后端优化

1. 数据库优化
  • 合理使用索引
  • 查询结果分页
  • 使用连接池
  • 定期清理历史数据
2. 缓存策略
  • 热点数据Redis缓存
  • 设置合理的过期时间
  • 缓存预热机制
3. 异步处理
  • 使用Celery处理耗时任务
  • 消息队列解耦
  • 定时任务自动化

前端优化

1. 性能优化
  • 图表按需加载
  • 虚拟滚动大数据列表
  • 防抖和节流
  • 代码分割和懒加载
2. 用户体验
  • 加载状态提示
  • 错误边界处理
  • 响应式设计
  • 离线提示

监控告警

1. 系统监控
  • Prometheus采集指标
  • Grafana可视化
  • 日志集中管理
2. 业务监控
  • 关键指标异常告警
  • 实时数据监控
  • 用户行为分析

总结

本文详细介绍了Python全栈数据可视化大屏项目的完整开发流程,涵盖了:

  • ✅ 技术架构设计与选型
  • ✅ 数据库设计与优化
  • ✅ 后端API开发(Flask + SQLAlchemy)
  • ✅ 实时通信(WebSocket)
  • ✅ 异步任务处理(Celery)
  • ✅ 前端可视化开发(ECharts)
  • ✅ Docker容器化部署
  • ✅ 性能优化与监控

这是一个功能完整、架构清晰、易于扩展的企业级项目,适合用于实际生产环境。通过本项目的学习,你可以掌握Python全栈开发的核心技能,并将其应用到实际项目中。

扩展方向

  • 🔧 添加数据导出功能(Excel、PDF)
  • 🔧 实现自定义看板配置
  • 🔧 支持多租户架构
  • 🔧 集成机器学习预测模型
  • 🔧 移动端适配与小程序开发

项目代码

下载链接

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值