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

Vue中 TodoList 示例: 浏览器本地存储、自定义事件、全局数据总线


Vue中 TodoList 示例: 浏览器本地存储、自定义事件、全局数据总线

1:使用说明:

总结TodoList案例

  1. 组件化编码流程:

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

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

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

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

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

  2. props适用于:

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

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

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

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



webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value'); 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

      ​ 该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key');

      ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      ​ 该方法会清空存储中的所有数据。

  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。


组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/>  <Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      
      
      <Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) }
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!



全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

    
    
    new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... })
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      
      
      methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。















2:界面效果

image

image

image






3:代码结构

image





4:代码内容


vue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false /*关闭语法检查*/
})



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;
        }
   });



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 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);

    },
    beforeDestroy(){
       this.$bus.$off('onChangeTodosOfDoneInfo');
       this.$bus.$off('deleteTodosById');

    },
    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
      deleteTodosById(id){
        this.todos=this.todos.filter((todo) =>{
           return todo.id !==id;
        });
      },
      // 全选  或者  取消全选
      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:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>


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>


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>


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>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="delTodosInfo(todo.id)">删除</button><!-- style="display:none"  控制按钮隐藏 -->
    </li>
</template>
<script>
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);
            }
        }

    }
}
</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>



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>









posted @ 2023-01-09 22:25  一品堂.技术学习笔记  阅读(111)  评论(0编辑  收藏  举报