效果图
入口
js
import React, { useState } from 'react';
import styles from './style.less'
import Header from './component/Header'
import List from './component/List'
import Footer from './component/Footer'
const index = () =>
{
const [toDos, setToDos] = useState([
{
id: '01', name: '吃饭', done: true
},
{
id: '02', name: '睡觉', done: true
},
{
id: '03', name: '打豆豆', done: false,
},
{
id: '04', name: '逛街', done: false
},
])
const addTodo = (todoObj) =>
{
const newToDos = [...toDos, todoObj]
setToDos(newToDos)
}
const updateTodo = (id, done) =>
{
const newToDos = toDos.map(todoObj =>
{
if (todoObj.id === id)
{
return { ...todoObj, done }
} else
{
return todoObj
}
})
setToDos(newToDos)
}
const deleteTodo = (id) =>
{
const newToDos = toDos.filter(todoObj =>
{
return todoObj.id !== id
})
setToDos(newToDos)
}
const checkAllTodo = (done) =>
{
const newToDos = toDos.map(todoObj =>
{
return { ...todoObj, done }
})
setToDos(newToDos)
}
const clearAllTodoDone = () =>
{
const newToDos = toDos.filter(todoObj =>
{
return !todoObj.done
})
setToDos(newToDos)
}
return (
<div className={styles.normal}>
<div className={styles.todo_container}>
<div className={styles.todo_wrap}>
<Header addTodo={addTodo} />
<List toDos={toDos} updateTodo={updateTodo} deleteTodo={deleteTodo} />
<Footer toDos={toDos} checkAllTodo={checkAllTodo} clearAllTodoDone={clearAllTodoDone} />
</div>
</div>
</div>
);
};
export default index
入口CSS
.normal {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.todo_container {
width: 600px;
margin: 0 auto;
}
.todo_container .todo_wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
Header
js
import React from 'react';
import styles from './style.less'
import PropTypes from 'prop-types'
import { nanoid } from 'nanoid'
const index = ({ addTodo }) =>
{
const handleKeyUp = (event) =>
{
console.log(addTodo);
const { keyCode, target } = event
if (keyCode === 13)
{
if (target.value === '')
{
alert('您输入的内容不能为空')
return
}
const toDoObj = { id: nanoid(), name: target.value, done: false }
addTodo(toDoObj)
target.value = ''
}
}
return (
<div className={styles.todo_header}>
<input
onKeyUp={handleKeyUp}
type="text"
placeholder="请输入你的任务名称,按回车键确认"
/>
</div>
);
};
index.propTypes = { addTodo: PropTypes.func.isRequired }
export default index
Header
CSS
.todo_header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo_header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 8px rgba(82, 168, 236, 0.6);
}
List
js
import React from 'react';
import styles from './style.less'
import Item from '../Item'
const index = (props) =>
{
const { toDos, updateTodo, deleteTodo } = props
return (
<ul className={styles.todo_main}>
{toDos.map((todo) =>
{
return (
<Item
key={todo.id}
{...todo}
updateTodo={updateTodo}
deleteTodo={deleteTodo}
/>
)
})}
</ul>
);
};
export default index
List
CSS
.todo_main {
margin: 2px 0 0 1px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
Item
js
import React, { useState } from 'react';
import styles from './style.less'
const index = (props) =>
{
const { id, name, done } = props
const [mouse, setMouse] = useState(false)
const handleMouse = (flag) =>
{
return () =>
{
setMouse(flag)
}
}
const handleCheck = (id) =>
{
const { updateTodo } = props
return (e) =>
{
console.log(e, 'e');
updateTodo(id, e.target.checked)
}
}
const btnClick = (id) =>
{
const { deleteTodo } = props
if (window.confirm('确定删除吗?'))
{
console.log('通知app删除', id);
deleteTodo(id)
}
}
return (
<li className={styles.todo_footer}
style={{ backgroundColor: mouse ? '#ddd' : 'white' }}
onMouseEnter={handleMouse(true)}
onMouseLeave={handleMouse(false)}>
<label>
{}
<input type="checkbox" checked={done}
onChange={handleCheck(id)}
/>
<span>{name}</span>
</label>
{}
<button onClick={() => btnClick(id)} className={`${styles.btn} ${styles.btn_danger}`} style={{ display: mouse ? 'block' : 'none' }}>删除</button>
</li>
);
};
export default index
Item
CSS
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn_danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn_danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn_focus {
outline: none;
}
Footer
js
import React from 'react';
import styles from './style.less'
const index = (props) =>
{
const { toDos } = props
const doneCount = toDos.reduce((pre, todo) =>
{
return pre + (todo.done ? 1 : 0)
}, 0)
const total = toDos.length
const handleCheckAll = (event) =>
{
props.checkAllTodo(event.target.checked)
}
const clearAllTodoDone = () =>
{
props.clearAllTodoDone()
}
return (
<div className={styles.todo_footer}>
<label >
<input
type="checkbox"
onChange={handleCheckAll}
checked={doneCount === total && doneCount !== 0 ? true : false}
/>
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button className={`${styles.btn} ${styles.btn_danger}`} onClick={clearAllTodoDone}>
清除已完成任务
</button>
</div>
);
};
export default index
Footer
CSS
.todo_footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo_footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo_footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo_footer button {
float: right;
margin-top: 5px;
}
.btn {
float: right;
}