在 Google Apps 脚本中,如何通过重新加载 doPost() 返回的 HTML 来防止多次提交表单数据?

In Google Apps Script, How to prevent multiple submission of form data by reloading the HTML returned by doPost()?

我使用 Google Apps 脚本创建了一个 Web 表单,表单访问者在提交数据后会看到 result.html。但是,如果访问者通过按 F5Ctrl + R 重新加载 result.html,数据可能会提交多次,忽略重新提交的警告。同样的问题已经发布 here, and I tried implementing one of the solutions,但没有用。

我现在在 Google Apps 脚本的同一个项目中有四个文件:

  1. index.html 生成表单
  2. JavaScript.html 定义了 index.html
  3. 中使用的函数
  4. result.html 表单提交后显示
  5. code.gsdoGet() 显示表单,并处理提交的数据并由 doPost() 呈现 result.html。此文件中定义的 include() 允许将 JavaScript.html 输入到 index.html

我尝试过的解决方案是添加以下 JavaScript 代码 result.html。我还将其添加到 JavaScript.html,这样代码也将在 index.html 中执行。

<script>
    if ( window.history.replaceState ) {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

但是,即使我将该代码添加到 result.htmlindex.html 之后,重新加载 result.html 时仍然会重新提交。我错过了什么?

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <!-- <?!= include("css"); ?> -->
  </head>

  <body onload="addOptions()">   <!--Execute addOptions function immediately after a page has been loaded-->
    <form class="" action="<?!= getScriptUrl(); ?>" method="post" onSubmit="document.getElementById('submit').disabled=true;">
      <div>
        <h1 id="Question">
          Choose either cheesecake or chocolate cake.
        </h1>
          <select id="dropdownList" name="cake" class="form-control"> 
          </select>
      </div>

      <div class="form-submit">
        <input type="submit" name="" value="Submit">
      </div>
    </form>
  </body>
  <?!= include('JavaScript') ?>
</html>

JavaScript.html

<script>
  function addOptions() {
    /*This will call server-side Apps Script function getAvailableExps and if it is successful, 
    it will pass the return value to function addListValues which will add options to the drop down menu*/
    google.script.run
      .withFailureHandler(onFailure)
      .withSuccessHandler(addListValues)
      .getAvailableExps();
  }

  function addListValues(values) { 
    //Add options to drop down menu using the values of parameter 'values'.     
    for (var i = 0; i < values.length; i++) {
      var option = document.createElement("option");
      option.text = values[i][0];
      option.value = values[i][0];
      var select = document.getElementById("dropdownList");
      select.appendChild(option);
    }
  }

  function onFailure(err) {
    alert('Error: ' + err.message);
  }

  if ( window.history.replaceState ) {
        window.history.replaceState( null, null, window.location.href );
  }
</script>

result.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <base />
    <title>Thank you for your order!</title>
    <!-- <?!= include('css'); ?> -->
  </head>
  <script>
    if ( window.history.replaceState ) {
      window.history.replaceState( null, null, window.location.href );
    }
  </script>
  <body>
    <p>
      Don't forget what you've ordered!
    </p>
  </body>
</html>

code.gs

var sheetID = "............................................";
var inventory_sheet = "Inventory";

function doGet(){
  return HtmlService.createTemplateFromFile("index").evaluate();
}

function include(filename){
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

function getScriptUrl() {
  var url = ScriptApp.getService().getUrl();
  Logger.log(url);
  return url;
}

function doPost(e){
  var ss = SpreadsheetApp.openById(sheetID);
  var sh = ss.getSheets()[0];
  sh.appendRow([String(e.parameters.cake)]);

  //update Inventory
  var inventory = ss.getSheetByName(inventory_sheet);
  var row = inventory.createTextFinder(e.parameters.cake).findNext().getRow();
  var range = inventory.getRange(row, 2);
  var data = range.getValue();
  range.setValue(parseInt(data - 1))

  return HtmlService.createTemplateFromFile("result").evaluate(); 
  
}

function getAvailableExps(){
  var inventory = SpreadsheetApp.openById(sheetID).getSheetByName(inventory_sheet);
  var data =  inventory.getRange(2, 1, 2, 2).getValues();
  var filtered = data.filter(arr =>  arr[1] > 0 || arr[1] != ''); //remove exp to array if quantity is 0 or empty
  return filtered;
}

在您的情况下,如何使用 PropertiesService 检查提交?当你的脚本被修改后,变成如下。

修改后的脚本:

本次修改修改了doGetcode.gsdoPost2个函数

doGet

function doGet() {
  PropertiesService.getScriptProperties().setProperty("key", "sample");
  return HtmlService.createTemplateFromFile("index").evaluate();
}

doPost

function doPost(e) {
  var p = PropertiesService.getScriptProperties();
  if (p.getProperty("key") == "sample") {

    var ss = SpreadsheetApp.openById(sheetID);
    var sh = ss.getSheets()[0];
    sh.appendRow([String(e.parameters.cake)]);

    //update Inventory
    var inventory = ss.getSheetByName(inventory_sheet);
    var row = inventory.createTextFinder(e.parameters.cake).findNext().getRow();
    var range = inventory.getRange(row, 2);
    var data = range.getValue();
    range.setValue(parseInt(data - 1))

    p.deleteProperty("key");
  }

  return HtmlService.createTemplateFromFile("result").evaluate();
}
  • 当您访问 Web 应用程序时,samplesetProperty("key", "sample") 存储在 doGet() 中。并且,当提交 HTML 表单时,PropertiesService 在 doPost(e) 中被选中。当sample存在时,放入数据,清除PropertiesService。这样,即使重新打开提交的页面,PropertiesService 也不存在。这样就可以避免重投了。

参考: