本文将详细说明如何借助 Google Apps Script,在 QR 码扫码完成后自动检测 Google Sheets 指定列中是否已经存在该内容:如果发现重复,则在 HTML 页面实时提示“Already existing”;如果不存在,则写入新的数据,从而实现表格数据去重与唯一性校验。

本文介绍如何通过 Google Apps Script 实现 QR 扫码后自动检查 Google Sheets 列中是否已存在该值,若重复则在 HTML 页面显示“Already existing”,否则写入新数据,确保数据唯一性。
在搭建基于 Google Web App 的 QR 扫码录入系统时,如果只是依赖前端缓存中的 lastResult 进行判断,其实无法真正保证 Google Sheets 中的数据唯一性。原因很简单:表格里可能早就已经保存了相同记录;此外,用户如果多次扫描同一个二维码,即使中间扫描过其他内容,系统依然有可能把重复值再次写入表格。更稳定、也更安全的实现方式是:每次扫码成功后,都由服务端(Apps Script)实时检查整个 Google Sheet 数据,先确认该值是否已存在,再决定是写入新行,还是返回重复提示信息。
✅ 正确实现逻辑(前后端协同)
1. 前端 Ja vaScript:使用 withSuccessHandler 接收结构化响应
原始代码中直接调用 google.script.run.sendback(decodedText) 属于异步执行,而且没有处理返回结果,因此前端无法判断当前扫码内容是否重复。正确做法是配合 withSuccessHandler 接收 Apps Script 服务端返回的结构化对象:
async function onScanSuccess(decodedText, decodedResult) {
// 封装为对象,便于传递和扩展
const payload = { text: decodedText };
google.script.run
.withSuccessHandler((response) => {
if (response.result === "fail") {
$('#qr-reader-results').prepend('⚠ Already existing.
');
console.log('Duplicate detected:', decodedText);
} else if (response.result === "ok") {
$('#qr-reader-results').prepend(`✅ Scanned: ${decodedText}
`);
console.log('New entry added:', decodedText);
}
})
.sendback(payload); // 传入对象,非纯字符串
}⚠ 注意:google.script.run 默认不会等待 Promise,withSuccessHandler 是其标准的回调处理机制,不能与 await 混合使用;onScanSuccess 本身也不一定需要声明为 async(除非函数内部还有其他 await 操作)。
2. 后端 Apps Script:高效查重 + 原子写入
这一部分的核心优化包括:
- 使用
getDataRange().getValues()一次性获取表格全部数据(适合千行以内的 Google Sheets 去重场景); - 遍历第一列(Column A)并进行严格匹配判断;
- 统一返回结构化对象(包含
result和text),避免前后端通信时出现类型歧义; - 在写入数据前确保
getLastRow()结果准确(建议结合空行校验,见下方优化建议)。
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
function sendback(payload) {
const searchText = payload.text?.toString().trim();
if (!searchText) {
return { result: "fail", text: searchText, reason: "Empty input" };
}
// 获取全部数据(含空行,但 getValues() 自动截断末尾空行)
const data = sheet.getDataRange().getValues();
// 查找 Column A(索引 0)中是否已存在该值(精确匹配,区分大小写)
for (let i = 0; i < data.length; i++) {
if (data[i][0] === searchText) {
return { result: "fail", text: searchText };
}
}
// 无重复 → 写入新行
const nextRow = sheet.getLastRow() + 1;
sheet.getRange(nextRow, 1).setValue(searchText);
return { result: "ok", text: searchText };
}3. 增强健壮性(推荐补充)
- 性能优化(大数据量):如果 Google Sheets 行数超过 5000,建议改用
sheet.getRange("A:A").getValues(),再通过.filter(r => r[0] !== "")缩小遍历范围,以提升查重效率; - 空行防护:
getLastRow()在首行为空时可能不够准确,可以改为:const lastRow = Math.max(sheet.getLastRow(), 1); const nextRow = sheet.getRange("A" + lastRow).getValue() ? lastRow + 1 : lastRow; - 去空格/大小写归一化(按需启用):
if (String(data[i][0]).trim().toLowerCase() === searchText.trim().toLowerCase())
4. HTML 页面提示优化(用户体验)
可以在 中插入样式更清晰的提示信息,例如:
✅ Scanned: ABC123
⚠ Already existing.
? 总结
- ❌ 不推荐做法:只在前端比对
lastResult,或者调用google.script.run后不处理异步返回结果; - ✅ 正确实现方式:服务端查重 → 返回结构化结果 → 前端通过
withSuccessHandler分支处理; - ? 安全提示:本方案的所有关键校验都发生在服务端,能够避免前端被篡改后导致的数据重复问题,提升数据一致性;
- ? 扩展建议:还可以继续加入时间戳、用户 ID、防抖与限频等字段,逐步搭建完整的二维码扫码日志管理系统。
通过上述优化,你的 Google Web App 就能真正实现「Google Sheets 级别去重」,有效防止重复录入,并为用户提供清晰、即时的扫码反馈体验。
