市面上的任务管理工具,但凡带个日历视图、数据导出功能,不是按月收费就是按年订阅。自己动手,用 Microsoft Graph API 一小时就能搭一个专属的 To Do 看板——月视图、周视图、CSV 导出,全部免费,数据还留在自己手里。

为什么做这个?

Microsoft To Do 本身非常轻量,但缺少一个直观的月视图/周视图。你只能按列表查看任务,无法一眼看出哪天有哪些事、哪些已经完成。更别说导出数据做周报、月报了。

市面上的“日历+任务”类应用,像 Todoist、TickTick 的高级版确实提供日历视图,但都放在了付费墙后面。对于个人开发者或者小团队,为了一个视图功能去订阅并不划算。

于是我花了一个晚上,基于 Microsoft Graph API 和纯前端技术,写了一个完全免费的 To Do 看板。它支持:

  • 月视图(周一为起始,符合国内习惯)
  • 周视图(显示 ISO 周数,每天的任务完成比例)
  • 一键导出 CSV(按时间排序,包含创建时间、完成时间、备注等字段)
  • 切换账号、实时统计(全部任务/已完成数量)

而且个人 Microsoft 账号(outlook.com、hotmail.com)和 E5 订阅账号都可以直接使用

技术选型

需求 技术方案
登录与授权 MSAL.js (Microsoft Authentication Library)
获取任务数据 Microsoft Graph API (/me/todo/lists/tasks)
日历渲染 手写 HTML/CSS/JS(轻量,不依赖第三方库)
数据导出 动态生成 CSV 文件,浏览器下载
性能优化 并行请求 + 限流控制

之所以不直接用 FullCalendar 等现成库,是为了完全控制 UI 样式,并且避免额外的依赖加载。

核心功能实现

1. 登录与权限

使用 MSAL.js 实现 OAuth 2.0 授权码流(PKCE)。只需要在 Azure AD 中注册一个应用,配置重定向 URI 为 SPA 类型,然后请求 Tasks.ReadUser.Read 权限即可。

const msalConfig = {
    auth: {
        clientId: '你的客户端ID',
        authority: 'https://login.microsoftonline.com/common',
        redirectUri: window.location.origin
    }
};
const loginRequest = { scopes: ['Tasks.Read', 'User.Read'] };

个人账号注意:Azure 应用注册时,必须选择“任何组织目录中的帐户和个人 Microsoft 帐户”,否则个人账号无法登录。

2. 获取所有任务

Microsoft To Do 的数据模型是:用户有多个任务列表,每个列表下有多个任务。所以需要两步:

  1. 请求 GET /me/todo/lists 获取所有列表。
  2. 对每个列表请求 GET /me/todo/lists/{id}/tasks?$top=500 获取其中的任务。

性能瓶颈:如果列表较多(比如十几个),串行请求会非常慢(每个请求约 200~300ms,总耗时 = 列表数 × 单次耗时)。例如 16 个列表,串行需要 5 秒左右。

3. 并行请求 + 限流处理

为了提速,我们使用 Promise.all 并发请求,但并发量过大会触发 Microsoft Graph 的限流(HTTP 429)。解决方案是限制并发数,同时加入简单的重试机制。

// 限制同时最多 3 个请求
async function asyncPool(poolLimit, array, iteratorFn) {
    // 实现略
}
const fetchListTasks = async (list) => {
    // 遇到 429 则等待 Retry-After 秒后重试
};
const results = await asyncPool(3, lists, fetchListTasks);

实测并发数 3 时,16 个列表的加载时间从 5 秒降到 1.2 秒左右,且不会触发限流。

4. 月视图实现(周一为起始)

纯手写一个日历表格,关键点在于计算每月第一天是周几,并转换为周一为起始的索引。

let startWeekday = firstDay.getDay(); // 0=周日
startWeekday = (startWeekday === 0 ? 6 : startWeekday - 1); // 周一=0, 周日=6

然后生成 42 个格子(6 行×7 列),根据日期填充任务。每个格子内显示当天任务列表,按“进行中 → 已完成”排序。

5. 周视图与 ISO 周数

周视图展示当前周的 7 天,每天的任务以列表形式纵向排列。为了显示“第几周”,实现了一个 ISO 周数计算函数(根据周一为一周的第一天)。

function getWeekNumber(date) {
    // 国际标准 ISO 8601 周数算法
}

6. CSV 导出

导出功能是看板的重要补充。将当前显示的任务(全部/本月/本周)转换为 CSV 格式,并自动下载。

  • 排序:按显示日期(北京时间)从早到晚,同一天内按创建时间升序。
  • 字段:创建日期、创建时间、状态、重要标记、销售(任务列表名)、任务内容、完成日期、备注。
  • 文件名任务导出_账号_范围_时间戳.csv,避免覆盖。

7. 时区处理

Graph API 返回的时间是 UTC 格式(例如 2026-04-17T07:55:54Z)。为了正确显示北京时间(UTC+8),我们在前端进行转换:

function utcToBeijing(utcDateStr) {
    const date = new Date(utcDateStr);
    return new Date(date.getTime() + 8 * 3600 * 1000);
}

这样任务显示日期和详情弹窗的时间都与本地一致。

遇到的坑与解决

1. MSAL 弹窗被拦截

某些浏览器会阻止跨域弹窗。解决方案是在用户点击“登录”按钮时再调用 loginPopup,而不是页面加载时自动弹出。

2. 令牌过期

acquireTokenSilent 可能因会话过期失败,此时需要回退到 acquireTokenPopup 重新交互登录。

3. Graph API 字段不存在

尝试使用 $select 只获取必要字段(如 dueDateTime)时,可能会返回 invalidRequest。稳妥做法是先不筛选,后续再优化。

4. 并行请求限流

如上所述,必须控制并发数。我们用了 3 个并发,并加入指数退避重试(实际只重试一次就够)。

成果展示

最终实现的效果:

  • 月视图:清晰展示整个月的任务分布,每天右上角显示 已完成/总数
  • 周视图:以表格形式展示本周七天,每行一个任务,每天下方显示 📋 已完成/总数
  • 统计卡片:顶部显示全部任务总数和已完成数。
  • 导出:一键导出 CSV,可用 Excel 打开。
  • 切换账号:一键登出并返回登录页,支持多账号切换。

性能数据

数据规模 列表数 串行耗时 并行+限流耗时
6天 107条 16 ~5.0 秒 ~1.2 秒
1个月 900条 16 ~5.2 秒 ~1.3 秒
1年 1万条 16 ~5.5 秒 ~1.5 秒

可见,请求次数是瓶颈,而非数据量。通过并行请求优化后,即使一年上万条任务,也能在 2 秒内完成加载。

开源与自定义

这个看板的所有代码都是纯前端的,你可以任意修改样式、增加字段、调整布局。唯一的“秘密”是你在 Azure 注册的应用客户端 ID,这个公开也无妨(因为重定向 URI 白名单和权限范围保证了安全)。

如果你想快速体验,只需:

  1. 在 Azure 注册一个应用,启用“个人帐户”支持。
  2. 添加重定向 URI 为你的本地地址(如 http://localhost:3000)。
  3. 将代码中的 CLIENT_ID 替换成你自己的。
  4. 用任意静态服务器打开 HTML 文件。

全程不需要任何后端,数据直接从 Microsoft Graph 获取,安全又隐私。

总结

从想法到落地,这个 To Do 看板只用了不到一天。它证明了:通过开放的 API,开发者完全可以绕过付费工具的壁垒,自己打造满足特定需求的工具

如果你也觉得任务管理工具的付费功能不够“值”,不妨试试这种 DIY 思路。技术栈简单(HTML + JS + MSAL),部署零成本,还能学到 OAuth 2.0、并行请求优化等实用技能。

最后,所有代码已整理成单个 HTML 文件,鉴于篇幅就不再贴了。


本文仅作技术分享,不涉及任何商业推广。使用 Microsoft Graph API 请遵守微软服务条款。