HTML+JavaScript 待办清单实战:新增、完成、删除、计数一次讲清楚|AI 编程第 3 课
前两课我们做出了个人效率工具箱首页并给番茄钟加入了自定义时长、暂停继续和结束提醒。这一课继续完善工具箱让“待办清单”真正能使用。你可以输入“学习 JavaScript”点击添加也可以直接按回车。完成任务后勾选复选框任务文字会出现删除线取消勾选就能恢复为待完成。点击删除按钮对应任务会从列表中移除。页面还会显示任务总数、已完成数和待完成数。这看起来只是几个按钮但它把前端开发中很常用的一条流程串了起来接收输入 → 修改数据 → 更新页面 → 反馈结果。理解这条流程后面再做便签、购物清单、收藏列表都能用到类似的方法。这节课提供完整的单文件代码并保留上一课的番茄钟功能。即使没有跟着前两课操作也可以直接保存代码运行。先把需求说清楚避免 AI 只生成一个能看的页面。可以将上一课的代码发给 AI再附上下面这段要求请在现有个人效率工具箱中实现待办清单保留番茄钟功能。待办清单需要支持输入任务点击添加或按回车都能新增。忽略任务内容两端的空格拒绝空白任务。每条任务有独立编号即使内容相同也能分别操作。勾选任务表示完成取消勾选恢复为待完成。完成任务显示删除线。删除按钮只删除对应任务。显示任务总数、已完成数和待完成数。列表为空时显示提示。用户输入的任务内容按普通文字显示。本课先将数据保存在当前页面的内存中。使用纯 HTML、CSS、JavaScript输出完整的单文件代码。其中最容易遗漏的是“同名任务也能分别操作”。假设列表里有两个“阅读技术文章”它们可能分别安排在上午和晚上。如果程序只根据任务文字删除就可能同时删掉两条。因此每条任务都需要一个独立编号。我们可以用一个对象表示任务{ id: 1, text: 学习 JavaScript, done: false }这三个字段分别表示任务编号、任务内容和完成状态。多条任务放进数组let tasks [ { id: 1, text: 学习 JavaScript, done: false }, { id: 2, text: 完成一轮专注, done: true } ];页面应该根据这个数组显示内容。新增、完成和删除都先修改数组再更新页面。例如统计已完成的任务可以这样写const completed tasks.filter(task task.done).length;删除编号为2的任务可以这样写tasks tasks.filter(task task.id ! 2);这里的filter()会生成一个新数组保留符合条件的元素。删除任务时我们保留所有“编号不等于目标编号”的任务。接下来把这些操作连接起来。下面是本课完整代码。用它替换上一课index.html的内容保存后打开浏览器即可使用。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title个人效率工具箱/title style * { box-sizing: border-box; } body { margin: 0; color: #1f2937; background: #f5f7fb; font-family: Microsoft YaHei, Arial, sans-serif; } header, main, #message { max-width: 1100px; margin-left: auto; margin-right: auto; } header { padding: 40px 20px 24px; } header h1 { margin: 0 0 12px; } header p, .description { color: #6b7280; line-height: 1.6; } main { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; padding: 0 20px; } .card { min-width: 0; padding: 24px; background: white; border-radius: 16px; box-shadow: 0 8px 24px #1f293712; } .card h2 { margin: 0 0 16px; font-size: 21px; } input[typenumber], input[typetext] { padding: 9px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 16px; } #duration { width: 80px; } button { padding: 10px 14px; border: 0; border-radius: 8px; color: white; background: #4f46e5; cursor: pointer; } button:hover { background: #4338ca; } #clock { margin: 24px 0; font-size: 52px; font-weight: bold; text-align: center; font-variant-numeric: tabular-nums; } .controls { display: flex; flex-wrap: wrap; gap: 8px; } #timer-status { min-height: 48px; color: #6b7280; line-height: 1.6; } #task-form { display: flex; flex-wrap: wrap; gap: 8px; } #task-input { flex: 1 1 140px; min-width: 0; } #task-summary, #task-empty { color: #6b7280; font-size: 14px; line-height: 1.6; } #task-list { margin: 0; padding: 0; list-style: none; } .task-item { display: flex; align-items: flex-start; gap: 10px; padding: 12px 0; border-bottom: 1px solid #eef0f5; } .task-item input[typecheckbox] { flex-shrink: 0; width: 18px; height: 18px; margin: 9px 0 0; cursor: pointer; } .task-text { flex: 1; min-width: 0; padding-top: 7px; line-height: 1.5; overflow-wrap: anywhere; } .task-item.done .task-text { color: #9ca3af; text-decoration: line-through; } .danger { flex-shrink: 0; padding: 8px 10px; color: #b91c1c; background: #fee2e2; } .danger:hover { background: #fecaca; } #task-status { min-height: 24px; color: #3730a3; font-size: 14px; } #message { padding: 16px 20px; color: #3730a3; } /style /head body header h1我的效率工具箱/h1 p先记录要做的事再给自己一段专注时间。/p /header main !-- 番茄钟延续第二课 -- article classcard h2 番茄钟/h2 label forduration专注时长/label input idduration typenumber min1 max120 step1 value25 span分钟/span div idclock25:00/div div classcontrols button idstart typebutton开始 / 继续/button button idpause typebutton暂停/button button idreset typebutton重置/button /div p idtimer-status rolestatus准备开始/p /article !-- 待办清单本课新增 -- article classcard h2✅ 待办清单/h2 p classdescription写下一件具体的小事。/p form idtask-form input idtask-input typetext maxlength80 placeholder例如学习 JavaScript aria-label任务内容 autocompleteoff button typesubmit添加/button /form p idtask-summary/p p idtask-empty还没有任务先添加一件事吧。/p ul idtask-list/ul p idtask-status rolestatus/p /article !-- 便签后续课程实现 -- article classcard h2 便签/h2 p classdescription随手记下想法和提醒。/p button idnote-preview typebutton查看功能/button /article /main p idmessage rolestatus工具箱正在逐步完善。/p script // 一、番茄钟 const durationInput document.querySelector(#duration); const clock document.querySelector(#clock); const timerStatus document.querySelector(#timer-status); let remainingMs 25 * 60 * 1000; let endAt 0; let timerId null; let started false; function readDuration() { const minutes Number(durationInput.value); if (!Number.isInteger(minutes) || minutes 1 || minutes 120) { timerStatus.textContent 请输入 1120 分钟的整数。; return null; } return minutes * 60 * 1000; } function renderClock() { const totalSeconds Math.ceil(remainingMs / 1000); const minutes Math.floor(totalSeconds / 60); const seconds totalSeconds % 60; clock.textContent String(minutes).padStart(2, 0) : String(seconds).padStart(2, 0); } function stopTimer() { clearInterval(timerId); timerId null; } function finishTimer() { stopTimer(); remainingMs 0; started false; durationInput.disabled false; renderClock(); timerStatus.textContent 时间到完成一轮专注休息一下吧。; document.title 时间到效率工具箱; } function updateTimer() { remainingMs Math.max(0, endAt - Date.now()); renderClock(); if (remainingMs 0) { finishTimer(); } else { document.title clock.textContent 专注中; } } document.querySelector(#start).addEventListener(click, () { if (timerId ! null) return; if (!started) { const duration readDuration(); if (duration null) return; remainingMs duration; started true; } endAt Date.now() remainingMs; durationInput.disabled true; timerStatus.textContent 专注中……; timerId setInterval(updateTimer, 250); updateTimer(); }); document.querySelector(#pause).addEventListener(click, () { if (timerId null) return; updateTimer(); if (timerId null) return; stopTimer(); timerStatus.textContent 已暂停点击开始可以继续。; document.title 已暂停效率工具箱; }); function resetTimer() { const duration readDuration(); if (duration null) return; stopTimer(); started false; durationInput.disabled false; remainingMs duration; renderClock(); timerStatus.textContent 已重置可以调整时长。; document.title 个人效率工具箱; } document.querySelector(#reset) .addEventListener(click, resetTimer); durationInput.addEventListener(change, () { if (!started) resetTimer(); }); document.addEventListener(visibilitychange, () { if (!document.hidden timerId ! null) updateTimer(); }); // 二、待办清单 const taskForm document.querySelector(#task-form); const taskInput document.querySelector(#task-input); const taskList document.querySelector(#task-list); const taskSummary document.querySelector(#task-summary); const taskEmpty document.querySelector(#task-empty); const taskStatus document.querySelector(#task-status); let tasks []; let nextTaskId 1; // 根据任务数组更新列表和统计 function renderTasks() { taskList.replaceChildren(); const completed tasks.filter(task task.done).length; taskSummary.textContent 共 ${tasks.length} 项已完成 ${completed} 项待完成 ${tasks.length - completed} 项; taskEmpty.hidden tasks.length 0; tasks.forEach(task { const item document.createElement(li); item.className task.done ? task-item done : task-item; const check document.createElement(input); check.type checkbox; check.checked task.done; check.dataset.action toggle; check.dataset.id String(task.id); check.setAttribute( aria-label, 切换任务完成状态 task.text ); const text document.createElement(span); text.className task-text; text.textContent task.text; const remove document.createElement(button); remove.type button; remove.className danger; remove.dataset.action delete; remove.dataset.id String(task.id); remove.textContent 删除; remove.setAttribute( aria-label, 删除任务 task.text ); item.append(check, text, remove); taskList.append(item); }); } // 点击添加或按回车都通过表单提交新增任务 taskForm.addEventListener(submit, event { event.preventDefault(); const text taskInput.value.trim(); if (!text || text.length 80) { taskStatus.textContent 请输入 180 个字符的任务。; taskInput.focus(); return; } tasks.push({ id: nextTaskId, text, done: false }); renderTasks(); taskInput.value ; taskInput.focus(); taskStatus.textContent 已添加任务。; }); // 切换完成状态 taskList.addEventListener(change, event { const target event.target; if (target.dataset.action ! toggle) return; const task tasks.find( item item.id Number(target.dataset.id) ); if (!task) return; task.done target.checked; renderTasks(); taskStatus.textContent task.done ? 任务已完成。 : 任务已恢复为待完成。; }); // 删除对应编号的任务 taskList.addEventListener(click, event { const target event.target; if (target.dataset.action ! delete) return; const id Number(target.dataset.id); tasks tasks.filter(task task.id ! id); renderTasks(); taskStatus.textContent 已删除任务。; }); renderTasks(); // 三、便签预告 document.querySelector(#note-preview) .addEventListener(click, () { document.querySelector(#message).textContent 便签将在第 5 课实现。; }); /script /body /html保存时Windows 记事本的“保存类型”选择“所有文件”编码使用 UTF-8文件名填写index.html。如果保存成了index.html.txt浏览器可能无法按网页打开。可以在文件资源管理器中显示文件扩展名确认最后的后缀是.html。打开页面后先添加三条任务学习 JavaScript。阅读一篇技术文章。完成一轮专注。勾选第一条任务统计应变成“共 3 项已完成 1 项待完成 2 项”。取消勾选后已完成数应回到 0。删除其中一条后总数应变成 2。这些操作有助于理解程序是怎样运行的。添加任务时程序先执行const text taskInput.value.trim();trim()去掉输入内容两端的空格。这样输入“ 学习 JavaScript ”最终保存的就是“学习 JavaScript”。如果输入全是空格处理后就会变成空字符串程序会提示重新输入。接着程序把任务加入数组tasks.push({ id: nextTaskId, text, done: false });nextTaskId的意思是本次使用当前编号然后把编号加一。第一条任务编号为 1第二条为 2即使两条任务文字相同它们也有不同的身份。然后执行renderTasks()根据新数组更新页面。每次渲染前代码都会清空旧列表taskList.replaceChildren();如果省略这一步又把数组里的任务全部追加到页面列表就可能越来越多出现重复显示。完成任务时程序从复选框上取得任务编号找到对应对象再修改donetask.done target.checked;复选框勾选时checked为true取消勾选时为false。页面根据这个状态添加或移除done样式让任务文字出现或取消删除线。删除任务时仍然根据编号操作tasks tasks.filter(task task.id ! id);这也是为什么要给任务独立编号。仅凭文字无法准确区分两条同名任务。还有两个细节很值得注意。第一个是表单提交。我们使用form所以点击添加按钮和按回车都可以触发submit事件。代码里的event.preventDefault();用于取消这次表单提交的默认行为让新增任务由当前页面的 JavaScript 处理。参考MDN preventDefault 文档。第二个是任务文字的显示方式text.textContent task.text;这里使用textContent把输入内容作为普通文字显示。例如输入b学习 JavaScript/b页面会显示这些文字本身。MDN 建议在用户输入应当作为纯文本展示时使用textContent避免将其解析成 HTML。参考MDN innerHTML 文档中的说明。最后按这张表检查功能是否符合要求操作预期结果不输入内容点击添加显示输入提示列表不增加任务输入几个空格后添加拒绝空白任务输入任务后按回车新增一条任务输入框清空添加两条同名任务两条分别显示可以独立操作勾选任务文字出现删除线已完成数增加取消勾选恢复待完成状态统计随之更新删除一条同名任务只删除点击的那一条删除全部任务显示空列表提示统计归零输入较长的任务文字自动换行使用番茄钟开始、暂停、继续和重置仍可操作如果发现某项行为不对向 AI 提问时可以使用这个格式我在待办清单中添加了两条同名任务点击第一条的删除按钮后两条都消失了。预期是只删除点击的那一条。请检查删除时是否使用了任务编号给出修改后的相关代码。把操作步骤、实际结果和预期结果写清楚比只说“代码有问题”更方便定位。本课的任务数据保存在当前页面内存中刷新页面后会清空。下一课我们加入localStorage让已经添加的任务在刷新页面后继续保留。

相关新闻

FEX的dispatcher位置相关编码bug:JIT池低于0x119000000为何致命

FEX的dispatcher位置相关编码bug:JIT池低于0x119000000为何致命

FEX的dispatcher位置相关编码bug:JIT池低于0x119000000为何致命 【免费下载链接】Madeira Run x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT 项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira Madeira 是一个在越狱 iOS 设备上…

2026/10/4 4:55:33 阅读更多 →
agency-agents 威胁检测工程师智能体全解析:从 Sigma 规则、MITRE ATTCK 覆盖到检测即代码流水线

agency-agents 威胁检测工程师智能体全解析:从 Sigma 规则、MITRE ATTCK 覆盖到检测即代码流水线

人工智能AI 技能提示工程 【免费下载链接】agency-agents-zh 🎭 277 个即插即用的 AI 专家角色 — 支持 Claude Code/Cursor/Copilot 等 20 种工具,覆盖工程/设计/营销/金融等 20 个部门。含 64 个中国市场原创智能体(小红书/抖音/微信/飞书/…

2026/10/4 4:54:33 阅读更多 →
MR25H40CDF+TM4C1294:SPI MRAM实现不掉电数据存储的完整方案

MR25H40CDF+TM4C1294:SPI MRAM实现不掉电数据存储的完整方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 4:54:33 阅读更多 →

最新新闻

AI工程化实战:四语言协同构建生产级系统

AI工程化实战:四语言协同构建生产级系统

1. 从零构建AI工程体系:这不是写个模型脚本,而是搭一座桥“AI Engineering from Scratch”这个标题乍看像极了某门网课的副标题,但真正干过三年以上AI落地项目的人一眼就能看出——它根本不是教你怎么调用transformers.pipeline()跑通一个文本…

2026/10/4 5:29:52 阅读更多 →
STM32异常与中断本质区别:从硬件信号到C函数的完整映射链

STM32异常与中断本质区别:从硬件信号到C函数的完整映射链

1. 为什么STM32的“异常”和“中断”总被混为一谈?——从硬件信号到C语言函数的真实映射链刚接触STM32时,我翻遍《参考手册》第10章“中断与异常”,却越看越糊涂:NVIC、SysTick、HardFault、PendSV、SVC……这些名词像一串密码&am…

2026/10/4 5:29:52 阅读更多 →
激光测振仪选型指南:非接触测量如何兼顾精度与效率?

激光测振仪选型指南:非接触测量如何兼顾精度与效率?

在结构动力学测试和振动分析领域,工程师们常常面临一个两难选择:是追求极致的单点测量精度,还是为了获取全场模态振型而牺牲效率?传统的接触式加速度计虽然成熟,但在面对轻质薄壁结构、高温旋转部件或微小电子元件时&a…

2026/10/4 5:29:52 阅读更多 →
M Plan:大模型平台从Token计费到资源当量统一分配的范式升级

M Plan:大模型平台从Token计费到资源当量统一分配的范式升级

1. 这不是一次普通升级:M Plan背后的技术逻辑与真实价值最近朋友圈和开发者群都在刷“Token Plan成为历史”这句话,配上MiniMax新发布的M Plan宣传图——H3视频生成解禁、Claude Code免密接入、Cursor深度整合。很多人第一反应是:“又一个营销…

2026/10/4 5:29:52 阅读更多 →
C# WinForm+SQL Server酒店管理系统实战指南

C# WinForm+SQL Server酒店管理系统实战指南

简介:这是一套基于C# WinForm与SQL Server开发的酒店管理系统完整源码工程,面向.NET初学者及中小型酒店信息化项目开发者,旨在解决传统手工管理效率低、易出错、数据难追溯等问题,助力实现客房预订、入住登记、费用结算、报表统计…

2026/10/4 5:29:52 阅读更多 →
STM32F469与MR25H40CDF的嵌入式MRAM存储方案详解

STM32F469与MR25H40CDF的嵌入式MRAM存储方案详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 5:28:51 阅读更多 →

日新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 10:36:31 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:35 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:36 阅读更多 →