Office 对话框 API 的最佳做法和规则

本文提供 Office 对话 API 的规则、限制和最佳做法,包括设计对话框 UI 并在单页应用程序中使用 API 的最佳做法, (SPA) 。

注意

若要熟悉使用 Office 对话框 API 的基础知识,请参阅 在 Office 外接程序中使用 Office 对话 API

另请参阅 使用 Office 对话框处理错误和事件

规则和限制

  • 对话框只能导航到 HTTPS URL,不能导航到 HTTP。

  • 传递给 displayDialogAsync 方法的 URL 必须与加载项本身位于完全相同的域中。 它不能是子域。 但是,传递给它的页面可以重定向到另一个域中的页面。

  • 主机页一次只能打开一个对话框。 主机页可以是任务窗格,也可以是函数命令的函数文件。 可以从自定义功能区按钮或菜单项同时打开多个对话框。

  • 该对话框只能调用两个 Office API:

  • 通常从与加载项本身完全相同的域中的页面调用 messageParent 函数,但这不是必需的。 有关详细信息,请参阅向主机运行时间跨域消息传递

  • 打开对话框时,它将居中位于 Office 应用程序顶部的屏幕上。

  • 用户可以移动对话框并调整其大小。

  • 对话框按创建顺序显示。

    提示

    在 Office web 版 和新 Outlook on Windows 中,如果对话框的域与加载项的域不同,并且它强制实施跨源-Opener-Policy:同源响应标头,则会阻止加载项访问来自对话框的邮件,并且用户会看到错误 12006。 若要防止此错误,请将标头设置为 Cross-Origin-Opener-Policy: unsafe-none 或将加载项和对话框配置为在同一域中。

  • 在 Outlook 网页版 和新的 Outlook on Windows 中,在外接程序中配置对话框时,不要设置 window.name 属性。 这些 Outlook 客户端使用 window.name 属性跨页面重定向维护功能。

最佳做法

避免过度使用对话框

由于不赞成重叠 UI 元素,因此除非应用场景需要,否则请勿从任务窗格打开对话框。 考虑如何使用任务窗格区域时,请注意任务窗格中可以有选项卡。 有关选项卡式任务窗格的示例,请参阅 Excel 外接程序 JavaScript SalesTracker 示例。

设计对话框 UI

有关对话框设计的最佳做法,请参阅 Office 外接程序中的对话框

使用Office web 版处理弹出窗口阻止程序

如果在使用 Office web 版 时尝试显示对话框,浏览器的弹出窗口阻止程序可能会阻止该对话框。 若要防止此问题,Office web 版提示用户打开对话框时允许忽略

带有简短说明的提示以及加载项可以生成的“允许”和“忽略”按钮,以避免浏览器内弹出窗口阻止程序。

如果用户选择 “允许”,则会打开“Office”对话框。 如果用户选择 “忽略”,则提示将关闭,并且 Office 对话框不会打开。 相反, displayDialogAsync 该方法返回错误 12009。 代码应捕获此错误,并提供不需要对话框的备用体验,或者向用户显示一条消息,告知加载项要求他们允许对话。 有关错误 12009 的详细信息,请参阅 displayDialogAsync 中的错误

处理阻止的对话 (错误 12009)

加载项应始终正常处理错误 12009。 下面是一些建议的方法:

  • 显示一条用户友好的消息,说明为何需要对话。

    Office.context.ui.displayDialogAsync(
      "https://www.contoso.com/auth.html",
      { height: 60, width: 30 },
      (asyncResult) => {
        if (asyncResult.status === Office.AsyncResultStatus.Failed) {
          if (asyncResult.error.code === 12009) {
            // User blocked the dialog.
            showNotification(
              "Dialog Required",
              "This add-in needs to open a dialog to sign you in. " +
              "Please click the add-in button again and choose 'Allow' when prompted."
            );
          } else {
            // Handle other errors.
            showNotification("Error", `Unable to open dialog: ${asyncResult.error.message}`);
          }
        } else {
          // Dialog opened successfully.
          const dialog = asyncResult.value;
          // ... Handle dialog events here.
        }
      }
    );
    
  • 尽可能提供替代工作流。 如果加载项在对话框被阻止时功能降低,请为用户提供该替代项。

禁用提示

如果要关闭允许/忽略提示,代码必须选择退出。使用传递给 displayDialogAsync 方法的 DialogOptions 对象发出此请求。 具体而言,包括在 promptBeforeOpen: false 对象中。 将此选项设置为 false 时,Office web 版不会提示用户允许加载项打开对话框,并且如果浏览器的弹出窗口阻止程序阻止它,Office 对话框也不会打开。

注意

对于大多数方案,不建议设置 promptBeforeOpen: false 。 提示用户的默认行为提供更好的用户体验,并帮助用户了解需要对话框的原因。

请求访问 Office web 版 和新的 Outlook on Windows 中的设备功能

如果外接程序需要访问用户的设备功能,请使用 设备权限 API 通过对话框请求权限。 设备功能包括用户的相机、地理位置和麦克风。 此要求适用于以下 Office 应用程序。

  • Office web 版 (Excel、Outlook、PowerPoint 和基于Chromium的浏览器(如 Microsoft Edge 或 Google Chrome)中运行的Word)
  • 新的 Outlook on Windows

当外接程序调用 Office.context.devicePermission.requestPermissionsOffice.context.devicePermission.requestPermissionsAsync 时,将显示一个对话框,其中包含请求的设备功能和“允许一次”或“拒绝访问”选项。 有关详细信息,请参阅查看、管理和安装 Excel、PowerPoint 和 Word加载项

注意

  • 在 Office 桌面客户端或浏览器中运行的加载项不基于Chromium会自动显示请求用户权限的对话。 开发人员无需在这些平台上实现设备权限 API。
  • 在 Safari 中运行的加载项被阻止访问用户的设备功能。 Safari 不支持设备权限 API。
  • Outlook 网页版和新的 Windows 版 Outlook 中支持访问用户的地理位置。

不要使用_host_info值

Office 自动将名为 的 _host_info 查询参数添加到传递给 的 displayDialogAsyncURL。 它会在自定义查询参数后面追加此参数(如果有)。 它不会将此参数追加到对话框导航到的任何后续 URL。 Microsoft可能会更改此值的内容或将其完全删除,因此代码不应读取该值。 相同的值将添加到对话框的会话存储 (即 Window.sessionStorage 属性) 。 同样,代码不得对此值执行读取和写入操作

关闭另一个对话框后立即打开另一个对话框

不能从给定的主机页打开多个对话框,因此代码应在打开的对话上调用 Dialog.close ,然后再调用 displayDialogAsync 以打开另一个对话框。 方法是 close 异步的。 因此,如果在调用 后立即调用 displayDialogAsyncclose,当 Office 尝试打开第二个对话框时,第一个对话框可能不会完全关闭。 如果发生这种情况,Office 将返回 12007 错误:“操作失败,因为此加载项已具有活动对话框。”

方法close不接受回调参数,并且不返回 Promise 对象,因此不能使用 await 关键字 (keyword) 或 then 方法等待它。 因此,在关闭对话后需要立即打开新对话时,请使用以下方法:封装代码以在函数中打开新对话,并设计函数以递归方式调用自身(如果 的 displayDialogAsync 调用返回 12007)。 以下示例演示如何实现此技术。

function openFirstDialog() {
  Office.context.ui.displayDialogAsync(
    "https://MyDomain/firstDialog.html",
    { width: 50, height: 50 },
    (result) => {
      if (result.status === Office.AsyncResultStatus.Succeeded) {
        const dialog = result.value;
        dialog.close();
        openSecondDialog();
      }
      else {
         // Handle errors.
      }
    }
  );
}
 
function openSecondDialog() {
  Office.context.ui.displayDialogAsync(
    "https://MyDomain/secondDialog.html",
    { width: 50, height: 50 },
    (result) => {
      if (result.status === Office.AsyncResultStatus.Failed) {
        if (result.error.code === 12007) {
          openSecondDialog(); // Recursive call.
        }
        else {
         // Handle other errors.
        }
      }
    }
  );
}

或者,可以在代码尝试使用 setTimeout 方法打开第二个对话框之前强制暂停代码。 以下示例演示如何实现此方法。

function openFirstDialog() {
  Office.context.ui.displayDialogAsync(
    "https://MyDomain/firstDialog.html",
    { width: 50, height: 50 },
    (result) => {
      if (result.status === Office.AsyncResultStatus.Succeeded) {
        const dialog = result.value;
        dialog.close();
        setTimeout(() => { 
          Office.context.ui.displayDialogAsync(
            "https://MyDomain/secondDialog.html",
            { width: 50, height: 50 },
            (result) => {
              // Callback body.
            }
          );
        }, 1000);
      }
      else {
         // Handle errors.
      }
    }
  );
}

在 SPA 中使用 Office 对话框 API 的最佳做法

如果外接程序使用客户端路由,就像单页应用程序 (SPA) 通常一样,可以将路由的 URL 传递给 displayDialogAsync 方法,而不是单独的 HTML 页面的 URL。 由于以下部分给出的原因,请勿使用此方法。

注意

本文与 服务器端 路由无关,例如在基于 Express 的 Web 应用程序中。

SPA 和 Office 对话框 API 的问题

Office 对话框位于一个新窗口中,其中包含其自己的 JavaScript 引擎实例,因此具有自己的完整执行上下文。 如果传递路由,则基页及其所有初始化和引导代码都会在此新上下文中再次运行,并在对话框中将任何变量设置为其初始值。 因此,此方法在对话框窗口中下载并启动应用程序的第二个实例,这在一定程度上违背了 SPA 的用途。 此外,更改对话框窗口中变量的代码不会更改相同变量的任务窗格版本。 同样,对话框窗口具有自己的会话存储 (Window.sessionStorage 属性) ,这无法从任务窗格中的代码访问。 所调用的对话框和主机页 displayDialogAsync 与服务器的两个不同的客户端类似。 (有关主机页的提醒,请参阅 从主机页打开对话框。)

因此,如果将路由传递给 displayDialogAsync 方法,则实际上没有 SPA;你有 同一 SPA 的两个实例。 此外,任务窗格实例中的大部分代码永远不会在该实例中使用,对话框实例中的大部分代码从未在该实例中使用。 这就像在同一捆绑包中有两个 SPA。

Microsoft 建议

使用下列方法之一,而不是将客户端路由传递给 displayDialogAsync 方法。

  • 如果要在对话框中运行的代码足够复杂,请显式创建两个不同的 SPA:也就是说,在同一域的不同文件夹中有两个 SPA。 一个 SPA 在对话框中运行,另一个在调用的对话框的主机页 displayDialogAsync 中运行。
  • 在大多数情况下,对话框中只需要简单的逻辑。 在这种情况下,通过在 SPA 的域中托管单个 HTML 页面(嵌入或引用的 JavaScript),大大简化了项目。 将页面的 URL 传递给 displayDialogAsync 方法。 虽然此方法意味着你偏离了单页应用的文本概念,但使用 Office 对话框 API 时,实际上没有 SPA 的单个实例。

另请参阅