AI 生成: 一个简洁的待办事项 Web 应用,支持添加、完成、删除任务,并使用 localStorage 在浏览器本地保存数据。
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
// 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();
|
||||
})();
|
||||
Reference in New Issue
Block a user