You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 22:03:34