175 lines
3.9 KiB
JavaScript
175 lines
3.9 KiB
JavaScript
// 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();
|
|
})();
|