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

JavaScript异步函数重复执行引发多文件下载异常求助

问题描述

CalculateMedia函数嵌套在calculate()函数内部,每次点击特定按钮会触发calculate()执行。当前遇到的问题是:每点击一次计算按钮,后续点击下载按钮时,会下载所有之前生成过的文件,而非仅下载最后一次计算生成的文件。

问题根源

每次调用calculate()都会执行CalculateMedia,而CalculateMedia内部会重复给downloadButton和downloadButtonES添加点击事件监听器。例如:

  • 第一次点击计算按钮,给下载按钮绑定1次下载回调;
  • 第二次点击计算按钮,又绑定1次回调,此时下载按钮上有2个监听器;
  • 点击下载时,所有绑定的回调都会触发,导致生成并下载多个历史文件。
解决方案

方案1:分离计算与下载逻辑(推荐)

将下载按钮的事件监听逻辑移到CalculateMedia外部,只绑定一次。在CalculateMedia中仅计算并保存最新的表单数据,下载时直接使用最新数据生成文件。

修改后的代码示例:

// 存储最新计算的数据
let latestCalculationData = {};

function calculate() {
  CalculateMedia();
}

function CalculateMedia() {
  // 原有获取表单值、计算逻辑保持不变
  let TrienalHolder = document.getElementById('trienal');
  let trienal = (TrienalHolder.options[TrienalHolder.selectedIndex]).textContent;

  let BienalIHolder = document.getElementById('bienal_i');
  let bienal_i = (BienalIHolder.options[BienalIHolder.selectedIndex]).textContent;

  let BienalIIHolder = document.getElementById('bienal_ii');
  let bienal_ii = (BienalIIHolder.options[BienalIIHolder.selectedIndex]).textContent;

  let pt10 = document.getElementById("pt_10").value;
  let pt11 = document.getElementById("pt_11").value;
  let pt12 = document.getElementById("pt_12").value;

  let le10 = document.getElementById("le_10").value;
  let le11 = document.getElementById("le_11").value;

  let fil10 = document.getElementById("fil_10").value;
  let fil11 = document.getElementById("fil_11").value;

  let trienal10 = document.getElementById("trienal_10").value;
  let trienal11 = document.getElementById("trienal_11").value;
  let trienal12 = document.getElementById("trienal_12").value;

  let bienali10 = document.getElementById("bienal_i_10").value;
  let bienali11 = document.getElementById("bienal_i_11").value;

  let bienalii10 = document.getElementById("bienal_ii_10").value;
  let bienalii11 = document.getElementById("bienal_ii_11").value;

  let edf10 = document.getElementById("edf_10").value;
  let edf11 = document.getElementById("edf_11").value;
  let edf12 = document.getElementById("edf_12").value;

  let anuali = document.getElementById("anual_i").value;
  let anualii = document.getElementById("anual_ii").value;

  let Pi1 = document.getElementById("pi_pt").value;
  let Pi2 = document.getElementById("pi_le").value;
  let Pi3 = document.getElementById("pi_fil").value;
  let Pi4 = document.getElementById("pi_trienal").value;
  let Pi5 = document.getElementById("pi_bienal_i").value;
  let Pi6 = document.getElementById("pi_bienal_ii").value;

  let PPI = document.getElementById("peso_pi").value;

  //Média Final de cada disciplina
  let pt_final = (parseFloat(pt10) + parseFloat(pt11) + parseFloat(pt12)) / 3;
  let le_final = (parseFloat(le10) + parseFloat(le11)) / 2;
  let fil_final = (parseFloat(fil10) + parseFloat(fil11)) / 2;
  let trienal_final = (parseFloat(trienal10) + parseFloat(trienal11) + parseFloat(trienal12)) / 3;
  let bienal_i_final = (parseFloat(bienali10) + parseFloat(bienali11)) / 2;
  let bienal_ii_final = (parseFloat(bienalii10) + parseFloat(bienalii11)) / 2;
  let edf_final = (parseFloat(edf10) + parseFloat(edf11) + parseFloat(edf12)) / 3;
  let anual_i_final = parseFloat(anuali);
  let anual_ii_final = parseFloat(anualii);

  let PiPt = (parseFloat(Pi1)) / 10;
  let PiLe = (parseFloat(Pi2)) / 10;
  let PiFil = (parseFloat(Pi3)) / 10;
  let PiTrienal = (parseFloat(Pi4)) / 10;
  let PiBienali = (parseFloat(Pi5)) / 10;
  let PiBienalii = (parseFloat(Pi6)) / 10;

  let PesoProvasIngresso = (parseFloat(PPI)) / 100;
  let PesoMediaFinal = 1 - PesoProvasIngresso;

  let MediaFinalSecundario = ((pt_final + le_final + fil_final + trienal_final + bienal_i_final + bienal_ii_final + edf_final + anual_i_final + anual_ii_final) / 9).toFixed(2);

  let piArray = [PiPt, PiLe, PiFil, PiTrienal, PiBienali, PiBienalii];
  let validPiArray = piArray.filter(function(value) {
    return !isNaN(value);
  });

  let MediaProvasIngresso = validPiArray.length > 0 ? validPiArray.reduce(function(acc, value) {
    return acc + value;
  }, 0) / validPiArray.length : 0;

  let MediaFinalAcessoES = ((MediaFinalSecundario * PesoMediaFinal) + (MediaProvasIngresso * PesoProvasIngresso)).toFixed(2);

  let checkboxes = document.querySelectorAll("[id^='check_']");
  let atLeastOneChecked = false;

  for (let i = 0; i < checkboxes.length; i++) {
    if (checkboxes[i].checked) {
      atLeastOneChecked = true;
      break;
    }
  }

  if (atLeastOneChecked) {
    document.getElementById("media-secundarioES").textContent = `${MediaFinalSecundario}`;
    document.getElementById("media-ES").textContent = `${MediaFinalAcessoES}`;
    $('#notafinalES').modal('show');
  } else {
    document.getElementById("media-secundario").textContent = `${MediaFinalSecundario}`;
    $('#notafinal').modal('show');
  }

  // 将最新计算结果存储到全局变量
  latestCalculationData = {
    trienal,
    bienal_i,
    bienal_ii,
    pt10, pt11, pt12,
    le10, le11,
    fil10, fil11,
    trienal10, trienal11, trienal12,
    bienali10, bienali11,
    bienalii10, bienalii11,
    edf10, edf11, edf12,
    anuali, anualii,
    Pi1, Pi2, Pi3, Pi4, Pi5, Pi6,
    PPI,
    MediaFinalSecundario,
    MediaFinalAcessoES
  };
}

// 独立的Excel生成下载函数
async function generateAndDownloadExcel(fillB19 = true) {
  const {
    trienal, bienal_i, bienal_ii,
    pt10, pt11, pt12,
    le10, le11,
    fil10, fil11,
    trienal10, trienal11, trienal12,
    bienali10, bienali11,
    bienalii10, bienalii11,
    edf10, edf11, edf12,
    anuali, anualii,
    Pi1, Pi2, Pi3, Pi4, Pi5, Pi6,
    PPI,
    MediaFinalSecundario,
    MediaFinalAcessoES
  } = latestCalculationData;

  const workbook = new ExcelJS.Workbook();
  const response = await fetch('ExcelTemplate.xlsx');
  const arrayBuffer = await response.arrayBuffer();

  await workbook.xlsx.load(arrayBuffer);
  const sheet = workbook.getWorksheet(1);

  // 更新单元格值
  sheet.getCell('B6').value = pt10;
  sheet.getCell('C6').value = pt11;
  sheet.getCell('D6').value = pt12;

  sheet.getCell('B7').value = le10;
  sheet.getCell('C7').value = le11;

  sheet.getCell('B8').value = fil10;
  sheet.getCell('C8').value = fil11;

  sheet.getCell('B9').value = trienal10;
  sheet.getCell('C9').value = trienal11;
  sheet.getCell('D9').value = trienal12;

  sheet.getCell('B10').value = bienali10;
  sheet.getCell('C10').value = bienali11;

  sheet.getCell('B11').value = bienalii10;
  sheet.getCell('C11').value = bienalii11;

  sheet.getCell('B12').value = edf10;
  sheet.getCell('C12').value = edf11;
  sheet.getCell('D12').value = edf12;

  sheet.getCell('D13').value = anuali;
  sheet.getCell('D14').value = anualii;

  sheet.getCell('E6').value = Pi1;
  sheet.getCell('E7').value = Pi2;
  sheet.getCell('E8').value = Pi3;
  sheet.getCell('E9').value = Pi4;
  sheet.getCell('E10').value = Pi5;
  sheet.getCell('E11').value = Pi6;

  sheet.getCell('B16').value = PPI;

  sheet.getCell('B18').value = MediaFinalSecundario;
  if (fillB19) {
    sheet.getCell('B19').value = MediaFinalAcessoES;
  }

  sheet.getCell('A9').value = trienal;
  sheet.getCell('A10').value = bienal_i;
  sheet.getCell('A11').value = bienal_ii;

  // 生成并下载文件
  const buffer = await workbook.xlsx.writeBuffer();
  const blob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  let currentDate = new Date().toLocaleDateString().replaceAll('/', '-');
  let fileName = `MediaCalculada[${currentDate}].xlsx`;
  a.href = url;
  a.download = fileName;
  a.click();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// 页面加载完成后只绑定一次下载事件
document.addEventListener('DOMContentLoaded', () => {
  const downloadButton = document.getElementById('downloadButton');
  const downloadButtonES = document.getElementById('downloadButtonES');

  downloadButton?.addEventListener('click', () => {
    generateAndDownloadExcel(false);
  });

  downloadButtonES?.addEventListener('click', () => {
    generateAndDownloadExcel();
  });
});

方案2:绑定新监听器前移除旧监听器

如果必须在CalculateMedia内部绑定事件,可以通过保存回调引用的方式,每次绑定前移除旧监听器:

// 保存回调引用,用于移除旧监听器
let downloadBtnCallback, downloadBtnESCallback;

function CalculateMedia() {
  // 原有计算逻辑...

  // 先移除旧的监听器
  const downloadButton = document.getElementById('downloadButton');
  const downloadButtonES = document.getElementById('downloadButtonES');
  if (downloadBtnCallback && downloadButton) {
    downloadButton.removeEventListener('click', downloadBtnCallback);
  }
  if (downloadBtnESCallback && downloadButtonES) {
    downloadButtonES.removeEventListener('click', downloadBtnESCallback);
  }

  // 定义新的回调并绑定
  downloadBtnCallback = () => generateAndDownloadExcel(false);
  downloadBtnESCallback = () => generateAndDownloadExcel();

  downloadButton?.addEventListener('click', downloadBtnCallback);
  downloadButtonES?.addEventListener('click', downloadBtnESCallback);

  // 原有generateAndDownloadExcel函数保持不变...
}
说明

方案1更推荐,它将计算逻辑与下载逻辑分离,彻底避免了重复绑定事件的问题,同时代码结构更清晰,能确保下载时始终使用最新的计算数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 21:04:54