/**PageBeginHtml Block Begin **/ /***自定义返回顶部小火箭***/ /*生成博客目录的JS 开始*/ /*生成博客目录的JS 结束*/

Vue 中的 todolist 的修改todo事件名称操作:消息订阅_全局事件总线_nextTick 定时执行事件、本地储存


Vue  中的 todolist  的修改todo事件名称操作:消息订阅_全局事件总线_nextTick 定时执行事件、本地储存


1:nextTick  事件说明


nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。


image


2:界面演示


image

image

image




3:代码示例


3-1 : 代码结构


image



3-2:ToItem.vue


<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done"  @change="handleCheck(todo.id)" />
            <!-- 下面的方案可以 替代 @change="handleCheck(todo.id)" 作功能实现,不推荐。因为违反原则,修改里props里的对象数据,vue没有监测到 -->
            <!-- <input type="checkbox"  v-model="todo.done" /> --><!-- 一般不建议这么使用  v-model="todo.done"  调用 props传递过来的对象里的数据信息进行数据操作改变-->
            <span v-show="!todo.isEdit">{{todo.title}}</span>
            <input type="text"
                  v-show="todo.isEdit"
                  :value="todo.title"
                  @blur="handleBlur(todo,$event)"
                  ref="inputTitle"
            >
        </label>
        <button class="btn btn-danger" @click="delTodosInfo(todo.id)">删除</button><!-- style="display:none"  控制按钮隐藏 -->
        <button class="btn btn-edit"  v-show="!todo.isEdit" @click="handleEdit(todo)" >编辑</button><!-- style="display:none"  控制按钮隐藏 -->

    </li>
</template>

<script>
/**
 * 引入库管理说明:
 *  一般自己写的组件都放在下面
 *  引入第三方的组件 都放在上面
 */
// 导入消息订阅组件:
import pubsub from 'pubsub-js';



export default {
    name: 'ToItem',
    // 说明接收todo对象
    props:['todo'],  /*  作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作:  ,'onChangeTodosOfDoneInfo','deleteTodosById' */
    mixins: [],
    components: {},
    data() {
        return {
        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {
      //勾选 或者 取消勾选
        handleCheck(id){
            //通知
            // this.onChangeTodosOfDoneInfo(id);
            this.$bus.$emit('onChangeTodosOfDoneInfo',id);
        },
        // 删除
        delTodosInfo(id){
            if(confirm('确定删除吗?')){
              console.log(id);
              // this.deleteTodosById(id);

              //  演示消息订阅: 注释掉全局事件总线
              // this.$bus.$emit('deleteTodosById',id);
              console.log("开始消息传递"+id );
              pubsub.publish('deleteTodosById',id);

            }
        } ,
        //编辑
        handleEdit(todo){
          if(todo.hasOwnProperty('isEdit')){
            console.log('  再次编辑   isEdit   属性 ');
            todo.isEdit  =  true;
          }else{
            console.log('第一次增加    isEdit   属性 ');
            this.$set(todo,'isEdit',true);
          }
          /*
          * 处理自动获取input 焦点事件失效的问题
          *   知识点:  nextTick
          *  语法:this.$nextTick(回调函数)
          *  作用:在下一次 DOM 更新结束后执行其指定的回调。
          *  什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
          */
          /*
           // 方案一:采用定时任务设置事件外调
          setTimeout (() =>{
            this.$refs.inputTitle.focus();//获取焦点
          },200);
           */
          // 方案二:采用 vue的特定的Api 函数进行回调设置
          this.$nextTick(function(){
            this.$refs.inputTitle.focus();//获取焦点
          });
          console.log(todo);

        },
        //光标失去焦点事件,关闭输入框、保存更新修改数据
        handleBlur(todo,event){
          todo.isEdit  =  false;
          //  使用事件总线 ,回调 updateTodoById 事件 插入 todo.id 及  imput 输入框修改
          if(!event.target.value.trim()){
             return alert('输入不能为空 !');
          }else{
            this.$bus.$emit('updateTodoById',todo.id,event.target.value);
          }


        }

    }
}
</script>

<style scoped lang="less">
/*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-3:ToList.vue


<template>
    <ul class="todo-main">
        <ToItem
            v-for="todoObj in todos"
            :key="todoObj.id"
            :todo="todoObj"
        />  <!--
                作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作:
                :onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo"
                :deleteTodosById="deleteTodosById"
            -->
    </ul>
</template>

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


export default {
    name: 'ToList',
    mixins: [],
    components: {
        ToItem: ToItem
    },
    props: ['todos'], /*  作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作:, 'onChangeTodosOfDoneInfo','deleteTodosById' */
    data() {
        return {

        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {}
}
</script>

<style scoped lang="less">
/*main*/
.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}
</style>



3-4 :ToListFooter.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: 'ToListFooter',

    mixins: [],
    components: {},
    props: ['todos','checkAllTodos','ckearAllTodo'],
    data() {
        return {
        }
    },
    computed: {
      // 统计总数
      total(){
        return this.todos.length;
      },
      // 统计勾选数量
      doneTotal(){
        // 计数统计实现方式一:
          let total=0;
          this.todos.forEach((todo)=>{
                if(todo.done){
                  total++;
                }
          } );
        // return total; 
        //计数统计实现方式二: 
         const td= this.todos.reduce((index,todo)=>{
            return index+(todo.done?1:0);
         },0);
         console.log("返回已完成结果值:",td);
        // 返回结果值
        //  return td;

         //计数统计实现方式三:
        return this.todos.reduce((index,todo)=>index+(todo.done?1:0),0);
      },
      // 操作全选,取消全选
      /* isAll(){ return this.doneTotal === this.total && this.total > 0; } */
      isALL:{
       get(){
         return this.doneTotal === this.total && this.total > 0;
       },
       set(checked){
          return this.checkAllTodos(checked);
       }
      }
    },
    watch: {},
    created() { },
    mounted() { },
    methods: {
      // 全选,或者 取消全选
      checkAll(event){
          this.checkAllTodos(event.target.checked);
      },
      // 清除已完成事项
      clearAll(){
        this.ckearAllTodo();
      }
     }

}
</script>

<style scoped lang="less">
/*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>


3-5:ToListTop.vue



<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title"  @keyup.enter="addTodoInfo"/>
    </div>
</template>

<script>

import {nanoid} from 'nanoid'

export default {
    name: 'ToListTop',
    mixins: [],
    components: {},
    props: ['addTodoInfos'],/* addTodoInfos:接收到父组件的传递过来的方法 ,不同组件的相互调用的方法不能重名*/
    data() {
        return {
            title:""
        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {
        addTodoInfo(event){
            // 校验数据
            if(this.title.trim()=="" /* event.target.value */){
                return  alert("输入内容不能为空");
            }
            const todo={id:nanoid(),title:this.title/* event.target.value */,done:false};
            console.log(todo);
            this.addTodoInfos(todo);/* 将子组件的数据通过调用父组件的方式传递给父组件里的todos对象里 */
            /* event.target.value=""; */
            this.title=""; //清空数据
        }
    }
}
</script>

<style scoped lang="less">
/*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>


3-6:App.vue


<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <ToListTop :addTodoInfos="addTodoInfos" /><!-- :addTodoInfos="addTodoInfos" 跨组件传递方法并符合数据对象 -->
        <ToList :todos="todos"
        />  <!-- :todos="todos" 跨组件进行传值;:onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo":组件间数据方法的逐层传递 -->
        <!--    作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作
                :onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo"
                :deleteTodosById="deleteTodosById"  -->
        <ToListFooter  :todos="todos" :checkAllTodos="checkAllTodos" :ckearAllTodo="ckearAllTodo" />
      </div>
    </div>
  </div>
</template>

<script>
/**
 * 引入库管理说明:
 *  一般自己写的组件都放在下面
 *  引入第三方的组件 都放在上面
 */
// 导入消息订阅组件:
import pubsub from 'pubsub-js';


import ToListTop from './components/ToListTop.vue';
import ToList from './components/ToList.vue';
import ToListFooter from './components/ToListFooter.vue';


export default {
  name: 'App',
  components: {
    ToListTop: ToListTop,
    ToList: ToList,
    ToListFooter: ToListFooter
  },
  data() {
    return {
      todos: JSON.parse(localStorage.getItem('todos')) ||[]
    }
  },
    computed: {},
    watch: {
      todos:{
         deep:true,//深度监控
         handler(value){
            localStorage.setItem('todos',JSON.stringify(value));
         }
      }
    },
    created() { },
    mounted() {

       this.$bus.$on('onChangeTodosOfDoneInfo',this.onChangeTodosOfDoneInfo);
      //  演示消息订阅: 注释掉全局事件总线
      //  this.$bus.$on('deleteTodosById',this.deleteTodosById);
      this.pubSubId=pubsub.subscribe('deleteTodosById',this.deleteTodosById);
      // 增加修改标题的名称的数据更新事件
      this.$bus.$on('updateTodoById',this.updateTodoById);
    },
    beforeDestroy(){
       this.$bus.$off('onChangeTodosOfDoneInfo');
       //  演示消息订阅: 注释掉全局事件总线
       //  this.$bus.$off('deleteTodosById');
       pubsub.unsubscribe(this.pubSubId);
       // 销毁的
       this.$bus.$off('updateTodoById');

    },
    methods: {
      // 添加1个todaoObj
      addTodoInfos(todoObj){
         console.log("App组件,接收到数据",todoObj);
         this.todos.unshift(todoObj);
      },
      // 勾选或者取消勾选1个todoObj
      onChangeTodosOfDoneInfo(id){
          this.todos.forEach((todo)=>{
             if(todo.id==id){
               todo.done =!todo.done; //赋值取反操作
             }
          });
      },
      //删除一个todo
      /*
       *  _, :表示参数占位
       * 注意:通过 pubsub-js 第三方插件实现消息订阅,消息接收方的回调参数 是由2个的 第一个为消息名称 msgName ,第二个参数为 消息数据msgData
       */
      deleteTodosById(_,id){
        this.todos=this.todos.filter((todo) =>{
          console.log("接收到 消息订阅的内容"+id);
           return todo.id !==id;
        });
      },
      //更新 todo  的标题信息
      updateTodoById(todoId ,todoTitleName){
        this.todos.forEach((todo)=>{
             if(todo.id==todoId){
               todo.title =todoTitleName; //更新title 数据
             }
          });
      },
      // 全选  或者  取消全选
      checkAllTodos(done){
        this.todos.forEach((todo)=>{
            todo.done=done;
        });
      },
      // 清除所有已经完成的todo事项
      ckearAllTodo(){
        this.todos=this.todos.filter((todo)=>{
          return !todo.done;
        });
      }
    }

}
</script>
<!--
  1:指定Css 样式的编写方式
  less 最大特点:内容可以嵌套着写

 -->
<style lang="less">
.divCss {
  background-color: chocolate;
  margin: auto;
  padding: 20px;

  .h1Css {
    font-size: 36px;
    color: white;
  }
}

/*base*/
body {
  background: #fff;
}

.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-edit {
  color: #fff;
  background-color: #307fe7;
  border: 1px solid #0c5bee;
  margin-right:5px;
}

.btn-edit:hover {
  color: #fff;
  background-color: #0968f7;
}



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



3-7:main.js


//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue生产提示
Vue.config.productionTip=false;

// 创建Vm
const vm = new Vue(  {
        el:'#app',
        render: (h) => h(App),
        //添加全局事件总线对象
        beforeCreate(){
             Vue.prototype.$bus=this;
        }
   });
















posted @ 2023-03-06 23:59  一品堂.技术学习笔记  阅读(55)  评论(0编辑  收藏  举报