项目概述
本文将带你从零开始构建一个完整的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)
- 🔧 实现自定义看板配置
- 🔧 支持多租户架构
- 🔧 集成机器学习预测模型
- 🔧 移动端适配与小程序开发
Python全栈数据大屏实战
1万+

被折叠的 条评论
为什么被折叠?



