VueCLI核心知识综合案例TodoList

目录

1 拿到一个功能模块首先需要拆分组件:

2 使用组件实现静态页面的效果

3 分析数据保存在哪个组件

4 实现添加数据

5 实现复选框勾选

6 实现数据的删除

7 实现底部组件中数据的统计

8 实现勾选全部的小复选框来实现大复选框的勾选

9 实现勾选大复选框来实现所有的小复选框都被勾选

10 清空所有数据

11 实现案例中的数据存入本地存储

12 案例中使用自定义事件完成组件间的数据通信

13 案例中实现数据的编辑

14 实现数据进出的动画效果


【分析】组件化编码的流程

1. 实现静态组件:抽取组件,使用组件实现静态页面效果

2.展示动态数据:

        2.1 数据的类型、名称是什么?

        2.2 数据保存在哪个组件?

3.交互---从绑定事件监听开始


1 拿到一个功能模块首先需要拆分组件:


2 使用组件实现静态页面的效果

【main.js】

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  render: h => h(App)
})

【MyHeader】

<template>
    <div class="todo-header">
       <input type="text"/>
    </div>
</template>

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

<style scoped>
    /*header*/
    .todo-header input {
        width: 560px;
        height: 28px;
        font-size: 14px;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 4px 7px;
    }

    .todo-header input:focus {
        outline: none;
        border-color: rgba(82, 168, 236, 0.8);
        box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    }
</style>

【Item】

<template> 
    <li>
        <label>
            <input type="checkbox"/>
            <span v-for="todo in todos" :key="todo.id">{{todo.title}}</span>
        </label>
        <button class="btn btn-danger">删除</button>
    </li>
</template>

<script>
    export default {
        name: 'Item',
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
    }
</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: rgb(196, 195, 195);
    }
    li:hover button{
        display: block;
    }
</style>

【List】

<template>
    <ul class="todo-main">
        <Item></Item>
        <Item></Item>
    </ul>
</template>

<script>
    import Item from './Item.vue'
    export default {
        name: 'List',
        components:{
            Item
        },
        
    }
</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>

【MyFooter】

<template>
    <div class="todo-footer">
        <label>
            <input type="checkbox"/>
        </label>

        <span>
            <span>已完成 0</span> / 3
        </span>
        <button class="btn btn-danger">清除已完成任务</button>
    </div>
</template>

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

<style scoped>
    /*footer*/
    .todo-footer {
        height: 40px;
        line-height: 40px;
        padding-left: 6px;
        margin-top: 5px;
    }

    .todo-footer label {
        display: inline-block;
        margin-right: 20px;
        cursor: pointer;
    }

    .todo-footer label input {
        position: relative;
        top: -1px;
        vertical-align: middle;
        margin-right: 5px;
    }

    .todo-footer button {
        float: right;
        margin-top: 5px;
    }
</style>

【App】 

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader></MyHeader>
                <List></List>
                <MyFooter></MyFooter>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            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>

通过以上代码就可以实现静态页面的效果了!!!

3 分析数据保存在哪个组件

在上述代码中数据是保存在Item组件中的,但是如果想要在后续实现一系列交互效果:在MyHeader组件中需要添加数据,而MyHeader组件和Item组件没有直接的关系, 就当前学习阶段的知识而言,并不能实现这两个组件之间的通信(后续会有解决方案),同理MyFooter也一样。


【分析】因为App组件是所有组件的父组件,所以数据放在App组件中,再使用props配置,所有的子组件就都可以访问到。

【App】(同时需要将数据传递到Item组件中,在当前阶段只能通过props配置一层一层往下传,所以是 App-->List,List-->Item

1. 实现 App-->List 传递todos数据

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader></MyHeader>
                <List :todos="todos"></List>
                <MyFooter></MyFooter>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            MyFooter
        },
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
    }
</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>

2. List接受todos数据

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

<script>
    import Item from './Item.vue'
    export default {
        name: 'List',
        components:{
            Item
        },
        props: ['todos']
        
    }
</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>

通过上述的代码便可以根据数据的数量来渲染出几个Item了,但是此时Item里面是没有内容的,所以需要 List-->Item 再次传递每条数据


3. 实现 List-->Item 传递todo数据

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

<script>
    import Item from './Item.vue'
    export default {
        name: 'List',
        components:{
            Item
        },
        props: ['todos']
        
    }
</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>

4. Item接受todo数据

<template> 
    <li>
        <label>
            <input type="checkbox"/>
          
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger">删除</button>
    </li>
</template>

<script>
    export default {
        name: 'Item',
        // 声明接收todo对象
        props:['todo'],
    }
</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: rgb(196, 195, 195);
    }
    li:hover button{
        display: block;
    }
</style>

4 实现添加数据

【App】定义接收数据的回调函数

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

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            MyFooter
        },
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj) {
                this.todos.unshift(todoObj)
            }
        }
    }
</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>

【MyHeader】实现添加数据的方法

<template>
    <div class="todo-header">
        <!-- 绑定键盘回车事件 -->
       <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add" v-model="title"/>
    </div>
</template>

<script>
    import {nanoid} from 'nanoid'  // 生成id
    export default {
        name: 'MyHeader',
        data() {
            return {
                title: ''
            }
        },
        props: ['addTodo'],  // 接收父组件传过来的addTodo函数
        methods: {
            add(e) {
                // 校验数据
                if (!this.title.trim()) return alert('输入不能为空')

                // 将用户的输入包装成为一个todo对象
                const todoObj = {
                    id: nanoid(),
                    /* title: e.target.value, */
                    title: this.title,
                    done: false
                }
                console.log(todoObj)
                // console.log(e.target.value)
                // console.log(this.title)
                // 通知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>

5 实现复选框勾选

【App】也是要逐层传递

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader :addTodo="addTodo"></MyHeader>
                <List :todos="todos" :changeTodo="changeTodo"></List>
                <MyFooter></MyFooter>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            MyFooter
        },
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj) {
                this.todos.unshift(todoObj)
            },
            // 勾选或者取消勾选一个todo
            changeTodo(id) {
                this.todos.forEach((todo) => {
                  if (todo.id === id) todo.done = !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>

【List】

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

<script>
    import Item from './Item.vue'
    export default {
        name: 'List',
        components:{
            Item
        },
        props: ['todos', 'changeTodo']
        
    }
</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>

【Item】

<template> 
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
          
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger">删除</button>
    </li>
</template>

<script>
    export default {
        name: 'Item',
        // 声明接收todo对象
        props:['todo', 'changeTodo'],
        methods: {
            // 勾选 or 取消勾选
            handleCheck(id) {
                // 通知 App组件将对应的todo对象的状态改变
                this.changeTodo(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: rgb(196, 195, 195);
    }
    li:hover button{
        display: block;
    }
</style>

6 实现数据的删除

【App】

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader :addTodo="addTodo"></MyHeader>
                <List 
                    :todos="todos" 
                    :changeTodo="changeTodo" 
                    :deleteTodo="deleteTodo">
                </List>
                <MyFooter></MyFooter>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            MyFooter
        },
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj) {
                this.todos.unshift(todoObj)
            },

            // 勾选或者取消勾选一个todo
            changeTodo(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)
            },
        }
    }
</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>

【List】

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

<script>
    import Item from './Item.vue'
    export default {
        name: 'List',
        components:{
            Item
        },
        props: ['todos', 'changeTodo', '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>

【Item】

<template> 
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
          
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    </li>
</template>

<script>
    export default {
        name: 'Item',
        // 声明接收todo对象
        props:['todo', 'changeTodo', 'deleteTodo'],
        methods: {
            // 勾选 or 取消勾选
            handleCheck(id) {
                // 通知 App组件将对应的todo对象的状态改变
                this.changeTodo(id)
                
            },

            // 删除操作
            handleDelete(id) {
                // console.log(id)
                if (confirm("确定删除吗?")) {
                    // 通知App删除
                    this.deleteTodo(id)
                }
            }
        }
    }
</script>

<style scoped>
    /*item*/
    li {
        list-style: none;
        height: 36px;
        line-height: 36px;
        padding: 0 5px;
        border-bottom: 1px solid #ddd;
    }

    li label {
        float: left;
        cursor: pointer;
    }

    li 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: rgb(196, 195, 195);
    }
    li:hover button{
        display: block;
    }
</style>

7 实现底部组件中数据的统计

【分析】如果想要统计数据的数量,就需要将数据传递到MyFooter组件中

【App】

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader :addTodo="addTodo"></MyHeader>
                <List 
                    :todos="todos" 
                    :changeTodo="changeTodo" 
                    :deleteTodo="deleteTodo">
                </List>
                <MyFooter
                    :todos="todos"
                >
                </MyFooter>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            MyFooter
        },
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj) {
                this.todos.unshift(todoObj)
            },

            // 勾选或者取消勾选一个todo
            changeTodo(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)
            },
        }
    }
</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>

【MyFooter】

在这个组件中可以使用计算属性实现数据的总长度和被勾选的数据的计算

<template>
    <div class="todo-footer" v-if="todosLength">
        <label>
            <input type="checkbox"/>
        </label>
        <span>
            <span>已完成 {{doneTotal}}</span> / {{todosLength}}
        </span>
        <button class="btn btn-danger">清除已完成任务</button>
    </div>
</template>

<script>
export default {
    name: 'MyFooter',
    props: ['todos'],
    computed: {
        todosLength() {
            return this.todos.length
        },
        doneTotal() {
            return this.todos.filter(todo => todo.done).length

            // 也可以使用下面求和来实现
            // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
        }
    }
}
</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>

8 实现勾选全部的小复选框来实现大复选框的勾选

:checked="isAll"

isAll也是通过计算属性计算得来的

【MyFooter】 

<template>
    <div class="todo-footer" v-if="todosLength">
        <label>
            <input type="checkbox" :checked="isAll"/>
        </label>
        <span>
            <span>已完成 {{doneTotal}}</span> / {{todosLength}}
        </span>
        <button class="btn btn-danger">清除已完成任务</button>
    </div>
</template>

<script>
export default {
    name: 'MyFooter',
    props: ['todos'],
    computed: {
        todosLength() {
            return this.todos.length
        },
        doneTotal() {
            return this.todos.filter(todo => todo.done).length

            // 也可以使用下面求和来实现
            // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
        },

        isAll() {
            return this.doneTotal === this.todosLength && this.todosLength > 0
        }, 
    }
}
</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>

9 实现勾选大复选框来实现所有的小复选框都被勾选

【App】

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader :addTodo="addTodo"></MyHeader>
                <List 
                    :todos="todos" 
                    :changeTodo="changeTodo" 
                    :deleteTodo="deleteTodo">
                </List>
                <MyFooter
                    :todos="todos"
                    :checkAllTodo="checkAllTodo"
                >
                </MyFooter>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            MyFooter
        },
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj) {
                this.todos.unshift(todoObj)
            },

            // 勾选或者取消勾选一个todo
            changeTodo(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
                })
            },
        }
    }
</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>

【MyFooter】

<template>
    <div class="todo-footer" v-if="todosLength">
        <label>
            <input type="checkbox" :checked="isAll" @change="checkAll"/>
        </label>
        <span>
            <span>已完成 {{doneTotal}}</span> / {{todosLength}}
        </span>
        <button class="btn btn-danger">清除已完成任务</button>
    </div>
</template>

<script>
export default {
    name: 'MyFooter',
    props: ['todos', 'checkAllTodo'],
    computed: {
        todosLength() {
            return this.todos.length
        },
        doneTotal() {
            return this.todos.filter(todo => todo.done).length

            // 也可以使用下面求和来实现
            // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
        },

        isAll() {
            return this.doneTotal === this.todosLength && this.todosLength > 0
        }, 
    },
    methods: {
        checkAll(e) {
            this.checkAllTodo(e.target.checked)
        }
    }
}
</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>

10 清空所有数据

【App】

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader :addTodo="addTodo"></MyHeader>
                <List 
                    :todos="todos" 
                    :changeTodo="changeTodo" 
                    :deleteTodo="deleteTodo">
                </List>
                <MyFooter
                    :todos="todos"
                    :checkAllTodo="checkAllTodo"
                    :clearAllTodo="clearAllTodo"
                >
                </MyFooter>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import List from './components/List.vue'
    import MyFooter from './components/MyFooter.vue'
    
    export default {
        name:'App',
        components:{
            MyHeader,
            List,
            MyFooter
        },
        data() {
            return {
                todos: [
                    {id: '001', title: '吃饭', done: true},
                    {id: '002', title: '学习', done: false},
                    {id: '003', title: '追剧', done: true},
                ]
            }
        },
        methods: {
            // 添加一个todo
            addTodo(todoObj) {
                this.todos.unshift(todoObj)
            },

            // 勾选或者取消勾选一个todo
            changeTodo(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) => !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>

【MyFooter】

<template>
    <div class="todo-footer" v-if="todosLength">
        <label>
            <input type="checkbox" :checked="isAll" @change="checkAll"/>
        </label>
        <span>
            <span>已完成 {{doneTotal}}</span> / {{todosLength}}
        </span>
        <button class="btn btn-danger"  @click="clearTodo">清除已完成任务</button>
    </div>
</template>

<script>
export default {
    name: 'MyFooter',
    props: ['todos', 'checkAllTodo', 'clearAllTodo'],
    computed: {
        todosLength() {
            return this.todos.length
        },
        doneTotal() {
            return this.todos.filter(todo => todo.done).length

            // 也可以使用下面求和来实现
            // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
        },

        isAll() {
            return this.doneTotal === this.todosLength && this.todosLength > 0
        }, 
    },
    methods: {
        checkAll(e) {
            this.checkAllTodo(e.target.checked)
        },

        clearTodo() {
            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>

11 实现案例中的数据存入本地存储

【分析】首先我们要知道什么时候需要将数据存入本地存储?所以这就用到了watch监听,当todos的值发生变化时,将新的值存入本地存储。

又因为当我们勾选复选框时,我们发现本地存储中的 done 值并没有发生变化?这主要是因为监听默认只会监听第一层,如果想要监听对象中某个数据发生变化时,就需要深度监视了。

【App】

这里使用 || 运算可以防止一开始本地存储中没有数据而报错

12 案例中使用自定义事件完成组件间的数据通信

这边以添加数据为例

【App】

给发送数据的组件绑定自定义事件

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

...
 methods: {
      // 添加一个todo
      addTodo(todoObj) {
            this.todos.unshift(todoObj)
      },
}

【MyHeader】

13 案例中实现数据的编辑

需求分析:当点击编辑按钮时,变成input表单修改数据,此时编辑按钮隐藏,当失去焦点时,编辑完成,显示编辑后的数据,同时编辑按钮显示。

 这边使用全局事件总线来实现通信

【App】

        methods: {
            ...
            // 更改
            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')
        }

【Item】



因为如果想要失去焦点时实现数据的修改,那么你必须提前获取焦点,但是由于Vue的执行机制,当Vue底层监视到数据发生改变时,它并不会立即去重新渲染模板,而是继续执行后面的代码,所以如果不加以处理的话,直接获取焦点,肯定会报错,因为页面中的元素还没有加载解析出,找不到获取焦点的input元素,所以可以通过以下的代码实现

this.$nextTick(function() {  // 告诉Vue,DOM渲染完毕后,再执行focus()方法
      this.$refs.inputTiltle.focus()
})

14 实现数据进出的动画效果

【Item】

使用<transtion></transtion>标签包裹


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/387315.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

​StableSwarmUI#超越文本的prompt

今天看到一个新的webui方案&#xff0c;是Stability-AI开源的&#xff1a; StableSwarmUI 是一个模块化的稳定扩散web用户界面&#xff0c;着重于使强大的工具易于访问、高性能和可扩展性。 由于项目还在开发中&#xff0c;我们可以先了解下&#xff0c;翻看了它的特点&#xf…

幻兽帕鲁游戏联机的时候,显示“网络连接超时”怎么解决?

如果你在游戏联机的时候&#xff0c;显示“网络连接超时”&#xff0c;可以检查下&#xff1a; 1、前提是你已经按照教程部署成功 2、检查防火墙有没有忘记设置&#xff0c;协议是UDP&#xff08;只有TCP不行&#xff0c;一定要有UDP&#xff09;&#xff0c;端口是否填了8211&…

K210开发环境搭建(VS Code)

一、新建一个文件夹&#xff0c;就叫K210 二、再K210文件夹里面再新建一个文件夹&#xff0c;就叫CMake 三、找到官方提供的资料包里的cmake安装包&#xff0c; 或者直接去cmake官方下载网址进行下载 CMake官方下载网址&#xff1a;https://cmake.org/download/ 四、双击安装…

每日一题 (不用加减乘除做加法,找到数组中消失的数字)

不用加减乘除做加法_牛客题霸_牛客网 (nowcoder.com) 可以使用位运算符实现两个整数的加法&#xff1a; 在二进制加法中&#xff0c;我们通常使用“逐位相加”的方法来模拟常规加法的过程。当两个数字进行加法运算时&#xff0c;从最低位&#xff08;通常是右侧&#xff09;开…

开源≠不赚钱,开源软件盈利的7大模式。

开源不是目的&#xff0c;目的是圈用户&#xff0c;留住用户&#xff0c;盈利自然不成问题。 开源系统可以通过多种方式赚钱&#xff0c;以下是其中几种常见的方式&#xff1a; 提供付费支持&#xff1a; 开源系统可以提供付费的技术支持服务&#xff0c;包括安装、配置、维…

PyTorch深度学习快速入门教程 - 【小土堆学习笔记】

小土堆Pytorch视频教程链接 声明&#xff1a; 博主本人技术力不高&#xff0c;这篇博客可能会因为个人水平问题出现一些错误&#xff0c;但作为小白&#xff0c;还是希望能写下一些碰到的坑&#xff0c;尽力帮到其他小白 1 环境配置 1.1 pycharm pycharm建议使用2020的&…

ArcgisForJS基础

文章目录 0.引言1.第一个ArcgisForJS应用程序1.1.安装部署ArcgisForJS1.2.实现ArcgisForJS应用程序 2.开发与调试工具2.1.集成开发环境2.2.调试工具2.3.Firebug 0.引言 ArcGIS API for JavaScript是一款由Esri公司开发的用于创建WebGIS应用的JavaScript库。它允许开发者通过调…

【王道数据结构】【chapter5树与二叉树】【P158t9】

假设二叉树采用二叉链存储结构存储&#xff0c;设计一个算法&#xff0c;求先序遍历序列中第k个结点的值 #include <iostream> #include <stack> typedef struct treenode{char data;struct treenode *left;struct treenode *right; }treenode,*ptreenode;ptreenod…

支付交易——清结算

摘要 老王有个账本&#xff0c;店里进了哪些货、进的谁家货、花了多少钱&#xff0c;老王都会—一记下来;卖了哪些货、卖给了谁、卖了多少钱&#xff0c;也都会记下来。为什么要有个账本&#xff0c;看看老王是怎么进货和卖货的就知道了。老王店里虽然商品种类很多&#xff0c…

【数据结构】图

文章目录 图1.图的两种存储结构2.图的两种遍历方式3.最小生成树的两种算法&#xff08;无向连通图一定有最小生成树&#xff09;4.单源最短路径的两种算法5.多源最短路径 图 1.图的两种存储结构 1. 图这种数据结构相信大家都不陌生&#xff0c;实际上图就是另一种多叉树&…

刘谦竟然不是第一个吃螃蟹的!——历年春晚数学魔术精选

早点关注我&#xff0c;精彩不错过&#xff01; 在今年2024的央视春晚&#xff0c;刘谦用一个手法数学魔术的流程&#xff0c;配合上小尼的完美衬托&#xff0c;时隔5年&#xff0c;再一次为全国观众见证奇迹。 如此江湖地位的加持&#xff0c;使得他表演什么甚至失误都已经不再…

MySQL 基础知识(五)之数据增删改

目录 1 插入数据 2 删除数据 3 更改数据 创建 goods 表 drop table if exists goods; create table goods ( id int(10) primary key auto_increment, name varchar(14) unique, stockdate date )charsetutf8; 1 插入数据 当要插入的数据为日期/时间类型时&#xff0c;如果…

Python数学建模之回归分析

1.基本概念及应用场景 回归分析是一种预测性的建模技术&#xff0c;数学建模中常用回归分析技术寻找存在相关关系的变量间的数学表达式&#xff0c;并进行统计推断。例如&#xff0c;司机的鲁莽驾驶与交通事故的数量之间的关系就可以用回归分析研究。回归分析根据变量的…

2048游戏C++板来啦!

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 大家好呀&#xff0c;我是PingdiGuo_guo&#xff0c;今天我们来学习如何用C编写一个2048小游戏。 文章目录 1.2048的规则 2.步骤实现 2.1: 初始化游戏界面 2.1.1知识点 2.1.2: 创建游戏界面 2.2: 随机…

ng : 无法加载文件 C:\Program Files\nodejs\node_global\ng.ps1, 因为在此系统上禁止运行脚本

ng : 无法加载文件 C:\Program Files\nodejs\node_global\ng.ps1&#xff0c;因为在此系统上禁止运行脚本 今天在VSCode中运行ng serve --port 8081运行基于Angular的项目时&#xff0c;报错了&#xff0c;错误如下图所示&#xff1a; 解决方法&#xff1a; 按照下图的5步即…

【AI视野·今日NLP 自然语言处理论文速览 第七十八期】Wed, 17 Jan 2024

AI视野今日CS.NLP 自然语言处理论文速览 Wed, 17 Jan 2024 (showing first 100 of 163 entries) Totally 100 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Deductive Closure Training of Language Models for Coherence, Accur…

一探Lepton Search究竟

2024年1月25日&#xff0c;阿里巴巴原技术副总裁在 Twitter 上称用不到 500 行 Python 代码实现了 AI 对话搜索引擎&#xff0c;并在27日附上了开源地址&#xff1a;https://github.com/leptonai/search_with_lepton&#xff0c;截止春节期间已经5.8K的Star。 Twitter截图 Comm…

单测的思路

文章目录 单测的定义方法的单测几种生成工具的对比生成步骤 接口的单测场景的单测总结参考 单测的定义 单元测试&#xff08;Unit Testing&#xff09;是一种软件开发中的测试方法&#xff0c;它的主要目的是确保软件中的最小可测试单元&#xff08;通常是函数、方法或类&…

【蓝桥杯冲冲冲】Prime Gift

【蓝桥杯冲冲冲】Prime Gift 蓝桥杯备赛 | 洛谷做题打卡day31 文章目录 蓝桥杯备赛 | 洛谷做题打卡day31Prime Gift题面翻译题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示题解代码我的一些话 Prime Gift 题面翻译 给你 n n n 个…

学习笔记17:AtCoder Beginner Contest 340

C C - Divide and Divide (atcoder.jp) 1e17暴力肯定不行 模拟暴力的过程我们发现很多运算是重复的 记忆化一下 #include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #include<vector> #incl…
最新文章