实现效果:
todoList.gif搭建项目
使用 create-react-app 快速创建一个项目,删除不必要的文件,保留文件如下:
文件目录
目录说明
- index.js - 项目入口文件
- index.less - 样式
- mock.js - 模拟 todo 数据
- TodoList.js - 实现的逻辑文件
在项目中引入 antd,具体方法见 在 create-react-app 中使用 antd
各部分代码展示
mock.js
// 引入 mock 模拟数据
import Mock from 'mockjs'
const Random = Mock.Random
Random.extend({
planInfo: function(date) {
const Info = ["吃饭", "睡觉", "打豆豆"]
return this.pick(Info)
}
})
const data = Mock.mock('/fakeData', 'get', {
success: true,
message: 'success',
todoList: {
"filter": "SHOW_ALL",
"list|1-10": [
{
"id|+1": 1,
"content": '@planInfo',
"isDone": Random.boolean(),
"dataTime": Random.date('yyyy-MM-dd')
},
]
}
})
export default data
TodoList.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./mock";
import { Input, List, Button, Icon } from "antd";
const { Search } = Input;
export default function TodoList() {
// data - 完整的 todo 数据
const [data, setData] = useState([]);
// 在此处请求接口数据并初始化 todoList,
// useEffect 传入第二个参数为空数组,使得该 effect 仅执行一次
useEffect(() => {
(async () => {
const res = await axios.get("/fakeData");
const { list } = res.data.todoList;
setData(list);
})();
}, []);
// 展示的数据,之所以出现需要出现这个数据,
// 是因为我们的所有操作都是在前端完成的,不存在请求接口调用数据。
// 那么我们在进行展示 "已完成" "未完成" 的不同任务时,就需要全部的数据不会被覆盖,showArr 只负责数据
const [showArr, setShowArr] = useState([].concat(data));
// 当 data 发生改变时,执行该 effect,替换 showArr
useEffect(() => {
setShowArr([].concat(data));
}, [data]);
// 实现任务输入框的双向绑定
const [inputValue, setInputValue] = useState("");
// 控制当前展示任务按钮样式 active
const [active, setActive] = useState("SHOW_ALL");
function addTodo(content) {
const newData = [].concat(data);
newData.push({
id: Date.now(),
content: content,
isDone: false,
dataTime: "2019-10-28"
});
setData(newData);
setInputValue("");
}
function showStatusList(showStatus) {
if (showStatus === "SHOW_COMPLETED") {
const newData = data.filter(item => item.isDone);
setShowArr([].concat(newData));
setActive("SHOW_COMPLETED");
} else if (showStatus === "SHOW_ACTIVE") {
const newData = data.filter(item => !item.isDone);
setShowArr([].concat(newData));
setActive("SHOW_ACTIVE");
} else {
setShowArr([].concat(data));
setActive("SHOW_ALL");
}
}
function finishTodo(id) {
const newData = [].concat(data);
const index = newData.findIndex(item => item.id === id);
newData[index].isDone = true;
setData(newData);
}
function toggleTodo(id) {
const newData = [].concat(data);
const index = newData.findIndex(item => item.id === id);
newData[index].isDone = !newData[index].isDone;
setData(newData);
}
function deleteTodo(id) {
const newData = [].concat(data);
const index = newData.findIndex(item => item.id === id);
newData.splice(index, 1);
setData(newData);
}
function todoHeader() {
return (
<div className={"mainHeader"}>
任务列表
<div className="mainHandle">
<Button
size="small"
type="default"
className={[
"classifyBtn",
active === "SHOW_ALL" ? "active" : null
].join(" ")}
onClick={() => showStatusList("SHOW_ALL")}
>
全部
</Button>
<Button
size="small"
type="default"
className={[
"classifyBtn",
active === "SHOW_COMPLETED" ? "active" : null
].join(" ")}
onClick={() => showStatusList("SHOW_COMPLETED")}
>
已完成
</Button>
<Button
size="small"
type="default"
className={[
"classifyBtn",
active === "SHOW_ACTIVE" ? "active" : null
].join(" ")}
onClick={() => showStatusList("SHOW_ACTIVE")}
>
未完成
</Button>
</div>
</div>
);
}
return (
<div className="todoList">
<div className="todoHeader">
<h1 className="card-title">React Hooks Todo</h1>
<span className="card-subtitle">添加任务,管理每日计划</span>
</div>
<div className="todoSearch">
<Search
placeholder="今天完成什么计划?"
enterButton="添加任务"
size="default"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
onSearch={(content, event) => addTodo(content, event)}
/>
</div>
<div className="todoMain">
<List
header={todoHeader()}
footer={
<div className={"mainFooter"}>共 {showArr.length} 项任务</div>
}
bordered
dataSource={showArr}
renderItem={item => (
<List.Item>
<Button
size="small"
type={item.isDone ? "primary" : "danger"}
className="status"
>
{item.isDone ? "完成" : "未完成"}
</Button>
<p className="content">{item.content}</p>
<div className="operate">
{/* <span>{item.dataTime}</span> */}
{item.isDone ? (
""
) : (
<Icon
type="check-square"
style={{ color: "green" }}
onClick={() => finishTodo(item.id)}
/>
)}
<Icon
type="retweet"
style={{ color: "#E7AF62" }}
onClick={() => toggleTodo(item.id)}
/>
<Icon
type="close"
style={{ color: "red" }}
onClick={() => deleteTodo(item.id)}
/>
</div>
</List.Item>
)}
/>
</div>
</div>
);
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.less';
import TodoList from './TodoList';
ReactDOM.render(<TodoList />, document.getElementById('root'));
index.less
.todoHeader {
width: 100%;
height: 200px;
background: #eee;
position: relative;
text-align: center;
.card-title {
padding: 50px 0 30px 0;
margin: 0;
font-size: 44px;
}
.card-subtitle {
color: #666;
font-size: 14px;
}
}
.todoSearch {
width: 100%;
padding: 20px 25%;
}
.todoMain {
width: 100%;
padding: 0 15%;
border-color: #1890ff;
.mainHeader {
.mainHandle {
float: right;
button {
margin: 0 5px;
}
.active {
background: #96D196;
border-color: #96D196;
color: #fff;
}
}
}
.ant-list {
border-color: #1890ff;
.ant-list-header {
color: #fff;
background-color: #1890ff;
font-size: 16px;
}
.ant-list-item {
display: flex;
.status {
flex: 1;
}
.content {
flex: 9;
padding-left: 5px;
margin: 0;
}
.operate {
flex: 4;
text-align: right;
i {
padding-left: 5px;
cursor: pointer;
}
}
}
.ant-list-footer {
background: #eee;
.mainFooter {
text-align: right;
}
}
}
}
网友评论