初学Vue(全家桶):todoList案例进阶版-全局总线实现组件间通信

初学Vue

todoList案例-全局事件总线实现组件间通信

前面学过的几种实现方式:

页面效果

请添加图片描述

代码实现

  • main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    
    
  render: h => h(App),
  beforeCreate(){
    
    
    // 安装全局事件总线
    Vue.prototype.$bus=this
  }
}).$mount('#app')

  • App.vue
<template>
  <div id="app">
    <div class="todo-container">
      <div class="todo-wrap">
        <!-- 输入框部分 -->
        <!-- 给组件标签绑定方法 -->
        <MyHeader @addTodo="addTodo"></MyHeader>
        <!-- /输入框部分 -->

        <!-- 列表部分 -->
        <MyList 
          :todos="todos" 
        ></MyList>
        <!-- /列表部分 -->

        <!-- 底部部分 -->
        <MyFooter
          :todos="todos"
          @checkAllTodo="checkAllTodo"
          @clearAllTodo="clearAllTodo"
        ></MyFooter>
        <!-- /底部部分 -->
      </div>
    </div>
  </div>
</template>

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

export default {
      
      
  name: "App",
  components: {
      
      
    MyHeader,
    MyFooter,
    MyList,
  },
  data() {
      
      
    return {
      
      
      todos:JSON.parse(localStorage.getItem("todos")) || [],
    };
  },
  methods:{
      
      
    // 添加一个列表事项
    addTodo(e){
      
      
      // console.log("我是App组件,我收到了数据",x)
      this.todos.unshift(e)
    },
    // 勾选or取消勾选todo事项
    checkTodo(id){
      
      
      // 拿到todos中的每一项,找到对应id的那一项
      this.todos.forEach((todo)=>{
      
      
        if(todo.id === id)
          todo.done = !todo.done
      })
    },
    // 点击删除一个todo
    deleteTodo(id){
      
      
      this.todos = this.todos.filter((todo)=>{
      
      
        return todo.id !== id
      })
    },
    // 控制全选或全不选
    checkAllTodo(done){
      
      
      this.todos.forEach((todo)=>{
      
      
        todo.done = done;
      })
    },
    // 清除已经完成的事项
    clearAllTodo(){
      
      
      this.todos = this.todos.filter((todo)=>{
      
      
        return !todo.done
      })
    }
  },
  watch:{
      
      
    // 监视todos,当todos发生改变时,触发
    todos:{
      
      
      handler(value){
      
      
        localStorage.setItem("todos",JSON.stringify(value))
      }
    }
  },
  mounted(){
      
      
    this.$bus.$on('checkTodo',this.checkTodo)
    this.$bus.$on('deleteTodo',this.deleteTodo)
  },
  beforeDestroy(){
      
      
    this.$bus.$off('checkTodo')
    this.$bus.$off('deleteTodo')
  }
}
</script>

<style>
/* 整体样式 */
body {
      
      
  background: rgba(230, 241, 245, 0.816);
}

.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>

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

<script>
import {
      
      nanoid} from "nanoid"
export default {
      
      
  name: "MyHeader",
  methods:{
      
      
    add(event){
      
      
      // 如果输入为空,则结束方法,不添加任何数据
      if(!event.target.value){
      
      
        return alert("输入不能为空")
      }
      // 使用能生成唯一标识的库nanoid或者uuid生成id,这里用nanoid(轻量级的uuid)
      // 将用户的输入包装成一个todo对象
      const todoObj = {
      
      id:nanoid(),title:event.target.value,done:false}
      this.$emit("addTodo",todoObj)
      event.target.value='' // 回车后清空输入框中的内容
    }
  },
};
</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>
  • MyList.vue
<template>
  <div>
    <ul ul="todo-main">
        <!-- 显示todos数组中所有的事项,用id作为key -->
      <MyItem 
      v-for="todoObj in todos" 
      :key="todoObj.id"
      :todo="todoObj"
      ></MyItem>
    </ul>
  </div>
</template>

<script>
import MyItem from "./MyItem.vue";

export default {
      
      
  name: "MyList",
  components: {
      
       MyItem },
  props:['todos']
};
</script>

<style scoped>
/*main*/
.todo-main {
      
      
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}
</style>
  • MyItem.vue
<template>
  <div>
    <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>
  </div>
</template>

<script>
export default {
      
      
  name: "MyItem",
  props:['todo'], // 这里todo是个对象,由MyList传过来的,存储了id、title、done属性
  methods:{
      
      
    handleCheck(id){
      
      
      // 通过事件总线触发事件checkTodo,并传入id
      this.$bus.$emit('checkTodo',id)
    },
    // 删除事项
    handleDelete(id){
      
      
      if(confirm("确定删除吗?")){
      
      
        // 通过事件总线触发事件deleteTodo,并传入id
        this.$bus.$emit('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:hover {
      
      
  background-color: rgb(226, 219, 219);
  cursor: pointer;
}

li:hover button{
      
      
  display:block
}

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;
}
</style>
  • MyFooter.vue
<template>
  <div>
      <!--当列表中没有事项时,v-show用于隐藏Footer部分-->
    <div class="todo-footer" v-show="total"> 
      <label>
        <input type="checkbox" :checked="isAll" @change="checkAll"/>
      </label>
      <span> <span>已完成{
   
   {doneTotal}}</span>/ 全部{
   
   {total}}</span>
      <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
  </div>
</template>

<script>
export default {
      
      
  name: "MyFooter",
  props:['todos'],
  computed:{
      
      
    total(){
      
      
      return this.todos.length
    },
    doneTotal(){
      
      
      // 高端一点的方法
      // return this.todos.reduce((pre,current)=>{
      
      
      //   return pre + (current.done ? 1 : 0)
      // },0)

      // 普通的方法
      let i=0
      this.todos.forEach((todo)=>{
      
      
        if(todo.done) i++
      })
      return i
    },
    isAll:{
      
      
      // 看全选框是否勾选
      get(){
      
      
        return this.doneTotal === this.total && this.total>0
      },
      // isAll被修改时set被调用
      set(value){
      
      
        // this.checkAllTodo(value)
        this.$emit("checkAllTodo",value);
      }
    }
  },
  methods:{
      
      
    // 全选或取消全选
    checkAll(event){
      
      
      // this.checkAllTodo(event.target.checked)
      this.$emit("checkAllTodo",event.target.checked)
    },
    // 清除已完成事项
    // clearAll(){
      
      
    //   this.clearAllTodo()
    // }
    clearAll(){
      
      
      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>

这里的App.vue是MyHeader.vue,MyList.vue,MyFooter的父组件,是MyItem的爷组件,这里爷孙之间的通信(App.vue和MyItem)是通过事件总线实现的。

猜你喜欢

转载自blog.csdn.net/lalala_dxf/article/details/125200491