T learn.traeai

第 10 节 · 本站原创中文课程 · 建议 35 分钟

创作4:用 AI 做打卡器

学完能做:实现可恢复的本地持久化,明确日期、去重和存储失败的行为。

小鱼模型实验 · 用自己的话写下发现,保留实际观察与待验证的问题。

先确定作品要解决什么

日期键必须使用用户的本地日历日期。toISOString 使用 UTC,接近午夜时可能得到不同日期。参考实现用本地年月日并按日期去重,读取失败会提示;file:// 下存储行为因浏览器不同,验证持久化应使用固定 HTTP 来源。

给 AI 的任务卡

把主题和内容换成自己的版本,先检查助手复述的需求再生成代码。使用家长或学校允许的编程工具;本站提供的代码是预写参考,没有调用实时 AI。记录实际借助了什么帮助。

请在单文件打卡器中使用本地 YYYY-MM-DD 日期键,分离日期计算、数据校验、存储与渲染。重复打卡幂等,支持撤销今天;损坏 JSON 和存储不可用要有明确提示。增加 JSON 导出与导入,导入前校验真实日期、去重,并让用户预览数量再确认合并。不得上传个人记录。先列边界与测试。

从可运行的起点开始

下方是完整参考代码,也已放入练习草稿。点击“导出当前草稿”,再在预览中点击“下载文件”;把下载的文件重命名为 tracker.html,保留 .html 后缀,在电脑浏览器中打开。先验证原版,再另存自己的版本。代码在本地运行,不会训练或调用 GuppyLM。

<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>我的阅读打卡</title>
<style>
*{box-sizing:border-box}body{font:18px/1.7 system-ui;margin:24px auto;padding:0 20px;max-width:720px;color:#183b36;background:#f7faf9}
h1{font-size:28px}h2{font-size:22px}button,input{font:inherit}button,a{touch-action:manipulation}button{padding:10px 16px;margin:4px;border:1px solid #287b68;border-radius:6px;background:white;color:#183b36;cursor:pointer}button:disabled{opacity:.5;cursor:default}a{color:#1468a0}label{display:block;padding:6px 0}section{border-top:1px solid #bfcfc8;padding:16px 0}p,li{overflow-wrap:anywhere}img{max-width:100%}:focus-visible{outline:3px solid #cc7600;outline-offset:3px}
</style>
<h1>我的阅读打卡</h1>
<p>目标:每天读一小段,记下一个发现。</p><p id="summary" role="status"></p>
<button id="check">今天已阅读</button><button id="undo">撤销今天</button>
<p id="storage-note"></p><h2>我的记录</h2><ul id="days"></ul>
<script>
const key = 'reading-days-v1';
let days = [];
let storageWarning = '';
try {
  const saved = JSON.parse(localStorage.getItem(key) || '[]');
  if (!Array.isArray(saved) || !saved.every(day => typeof day === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(day))) throw new Error('invalid');
  days = [...new Set(saved)].sort();
} catch { storageWarning = '未能读取旧记录;本页从空记录开始,请保留自己的备份。'; }
function today() {
  const d = new Date();
  return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
}
function render() {
  const date = today();
  const checked = days.includes(date);
  document.getElementById('summary').textContent = date + ' · ' + (checked ? '今天已打卡' : '今天还未打卡') + ' · 累计 ' + days.length + ' 天';
  document.getElementById('check').disabled = checked;
  document.getElementById('undo').disabled = !checked;
  document.getElementById('storage-note').textContent = storageWarning || '只保存在当前浏览器,不会跨设备同步;更换网址后记录也不会自动迁移。';
  const list = document.getElementById('days');
  list.replaceChildren();
  for (const day of [...days].reverse()) { const item = document.createElement('li'); item.textContent = day; list.append(item); }
}
function save() {
  try { localStorage.setItem(key, JSON.stringify(days)); storageWarning = ''; }
  catch { storageWarning = '保存失败:本次改动只在当前页面,刷新可能丢失。'; }
  render();
}
document.getElementById('check').onclick = () => { days = [...new Set([...days,today()])].sort(); save(); };
document.getElementById('undo').onclick = () => { days = days.filter(day => day !== today()); save(); };
window.addEventListener('focus',render);
render();
</script>
</html>

做一次有理由的修改

先测试参考版本,再实现备份导入导出。校验不仅是正则匹配,还要拒绝2月30日等不存在的日期;合并不能覆盖原记录。让 AI 把日期函数改为可传入 Date,测试午夜两侧时不必修改系统时钟。

用实际操作验收

覆盖本地午夜前后、重复日期、空数组、损坏 JSON、错误数据类型、不存在的日期、存储拒绝、导入取消与成功合并。明确另一域名或浏览器不会自动共享记录,部署后用固定网址再次验证。

这是本站独立编写的讲解与示例,并非官方课程逐字翻译。理解之后,请完成下面的练习、自测和笔记。

参考来源:arman-bd / GuppyLM 开源项目

动手,把理解变成成果

本节练习

正在读取本机草稿…

完成打卡器,导出为 tracker.html。在学习笔记中保留需求、提示词、自己的修改与逐项测试;未执行的项目标注待测。

2611 / 6000 字。用自己的话记录发现;作品保存在本机,不发送给外部伴学模型。

给我一个提示

参考版本只做基础格式校验;把真实日期验证与导入功能作为你的扩展,不要声称参考代码已经实现。

查看参考思路(本站预写,非实时 Agent)

先保留参考版,再完成要求中的扩展。按本节场景逐项记录“操作、预期、实际结果”;这里只给记录方法,不代表你的作品已经通过。说明使用了哪种 AI 帮助或本站参考代码。

人工自评:逐项核对自己的作品

这些勾选由你确认,不是自动测试结果。遇到待测项,请在作品里明确注明。

理解自测

修改完成后,怎样判断作品是否符合需求?

自己的学习笔记

0 / 3000 字 · 仅保存在此浏览器

走完学习闭环

完成本节

保留练习、勾选人工自评并通过自测后,可记录完成。修改作品或答案会重新打开本节。

课程进度:0 / 15 · 本机记录,不是外部官方证书

下一节:创作5:用 AI 做个人网站 →
← 上一节:创作3:用 AI 做小游戏