0


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'exportdefault{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 0rgba(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 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),00 8px rgba(82,168,236,0.6);}</style>

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/>// 想要数据多 就继续引入 <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>

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>

在这里插入图片描述

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

数据的类型、名称是什么

  • 一堆要做的事情是一个数组,一个个要做的事情是对象,对象里面的内容=={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'exportdefault{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><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>exportdefault{name:'MyItem',//声明接收todoprops:['todo'],}</script>

在这里插入图片描述

三、交互

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

3.1 添加

MyHeader.vue

  • 绑定个键盘事件
  • 把用户的输入打印
  • 获取用户的输入- 方式一:event 事件对象add(event){ consloe.log(event.target.value)// 获得发生事件对象的元素}- 方式二:v-model<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model='title' @keyup.enter="add"/>data(){return{title:''}}menthod:{add(event){ consloe.log(this.target)// 获得发生事件对象的元素}}
  • 把获取到的数据包装成一个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'exportdefault{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中添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)}},}</script>

MyHeader.vue

<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/></div></template><script>// 引入 nanoid import{nanoid}from'nanoid'exportdefault{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)// 方式一:实现 清空数据时操作了domthis.addTodo(todoObj)//清空输入
                event.target.value =''}},// 方式二:v-modeldata(){return{//收集用户输入的titletitle:''}},methods:{add(){//校验数据if(!this.title.trim())returnalert('输入不能为空')//将用户的输入包装成一个todo对象const todoObj ={id:nanoid(),title:this.title,done:false}//通知App组件去添加一个todo对象this.addTodo(todoObj)//清空输入this.title =''}},}</script>

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'exportdefault{name:'MyList',components:{MyItem},props:['todos'],}</script>

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>exportdefault{name:'MyItem',//声明接收todoprops:['todo'],methods:{//勾选or取消勾选handleCheck(id){//通知App组件将对应的todo对象的done值取反//checkTodo为App.vue定义的方法this.checkTodo(id)}},}</script>

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>exportdefault{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:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){//通过Item传回的id参数,对todos做遍历,找到对应id的对象,将其done取反this.todos.forEach((todo)=>{if(todo.id === id) todo.done =!todo.done
                })}}}</script>

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

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

3.3 删除

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

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

<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>...//声明接收todo、checkTodo、deleteTodoprops:['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>

App.vue 传 list

<MyList :todos="todos":checkTodo="checkTodo":deleteTodo="deleteTodo"/>...methods:{//删除一个tododeleteTodo(id){// filter 不改变原数组 this.todos =this.todos.filter(todo=> todo.id !== id )}}...

list 接收

<MyItem 
    v-for="todoObj in todos":key="todoObj.id":todo="todoObj":checkTodo="checkTodo":deleteTodo="deleteTodo"/>...props:['todos','checkTodo','deleteTodo']

在这里插入图片描述

3.4 底部统计

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

App.vue 给 footer 传递todos数组

<MyFooter :todos="todos"/>

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(){returnthis.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) *///简写returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)}// 方式二:常规遍历doneTotal(){let i =0this.todos.forEach((todo)=>){if(todo.done) i++}return i
            }},

在这里插入图片描述

3.5 底部交互

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

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

MyFooter.vue

//1.复杂写法//<input type="checkbox" :checked="doneTotal === tatal"/>//2.vue简便写法//total = 0即没有添加事件时,该模块不显示<div v-show="total"><input type="checkbox":checked="isAll" @change="checkAll"/></div></script>exportdefault{name:'MyFooter',props:['todos','checkAllTodo','clearAllTodo'],computed:{//总数total(){returnthis.todos.length
            },//已完成数doneTotal(){returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)},// 简写方式,没有setter 方法  只能被读取不能被修改才可以  后面需要修改//控制全选框// 一个计算属性可以通过其他的计算属性 在进行计算 isAll(){//已完成事件等于全部事件 且 全部事件大于0  才返回真returnthis.doneTotal ===this.total &&this.total >0}},}</script>

在这里插入图片描述

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(){returnthis.doneTotal ===this.total &&this.total >0},//isAll被修改时set被调用set(value){this.checkAllTodo(value)}}},

App.vue

<MyFooter :todos="todos":checkAllTodo="checkAllTodo"/>methods:{//全选or取消全选//这个done就是全选框的true或falsecheckAllTodo(done){//遍历每一个小框,将小框的true或false和全选框的选择状态同步this.todos.forEach((todo)=>{
            todo.done = done
        })},}

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()}},

App.vue

//清除所有已经完成的todoclearAllTodo(){this.todos =this.todos.filter((todo)=>{return!todo.done
        })}

四、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'exportdefault{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:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done =!todo.done
               })},//删除一个tododeleteTodo(id){this.todos =this.todos.filter(todo=> todo.id !== id )},//全选or取消全选checkAllTodo(done){this.todos.forEach((todo)=>{
                   todo.done = done
               })},//清除所有已经完成的todoclearAllTodo(){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 0rgba(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 class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/></div></template><script>import{nanoid}from'nanoid'exportdefault{name:'MyHeader',//接收从App传递过来的addTodoprops:['addTodo'],data(){return{//收集用户输入的titletitle:''}},methods:{add(){//校验数据if(!this.title.trim())returnalert('输入不能为空')//将用户的输入包装成一个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),00 8px rgba(82,168,236,0.6);}</style>

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>exportdefault{name:'MyFooter',props:['todos','checkAllTodo','clearAllTodo'],computed:{//总数total(){returnthis.todos.length
           },//已完成数doneTotal(){//此处使用reduce方法做条件统计/* const x = this.todos.reduce((pre,current)=>{
                   console.log('@',pre,current)
                   return pre + (current.done ? 1 : 0)
               },0) *///简写returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)},//控制全选框isAll:{//全选框是否勾选get(){returnthis.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>

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'exportdefault{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>

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>exportdefault{name:'MyItem',//声明接收todo、checkTodo、deleteTodoprops:['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>

六、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'exportdefault{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>

在这里插入图片描述

七、TodoList自定义事件

app.vue对MyHeader.vue

<MyHeader @addTodo="addTodo"/>

MyHeader.vue

<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/></div></template><script>import{nanoid}from'nanoid'exportdefault{name:'MyHeader',// 接收App传递的过来的addTodo// props:['addTodo'],   不需要接收了data(){return{title:''}},methods:{add(){if(!this.title.trim())returnalert('输入不能为空')const todoObj ={id:nanoid(),title:this.title,done:false}//通知App组件去添加一个todo对象// this.addTodo(todoObj)this.$emit('addTodo',todoObj,1,2,3)// 触发事件this.title =''}},}</script>

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

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

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>exportdefault{name:'MyFooter',//props:['todos','checkAllTodo','clearAllTodo'],props:['todos'],computed:{total(){returnthis.todos.length
            },doneTotal(){returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)},isAll:{get(){returnthis.doneTotal ===this.total &&this.total >0},set(value){// this.checkAllTodo(value)this.$emit('checkAllTodo',value)}}},methods:{//清空所有已完成clearAll(){// this.clearAllTodo()this.$emit('clearAllTodo')}},}</script>

在这里插入图片描述

八、 TodoList事件总线![

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

main.js

// 安装全局事件总线//创建vmnewVue({el:'#app',render:h=>h(App),beforeCreate(){Vue.prototype.$bus =this},})

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>exportdefault{//收数据绑定事件总线,身上的自定义事件mounted(){this.$bus.$on('checkTodo',this.checkTodo)// 2.1this.$bus.$on('deleteTodo',this.deleteTodo)// 2.1},beforeDestroy(){this.$bus.$off('checkTodo')// 2.1this.$bus.$off('deleteTodo')// 2.1 },}</script>

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'exportdefault{name:'MyList',components:{MyItem},//声明接收App传递过来的数据// 1.2 props:['todos','checkTodo','clearAllTodo'] // List也不接收props:['todos']}</script>

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>exportdefault{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>

在这里插入图片描述

九、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'exportdefault{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>

MyItem.vue

<script>import pubsub from'pubsub-js'exportdefault{methods:{//删除handleDelete(id){if(confirm('确定删除吗?')){// this.$bus.$emit('deleteTodo',id)
                    pubsub.publish('deleteTodo',id)}}},}</script>

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'exportdefault{name:'MyItem',//声明接收todoprops:['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 =falseif(!e.target.value.trim())returnalert('输入不能为空!')this.$bus.$emit('updateTodo',todo.id,e.target.value)}},}</script>

app.vue

<script>exportdefault{methods:{//更新一个todoupdateTodo(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>

十、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>

方式二: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>

在这里插入图片描述


本文转载自: https://blog.csdn.net/m0_51487301/article/details/126159930
版权归原作者 做一只猫 所有, 如有侵权,请联系我们删除。

“Vue:实现TodoList案例(尚硅谷)”的评论:

还没有评论