Google Apps Script Code Snippets

这是一个专门收集和整理 Google Apps Script(GAS)常用代码片段的网站。每个代码片段都附带详细的说明和注释,方便理解和使用,帮助优化工作流程,提高开发效率,让脚本编写更加轻松高效。

需要添加 Drive 服务,并且设置为 v2 版本。

/**
 * @description 识别图片中的文字
 * @param {string} imageUrl - 图片链接
 * @returns {string} 返回识别出的文本内容
 */
function imageOCR (imageUrl) {
  const imageBlob = UrlFetchApp.fetch(imageUrl).getBlob()
  const options = { ocr: true }
  const resource = {
    title: imageBlob.getName(),
    mimeType: imageBlob.getContentType()
  }
  const docFile = Drive.Files.insert(resource, imageBlob, options)
  const doc = DocumentApp.openById(docFile.id)
  const text = doc.getBody().getText().replace(/\n+/g, ' ').trim()
  Drive.Files.remove(docFile.id)
  return text
}

通过表格链接中的 gid 参数匹配出指定的工作表对象。

/**
 * @description 根据 Google Sheets 链接的 gid 参数获取指定表格
 * @param {string} sheetUrl - Google Sheets 的 URL 地址
 * @returns {GoogleAppsScript.Spreadsheet.Sheet} 返回匹配的工作表对象
 */
function getSheet (sheetUrl) {
  const ss = SpreadsheetApp.openByUrl(sheetUrl)
  const gid = sheetUrl.match(/(?<=gid=).*[0-9]/g, '')[0]
  return ss.getSheets().find(function (s) {
    return s.getSheetId() === parseInt(gid)
  })
}

将数字转换成表格列字母;把表格列字母转换为数字。

/**
 * @description 将列字母转换为数字
 * @param {string} column - 列名称(如 'A', 'Z', 'AA')
 * @returns {number} 对应的数字(如 1, 26, 27)
 */
const columnToNumber = column => {
  let number = 0
  for (let i = 0; i < column.length; i++) {
    number = number * 26 + (column.charCodeAt(i) - 64)
  }
  return number
}

/**
 * @description 将数字转换为列字母
 * @param {number} num - Excel 列号(如 1, 26, 27)
 * @returns {string} 对应的 Excel 列名称(如 'A', 'Z', 'AA')
 */
const numberToColumn = num => {
  let column = ''
  while (num > 0) {
    const remainder = (num - 1) % 26
    column = String.fromCharCode(65 + remainder) + column
    num = Math.floor((num - 1) / 26)
  }
  return column
}

从另外一个电子表格把数据同步到当前表格。需要修改「数据表格ID」、「数据表格名字」、「当前表格名字」和同步数据的范围。

// 同步数据到当前表格
function syncData () {
  const dataSheet = SpreadsheetApp.openById('数据表格ID').getSheetByName('数据表格名字')
  const data = dataSheet.getRange('A:F').getValues()
  const currentSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('当前表格名字')
  // 清空原始内容
  currentSheet.getRange('A:F').clearContent()
  currentSheet.getRange(1, 1, data.length, data[0].length).setValues(data)
}

在菜单栏加载自定义菜单,打开表格后自动加载。需要需要改对应的函数名称才会有功能。

// 启动菜单
function onOpen () {
  const ui = SpreadsheetApp.getUi()
  // 菜单栏显示按钮
  ui.createMenu('主菜单')
    .addItem('按钮1', 'menuItem1')
    .addItem('按钮2', 'menuItem2')
    // 分割线
    .addSeparator()
    // 添加子菜单
    .addSubMenu(ui.createMenu('子菜单')
      .addItem('子菜单1', 'menuItem2')
      .addItem('子菜单2', 'menuItem2'))
    .addToUi()
}

在菜单栏加载自定义菜单,打开表格后自动加载。需要需要改对应的函数名称才会有功能。

const menuConfig = [
  ['按钮1', 'menuItem1'],
  ['按钮2', 'menuItem2'],
  '---', // 分隔线
  ['分类1', [
    ['归档1', [
      ['按钮3', 'menuItem3'],
      ['按钮4', 'menuItem4'],
      ['按钮5', 'menuItem5']
    ]],
    ['归档2', [
      ['按钮6', 'menuItem6'],
      ['按钮7', 'menuItem7'],
      ['按钮8', 'menuItem8']
    ]],
    ['归档3', [
      ['按钮9', 'menuItem9'],
      ['按钮10', 'menuItem10'],
      ['按钮11', 'menuItem11']
    ]]
  ]],
  ['分类2', [
      ['按钮12', 'menuItem12'],
      ['按钮13', 'menuItem13']
  ]],
  ['分类3', [
    ['按钮14', 'menuItem14'],
    ['按钮15', 'menuItem15'],
    '---',
    ['按钮16', 'menuItem16'],
    ['按钮17', 'menuItem17']
  ]]
]

function onOpen () {
  const ui = SpreadsheetApp.getUi()
  const rootMenu = ui.createMenu('🧰 主菜单')
  buildMenu(ui, rootMenu, menuConfig)
  rootMenu.addToUi()
}

/**
 * @description 递归构建菜单和子菜单,根据传入的配置动态生成层级结构。
 * @param {Object} ui - SpreadsheetApp 的 UI 实例,用于创建菜单项和子菜单。
 * @param {Object} parent - 当前正在构建的菜单或子菜单对象。
 * @param {Array} menuConfig - 当前层级的菜单配置数组,支持普通项、分隔线和嵌套结构。
 */
function buildMenu (ui, parent, menuConfig) {
  menuConfig.forEach(item => {
    if (item === '---') {
      parent.addSeparator()
    } else if (Array.isArray(item[1])) {
      // 子菜单
      const subMenu = ui.createMenu(item[0])
      buildMenu(ui, subMenu, item[1])
      parent.addSubMenu(subMenu)
    } else {
      // 普通菜单项
      parent.addItem(item[0], item[1])
    }
  })
}

下面例子介绍了前端不同的请求类型和 GAS 的不同处理数据的方式。

  • 使用 JSON POST 适合传输结构化的数据(如对象、数组),并且数据较复杂。
  • 使用 FormData POST 适合提交表单数据,尤其是文件上传或简单的表单提交。
  • 使用 GET 请求 适合查询操作,数据量较小,且无需提交大量内容。

1. JSON 格式的 POST 请求

// GAS 服务端
function doPost (request) {
  const json = JSON.parse(request.postData.contents)
  return ContentService.createTextOutput('success')
}

// 前端 fetch
const params = {
  action: '写入',
  content: '测试'
}
await fetch(api, {
  body: JSON.stringify(params),
  method: 'POST'
}).then(response => response.text())

2. FormData 格式的 POST 请求

// GAS 服务端
function doPost (request) {
  const json = request.parameter
  return ContentService.createTextOutput('ok')
}

// 前端 fetch
const body = new FormData()
body.append('action', '写入')
body.append('content', '测试')
await fetch(api, {
  body,
  method: 'POST'
}).then(response => response.text())

3. GET 请求(参数拼接在 URL 里)

function doGet (request) {
  const params = request.parameter
  return ContentService.createTextOutput('ok')
 }

// 前端 fetch
const params = {
  action: '写入',
  content: '测试'
}
await fetch(`${api}?${new URLSearchParams(params).toString()}`, {
  body,
  method: 'POST'
}).then(response => response.text())

在菜单栏加载自定义菜单,打开表格后自动加载。需要需要改对应的函数名称才会有功能。

// 删除多余行和列
function clearRowsColumns () {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表格名字')
  // 删除多余行
  const maxRows = sheet.getMaxRows()
  const lastRow = sheet.getLastRow()
  if (lastRow !== 0 && maxRows - lastRow !== 0) sheet.deleteRows(lastRow + 1, maxRows - lastRow)
  // 删除多余列
  const maxColumns = sheet.getMaxColumns()
  const lastColumn = sheet.getLastColumn()
  if (lastColumn !== 0 && maxColumns - lastColumn !== 0) sheet.deleteColumns(lastColumn + 1, maxColumns - lastColumn)
}

根据指定内容删除行或者列,需要设置完整的范围,例如一整行或者一整列。

根据指定内容删除行

// 根据指定内容删除行
function deleteRowByContent () {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表格名字')
  // 检测的范围
  const title = sheet.getRange('A:A').getValues()
  for (let i = title.length - 1; i >= 0; i--) {
    console.log(title[i], i)
    // 判断的内容
    if (title[i][0] === '备注') {
      sheet.deleteRow(i + 1)
    }
  }
}

根据指定内容删除列

// 根据指定内容删除列
function deleteColumnByContent () {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表格名字')
  // 检测的范围
  const title = sheet.getRange('1:1').getValues()[0]
  for (let i = title.length - 1; i >= 0; i--) {
    // 判断的内容
    if (title[i] === '备注') {
      sheet.deleteColumns(i + 1)
    }
  }
}

移除重复内容。

// 移除重复内容
function removeDuplicate () {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表格名字')
  sheet.getRange('A:R').removeDuplicates()
  // 根据指定列内容排除重复
  // sheet.getRange('A:R').removeDuplicates([4, 10])
}

根据函数名批量添加定时器。函数名为数组格式。

// 批量添加定时器
function setupTriggers () {
  const funcList = ['函数名']
  // 遍历函数名称数组,为每个函数设置定时器
  funcList.forEach(funcName => {
    ScriptApp.newTrigger(funcName)
      .timeBased()
      // 天定时器
      // .atHour(2) // 0 ~ 23
      // .everyDays(1)

      // 小时定时器
      // .everyHours(3) // 1, 2, 4, 6, 8, 12

      // 分钟定时器
      // .everyMinutes(2)  // 1, 5, 10, 15 or 30
      .create()
  })
}

删除指定名字的定时器。

// 删除指定名字的定时器
function deleteTriggersByName () {
  // 获取所有定时器
  const triggers = ScriptApp.getProjectTriggers()
  // 遍历所有触发器
  for (let i = 0; i < triggers.length; i++) {
    const trigger = triggers[i]
    // 获取定时器名称
    const triggerFunctionName = trigger.getHandlerFunction()
    // 检测是否包含指定名字
    if (triggerFunctionName.includes('需要删除的定时器名字')) {
      ScriptApp.deleteTrigger(trigger)
    }
  }
}

删除所有定时器。

// 删除所有定时器
function deleteAllTriggers () {
  // 获取所有定时器
  const triggers = ScriptApp.getProjectTriggers()
  for (let i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i])
  }
}