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
相关产品推荐
相关产品推荐

