用vue实现一个简单的todolist

934 阅读2分钟

一、设计一个简单的todolist的基本思路

在HTML文件中添加Vue框架,以及一个容器元素来放置todolist的所有元素。

在Vue实例中添加data对象,用于存储todolist的数据,例如一个数组来存储待办事项和已完成事项。

在Vue实例中添加方法,用于添加待办事项、完成事项、删除事项等操作。

在HTML中添加输入框和按钮,用于输入待办事项并添加到列表中。

在HTML中添加复选框和标记按钮,用于将待办事项标记为已完成并移到已完成列表中。

在HTML中添加删除按钮,用于删除已完成事项。

在HTML中添加两个列表,一个用于显示待办事项,一个用于显示已完成事项。

在Vue实例中添加计算属性,用于过滤待办事项和已完成事项。

在Vue实例中添加生命周期钩子,用于从本地存储中获取数据并保存数据。

根据需要添加样式表,美化todolist的外观。

image.png

3、涉及的基本功能

1、APP:

1、 数组todos存储待办事项,每项待办包括唯一ID,待办事项内容,是否完成(boolean2、 数组filtertodos用于存储根据某种条件过滤后的数组,用于实现“隐藏、显示已完成任务”功能
3、数组todos存储于localstorage中
4、根据需要将todos或filtertodos传递给子组件
5、增删改功能函数

2、myheader:将输入框中输入的数据存储到todos中

3、mylist:用于显示todos各项待办内容,每项待办均有删除和修改菜单,用于修改todos数据或删除

4、myfooter:统计完成任务数量,隐藏或显示已完成任务功能

三、涉及的一些知识

1、flex布局,浮动,滚动条(overflow) 2、利用props从父组件向子组件传送数据;通过emitemit,on实现从子组件向父组件传递数据;生命周期钩子读取数据(watched);computed计算属性;localstorage相关知识

四、具体代码

//APP
<template>
    <div id="app">
        <div>
            <MyHeader></MyHeader>
            <MyList :todos="filtertodos"></MyList>
            <MyFooter :todos="todos" :isshowitem="isshowitem"></MyFooter>
        </div>
    </div>
</template>

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

export default {
    name: "App",
    components: {
        MyHeader,
        MyList,
        MyFooter
    },
    data() {
        return {
       
            todos: JSON.parse(localStorage.getItem('todos')) || [],
            filtertodos: JSON.parse(localStorage.getItem('todos')) || [],
            isshowitem: false
        }
    },
    methods: {
        // 添加
        recieve(x) {
            this.todos.unshift(x);
            this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;

        },
        //对某个待办事项进行编辑
        handupData(id, newvalue) {
            this.todos.forEach((todo) => {
                if (todo.id === id)
                    todo.todo = newvalue
            })
            this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
        },
        // 删除某个特定的待办
        handleMove(id) {

            this.todos = this.todos.filter(todo => todo.id !== id)
            this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;

        },
        //检查是否勾选
        checkTodo(id) {
            this.todos.forEach((todo) => {
                if (todo.id === id) todo.done = !todo.done
            })
            this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
        },

        //全选
        chooseAll(bool) {
            this.todos.forEach((todo) => {
                todo.done = bool;
            })
            this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
        },
        //删除已经完成的项目
        deleteHaveDone() {
            this.todos = this.todos.filter((todo) => {
                return !todo.done
            });
            this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
        },
        changeisshowitem() {
            this.isshowitem = !this.isshowitem;
            this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
        }
    },

    watch: {
        todos: {
            deep: true,
            handler(value) {
                localStorage.setItem('todos', JSON.stringify(value))
                // localStorage.clear()

            },
        },
    },
    mounted() {
        this.$bus.$on('recieve', this.recieve)
        this.$bus.$on('checkTodo', this.checkTodo)
        this.$bus.$on('handleMove', this.handleMove)
        this.$bus.$on('chooseAll', this.chooseAll)
        this.$bus.$on('deleteHaveDone', this.deleteHaveDone)
        this.$bus.$on('handupData', this.handupData)
        this.$bus.$on('changeisshowitem', this.changeisshowitem)
        

    },
    beforeDestroy() {
        this.$bus.$off('checkTodo')
        this.$bus.$off('handleMove')
        this.$bus.$off('recieve')
        this.$bus.$off('chooseAll')
        this.$bus.$off('deleteHaveDone')
    },
}

</script>

<style>
#app {


    padding: 10px;
    border: 1px solid gray;
    background-color: rgb(242, 242, 242);
    display: flex;
    align-items: center;
    justify-content: center;
}




button {

    margin: 5px 10px;
}
</style>


//myfooter
<template>
    <div class="todo-footer" v-show="total">
        <label>
        
            <input type="checkbox" v-model="isAll" />
        </label>
        <span>
            <span>已完成{{ doneNum }}</span>/ 全部{{ total }}
        </span>
        <button @click="changeisshowitem">{{ isshowitem ? "显示所有" : "隐藏已完成" }}</button>
        <button class="btn btn-danger" @click="clearHaveDone">清除已完成任务</button>

    </div>
</template>
<script>


export default {
    name: 'MyFooter',
    // props: ['todos', 'chooseAll', 'deleteHaveDone'],
    props: ['todos', 'isshowitem'],
    data() {
        return {

        }
    },
    computed: {
        total() {
            return this.todos.length;
        },
        doneNum() {
            // const x = this.todos.reduce((num, todo) => {
            //     return num + (todo.done ? 1 : 0)
            // }, 0)
            // return x;
            return this.todos.reduce((num, todo) => num + (todo.done ? 1 : 0), 0)
        },
        isAll: {
            get() {
                if (this.todos.length === 0) {
                    return false;
                }
                return this.todos.length === this.doneNum
            },
            set(value) {
                this.$bus.$emit('chooseAll', value);
            }
        },

    },
    methods: {
        //     checkAll(e) {
        //         // console.log(e.target.checked);
        //         this.chooseAll(e.target.checked);
        //     }
        clearHaveDone() {

            this.$bus.$emit('deleteHaveDone');
        },
        changeisshowitem() {
            this.$bus.$emit('changeisshowitem');
        }
    },

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

}

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

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

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



//mylist
<template>
    <ul>
        <MyItem v-for="(item) in todos" :key="item.id" :item="item">
        </MyItem>
    </ul>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
    name: "MyList",
    components: {
        MyItem
    },
    props: ['todos']
}
</script>
<style>
ul {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
    height: 200px;
    overflow-y: auto;
    overflow-x: hidden;

}
</style>




//myitem
<template>
    <li>
        <label>
            <!-- <input type="checkbox" :checked="item.done" v-model="item.done" /> -->
            <!-- //不建议使用v-model,因为props不能修改 -->
            <input type="checkbox" :checked="item.done" @change="handleCheck(item.id)" />
            <span :class="{ merge: item.done === false, more: item.done === true }" v-show="!item.isEdit">{{
                item.todo
            }}</span>

            <input v-show="item.isEdit" @blur="upData($event, item)" ref="inputTitle" />
        </label>

        <button class="btn btn-danger" @click="handleCheck1(item.id)">删除</button>
        <button @click="handleEidt(item)">编辑</button>
    </li>
</template>
<script>
export default {
    name: 'MyItem',
    props: ['item'],
    methods: {

        handleCheck(id) {
            this.$bus.$emit('checkTodo', id);
            // this.isActive = !this.item.done;
            // console.log(this.isActive)
            console.log(this.$bus)

        },
        handleCheck1(id) {
            this.$bus.$emit('handleMove', id);
        },
        handleEidt(item) {
            if (item.hasOwnProperty('isEdit')) {
                item.isEdit = true;
            }
            else {
                this.$set(item, 'isEdit', true)
            }
            this.$nextTick(function () {
                this.$refs.inputTitle.focus()
            })
        },
        upData(e, item) {
            item.isEdit = false;
            const newvalue = e.target.value;
            this.$bus.$emit('handupData', item.id, newvalue)

        }

    }

}
</script>
<style class="scoped">
.merge {
    color: black;
}

.more {
    text-decoration: line-through;
    color: rgb(80, 80, 80)
}

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: 9px;
    /* border: none; */
    border-radius: 5px;
    text-align: center;
}

li:before {
    content: initial;
}

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

li:hover {
    background-color: rgb(255, 255, 255);
}

li:hover button {
    display: block;
    /* background-color: rgb(245, 115, 115); */
}
</style>






//myheader
<template>
    <input type="text" placeholder="请输入您的任务名称" @keyup.enter="add" />
</template>
<script>

import { nanoid } from 'nanoid'
export default {
    name: 'MyHeader',
    data() {

    },
    methods: {
        add(e) {
            if (e.target.value === '') {
                return
            };
            const anitem = { id: nanoid(), todo: e.target.value, done: false };
            this.$bus.$emit('recieve', anitem);
            console.log(this.$bus.recieve);
            e.target.value = '';

        }
    },
    // props: ['recieve']
}
</script>
<style scoped>
/*header*/
input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}

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>