教程:创建 Word 任务窗格加载项

在本教程中,将创建 Word 任务窗格加载项,该加载项将:

  • 插入文本区域
  • 设置文本格式
  • 替换文本并在各个位置插入文本
  • 插入图像、HTML 和表格
  • 创建和更新内容控件

提示

如果已完成了创建首个 Word 任务窗格加载项快速入门,并希望使用该项目作为本教程的起点,请直接转到插入文本区域以开始此教程。

如果需要本教程的完整版本,请转到 GitHub 上的 Office 外接程序示例存储库

先决条件

  • Node.js(最新LTS 版本)。 访问 Node.js 站点 ,下载并安装适合你的操作系统的版本。

  • 最新版本的 Yeoman 和适用于 Office 加载项的 Yeoman 生成器。若要全局安装这些工具,请从命令提示符处运行以下命令。

    npm install -g yo generator-office
    

    注意

    即便先前已安装了 Yeoman 生成器,我们还是建议你通过 npm 将包更新为最新版本。

  • 已连接到 Microsoft 365 订阅的 Office (包括 Office 网页版)。

    注意

    如果你还没有 Office,你可能有资格通过 Microsoft 365 开发人员计划获得Microsoft 365 E5开发人员订阅;有关详细信息,请参阅常见问题解答。 或者,可以 注册 1 个月的免费试用版购买 Microsoft 365 计划

创建加载项项目

运行以下命令,使用 Yeoman 生成器创建加载项项目。 包含项目的文件夹将添加到当前目录。

yo office

注意

运行该yo office命令时,可能会收到有关 Yeoman 和 Office 加载项 CLI 工具的数据收集策略的提示。 根据你的需要,使用提供的信息来响应提示。

出现提示时,请提供以下信息以创建加载项项目。

  • 选择项目类型:Office Add-in Task Pane project
  • 选择脚本类型:JavaScript
  • 要为外接程序命名什么名称?My Office Add-in
  • 你希望支持哪个 Office 客户端应用程序?Word

前面在命令行界面中为 Yeoman 生成器提供的提示和答案。

完成此向导后,生成器会创建项目,并安装支持的 Node 组件。

注意

如果使用 Node.js 20.0.0 或更高版本,则当生成器运行安装时,可能会看到一条警告,指出你的引擎不受支持。 我们正在努力解决此问题。 同时,警告不会影响生成的生成器或项目,因此可以忽略它。

提示

创建加载项项目后,可忽略 Yeoman 生成器提供的后续步骤指南。 本文中的分步说明提供了完成本教程所需的全部指南。

插入文本区域

本教程的这一步是,先以编程方式测试加载项是否支持用户的当前版本 Word,再在文档中插入段落。

编码加载项

  1. 在代码编辑器中打开项目。

  2. 打开 ./src/taskpane/taskpane.html 文件。 此文件含有任务窗格的 HTML 标记。

  3. 找到 <main> 元素并删除在开始 <main> 标记后和关闭 </main> 标记前出现的所有行。

  4. 打开 <main> 标记后立即添加下列标记:

    <button class="ms-Button" id="insert-paragraph">Insert Paragraph</button><br/><br/>
    
  5. 打开 ./src/taskpane/taskpane.js 文件。 此文件包含用于加快任务窗格与 Office 客户端应用程序之间的交互的 Office JavaScript API 代码。

  6. 执行以下操作,删除对 run 按钮和 run() 函数的所有引用:

    • 查找并删除行 document.getElementById("run").onclick = run;

    • 查找并删除整个 run() 函数。

  7. Office.onReady 函数调用中,找到行 if (info.host === Office.HostType.Word) { 并紧跟该行添加下列代码。 注意:

    • 此代码为 insert-paragraph 按钮添加事件处理程序。
    • 函数 insertParagraph 包装在调用 tryCatch 中, (将在下一步) 中添加这两个函数。 这允许与服务代码分开处理 Office JavaScript API 层生成的任何错误。
    // Assign event handlers and other initialization logic.
    document.getElementById("insert-paragraph").onclick = () => tryCatch(insertParagraph);
    
  8. 将以下函数添加到文件末尾。 注意:

    • Word.js 业务逻辑将添加到传递给 的 Word.run函数中。 此逻辑不立即执行。 相反,它会添加到挂起的命令队列中。

    • context.sync 方法将所有已排入队列的命令都发送到 Word 以供执行。

    • 通过 tryCatch 任务窗格与工作簿交互的所有函数都将使用该函数。 以这种方式捕获 Office JavaScript 错误是一种通用处理未捕获的错误的便捷方法。

    async function insertParagraph() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to insert a paragraph into the document.
    
            await context.sync();
        });
    }
    
    /** Default helper for invoking an action and handling errors. */
    async function tryCatch(callback) {
        try {
            await callback();
        } catch (error) {
            // Note: In a production add-in, you'd want to notify the user through your add-in's UI.
            console.error(error);
        }
    }
    
  9. insertParagraph() 函数中,将 TODO1 替换为以下代码。 注意:

    • insertParagraph 方法的第一个参数是新段落的文本。

    • 第二个参数是应在正文中的什么位置插入段落。 如果父对象为正文,其他段落插入选项包括“End”和“Replace”。

    const docBody = context.document.body;
    docBody.insertParagraph("Office has several versions, including Office 2016, Microsoft 365 subscription, and Office on the web.",
                            Word.InsertLocation.start);
    
  10. 保存对项目所做的所有更改。

测试加载项

  1. 完成以下步骤,以启动本地 Web 服务器并旁加载你的加载项。

    注意

    即使在开发过程中,Office 外接程序也应使用 HTTPS,而不是 HTTP。 如果在运行以下命令之一后系统提示安装证书,请接受安装 Yeoman 生成器提供的证书的提示。 你可能还必须以管理员身份运行命令提示符或终端才能进行更改。

    提示

    如果在 Mac 上测试加载项,请先运行项目根目录中的以下命令,然后再继续。 运行此命令时,本地 Web 服务器将启动。

    npm run dev-server
    
    • 若要在 Word 中测试加载项,请在项目的根目录中运行以下命令。 如果本地 Web 服务器尚未运行) ,则会启动本地 Web 服务器 (,并在加载加载项时打开Word。

      npm start
      
    • 若要在 Word 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将“{url}”替换为你有权访问的 OneDrive 或 SharePoint 库中 Word 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 在 Word中,如果“我的 Office 外接程序”任务窗格尚未打开,请选择“开始”选项卡,然后选择功能区上的“显示任务窗格”按钮以打开外接程序任务窗格。

    Word中突出显示的“显示任务窗格”按钮。

  3. 在任务窗格中,选择“插入段落”按钮。

  4. 在段落中进行一些更改。

  5. 再次选择“插入段落”按钮。 观察新段落是否位于上一段落之上,因为 insertParagraph 方法要在文档正文的“开头”插入内容。

    加载项中的“插入段落”按钮。

设置文本格式

在本教程的此步骤中,你将向文本应用嵌入样式、向文本应用自定义样式并更改文本字体。

向文本应用嵌入样式

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找insert-paragraph按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="apply-style">Apply Style</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 insert-paragraph 按钮的行,并在该行后添加以下代码。

    document.getElementById("apply-style").onclick = () => tryCatch(applyStyle);
    
  5. 将以下函数添加到文件结尾。

    async function applyStyle() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to style text.
    
            await context.sync();
        });
    }
    
  6. applyStyle() 函数中,将 TODO1 替换为以下代码。 请注意,此代码向段落应用样式,但也可以向文本区域应用样式。

    const firstParagraph = context.document.body.paragraphs.getFirst();
    firstParagraph.styleBuiltIn = Word.Style.intenseReference;
    

向文本应用自定义样式

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找apply-style按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="apply-custom-style">Apply Custom Style</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 apply-style 按钮的行,并在该行后添加以下代码。

    document.getElementById("apply-custom-style").onclick = () => tryCatch(applyCustomStyle);
    
  5. 将以下函数添加到文件结尾。

    async function applyCustomStyle() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to apply the custom style.
    
            await context.sync();
        });
    }
    
  6. applyCustomStyle() 函数中,将 TODO1 替换为以下代码。 请注意,此代码应用的自定义样式尚不存在。 将在测试加载项步骤中创建 MyCustomStyle 样式。

    const lastParagraph = context.document.body.paragraphs.getLast();
    lastParagraph.style = "MyCustomStyle";
    
  7. 保存对项目所做的所有更改。

更改文本字体

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找apply-custom-style按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="change-font">Change Font</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 apply-custom-style 按钮的行,并在该行后添加以下代码。

    document.getElementById("change-font").onclick = () => tryCatch(changeFont);
    
  5. 将以下函数添加到文件结尾。

    async function changeFont() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to apply a different font.
    
            await context.sync();
        });
    }
    
  6. changeFont() 函数中,将 TODO1 替换为以下代码。 请注意,此代码使用链接到 Paragraph.getNext 方法的 ParagraphCollection.getFirst 方法,获取对第二个段落的引用。

    const secondParagraph = context.document.body.paragraphs.getFirst().getNext();
    secondParagraph.font.set({
            name: "Courier New",
            bold: true,
            size: 18
        });
    
  7. 保存对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Word 中,则继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项。

    • 若要在 Word 中测试加载项,请在项目的根目录中运行以下命令。 如果本地 Web 服务器尚未运行) ,则会启动本地 Web 服务器 (,并在加载加载项时打开Word。

      npm start
      
    • 若要在 Word 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将“{url}”替换为你有权访问的 OneDrive 或 SharePoint 库中 Word 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在Word中打开,请转到“开始”选项卡,然后选择功能区上的“显示任务窗格”按钮将其打开。

  3. 请确保文档中至少有三个段落。 可以选择“插入段落”按钮三次。 仔细检查文档末尾是否没有空白段落。 如果有,请将其删除。

  4. 在 Word 中,创建自定义样式“MyCustomStyle”。 其中可以包含所需的任何格式。

  5. 选择“应用样式”按钮。 第一个段落将采用嵌入样式“明显参考”

  6. 选择“应用自定义样式”按钮。 最后一个段落将采用自定义样式。 (如果似乎未发生任何操作,则最后一段可能为空。如果是这样,请向其添加一些文本。)

  7. 选择“更改字体”按钮。 第二个段落的字体更改为 18 磅的粗体 Courier New。

    应用为加载项按钮“应用样式”、“应用自定义样式”和“更改字体”定义的样式和字体的结果。

替换文本和插入文本

本教程的这一步是,在选定文本区域内外添加文本,并替换选定区域的文本。

在区域内添加文本

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找change-font按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="insert-text-into-range">Insert Abbreviation</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 change-font 按钮的行,并在该行后添加以下代码。

    document.getElementById("insert-text-into-range").onclick = () => tryCatch(insertTextIntoRange);
    
  5. 将以下函数添加到文件结尾。

    async function insertTextIntoRange() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to insert text into a selected range.
    
            // TODO2: Load the text of the range and sync so that the
            //        current range text can be read.
    
            // TODO3: Queue commands to repeat the text of the original
            //        range at the end of the document.
    
            await context.sync();
        });
    }
    
  6. insertTextIntoRange() 函数中,将 TODO1 替换为以下代码。 注意:

    • 函数旨在将缩写 [“ (M365) ”] 插入到其文本为“Microsoft 365”的范围的末尾。 它做了一个简化假设,即存在字符串,且用户已选择它。

    • Range.insertText 方法的第一个参数是要插入到 Range 对象的字符串。

    • 第二个参数指定了应在区域中的什么位置插入其他文本。 除了“End”外,其他可用选项包括“Start”、“Before”、“After”和“Replace”。

    • “End”和“After”的区别在于,“End”在现有区域末尾插入新文本,而“After”则是新建包含字符串的区域,并在现有区域后面插入新区域。 同样,“Start”是在现有区域的开头位置插入文本,而“Before”插入的是新区域。 “Replace”将现有区域文本替换为第一个参数中的字符串。

    • 在本教程 insert* 的早期阶段,你看到正文对象的方法没有“Before”和“After”选项。 这是因为不能将内容置于文档正文外。

    const doc = context.document;
    const originalRange = doc.getSelection();
    originalRange.insertText(" (M365)", Word.InsertLocation.end);
    
  7. 在下一部分前,将跳过 TODO2。 在 insertTextIntoRange() 函数中,将 TODO3 替换为以下代码。 此代码类似于在本教程第一阶段中创建的代码,区别在于现在是要在文档末尾(而不是开头)插入新段落。 这一新段落将说明,新文本现属于原始区域。

    doc.body.insertParagraph("Original range: " + originalRange.text, Word.InsertLocation.end);
    

添加代码以将文档属性提取到任务窗格的脚本对象

在本教程之前的所有函数中,你已将命令排队以 写入 Office 文档。 每个函数结束时都会调用 context.sync() 方法,从而将排入队列的命令发送到文档,以供执行。 不过,在上一步中添加的代码调用的是 originalRange.text 属性,这与之前编写的函数明显不同,因为 originalRange 对象只是任务窗格脚本中的代理对象。 由于它并不了解文档中区域的实际文本,因此它的 text 属性无法有实值。 有必要先从文档中提取区域的文本值,再用它设置 originalRange.text 的值。 只有这样才能调用 originalRange.text,而又不会导致异常抛出。 此获取过程分为三步:

  1. 将命令排入队列,以加载 (即提取) 代码需要读取的属性。

  2. 调用上下文对象的 sync方法,从而向文档发送已排入队列的命令以供执行,并返回请求获取的信息。

  3. 由于 sync 是异步方法,因此请先确保它已完成,然后代码才能调用已提取的属性。

每当代码需要从 Office 文档中 读取 信息时,都必须完成以下步骤。

  1. insertTextIntoRange() 函数中,将 TODO2 替换为以下代码。

    originalRange.load("text");
    await context.sync();
    

完成后,整个函数应如下所示:

async function insertTextIntoRange() {
    await Word.run(async (context) => {

        const doc = context.document;
        const originalRange = doc.getSelection();
        originalRange.insertText(" (M365)", Word.InsertLocation.end);

        originalRange.load("text");
        await context.sync();

        doc.body.insertParagraph("Original range: " + originalRange.text, Word.InsertLocation.end);

        await context.sync();
    });
}

在区域间添加文本

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找insert-text-into-range按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="insert-text-outside-range">Add Version Info</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 insert-text-into-range 按钮的行,并在该行后添加以下代码。

    document.getElementById("insert-text-outside-range").onclick = () => tryCatch(insertTextBeforeRange);
    
  5. 将以下函数添加到文件结尾。

    async function insertTextBeforeRange() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to insert a new range before the
            //        selected range.
    
            // TODO2: Load the text of the original range and sync so that the
            //        range text can be read and inserted.
    
        });
    }
    
  6. insertTextBeforeRange() 函数中,将 TODO1 替换为以下代码。 注意:

    • 此函数用于带有文本“Microsoft 365”的区域前添加文本为“Office 2019”的区域。 它假设字符串存在,并且用户已选择该字符串。

    • Range.insertText 方法的第一个参数是要添加的字符串。

    • 第二个参数指定了应在区域中的什么位置插入其他文本。 若要详细了解位置选项,请参阅前面介绍的 insertTextIntoRange 函数。

    const doc = context.document;
    const originalRange = doc.getSelection();
    originalRange.insertText("Office 2019, ", Word.InsertLocation.before);
    
  7. insertTextBeforeRange() 函数中,将 TODO2 替换为以下代码。

    originalRange.load("text");
    await context.sync();
    
    // TODO3: Queue commands to insert the original range as a
    //        paragraph at the end of the document.
    
    // TODO4: Make a final call of context.sync here and ensure
    //        that it runs after the insertParagraph has been queued.
    
  8. TODO3 替换为以下代码。 此新段落将演示新文本 不属于 原始选定范围这一事实。 原始区域中的文本仍与用户选择它时一样。

    doc.body.insertParagraph("Current text of original range: " + originalRange.text, Word.InsertLocation.end);
    
  9. TODO4 替换为下面的代码。

    await context.sync();
    

替换区域文本

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找insert-text-outside-range按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="replace-text">Change Quantity Term</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 insert-text-outside-range 按钮的行,并在该行后添加以下代码。

    document.getElementById("replace-text").onclick = () => tryCatch(replaceText);
    
  5. 将以下函数添加到文件结尾。

    async function replaceText() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to replace the text.
    
            await context.sync();
        });
    }
    
  6. replaceText() 函数中,将 TODO1 替换为以下代码。 请注意,此函数用于将字符串“几个”替换为字符串“许多”。 它做了一个简化假设,即存在字符串,且用户已选择它。

    const doc = context.document;
    const originalRange = doc.getSelection();
    originalRange.insertText("many", Word.InsertLocation.replace);
    
  7. 保存对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Word 中,则继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项。

    • 若要在 Word 中测试加载项,请在项目的根目录中运行以下命令。 如果本地 Web 服务器尚未运行) ,则会启动本地 Web 服务器 (,并在加载加载项时打开Word。

      npm start
      
    • 若要在 Word 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将“{url}”替换为你有权访问的 OneDrive 或 SharePoint 库中 Word 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在Word中打开,请转到“开始”选项卡,然后选择功能区上的“显示任务窗格”按钮将其打开。

  3. 在任务窗格中,选择“ 插入段落 ”按钮,确保文档开头有一个段落。

  4. 在文档中,选择短语“Microsoft 365 订阅”。 请注意不要包含选定区域前的空格和其后的逗号。

  5. 选择“插入缩写”按钮。 请注意,添加了“ (M365) ”。 此外,还请观察,文档底部是否添加了包含整个扩展文本的新段落,因为新字符串已添加到现有区域中。

  6. 在文档中,选择短语“Microsoft 365”。 请注意不要包含选定区域前后的空格。

  7. 选择“添加版本信息”按钮。 观察是否已在“Office 2016”和“Microsoft 365”之间插入“Office 2019”。 此外,还请观察,文档底部是否添加了仅包含最初选定文本的新段落,因为新字符串已变成新区域,而不是添加到原始区域中。

  8. 在文档中,选择“几个”一词。 请注意,不要在选定区域的前后添加空格。

  9. 选择“更改数量术语”按钮。 观察选定文本是否替换为“多个”。

    选择加载项按钮“插入缩写”、“添加版本信息”和“更改数量术语”的结果。

插入图像、HTML 和表格

本教程的这一步是,了解如何在文档中插入图像、HTML 和表格。

定义图像

完成以下步骤,定义要在本教程的下一部分插入到文档中的图像。

  1. 在项目的根目录中,创建一个名为 base64Image.js 的新文件。

  2. 打开文件 base64Image.js 并添加以下代码以指定表示图像的 Base64 编码字符串。

    export const base64Image =
        "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";
    

插入图像

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找replace-text按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="insert-image">Insert Image</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. 在文件顶部附近找到 Office.onReady 函数调用,然后在该行之前添加以下代码。 此代码将导入你先前在文件 /base64Image.js 中定义的变量。

    import { base64Image } from "../../base64Image";
    
  5. Office.onReady 函数调用中,定位将单击处理程序分配到 replace-text 按钮的行,并在该行后添加以下代码。

    document.getElementById("insert-image").onclick = () => tryCatch(insertImage);
    
  6. 将以下函数添加到文件结尾。

    async function insertImage() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to insert an image.
    
            await context.sync();
        });
    }
    
  7. insertImage() 函数中,将 TODO1 替换为以下代码。 请注意,此行在文档末尾插入 Base64 编码的图像。 (对象 Paragraph 还具有 insertInlinePictureFromBase64 方法和其他 insert* 方法。有关 example,请参阅以下“插入 HTML”部分。)

    context.document.body.insertInlinePictureFromBase64(base64Image, Word.InsertLocation.end);
    

插入 HTML

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找insert-image按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="insert-html">Insert HTML</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 insert-image 按钮的行,并在该行后添加以下代码。

    document.getElementById("insert-html").onclick = () => tryCatch(insertHTML);
    
  5. 将以下函数添加到文件结尾。

    async function insertHTML() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to insert a string of HTML.
    
            await context.sync();
        });
    }
    
  6. insertHTML() 函数中,将 TODO1 替换为以下代码。 注意:

    • 第一行代码在文档末尾添加空白段落。

    • 第二行在段落末尾插入 HTML 字符串;特别是两个段落,一个使用 Verdana 字体设置格式,另一个段落采用Word文档的默认样式。 (如前面的 insertImage 方法一样,context.document.body 对象还包含 insert* 方法。)

    const blankParagraph = context.document.body.paragraphs.getLast().insertParagraph("", Word.InsertLocation.after);
    blankParagraph.insertHtml('<p style="font-family: verdana;">Inserted HTML.</p><p>Another paragraph</p>', Word.InsertLocation.end);
    

插入表格

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找insert-html按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="insert-table">Insert Table</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 insert-html 按钮的行,并在该行后添加以下代码。

    document.getElementById("insert-table").onclick = () => tryCatch(insertTable);
    
  5. 将以下函数添加到文件结尾。

    async function insertTable() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to get a reference to the paragraph
            //        that will precede the table.
    
            // TODO2: Queue commands to create a table and populate it with data.
    
            await context.sync();
        });
    }
    
  6. insertTable() 函数中,将 TODO1 替换为以下代码。 请注意,此行使用 ParagraphCollection.getFirst 方法获取对第一段的引用,然后使用 Paragraph.getNext 方法获取对第二段的引用。

    const secondParagraph = context.document.body.paragraphs.getFirst().getNext();
    
  7. insertTable() 函数中,将 TODO2 替换为以下代码。 注意:

    • insertTable 方法的前两个参数指定行数和列数。

    • 第三个参数指定要在哪里插入表格(在此示例中,是在段落后面插入)。

    • 第四个参数是用于设置表格单元格值的二维数组。

    • 虽然表格采用普通的默认样式,但 insertTable 方法返回的 Table 对象包含多个成员,其中部分成员用于设置表格样式。

    const tableData = [
            ["Name", "ID", "Birth City"],
            ["Bob", "434", "Chicago"],
            ["Sue", "719", "Havana"],
        ];
    secondParagraph.insertTable(3, 3, Word.InsertLocation.after, tableData);
    
  8. 保存对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Word 中,则继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项。

    • 若要在 Word 中测试加载项,请在项目的根目录中运行以下命令。 如果本地 Web 服务器尚未运行) ,则会启动本地 Web 服务器 (,并在加载加载项时打开Word。

      npm start
      
    • 若要在 Word 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将“{url}”替换为你有权访问的 OneDrive 或 SharePoint 库中 Word 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在Word中打开,请转到“开始”选项卡,然后选择功能区上的“显示任务窗格”按钮将其打开。

  3. 在任务窗格中,至少选择“插入段落”按钮三次,以确保文档中有多个段落。

  4. 选择“插入图像”按钮,观察图像是否插入在文档末尾。

  5. 选择“ 插入 HTML ”按钮,请注意,文档末尾插入了两个段落,第一个段落具有 Verdana 字体。

  6. 选择“插入表格”按钮,观察是否在第二个段落后面插入了表格。

    选择加载项按钮“插入图像”、“插入 HTML”和“插入表”的结果。

创建和更新内容控件

本教程的这一步是,了解如何在文档中创建格式文本内容控件,以及如何插入和替换控件的内容。

注意

开始执行本教程的这一步之前,建议通过 Word UI 创建和控制格式文本内容控件,以便熟悉此类控件及其属性。 有关详细信息,请参阅在 Word 中创建用户填写或打印的表单

创建内容控件

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找insert-table按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="create-content-control">Create Content Control</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 insert-table 按钮的行,并在该行后添加以下代码。

    document.getElementById("create-content-control").onclick = () => tryCatch(createContentControl);
    
  5. 将以下函数添加到文件结尾。

    async function createContentControl() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to create a content control.
    
            await context.sync();
        });
    }
    
  6. createContentControl() 函数中,将 TODO1 替换为以下代码。 注意:

    • 此代码旨在将短语“Microsoft 365”包装在内容控件中。 它做了一个简化假设,即存在字符串,且用户已选择它。

    • ContentControl.title 属性指定内容控件的可见标题。

    • ContentControl.tag 属性指定标记,可用于通过 ContentControlCollection.getByTag 方法获取对内容控件的引用,将用于稍后出现的函数。

    • ContentControl.appearance 属性指定控件的外观。 使用值“Tags”表示,控件包装在开始标记和结束标记中,且开始标记包含内容控件标题。 其他可取值包括“BoundingBox”和“None”。

    • ContentControl.color 属性指定标记颜色或边界框的边框。

    const serviceNameRange = context.document.getSelection();
    const serviceNameContentControl = serviceNameRange.insertContentControl();
    serviceNameContentControl.title = "Service Name";
    serviceNameContentControl.tag = "serviceName";
    serviceNameContentControl.appearance = "Tags";
    serviceNameContentControl.color = "blue";
    

替换内容控件的内容

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找create-content-control按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="replace-content-in-control">Rename Service</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 create-content-control 按钮的行,并在该行后添加以下代码。

    document.getElementById("replace-content-in-control").onclick = () => tryCatch(replaceContentInControl);
    
  5. 将以下函数添加到文件结尾。

    async function replaceContentInControl() {
        await Word.run(async (context) => {
    
            // TODO1: Queue commands to replace the text in the Service Name
            //        content control.
    
            await context.sync();
        });
    }
    
  6. replaceContentInControl() 函数中,将 TODO1 替换为以下代码。 注意:

    • ContentControlCollection.getByTag 方法将返回指定标记的所有内容控件的 ContentControlCollection。 我们使用 getFirst 来获取对所需控件的引用。
    const serviceNameContentControl = context.document.contentControls.getByTag("serviceName").getFirst();
    serviceNameContentControl.insertText("Fabrikam Online Productivity Suite", Word.InsertLocation.replace);
    
  7. 保存对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Word 中,则继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项。

    • 若要在 Word 中测试加载项,请在项目的根目录中运行以下命令。 如果本地 Web 服务器尚未运行) ,则会启动本地 Web 服务器 (,并在加载加载项时打开Word。

      npm start
      
    • 若要在 Word 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将“{url}”替换为你有权访问的 OneDrive 或 SharePoint 库中 Word 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在Word中打开,请转到“开始”选项卡,然后选择功能区上的“显示任务窗格”按钮将其打开。

  3. 在任务窗格中,选择“ 插入段落 ”按钮,确保文档顶部有一个带有“Microsoft 365”的段落。

  4. 在文档中,选择文本“Microsoft 365”,然后选择“创建内容控件”按钮。 观察此短语是否包装在标签为“服务名称”的标记中。

  5. 选择“重命名服务”按钮,并观察内容控件的文本是否变成“Fabrikam Online Productivity Suite”。

    选择加载项按钮“创建内容控件”和“重命名服务”的结果。

后续步骤

在本教程中,你已创建 Word 任务窗格加载项,用于在 Word 文档中插入和替换文本、图像和其他内容。 若要了解有关构建 Word 加载项的详细信息,请继续阅读以下文章。

代码示例

另请参阅