// Simple Todo App // 数据保存在 localStorage 中 (function () { "use strict"; const STORAGE_KEY = "simple-todo-app.tasks"; // DOM 元素 const form = document.getElementById("todo-form"); const input = document.getElementById("todo-input"); const list = document.getElementById("todo-list"); const countEl = document.getElementById("todo-count"); const emptyState = document.getElementById("empty-state"); const clearCompletedBtn = document.getElementById("clear-completed"); /** * 从 localStorage 读取任务列表 * @returns {Array<{id: string, text: string, completed: boolean}>} */ function loadTasks() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const parsed = JSON.parse(raw); return Array.isArray(parsed) ? parsed : []; } catch (err) { console.error("读取任务失败:", err); return []; } } /** * 保存任务列表到 localStorage * @param {Array} tasks */ function saveTasks(tasks) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks)); } catch (err) { console.error("保存任务失败:", err); } } // 当前任务状态 let tasks = loadTasks(); /** * 生成唯一 ID */ function generateId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 8); } /** * 渲染任务列表 */ function render() { list.innerHTML = ""; tasks.forEach(function (task) { const li = document.createElement("li"); li.className = "todo-item" + (task.completed ? " completed" : ""); li.dataset.id = task.id; // 复选框 const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = task.completed; checkbox.addEventListener("change", function () { toggleTask(task.id); }); // 文本 const span = document.createElement("span"); span.className = "todo-text"; span.textContent = task.text; // 删除按钮 const delBtn = document.createElement("button"); delBtn.className = "delete-btn"; delBtn.type = "button"; delBtn.setAttribute("aria-label", "删除任务"); delBtn.textContent = "✕"; delBtn.addEventListener("click", function () { deleteTask(task.id); }); li.appendChild(checkbox); li.appendChild(span); li.appendChild(delBtn); list.appendChild(li); }); // 空状态 if (tasks.length === 0) { emptyState.classList.remove("hidden"); } else { emptyState.classList.add("hidden"); } // 统计 const remaining = tasks.filter(function (t) { return !t.completed; }).length; countEl.textContent = "共 " + tasks.length + " 项 · 未完成 " + remaining + " 项"; } /** * 添加任务 */ function addTask(text) { const trimmed = text.trim(); if (!trimmed) return; tasks.push({ id: generateId(), text: trimmed, completed: false }); saveTasks(tasks); render(); } /** * 切换任务完成状态 */ function toggleTask(id) { tasks = tasks.map(function (t) { return t.id === id ? Object.assign({}, t, { completed: !t.completed }) : t; }); saveTasks(tasks); render(); } /** * 删除任务 */ function deleteTask(id) { tasks = tasks.filter(function (t) { return t.id !== id; }); saveTasks(tasks); render(); } /** * 清除已完成任务 */ function clearCompleted() { tasks = tasks.filter(function (t) { return !t.completed; }); saveTasks(tasks); render(); } // 事件绑定 form.addEventListener("submit", function (e) { e.preventDefault(); addTask(input.value); input.value = ""; input.focus(); }); clearCompletedBtn.addEventListener("click", clearCompleted); // 初始渲染 render(); // 自动聚焦输入框 input.focus(); })();