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

如何实现Flask模板中商品卡片点击弹出弹窗功能?

实现商品卡片点击弹出弹窗功能

问题背景

现有基于Jinja2模板的商品列表页面,希望点击商品卡片后弹出弹窗展示商品详情,尝试过结合模板循环定义变量绑定事件但未成功,现有代码如下:

{% extends "layout.html" %}

{% block content %}
    <div class="row">
      {% for product in products %}
      <div class="col-md-4 mb-3">
        <div class="card h-100">
          <img src="data:image/jpeg;base64,{{ base64.b64encode(product[3]).decode() }}" class="card-img-top" alt="{{ product[1] }}">
          <div class="card-body">
            <h5 class="card-title">{{ product[0] }}</h5>
            <p class="card-text">{{ product[1] }}</p>
            <p class="card-text">&dollar; {{ product[2] }}</p>
            <form method="POST" action="/store">
              <input type="hidden" name="product_name" value="{{ product[0] }}">
              <button type="submit" class="add-to-cart-btn">Add to Cart</button>
            </form>
          </div>
        </div>
      </div>
      {% endfor %}
    </div>
    <script>
    </script>

<style>
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"] {
  padding-left: 40px; /* Add space for the minus button */
  padding-right: 40px; /* Add space for the plus button */
}
  .button_minus {
    position: absolute;
    width: 30px;
    height: 30px;
    background: #fff;
    cursor: pointer;
    border: 2px solid #095776;
  }

  .button_minus:after {
    content: '';
    position: absolute;
    transform: translate(-50%, -50%);
    height: 4px;
    width: 50%;
    background: #095776;
    top: 50%;
    left: 50%;
  }

  .button_minus:hover:after {
    background: #fff;
    transition: 0.2s;
  }

  .button_minus:hover {
    background-color: #095776;
    transition: 0.2s;
  }

    .button_plus {
  position: absolute;
  width: 30px;
  height: 30px;
  background: #fff;
  cursor: pointer;
  border: 2px solid #095776;
}

.button_plus:after {
  content: '';
  position: absolute;
  transform: translate(-50%, -50%);
  height: 4px;
  width: 50%;
  background: #095776;
  top: 50%;
  left: 50%;
}

.button_plus:before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #095776;
  height: 50%;
  width: 4px;
}

.button_plus:hover:before,
.button_plus:hover:after {
  background: #fff;
  transition: 0.2s;
}

.button_plus:hover {
  background-color: #095776;
  transition: 0.2s;
}

</style>
{% endblock %}

解决方案

采用事件委托实现弹窗功能,避免循环绑定事件的问题,具体步骤如下:

1. 添加弹窗HTML结构

在商品列表的row容器后插入弹窗基础结构:

<!-- 弹窗容器 -->
<div id="productModal" class="modal">
  <div class="modal-content">
    <span class="close-btn">&times;</span>
    <div id="modalProductInfo">
      <!-- 商品详情将通过JS动态填充 -->
    </div>
  </div>
</div>

2. 为商品卡片添加数据标识

在循环生成的card元素上添加data-*属性,存储商品核心信息,方便点击时快速获取:

<div class="card h-100" 
     data-name="{{ product[0] }}" 
     data-desc="{{ product[1] }}" 
     data-price="{{ product[2] }}"
     data-image="data:image/jpeg;base64,{{ base64.b64encode(product[3]).decode() }}">
  <!-- 原有卡片内容保持不变 -->
</div>

3. 编写JavaScript交互逻辑

在<script>标签中添加弹窗的显示、隐藏及内容填充逻辑:

// 获取弹窗相关元素
const modal = document.getElementById('productModal');
const closeBtn = document.querySelector('.close-btn');
const modalInfo = document.getElementById('modalProductInfo');

// 事件委托:监听父容器的点击事件
document.querySelector('.row').addEventListener('click', function(e) {
  // 定位到点击的卡片元素
  const card = e.target.closest('.card');
  if (!card) return;

  // 从data属性提取商品信息
  const productName = card.dataset.name;
  const productDesc = card.dataset.desc;
  const productPrice = card.dataset.price;
  const productImage = card.dataset.image;

  // 填充弹窗内容
  modalInfo.innerHTML = `
    <img src="${productImage}" class="img-fluid mb-3" alt="${productName}">
    <h3>${productName}</h3>
    <p>${productDesc}</p>
    <p class="fw-bold">$${productPrice}</p>
  `;

  // 显示弹窗
  modal.style.display = 'block';
});

// 关闭按钮事件
closeBtn.addEventListener('click', function() {
  modal.style.display = 'none';
});

// 点击弹窗外部关闭
window.addEventListener('click', function(e) {
  if (e.target === modal) {
    modal.style.display = 'none';
  }
});

4. 添加弹窗样式

在<style>标签中补充弹窗的基础样式:

/* 弹窗背景遮罩 */
.modal {
  display: none; /* 默认隐藏 */
  position: fixed;
  z-index: 1000;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgba(0,0,0,0.5);
}

/* 弹窗内容容器 */
.modal-content {
  background-color: #fefefe;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #888;
  width: 80%;
  max-width: 500px;
  border-radius: 8px;
  position: relative;
}

/* 关闭按钮样式 */
.close-btn {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
  cursor: pointer;
}

.close-btn:hover,
.close-btn:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}

完整修改后的代码

整合上述部分后的最终content块代码:

{% block content %}
    <div class="row">
      {% for product in products %}
      <div class="col-md-4 mb-3">
        <div class="card h-100" 
             data-name="{{ product[0] }}" 
             data-desc="{{ product[1] }}" 
             data-price="{{ product[2] }}"
             data-image="data:image/jpeg;base64,{{ base64.b64encode(product[3]).decode() }}">
          <img src="data:image/jpeg;base64,{{ base64.b64encode(product[3]).decode() }}" class="card-img-top" alt="{{ product[1] }}">
          <div class="card-body">
            <h5 class="card-title">{{ product[0] }}</h5>
            <p class="card-text">{{ product[1] }}</p>
            <p class="card-text">&dollar; {{ product[2] }}</p>
            <form method="POST" action="/store">
              <input type="hidden" name="product_name" value="{{ product[0] }}">
              <button type="submit" class="add-to-cart-btn">Add to Cart</button>
            </form>
          </div>
        </div>
      </div>
      {% endfor %}
    </div>

    <!-- 弹窗容器 -->
    <div id="productModal" class="modal">
      <div class="modal-content">
        <span class="close-btn">&times;</span>
        <div id="modalProductInfo">
          <!-- 商品详情将通过JS动态填充 -->
        </div>
      </div>
    </div>

    <script>
      // 获取弹窗相关元素
      const modal = document.getElementById('productModal');
      const closeBtn = document.querySelector('.close-btn');
      const modalInfo = document.getElementById('modalProductInfo');

      // 事件委托:监听父容器的点击事件
      document.querySelector('.row').addEventListener('click', function(e) {
        // 定位到点击的卡片元素
        const card = e.target.closest('.card');
        if (!card) return;

        // 从data属性提取商品信息
        const productName = card.dataset.name;
        const productDesc = card.dataset.desc;
        const productPrice = card.dataset.price;
        const productImage = card.dataset.image;

        // 填充弹窗内容
        modalInfo.innerHTML = `
          <img src="${productImage}" class="img-fluid mb-3" alt="${productName}">
          <h3>${productName}</h3>
          <p>${productDesc}</p>
          <p class="fw-bold">$${productPrice}</p>
        `;

        // 显示弹窗
        modal.style.display = 'block';
      });

      // 关闭按钮事件
      closeBtn.addEventListener('click', function() {
        modal.style.display = 'none';
      });

      // 点击弹窗外部关闭
      window.addEventListener('click', function(e) {
        if (e.target === modal) {
          modal.style.display = 'none';
        }
      });
    </script>

<style>
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"] {
  padding-left: 40px; /* Add space for the minus button */
  padding-right: 40px; /* Add space for the plus button */
}
  .button_minus {
    position: absolute;
    width: 30px;
    height: 30px;
    background: #fff;
    cursor: pointer;
    border: 2px solid #095776;
  }

  .button_minus:after {
    content: '';
    position: absolute;
    transform: translate(-50%, -50%);
    height: 4px;
    width: 50%;
    background: #095776;
    top: 50%;
    left: 50%;
  }

  .button_minus:hover:after {
    background: #fff;
    transition: 0.2s;
  }

  .button_minus:hover {
    background-color: #095776;
    transition: 0.2s;
  }

    .button_plus {
  position: absolute;
  width: 30px;
  height: 30px;
  background: #fff;
  cursor: pointer;
  border: 2px solid #095776;
}

.button_plus:after {
  content: '';
  position: absolute;
  transform: translate(-50%, -50%);
  height: 4px;
  width: 50%;
  background: #095776;
  top: 50%;
  left: 50%;
}

.button_plus:before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #095776;
  height: 50%;
  width: 4px;
}

.button_plus:hover:before,
.button_plus:hover:after {
  background: #fff;
  transition: 0.2s;
}

.button_plus:hover {
  background-color: #095776;
  transition: 0.2s;
}

/* 弹窗样式 */
.modal {
  display: none; /* 默认隐藏 */
  position: fixed;
  z-index: 1000;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgba(0,0,0,0.5);
}

.modal-content {
  background-color: #fefefe;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #888;
  width: 80%;
  max-width: 500px;
  border-radius: 8px;
  position: relative;
}

.close-btn {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
  cursor: pointer;
}

.close-btn:hover,
.close-btn:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}

</style>
{% endblock %}

关键说明

  • 事件委托:通过监听父容器的点击事件,统一处理所有卡片的点击,避免循环绑定的冗余问题,同时兼容动态新增的商品卡片。
  • data-*属性:直接存储商品信息,无需从DOM节点中逐个提取文本,代码更简洁高效。
  • 弹窗交互:实现了点击卡片显示弹窗、点击关闭按钮/弹窗外部隐藏弹窗的完整交互逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:57:00