天天看点

Vue中组件化编码 完成任务的添加、删除、统计、勾选需求(实战练习三完结)

上一个章节实现数据在组件之间的传递 。这一章主要是完成添加任务到任务栏、删除任务栏、统计任务完成情况。主要还是参数在各个组件之间的传递。

文章目录

  • ​​1、实现的效果演示​​
  • ​​2、需求实现的大致流程​​
  • ​​3 、代码(这里只给出主要部分代码、详细代码请看第一章节)​​
  • ​​3.1 App.vue(省略了样式)​​
  • ​​3.2 TheList.vue (省略了样式)​​
  • ​​3.3 TheItem.vue(样式改进:删除按钮只有停留在对应行才会显示出来)​​
  • ​​3.4 TheHeader.vue (省略了样式)​​
  • ​​3.5 TheFooter.vue (省略了样式)​​
  • ​​4、实现的效果​​

1、实现的效果演示

Vue组件实战应用完成任务功能演示

2、需求实现的大致流程

  • 添加任务到任务栏:需要考虑TheHeader中的数据怎样传入到TheList组件中
  • 删除一个任务:需要拿到待删除的任务主键标识、然后在数组对象中删除(遍历的形式)
  • 勾选任务:根据对象中的勾状态、取反
  • 初次加载勾选已完成任务:在checkbox中 根据任务的状态展示完成情况(checked)
  • 统计任务完成情况:根据对象中的任务完成状态 遍历数组。累加计算

3 、代码(这里只给出主要部分代码、详细代码请看第一章节)

3.1 App.vue(省略了样式)

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <TheHeader :addTodo="addTodo" />
        <TheList
          :todos="todos"
          :checkTodo="checkTodo"
          :deleteTodo="deleteTodo"
        />
        <TheFooter
          :todos="todos"
          :checkAllTodo="checkAllTodo"
          :clearAllTodo="clearAllTodo"
        />
      </div>
    </div>
  </div>
</template>

<script>import TheHeader from "./components/TheHeader";
import TheList from "./components/TheList";
import TheFooter from "./components/TheFooter.vue";

export default {
  name: "App",
  components: { TheHeader, TheList, TheFooter },
  data() {
    return {
      //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
      todos: [
        { id: "001", title: "吃饭", done: true },
        { id: "002", title: "睡觉", done: false },
        { id: "003", title: "打豆豆", done: true },
      ],
    };
  },
  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;
      });
    },
  },
};</script>      

3.2 TheList.vue (省略了样式)

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

<script>import TheItem from "./TheItem";

export default {
  name: "TheList",
  components: { TheItem },
  //声明接收App传递过来的数据,其中todos是自己用的
  props: ["todos", "checkTodo",'deleteTodo'],
};</script>      

3.3 TheItem.vue(样式改进:删除按钮只有停留在对应行才会显示出来)

<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: "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>      

3.4 TheHeader.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: "TheHeader",
  //接收从App传递过来的addTodo
    props:['addTodo'],
  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.addTodo(todoObj);
      //清空输入
      this.title = "";
    },
  },
};</script>      

3.5 TheFooter.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:'TheFooter',
    props:['todos','checkAllTodo','clearAllTodo'],
    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)
        }
      }
    },
    methods: {
      /* checkAll(e){
        this.checkAllTodo(e.target.checked)
      } */
      //清空所有已完成
      clearAll(){
        this.clearAllTodo()
      }
    },
  }</script>      

4、实现的效果

继续阅读