You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Outlook插件Dialog回调页无法向TaskPane发送SignInSuccessful消息

问题分析与解决方案

核心问题

回调页CallBackUrl中Office.initialize未被正确触发,导致无法执行addHandlerAsync和messageParent,根本原因有两点:

  • 回调页是登录跳转后的新页面,Office.js加载时机与组件渲染时机不匹配,直接赋值Office.initialize无法保证在Office环境就绪后执行
  • 组件渲染阶段提前执行了Dialog.close逻辑,导致Office初始化代码还没运行就关闭了页面

解决方案步骤

1. 确保回调页正确引入Office.js

在回调页的HTML入口文件中,必须在<head>标签内优先加载Office.js,确保Office环境就绪后再渲染React组件:

<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>

2. 调整Office初始化逻辑到React生命周期中

在React组件内,不要直接赋值Office.initialize,改用useEffect监听Office加载状态,确保Office环境就绪后再执行初始化和消息发送。

3. 延迟页面关闭逻辑到Office初始化完成后

将原来直接在组件顶层执行的Dialog.close逻辑,移到Office初始化完成并发送消息之后执行。

修改后的CallBackUrl组件代码

/* global Office, */
import React, { useEffect, useState } from 'react';
import { Dialog } from '@microsoft/office-js-helpers';
import * as msTeams from '@microsoft/teams-js';
import { connect, useSelector } from 'react-redux';
import { withRouter } from 'react-router';
import Spinner from '../../common/spinner';
import { API_URL_CALLBACK } from '../../../Configurations';
import isExpiredToken from '../../../utils/isExpiredToken';
import {
  currentAppName,
  getLocalStorageValue,
  isTeamsApp,
} from '../../../utils/CommonUtils';
import Loader from '../../common/Loader';

const CallBackUrl = (props) => {
  const urlParams = new URLSearchParams(window.location.search);
  const loginDetail = useSelector((state) => state.ssoLogin.loginData);
  const login = useSelector((state) => state.ssoLogin);
  const myParam = urlParams.get('code');
  const isTeamsDialog = getLocalStorageValue('isTeamsDialog') === 'true';
  const [officeReady, setOfficeReady] = useState(false);

  useEffect(() => {
    // 处理Office初始化
    const initOffice = async () => {
      if (window.Office) {
        await Office.context.ui.addHandlerAsync(Office.EventType.DialogParentMessageReceived, (args) => {
          console.log(args.message);
          localStorage.setItem('parentData', args.message);
        });
        // 发送登录成功消息
        Office.context.ui.messageParent('SignInSuccessful');
        setOfficeReady(true);

        // 消息发送后关闭对话框(仅当isDialog标记存在时)
        if (getLocalStorageValue('isDialog') === 'true') {
          console.log('location: ', window.location.href);
          Dialog.close(window.location.href);
          localStorage.removeItem('isDialog');
        }
      } else {
        // 如果Office还未加载,监听OfficeReady事件
        document.addEventListener('OfficeReady', initOffice);
      }
    };

    initOffice();

    // 清理监听
    return () => {
      document.removeEventListener('OfficeReady', initOffice);
    };
  }, []);

  useEffect(() => {
    // 处理Teams和普通网页登录逻辑
    if (isTeamsDialog || isTeamsApp()) {
      localStorage.removeItem('isTeamsDialog');
      msTeams.initialize();
      localStorage.removeItem('auth.error');
      if (myParam) {
        const data = { code: myParam };
        msTeams.authentication.notifySuccess(data);
      } else {
        msTeams.authentication.notifyFailure({ error: 'something went wrong' });
      }
    } else if (!isTeamsDialog && !getLocalStorageValue('isDialog')) {
      const userData = {
        code: myParam,
        company_id: getLocalStorageValue('companySsoId'),
        email: getLocalStorageValue('companySsoEmail'),
        appName: currentAppName(),
      };

      const actionStates = {};
      const { history } = props;
      console.log('history is not empty', history);
      isExpiredToken(
        userData,
        API_URL_CALLBACK,
        history,
        actionStates,
        null,
        null,
        null,
      );
    }
  }, [myParam, props.history]);

  return (
    <main id="main">
      {isTeamsDialog ? <Loader /> : <Spinner key="Spinner" />}
    </main>
  );
};

const mapStatetoProps = (state) => ({
  status: state.auth.status,
  name: state.auth.name,
  token: state.auth.token,
});

export default connect(mapStatetoProps, {})(withRouter(CallBackUrl));

关键修改说明

  • 将Office初始化逻辑移到useEffect中,通过监听OfficeReady事件确保Office环境完全就绪
  • 把Dialog.close逻辑延迟到messageParent执行之后,保证消息能成功发送到任务窗格
  • 拆分不同场景的逻辑到独立的useEffect中,避免执行顺序冲突
  • 移除了组件顶层的即时执行逻辑,确保所有操作都在React生命周期内有序执行

内容的提问来源于stack exchange,提问作者Zabeeh Ullah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 15:33:14