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

Vue中 TodoList 示例: 浏览器本地存储、自定义事件



Vue中 TodoList 示例: 浏览器本地存储、自定义事件


1:界面展示情况



image


2:源代码


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



App.vue


<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <ToListTop @addTodoInfos="addTodoInfos" /><!-- @addTodoInfos="addTodoInfos"  给toListTop 组件添加一个自定义事件,事件名为addTodoInfos,事件的回调为:addTodoInfos -->
        <ToList :todos="todos"
                :onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo"
                :deleteTodosById="deleteTodosById"
        />  <!-- :todos="todos" 跨组件进行传值;:onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo":组件间数据方法的逐层传递 -->
        <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() { },
    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: {},
    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.$emit('addTodoInfos',todo);//绑定自定义事件:addTodoInfos  传值给父组件  
            /* 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);
        },
        // 删除
        delTodosInfo(id){
            if(confirm('确定删除吗?')){
              console.log(id);
              this.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'],
    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.$emit('checkAllTodos',checked);//父组件传递子组件的自定义事件
       }
      }
    },
    watch: {},
    created() { },
    mounted() { },
    methods: {
      // 全选,或者 取消全选
      checkAll(event){
          this.$emit('checkAllTodos',event.target.checked);//父组件传递子组件的自定义事件
      },
      // 清除已完成事项
      clearAll(){
        this.$emit('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 @ 2022-12-22 20:12  一品堂.技术学习笔记  阅读(68)  评论(0编辑  收藏  举报