You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Flask将MySQL查询结果传递至HTML,复用表单加载产品数据

嘿,看起来你已经搞定了产品创建的表单,现在想复用这个模板来加载已有产品的组件、追踪更换后的新序列号对吧?结合Flask和MySQL,我给你梳理一套可行的实现方案,咱们一步步来:

1. 先把数据库结构捋顺

首先得确保你的MySQL表结构能支撑这个需求——毕竟要关联产品和组件,还要追踪更换记录对吧?假设你已经有productscomponents表,要是还没加追踪更换的功能,建议补个历史表,这样后续溯源更方便:

-- 产品主表(你应该已经有类似结构了)
CREATE TABLE products (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL,
    -- 其他产品属性,比如型号、创建时间等
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 组件表,关联产品ID,序列号设为唯一避免重复
CREATE TABLE components (
    component_id INT PRIMARY KEY AUTO_INCREMENT,
    product_id INT NOT NULL,
    serial_number VARCHAR(50) UNIQUE NOT NULL,
    hardware_version VARCHAR(20) NOT NULL,
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);

-- 组件更换历史表,记录每次更换的旧/新序列号
CREATE TABLE component_history (
    history_id INT PRIMARY KEY AUTO_INCREMENT,
    component_id INT NOT NULL,
    old_serial_number VARCHAR(50) NOT NULL,
    new_serial_number VARCHAR(50) NOT NULL,
    change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (component_id) REFERENCES components(component_id)
);
2. Flask后端的核心逻辑

2.1 加载已有产品和组件的路由

写一个路由,根据产品ID从数据库拉取产品主信息和关联的所有组件,然后传到你复用的表单模板里:

from flask import Flask, render_template, request, redirect, url_for
import mysql.connector
from mysql.connector import Error

app = Flask(__name__)

# 数据库配置建议用环境变量,别硬编码在代码里!
db_config = {
    'host': 'localhost',
    'database': 'product_tracking',
    'user': 'your_db_user',
    'password': 'your_db_pwd'
}

def get_db_conn():
    try:
        conn = mysql.connector.connect(**db_config)
        return conn
    except Error as e:
        print(f"数据库连接炸了: {e}")
        return None

@app.route('/product/<int:product_id>/edit', methods=['GET'])
def edit_product(product_id):
    conn = get_db_conn()
    if not conn:
        return "数据库连接失败", 500
    
    cursor = conn.cursor(dictionary=True)
    
    # 拉取产品主信息
    cursor.execute("SELECT * FROM products WHERE product_id = %s", (product_id,))
    product = cursor.fetchone()
    
    if not product:
        return "找不到这个产品哦", 404
    
    # 拉取该产品下的所有组件
    cursor.execute("SELECT * FROM components WHERE product_id = %s", (product_id,))
    components = cursor.fetchall()
    
    cursor.close()
    conn.close()
    
    # 把数据传到表单模板,复用创建页面的布局
    return render_template('product_form.html', product=product, components=components)

2.2 处理组件更新/更换的路由

再写一个POST路由,接收表单提交的数据,处理组件的序列号更换(或新增组件),同时记录更换历史:

@app.route('/product/<int:product_id>/update', methods=['POST'])
def update_product(product_id):
    conn = get_db_conn()
    if not conn:
        return "数据库连接失败", 500
    
    cursor = conn.cursor()
    try:
        # 先更新产品主信息(如果表单里有需要修改的产品字段)
        product_name = request.form.get('product_name')
        cursor.execute("UPDATE products SET product_name = %s WHERE product_id = %s", 
                      (product_name, product_id))
        
        # 处理组件数据:假设表单用component_id_0、component_serial_0这种命名方式
        component_count = int(request.form.get('component_count', 0))
        for i in range(component_count):
            component_id = request.form.get(f'component_id_{i}')
            new_serial = request.form.get(f'component_serial_{i}')
            new_version = request.form.get(f'component_version_{i}')
            
            if component_id:
                # 已有组件,检查序列号是否变更
                cursor.execute("SELECT serial_number FROM components WHERE component_id = %s", (component_id,))
                old_serial = cursor.fetchone()[0]
                if old_serial != new_serial:
                    # 序列号变了,先记录历史
                    cursor.execute("INSERT INTO component_history (component_id, old_serial_number, new_serial_number) VALUES (%s, %s, %s)",
                                  (component_id, old_serial, new_serial))
                    # 更新组件的序列号和版本
                    cursor.execute("UPDATE components SET serial_number = %s, hardware_version = %s WHERE component_id = %s",
                                  (new_serial, new_version, component_id))
                else:
                    # 只有版本变更,直接更新
                    cursor.execute("UPDATE components SET hardware_version = %s WHERE component_id = %s",
                                  (new_version, component_id))
            else:
                # 没有component_id,说明是新增组件
                cursor.execute("INSERT INTO components (product_id, serial_number, hardware_version) VALUES (%s, %s, %s)",
                              (product_id, new_serial, new_version))
        
        conn.commit()
    except Error as e:
        conn.rollback()
        return f"更新出错了: {e}", 500
    finally:
        cursor.close()
        conn.close()
    
    return redirect(url_for('edit_product', product_id=product_id))
3. 复用表单模板(product_form.html)

把你原来的创建表单改成支持编辑的模式,核心是判断是否传入了productcomponents数据,来切换创建/编辑状态:

<form method="POST" action="{% if product %}{{ url_for('update_product', product_id=product.product_id) }}{% else %}{{ url_for('create_product') }}{% endif %}">
    <!-- 产品主信息字段 -->
    <div class="form-group">
        <label class="control-label col-md-3">产品名称</label>
        <div class="col-md-9">
            <input type="text" name="product_name" class="form-control" value="{{ product.product_name if product else '' }}" required>
        </div>
    </div>

    <!-- 组件区域 -->
    <div id="components-container">
        <h4>组件信息</h4>
        <!-- 编辑状态下,循环渲染已有组件 -->
        {% if components %}
            {% for component in components %}
                <div class="component-row">
                    <input type="hidden" name="component_id_{{ loop.index0 }}" value="{{ component.component_id }}">
                    <div class="form-group">
                        <label class="control-label col-md-3">组件序列号</label>
                        <div class="col-md-9">
                            <input type="text" name="component_serial_{{ loop.index0 }}" class="form-control" value="{{ component.serial_number }}" required>
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="control-label col-md-3">硬件版本</label>
                        <div class="col-md-9">
                            <input type="text" name="component_version_{{ loop.index0 }}" class="form-control" value="{{ component.hardware_version }}" required>
                        </div>
                    </div>
                    <button type="button" class="btn btn-danger remove-component">删除组件</button>
                </div>
            {% endfor %}
        {% endif %}
        <!-- 新增组件按钮 -->
        <button type="button" class="btn btn-primary" id="add-component">添加新组件</button>
        <!-- 隐藏字段记录组件数量,后端用来循环处理 -->
        <input type="hidden" name="component_count" id="component-count" value="{{ components|length if components else 0 }}">
    </div>

    <button type="submit" class="btn btn-success mt-3">{% if product %}更新产品{% else %}创建产品{% endif %}</button>
</form>

<!-- 动态添加/删除组件的JS -->
<script>
// 添加组件逻辑
document.getElementById('add-component').addEventListener('click', function() {
    const container = document.getElementById('components-container');
    const count = parseInt(document.getElementById('component-count').value);
    
    const newRow = document.createElement('div');
    newRow.className = 'component-row mb-2';
    newRow.innerHTML = `
        <input type="hidden" name="component_id_${count}" value="">
        <div class="form-group row">
            <label class="control-label col-md-3">组件序列号</label>
            <div class="col-md-9">
                <input type="text" name="component_serial_${count}" class="form-control" required>
            </div>
        </div>
        <div class="form-group row">
            <label class="control-label col-md-3">硬件版本</label>
            <div class="col-md-9">
                <input type="text" name="component_version_${count}" class="form-control" required>
            </div>
        </div>
        <button type="button" class="btn btn-danger remove-component">删除组件</button>
    `;
    
    container.insertBefore(newRow, document.getElementById('add-component'));
    document.getElementById('component-count').value = count + 1;
    
    // 给新的删除按钮绑定事件
    newRow.querySelector('.remove-component').addEventListener('click', function() {
        newRow.remove();
        document.getElementById('component-count').value = document.querySelectorAll('.component-row').length;
    });
});

// 已有组件的删除逻辑
document.querySelectorAll('.remove-component').forEach(btn => {
    btn.addEventListener('click', function() {
        this.parentElement.remove();
        document.getElementById('component-count').value = document.querySelectorAll('.component-row').length;
    });
});
</script>
几个关键注意事项
  • 事务处理:上面的更新路由里加了try-except和事务回滚,避免部分更新失败导致数据不一致,这个很重要!
  • SQL注入防护:全程用参数化查询(%s占位符),别直接拼SQL字符串,安全第一。
  • 序列号唯一性:数据库里已经给serial_number加了UNIQUE约束,后端要捕获重复序列号的异常,给用户友好提示。
  • 生产环境优化:数据库配置别硬编码,用环境变量或者配置文件;可以加个缓存来减少数据库查询次数。

这样你就能完美复用原来的表单模板,实现加载已有产品组件、追踪更换记录的功能啦!

内容的提问来源于stack exchange,提问作者Colin Roberts

火山引擎 最新活动