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

Google Sheets脚本复制行组重复问题排查求助

问题诊断与修复

核心问题分析

你的代码出现重复复制的原因主要有以下几点:

  1. 错误的数组遍历方式:使用for (i in data)遍历数组,会意外遍历到数组原型链上的属性,导致循环次数超出预期。
  2. 数据范围计算错误:getRange(5, 1, numRows, logsheet.getLastColumn())中,第三个参数是从起始行开始的行数,你直接用了numRows(整个表的最后行号),导致data数组包含大量空行,循环时重复处理。
  3. 重复触发复制逻辑:假设每条日志占33行,你循环遍历每一行时,只要该行未标记已发送,就会复制整个33行的日志组到Email表,同一组的33行都会触发一次复制,导致重复多次(你看到3份可能是测试数据只有3行满足条件)。
  4. 重复粘贴操作:同时调用了setValues(copydata)和copyTo(pastespot),这两个方法都会执行粘贴,等于一次循环里粘贴两次。
  5. 提前标记已发送:在复制完成前就把当前行标记为yes,如果后续复制失败,会导致状态异常且无法重新处理。

修复后的代码

function sendReport() {
  // 隐藏不需要的工作表
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheetsToHide = ["Today", "Enjoyable Activities Inventory", "Use Guide", "Skills List"];
  sheetsToHide.forEach(sheetName => ss.getSheetByName(sheetName).hideSheet());

  const LOG = "LOG"; // 确保这个常量已定义
  const logsheet = ss.getSheetByName(LOG);
  const emailSheet = ss.getSheetByName("Email");
  const tz = Session.getTimeZone();
  const rowsPerLog = 33; // 每条日志占的行数

  // 计算正确的数据范围:从第5行开始,到最后一行结束,行数为 lastRow - 5 + 1
  const lastRow = logsheet.getLastRow();
  if (lastRow < 5) return; // 没有日志数据,直接退出
  const dataRange = logsheet.getRange(5, 1, lastRow - 4, logsheet.getLastColumn());
  const data = dataRange.getValues();

  // 记录已经处理过的日期,避免重复处理同一组日志
  const processedDates = new Set();

  // 改用普通for循环遍历数组,避免原型链属性干扰
  for (let i = 0; i < data.length; i++) {
    const rowIndex = i + 5; // 对应的工作表行号(第5行开始)
    const info = {
      date: data[i][1],
      emailSent: data[i][44]
    };

    // 跳过非日期数据或已发送的记录
    if (!(info.date instanceof Date) || info.emailSent === 'yes') continue;

    const formattedDate = Utilities.formatDate(info.date, tz, "MM/dd/yyyy");
    // 如果该日期已经处理过,跳过
    if (processedDates.has(formattedDate)) continue;
    processedDates.add(formattedDate);

    // 复制当前日志组(rowsPerLog行)到Email表
    const copyRange = logsheet.getRange(rowIndex, 1, rowsPerLog, 44);
    const pasteStartRow = emailSheet.getLastRow() + 4;
    const pasteRange = emailSheet.getRange(pasteStartRow, 1, rowsPerLog, 44);
    copyRange.copyTo(pasteRange); // 用copyTo一次性完成复制(包含格式)

    // 标记当前日志组的所有行为已发送
    const markRange = logsheet.getRange(rowIndex, 45, rowsPerLog, 1);
    markRange.setValue('yes');

    // 发送邮件(如果需要)
    const userName = Session.getActiveUser().getUsername();
    const subject = `${userName} DBT Card Report | ${formattedDate}`;
    // GmailApp.sendEmail(治疗师邮箱地址, subject, "test");
  }
}

关键修改点说明

  • 改用普通for循环:避免for...in的遍历异常,确保只遍历数组内的有效元素。
  • 正确计算数据范围:lastRow - 4确保只获取从第5行到最后一行的有效数据,排除空行。
  • 按日期去重:用Set记录已处理的日期,同一组日志只处理一次,避免重复复制。
  • 合并粘贴操作:只保留copyTo(如果需要保留格式),去掉重复的setValues。
  • 批量标记已发送:一次性标记整个日志组的所有行为yes,避免逐行标记的重复操作。
  • 提前退出逻辑:当没有日志数据时直接退出,避免无效执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 22:35:10