From 1ead49fab34ae528ccbc43351f686271da3341b1 Mon Sep 17 00:00:00 2001 From: xuedongZ Date: Thu, 15 Aug 2024 22:05:09 +0800 Subject: [PATCH] feat: react-todo app --- members/danileTX/task1/.gitignore | 24 +++++++++ members/danileTX/task1/README.md | 17 +++++++ members/danileTX/task1/eslint.config.js | 26 ++++++++++ members/danileTX/task1/index.html | 13 +++++ members/danileTX/task1/package.json | 32 ++++++++++++ members/danileTX/task1/public/vite.svg | 1 + members/danileTX/task1/src/AddToDo.tsx | 57 +++++++++++++++++++++ members/danileTX/task1/src/App.css | 42 ++++++++++++++++ members/danileTX/task1/src/App.tsx | 60 +++++++++++++++++++++++ members/danileTX/task1/src/Header.tsx | 5 ++ members/danileTX/task1/src/ToDoItem.tsx | 39 +++++++++++++++ members/danileTX/task1/src/ToDoList.tsx | 28 +++++++++++ members/danileTX/task1/src/index.css | 36 ++++++++++++++ members/danileTX/task1/src/main.tsx | 10 ++++ members/danileTX/task1/src/service.js | 28 +++++++++++ members/danileTX/task1/src/types.ts | 8 +++ members/danileTX/task1/src/vite-env.d.ts | 1 + members/danileTX/task1/tsconfig.app.json | 24 +++++++++ members/danileTX/task1/tsconfig.json | 7 +++ members/danileTX/task1/tsconfig.node.json | 22 +++++++++ members/danileTX/task1/vite.config.ts | 7 +++ 21 files changed, 487 insertions(+) create mode 100644 members/danileTX/task1/.gitignore create mode 100644 members/danileTX/task1/README.md create mode 100644 members/danileTX/task1/eslint.config.js create mode 100644 members/danileTX/task1/index.html create mode 100644 members/danileTX/task1/package.json create mode 100644 members/danileTX/task1/public/vite.svg create mode 100644 members/danileTX/task1/src/AddToDo.tsx create mode 100644 members/danileTX/task1/src/App.css create mode 100644 members/danileTX/task1/src/App.tsx create mode 100644 members/danileTX/task1/src/Header.tsx create mode 100644 members/danileTX/task1/src/ToDoItem.tsx create mode 100644 members/danileTX/task1/src/ToDoList.tsx create mode 100644 members/danileTX/task1/src/index.css create mode 100644 members/danileTX/task1/src/main.tsx create mode 100644 members/danileTX/task1/src/service.js create mode 100644 members/danileTX/task1/src/types.ts create mode 100644 members/danileTX/task1/src/vite-env.d.ts create mode 100644 members/danileTX/task1/tsconfig.app.json create mode 100644 members/danileTX/task1/tsconfig.json create mode 100644 members/danileTX/task1/tsconfig.node.json create mode 100644 members/danileTX/task1/vite.config.ts diff --git a/members/danileTX/task1/.gitignore b/members/danileTX/task1/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/members/danileTX/task1/.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/danileTX/task1/README.md b/members/danileTX/task1/README.md new file mode 100644 index 000000000..7e85bf429 --- /dev/null +++ b/members/danileTX/task1/README.md @@ -0,0 +1,17 @@ +# React-todo-list + +## 项目简介 +一个代办事项app。具备以下功能: +1.添加待办事项:用户可以通过输入框输入待办事项,并点击按钮进行添加。 +2.删除待办事项:每个待办事项旁边有一个删除按钮,点击后可以删除该事项。 +3.标记完成:用户可以通过点击待办事项,标记其为已完成或未完成。 +4.保存到本地存储:使用 useEffect 将待办事项保存到本地存储,并在页面刷新时恢复。 + +## 技术栈 +- **框架/库**: React, react-bootstrap等等 +- **状态管理**: localstorage +- **构建工具**: Vite 等 +- **其他**: TypeScript + +## demo +https://daniletx-react-todo.netlify.app/ diff --git a/members/danileTX/task1/eslint.config.js b/members/danileTX/task1/eslint.config.js new file mode 100644 index 000000000..c4bc8f33c --- /dev/null +++ b/members/danileTX/task1/eslint.config.js @@ -0,0 +1,26 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config({ + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + ignores: ['dist'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +}) diff --git a/members/danileTX/task1/index.html b/members/danileTX/task1/index.html new file mode 100644 index 000000000..e4b78eae1 --- /dev/null +++ b/members/danileTX/task1/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/members/danileTX/task1/package.json b/members/danileTX/task1/package.json new file mode 100644 index 000000000..1b2ec50e7 --- /dev/null +++ b/members/danileTX/task1/package.json @@ -0,0 +1,32 @@ +{ + "name": "react-todo-list", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "bootstrap": "^5.3.3", + "react": "^18.3.1", + "react-bootstrap": "^2.10.4", + "react-dom": "^18.3.1", + "react-icons": "^5.3.0" + }, + "devDependencies": { + "@eslint/js": "^9.8.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^9.8.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.0", + "vite": "^5.4.0" + } +} diff --git a/members/danileTX/task1/public/vite.svg b/members/danileTX/task1/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/members/danileTX/task1/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/members/danileTX/task1/src/AddToDo.tsx b/members/danileTX/task1/src/AddToDo.tsx new file mode 100644 index 000000000..db0bfa902 --- /dev/null +++ b/members/danileTX/task1/src/AddToDo.tsx @@ -0,0 +1,57 @@ +import { useState } from "react"; + +interface InputTaskProps { + addTaskCallback: (arg0: string) => void; +} + + + +const InputTask = (props: InputTaskProps) => { + const [inputValue, setInputValue] = useState(""); + const callback = props.addTaskCallback; + + const clearInput = () => { + const tempval = inputValue.trim(); + if (!tempval) { + return; + } + callback(tempval); + + setInputValue(""); + }; + + + return ( +
+

接下来,做什么

+
+
+
+
+ setInputValue(e.target.value)} + required + > +
+ +
+
+
+
+
+
+ ) +} + +export default InputTask; \ No newline at end of file diff --git a/members/danileTX/task1/src/App.css b/members/danileTX/task1/src/App.css new file mode 100644 index 000000000..b9d355df2 --- /dev/null +++ b/members/danileTX/task1/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/members/danileTX/task1/src/App.tsx b/members/danileTX/task1/src/App.tsx new file mode 100644 index 000000000..8bf8cec1d --- /dev/null +++ b/members/danileTX/task1/src/App.tsx @@ -0,0 +1,60 @@ +import { useState, useEffect } from 'react'; +import Header from './Header'; +import AddToDo from "./AddToDo"; +import ToDoList from "./ToDoList"; +import {taskItemType} from "./types" +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error +import StorageService from "./service.js" +import "bootstrap/dist/css/bootstrap.min.css"; +import './App.css'; + +function App() { + const [tasks, setTasks] = useState({ tasks: [] as taskItemType[] }) + useEffect(()=>{ + const cacheTasks = StorageService.getItem('tasks'); + if(Array.isArray(cacheTasks) && cacheTasks.length) { + setTasks({ tasks: cacheTasks}); + } + },[]) + + const addTasks = (task: string) => { + const tempTasks: taskItemType[] = tasks["tasks"]; + const taskItem: taskItemType = { id: Date.now().toString(32), text: task, strike: false }; + tempTasks.push(taskItem) + setTasks({ tasks: tempTasks }); + StorageService.setItem('tasks',tempTasks) + } + + const removeTasks = (taskid: string) => { + const tempTasks = tasks["tasks"]; + const result = tempTasks.filter(item=> item.id !== taskid) + + setTasks({ tasks: result }); + StorageService.setItem('tasks',result) + } + + const strikeTask = (taskid: string) => { + const tempTasks = tasks["tasks"]; + const result = []; + for (let i = 0; i < tempTasks.length; i++) { + if (tempTasks[i].id === taskid) { + const boolVal= tempTasks[i].strike; + tempTasks[i].strike = !boolVal + } + result.push(tempTasks[i]); + } + setTasks({ tasks: result }); + StorageService.setItem('tasks',result) + } + + return ( +
+
+ + +
+ ) +} + +export default App diff --git a/members/danileTX/task1/src/Header.tsx b/members/danileTX/task1/src/Header.tsx new file mode 100644 index 000000000..19c4932e2 --- /dev/null +++ b/members/danileTX/task1/src/Header.tsx @@ -0,0 +1,5 @@ +function Header() { + return

React To-Do List

+} + +export default Header diff --git a/members/danileTX/task1/src/ToDoItem.tsx b/members/danileTX/task1/src/ToDoItem.tsx new file mode 100644 index 000000000..27e8ab325 --- /dev/null +++ b/members/danileTX/task1/src/ToDoItem.tsx @@ -0,0 +1,39 @@ +import { FaTrash } from "react-icons/fa"; +import type { taskItemType } from "./types"; + +interface ToDoItemProps { + task: taskItemType; + removeTaskCallback: (id: string) => void; + strikeTaskCallback: (id: string) => void +} + + +function ToDoItem(props: ToDoItemProps) { + return ( +
  • +
    +
    +

    + props.strikeTaskCallback(props.task.id)} className="form-check-input me-3" type="checkbox" value="" id="flexCheckDefault" checked={props.task.strike} /> + {props.task.strike ? + {props.task.text} : + <>{props.task.text} + } +

    +
    +
    + +
    +
    +
  • + ) +} + +export default ToDoItem; \ No newline at end of file diff --git a/members/danileTX/task1/src/ToDoList.tsx b/members/danileTX/task1/src/ToDoList.tsx new file mode 100644 index 000000000..ce5007759 --- /dev/null +++ b/members/danileTX/task1/src/ToDoList.tsx @@ -0,0 +1,28 @@ +import ToDoItem from "./ToDoItem" +import type { taskItemType } from "./types"; + +interface ListTasksProps { + tasksList: taskItemType[]; + removeTaskCallback: (id: string) => void; + strikeTaskCallback: (id: string) => void; +} + +export default function ListTasks(props: ListTasksProps) { + return ( +
    + +
    + ) +} \ No newline at end of file diff --git a/members/danileTX/task1/src/index.css b/members/danileTX/task1/src/index.css new file mode 100644 index 000000000..4ab297378 --- /dev/null +++ b/members/danileTX/task1/src/index.css @@ -0,0 +1,36 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + background: linear-gradient(-45deg, #380036, #0cbaba); + background-size: 800% 800%; + animation: gradient 15s ease infinite; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +.vertical-center { + margin: 0; + position: absolute; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +@keyframes gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} diff --git a/members/danileTX/task1/src/main.tsx b/members/danileTX/task1/src/main.tsx new file mode 100644 index 000000000..6f4ac9bcc --- /dev/null +++ b/members/danileTX/task1/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/members/danileTX/task1/src/service.js b/members/danileTX/task1/src/service.js new file mode 100644 index 000000000..36d5b1d45 --- /dev/null +++ b/members/danileTX/task1/src/service.js @@ -0,0 +1,28 @@ +const StorageService = { + setItem(key, value) { + if (typeof value === 'object') { + value = JSON.stringify(value); + } + localStorage.setItem(key, value); + }, + + getItem(key) { + const value = localStorage.getItem(key); + try { + return JSON.parse(value); + } catch (e) { + return value; + } + }, + + removeItem(key) { + localStorage.removeItem(key); + }, + + clear() { + localStorage.clear(); + } + }; + + + export default StorageService; diff --git a/members/danileTX/task1/src/types.ts b/members/danileTX/task1/src/types.ts new file mode 100644 index 000000000..7c43f6006 --- /dev/null +++ b/members/danileTX/task1/src/types.ts @@ -0,0 +1,8 @@ +type taskItemType = { + id: string; + text: string; + strike: boolean; +} + +export type { taskItemType } + diff --git a/members/danileTX/task1/src/vite-env.d.ts b/members/danileTX/task1/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/members/danileTX/task1/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/members/danileTX/task1/tsconfig.app.json b/members/danileTX/task1/tsconfig.app.json new file mode 100644 index 000000000..f0a235055 --- /dev/null +++ b/members/danileTX/task1/tsconfig.app.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/members/danileTX/task1/tsconfig.json b/members/danileTX/task1/tsconfig.json new file mode 100644 index 000000000..1ffef600d --- /dev/null +++ b/members/danileTX/task1/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/members/danileTX/task1/tsconfig.node.json b/members/danileTX/task1/tsconfig.node.json new file mode 100644 index 000000000..0d3d71446 --- /dev/null +++ b/members/danileTX/task1/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/members/danileTX/task1/vite.config.ts b/members/danileTX/task1/vite.config.ts new file mode 100644 index 000000000..5a33944a9 --- /dev/null +++ b/members/danileTX/task1/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +})