Sao chép macro sang các bảng tính khác trên Google Trang tính

Cấp độ lập trình: Trung cấp
Thời lượng: 30 phút
Loại dự án: Tiện ích bổ sung của Google Workspace

Mục tiêu

  • Tìm hiểu chức năng của giải pháp.
  • Tìm hiểu chức năng của các dịch vụ Apps Script trong giải pháp.
  • Thiết lập môi trường.
  • Thiết lập tập lệnh.
  • Chạy tập lệnh.

Giới thiệu về giải pháp này

Việc sao chép macro trong Google Trang tính theo cách thủ công từ bảng tính này sang bảng tính khác có thể tốn thời gian và dễ xảy ra lỗi. Tiện ích bổ sung này của Google Workspace sẽ tự động sao chép một dự án tập lệnh và đính kèm dự án đó vào một bảng tính do người dùng chỉ định. Mặc dù giải pháp này tập trung vào macro trong Trang tính, nhưng bạn có thể dùng giải pháp này để sao chép và chia sẻ mọi tập lệnh liên kết với vùng chứa.

Tiện ích bổ sung Share Macro của Google Workspace

Tiện ích bổ sung Share Macro của Google Workspace

Cách hoạt động

Tập lệnh này sẽ sao chép dự án Apps Script được liên kết với bảng tính gốc và tạo một dự án Apps Script trùng lặp được liên kết với bảng tính do người dùng chỉ định.

Dịch vụ Apps Script

Giải pháp này sử dụng các dịch vụ sau:

Điều kiện tiên quyết

Để sử dụng mẫu này, bạn cần đáp ứng các điều kiện tiên quyết sau:

  • Tài khoản Google (tài khoản Google Workspace có thể cần được quản trị viên phê duyệt).
  • Trình duyệt web có quyền truy cập vào Internet.

  • Dự án trên Google Cloud.

  • API Google Apps Script được bật trong phần cài đặt trang tổng quan Apps Script.

Thiết lập môi trường

Mở dự án trên đám mây trong bảng điều khiển Cloud

Nếu chưa mở, hãy mở dự án trên đám mây mà bạn định sử dụng cho mẫu này:

  1. Trong Bảng điều khiển Google Cloud, hãy chuyển đến trang Select a project (Chọn dự án).

    Chọn dự án trên đám mây

  2. Chọn dự án trên Google Cloud mà bạn muốn sử dụng. Hoặc nhấp vào Create project (Tạo dự án) rồi làm theo hướng dẫn trên màn hình. Nếu tạo dự án trên Google Cloud, bạn có thể cần bật tính năng thanh toán cho dự án đó.

Bật API Google Apps Script

Hướng dẫn nhanh này sử dụng API Google Apps Script.

Trước khi sử dụng API của Google, bạn cần bật các API đó trong một dự án trên Google Cloud. Bạn có thể bật một hoặc nhiều API trong một dự án trên Google Cloud.
  • Trong dự án trên đám mây, hãy bật API Google Apps Script.

    Bật API

Tiện ích bổ sung của Google Workspace yêu cầu cấu hình màn hình xin phép. Việc định cấu hình màn hình xin phép bằng OAuth cho tiện ích bổ sung sẽ xác định những nội dung mà Google hiển thị cho người dùng.

  1. Trong Bảng điều khiển API của Google, hãy chuyển đến Trình đơn > Google Auth platform > Branding.

    Chuyển đến phần Thương hiệu

  2. Nếu đã định cấu hình Nền tảng xác thực của Google, bạn có thể định cấu hình các chế độ cài đặt sau đây cho Màn hình xin phép bằng OAuth trong phần Thương hiệu, Đối tượngQuyền truy cập dữ liệu. Nếu bạn thấy thông báo cho biết Google Auth platform not configured yet (Nền tảng xác thực của Google chưa được định cấu hình), hãy nhấp vào Get Started (Bắt đầu):
    1. Trong phần App Information (Thông tin ứng dụng), ở mục App name (Tên ứng dụng), hãy nhập tên cho ứng dụng.
    2. Trong mục User support email (Email hỗ trợ người dùng), hãy chọn một địa chỉ email hỗ trợ mà người dùng có thể liên hệ với bạn nếu họ có câu hỏi về việc đồng ý.
    3. Nhấp vào Tiếp theo.
    4. Trong phần Đối tượng, hãy chọn Nội bộ.
    5. Nhấp vào Tiếp theo.
    6. Trong phần Contact Information (Thông tin liên hệ), hãy nhập Email address (Địa chỉ email) mà bạn có thể nhận thông báo về mọi thay đổi đối với dự án của mình.
    7. Nhấp vào Tiếp theo.
    8. Trong phần Finish (Hoàn tất), hãy xem lại Chính sách dữ liệu người dùng của các dịch vụ API của Google và nếu bạn đồng ý, hãy chọn I agree to the Google API Services: User Data Policy (Tôi đồng ý với Chính sách dữ liệu người dùng của các dịch vụ API của Google).
    9. Nhấp vào Tiếp tục.
    10. Nhấp vào Tạo.
  3. Hiện tại, bạn có thể bỏ qua bước thêm phạm vi. Trong tương lai, khi tạo một ứng dụng để sử dụng bên ngoài tổ chức Google Workspace, bạn phải thay đổi Loại người dùng thành Bên ngoài. Sau đó, hãy thêm các phạm vi uỷ quyền mà ứng dụng của bạn yêu cầu. Để tìm hiểu thêm, hãy xem hướng dẫn đầy đủ về cách Định cấu hình màn hình xin phép bằng OAuth.

Thiết lập tập lệnh

Tạo dự án Apps Script

  1. Nhấp vào nút sau để mở dự án Apps Script Share a macro (Chia sẻ macro).
    Mở dự án
  2. Nhấp vào Overview (Tổng quan) .
  3. Trên trang tổng quan, hãy nhấp vào Make a copy (Tạo bản sao) Biểu tượng tạo bản sao.

Sao chép số dự án trên đám mây

  1. Trong Bảng điều khiển API của Google, hãy chuyển đến Trình đơn > IAM & Admin > Settings (Cài đặt).

    Chuyển đến phần Cài đặt IAM và Quản trị

  2. Trong trường Project number (Số dự án), hãy sao chép giá trị.

Đặt dự án trên đám mây cho dự án Apps Script

  1. Trong dự án Apps Script đã sao chép, hãy nhấp vào Project Settings (Cài đặt dự án) Biểu tượng cho phần cài đặt dự án.
  2. Trong phần Google Cloud Platform (GCP) Project (Dự án trên Google Cloud Platform (GCP)), hãy nhấp vào Change project (Thay đổi dự án).
  3. Trong GCP project number (Số dự án trên đám mây của GCP), hãy dán số dự án trên Google Cloud.
  4. Nhấp vào Set project (Đặt dự án).

Cài đặt bản triển khai thử nghiệm

  1. Trong dự án Apps Script đã sao chép, hãy nhấp vào Editor .
  2. Mở tệp UI.gs rồi nhấp vào Run (Chạy). Khi được nhắc, hãy cho phép tập lệnh chạy.
  3. Nhấp vào Deploy (Triển khai) > Test deployments (Bản triển khai thử nghiệm).
  4. Nhấp vào Install (Cài đặt) > Done (Xong).

Nhận tập lệnh macro và thông tin bảng tính

  1. Mở một bảng tính trong Trang tính có macro và bạn có quyền chỉnh sửa. Để sử dụng bảng tính mẫu, hãy tạo bản sao của bảng tính Sample macro (Macro mẫu).
  2. Nhấp vào Extensions (Tiện ích) > Apps Script.
  3. Trong dự án Apps Script, hãy nhấp vào Cài đặt dự án Biểu tượng cho phần cài đặt dự án.
  4. Trong mã tập lệnh, hãy nhấp vào Copy (Sao chép).
  5. Đặt mã tập lệnh sang một bên để sử dụng ở bước sau.
  6. Mở hoặc tạo một bảng tính mới mà bạn muốn thêm macro. Bạn phải có quyền chỉnh sửa bảng tính.
  7. Sao chép URL bảng tính và đặt sang một bên để sử dụng ở bước sau.

Chạy tập lệnh

Đảm bảo bạn đã bật API Google Apps Script trong phần cài đặt trang tổng quan. Hãy làm theo các bước trong phần sau để chạy tập lệnh.

Sao chép macro

  1. Trong Trang tính, trên thanh bên phải, hãy mở tiện ích bổ sung Share Macro (Chia sẻ macro) Biểu tượng tạo bản sao.
  2. Trong phần Source macro (Macro nguồn), hãy dán mã tập lệnh.
  3. Trong phần Target spreadsheet (Bảng tính đích), hãy dán URL bảng tính.
  4. Nhấp vào Share macro (Chia sẻ macro).
  5. Nhấp vào Authorize access (Cho phép truy cập) và cho phép tiện ích bổ sung.
  6. Lặp lại các bước từ 2 đến 4.

Mở macro đã sao chép

  1. Nếu chưa mở, hãy mở bảng tính mà bạn đã sao chép macro.
  2. Nhấp vào Extensions (Tiện ích) > Apps Script.
  3. Nếu bạn không thấy dự án Apps Script đã sao chép, hãy đảm bảo rằng bạn đã bật API Google Apps Script trong phần cài đặttrang tổng quan và lặp lại các bước được liệt kê trong phần Sao chép macro.

Xem lại mã

Để xem lại mã Apps Script cho giải pháp này, hãy nhấp vào View source code bên dưới:

Xem mã nguồn

Code.gs

solutions/add-on/share-macro/Code.js
// To learn how to use this script, refer to the documentation:
// https://developers.devsite.corp.google.com/apps-script/add-ons/share-macro

/*
Copyright 2022 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Uses Apps Script API to copy source Apps Script project
 * to destination Google Spreadsheet container.
 *
 * @param {string} sourceScriptId - Script ID of the source project.
 * @param {string} targetSpreadsheetUrl - URL if the target spreadsheet.
 */
function shareMacro_(sourceScriptId, targetSpreadsheetUrl) {
  // Gets the source project content using the Apps Script API.
  const sourceProject = APPS_SCRIPT_API.get(sourceScriptId);
  const sourceFiles = APPS_SCRIPT_API.getContent(sourceScriptId);

  // Opens the target spreadsheet and gets its ID.
  const parentSSId = SpreadsheetApp.openByUrl(targetSpreadsheetUrl).getId();

  // Creates an Apps Script project that's bound to the target spreadsheet.
  const targetProjectObj = APPS_SCRIPT_API.create(
    sourceProject.title,
    parentSSId,
  );

  // Updates the Apps Script project with the source project content.
  APPS_SCRIPT_API.updateContent(targetProjectObj.scriptId, sourceFiles);
}

/**
 * Function that encapsulates Apps Script API project manipulation.
 */
const APPS_SCRIPT_API = {
  accessToken: ScriptApp.getOAuthToken(),

  /* APPS_SCRIPT_API.get
   * Gets Apps Script source project.
   * @param {string} scriptId - Script ID of the source project.
   * @return {Object} - JSON representation of source project.
   */
  get: function (scriptId) {
    const url = `https://script.googleapis.com/v1/projects/${scriptId}`;
    const options = {
      method: "get",
      headers: {
        Authorization: `Bearer ${this.accessToken}`,
      },
      muteHttpExceptions: true,
    };
    const res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() === 200) {
      return JSON.parse(res);
    }
    console.log("An error occurred gettting the project details");
    console.log(res.getResponseCode());
    console.log(res.getContentText());
    console.log(res);
    return false;
  },

  /* APPS_SCRIPT_API.create
   * Creates new Apps Script project in the target spreadsheet.
   * @param {string} title - Name of Apps Script project.
   * @param {string} parentId - Internal ID of target spreadsheet.
   * @return {Object} - JSON representation completed project creation.
   */
  create: function (title, parentId) {
    const url = "https://script.googleapis.com/v1/projects";
    const options = {
      headers: {
        Authorization: `Bearer ${this.accessToken}`,
        "Content-Type": "application/json",
      },
      muteHttpExceptions: true,
      method: "POST",
      payload: { title: title },
    };
    if (parentId) {
      options.payload.parentId = parentId;
    }
    options.payload = JSON.stringify(options.payload);
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() === 200) {
      res = JSON.parse(res);
      return res;
    }
    console.log("An error occurred while creating the project");
    console.log(res.getResponseCode());
    console.log(res.getContentText());
    console.log(res);
    return false;
  },
  /* APPS_SCRIPT_API.getContent
   * Gets the content of the source Apps Script project.
   * @param {string} scriptId - Script ID of the source project.
   * @return {Object} - JSON representation of Apps Script project content.
   */
  getContent: function (scriptId) {
    const url = `https://script.googleapis.com/v1/projects/${scriptId}/content`;
    const options = {
      method: "get",
      headers: {
        Authorization: `Bearer ${this.accessToken}`,
      },
      muteHttpExceptions: true,
    };
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() === 200) {
      res = JSON.parse(res);
      return res.files;
    }
    console.log(
      "An error occurred obtaining the content from the source script",
    );
    console.log(res.getResponseCode());
    console.log(res.getContentText());
    console.log(res);
    return false;
  },

  /* APPS_SCRIPT_API.updateContent
   * Updates (copies) content from source to target Apps Script project.
   * @param {string} scriptId - Script ID of the source project.
   * @param {Object} files - JSON representation of Apps Script project content.
   * @return {boolean} - Result status of the function.
   */
  updateContent: function (scriptId, files) {
    const url = `https://script.googleapis.com/v1/projects/${scriptId}/content`;
    const options = {
      method: "put",
      headers: {
        Authorization: `Bearer ${this.accessToken}`,
      },
      contentType: "application/json",
      payload: JSON.stringify({ files: files }),
      muteHttpExceptions: true,
    };
    const res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() === 200) {
      return true;
    }
    console.log(`An error occurred updating content of script ${scriptId}`);
    console.log(res.getResponseCode());
    console.log(res.getContentText());
    console.log(res);
    return false;
  },
};

UI.gs

solutions/add-on/share-macro/UI.js
/**
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Change application logo here (and in manifest) as desired.
const ADDON_LOGO =
  "https://www.gstatic.com/images/branding/product/2x/apps_script_48dp.png";

/**
 * Callback function for rendering the main card.
 * @return {CardService.Card} The card to show the user.
 */
function onHomepage(e) {
  return createSelectionCard(e);
}

/**
 * Builds the primary card interface used to collect user inputs.
 *
 * @param {Object} e - Add-on event object.
 * @param {string} sourceScriptId - Script ID of the source project.
 * @param {string} targetSpreadsheetUrl - URL of the target spreadsheet.
 * @param {string[]} errors - Array of error messages.
 *
 * @return {CardService.Card} The card to show to the user for inputs.
 */
function createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, errors) {
  // Configures card header.
  const cardHeader = CardService.newCardHeader()
    .setTitle("Share macros with other spreadheets!")
    .setImageUrl(ADDON_LOGO)
    .setImageStyle(CardService.ImageStyle.SQUARE);

  // If form errors exist, configures section with error messages.
  let showErrors = false;

  if (errors?.length) {
    showErrors = true;
    let msg = errors.reduce((str, err) => `${str}${err}<br>`, "");
    msg = `<b>Form submission errors:</b><br><font color="#ba0000">${msg}</font>`;

    // Builds error message section.
    sectionErrors = CardService.newCardSection().addWidget(
      CardService.newDecoratedText().setText(msg).setWrapText(true),
    );
  }

  // Configures source project section.
  const sectionSource = CardService.newCardSection()
    .addWidget(
      CardService.newDecoratedText().setText(
        "<b>Source macro</b><br>The Apps Script project to copy",
      ),
    )

    .addWidget(
      CardService.newTextInput()
        .setFieldName("sourceScriptId")
        .setValue(sourceScriptId || "")
        .setTitle("Script ID of the source macro")
        .setHint(
          "You must have at least edit permission for the source spreadsheet to access its script project",
        ),
    )

    .addWidget(
      CardService.newTextButton()
        .setText("Find the script ID")
        .setOpenLink(
          CardService.newOpenLink()
            .setUrl(
              "https://developers.google.com/apps-script/api/samples/execute",
            )
            .setOpenAs(CardService.OpenAs.FULL_SIZE)
            .setOnClose(CardService.OnClose.NOTHING),
        ),
    );

  // Configures target spreadsheet section.
  const sectionTarget = CardService.newCardSection()
    .addWidget(
      CardService.newDecoratedText().setText("<b>Target spreadsheet</b>"),
    )

    .addWidget(
      CardService.newTextInput()
        .setFieldName("targetSpreadsheetUrl")
        .setValue(targetSpreadsheetUrl || "")
        .setHint(
          "You must have at least edit permission for the target spreadsheet",
        )
        .setTitle("Target spreadsheet URL"),
    );

  // Configures help section.
  const sectionHelp = CardService.newCardSection()
    .addWidget(
      CardService.newDecoratedText()
        .setText(
          "<b><font color=#c80000>NOTE: </font></b>" +
            "The Apps Script API must be turned on.",
        )
        .setWrapText(true),
    )

    .addWidget(
      CardService.newTextButton()
        .setText("Turn on Apps Script API")
        .setOpenLink(
          CardService.newOpenLink()
            .setUrl("https://script.google.com/home/usersettings")
            .setOpenAs(CardService.OpenAs.FULL_SIZE)
            .setOnClose(CardService.OnClose.NOTHING),
        ),
    );

  // Configures card footer with action to copy the macro.
  const cardFooter = CardService.newFixedFooter().setPrimaryButton(
    CardService.newTextButton()
      .setText("Share macro")
      .setOnClickAction(
        CardService.newAction().setFunctionName("onClickFunction_"),
      ),
  );

  // Begins building the card.
  const builder = CardService.newCardBuilder().setHeader(cardHeader);

  // Adds error section if applicable.
  if (showErrors) {
    builder.addSection(sectionErrors);
  }

  // Adds final sections & footer.
  builder
    .addSection(sectionSource)
    .addSection(sectionTarget)
    .addSection(sectionHelp)
    .setFixedFooter(cardFooter);

  return builder.build();
}

/**
 * Action handler that validates user inputs and calls shareMacro_
 * function to copy Apps Script project to target spreadsheet.
 *
 * @param {Object} e - Add-on event object.
 *
 * @return {CardService.Card} Responds with either a success or error card.
 */
function onClickFunction_(e) {
  const sourceScriptId = e.formInput.sourceScriptId;
  const targetSpreadsheetUrl = e.formInput.targetSpreadsheetUrl;

  // Validates inputs for errors.
  const errors = [];

  // Pushes an error message if the Script ID parameter is missing.
  if (!sourceScriptId) {
    errors.push("Missing script ID");
  } else {
    // Gets the Apps Script project if the Script ID parameter is valid.
    const sourceProject = APPS_SCRIPT_API.get(sourceScriptId);
    if (!sourceProject) {
      // Pushes an error message if the Script ID parameter isn't valid.
      errors.push("Invalid script ID");
    }
  }

  // Pushes an error message if the spreadsheet URL is missing.
  if (!targetSpreadsheetUrl) {
    errors.push("Missing Spreadsheet URL");
  } else
    try {
      // Tests for valid spreadsheet URL to get the spreadsheet ID.
      const ssId = SpreadsheetApp.openByUrl(targetSpreadsheetUrl).getId();
    } catch (err) {
      // Pushes an error message if the spreadsheet URL parameter isn't valid.
      errors.push("Invalid spreadsheet URL");
    }

  if (errors?.length) {
    // Redisplays form if inputs are missing or invalid.
    return createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, errors);
  }
  // Calls shareMacro function to copy the project.
  shareMacro_(sourceScriptId, targetSpreadsheetUrl);

  // Creates a success card to display to users.
  return buildSuccessCard(e, targetSpreadsheetUrl);
}

/**
 * Builds success card to inform user & let them open the spreadsheet.
 *
 * @param {Object} e - Add-on event object.
 * @param {string} targetSpreadsheetUrl - URL of the target spreadsheet.
 *
 * @return {CardService.Card} Returns success card.
 */ function buildSuccessCard(e, targetSpreadsheetUrl) {
  // Configures card header.
  const cardHeader = CardService.newCardHeader()
    .setTitle("Share macros with other spreadsheets!")
    .setImageUrl(ADDON_LOGO)
    .setImageStyle(CardService.ImageStyle.SQUARE);

  // Configures card body section with success message and open button.
  const sectionBody1 = CardService.newCardSection()
    .addWidget(
      CardService.newTextParagraph().setText("Sharing process is complete!"),
    )
    .addWidget(
      CardService.newTextButton()
        .setText("Open spreadsheet")
        .setOpenLink(
          CardService.newOpenLink()
            .setUrl(targetSpreadsheetUrl)
            .setOpenAs(CardService.OpenAs.FULL_SIZE)
            .setOnClose(CardService.OnClose.RELOAD_ADD_ON),
        ),
    );
  const sectionBody2 = CardService.newCardSection()
    .addWidget(
      CardService.newTextParagraph().setText(
        "If you don't see the copied project in your target spreadsheet," +
          " make sure you turned on the Apps Script API in the Apps Script dashboard.",
      ),
    )
    .addWidget(
      CardService.newTextButton()
        .setText("Check API")
        .setOpenLink(
          CardService.newOpenLink()
            .setUrl("https://script.google.com/home/usersettings")
            .setOpenAs(CardService.OpenAs.FULL_SIZE)
            .setOnClose(CardService.OnClose.RELOAD_ADD_ON),
        ),
    );

  // Configures the card footer with action to start new process.
  const cardFooter = CardService.newFixedFooter().setPrimaryButton(
    CardService.newTextButton()
      .setText("Share another")
      .setOnClickAction(CardService.newAction().setFunctionName("onHomepage")),
  );

  const builder = CardService.newCardBuilder()
    .setHeader(cardHeader)
    .addSection(sectionBody1)
    .addSection(sectionBody2)
    .setFixedFooter(cardFooter);

  return builder.build();
}

appsscript.json

solutions/add-on/share-macro/appsscript.json
{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/script.projects"
  ],
  "urlFetchWhitelist": ["https://script.googleapis.com/"],
  "addOns": {
    "common": {
      "name": "Share Macro",
      "logoUrl": "https://www.gstatic.com/images/branding/product/2x/apps_script_48dp.png",
      "layoutProperties": {
        "primaryColor": "#188038",
        "secondaryColor": "#34a853"
      },
      "homepageTrigger": {
        "runFunction": "onHomepage"
      }
    },
    "sheets": {}
  }
}

Người đóng góp

Mẫu này do Google duy trì với sự trợ giúp của Google Developer Experts.

Các bước tiếp theo