当前位置: 首页 > news >正文

JavaScript案例(待办事项列表)

项目概述

使用原生JavaScript实现一个功能完善的待办事项列表应用,支持任务的添加、编辑、删除和完成状态切换,并通过localStorage实现数据持久化。

核心功能需求

  1. 任务管理:添加、编辑、删除任务
  2. 状态切换:标记任务完成/未完成,带删除线样式
  3. 数据持久化:使用localStorage保存任务数据
  4. 交互设计:支持回车添加任务,按钮操作

技术栈

  • HTML5:页面结构
  • CSS3:样式设计,包括完成状态样式
  • JavaScript:DOM操作、事件处理、本地存储
  • 本地存储:localStorage API

项目结构

todo-list/
├── index.html      # 主页面
├── css/
│   └── style.css   # 样式文件
├── js/└── app.js      # 核心逻辑

实现思路

1. 数据结构设计

// 任务对象结构
{id: Number,         // 唯一标识符content: String,    // 任务内容completed: Boolean, // 完成状态createdAt: Date     // 创建时间
}// 任务数组
let tasks = [];

2. 核心功能模块

  • DOM操作模块:动态创建任务元素、更新任务列表
  • 事件处理模块:添加、编辑、删除、状态切换事件
  • 本地存储模块:保存、读取、更新本地数据
  • 交互优化模块:回车添加、焦点管理、动画效果

3. 关键技术点实现

  • 动态创建元素:使用document.createElement和appendChild
  • 状态切换:通过classList.toggle(‘completed’)实现样式切换
  • 本地存储:JSON.stringify和JSON.parse处理数据
  • 事件委托:利用事件冒泡机制处理动态元素事件

代码:

  • index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>JavaScript待办事项列表</title><link rel="stylesheet" href="css/style.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body><div class="container"><header><h1>待办事项列表 <i class="fas fa-check-circle"></i></h1><p>管理你的任务,提高工作效率</p></header><div class="task-input"><input type="text" id="taskInput" placeholder="添加新任务..."><button id="addTaskBtn"><i class="fas fa-plus"></i> 添加任务</button></div><div class="task-stats"><span>剩余任务: <strong id="remainingTasks">0</strong></span><button id="clearCompletedBtn"><i class="fas fa-trash"></i> 清除已完成</button></div><ul id="taskList" class="task-list"><!-- 任务项将通过JavaScript动态生成 --><li class="task-item placeholder"><span>你的任务将显示在这里</span></li></ul></div><script src="js/app.js"></script>
</body>
</html>
  • style.css
/* 基础样式重置 */
.container{background-color: #ebf2fa;
}
* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}body {background-color: #f5f5f5;color: #333;line-height: 1.6;
}.container {max-width: 800px;margin: 0 auto;padding: 20px;
}header {text-align: center;margin-bottom: 30px;padding-bottom: 20px;border-bottom: 1px solid #eee;
}header h1 {font-size: 2.5rem;margin-bottom: 10px;color: #2c3e50;
}header p {color: #7f8c8d;font-size: 1.1rem;
}/* 任务输入区域 */
.task-input {display: flex;gap: 10px;margin-bottom: 25px;
}.task-input input {flex: 1;padding: 12px 15px;border: 2px solid #ddd;border-radius: 6px;font-size: 1rem;transition: all 0.3s ease;
}.task-input input:focus {outline: none;border-color: #3498db;box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}.task-input button {padding: 12px 20px;background-color: #3498db;color: white;border: none;border-radius: 6px;cursor: pointer;font-size: 1rem;transition: background-color 0.3s ease;display: flex;align-items: center;gap: 8px;
}.task-input button:hover {background-color: #2980b9;
}/* 任务统计区域 */
.task-stats {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;padding: 10px 15px;background-color: #fff;border-radius: 6px;box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}.task-stats span {color: #7f8c8d;font-size: 0.95rem;
}.task-stats strong {color: #2c3e50;
}.task-stats button {background-color: transparent;color: #e74c3c;border: none;cursor: pointer;font-size: 0.95rem;display: flex;align-items: center;gap: 5px;padding: 5px 10px;border-radius: 4px;transition: all 0.2s ease;
}.task-stats button:hover {background-color: rgba(231, 76, 60, 0.1);
}/* 任务列表 */
.task-list {list-style: none;margin-top: 20px;
}.task-item {background-color: white;padding: 15px;border-radius: 6px;margin-bottom: 10px;display: flex;align-items: center;gap: 15px;box-shadow: 0 2px 4px rgba(0,0,0,0.05);transition: transform 0.2s ease, box-shadow 0.2s ease;
}.task-item:hover {transform: translateY(-2px);box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}.task-item.placeholder {background-color: rgba(255,255,255,0.5);justify-content: center;color: #95a5a6;padding: 40px 20px;
}/* 任务复选框 */
.task-checkbox {width: 22px;height: 22px;border-radius: 50%;border: 2px solid #ddd;cursor: pointer;display: flex;align-items: center;justify-content: center;flex-shrink: 0;transition: all 0.3s ease;
}.task-checkbox:hover {border-color: #3498db;
}.task-checkbox.checked {background-color: #2ecc71;border-color: #2ecc71;
}/* 任务内容 */
.task-content {flex: 1;transition: color 0.3s ease, text-decoration 0.3s ease;font-size: 1.05rem;
}.task-item.completed .task-content {text-decoration: line-through;color: #95a5a6;
}/* 任务操作按钮 */
.task-actions {display: flex;gap: 8px;
}.task-btn {background: none;border: none;cursor: pointer;font-size: 1rem;padding: 5px;border-radius: 4px;transition: all 0.2s ease;display: flex;align-items: center;justify-content: center;
}.edit-btn {color: #f39c12;
}.edit-btn:hover {background-color: rgba(243, 156, 18, 0.1);
}.delete-btn {color: #e74c3c;
}.delete-btn:hover {background-color: rgba(231, 76, 60, 0.1);
}/* 响应式设计 */
@media (max-width: 600px) {.task-input {flex-direction: column;}.task-input button {width: 100%;justify-content: center;}.task-stats {flex-direction: column;align-items: stretch;gap: 10px;}.task-stats button {width: 100%;justify-content: center;}
}
  • app.js
// 任务数据存储
let tasks = [];// DOM元素
const taskInput = document.getElementById('taskInput');
const addTaskBtn = document.getElementById('addTaskBtn');
const taskList = document.getElementById('taskList');
const remainingTasksEl = document.getElementById('remainingTasks');
const clearCompletedBtn = document.getElementById('clearCompletedBtn');
const placeholderEl = document.querySelector('.placeholder');// 初始化应用
function init() {// 从本地存储加载任务loadTasks();// 渲染任务列表renderTasks();// 绑定事件监听器bindEvents();
}// 从本地存储加载任务
function loadTasks() {const storedTasks = localStorage.getItem('tasks');if (storedTasks) {tasks = JSON.parse(storedTasks);}
}// 保存任务到本地存储
function saveTasks() {localStorage.setItem('tasks', JSON.stringify(tasks));
}// 渲染任务列表
function renderTasks() {// 清空任务列表taskList.innerHTML = '';// 检查是否有任务if (tasks.length === 0) {taskList.appendChild(placeholderEl);remainingTasksEl.textContent = '0';return;}// 隐藏占位符if (placeholderEl.parentNode === taskList) {taskList.removeChild(placeholderEl);}// 渲染每个任务tasks.forEach(task => {const taskElement = createTaskElement(task);taskList.appendChild(taskElement);});// 更新剩余任务数量updateRemainingTasks();
}// 创建任务元素
function createTaskElement(task) {const li = document.createElement('li');li.className = `task-item ${task.completed ? 'completed' : ''}`;li.dataset.taskId = task.id;li.innerHTML = `<div class="task-checkbox ${task.completed ? 'checked' : ''}">${task.completed ? '<i class="fas fa-check"></i>' : ''}</div><div class="task-content" contenteditable="false">${escapeHTML(task.content)}</div><div class="task-actions"><button class="task-btn edit-btn" title="编辑任务"><i class="fas fa-edit"></i></button><button class="task-btn delete-btn" title="删除任务"><i class="fas fa-trash"></i></button></div>`;return li;
}// 添加新任务
function addTask(content) {if (!content.trim()) return; // 忽略空任务const newTask = {id: generateId(),content: content.trim(),completed: false,createdAt: new Date().toISOString()};tasks.unshift(newTask); // 添加到数组开头saveTasks();renderTasks();// 清空输入框taskInput.value = '';// 聚焦输入框taskInput.focus();
}// 切换任务完成状态
function toggleTaskCompletion(taskId) {const task = tasks.find(t => t.id === taskId);if (task) {task.completed = !task.completed;saveTasks();renderTasks();}
}// 编辑任务内容
function editTask(taskId, newContent) {const task = tasks.find(t => t.id === taskId);if (task && newContent.trim()) {task.content = newContent.trim();saveTasks();renderTasks();}
}// 删除任务
function deleteTask(taskId) {tasks = tasks.filter(task => task.id !== taskId);saveTasks();renderTasks();
}// 清除所有已完成任务
function clearCompletedTasks() {tasks = tasks.filter(task => !task.completed);saveTasks();renderTasks();
}// 更新剩余任务数量
function updateRemainingTasks() {const remaining = tasks.filter(task => !task.completed).length;remainingTasksEl.textContent = remaining;
}// 绑定事件监听器
function bindEvents() {// 添加任务按钮点击事件addTaskBtn.addEventListener('click', () => {addTask(taskInput.value);});// 输入框回车事件taskInput.addEventListener('keypress', (e) => {if (e.key === 'Enter') {addTask(taskInput.value);}});// 任务列表事件委托taskList.addEventListener('click', (e) => {const taskItem = e.target.closest('.task-item');if (!taskItem || taskItem.classList.contains('placeholder')) return;const taskId = parseInt(taskItem.dataset.taskId);// 切换完成状态if (e.target.closest('.task-checkbox')) {toggleTaskCompletion(taskId);}// 删除任务if (e.target.closest('.delete-btn')) {deleteTask(taskId);}// 编辑任务if (e.target.closest('.edit-btn')) {const contentEl = taskItem.querySelector('.task-content');enableEditMode(contentEl, taskId);}});// 双击任务内容编辑taskList.addEventListener('dblclick', (e) => {const contentEl = e.target.closest('.task-content');if (contentEl) {const taskItem = contentEl.closest('.task-item');const taskId = parseInt(taskItem.dataset.taskId);enableEditMode(contentEl, taskId);}});// 清除已完成按钮点击事件clearCompletedBtn.addEventListener('click', clearCompletedTasks);
}// 启用编辑模式
function enableEditMode(contentEl, taskId) {// 移除所有任务的编辑状态document.querySelectorAll('.task-content').forEach(el => {el.contentEditable = 'false';el.classList.remove('editing');});// 设置当前任务为编辑状态contentEl.contentEditable = 'true';contentEl.classList.add('editing');contentEl.focus();// 保存编辑内容const saveEdit = (e) => {if (e.type === 'blur' || (e.type === 'keydown' && e.key === 'Enter')) {e.preventDefault();editTask(taskId, contentEl.textContent);contentEl.contentEditable = 'false';contentEl.classList.remove('editing');// 移除事件监听器contentEl.removeEventListener('blur', saveEdit);contentEl.removeEventListener('keydown', saveEdit);}};// 添加事件监听器contentEl.addEventListener('blur', saveEdit);contentEl.addEventListener('keydown', saveEdit);
}// 生成唯一ID
function generateId() {return Date.now() + Math.floor(Math.random() * 1000);
}// HTML转义
function escapeHTML(str) {return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
}// 初始化应用
document.addEventListener('DOMContentLoaded', init);

效果展示:
在这里插入图片描述

http://www.lryc.cn/news/610247.html

相关文章:

  • 项目配置文件正确但是启动失败,报配置文件内容错误或中间件地址与实际不符
  • 蓝桥杯----AT24C02
  • 在Windows 11+I7+32GB内存+RTX 3060上部署Stable Diffusion 3.5 Medium详细步骤
  • 《Python 实用项目与工具制作指南》· 3.2 实战·开发密码管理器
  • Spring AI实战:SpringBoot项目结合Spring AI开发——提示词(Prompt)技术与工程实战详解
  • 在CAPL自动化脚本中巧用panel函数
  • 贯穿全生命周期,生成式AI正在重塑游戏行业
  • Pytorch-05 所以计算图和自动微分到底是什么?(计算图及自动微分引擎原理讲解)
  • 数分思维13:AB测试
  • HTTP、WebSocket、TCP、Kafka等通讯渠道对比详解
  • C# 类型
  • Python-初学openCV——图像预处理(七)——模板匹配、霍夫变换
  • 专题:2025生命科学与生物制药全景报告:产业图谱、投资方向及策略洞察|附130+份报告PDF、原数据表汇总下载
  • idea添加gitlab访问令牌
  • Docker-07.Docker基础-数据卷挂载
  • leetcode_11 盛最多水的容器
  • C++ stdset 与 stdmultiset 深度比较
  • pathspec ‘with_def_layout‘ did not match any file(s) known to git`
  • jenkins+gitlab自动发布系统
  • IntelliJIDEA上传GitHub全攻略
  • JVM学习专题(四)对象创建过程
  • 数据结构:如何判断一个链表中是否存在环(Check for LOOP in Linked List)
  • IDM(Internet Download Manager)是什么?它有什么作用
  • 微帧GPU视频硬编优化引擎:面向人工智能大时代的AI算法与硬编协同优化方案
  • C语言实现Elasticsearch增删改查API
  • 部署 Kibana 8.2.2 可视化管理 Elasticsearch 8.2.2 集群
  • 解决 PS暂存盘已满的五种方法
  • PSOFT Pencil+ 4.25 插件安装教程(适用于 3ds Max 2022-2025)
  • 【c51单片机利用p2口,外接八个流水灯实现流水灯效果1.3.5.7.2.4.6.8亮】2022-10-9
  • MinIO 服务日志与监控实战:日志配置、Webhook、事件通知、Prometheus+Grafana 可视化全流程指南