Vue08-TodoList案例

 


Vue08-TodoList案例

1.TodoList案例

  1. TodoList案例是一个未完成事情和已经完成事情管理的案例。

  2. TodoList中总共包含五个组件。App.vue;MyHeader.vue;MyFooter.vue;MyList.vue;MyItem.vue。

2.main.js

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

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

3.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" :clearDoneTodo="clearDoneTodo" />
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader'
    import MyList from './components/MyList'
    import MyFooter from './components/MyFooter'

    export default {
        name: "App",
        data() {
            return {
                // 将MyList中的列表数据放在App中。
                // 原因:MyHeader组件中要往todos中添加数据,但是MyHeader和MyList组件是平级关系,暂时没有
                // 办法直接传递数据,所以需要借助MyList和MyHeader的父类App(App相当与一个桥梁)来完成。

                // App将数据传递给MyList通过props传递。

                // MyHeader将提交的数据传递给App。也是通过props完成,不过props传递一个函数,MyHeader添加
                // 数据时,调用函数即可。
                todos: [
                    {id: '001', name: '学习', done: true},
                    {id: '002', name: '吃饭', done: true},
                    {id: '003', name: '喝酒', done: false},
                ]
            }
        },
        methods: {
            // 添加todo
            addTodo(todoObj) {
                console.log('App组件接受到数据', todoObj);
                this.todos.unshift(todoObj);
            },
            // 通过id修改todo的状态。
            checkTodo(id) {
                this.todos.forEach((todo) => {
                    if (todo.id === id) {
                        todo.done = !todo.done;
                    }
                })
            },
            // 通过id删除todo。
            deleteTodo(id) {
                this.todos = this.todos.filter(todo => todo.id !== id);
            },
            // 修改所有todo的状态为指定值。
            checkAllTodo(value) {
                this.todos.forEach((todo) => {
                    todo.done = value;
                })
            },
            // 清除所有已经完成的事情
            clearDoneTodo() {
                this.todos = this.todos.filter(todo => {
                    return !todo.done;
                })
            }
        },
        components: {
            MyHeader,
            MyList,
            MyFooter
        }
    }
</script>

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

4.MyHeader.vue

<template>
    <div class="todo-header">
        <!-- 获取input输出数据的两种方式。
                1 通过事件对象获取。event.target.value
                2 在data中声明属性msg,然后在input中使用v-model双向绑定。
         -->
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/>
    </div>
</template>

<script>
    // nanoid是一个变种的UUID生成库。
    // 按照nanoid库,npm i nanoid
    import {nanoid} from 'nanoid'

    export default {
        name: "MyHeader",
        methods: {
            add(event) {
                // 校验输入的值
                if (!event.target.value.trim()) {
                    return alert('输入不能为空');
                }

                // 将输入的数据组成为对象
                const todoObj = {id: nanoid(), name: event.target.value, done: false};
                // 将对象传递给App组件。
                this.addTodo(todoObj);
                // 清除输入框。
                event.target.value = '';
            }
        },
        props: ['addTodo']
    }
</script>

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

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!-- 全选和全不全的第一种判断方式,通过@change调用handlerChange()完成。 -->
            <!--<input type="checkbox" :checked="isAll" @change="handlerChange"/>-->

            <!-- 全选和全不全的第一种判断方式,通过v-model绑定isAll计算属性实现。
                当check改变之后会调用计算属性isAll的set方法,所以需要给isAll设置set方法。
                在set中调用App组件中checkAllTodo()函数修改状态。
             -->
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
       <span>已完成 {{doneTotal}}</span> / 全部{{total}}
    </span>
        <button class="btn btn-danger" @click="handlerClear">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name: "MyFooter",
        methods: {
            // 全选或者全不选所有的todo。
            handlerChange(event) {
                // event.target.checked通过事件获取操作元素的勾选状态。
                this.checkAllTodo(event.target.checked);
            },
            // 清除所有已经完成的事情
            handlerClear() {
                this.clearDoneTodo();
            }
        },
        computed: {
            // 计算所有的事情
            total() {
                return this.todos.length;
            },
            // 统计完成事情的数量
            doneTotal() {
                // 使用ES6数组的reduce方法统计。
                // reduce()有两个参数,第一个参数是为函数,第二个参数是reduce返回值的初始值。
                // 如果数组的长度是n,reduce就会被调用n次。
                
                // 第一次调用时,会将reduce第二个参数,即返回值的初始值,传递给箭头函数的第一个参数pre。
                // 之后的每次调用都会将reduce的返回值传递给箭头函数的第一个参数pre。
                
                // 箭头函数的第二个参数是当前数组遍历时的对象。
                // const total = this.todos.reduce((pre, todo) => {
                //     if (todo.done) {
                //         pre++;
                //     }
                //     return pre;
                // }, 0);
                // return total;

                // reduce简写
                return this.todos.reduce((pre, todo) => todo.done ? pre + 1 : pre, 0);
            },
            // 判断所有的事情是否都完成了。
            // isAll() {
            //     return this.total === this.doneTotal && this.total > 0;
            // }

            isAll: {
                get() {
                    return this.total === this.doneTotal && this.total > 0;
                },
                set(value) {
                    this.checkAllTodo(value);
                }
            }
        },
        props: ['todos', 'checkAllTodo', 'clearDoneTodo']
    }
</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>

6.MyList.vue

<template>
    <ul class="todo-main">
        <!--
            1 通过v-for遍历数据,然后显示。
            2 通过:todo将数据传递给MyItem组件,不能通过todo传递,
                因为todo不会解析todoObj,会直接将"todoObj"当做字符串传递给MyItem。
        -->
        <MyItem v-for="todoObj in todos"
                :key="todoObj.id"
                :todo="todoObj"
                :checkTodo="checkTodo"
                :deleteTodo="deleteTodo"
        />
    </ul>
</template>

<script>
    import MyItem from './MyItem'

    export default {
        name: "MyList",
        props: ['todos', 'checkTodo', 'deleteTodo'],
        components: {
            MyItem
        }
    }
</script>

<style scoped>
    .todo-main {
        margin-left: 0;
        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>

7.MyItem.vue

<template>
    <li>
        <label>
            <!-- 1 :checked,控制元素中属性的动态显示。
                如果:checked的值为true元素中就有checked属性;否则元素中没有checked属性。
            -->

            <!-- 2 点击input后调用checkTodo()函数修改状态的两种方式。
                    1 通过添加事件@click来完成。
                    2 通过@change来完成,因为input是checkbox选框框,当添加后checked会发生改变。
            -->
            <input type="checkbox" :checked="todo.done" @change="checkTodo(todo.id)"/>

            <!--
                v-model="todo.done",也可以使用v-model来在变化时修改数据。
                   当v-model绑定的是一个布尔值时,如果是true就在元素上显示checked属性。
                   如果是false,则不显示checked。

                但是不推荐使用v-model来修改值。
                    1 vue不建议修改props中的值。
                    2 todo数据是在App中定义的,所以最好通过App中的方法修改值。
            -->
            <!--<input type="checkbox" v-model="todo.done"/>-->
            <span>{{  todo.name }}</span>
        </label>
        <button class="btn btn-danger" @click="handlerDelete(todo.id)">删除</button>
    </li>
</template>

<script>
    export default {
        name: "MyItem",
        methods: {
            handlerDelete(id) {
                if (confirm('确认删除')) {
                    this.deleteTodo(id);
                }
            }
        },
        props: ['todo', 'checkTodo', 'deleteTodo']
    }
</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: #ddd;
    }

    /* 鼠标悬浮时显示删除按钮。 */
    li:hover button{
        display: block;
    }
</style>
posted @   行稳致远方  阅读(32)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示