Vue TodoList案例

发布于:2025-07-25 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、总结TodoList案例

  1. 组件化编码流程:

    (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

    (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

            (1.一个组件在用:放在组件自身即可。

            (2. 一些组件在用:放在他们共同的父组件上(状态提升)。

    (3).实现交互:从绑定事件开始。

  2. props适用于:

    (1).父组件 ==> 子组件 通信

    (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

  4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

二、Components

        1.App.vue:

<template>
    <div class="add-todo">
        <input class="add-input" v-model="todo" @keydown.enter="loseFocus" ref="addInput" @blur="add">
    </div>
</template>

<script>
import { nanoid } from 'nanoid'

    export default {
        name:'Add',
        data() {
            return {
                todo:'',
            }
        },
        props:['addTodo'],
        methods:{
            loseFocus(){
                this.$refs.addInput.blur()
            },
            add(){
                const todoObj = {id:nanoid(),text:this.todo,is_check:false}
                this.addTodo(todoObj)
                this.todo=''
            },
            
        }
    }
</script>

<style scoped lang="less">
    .add-todo{
        padding-bottom: 10px;
    }
    .add-input {
        width: 350px;
        height: 40px;
        padding: 0 10px;
        font-size: 16px;
        border: 1px solid #000;
        border-radius: 4px;
    }
</style>

        2.allTodo.vue:

<template>
    <div class="all-todo" v-show="todoList.length">
        <input type="checkbox" v-model="allCompeled"> 
        <!-- <input type="checkbox" @change="toggleTodoAll" :checked="allCompeled">  -->
        <span>已完成{{ finish }}/全部{{ todoList.length }}</span>
        <!-- <button class="todo-item-delete" :style="{'display': show}" @click="todoDelete">清除已完成</button> -->
        <button class="todo-item-delete" v-show="show" @click="todoDelete">清除已完成</button>
    </div>
</template>

<script>
    export default {
        name:'allTodo',
        props:['todoList','allTodoDelete','toggleAll'],
        computed:{
            allCompeled:{
                get(){
                    return this.todoList.length > 0 && this.todoList.every(item => item.is_check)
                },
                set(value){
                    this.toggleAll(value)
                }
            },
            finish(){
                // return this.todoList.filter(item => item.is_check).length
                return this.todoList.reduce((pre,current) => pre+(current.is_check?1:0),0)
            },
            show(){
                return this.finish>0 
                // return this.finish>0 ? 'block' : 'none'
            }
        },
        methods: {
            // toggleTodoAll(e){
            //     console.log(e.target.checked)
            //     this.toggleAll(e.target.checked)
            // },
            todoDelete(){
                this.allTodoDelete()
            }
        },
    }
</script>

<style scoped lang="less">
    .all-todo{
        display: flex;
        height: 30px;
        padding-top: 10px;
    }
    .todo-item-delete{
        // display: none;
        height: 30px;
        background-color: red;
        color: #fff;
        border-radius: 4px;
        cursor: pointer;
        margin-left: auto;
        border: none;

        &:hover {
            background-color: #f5222d;
        }
    }
</style>

        3.todoList.vue:

<template>
    <div class="todo-list">
        <todoItem class="todo-item" v-for="(item) in todoList" 
            :key="item.id" 
            :item="item" 
            :todoDelete="todoDelete"
            :itemCheckChange="itemCheckChange"
        />
    </div>
</template>

<script>
    import todoItem from './todoItem.vue';

    export default {
        name:'todoList',
        props:['todoList','todoDelete','itemCheckChange'],
        components:{
            todoItem
        }
    }
</script>

<style scoped>
    .todo-list{
        width: 350px;
        display: flex;
        flex-direction: column;
        border: 1px solid #ccc;
        border-radius: 4px;
        overflow: hidden; /* 防止边框圆角被内容覆盖 */
        padding: 0 10px;
    }
</style>

        4.todoItem.vue:

<template>
    <div >
        <!-- <input type="checkbox" v-model="item.is_check"> -->
        <input type="checkbox" :checked="item.is_check" @change="checkChange(item.id)">
        <span>{{ item.text }}</span>
        <button class="todo-item-delete" @click="todosDelete(item.id)">删除</button>
    </div>
</template>

<script>
    export default {
        name:'todoItem',
        props:['item','todoDelete','itemCheckChange'],
        methods: {
            todosDelete(id){
                this.todoDelete(id)
            },
            checkChange(id){
                this.itemCheckChange(id)
            }
        },
    }
</script>

<style scoped lang="less">
    .todo-item{
        height: 30px;
        display: flex;
        align-items: center;
        padding: 10px 15px;
        border-bottom: 1px solid #eee;

        &:hover {
            background-color: #e8e8e8;
            .todo-item-delete {
                display: block;
            }
        }
    }
    .todo-item-delete{
        display: none;
        height: 30px;
        background-color: red;
        color: #fff;
        border-radius: 4px;
        cursor: pointer;
        margin-left: auto;
        border: none;

        &:hover {
            background-color: #f5222d;
        }
    }
    .todo-item:last-child {
        border-bottom: none;
    }
</style>

        5.App.vue:

<template>
  <div class="centent">
    <Add 
      :todoList="todoList" 
      :addTodo="addTodo"
    />
    <todoList 
      :todoList="todoList" 
      :todoDelete="todoDelete" 
      :itemCheckChange="itemCheckChange"
    />
    <allTodo 
      :todoList="todoList" 
      :allTodoDelete="allTodoDelete"
      :toggleAll="toggleAll"
    />
  </div>
</template>

<script>
    import Add from './Components/Add.vue'
    import todoList from './Components/todoList.vue';
    import {nanoid} from 'nanoid'
    import allTodo from './Components/allTodo.vue';

    export default {
      name:'App',
      data() {
        return {
          todoList:[
            {
              id:nanoid(),
              text:'test',
              is_check:false
            }
          ]
        }
      },
      components:{Add,todoList,allTodo},
      methods:{
        addTodo(x){
          if(x.text.trim()){
            this.todoList.unshift(x)
          }
        },
        allTodoDelete(){
          this.todoList = this.todoList.filter(item => !item.is_check)
        },
        todoDelete(id){
          this.todoList = this.todoList.filter(item=>item.id!=id)
        },
        itemCheckChange(id){
          this.todoList.forEach(item=>{
            if(item.id === id) item.is_check =!item.is_check
          })
        },
        toggleAll(value){
          this.todoList.forEach(item => {
            item.is_check=value
          })
        }
      },
    }
</script>

<style scoped>
  .centent{
    width: 400px;
    margin: auto;
    border: 1px  solid #000;
    min-height: 50px;
    padding: 20px;
  }
</style>


网站公告

今日签到

点亮在社区的每一天
去签到