曂沅仴駦 发表于 2024-8-22 02:17:03

Vue50 todolist自界说事件版本

代码

MyFooter.vue

<template>
        <div class="todo-footer" v-show="total">
                <label>
                        <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
                        <input type="checkbox" v-model="isAll"/>
                </label>
                <span>
                        <span>已完成{{doneTotal}}</span> / 全部{{total}}
                </span>
                <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
        </div>
</template>

<script>
        export default {
                name:'MyFooter',
                props:['todos'],
                computed: {
                        //总数
                        total(){
                                return this.todos.length
                        },
                        //已完成数
                        doneTotal(){
                                //此处使用reduce方法做条件统计
                                /* const x = this.todos.reduce((pre,current)=>{
                                        console.log('@',pre,current)
                                        return pre + (current.done ? 1 : 0)
                                },0) */
                                //简写
                                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
                        },
                        //控制全选框
                        isAll:{
                                //全选框是否勾选
                                get(){
                                        return this.doneTotal === this.total && this.total > 0
                                },
                                //isAll被修改时set被调用
                                set(value){
                                        // this.checkAllTodo(value)
                                        this.$emit('checkAllTodo',value)
                                }
                        }
                },
                methods: {
                        /* checkAll(e){
                                this.checkAllTodo(e.target.checked)
                        } */
                        //清空所有已完成
                        clearAll(){
                                // this.clearAllTodo()
                                this.$emit('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>
MyHeader.vue

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

<script>
        import {nanoid} from 'nanoid'
        export default {
                name:'MyHeader',
                data() {
                        return {
                                //收集用户输入的title
                                title:''
                        }
                },
                methods: {
                        add(){
                                //校验数据
                                if(!this.title.trim()) return alert('输入不能为空')
                                //将用户的输入包装成一个todo对象
                                const todoObj = {id:nanoid(),title:this.title,done:false}
                                //通知App组件去添加一个todo对象
                                this.$emit('addTodo',todoObj,1,2,3)
                                //清空输入
                                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>
MyItem.vue

<template>
        <li>
                <label>
                        <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
                        <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
                        <!-- <input type="checkbox" v-model="todo.done"/> -->
                        <span>{{todo.title}}</span>
                </label>
                <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
        </li>
</template>

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

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

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

        li label li input {
                vertical-align: middle;
                margin-right: 6px;
                position: relative;
                top: -1px;
        }

        li button {
                float: right;
                display: none;
                margin-top: 3px;
        }

        li:before {
                content: initial;
        }

        li:last-child {
                border-bottom: none;
        }

        li:hover{
                background-color: #ddd;
        }
       
        li:hover button{
                display: block;
        }
</style>
MyList.vue

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

<script>
        import MyItem from './MyItem'

        export default {
                name:'MyList',
                components:{MyItem},
                //声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
                props:['todos','checkTodo','deleteTodo']
        }
</script>

<style scoped>
        /*main*/
        .todo-main {
                margin-left: 0px;
                border: 1px solid #ddd;
                border-radius: 2px;
                padding: 0px;
        }

        .todo-empty {
                height: 40px;
                line-height: 40px;
                border: 1px solid #ddd;
                border-radius: 2px;
                padding-left: 5px;
                margin-top: 10px;
        }
</style>
App.vue

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

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

        export default {
                name:'App',
                components:{MyHeader,MyList,MyFooter},
                data() {
                        return {
                                //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
                                todos:JSON.parse(localStorage.getItem('todos')) || []
                        }
                },
                methods: {
                        //添加一个todo
                        addTodo(todoObj){
                                this.todos.unshift(todoObj)
                        },
                        //勾选or取消勾选一个todo
                        checkTodo(id){
                                this.todos.forEach((todo)=>{
                                        if(todo.id === id) todo.done = !todo.done
                                })
                        },
                        //删除一个todo
                        deleteTodo(id){
                                this.todos = this.todos.filter( todo => todo.id !== id )
                        },
                        //全选or取消全选
                        checkAllTodo(done){
                                this.todos.forEach((todo)=>{
                                        todo.done = done
                                })
                        },
                        //清除所有已经完成的todo
                        clearAllTodo(){
                                this.todos = this.todos.filter((todo)=>{
                                        return !todo.done
                                })
                        }
                },
                watch: {
                        todos:{
                                deep:true,
                                handler(value){
                                        localStorage.setItem('todos',JSON.stringify(value))
                                }
                        }
                },
        }
</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>

https://i-blog.csdnimg.cn/direct/15752178e177442abbf624139fd3eced.png
运行

https://i-blog.csdnimg.cn/direct/9b089d46633948b6aaeb24f57daf31be.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Vue50 todolist自界说事件版本