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

使用FullCalendar加载Outlook日历失败,登录后无渲染问题排查

问题诊断与修复方案

核心问题

  1. 未处理MSAL登录重定向回调:使用loginRedirect完成登录后,页面重定向回网站时,MSAL需要处理回调来保存账户信息,但当前代码缺失这一步,导致msalInstance.getAllAccounts()始终返回空数组,重复触发登录流程后抛出interaction_in_progress错误。
  2. 登录成功后未自动触发日历渲染:重定向回调处理完成后,没有自动调用日历加载逻辑,导致登录返回后日历无生成动作。
  3. 函数作用域冲突:getCalendarEvents、renderCalendar等核心函数定义在DOMContentLoaded回调内部,全局函数mostrarCalendarioOutlook无法访问这些函数,即使登录成功也无法执行后续流程。

具体修复步骤

1. 添加MSAL登录回调处理

初始化msalInstance后,立即处理登录重定向回调,确保账户信息正确保存并自动触发日历加载:

document.addEventListener('DOMContentLoaded', async function() {                
    try {
        const msalConfig = {
            auth: {
                clientId: "Client_ID", 
                authority: "https://login.microsoftonline.com/common",                    
                redirectUri: "redirect_Uri",
                popUp: true
            }
        };

        msalInstance = new msal.PublicClientApplication(msalConfig);            

        // 处理登录重定向回调
        await msalInstance.handleRedirectPromise()
            .then(response => {
                if (response) {
                    // 登录成功后自动加载日历
                    mostrarCalendarioOutlook();
                }
            })
            .catch(err => {
                console.error('回调处理失败:', err);
            });

        // ... 其他函数定义
    } catch (error) {
        console.error('初始化错误:', error);
        Swal.fire({
            icon: 'error',
            title: '初始化失败',
            text: '加载配置时出现错误,请稍后重试。'
        });
    }
});

2. 调整函数作用域

将getCalendarEvents、renderCalendar等核心函数移到全局作用域,确保mostrarCalendarioOutlook能正常调用:

let msalInstance;
const loginRequest = {
    scopes: ["User.Read", "Calendars.Read", "Calendars.Read.Shared"]
};

// 全局定义核心函数
async function getCalendarEvents(accessToken) {
    console.log('进入getCalendarEvents');
    const response = await fetch('https://graph.microsoft.com/v1.0/me/events', {
        headers: {
            'Authorization': `Bearer ${accessToken}`
        }
    });

    if (!response.ok) {
        const errorText = await response.text();
        console.error(`获取日历事件失败: ${response.status} ${response.statusText}`, errorText);
        throw new Error('获取日历事件失败');
    }

    const data = await response.json();
    const events = [];

    function adjustToLocalTime(dateTime) {
        const date = new Date(dateTime);
        const timeZoneOffset = date.getTimezoneOffset() * 60000;
        return new Date(date.getTime() - timeZoneOffset).toISOString();
    }

    const recurringEvents = data.value.filter(event => event.recurrence && event.recurrence.pattern);
    recurringEvents.forEach(event => {
        // 原循环逻辑不变
    });

    const singleEvents = data.value.filter(event => !event.recurrence);
    singleEvents.forEach(event => {
        events.push({
            title: event.subject,
            start: adjustToLocalTime(event.start.dateTime),
            end: adjustToLocalTime(event.end.dateTime),
            allDay: event.isAllDay
        });
    });               

    return events;
}

function renderCalendar(events) {
    console.log('进入renderCalendar');
    const calendarEl = document.getElementById('calendar');
    const calendar = new FullCalendar.Calendar(calendarEl, {
        // 原日历配置不变
    });
    calendar.render();
}

async function mostrarCalendarioOutlook() {
    console.log('进入mostrarCalendarioOutlook');
    try {
        if (!msalInstance) {
            throw new Error("msalInstance未初始化");
        }
        const accounts = msalInstance.getAllAccounts();
        if (accounts.length > 0) {
            console.log('检测到已登录账户');
            const account = accounts[0];
            const tokenResponse = await msalInstance.acquireTokenSilent({
                account,
                scopes: ["User.Read", "Calendars.Read", "Calendars.Read.Shared"]
            });
            const events = await getCalendarEvents(tokenResponse.accessToken);
            console.log('准备渲染日历');
            renderCalendar(events);
        } else {                
            console.log('未检测到登录账户');
            // 检查是否有正在进行的交互,避免重复触发登录
            if (!msalInstance.isInteractionInProgress()) {
                await msalInstance.loginRedirect(loginRequest);
            }
        }
    } catch (error) {                        
        handleError(error);            
    }
}

// ... 其他全局函数(如handleError)

document.addEventListener('DOMContentLoaded', async function() {                
    try {
        const msalConfig = {
            auth: {
                clientId: "Client_ID", 
                authority: "https://login.microsoftonline.com/common",                    
                redirectUri: "redirect_Uri",
                popUp: true
            }
        };

        msalInstance = new msal.PublicClientApplication(msalConfig);            

        // 处理登录回调
        await msalInstance.handleRedirectPromise()
            .then(response => {
                if (response) {
                    mostrarCalendarioOutlook();
                }
            })
            .catch(err => {
                console.error('回调处理失败:', err);
            });

        // 保留仅在初始化时需要的函数
        async function handleLogoutClick() {
            // 原注销逻辑不变
        }
    } catch (error) {
        console.error('初始化错误:', error);
        Swal.fire({
            icon: 'error',
            title: '初始化失败',
            text: '加载配置时出现错误,请稍后重试。'
        });
    }
});

3. 避免重复触发登录冲突

在mostrarCalendarioOutlook的else分支中,先检查是否有正在进行的交互,再执行登录跳转:

async function mostrarCalendarioOutlook() {
    console.log('进入mostrarCalendarioOutlook');
    try {
        if (!msalInstance) {
            throw new Error("msalInstance未初始化");
        }
        const accounts = msalInstance.getAllAccounts();
        if (accounts.length > 0) {
            // 原登录后逻辑不变
        } else {                
            console.log('未检测到登录账户');
            // 检查是否有交互在进行,防止重复触发登录
            if (!msalInstance.isInteractionInProgress()) {
                await msalInstance.loginRedirect(loginRequest);
            }
        }
    } catch (error) {                        
        handleError(error);            
    }
}

额外验证点

  • 确认Azure AD应用的redirectUri与代码配置一致,且已添加到应用的重定向URI列表中。
  • 检查应用是否已获得Calendars.Read和Calendars.Read.Shared权限(管理员或用户已完成授权)。

内容的提问来源于stack exchange,提问作者Hugo Jiménez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:07:02