1、项目结构

index.html引入

<link rel="stylesheet" type="text/css" href="./css/bootstrap.min.css">

2、App.vue

<template>
  <div class="container">
    <div class="row">
      <div class="col-xs-6 col-xs-push-3 header-site">
        <!-- 引入子组件 -->
        <Header ref="header" />
        <List :todos="todos" @changeStatusByIndex="changeTodoStatus" :delTodo="delTodo" />
        <Footer :todos="todos" :delDone="delDone" />
      </div>
    </div>
  </div>
</template>

<script>
// 引入子组件
import Header from "./components/Header.vue";
import List from "./components/List.vue";
import Footer from "./components/Footer.vue";
export default {
  components: {
    //定义主件
    Header,
    List,
    Footer
  },
  data() {
    return {
      // 代办事情从sessionStorage获取
      todos: JSON.parse(sessionStorage.getItem("myTodos")) || []
    };
  },
  mounted() {
    //给子组件提供获取该方法的途径
    this.$refs.header.$on("addTodo", this.addTodo);
  },
  watch: {
    todos: {
      deep: true,
      handler(newVal) {
        //添加代办的事情存到sessionStorage
        sessionStorage.setItem("myTodos", JSON.stringify(newVal));
      }
    }
  },
  methods: {
    //添加代办事件
    addTodo(todo) {
      this.todos.unshift(todo);
    },
    //删除已完成事件
    delDone() {
      this.todos = this.todos.filter(todo => !todo.complete);
    },
    //改变代办事件状态,是否已完成
    changeTodoStatus(index, completed) {
      this.todos[index].complete = completed;
    },
    //删除代办事件
    delTodo(index) {
      this.todos.splice(index, 1);
    }
  }
};
</script>

<style>
.header-site {
  margin-top: 20px;
  border: 1px solid #888888;
  border-radius: 5px;
  padding: 20px 10px;
}
</style>

3、Header.vue

<template>
  <p>
    <input class="form-control" @keyup.enter="addItem" v-model="title" placeholder="请输入待办事项" />
  </p>
</template>

<script>
export default {
  components: {},
  data() {
    return {
      //绑定输入框的值
      title: ""
    };
  },
  methods: {
    //添加代办事件
    addItem() {
      let title = this.title.trim();
      if (!title) {
        alert("代办事项不能为空");
        return;
      }
      let todo = {title:title,complete:false}
      //获取父组件的值
      this.$emit("addTodo", todo);
      this.title = "";
    }
  }
};
</script>

<style>
</style>

4、List.vue

<template>
    <ul class="list-group">
        <li v-for="(todo, index) in todos" @mouseenter="mouseEnterTodo(index)" @mouseleave="mouseLeaveTodo" 
                        :key="index" :class="['list-group-item', {'todo-bg':index==currentIndex}]">
            <input v-model="todo.complete" @change="changeTodoStatus(index, todo.complete)"
                        type="checkbox"> {{todo.title}}
            <button @click="delItem(index)"
                    v-show="index==currentIndex" class="btn btn-danger btn-xs pull-right">删除</button>
        </li>
    </ul>
</template>

<script>
export default {
    props:{
        //获取父组件的属性和方法
        todos:Array,
        delTodo:Function
    },
  components: {
    
  },
  data(){
      return {
          //记录鼠标进入和离开状态,默认-1离开
        currentIndex:-1
      }
  },
  methods:{
      //鼠标进入时
      mouseEnterTodo(index){
            this.currentIndex = index
      },
      //鼠标离开时
      mouseLeaveTodo(){
          this.currentIndex = -1
      },
      //改变代办事件状态
      changeTodoStatus(index, completed){
          //获取父组件的方法
        this.$emit('changeStatusByIndex',index,completed)
      },
      //删除代办事件
      delItem(index){
          //获取父组件的方法
          this.delTodo(index)
      }
  }
};
</script>

<style>
    .todo-bg{
        background-color: bisque;
    }
</style>

5、Footer.vue

<template>
  <div v-show="todos.length != 0">
    <input type="checkbox" v-model="selectAllorNot" />
    已完成 {{completedTodoNum}} / 总共 {{todos.length}}
    <button
      @click="delDoneItems"
      class="btn btn-danger btn-xs pull-right"
    >删除已完成</button>
  </div>
</template>

<script>
export default {
  props: {
    //获取父主键的属性和方法
    todos: Array,
    delDone: Function
  },
  computed: {
    //计算出已经完成的todo的数量
    completedTodoNum: function() {
      let todos = this.todos;
      return todos.reduce(
        (total, current) => total + (current.complete ? 1 : 0),
        0
      );
    },
    selectAllorNot: {
      get() {
        // todos只要有一个是没选中,那么最终状态是不选中。
        let todos = this.todos;
        let flag = true;
        todos.forEach(element => {
          if (!element.complete) {
            flag = false;
            return flag;
          }
        });
        return flag;
      },
      //改变selectAllOrNot的状态,保证每个todo和它的状态一致就可以了。
      set(value) {
        let todos = this.todos;
        todos.forEach(element => {
          // value的值为true或者false
          element.complete = value;
        });
      }
    }
  },
  data() {
    return {};
  },
  methods: {
    //获取父组件的方法
    delDoneItems() {
      this.delDone();
    }
  },
  components: {}
};
</script>

<style>
</style>