• Vue:实现TodoList案例(尚硅谷)


    Vue核心:Vue核心:组件化编程(脚手架)

    一、静态页面

    app.vue
    注: MyItem.vue不直接在app.vue中引入,而在MyList.vue中引入

    <template>
       <div id="root">
         <div class="todo-container">
       	<div class="todo-wrap">
       	  <MyHeader/>
            <MyList/>
            <MyFooter/>
       	</div>
         </div>
       </div>
    
    </template>
    
    
       import MyHeader from './components/MyHeader'
       import MyList from './components/MyList'
       import MyFooter from './components/MyFooter.vue'
    
       export default {
       	name:'App',
       	components:{MyHeader,MyList,MyFooter},
       }
    </script>
    
    <style>
       /*base*/
       body {
         background: #fff;
       }
    
       .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> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    MyHeader.vue

    <template>
    	<div class="todo-header">
    		<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
    	 </div>
    </template>
    
    <style>
       /*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>    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    MyList.vue

    <template>
    	<ul class="todo-main">
    		<li>
    			<label>
    				<input type="checkbox"/>
    				<span>xxxxx</span>
    			</label>
    			<button class="btn btn-danger" style="display:none">删除</button>
    		</li>
    	</ul>
    </template>
    
    // 在拆 到 MyItem中
    <template>
    	<ul class="todo-main">
    		<MyItem/>
           // 想要数据多 就继续引入  组件
    	</ul>
    </template>
    
    <style>
      	/*list*/
    	.todo-main {
    	  margin-left: 0px;
    	  border: 1px solid #ddd;
    	  border-radius: 2px;
    	  padding: 0px;
    	}
    
    	.todo-empty {
    	  height: 40px;
    	  line-height: 40px;
    	  border: 1px solid #ddd;
    	  border-radius: 2px;
    	  padding-left: 5px;
    	  margin-top: 10px;
    	} 
    </style>    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    MyItem.vue

    <template>
    	<li>
    		 <label>
    				<input type="checkbox"/>
    				<span>xxxxx</span>
    		 </label>
    			  <button class="btn btn-danger" style="display:none">删除</button>
    	</li>
    </template>
    
    <style>
    	/*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 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>  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在这里插入图片描述

    二、展示动态的数据在这里插入图片描述

    数据的类型、名称是什么

    • 一堆要做的事情是一个数组,一个个要做的事情是对象,对象里面的内容=={id,name,done(标识,完成)}==

    数据保存在哪个组件

    • List组件展示就将数据保存在List中

    MyList.vue

    • 根据数据决定使用多少次 MyItem
    • 把每一条的具体信息对象传递给 MyItem
    <template>
    	<ul class="todo-main">
    		<MyItem v-for:"todoObj in todos" :key="todoObj.key" :todo="todoObj"/>
         
    	</ul>
    </template>
    
    <script>
    	import MyItem from './MyItem'
    
    	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>   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    MyItem.vue

    • 接收
    • 动态决定是否勾选
    <template>
    	<li>
    		 <label>
                	<!--动态决定是否勾选-->
    				<input type="checkbox" :checked="todo.done"/>
    				<span>{{todo.title}}</span>
    		 </label>
    			  <button class="btn btn-danger" style="display:none">删除</button>
    	</li>
    </template>
    
    <script>
    	export default {
    		name:'MyItem',
    		//声明接收todo
    		props:['todo'],
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    三、交互

    组件之间的通信(兄弟、子传父、爷传孙),后面有更好的方式实现

    3.1 添加

    MyHeader.vue

    • 绑定个键盘事件

    • 把用户的输入打印

    • 获取用户的输入

      • 方式一:event 事件对象
      add(event){
         consloe.log(event.target.value) // 获得发生事件对象的元素
      }
      
      • 1
      • 2
      • 3
      • 方式二:v-model
      <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model='title' @keyup.enter="add"/>
      
      	data() {
              return {
                  title:''
              }
          }
      	menthod: {
              add(event){
          		consloe.log(this.target) // 获得发生事件对象的元素
              }
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 把获取到的数据包装成一个todo对象 id使用uuid 的压缩版本 nanoid (单机版本) npm i nanoid

    • 把对象放到数组的前民(unshift),在List组件中保存数据的todos ,在Header组件输出

    • 两个兄弟组件之间直接进行数据传递——暂时实现不了

    • 原始间接传递

      • 把List中的todos[] 给 App,让App通过 props 方式传递给list
      • 让Header 把todoObj 给App
        在这里插入图片描述

    具体案例实现:

    • 在App里定义一个addTodo方法,通过父传子的形式传给MyHeader
    • MyHeader调用了addTodo方法,并对App.vue在data.todos中添加一个todo
    • App.vue向MyList中传todos,即可达到插入新的事件的效果

    App.vue

    <template>
    	<div id="root">
    		<div class="todo-container">
    			<div class="todo-wrap">
    				<MyHeader :addTodo="addTodo"/>
    				<MyList :todos="todos"/>
    				<MyFoote/>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script>
    	import MyHeader from './components/MyHeader'
    	import MyList from './components/MyList'
    	import MyFooter from './components/MyFooter.vue'
    
    	export default {
    		name:'App',
    		components:{MyHeader,MyList,MyFooter},
    		data() {
    			return {
    				//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
    				todos:[
    					{id:'001',title:'抽烟',done:true},
    					{id:'002',title:'喝酒',done:false},
    					{id:'003',title:'开车',done:true}
    				]
    			}
    		},
            methods: {
    			//在data.todos中添加一个todo
    			addTodo(todoObj){
    				this.todos.unshift(todoObj)
    			}
    		},
    
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    MyHeader.vue

    	<template>
    	<div class="todo-header">
    		<input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/>
    	 </div>
    </template>
    
    <script>
       // 引入 nanoid 
       import {nanoid} from 'nanoid'
    	export default {
    		name:'MyHeader',
    		props:['addTodo'],
    		menthod: {
               add(event){
                   	consloe.log(event.target.value) // 获得发生事件对象的元素
    				//将用户的输入包装成一个todo对象
    				const todoObj = {id:nanoid(),title:event.target.value,done:false}
                   	consloe.log(todoObj)
                   	// 方式一:实现 清空数据时操作了dom
                   	this.addTodo(todoObj)
                   	//清空输入
    				event.target.value = ''
               }
           },
           // 方式二:v-model
           data() {
    			return {
    				//收集用户输入的title
    				title:''
    			}
    		},
    		methods: {
    			add(){
    				//校验数据
    				if(!this.title.trim()) return alert('输入不能为空')
    				//将用户的输入包装成一个todo对象
    				const todoObj = {id:nanoid(),title:this.title,done:false}
    				//通知App组件去添加一个todo对象
    				this.addTodo(todoObj)
    				//清空输入
    				this.title = ''
    			}
    		},
           
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    MyList.vue

    <template>
    	<ul class="todo-main">
    		<MyItem v-for:"todoObj in todos" :key="todoObj.key" :todo="todoObj"/>
    	</ul>
    </template>
    
    <script>
    	import MyItem from './MyItem'
    
    	export default {
    		name:'MyList',
           	components:{MyItem},
    		props:['todos'],
    	}
    </script>   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.2 勾选

    MyItem.vue

    • 拿到勾选的id,去todos中找到具体的某个人的 done 属性取反
    • todos数据在App (数据在哪里操作数据的方法就在哪里)
    <template>
    	<li>
    		 <label>
               	<!--动态决定是否勾选-->
               	<!--change 改变就会触发-->
    				<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 
    			<!-- 	
    				如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props 
    				v-model 绑定的是传递过来的数据 props 不建议
    			-->
    			<!-- <input type="checkbox" v-model="todo.done"/> -->
    				<span>{{todo.title}}</span>
    		 </label>
    		 <button class="btn btn-danger" style="display:none">删除</button>
    	</li>
    </template>
    
    <script>
    	export default {
    		name:'MyItem',
    		//声明接收todo
    		props:['todo'],
          	methods: {
    			//勾选or取消勾选
    			handleCheck(id){
    				//通知App组件将对应的todo对象的done值取反
    				//checkTodo为App.vue定义的方法
    				this.checkTodo(id)
    			}
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    App.vue

    <template>
    	<div id="root">
    		<div class="todo-container">
    			<div class="todo-wrap">
    				<MyHeader :addTodo="addTodo" :checkTodo="checkTodo"/>
    				<MyList :todos="todos"/>
    				<MyFoote/>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script>
       
    	export default {
    		name:'App',
    		components:{MyHeader,MyList,MyFooter},
    		data() {
    			return {
    				//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
    				todos:[
    					{id:'001',title:'抽烟',done:true},
    					{id:'002',title:'喝酒',done:false},
    					{id:'003',title:'开车',done:true}
    				]
    			}
    		},
           methods: {
    			//添加一个todo
    			addTodo(todoObj){
    				this.todos.unshift(todoObj)
    			},
    			//勾选or取消勾选一个todo
    			checkTodo(id){
    				//通过Item传回的id参数,对todos做遍历,找到对应id的对象,将其done取反
    				this.todos.forEach((todo)=>{
    					if(todo.id === id) todo.done = !todo.done
    				})
    			}
    		}
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述
    MyList.vue
    补充下列代码

    <MyItem v-for:"todoObj in todos" :key="todoObj.key" :todo="todoObj" :checkTodo="checkTodo"/>	
    
    props:['todos','checkTodo']
    
    • 1
    • 2
    • 3

    3.3 删除

    • 鼠标悬浮有高亮效果,并出现删除按钮
    • 获取id,根据id删除

    MyItem.vue 通知app删除对应项 同样是 爷 传 孙

    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    	...	
    	//声明接收todo、checkTodo、deleteTodo
    	props:['todo','checkTodo','deleteTodo'],
    	methods: {
    			//删除
    			handleDelete(id){
    				//confirm会跳出个弹框让用户选择 确定 或 取消,并返回bool值
    				if(confirm('确定删除吗?')){
    					//通知App组件将对应的todo对象删除
    					this.deleteTodo(id)
    				}
    			}
    		},
    	...		
    <style scoped>
    	li:hover{
    		background-color: #ddd;
    	} 
    	li:hover button{
    		display: block; // 鼠标滑过显示 删除按钮
    	}
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    App.vue 传 list

    <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
    ...
    methods: {
    	//删除一个todo
    	deleteTodo(id){
    		// filter 不改变原数组 
    		this.todos = this.todos.filter( todo => todo.id !== id )
    	}
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    list 接收

    <MyItem 
    	v-for="todoObj in todos"
    	:key="todoObj.id" 
    	:todo="todoObj" 
    	:checkTodo="checkTodo"
    	:deleteTodo="deleteTodo"
    />
    ...
    props:['todos','checkTodo','deleteTodo']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    3.4 底部统计

    • 统计全部和已完成 MyFooter –> todos 数组的长度 done 为真的数量

    App.vue 给 footer 传递todos数组

    <MyFooter :todos="todos" />
    
    • 1

    MyFooter.vue 声明接收

    // 1
    <span>已完成{{todos.???}}</span> / 全部{{todos.length}}
    
    props:['todos'],
    
    //2
    // 等于0 时不展示
    <div class="todo-footer" v-show="total">
       
    <span>已完成{{doneTotal}}</span> / 全部{{total}}
    
    		computed: {
    			//总数
    			total(){
    				return this.todos.length
    			},
    			//已完成数
    			// 方式一: 数组中的方法 reduce 推荐
    			doneTotal(){
    				//此处使用reduce方法做条件统计
    				//reduce以todos中的个数作为循环次数,第一次循环以程序员写的0作为pre,current是现在的todos[i]对象
    				//第二次循环以第一次循环的返回值为pre,以此类推
    				//最后一次循环的返回值作为整个函数的返回值,即返回给x
    				
    				/* const x = this.todos.reduce((pre,current)=>{
    					console.log('@',pre,current)
    					return pre + (current.done ? 1 : 0)
    				},0) */
    				
    				//简写
    				return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
    			}
    			// 方式二:常规遍历
    			doneTotal(){
    				let i = 0
    				this.todos.forEach((todo)=>){
    					if(todo.done) i++
    				}
    				return i
    			}
    		},
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在这里插入图片描述

    3.5 底部交互

    • 全选 / 全不选,取决于 已完成 和 全部 是否相等
    • 如果没有数据时,不应该勾选,且不应该展示下面整个框

    3.5.1 MyFooter.vue 已完成 / 完成数量的动态变化

    MyFooter.vue

    //1.复杂写法
    //
    
    //2.vue简便写法
    //total = 0即没有添加事件时,该模块不显示
    <div v-show="total">
    	<input type="checkbox" :checked="isAll" @change="checkAll"/>
    </div>
    </script>
    export default {
    		name:'MyFooter',
    		props:['todos','checkAllTodo','clearAllTodo'],
    		computed: {
    			//总数
    			total(){
    				return this.todos.length
    			},
    			//已完成数
    			doneTotal(){
    				return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
    			},
    			
    			// 简写方式,没有setter 方法  只能被读取不能被修改才可以  后面需要修改
    			//控制全选框
    			// 一个计算属性可以通过其他的计算属性 在进行计算 
    			isAll(){ 
    				//已完成事件等于全部事件 且 全部事件大于0  才返回真
    				return this.doneTotal === this.total && this.total > 0
    			}
    			
    			
    			
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述

    3.5.2 MyFooter.vue 全选 和 局部选 的动态绑定

    • this.checkAllTodo(e.target.checked) // true false 全选 或者 全不选
    • 告诉存储 todos 的人全选全不选

    MyFooter.vue

    // 全选按钮
    
    //方法一:普通方法
    <input type="checkbox" :checked="isAll" @change="checkAll"/>
    ...
    
    
    methods: {
    	checkAll(e){
    		// true,false表示全选 或 全不选,传给app.vue中checkAllTodo方法
    		this.checkAllTodo(e.target.checked) 
    	} 		
    },
    
    
    // 方法二: v-model(推荐)
    //注意这里修改的不是props,而是直接修改的todos,所以可以用v-model
    <input type="checkbox" v-model="isAll"/>
    ...
    	//非简写方式 可读可写
    	computed: {
    		//控制全选框
    		isAll:{
    			//全选框是否勾选
    			get(){
    				return this.doneTotal === this.total && this.total > 0
    			},
    			//isAll被修改时set被调用
    			set(value){
    				this.checkAllTodo(value)
    			}
    		}
    	},
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    App.vue

    <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" />
    methods: {
    	//全选or取消全选
    	//这个done就是全选框的true或false
    	checkAllTodo(done){
    		//遍历每一个小框,将小框的true或false和全选框的选择状态同步
    		this.todos.forEach((todo)=>{
    			todo.done = done
    		})
    	},
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.5.3 批量删除已完成事件

    <div class="todo-footer" v-show="total">
    	<label>
    		<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
    		<input type="checkbox" v-model="isAll"/>
    	</label>
    	<span>
    		<span>已完成{{doneTotal}}</span> / 全部{{total}}
    	</span>
    	<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
    ...
    	props:['todos','checkAllTodo',,'clearAllTodo'],
    	methods: {
    		//批量删除已完成事件  
    		clearAll(){
    			this.clearAllTodo()
    		}
    	},
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    App.vue

    //清除所有已经完成的todo
    	clearAllTodo(){
    		this.todos = this.todos.filter((todo)=>{
    			return !todo.done
    		})
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    四、todoList案例总结

    在这里插入图片描述

    五、完整代码

    App.vue

    <template>
       <div id="root">
       	<div class="todo-container">
       		<div class="todo-wrap">
       			<MyHeader :addTodo="addTodo"/>
       			<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
       			<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
       		</div>
       	</div>
       </div>
    </template>
    
    <script>
       import MyHeader from './components/MyHeader'
       import MyList from './components/MyList'
       import MyFooter from './components/MyFooter.vue'
    
       export default {
       	name:'App',
       	components:{MyHeader,MyList,MyFooter},
       	data() {
       		return {
       			//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
       			todos:[
       				{id:'001',title:'抽烟',done:true},
       				{id:'002',title:'喝酒',done:false},
       				{id:'003',title:'开车',done:true}
       			]
       		}
       	},
       	methods: {
       		//添加一个todo
       		addTodo(todoObj){
       			this.todos.unshift(todoObj)
       		},
       		//勾选or取消勾选一个todo
       		checkTodo(id){
       			this.todos.forEach((todo)=>{
       				if(todo.id === id) todo.done = !todo.done
       			})
       		},
       		//删除一个todo
       		deleteTodo(id){
       			this.todos = this.todos.filter( todo => todo.id !== id )
       		},
       		//全选or取消全选
       		checkAllTodo(done){
       			this.todos.forEach((todo)=>{
       				todo.done = done
       			})
       		},
       		//清除所有已经完成的todo
       		clearAllTodo(){
       			this.todos = this.todos.filter((todo)=>{
       				return !todo.done
       			})
       		}
       	}
       }
    </script>
    
    <style>
       /*base*/
       body {
       	background: #fff;
       }
       .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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    MyHeader.vue

    <template>
       <div class="todo-header">
       	<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
       </div>
    </template>
    
    <script>
       import {nanoid} from 'nanoid'
       export default {
       	name:'MyHeader',
       	//接收从App传递过来的addTodo
       	props:['addTodo'],
       	data() {
       		return {
       			//收集用户输入的title
       			title:''
       		}
       	},
       	methods: {
       		add(){
       			//校验数据
       			if(!this.title.trim()) return alert('输入不能为空')
       			//将用户的输入包装成一个todo对象
       			const todoObj = {id:nanoid(),title:this.title,done:false}
       			//通知App组件去添加一个todo对象
       			this.addTodo(todoObj)
       			//清空输入
       			this.title = ''
       		}
       	},
       }
    </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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    MyFooter.vue

    <template>
       <div class="todo-footer" v-show="total">
       	<label>
       		<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
       		<input type="checkbox" v-model="isAll"/>
       	</label>
       	<span>
       		<span>已完成{{doneTotal}}</span> / 全部{{total}}
       	</span>
       	<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
       </div>
    </template>
    
    <script>
       export default {
       	name:'MyFooter',
       	props:['todos','checkAllTodo','clearAllTodo'],
       	computed: {
       		//总数
       		total(){
       			return this.todos.length
       		},
       		//已完成数
       		doneTotal(){
       			//此处使用reduce方法做条件统计
       			/* const x = this.todos.reduce((pre,current)=>{
       				console.log('@',pre,current)
       				return pre + (current.done ? 1 : 0)
       			},0) */
       			//简写
       			return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
       		},
       		//控制全选框
       		isAll:{
       			//全选框是否勾选
       			get(){
       				return this.doneTotal === this.total && this.total > 0
       			},
       			//isAll被修改时set被调用
       			set(value){
       				this.checkAllTodo(value)
       			}
       		}
       	},
       	methods: {
       		/* checkAll(e){
       			this.checkAllTodo(e.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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    MyList.vue

    <template>
       <ul class="todo-main">
       	<MyItem 
       		v-for="todoObj in todos"
       		:key="todoObj.id" 
       		:todo="todoObj" 
       		:checkTodo="checkTodo"
       		:deleteTodo="deleteTodo"
       	/>
       </ul>
    </template>
    
    <script>
       import MyItem from './MyItem'
    
       export default {
       	name:'MyList',
       	components:{MyItem},
       	//声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
       	props:['todos','checkTodo','deleteTodo']
       }
    </script>
    
    <style scoped>
       /*main*/
       .todo-main {
       	margin-left: 0px;
       	border: 1px solid #ddd;
       	border-radius: 2px;
       	padding: 0px;
       }
    
       .todo-empty {
       	height: 40px;
       	line-height: 40px;
       	border: 1px solid #ddd;
       	border-radius: 2px;
       	padding-left: 5px;
       	margin-top: 10px;
       }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    MyItem.vue

    <template>
       <li>
       	<label>
       		<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
       		<!-- 
       			
       			如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props 
       			v-model 绑定的是传递过来的数据
       		-->
       		<!-- <input type="checkbox" v-model="todo.done"/> -->
       		<span>{{todo.title}}</span>
       	</label>
       	<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
       </li>
    </template>
    
    <script>
       export default {
       	name:'MyItem',
       	//声明接收todo、checkTodo、deleteTodo
       	props:['todo','checkTodo','deleteTodo'],
       	methods: {
       		//勾选or取消勾选
       		handleCheck(id){
       			//通知App组件将对应的todo对象的done值取反
       			this.checkTodo(id)
       		},
       		//删除
       		handleDelete(id){
       			if(confirm('确定删除吗?')){
       				//通知App组件将对应的todo对象删除
       				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 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;
       }
    
       li:hover{
       	background-color: #ddd;
       }
       
       li:hover button{
       	display: block;
       }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    六、TodoList本地监视

    关于浏览器本地存储不熟悉的可以看回这篇博客:

    使用监视switch,监视数据todos的变化,变化后拿最新的数据存储

    • 第一次使用时,没有数据,JSON.parse 读取为空会报错,应该给一个空数组
    • 有勾选,监视的是todos下的done属性,所以应该是深度监视
      -完整版 deep:true

    app.vue

    <template>
    	<div id="root">
    		<div class="todo-container">
    			<div class="todo-wrap">
    				<MyHeader :addTodo="addTodo"/>
    				<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
    				<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script>
    	import MyHeader from './components/MyHeader'
    	import MyList from './components/MyList'
    	import MyFooter from './components/MyFooter.vue'
    
    	export default {
    		name:'App',
    		components:{MyHeader,MyList,MyFooter},
    		data() {
    			return {
    				//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
                   // 第一次使用时,没有数据,JSON.parse 读取为空会报错,应该给一个空数组
                   //不为空则返回JSON对象
    				todos:JSON.parse(localStorage.getItem('todos')) || []
    			}
    		},
    		watch: {
    			todos:{
    				deep:true,
    				handler(value){
    					localStorage.setItem('todos',JSON.stringify(value))
    				}
    			}
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    在这里插入图片描述

    七、TodoList自定义事件

    app.vue对MyHeader.vue

    <MyHeader @addTodo="addTodo"/>
    
    • 1

    MyHeader.vue

    <template>
       <div class="todo-header">
       	<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
       </div>
    </template>
    
    <script>
       import {nanoid} from 'nanoid'
       export default {
       	name:'MyHeader',
          // 接收App传递的过来的addTodo
          // props:['addTodo'],   不需要接收了
       	data() {
       		return {
       			title:''
       		}
       	},
       	methods: {
       		add(){
       			if(!this.title.trim()) return alert('输入不能为空')
       			const todoObj = {id:nanoid(),title:this.title,done:false}
       			//通知App组件去添加一个todo对象
                  // this.addTodo(todoObj)
       			this.$emit('addTodo',todoObj,1,2,3) // 触发事件
       			this.title = ''
       		}
       	},
       }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述
    app.vue对MyFooter.vue

    // :todos="todos" 是传的数据,不用改
    <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>  
    
    • 1
    • 2

    MyFooter.vue

    <template>
    	<div class="todo-footer" v-show="total">
    		<label>
    			<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
    			<input type="checkbox" v-model="isAll"/>
    		</label>
    		<span>
    			<span>已完成{{doneTotal}}</span> / 全部{{total}}
    		</span>
    		<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'MyFooter',
           //props:['todos','checkAllTodo','clearAllTodo'],
    		props:['todos'],
    		computed: {
    			total(){
    				return this.todos.length
    			},
    			doneTotal(){
    				return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
    			},
    			isAll:{
    				get(){
    					return this.doneTotal === this.total && this.total > 0
    				},
    				set(value){
    					// this.checkAllTodo(value)
    					this.$emit('checkAllTodo',value)
    				}
    			}
    		},
    		methods: {
    			//清空所有已完成
    			clearAll(){
    				// this.clearAllTodo()
    				this.$emit('clearAllTodo')
    			}
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这里插入图片描述

    八、 TodoList事件总线![

    原本是App –> Mylist –>MyItem 逐层传递

    main.js

    // 安装全局事件总线
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	beforeCreate() {
    		Vue.prototype.$bus = this
    	},
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    App.vue

    <template>
    	<div id="root">
    		<div class="todo-container">
    			<div class="todo-wrap">
    				<!-- 1.1<MyHeader @addTodo="addTodo" :checkTodo="checkTodo":deleteTodo="deleteTodo"/> -->
    				<MyList :todos="todos"/> // 不给list传
    				<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script>
    	export default {
            //收数据绑定事件总线,身上的自定义事件
    		mounted() {
    			this.$bus.$on('checkTodo',this.checkTodo) // 2.1
    			this.$bus.$on('deleteTodo',this.deleteTodo) // 2.1
    		},
    		beforeDestroy() {
    			this.$bus.$off('checkTodo') // 2.1
    			this.$bus.$off('deleteTodo') // 2.1 
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    MyList.vue

    <template>
    	<ul class="todo-main">
    		<MyItem 
    			v-for="todoObj in todos"
    			:key="todoObj.id" 
    			:todo="todoObj" 
                <!--1.3:checkTodo="checkTodo" -->
                <!--1.4:deleteTodo="deleteTodo" -->  
    		/>
    	</ul>
    </template>
    
    <script>
    	import MyItem from './MyItem'
    
    	export default {
    		name:'MyList',
    		components:{MyItem},
    		//声明接收App传递过来的数据
            // 1.2 props:['todos','checkTodo','clearAllTodo'] // List也不接收
    		props:['todos']
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    MyItem.vue

    <template>
    	<li>
    		<label>
    			<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
    			<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
    			<!-- <input type="checkbox" v-model="todo.done"/> -->
    			<span>{{todo.title}}</span>
    		</label>
    		<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    	</li>
    </template>
    
    <script>
    	export default {
    		name:'MyItem',
    		//声明接收todo
    		// 1.5 props:['todo','checkTodo','deleteTodo'], // Item 也接收不到了
            props:['todo'],
    		methods: {
    			//勾选or取消勾选
    			handleCheck(id){
    				// this.checkTodo(id)
    				this.$bus.$emit('checkTodo',id)// tem里面触发,绑定事件 2.2
    			},
    			//删除
    			handleDelete(id){
    					// this.deleteTodo(id)
    					this.$bus.$emit('deleteTodo',id) // 2.2
    				}
    			}
    		},
    	}
    </script> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述

    九、TodoList消息订阅与发布

    9.1 删除功能

    App.vue 订阅 Item 发布
    App.vue

    <template>
      <div id="root">
      	<div class="todo-container">
      		<div class="todo-wrap">
      			<MyHeader @addTodo="addTodo"/>
      			<MyList :todos="todos"/>
      			<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
      		</div>
      	</div>
      </div>
    </template>
    
    <script>
      import pubsub from 'pubsub-js'
    
      export default {
      	methods: {
      		//删除一个todo
      		//下划线占位,第一个参数是方法名
      		deleteTodo(_,id){
      			this.todos = this.todos.filter( todo => todo.id !== id )
      		}
      	},
      	mounted() {
      		this.$bus.$on('checkTodo',this.checkTodo)
      		this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)
      	},
      	beforeDestroy() {
      		this.$bus.$off('checkTodo')
      		pubsub.unsubscribe(this.pubId)
      	},
      }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    MyItem.vue

    <script>
    	import pubsub from 'pubsub-js'
    	export default {
    		methods: {
    			//删除
    			handleDelete(id){
    				if(confirm('确定删除吗?')){
    					// this.$bus.$emit('deleteTodo',id)
    					pubsub.publish('deleteTodo',id)
    				}
    			}
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    9.2 TodoList编辑功能

    • 新增编辑按钮,点击编辑按钮,变成input框
    • 需要修改完后input变回文字,但由于在浏览器中存储了数据,所以刷新还是input,所以需要使用失去焦点事件
    • 数据校验输入不能为空
    • 点击编辑按钮时,新出现的输入框自动获取焦点

    MyItem.vue

    <template>
    	<li>
    		<label>
    			<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
    			<span v-show="!todo.isEdit">{{todo.title}}</span>
    			<input 
    				type="text" 
    				v-show="todo.isEdit" 
    				:value="todo.title" 
    				@blur="handleBlur(todo,$event)"
    				ref="inputTitle"
    			>
    		</label>
    		<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    		<button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
    	</li>
    </template>
    
    <script>
    	import pubsub from 'pubsub-js'
    	export default {
    		name:'MyItem',
    		//声明接收todo
    		props:['todo'],
    		methods: {
    			//勾选or取消勾选
    			handleCheck(id){
    				//通知App组件将对应的todo对象的done值取反
    				// this.checkTodo(id)
    				this.$bus.$emit('checkTodo',id)
    			},
    			//删除
    			handleDelete(id){
    				if(confirm('确定删除吗?')){
    					//通知App组件将对应的todo对象删除
    					// this.deleteTodo(id)
    					// this.$bus.$emit('deleteTodo',id)
    					pubsub.publish('deleteTodo',id)
    				}
    			},
    			//编辑
    			handleEdit(todo){
                   // 判断 todo 身上是否有 isEdit 属性(正在修改的状态)
    				if(todo.hasOwnProperty('isEdit')){ 
    				// 有就直接修改
    					todo.isEdit = true 
    				}else{
    					// console.log('@')
    					// 没有添加 $set 添加数据(响应式)
    					this.$set(todo,'isEdit',true) 
                       	console.log(todo)
    				}
    				//1.直接写focus会出现一个问题:系统在执行完整个代码才会重载Vue,
    				//而在这过程中input还没有显示,即往一个不存在的input上挂focus,则无法实现
                   //2. 解决方法一:简单实现-使用定时器setTimeout,可不给时间。因为定时器会在该区域代码执行完后再调用
                   //3. 解决方法二(官方写法):$nextTick会在下一次DOM更新结束后执行其指定的回调
    				this.$nextTick(function(){ // $nextTick 下一轮 
    					this.$refs.inputTitle.focus() // 拿到输入框获取焦点 focus获取焦点
    				})
    			},
    			
    			//失去焦点回调(真正执行修改逻辑)
    			//e是输入框事件
    			handleBlur(todo,e){
    				todo.isEdit = false
    				if(!e.target.value.trim()) return alert('输入不能为空!')
    				this.$bus.$emit('updateTodo',todo.id,e.target.value)
    			}
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    app.vue

    <script>
    	export default {
    
    		methods: {
    			//更新一个todo
    			updateTodo(id,title){
    				this.todos.forEach((todo)=>{
    					if(todo.id === id) todo.title = title
    				})
    			}
    		},
    		mounted() {
    			this.$bus.$on('updateTodo',this.updateTodo)
    		},
    		beforeDestroy() {
    			this.$bus.$off('updateTodo')
    		},
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    十、TodoList过度与动画

    给每件todoThing添加和删除添加动画效果

    • 方式一:给todo —>Item
    • 方式二:List

    方式一:

    <template>
    	<transition name="todo" apper>
       	<li>
    			<label>
    			<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
    			<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
    			<!-- <input type="checkbox" v-model="todo.done"/> -->
    			<span v-show="!todo.isEdit">{{todo.title}}</span>
    				<input 
    					type="text" 
    					v-show="todo.isEdit" 
    					:value="todo.title" 
    					@blur="handleBlur(todo,$event)"
    					ref="inputTitle"
    				>
    			</label>
    			<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    			<button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
    		</li>
       </transition>
    </template>
    
    <style scoped>
    	.todo-enter-active{
    		animation: atguigu 0.5s linear;
    	}
    
    	.todo-leave-active{
    		animation: atguigu 0.5s linear reverse;
    	}
    
    	@keyframes atguigu {
    		from{
    			transform: translateX(100%);
    		}
    		to{
    			transform: translateX(0px);
    		}
    	}
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    方式二:List

    <template>
    	<ul class="todo-main">
    		<transition-group name="todo" appear>
    			<!--使用一次,就是一次todo-->
    			<MyItem 
    				v-for="todoObj in todos"
    				:key="todoObj.id" 
    				:todo="todoObj" 
    			/>
    		</transition-group>
    	</ul>
    </template>
    
    <style scoped>
    	.todo-enter-active{
    		animation: atguigu 0.5s linear;
    	}
    
    	.todo-leave-active{
    		animation: atguigu 0.5s linear reverse;
    	}
    
    	@keyframes atguigu {
    		from{
    			transform: translateX(100%);
    		}
    		to{
    			transform: translateX(0px);
    		}
    	}
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

  • 相关阅读:
    【华为机试真题 Python实现】出错的或电路
    Nginx 面试题
    海康Visionmaster-环境配置:MFC 二次开发环境配置方法
    [CSS]文字旁边的竖线以及布局知识
    SQL 查询的执行顺序
    Nginx快速入门
    Vue2笔记_03配置项
    开发deepstram的自定义插件,使用gst-dseaxmple插件进行扩充,实现deepstream图像输出前的预处理,实现图像自定义绘制图(精四)
    Java 微服务管理工具V2.0来了
    Ceph
  • 原文地址:https://blog.csdn.net/m0_51487301/article/details/126159930