初学Vue
文章目录
toDoLlist案例入门版–props实现组件数据交互
前言
实现步骤中的代码会有重复的部分,代码是根据功能,按照思路一点一点添加上的,同时我也在代码之外添加了部分注释,帮助理解,完整代码在最后。
页面最终效果展示
实现步骤
1、脚手架搭建项目
vue create todolist
2、写出一个静态页面,包含html结构和css样式
- test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>做出基本模板</title>
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,回车键确认">
</div>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<ul ul="todo-main">
<li>
<label>
<input type="checkbox">
<span>xxxxx</span>
</label>
<button class="btn btn-danger">删除</button>
</li>
<li>
<label>
<input type="checkbox">
<span>xxxxx</span>
</label>
<button class="btn btn-danger">删除</button>
</li>
</ul>
<!-- /列表部分 -->
<!-- 底部部分 -->
<div class="todo-footer">
<label>
<input type="checkbox">
</label>
<span>
<span>已完成0</span>/ 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
<!-- /底部部分 -->
</div>
</div>
</div>
</body>
<style>
/* 整体样式 */
body {
background: rgba(230, 241, 245, 0.816)
}
.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;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.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);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li:hover{
background-color: grey;
cursur: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:hover button{
display:block
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.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;
}
</style>
</html>
3、将静态页面拆分,放入到各个组件中
- App.vue组件
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<MyHeader></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<MyList></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
// 引入各个子组件
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
// 注册组件
components: {
MyHeader,
MyFooter,
MyList,
},
};
</script>
<style>
/* 整体样式 */
body {
background: rgba(230, 241, 245, 0.816);
}
.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;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
- MyHeader.vue组件
<template>
<div>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,回车键确认" />
</div>
</div>
</template>
<script>
export default {
name: "MyHeader",
};
</script>
<style scoped>
/*header*/
.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);
}
</style>
- MyList.vue组件
<template>
<div>
<ul ul="todo-main">
<MyItem></MyItem>
<MyItem></MyItem>
<MyItem></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem },
};
</script>
<style scoped>
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
</style>
- MyItem.vue组件
<template>
<div>
<li>
<label>
<input type="checkbox" />
<span>xxxxx</span>
</label>
<button class="btn btn-danger">删除</button>
</li>
</div>
</template>
<script>
export default {
name: "MyItem",
};
</script>
<style scoped>
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li:hover {
background-color: rgb(226, 219, 219);
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:hover button{
display:block
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
</style>
- MyFooter.vue组件
<template>
<div>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span> <span>已完成0</span>/ 全部2 </span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</template>
<script>
export default {
name: "MyFooter",
};
</script>
<style scoped>
/*footer*/
.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;
}
</style>
(1)在子组件的样式中使用了scoped属性,用来避免子组件中样式因出现重名而出现问题。
(2)MyItem组件是MyList的子组件,所以MyItem是在MyList中导入并注册,而不是直接在App.vue中导入注册。
4、动态展示列表中的数据
需要注意数据的类型、名称,数据保存在哪个组件中
以下代码均省略样式部分
更改后的MyList.vue
<template>
<div>
<ul ul="todo-main">
<!-- 显示todos数组中所有的事项,用id作为key -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem },
data() {
return {
// 自己设置了三条数据
todos: [
{
id: "001", title: "唱歌", done: true },
{
id: "002", title: "跳舞", done: false },
{
id: "003", title: "打代码", done: true }
],
};
},
};
</script>
更改后的MyItem.vue
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done"/>
<span>{
{todo.title}}</span>
</label>
<button class="btn btn-danger">删除</button>
</li>
</div>
</template>
<script>
export default {
name: "MyItem",
props:['todo'] // 通过props配置项接收来自MyList.vue组件标签组件< MyItem>传过来的值(todo),todo中存储了id、title、done属性
};
</script>
1、使用一个数组来存储整个列表,因为列表有多个事项,而一个事项由多个属性组成,例如id(唯一标识),title(事项名称),done(是否完成),所以单个事项用一个对象表示。也就是说数组中存储的是对象。
2、有n个事项就显示n个事项,使用v-for属性借助id遍历出所有事项(此时只遍历了n个事项出来,并且不能显示事项中的具体内容)
3、在MyList.vue中的组件标签MyItem中的 :todo="todoObj"用于将从todos数组中获取到的单个对象传给MyItem.vue接收。在MyItem中通过配置项props接收todo。
4、MyItem.vue接收到todo后,通过插值语法{
{todo.title}}可以获取到事项内容,通过:checked="todo.done"来勾选是否已完成。
5、输入框输入数据添加事项
- 这里有一个坑需要注意下
在项目目录下,MyList.vue和Myheader.vue两个组件是同级的,
需要注意的是props配置项支持父组件中的数据传给子组件,但如果想要在兄弟级组件间传递数据则稍微麻烦点
添加事项是在MyHeader.vue中完成的,输入框输入值后将生成的对象传递出去,并且在另一个组件中通过props配置项接收数据,并使用todos数组存储传进来的对象,而todos数组此时是在MyList.vue组件中的,这两个组件是同级关系,因此不能通过props来传值。
也就是说上图中中间椭圆传值部分并不能在兄弟级的组件下使用props配置项实现。
- 解决措施
这里我们可以将data中的todos数组交给父组件App.vue管理,让它来起到桥梁的作用。如图:
todos数组指向MyList组件很好理解,就是在MyList.vue中将数据传递给MyList.vue组件,MyList用props接收数据,主要是todoObj指向todos数组这段该怎么实现,子组件怎么传数据给父组件?
可以这么实现,在父组件也就是App.vue中添加一个方法,这个方法的功能就是用来添加一个事项,并且通过组件标签将这个方法传到MyHeader.vue中去,MyHeader.vue用props接收这个方法,紧接着调用即可即可给父组件中添加数据。
简单来说就是在父组件中定义一个方法,将这个方法传给子组件,子组件用props接收后调用,更新父组件中的数据,实现兄弟级组件间的数据传递。
代码实现
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<!-- 将addTodo方法传到MyHeader组件去 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<!-- 将todos数组传递到MyList组件中 -->
<MyList :todos="todos"></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyFooter,
MyList,
},
data() {
return {
todos: [
{
id: "001", title: "唱歌", done: true },
{
id: "002", title: "跳舞", done: false },
{
id: "003", title: "打代码", done: true },
],
};
},
methods:{
addTodo(e){
// console.log("我是App组件,我收到了数据",x);
this.todos.unshift(e)
}
}
};
</script>
- MyList.vue
template>
<div>
<ul ul="todo-main">
<!-- 显示todos数组中所有的事项,用id作为key -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem },
props:['todos']
};
</script>
- MyHeader.vue
<template>
<div>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,回车键确认"
@keyup.enter="add"
/>
</div>
</div>
</template>
<script>
import {
nanoid} from "nanoid"
export default {
name: "MyHeader",
methods:{
add(event){
// 如果输入为空,则结束方法,不添加任何数据
if(!this.value) return alert("输入不能为空")
// 使用能生成唯一标识的库nanoid或者uuid生成id,这里用nanoid(轻量级的uuid)
// 将用户的输入包装成一个todo对象
const todoObj = {
id:nanoid(),title:event.target.value,done:false}
this.addTodo(todoObj) // 调用传递过来的方法,添加数据
event.target.value='' // 回车后清空输入框中的内容
}
},
props:["addTodo"], // 使用props接收到传递过来的方法
};
</script>
6、实现列表中勾选数据交互
主线是拿到单个事项的id值,然后根据id值修改done属性的值,支线一是如何拿到id值,支线二是如何根据id值修改done属性。
支线一:MyItem组件中,点击勾选与取消勾选时触发@change事件,调用对应的方法,该方法参数为id,并且该方法会调用从App传过来的方法。
支线二:在App组件中定义一个方法,该方法参数为id,作用时可以获取列表中每一项,然后比较id,修改对应id中done属性的值,然后将该方法传递给MyItem组件。
需要注意的是:(这里未使用组件通信的方法)
MyList是MyItem的父组件,而App是MyList的父组件,因此传递方法时要一层一层的传。
代码实现:
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<!-- 将addTodo方法传到MyHeader组件去 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<MyList :todos="todos" :checkTodo="checkTodo"></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyFooter,
MyList,
},
data() {
return {
todos: [
{
id: "001", title: "唱歌", done: true },
{
id: "002", title: "跳舞", done: false },
{
id: "003", title: "打代码", done: true },
],
};
},
methods:{
// 添加一个列表事项
addTodo(e){
// console.log("我是App组件,我收到了数据",x);
this.todos.unshift(e)
},
// 勾选or取消勾选todo事项
checkTodo(id){
// 拿到todos中的每一项,找到对应id的那一项
this.todos.forEach((todo)=>{
if(todo.id === id)
todo.done = !todo.done
})
}
}
}
</script>
- MyList.vue
<template>
<div>
<ul ul="todo-main">
<!-- 显示todos数组中所有的事项,用id作为key -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem },
props:['todos','checkTodo']
};
</script>
- MyItem.vue
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{
{todo.title}}</span>
</label>
<button class="btn btn-danger">删除</button>
</li>
</div>
</template>
<script>
export default {
name: "MyItem",
props:['todo','checkTodo'], // 这里todo是个对象,由MyList传过来的,存储了id、title、done属性
methods:{
handleCheck(id){
// 通知App组件将对应todo对象的done值取反
this.checkTodo(id)
}
}
};
</script>
7、实现列表删除按钮的功能
思路:拿id,删除对应事项
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<!-- 将addTodo方法传到MyHeader组件去 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<MyList
:todos="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyFooter,
MyList,
},
data() {
return {
todos: [
{
id: "001", title: "唱歌", done: true },
{
id: "002", title: "跳舞", done: false },
{
id: "003", title: "打代码", done: true },
],
};
},
methods:{
// 添加一个列表事项
addTodo(e){
// console.log("我是App组件,我收到了数据",x);
this.todos.unshift(e)
},
// 勾选or取消勾选todo事项
checkTodo(id){
// 拿到todos中的每一项,找到指定id的那一项
this.todos.forEach((todo)=>{
if(todo.id === id)
todo.done = !todo.done
})
},
// 删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
}
}
}
</script>
- MyList.vue
<template>
<div>
<ul ul="todo-main">
<!-- 显示todos数组中所有的事项,用id作为key -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem },
props:['todos','checkTodo','deleteTodo']
};
</script>
- MyItem.vue
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{
{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</div>
</template>
<script>
export default {
name: "MyItem",
props:['todo','checkTodo','deleteTodo'], // 这里todo是个对象,由MyList传过来的,存储了id、title、done属性
methods:{
handleCheck(id){
// 通知App组件将对应todo对象的done值取反
this.checkTodo(id)
},
// 删除事项
handleDelete(id){
if(confirm("确定删除吗?")){
// 通知App删除数据
this.deleteTodo(id)
}
}
}
};
</script>
8、底部统计功能实现
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<!-- 将addTodo方法传到MyHeader组件去 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<MyList
:todos="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter
:todos="todos"
></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyFooter,
MyList,
},
data() {
return {
todos: [
{
id: "001", title: "唱歌", done: true },
{
id: "002", title: "跳舞", done: false },
{
id: "003", title: "打代码", done: true },
],
};
},
methods:{
// 添加一个列表事项
addTodo(e){
// console.log("我是App组件,我收到了数据",x);
this.todos.unshift(e)
},
// 勾选or取消勾选todo事项
checkTodo(id){
// 拿到todos中的每一项,找到对应id的那一项
this.todos.forEach((todo)=>{
if(todo.id === id)
todo.done = !todo.done
})
},
// 点击删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
}
}
}
</script>
- MyFooter.vue
<template>
<div>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span> <span>已完成{
{doneTotal}}</span>/ 全部{
{todos.length}}</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</template>
<script>
export default {
name: "MyFooter",
props:['todos'],
computed:{
doneTotal(){
// 高端一点的方法
// return this.todos.reduce((pre,current)=>{
// return pre + (current.done ? 1 : 0)
// },0)
// 普通的方法
let i=0
this.todos.forEach((todo)=>{
if(todo.done) i++
})
return i
}
},
}
</script>
9、底部删除\勾选功能实现
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<!-- 将addTodo方法传到MyHeader组件去 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<MyList
:todos="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter
:todos="todos"
:checkAllTodo="checkAllTodo"
:clearAllTodo="clearAllTodo"
></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyFooter,
MyList,
},
data() {
return {
todos: [
{
id: "001", title: "唱歌", done: true },
{
id: "002", title: "跳舞", done: false },
{
id: "003", title: "打代码", done: true },
],
};
},
methods:{
// 添加一个列表事项
addTodo(e){
// console.log("我是App组件,我收到了数据",x)
this.todos.unshift(e)
},
// 勾选or取消勾选todo事项
checkTodo(id){
// 拿到todos中的每一项,找到对应id的那一项
this.todos.forEach((todo)=>{
if(todo.id === id)
todo.done = !todo.done
})
},
// 点击删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
},
// 控制全选或全不选
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done;
})
},
// 清除已经完成的事项
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
}
}
</script>
- MyFooter.vue
<template>
<div>
<!--当列表中没有事项时,v-show用于隐藏Footer部分-->
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
</label>
<span> <span>已完成{
{doneTotal}}</span>/ 全部{
{total}}</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</div>
</template>
<script>
export default {
name: "MyFooter",
props:['todos','checkAllTodo','clearAllTodo'],
computed:{
total(){
return this.todos.length
},
doneTotal(){
// 高端一点的方法
// return this.todos.reduce((pre,current)=>{
// return pre + (current.done ? 1 : 0)
// },0)
// 普通的方法
let i=0
this.todos.forEach((todo)=>{
if(todo.done) i++
})
return i
},
isAll:{
// 看全选框是否勾选
get(){
return this.doneTotal === this.total && this.total>0
},
// isAll被修改时set被调用
set(value){
this.checkAllTodo(value)
}
}
},
methods:{
// 全选或取消全选
checkAll(event){
this.checkAllTodo(event.target.checked)
},
// 清除已完成事项
clearAll(){
this.clearAllTodo()
}
}
}
</script>
完整代码
- index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<!-- 将addTodo方法传到MyHeader组件去 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<MyList
:todos="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter
:todos="todos"
:checkAllTodo="checkAllTodo"
></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyFooter,
MyList,
},
data() {
return {
todos: [
{
id: "001", title: "唱歌", done: true },
{
id: "002", title: "跳舞", done: false },
{
id: "003", title: "打代码", done: true },
],
};
},
methods:{
// 添加一个列表事项
addTodo(e){
// console.log("我是App组件,我收到了数据",x)
this.todos.unshift(e)
},
// 勾选or取消勾选todo事项
checkTodo(id){
// 拿到todos中的每一项,找到对应id的那一项
this.todos.forEach((todo)=>{
if(todo.id === id)
todo.done = !todo.done
})
},
// 点击删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
},
// 控制全选或全不选
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done;
})
}
}
}
</script>
<style>
/* 整体样式 */
body {
background: rgba(230, 241, 245, 0.816);
}
.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;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
- MyHeader.vue
<template>
<div>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,回车键确认"
@keyup.enter="add"
/>
</div>
</div>
</template>
<script>
import {
nanoid} from "nanoid"
export default {
name: "MyHeader",
methods:{
add(event){
// 如果输入为空,则结束方法,不添加任何数据
if(!event.target.value){
return alert("输入不能为空")
}
// 使用能生成唯一标识的库nanoid或者uuid生成id,这里用nanoid(轻量级的uuid)
// 将用户的输入包装成一个todo对象
const todoObj = {
id:nanoid(),title:event.target.value,done:false}
this.addTodo(todoObj) // 调用传递过来的方法,添加数据
event.target.value='' // 回车后清空输入框中的内容
}
},
props:["addTodo"], // 使用props接收到传递过来的方法
};
</script>
<style scoped>
/*header*/
.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);
}
</style>
- MyList.vue
<template>
<div>
<ul ul="todo-main">
<!-- 显示todos数组中所有的事项,用id作为key -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem },
props:['todos','checkTodo','deleteTodo']
};
</script>
<style scoped>
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
</style>
- MyItem.vue
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{
{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</div>
</template>
<script>
export default {
name: "MyItem",
props:['todo','checkTodo','deleteTodo'], // 这里todo是个对象,由MyList传过来的,存储了id、title、done属性
methods:{
handleCheck(id){
// 通知App组件将对应todo对象的done值取反
this.checkTodo(id)
},
// 删除事项
handleDelete(id){
if(confirm("确定删除吗?")){
// 通知App删除数据
this.deleteTodo(id)
}
}
}
};
</script>
<style scoped>
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li:hover {
background-color: rgb(226, 219, 219);
cursor: pointer;
}
li:hover button{
display:block
}
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;
}
</style>
- MyFooter.vue
<template>
<div>
<!--当列表中没有事项时,v-show用于隐藏Footer部分-->
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
</label>
<span> <span>已完成{
{doneTotal}}</span>/ 全部{
{total}}</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</div>
</template>
<script>
export default {
name: "MyFooter",
props:['todos','checkAllTodo','clearAllTodo'],
computed:{
total(){
return this.todos.length
},
doneTotal(){
// 高端一点的方法
// return this.todos.reduce((pre,current)=>{
// return pre + (current.done ? 1 : 0)
// },0)
// 普通的方法
let i=0
this.todos.forEach((todo)=>{
if(todo.done) i++
})
return i
},
isAll:{
// 看全选框是否勾选
get(){
return this.doneTotal === this.total && this.total>0
},
// isAll被修改时set被调用
set(value){
this.checkAllTodo(value)
}
}
},
methods:{
// 全选或取消全选
checkAll(event){
this.checkAllTodo(event.target.checked)
},
// 清除已完成事项
clearAll(){
this.clearAllTodo()
}
}
}
</script>
<style scoped>
/*footer*/
.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;
}
</style>