Vue2(八):TodoList案例

news2024/11/14 6:27:40

一、整体思路

1.分析结构

我们对大盒子拆分,分成header、list、footer,但是list最好也进行拆分,因为它里面的每个小盒子结构一样就是字不一样,可以用一个组件多次调用完成,所以分成app>header、list、footer>item

2.实现静态页面效果

抽取组件形成静态页面,当前的item个数还是我们自己写死的,里面的内容都是xxx

3.展示动态数据

现在我们要把里面的内容改成吃饭、睡觉、抽烟、喝酒,存到list.vue的data中(以数组内{}的形式,多个对象写成数组,每个对象不光有title还有id以及勾选情况用{})

<myItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"/>

item.vue中只有一个组件,我们用v-for进行遍历list中数组对象的个数,每一个都用item小盒子装起来。传递list中的数据给item,在item里用props:['todo'],就把数组对象传过去了。

然后我们就要实现动态的给这些被选中的爱好打勾了

 <input type="checkbox" :checked="todo.done"/>

如果后面是true,那么就拥有checked选项

4.添加todo功能

首先得在header的input表单里设置读取键盘输入的内容

<input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/>

然后我们得把用户输入的名字包装成一个todo对象

小tips:如果有服务器的话,应该是由服务器给数据生成id,但是我们这里没有

id可以用uuid包来生成全球唯一的id,nanoid是uuid的精简版,省空间

add(event){
      const todoObj={id:nanoid(),title:event.target.value,done:false}
      console.log(todoObj)
    }

接下来就把这个对象放进数组的前方

但是我们目前学过的不同组件传数据只能是父亲往儿子传,然后用儿子的标签里面写数据再用props传,但是我们写进去的数据在hearer里,跟list是兄弟

解决方法:我们把原本list的那些数组里的对象写在app里,也就是他俩的父亲上,list用props方法从父亲身上拿到,header就把新添加的数组对象给给父亲

实现儿子传给父亲:就是父亲提前给儿子一个函数,然后儿子在自己那里调函数

app父亲:

methods:{
    receive(x){
      this.todos.unshift(x)
    }
  }
//加入数组中
 <myHeader :receive="receive"/>

hearder儿子:

props:['receive'],
  methods:{
    add(event){
      const todoObj={id:nanoid(),title:event.target.value,done:false}
      this.receive(todoObj)
    }
  }

我写进去爱好之后在header里面调用了app里的函数receive,然后那个函数就开始工作了,将我添加的新添一个对象到数组中,todos数组变了,vue就开始重新解析模版。

最后再完善一下,添加进去爱好之后把input里的文字清空

event.target.value=''
      //但是这块就是在操作dom了,如果不用value的话,在input表单用v-model

注意:我们的computed、data、props、methods最终都会出现在vc中,所以避免重名的问题!

5.实现勾选功能

(1)第一种方法

我们数组对象里的done都写死了,不管勾不勾选都不变

思路:我们勾选之后先拿到这件事的id,然后找到它的done再取反就行

我们要修改的是app的数据的情况,所以增删改查都得在app 里做,app里设置函数然后在item调用,但是item是app的孙子,就先给她爸,传递下去

item:

methods: {
    handleCheck(id){
    this.checkTodo(id)
  }

app:

checkTodo(id)
    {
      this.todos.forEach((todo)=>{
        if(todo.id==id) todo.done=!todo.done 
      })
    }

(2)第二种方法(不推荐)

还有一种方式是,:checked是检查初始值然后显示出来,change是检查后来的改变值然后进行数据更改,这两个我混成v-model="todo.done"(属于藏在对象里的改,vue发现不了;“a”就发现饿了),直接双向修改,我点了之后页面就改变,也不用爷爷传给孙子了methods了,直接在孙子这里就能改(需要props传过来的data),但是props不是只读不改吗?因为修改的是数组里面的对象的某一个属性,就识别不出来可以修改,但是原则上这样不好所以不使用这种

 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>

6.实现删除功能

核心思想:点击之后拿到id然后删除就行

和上一个相似,这个我自己写出来了,但是注意

<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    <!-- 这里的click触动的方法不能再用deleteTodo了因为那是app里定义的方法你可以拿来用不能再重新声明用 -->
 

app:返回与点击元素的id不同的id对应的todos

deleteTodo(id)
    {
      this.todos=this.todos.filter((todo)=>{
        return todo.id!==id
      })
    },

item:

handleDelete(id){
     if(confirm('确定要删除吗?')){//确定的话返回true
       this.deleteTodo(id)
     }
     
   }

7.实现底部统计功能

(1)已完成/全部

全部就是todos.length

已完成就得遍历然后看谁是true了:

 computed:{
    doneTotal(){
      let i=0
      this.todos.forEach(todo => {
        if(todo.done==true) i++
      });
      return i
    }
  }

这种写法有点低级:用reduce方法

doneTotal(){
      //第二次的pre是第一次运行结果的返回值,第一次pre是0但是没有return所以第二次pre=undefined
     //current是每一个todo项
     
    //  return this.todos.reduce((pre,current)=>{
    //    return pre+(current.done?1:0)
    //  },0)
      return this.todos.reduce((pre,current)=>pre+(current.done?1:0),0)
     //最后一次调用箭头函数的返回值就作为reduce的返回值
    }

我觉得这种方法更麻烦

(2)全选

首先是如果所有爱好被全选那么全选框就得打勾,需要遍历一下(computed都需要return)

isAll(){
      if(this.total==this.doneTotal&&this.doneTotal>0) return true
      return false
      //return this.total==this.doneTotal
    },
 <input type="checkbox" :checked="isAll"

然后就是如果全选框打勾了那么所有爱好都得打勾,也就是动App里的数据

 <input type="checkbox" :checked="isAll" @click="checkAll"/>
methods:{
    checkAll(e){
     this.checkAllTodo(e.target.checked)
      //还是得动app里的数据,得this.checkAllTodo,methods又没有
    },

app:

 checkAllTodo(done){
      this.todos.forEach((todo)=>{
        todo.done=done
      })
    },

(3)清除

 <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
 clearAll(){
      this.clearAllTodo()
    }

点击之后动app里的数据

clearAllTodo(){
      this.todos=this.todos.filter((todo)=>{
        return !todo.done
      })
    },

二、代码文件总览

1.App.vue

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <myHeader :receive="receive"/>
        <!-- 把一个方法传给myHeader -->
        <myList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
        <myFooter :todos="todos" 
        :checkAllTodo="checkAllTodo" 
        :clearAllTodo="clearAllTodo"/>
      </div>
    </div>
  </div>
</template>

<script>
import myFooter from "./components/myFooter";
import myHeader from "./components/myHeader";
import myList from "./components/myList.vue";
//import myItem from './components.myItem'//这块不用引入item了因为它是包括在list中的

// 交互的样式
export default {
  name: "App",
  components: {
    myFooter,
    myHeader,
    myList,
  },
  //数据在app里,那么对数据的所有增删改查都应该在app里
  data(){
    return {
      todos:[
        {id:'001',title:'吃饭',done:true},
        {id:'002',title:'睡觉',done:true},
        {id:'003',title:'抽烟',done:false},
        {id:'004',title:'喝酒',done:false},
        // 一般id都用字符串,因为数字是有尽头的
      ]
    }
  },
  methods:{
    //添加一个todo
    receive(todoObj){
      this.todos.unshift(todoObj)
    },
    //勾选/取消勾选
    checkTodo(id)
    {
      this.todos.forEach((todo)=>{
        if(todo.id==id) todo.done=!todo.done 
      })
    },
    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
      })
    },
  }

};
</script>
<style scoped>
/*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>

2.MyHeader.vue

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

<script>
import {nanoid} from 'nanoid'
//nanoid是一个函数,直接调用就行
export default {
  name: "myHeader",
  props:['receive'],
  methods:{
    add(event){
      //校验数据,前后不能为空
      if(!event.target.value.trim()) return alert('不能输入空信息')
      const todoObj={id:nanoid(),title:event.target.value,done:false}
      this.receive(todoObj)
      event.target.value=''
      //但是这块就是在操作dom了,如果不用value的话,在input表单用v-model
    }
  }
};
</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>

3.MyList.vue

<template>
  <ul class="todo-main">
    <!-- 删除一个,取走一个 -->
    <myItem 
    v-for="todoObj in todos" 
    :key="todoObj.id" 
    :todo="todoObj" 
    :checkTodo="checkTodo"
    :deleteTodo="deleteTodo"/>
  </ul>
</template>

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

export default {
  name: "myList",
  components: {
    myItem,
  },
  props:['todos','checkTodo','deleteTodo']
};
</script>

<style scoped>
/*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>

4.MyItem.vue

<template>
  <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>
    <!-- 这里的click触动的方法不能再用deleteTodo了因为那是app里定义的方法你可以拿来用不能再重新声明用 -->
  </li>
</template>

<script>
export default {
  name: "myItem",
  //接收todo对象
  props:['todo','checkTodo','deleteTodo'],
  methods: {
    handleCheck(id){
    this.checkTodo(id)
   },
   handleDelete(id){
     if(confirm('确定要删除吗?')){//确定的话返回true
       this.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 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;
}
</style>

5.MyFooter.vue

<template>
  <div class="todo-footer" v-show="total">
    <label>
      <input type="checkbox" :checked="isAll" @click="checkAll"/>
    </label>
    <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "myFooter",
  props:['todos','checkAllTodo','clearAllTodo'],
  computed:{
    total(){
      return this.todos.length
    },
    doneTotal(){
      //第二次的pre是第一次运行结果的返回值,第一次pre是0但是没有return所以第二次pre=undefined
     //current是每一个todo项

    //  return this.todos.reduce((pre,current)=>{
    //    return pre+(current.done?1:0)
    //  },0)
      return this.todos.reduce((pre,current)=>pre+(current.done?1:0),0)
     //最后一次调用箭头函数的返回值就作为reduce的返回值
    },
    isAll(){
      if(this.total==this.doneTotal&&this.doneTotal>0) return true
      return false
      //return this.total==this.doneTotal
    },
    
  },
  methods:{
    checkAll(e){
     this.checkAllTodo(e.target.checked)
      //还是得动app里的数据,得this.checkAllTodo,methods又没有
    },
    clearAll(){
      this.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>

三、总结

1.组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

一个组件在用:放在组件自身即可。
一些组件在用:放在他们共同的父组件上(状态提升)。
(3)实现交互:从绑定事件开始。

2.props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1539474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Stability AI发布Stable Video 3D模型:可从单张图像创建多视图3D视频,视频扩散模型史诗级提升!

Stability AI发布了Stable Video 3D (SV3D)&#xff0c;这是一种基于稳定视频扩散的生成模型&#xff0c;推动了3D技术领域的发展&#xff0c;并大大提高了质量和视图一致性。 该版本有两个版本: SV3D_u:该变体基于单图像输入生成轨道视频&#xff0c;无需相机调节。 SV3D_p:扩…

yolov6实现遥感影像目标识别|以DIOR数据集为例

1 目标检测是计算机视觉领域中的一项重要任务&#xff0c;它的目标是在图像或视频中检测出物体的位置和类别。YOLO&#xff08;You Only Look Once&#xff09;是一系列经典的目标检测算法&#xff0c;最初由Joseph Redmon等人于2016年提出。YOLO算法具有快速、简单、端到端的特…

QT学习第一天,创建工程文件,创建按钮,对象树的概念

创建qt 方式一&#xff1a;欢迎》project》new project 方式二&#xff1a;菜单栏》文件》新建文件或项目 打开项目 方式1&#xff1a; 欢迎》project》open project 方式2&#xff1a;打开目录&#xff08;页面上不存在的项目&#xff09; 创建工程时需要注意&#xff1…

YOLOv5全网首发改进: 注意力机制改进 | 上下文锚点注意力(CAA) | CVPR2024 PKINet 遥感图像目标检测

💡💡💡本文独家改进:引入了CAA模块来捕捉长距离的上下文信息,利用全局平均池化和1D条形卷积来增强中心区域的特征,从而提升检测精度,CAA和C3进行结合实现二次创新,改进思路来自CVPR2024 PKINet,2024年前沿最新改进,抢先使用 💡💡💡小目标数据集,涨点近两个…

opencv图片处理基础

文章目录 计算机中图片构成红绿蓝边界填充色彩图片转二值图腐蚀操作sobel算子滤波图像阈值函数 边缘检测轮廓检测轮廓特征与轮廓近似轮廓特征轮廓近似 计算机中图片构成红绿蓝 图片由像素点构成&#xff0c;每个像素点有三个通道&#xff0c;分别是RGB&#xff0c;对应红绿蓝颜…

DFS深度优先搜索刷题(一)

一.P2089 烤鸡 算法思想&#xff1a; 指数型枚举&#xff0c;可以通过dfs深度优先搜索暴力枚举出所有可能的情况&#xff0c;在通过剪枝去除错误的方案来减少时间开销。主要用一个循环枚举每个调料放几克&#xff08;每个位置的分支情况都相同&#xff09;&#xff0c;注意回溯…

分类预测 | Matlab实现PSO-KELM粒子群优化算法优化核极限学习机分类预测

分类预测 | Matlab实现PSO-KELM粒子群优化算法优化核极限学习机分类预测 目录 分类预测 | Matlab实现PSO-KELM粒子群优化算法优化核极限学习机分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现PSO-KELM粒子群优化算法优化核极限学习机分类预测(完整源…

【c++初阶】C++入门(下)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

lvgl 窗口 windows lv_port_win_visual_studio 版本 已解决

不知道的东西&#xff0c;不知道lvgl窗口。一切从未知开始 lv_port_win_visual_studio 主分支 对应的分支 v7版本更新git submodule update --init --recursive同步 lvgl代码随后打开 visualSudio 打开.sln 文件 编译 release模式 允许 一切正常代码部分

iOS应用审核问题解决方案及优化方法 ✨

摘要 本文将针对iOS应用提交审核时可能遇到的问题&#xff0c;如“你必须在Xcode中添加com.apple.developer.game-center密钥”&#xff0c;以及突然间提交送审报错情况进行探讨。通过大量查询资料和尝试&#xff0c;结合案例分析&#xff0c;提供了解决方案和优化方法&#x…

Linux进程地址空间详解

文章目录 前言一、程序地址空间二、感受虚拟地址的存在三、进程地址空间四、程序从磁盘加载到内存的过程4.1 物理地址和虚拟地址的区别 五、写时拷贝5.1 解释fork()函数有两个返回值 前言 我们在学习C/C的时候用到的地址是什么地址呢&#xff1f;虚拟地址&#xff1f;物理地址&…

抗疫医疗用品销售平台|基于JSP技术+ Mysql+Java+ Tomcat的抗疫医疗用品销售平台设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

mysql 如何设计分库分表

在MySQL中设计分库分表的方法通常涉及到水平拆分与垂直拆分两种主要方式。 水平拆分&#xff1a; 按照某一列进行水平拆分&#xff1a; 可以根据某一列&#xff08;如用户ID、时间等&#xff09;的取值范围将数据拆分到不同的数据库或表中。基于哈希值的水平拆分&#xff1a;…

贪吃蛇(C语言超详细版)

目录 前言&#xff1a; 总览&#xff1a; API&#xff1a; 控制台程序&#xff08;Console&#xff09;&#xff1a; 设置坐标&#xff1a; COORD&#xff1a; GetStdHandle&#xff1a; STD_OUTPUT_HANDLE参数&#xff1a; SetConsoleCursorPosition&#xff1a; …

python--循环(作业)

作业一&#xff1a; 判断一个数是否为质数&#xff08;素数&#xff09; flag True prime int(input("请输入一个整数&#xff1a;")) for num in range(2, prime):if prime % num 0:flag Falsebreak if flag:print("它是质数") else:print("它…

2024年阿里云服务器地域和可用区所在地区城市分布表

阿里云服务器地域和可用区有哪些&#xff1f;阿里云服务器地域节点遍布全球29个地域、88个可用区&#xff0c;包括中国大陆、中国香港、日本、美国、新加坡、孟买、泰国、首尔、迪拜等地域&#xff0c;同一个地域下有多个可用区可以选择&#xff0c;阿里云服务器网aliyunfuwuqi…

RabbitMQ问题

如何实现顺序消费&#xff1f; 消息放入到同一个队列中消费 如何解决消息不丢失&#xff1f; 方案&#xff1a; 如上图&#xff1a;消息丢失有三种情况&#xff0c;解决了以上三种情况就解决了丢失的问题 1、丢失1--->消息在到达交换机的时候&#xff1b;解决&#xff1…

unity学习(68)——相机/模型的旋转/位置计算

这个比想象中要难&#xff0c;而且需要自己写。 1.相机可以转xy两个位置&#xff0c;可以点头和转圈。注意这里有一个if判断&#xff08;后面返回来发现了这些问题&#xff09; 2.角色不能点头&#xff0c;只能转圈。 难得是移动方向&#xff0c;因为移动方向(位置)和转向是相…

Spark与flink计算引擎工作原理

Spark是大批量分布式计算引擎框架&#xff0c;scale语言开发的&#xff0c;核心技术是弹性分布式数据集&#xff08;RDD&#xff09;可以快速在内存中对数据集进行多次迭代&#xff0c;支持复杂的数据挖掘算法及图形计算算法&#xff0c;spark与Hadoop区别主要是spark多个作业之…

Mac 搜索工具比对 ProEverything ProFind DFind 等

对比 Windows的everything用习惯了&#xff0c;其他的搜索追之不及啊。Mac上对比了一圈&#xff0c;简单总结一下。 比较项ProEverythingProFindDFindEasyFindScherlokk是否索引方式索引遍历遍历遍历待试用费用收费 推荐88元终身版收费收费免费待试用是否可全盘全盘无法查找影…