【Vue】Vue2.0+Vue3.0学习笔记day08 (TodoList案例)

目录

070.TodoList案例_静态

071.TodoList案例_初始化列表

072.TodoList案例_添加

073.TodoList案例_勾选

074.TodoList案例_删除

075.TodoList案例_底部设计

076.TodoList案例_底部交互

077.TodoList案例_总结

078.浏览器本地存储

079.TodoList_本地存储


070.TodoList案例_静态

 首先分析拆分组件,新建下面四个组件

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <Header></Header>
                <List></List>
                <Footer></Footer>
            </div>
        </div>
    </div>
</template>

<script>
    import Header from "./components/Header.vue";
    import Footer from "./components/Footer.vue";
    import List from "./components/List.vue";
    export default {
        name:'App',
        components:{
            Header,
            Footer,
            List
        },       

    }
</script>

<style>
    body{
        background: #fff;
    }
    .btn{
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        line-height: 20px;
        font-size: 14px;
        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>

<template>
    <div class="todo-header">
      <input type="text" placeholder="请输入您的任务名称,按回车键确认"/>     
    </div>
</template>

<script>
    export default {
        name:'Header',
     
    }
</script>


<style scoped>
    .todo-header input{
        width: 560px;
        height: 28px;
        font-size: 28px;
        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>
<template>
    <ul class="todo-main">
         <Item></Item>  
         <Item></Item>  
         <Item></Item>  
         <Item></Item>  
    </ul>
</template>

<script>
    import Item from "./Item.vue";
    export default {
        name:'List',
        components:{Item}
     
    }
</script>


<style scoped>
    .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>

<template>
    <li>
        <label>
            <input type="checkbox"/>
            <span>xxxxx</span>
        </label>
        <button class="btn btn-danger" style="display:none">删除</button>
    </li>
</template>

<script>
    export default {
        name:'Item',
        
    }
</script>

<style scoped>
    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>

<template>
    <div class="todo-footer">
         <label>
            <input type="checkbox"/> 
        </label> 
        <span>
           <span>已完成0</span> / 全部2  
        </span> 
        <button class="btn btn-danger">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'Footer',
     
    }
</script>


<style scoped>
    .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>

071.TodoList案例_初始化列表

首先在List中定义数据并用v-for循环,然后用v-bind(简写直接 ‘:’)绑定动态的传递数据给item

<template>
    <ul class="todo-main">
         <Item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"></Item>  
    </ul>
</template>

<script>
    import Item from "./Item.vue";
    export default {
        name:'List',
        components:{Item},
        data() {
            return {
                todos:[
                    {id:'001',title:'唱跳',done:true},
                    {id:'002',title:'rep',done:false},
                    {id:'003',title:'篮球',done:true},
                ]
            }
        },
     
    }
</script>

在item中声明接收数据,用插值语法渲染;input的checked属性默认勾选,动态决定加v-bind

<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:'Item',
        // 声明接收todo对象
        props:['todo']
        
    }
</script>

072.TodoList案例_添加

 首先绑定一个键盘事件add并使用数据的双向绑定v-model让todos获取输入的数据(也可以不用双向绑定,使用直接操作DOM的方式获取数据 add(e){title:e.target.value),然后对id的处理可以安装nanoid库并引入nanoid,安装方法: npm i nanoid

<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:'Header',
        props:['addTodo'],
        data() {
            return {
                title:''
            }
        },
        methods: {
            // 绑定键盘事件
            add(){
                // 校验数据
                if(!this.title.trim()) return alert('输入不能为空')
                // 将用户的输入包装成一个todo对象
                const todoObj = {id:nanoid(),title:this.title,done:false}
                // console.log(todoObj);
                // 通知App组件去添加一个todo对象
                this.addTodo(todoObj)
                // 清空输入
                this.title=''
            }
        },
    }
</script>

 在父组件App里定义函数,并给子组件Header使用

备注:上一节把todos放在List里面,由于Header返回的todoObj要传到todos里,但是Header和List是同级关系,以当前的知识量同级之间传递不了数据,所以可以把todos放到App里面,由App传给List(父向子传递数据),Header返回的todoObj传给App里的todos(子向父传递数据,父组件定义一个函数并给子组件使用,子组件调用这个函数,父组件就可以收到参数)

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <Header :addTodo="addTodo"></Header>
                <List :todos="todos"></List>
                <Footer></Footer>
            </div>
        </div>
    </div>
</template>

<script>
    import Header from "./components/Header.vue";
    import Footer from "./components/Footer.vue";
    import List from "./components/List.vue";
    export default {
        name:'App',
        components:{
            Header,
            Footer,
            List
        },       
         data() {
            return {
                todos:[
                    {id:'001',title:'唱跳',done:true},
                    {id:'002',title:'rep',done:false},
                    {id:'003',title:'篮球',done:true},
                ]
            }
        },
        methods: {
            addTodo(todoObj){
                // 在组数第一位加元素unshift
                this.todos.unshift(todoObj)
            }
        },
    }
</script

073.TodoList案例_勾选

 首先在App中定义一个勾选修改函数,并交给子组件使用

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <Header :addTodo="addTodo"></Header>
                <List :todos="todos" :checkTodo="checkTodo"></List>
                <Footer></Footer>
            </div>
        </div>
    </div>
</template>

<script>
    import Header from "./components/Header.vue";
    import Footer from "./components/Footer.vue";
    import List from "./components/List.vue";
    export default {
        name:'App',
        components:{
            Header,
            Footer,
            List
        },       
         data() {
            return {
                todos:[
                    {id:'001',title:'唱跳',done:true},
                    {id:'002',title:'rep',done:false},
                    {id:'003',title:'篮球',done:true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj){
                // 在组数第一位加元素unshift
                this.todos.unshift(todoObj)
            },
            // 勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach(todo=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            }
        },
    }
</script>

 因为App向item传递要经过list,所以list要中转接受传过来的函数

<template>
    <ul class="todo-main">
         <Item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo"></Item>  
    </ul>
</template>

<script>
    import Item from "./Item.vue";
    export default {
        name:'List',
        components:{Item},
       props:['todos','checkTodo']
     
    }
</script>

 在item中绑定一个change事件,并通知App组件将对应的todo对象的done值取反

<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" style="display:none">删除</button>
    </li>
</template>

<script>
    export default {
        name:'Item',
        // 声明接收todo对象
        props:['todo','checkTodo'],
        methods: {
            handleCheck(id){
                // 通知App组件将对应的todo对象的done值取反
                this.checkTodo(id)
            }
        },      
    }
</script>

074.TodoList案例_删除

 在item中绑定删除事件,设置悬浮显示删除按钮样式,调用函数

<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:'Item',
        // 声明接收todo对象
        props:['todo','checkTodo','deleteTodo'],
        methods: {
            // 勾选or取消勾选
            handleCheck(id){
                // 通知App组件将对应的todo对象的done值取反
                this.checkTodo(id)
            },
            // 删除
            handleDelete(id){
                if(confirm('确定删除吗?')){
                    this.deleteTodo(id)
                }
            }
        },      
    }
</script>

<style scoped>
  
    }
    /* 鼠标悬浮 */
    li:hover{
        background-color: #ddd;
    }
    /* 悬浮时删除按钮显现 */
     li:hover button{
        display: block;
    }
</style>

 中转

<template>
    <ul class="todo-main">
         <Item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></Item>  
    </ul>
</template>

<script>
    import Item from "./Item.vue";
    export default {
        name:'List',
        components:{Item},
       props:['todos','checkTodo','deleteTodo']
     
    }
</script>

 把除了要删除的过滤出来

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <Header :addTodo="addTodo"></Header>
                <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></List>
                <Footer></Footer>
            </div>
        </div>
    </div>
</template>

<script>
    import Header from "./components/Header.vue";
    import Footer from "./components/Footer.vue";
    import List from "./components/List.vue";
    export default {
        name:'App',
        components:{
            Header,
            Footer,
            List
        },       
         data() {
            return {
                todos:[
                    {id:'001',title:'唱跳',done:true},
                    {id:'002',title:'rep',done:false},
                    {id:'003',title:'篮球',done:true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj){
                // 在组数第一位加元素unshift
                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=>{
                    return todo.id !== id
                })
            }
        },
    }
</script>

075.TodoList案例_底部设计

 在App中给Footer传入toods

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <Header :addTodo="addTodo"></Header>
                <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></List>
                <Footer :todos="todos"></Footer>
            </div>
        </div>
    </div>
</template>

在Footer中利用计算属性 

<template>
    <div class="todo-footer">
         <label>
            <input type="checkbox"/> 
        </label> 
        <span>
           <span>已完成{{doneTotal}}</span> / 全部{{todos.length}}  
        </span> 
        <button class="btn btn-danger">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'Footer',
        props:['todos'],
        computed:{
            doneTotal(){
                /* 条件统计reduce((pre,current)=>{},0),统计初始值为0,数组长度是多少,函数调用几次,
                   pre上一次的返回值,current当前的值,最后一次调用函数的返回值是reduce的返回值 */
                /* this.todos.reduce((pre,current)=>{
                    console.log('@',pre,current);
                    return pre + (current.done? 1 : 0)
                },0) */
                return this.todos.reduce((pre,current)=> pre + (current.done ? 1 : 0),0)
            }
        }
    }
</script>

076.TodoList案例_底部交互

 

<template>
       <!-- 当内容删完时,list隐藏 -->
    <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:'Footer',
        props:['todos','checkAllTodo','clearAllTodo'],
        computed:{
            total(){
                return this.todos.length
            },
            doneTotal(){
                /* 条件统计reduce((pre,current)=>{},0),统计初始值为0,数组长度是多少,函数调用几次,
                   pre上一次的返回值,current当前的值,最后一次调用函数的返回值是reduce的返回值 */
                /* 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 
                },
                set(checked){
                    this.checkAllTodo(checked)
                }
            }
            /* isAll(){
                return this.doneTotal === this.total && this.total>0
            } */
        },
        methods: {
            /* checkAll(e){
                this.checkAllTodo(e.target.checked)
            } */
            clearAll(){
                this.clearAllTodo()
            }
        },
    }
</script>

 

template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <Header :addTodo="addTodo"></Header>
                <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></List>
                <Footer :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></Footer>
            </div>
        </div>
    </div>
</template>

<script>
    import Header from "./components/Header.vue";
    import Footer from "./components/Footer.vue";
    import List from "./components/List.vue";
    export default {
        name:'App',
        components:{
            Header,
            Footer,
            List
        },       
         data() {
            return {
                todos:[
                    {id:'001',title:'唱跳',done:true},
                    {id:'002',title:'rep',done:false},
                    {id:'003',title:'篮球',done:true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj){
                // 在组数第一位加元素unshift
                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=>{
                    return 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>

077.TodoList案例_总结

## 总结TodoList案例
   1.组件化编码流程:
      1 拆分静态组件:组件要按照功能点拆分,命名不用与HTML元素冲突
      2 实现动态组件:考虑好数据存放位置,数据是一个组件在用,还是一些组件在用
          一个组件在用放在组件自身即可
          一些组件在用放在他们共同的父组件上(状态提升)
      3 实现交互:从绑定事件开始    
   2.props适用于:
      1 父组件==>子组件 通信
      2 子组件==>父组件 通信(要求父给子一个函数)
   3.使用v-model时要切记v-model绑定的值不能是props传过来的值,因为props是不可以修改的
   4.props传过来的若是对象类型的值,修改对象中的属性时vue不会报错,但不推荐这样做 

078.浏览器本地存储

## webStorage
   1.存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
   2.浏览器端通过Widow.sessionStorage和Widow.localStorage属性来本地存储机制
   3.相关API:
      1 xxxStorage.setItem('key','value') 
       该方法接受一个键和值作为参数,会把键和值添加到存储中,如果键名存在则更新对应的值
      2 xxxStorage.getItem('person')
       该方法接受一个键名作为参数,返回键名对应的值
      3 xxxStorage.removeItem('key')
       该方法接受一个键名作为参数,并把该键名从存储中删除
      4 xxxStorage.clear()
       该方法会清空存储中所有的数据
   4.备注:
      1 sessionStorage存储的内容会随着浏览器窗口关闭而消失
      2 localStorage存储的内容需要手动清除才会消失
      3 xxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null
      4 JSON.parse(null)的结果依然是null

 

 

<!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>localStorage</title>
</head>
<body>
    <h2>localStorage</h2>
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>
    <button onclick="deleteData()">点我删除一个数据</button>
    <button onclick="deleteAllData()">点我清空数据</button>

    <script>
        let p ={name:'张三',age:'19'}

        function saveData() {
            localStorage.setItem('msg','hello!')
            localStorage.setItem('msg1',666)
            localStorage.setItem('person',JSON.stringify(p))
        }
        function readData() {           
            console.log(localStorage.getItem('msg'));
            console.log(localStorage.getItem('msg1'));
            const res =localStorage.getItem('person')
            console.log(JSON.parse(res));

            console.log(localStorage.getItem('msg2'));
        }
        function deleteData() {
            localStorage.removeItem('msg')
        }
        function deleteAllData() {
            localStorage.clear()
        }
    </script>
</body>

</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>sessionStorage</title>
</head>
<body>
    <h2>sessionStorage</h2>
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>
    <button onclick="deleteData()">点我删除一个数据</button>
    <button onclick="deleteAllData()">点我清空数据</button>

    <script>
        let p ={name:'张三',age:'19'}

        function saveData() {
            sessionStorage.setItem('msg','hello!')
            sessionStorage.setItem('msg1',666)
            sessionStorage.setItem('person',JSON.stringify(p))
        }
        function readData() {           
            console.log(sessionStorage.getItem('msg'));
            console.log(sessionStorage.getItem('msg1'));
            const res =sessionStorage.getItem('person')
            console.log(JSON.parse(res));

            console.log(sessionStorage.getItem('msg2'));
        }
        function deleteData() {
            sessionStorage.removeItem('msg')
        }
        function deleteAllData() {
            sessionStorage.clear()
        }
    </script>
</body>

</html>

079.TodoList_本地存储

 

 

 在App利用深度监视属性,实现本地存储

<script>
    import Header from "./components/Header.vue";
    import Footer from "./components/Footer.vue";
    import List from "./components/List.vue";
    export default {
        name:'App',
        components:{
            Header,
            Footer,
            List
        },       
         data() {
            return {
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
      
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        }
    }
</script>

  最后完成代码

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <Header :addTodo="addTodo"></Header>
                <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></List>
                <Footer :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></Footer>
            </div>
        </div>
    </div>
</template>

<script>
    import Header from "./components/Header.vue";
    import Footer from "./components/Footer.vue";
    import List from "./components/List.vue";
    export default {
        name:'App',
        components:{
            Header,
            Footer,
            List
        },       
         data() {
            return {
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj){
                // 在组数第一位加元素unshift
                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=>{
                    return todo.id !== id
                })
            },
            // 全选or取消全选
            checkAllTodo(done){
                this.todos.forEach(todo=>{
                    todo.done = done
                })
            },
            // 清除所有已经完成的todo
            clearAllTodo(){
                this.todos= this.todos.filter(todo=>{
                    return !todo.done
                })
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        }
    }
</script>

<style>
<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:'Header',
        props:['addTodo'],
        data() {
            return {
                title:''
            }
        },
        methods: {
            // 绑定键盘事件
            add(){
                // 校验数据
                if(!this.title.trim()) return alert('输入不能为空')
                // 将用户的输入包装成一个todo对象
                const todoObj = {id:nanoid(),title:this.title,done:false}
                // console.log(todoObj);
                // 通知App组件去添加一个todo对象
                this.addTodo(todoObj)
                // 清空输入
                this.title=''
            }
        },
    }
</script>


<style scoped>
    .todo-header input{
        width: 560px;
        height: 28px;
        font-size: 28px;
        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>

<template>
    <ul class="todo-main">
         <Item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></Item>  
    </ul>
</template>

<script>
    import Item from "./Item.vue";
    export default {
        name:'List',
        components:{Item},
       props:['todos','checkTodo','deleteTodo']
     
    }
</script>


<style scoped>
    .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>
<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:'Item',
        // 声明接收todo对象
        props:['todo','checkTodo','deleteTodo'],
        methods: {
            // 勾选or取消勾选
            handleCheck(id){
                // 通知App组件将对应的todo对象的done值取反
                this.checkTodo(id)
            },
            // 删除
            handleDelete(id){
                if(confirm('确定删除吗?')){
                    this.deleteTodo(id)
                }
            }
        },      
    }
</script>

<style scoped>
    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>
<template>
            <!-- 当内容删完时,list隐藏 -->
    <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:'Footer',
        props:['todos','checkAllTodo','clearAllTodo'],
        computed:{
            total(){
                return this.todos.length
            },
            doneTotal(){
                /* 条件统计reduce((pre,current)=>{},0),统计初始值为0,数组长度是多少,函数调用几次,
                   pre上一次的返回值,current当前的值,最后一次调用函数的返回值是reduce的返回值 */
                /* 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 
                },
                set(checked){
                    this.checkAllTodo(checked)
                }
            }
            /* isAll(){
                return this.doneTotal === this.total && this.total>0
            } */
        },
        methods: {
            /* checkAll(e){
                this.checkAllTodo(e.target.checked)
            } */
            clearAll(){
                this.clearAllTodo()
            }
        },
    }
</script>


<style scoped>
    .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>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值