创建 Next.js 项目:
npx create-next-app todo-app-with-edit
然后进入项目目录:
cd todo-app-with-edit
安装 Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
在tailwind.config.js
中配置内容路径:
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
在styles/globals.css
中导入 Tailwind 基础样式:
@tailwind base;
@tailwind components;
@tailwind utilities;
任务输入组件(components/TaskInput.js
)
// 导入 React 和 useState 钩子
import React, { useState } from 'react';
const TaskInput = ({ onAddTask }) => {
// 状态来存储任务标题和描述
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
// 检查标题是否为空
if (title.trim()!== '') {
// 创建新的任务对象
const newTask = {
id: Date.now(),
title,
description,
completed: false
};
onAddTask(newTask);
setTitle('');
setDescription('');
}
};
return (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded shadow-md">
<h2 className="text-2xl font-bold mb-4">Add New Task</h2>
<input
type="text"
placeholder="Task Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full border border-gray-300 p-2 rounded mb-4"
/>
<textarea
placeholder="Task Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full border border-gray-300 p-2 rounded mb-4"
/>
<button
type="submit"
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Add Task
</button>
</form>
);
};
export default TaskInput;
任务项组件(components/TaskItem.js
)
import React, { useState } from 'react';
const TaskItem = ({ task, onToggleComplete, onEditTask, onDeleteTask }) => {
const [isEditing, setIsEditing] = useState(false);
const [editedTitle, setEditedTitle] = useState(task.title);
const [editedDescription, setEditedDescription] = useState(task.description);
const handleEditToggle = () => {
setIsEditing(!isEditing);
};
const handleSave = () => {
const updatedTask = {
...task,
title: editedTitle,
description: editedDescription
};
onEditTask(updatedTask);
setIsEditing(false);
};
return (
<div className="bg-white p-4 rounded shadow-md mb-4">
{isEditing? (
<>
<input
type="text"
value={editedTitle}
onChange={(e) => setEditedTitle(e.target.value)}
className="w-full border border-gray-300 p-2 rounded mb-2"
/>
<textarea
value={editedDescription}
onChange={(e) => setEditedDescription(e.target.value)}
className="w-full border border-gray-300 p-2 rounded mb-2"
/>
<button
onClick={handleSave}
className="bg-green-500 text-white px-4 py-2 rounded mr-2"
>
Save
</button>
<button
onClick={handleEditToggle}
className="bg-gray-500 text-white px-4 py-2 rounded"
>
Cancel
</button>
) : (
<>
<h3 className={`text-xl font-bold ${task.completed? 'line-through' : ''}`}>{task.title}</h3>
<p className="text-gray-700">{task.description}</p>
<button
onClick={() => onToggleComplete(task.id)}
className={`${task.completed? 'bg-green-500' : 'bg-yellow-500'} text-white px-4 py-2 rounded mt-2 mr-2`}
>
{task.completed? 'Completed' : 'Mark as Completed'}
</button>
<button
onClick={handleEditToggle}
className="bg-blue-500 text-white px-4 py-2 rounded mt-2 mr-2"
>
Edit
</button>
<button
onClick={() => onDeleteTask(task.id)}
className="bg-red-500 text-white px-4 py-2 rounded mt-2"
>
Delete
</button>
)}
</div>
);
};
export default TaskItem;
任务列表组件(components/TaskList.js
)
import React from 'react';
import TaskItem from './TaskItem';
const TaskList = ({ tasks, onToggleComplete, onEditTask, onDeleteTask }) => {
return (
<div className="container mx-auto px-4">
{tasks.map(task => (
<TaskItem key={task.id} task={task} onToggleComplete={onToggleComplete} onEditTask={onEditTask} onDeleteTask={onDeleteTask} />
))}
</div>
);
};
export default TaskList;
pages/index.js
)import React, { useState, useEffect } from 'react';
import TaskInput from '../components/TaskInput';
import TaskList from '../components/TaskList';
const HomePage = () => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
// 从本地存储获取任务数据
const storedTasks = JSON.parse(localStorage.getItem('tasks')) || [];
setTasks(storedTasks);
}, []);
const addTask = (newTask) => {
setTasks([...tasks, newTask]);
};
const toggleComplete = (taskId) => {
setTasks(tasks.map(task => {
if (task.id === taskId) {
return {
...task,
completed:!task.completed
};
}
return task;
}));
};
const editTask = (updatedTask) => {
setTasks(tasks.map(task => {
if (task.id === updatedTask.id) {
return updatedTask;
}
return task;
}));
};
const deleteTask = (taskId) => {
setTasks(tasks.filter(task => task.id!== taskId));
};
useEffect(() => {
// 将任务数据保存到本地存储
localStorage.setItem('tasks', JSON.stringify(tasks));
}, [tasks]);
return (
<div className="min-h-screen bg-gray-100">
<TaskInput onAddTask={addTask} />
<TaskList tasks={tasks} onToggleComplete={toggleComplete} onEditTask={editTask} onDeleteTask={deleteTask} />
</div>
);
};
export default HomePage;
todo - app - with - edit
项目仓库。npm install && npm run build
就可以正常工作。这样,我们就完成了一个具有添加、编辑和删除功能的 ToDo 待办事项应用的构建和部署。用户可以方便地管理自己的任务列表,并且数据在本地存储中得以持久化保存。
项目仓库地址:https://github.com/bosichong/todo-app