diff --git a/members/justice010/task1/todo-app/.gitignore b/members/justice010/task1/todo-app/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/members/justice010/task1/todo-app/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/members/justice010/task1/todo-app/README.md b/members/justice010/task1/todo-app/README.md
new file mode 100644
index 000000000..f768e33fc
--- /dev/null
+++ b/members/justice010/task1/todo-app/README.md
@@ -0,0 +1,8 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
diff --git a/members/justice010/task1/todo-app/eslint.config.js b/members/justice010/task1/todo-app/eslint.config.js
new file mode 100644
index 000000000..238d2e4e6
--- /dev/null
+++ b/members/justice010/task1/todo-app/eslint.config.js
@@ -0,0 +1,38 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+
+export default [
+ { ignores: ['dist'] },
+ {
+ files: ['**/*.{js,jsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...js.configs.recommended.rules,
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ ...reactHooks.configs.recommended.rules,
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+]
diff --git a/members/justice010/task1/todo-app/index.html b/members/justice010/task1/todo-app/index.html
new file mode 100644
index 000000000..b72bac518
--- /dev/null
+++ b/members/justice010/task1/todo-app/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/members/justice010/task1/todo-app/package.json b/members/justice010/task1/todo-app/package.json
new file mode 100644
index 000000000..99056d599
--- /dev/null
+++ b/members/justice010/task1/todo-app/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "todo-app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "prop-types": "^15.8.1",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.9.0",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "eslint": "^9.9.0",
+ "eslint-plugin-react": "^7.35.0",
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
+ "eslint-plugin-react-refresh": "^0.4.9",
+ "globals": "^15.9.0",
+ "vite": "^5.4.1"
+ }
+}
diff --git a/members/justice010/task1/todo-app/src/AddToDo.jsx b/members/justice010/task1/todo-app/src/AddToDo.jsx
new file mode 100644
index 000000000..15742352b
--- /dev/null
+++ b/members/justice010/task1/todo-app/src/AddToDo.jsx
@@ -0,0 +1,31 @@
+import { useState } from "react";
+import { PropTypes } from "prop-types";
+
+export function AddToDo({ addToDo }) {
+ const [inputValue, setInputValue] = useState("");
+
+ function handleInputChange(e) {
+ setInputValue(e.target.value);
+ }
+
+ function handleSubmit() {
+ addToDo(inputValue);
+ setInputValue("");
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+AddToDo.propTypes = {
+ addToDo: PropTypes.func.isRequired,
+};
diff --git a/members/justice010/task1/todo-app/src/App.css b/members/justice010/task1/todo-app/src/App.css
new file mode 100644
index 000000000..78df96135
--- /dev/null
+++ b/members/justice010/task1/todo-app/src/App.css
@@ -0,0 +1,77 @@
+/* App.css */
+/* App.css */
+
+/* Header 组件样式 */
+.header {
+ background-color: #4caf50; /* 绿色背景 */
+ color: white; /* 白色文字 */
+ padding: 20px; /* 内边距 */
+ text-align: center; /* 文本居中 */
+ border-radius: 8px; /* 圆角边框 */
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
+ font-family: "Arial", sans-serif; /* 字体 */
+}
+
+/* AddToDo 组件样式 */
+.add-todo {
+ background-color: #f9f9f9; /* 浅灰色背景 */
+ border: 1px solid #ddd; /* 边框颜色 */
+ border-radius: 8px; /* 圆角边框 */
+ padding: 15px; /* 内边距 */
+ display: flex; /* 使用弹性布局 */
+ gap: 10px; /* 元素间距 */
+ align-items: center; /* 垂直居中对齐 */
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
+}
+
+.add-todo input {
+ flex: 1; /* 输入框占用剩余空间 */
+ padding: 10px; /* 内边距 */
+ border: 1px solid #ddd; /* 边框颜色 */
+ border-radius: 4px; /* 圆角边框 */
+}
+
+.add-todo button {
+ background-color: #4caf50; /* 绿色背景 */
+ color: white; /* 白色文字 */
+ border: none; /* 去掉边框 */
+ border-radius: 4px; /* 圆角边框 */
+ padding: 10px 15px; /* 内边距 */
+ cursor: pointer; /* 鼠标指针变为手形 */
+ font-size: 1rem; /* 字体大小 */
+}
+
+.add-todo button:hover {
+ background-color: #45a049; /* 鼠标悬停时的背景颜色 */
+}
+
+/* ToDoItem 组件样式 */
+.todo-item {
+ background-color: #fff; /* 白色背景 */
+ border: 1px solid #ddd; /* 边框颜色 */
+ border-radius: 8px; /* 圆角边框 */
+ padding: 15px; /* 内边距 */
+ display: flex; /* 使用弹性布局 */
+ align-items: center; /* 垂直居中对齐 */
+ gap: 10px; /* 元素间距 */
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
+ margin-bottom: 10px; /* 下边距 */
+}
+
+.todo-item input[type="checkbox"] {
+ margin-right: 10px; /* 右边距 */
+}
+
+.todo-item button {
+ background-color: #f44336; /* 红色背景 */
+ color: white; /* 白色文字 */
+ border: none; /* 去掉边框 */
+ border-radius: 4px; /* 圆角边框 */
+ padding: 5px 10px; /* 内边距 */
+ cursor: pointer; /* 鼠标指针变为手形 */
+ font-size: 0.875rem; /* 字体大小 */
+}
+
+.todo-item button:hover {
+ background-color: #d32f2f; /* 鼠标悬停时的背景颜色 */
+}
diff --git a/members/justice010/task1/todo-app/src/App.jsx b/members/justice010/task1/todo-app/src/App.jsx
new file mode 100644
index 000000000..b860bd2a3
--- /dev/null
+++ b/members/justice010/task1/todo-app/src/App.jsx
@@ -0,0 +1,53 @@
+import "./App.css";
+import { Header } from "./Header";
+import { AddToDo } from "./AddToDo";
+import { ToDoList } from "./ToDoList";
+import { useState, useEffect } from "react";
+
+// 主应用组件
+export default function App() {
+ const [todos, setTodos] = useState(() => {
+ const localTodos = localStorage.getItem("todos");
+ return localTodos ? JSON.parse(localTodos) : initialtodos;
+ });
+
+ // 当todos发生变化时,将其存储到localStorage
+ useEffect(() => {
+ localStorage.setItem("todos", JSON.stringify(todos));
+ }, [todos]);
+
+ // 添加新的待办事项
+ function addNewTodo(text) {
+ const nextId = todos.length ? Math.max(...todos.map((todo) => todo.id)) + 1 : 0;
+ setTodos([...todos, {id: nextId, text, isDone: false }]);
+ }
+
+ // 删除指定的待办事项
+ function deleteTodo(id) {
+ setTodos(todos.filter((todo) => todo.id !== id));
+ }
+
+ // 切换待办事项的完成状态
+ function toggleTodo(id) {
+ setTodos(
+ todos.map((todo) =>
+ todo.id === id ? { ...todo, isDone: !todo.isDone } : todo
+ )
+ );
+ }
+
+ return (
+
+ );
+}
+
+// 初始待办事项列表
+const initialtodos = [
+ { id: 0, text: "买菜", isDone: false },
+ { id: 1, text: "打扫卫生", isDone: false },
+ { id: 2, text: "洗车", isDone: true },
+];
\ No newline at end of file
diff --git a/members/justice010/task1/todo-app/src/Header.jsx b/members/justice010/task1/todo-app/src/Header.jsx
new file mode 100644
index 000000000..429d68613
--- /dev/null
+++ b/members/justice010/task1/todo-app/src/Header.jsx
@@ -0,0 +1,7 @@
+export function Header() {
+ return (
+
+
待办事项列表
+
+ );
+}
diff --git a/members/justice010/task1/todo-app/src/ToDoItem.jsx b/members/justice010/task1/todo-app/src/ToDoItem.jsx
new file mode 100644
index 000000000..40d8fcc25
--- /dev/null
+++ b/members/justice010/task1/todo-app/src/ToDoItem.jsx
@@ -0,0 +1,30 @@
+import PropTypes from "prop-types";
+
+export function ToDoItem({ todo, onToggle, onDelete }) {
+
+ function handleToggle() {
+ onToggle(todo.id);
+ }
+
+ function handleDelete() {
+ onDelete(todo.id);
+ }
+
+ return (
+
+
+ {todo.text}
+
+
+ );
+}
+
+ToDoItem.propTypes = {
+ todo: PropTypes.shape({
+ text: PropTypes.string.isRequired,
+ isDone: PropTypes.bool.isRequired,
+ id: PropTypes.number.isRequired,
+ }).isRequired,
+ onDelete: PropTypes.func.isRequired,
+ onToggle: PropTypes.func.isRequired,
+};
diff --git a/members/justice010/task1/todo-app/src/ToDoList.jsx b/members/justice010/task1/todo-app/src/ToDoList.jsx
new file mode 100644
index 000000000..2c5871875
--- /dev/null
+++ b/members/justice010/task1/todo-app/src/ToDoList.jsx
@@ -0,0 +1,23 @@
+import { ToDoItem } from "./ToDoItem";
+import { PropTypes } from "prop-types";
+
+export function ToDoList({ todos, onDelete, onToggle }) {
+ return (
+
+ {todos.map((todo) => (
+
+ ))}
+
+ );
+}
+
+ToDoList.propTypes = {
+ todos: PropTypes.array.isRequired,
+ onDelete: PropTypes.func.isRequired,
+ onToggle: PropTypes.func.isRequired,
+};
diff --git a/members/justice010/task1/todo-app/src/main.jsx b/members/justice010/task1/todo-app/src/main.jsx
new file mode 100644
index 000000000..3d9da8acd
--- /dev/null
+++ b/members/justice010/task1/todo-app/src/main.jsx
@@ -0,0 +1,9 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/members/justice010/task1/todo-app/vite.config.js b/members/justice010/task1/todo-app/vite.config.js
new file mode 100644
index 000000000..5a33944a9
--- /dev/null
+++ b/members/justice010/task1/todo-app/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})