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()], +})