JavaScript 中实现动态下拉框价格聚合计算的完整教程

6次阅读

JavaScript 中实现动态下拉框价格聚合计算的完整教程

本文介绍如何在动态创建多个 元素后,实时监听其值变化,通过 ajax 获取对应单价,并自动汇总所有已选项目的总价,全程使用原生 javaScript 实现,无需 jquery 依赖。

本文介绍如何在动态创建多个 `

在构建表单类应用(如故障报修、服务选配等)时,常需支持用户动态添加多个选择项(如“故障类型”),并为每个选项异步加载对应价格,最后实时汇总总金额。原问题中使用 jQuery 混合原生 js,且事件绑定存在重复注册与作用域混乱问题,导致聚合失效。以下是结构清晰、可维护性强的纯原生 javascript 解决方案。

✅ 核心设计原则

  • 委托监听:对容器 #newselectContainer 使用 change 事件委托,避免为每个动态 select 单独绑定事件;
  • 语义化标记:为价格展示 添加统一 class(如 pricePlaceHolder),便于精准定位;
  • 聚合解耦:将求和逻辑抽离为独立函数 countAll(),在每次选择变更后调用;
  • 健壮性处理:空值/错误响应时降级显示,不中断流程。

? 完整实现代码

<!-- HTML 结构示例(确保存在以下元素) --> <button id="createNewSelect">新增故障项</button> <div id="newSelectContainer"></div> <div>总计:<span id="total">0.00$</span></div>
const selectContainer = document.getElementById("newSelectContainer"); const totalElement = document.getElementById("total");  // 【聚合函数】遍历所有 select,累加对应价格(需先获取价格并缓存,见下文) const countAll = () => {   const prices = Array.from(selectContainer.querySelectorAll("select"))     .map(select => {       const priceSpan = select.closest(".form-group")?.querySelector(".pricePlaceHolder");       const text = priceSpan?.textContent || "";       // 提取数字:如 "(12.50 $)" → 12.50;或 "12.50$" → 12.50       const match = text.match(/([d.]+)s*$/);       return match ? parseFloat(match[1]) : 0;     })     .filter(price => !isNaN(price));    const total = prices.reduce((sum, price) => sum + price, 0);   totalElement.textContent = `${total.toFixed(2)}$`; };  // 【统一事件委托】监听所有动态 select 的 change 事件 selectContainer.addEventListener("change", function (e) {   const select = e.target;   if (!select.matches("select")) return;    const priceSpan = select.closest(".form-group")?.querySelector(".pricePlaceHolder");   if (!priceSpan) return;    const selectedId = select.value;   if (!selectedId) {     priceSpan.textContent = "";     countAll();     return;   }    // 异步获取单价   fetch(`/Home/GetPrice?selectedFaultId=${selectedId}`)     .then(res => {       if (!res.ok) throw new Error("Network response was not ok");       return res.json();     })     .then(data => {       if (data.UnitPrice === undefined) {         throw new Error("Invalid price data");       }       priceSpan.textContent = `${parseFloat(data.UnitPrice).toFixed(2)}$`;       countAll(); // 更新总计     })     .catch(err => {       console.error("价格获取失败:", err);       priceSpan.textContent = "—";       countAll();     }); });  // 【新增下拉项】点击按钮时创建新 select document.getElementById("createNewSelect").addEventListener("click", function () {   const count = selectContainer.querySelectorAll(".form-group").length + 1;    // 创建结构   const group = document.createElement("div");   group.className = "form-group";    const label = document.createElement("label");   label.className = "control-label col-md-3 col-sm-3 col-xs-12";   label.textContent = `Arıza ${count}`;    const colDiv = document.createElement("div");   colDiv.className = "col-md-6 col-sm-6 col-xs-12";    const select = document.createElement("select");   select.className = "form-control";   select.name = `fault${count}`;   select.id = `fault${count}`;    const priceSpan = document.createElement("span");   priceSpan.className = "pricePlaceHolder"; // 关键:统一 class 用于查找    // 插入 DOM   group.appendChild(label);   group.appendChild(colDiv);   colDiv.appendChild(select);   colDiv.appendChild(priceSpan);   selectContainer.appendChild(group);    // 加载选项数据   fetch("/Home/GetFaultOption")     .then(res => res.json())     .then(data => {       // 清空并填充选项       select.innerHTML = '<option value="">Lütfen Arıza Seçiniz</option>';       data.forEach(option => {         const opt = document.createElement("option");         opt.value = option.Id;         opt.textContent = option.Option;         select.appendChild(opt);       });     })     .catch(err => console.error("选项加载失败:", err)); });

⚠️ 注意事项与最佳实践

  • 不要重复绑定事件:原代码中在 $(function(){…}) 内循环绑定 change,会导致同一 select 被多次监听,引发重复请求与计算错误。本文采用事件委托,一劳永逸。
  • 价格缓存建议(进阶):若后端允许,可在首次 /GetFaultOption 响应中直接返回 UnitPrice 字段,避免后续 N 次 /GetPrice 请求,大幅提升性能。
  • 空值与校验
  • CSS 类命名一致性:确保 .form-group、.pricePlaceHolder 等类名与实际 HTML 结构严格匹配,否则 closest() 和 querySelector() 将失效。
  • 浮点精度处理:使用 toFixed(2) 格式化显示,但内部计算仍用 parseFloat() 保持精度,避免 0.1 + 0.2 !== 0.3 问题。

✅ 总结

本方案以「事件委托 + 函数式聚合」为核心,彻底解决动态表单项的价格汇总难题。代码无外部依赖、逻辑分层明确、错误处理完善,可直接集成至现有项目。后续如需扩展(如删除某一项、支持数量输入、多币种切换),均可基于此结构平滑演进。

text=ZqhQzanResources