墨墨背单词的 App 统计页用起来总差点意思——想看今日进度、累计词量、哪些词总忘,得翻好几个 tab。干脆自己做一个,挂在博客上,打开就是。这里记录实现过程和踩过的坑。
成品:博客首页 → 更多 → 学习进度。
墨墨开放平台
墨墨提供开放平台 open.maimemo.com,登录后创建应用即可获取 API Token。接口规格:
| 接口 | 方法 & 路径 | 用途 |
|---|---|---|
| 今日学习进度 | POST /study/get_study_progress |
今日已学 / 计划 / 时长 |
| 今日单词列表 | POST /study/get_today_items |
今日安排的全部单词(含反馈) |
| 学习记录 | POST /study/query_study_records |
所有学习记录,可算各种聚合 |
三个接口均为 POST + JSON body,请求头需带 Authorization: Bearer <token>。Token 放在 Cloudflare Pages 的环境变量 MAIMEMO_TOKEN 中,不要写进仓库。
后端:Cloudflare Pages Functions
在仓库根目录新建 functions/api/maimemo.js,Cloudflare 会自动把它映射为 /api/maimemo。
三个关键坑
- 响应有包装结构。接口返回
{ "errors": [], "data": {...}, "success": true },真实数据在data字段,需解包后使用:
1 | if (json && typeof json === 'object' && 'data' in json) return json.data; |
- 墨墨的”新的一天”以北京时间凌晨 4:00 为界,不是 0 点。计算
next_study_date查询区间时必须按 UTC+8 的 04:00 定位:
1 | dayStart.setUTCHours(4, 0, 0, 0); |
- 错误路径会返回 200 而非 404,且响应是 HTML。需校验
Content-Type是否为application/json,避免解析崩溃。
限频优化
每次刷新最多发 4 条墨墨请求,其余统计全部从主查询结果本地派生:
1 | const [progress, todayItems, records, totalCount] = await Promise.all([ |
明日待复习、顽固词汇 Top 10、学习跨度、平均学习次数等,均从 records(1000 条)中过滤 / 排序 / 聚合获得,不增加额外调用。
另加 5 分钟内存缓存(Worker 模块级变量)防止频繁刷新触发限频,响应带 Cache-Control: max-age=300 双保险。
前端:Hexo 独立页面
新建 source/maimemo/index.md:
1 | --- |
正文中整段 HTML 需用 ` / ` 包裹,否则 Markdown 渲染器会把缩进的标签当作代码块转义(页面会直接显示 <section> 源码)。
页面全部用纯 CSS + Vanilla JS 实现,不依赖任何第三方库:
- 环形图:
conic-gradient实现 - 反馈分布:flex 堆叠条
- 数字滚动:
requestAnimationFrame+ easing - 单词列表筛选:全部 / 已学习 / 忘记 三个 Tab
最初使用 Chart.js CDN,但在中国大陆访问 cdn.jsdelivr.net 经常超时导致图表卡在”加载中”,故改为纯 CSS 方案。所有样式统一挂在 .maimemo-dashboard 高特异性类下,避免污染主题。