psycopg2+Flask+AJAX实现PostgreSQL数据绘图路由按需渲染
Flask+AJAX按需加载模块实现方案
后端路由改造
原有路由存在路径冲突、全量返回页面的问题,按「页面框架路由+数据接口路由」拆分:
/postgresql:仅返回db.html主框架,不携带任何业务数据,页面初始加载只渲染侧边栏、主区域空容器/postgresql/api/epex:复用原有psycopg2查询、matplotlib绘图逻辑,生成图片后返回JSON格式的静态资源路径,不返回完整页面/postgresql/api/partners:查询Elektrolyseure表的AG/TV、Partner字段,返回结构化JSON数据
参考代码:
from flask import Blueprint, render_template, jsonify import psycopg2 import matplotlib.pyplot as plt pg_bp = Blueprint('postgresql', __name__, url_prefix='/postgresql') # 基础框架路由 @pg_bp.route('') def pg_index(): return render_template('db.html') # EPEX电价图接口 @pg_bp.route('/api/epex') def get_epex_data(): conn = psycopg2.connect("dbname='Demonstratoren Statische Daten' host=localhost") cur = conn.cursor() cur.execute("SELECT * FROM EPEX_2022 WHERE id=1") record = cur.fetchone() # 按实际表结构取24小时电价数据,示例为取第2到第25个字段对应24小时值 prices = record[1:25] hours = list(range(24)) # 绘图 plt.clf() plt.plot(hours, prices) plt.xlabel("时段(小时)") plt.ylabel("电价(欧元/兆瓦时)") save_path = "static/epex_2022.png" plt.savefig(save_path) plt.close() cur.close() conn.close() return jsonify({"img_url": f"/{save_path}"}) # 合作方表格接口 @pg_bp.route('/api/partners') def get_partner_data(): conn = psycopg2.connect("dbname='Demonstratoren Statische Daten' host=localhost") cur = conn.cursor() cur.execute("SELECT ag_tv, partner FROM Elektrolyseure") partners = [{"ag_tv": row[0], "partner": row[1]} for row in cur.fetchall()] cur.close() conn.close() return jsonify({"list": partners})
前端模板调整
db.html内主区域拆分为独立容器,用Bootstrap自带的d-none类控制显隐,默认只显示初始提示:
- 侧边栏导航替换为带
data-target属性的按钮,标记对应要加载的模块 - 主区域拆分为3个独立区块:初始提示区、EPEX图表区、合作方表格区,后两个默认隐藏
参考模板片段:
<!-- 侧边栏 --> <div class="col-md-2 bg-light p-3 sidebar"> <button class="btn btn-outline-primary w-100 mb-2 nav-trigger" data-type="epex">EPEX</button> <button class="btn btn-outline-primary w-100 mb-2 nav-trigger" data-type="partners">Show Partner</button> </div> <!-- 主内容区 --> <div class="col-md-10 p-4 main-area"> <!-- 初始提示,默认显示 --> <div id="tip-block" class="h-100 d-flex justify-content-center align-items-center"> <p class="text-muted fs-5">请从左侧导航选择要查看的内容</p> </div> <!-- EPEX图表模块,默认隐藏 --> <div id="epex-block" class="d-none"> <div class="d-flex justify-content-between align-items-center mb-3"> <h4>EPEX现货电价时序图</h4> <select class="form-select w-auto"> <option>2022年</option> </select> </div> <div class="text-center"> <img id="epex-img" src="" class="img-fluid" alt="EPEX电价时序图"> </div> </div> <!-- 合作方表格模块,默认隐藏 --> <div id="partners-block" class="d-none"> <h4 class="mb-3">电解槽合作方列表</h4> <table class="table table-striped table-bordered"> <thead> <tr> <th>AG/TV</th> <th>合作方</th> </tr> </thead> <tbody id="partner-tbody"></tbody> </table> </div> </div>
前端AJAX交互逻辑
在模板底部加JS逻辑,实现点击导航时无刷新拉取数据、切换模块显隐:
- 点击导航按钮时先隐藏所有模块,显示加载提示
- 拉取对应接口数据,填充到对应容器后显示目标模块
- 处理请求异常场景,显示错误提示
参考代码:
<script> const tipBlock = document.getElementById('tip-block') const allBlocks = [tipBlock, document.getElementById('epex-block'), document.getElementById('partners-block')] // 统一隐藏所有区块的方法 function hideAllBlocks() { allBlocks.forEach(block => block.classList.add('d-none')) } // 绑定导航点击事件 document.querySelectorAll('.nav-trigger').forEach(btn => { btn.addEventListener('click', function() { const loadType = this.dataset.type hideAllBlocks() // 显示加载提示 tipBlock.innerHTML = '<p class="text-primary fs-5">加载中...</p>' tipBlock.classList.remove('d-none') if(loadType === 'epex') { fetch('/postgresql/api/epex') .then(res => res.json()) .then(res => { document.getElementById('epex-img').src = res.img_url hideAllBlocks() document.getElementById('epex-block').classList.remove('d-none') }) .catch(() => { tipBlock.innerHTML = '<p class="text-danger fs-5">图表加载失败,请重试</p>' }) } if(loadType === 'partners') { fetch('/postgresql/api/partners') .then(res => res.json()) .then(res => { const tbody = document.getElementById('partner-tbody') tbody.innerHTML = '' res.list.forEach(item => { const tr = document.createElement('tr') tr.innerHTML = `<td>${item.ag_tv}</td><td>${item.partner}</td>` tbody.append(tr) }) hideAllBlocks() document.getElementById('partners-block').classList.remove('d-none') }) .catch(() => { tipBlock.innerHTML = '<p class="text-danger fs-5">表格加载失败,请重试</p>' }) } }) }) </script>
踩坑提示
- 数据库连接配置补全自己的用户名、密码、端口参数,每次查询后必须关闭游标和连接,避免连接泄漏
- matplotlib每次绘图前加
plt.clf()、绘图后加plt.close(),否则多次请求会导致内存溢出 - 图片地址可以拼接时间戳参数
?t=${Date.now()},避免浏览器缓存旧的绘图结果 - 接口路由统一加
/api前缀,和页面路由区分,避免路径匹配冲突
内容的提问来源于stack exchange,提问作者Olej
相关产品推荐
相关产品推荐

