Vue2(八):TodoList案例

2024-03-24 03:52

本文主要是介绍Vue2(八):TodoList案例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、整体思路

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('确定要删除吗?')){//确定的话返回truethis.deleteTodo(id)}}

7.实现底部统计功能

(1)已完成/全部

全部就是todos.length

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

 computed:{doneTotal(){let i=0this.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 truereturn 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:{//添加一个todoreceive(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('确定要删除吗?')){//确定的话返回truethis.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 truereturn 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不会报错,但不推荐这样做。
 

这篇关于Vue2(八):TodoList案例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/840384

相关文章

Vue中动态权限到按钮的完整实现方案详解

《Vue中动态权限到按钮的完整实现方案详解》这篇文章主要为大家详细介绍了Vue如何在现有方案的基础上加入对路由的增、删、改、查权限控制,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、数据库设计扩展1.1 修改路由表(routes)1.2 修改角色与路由权限表(role_routes)二、后端接口设计

Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)

《Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)》文章介绍了如何使用dhtmlx-gantt组件来实现公司的甘特图需求,并提供了一个简单的Vue组件示例,文章还分享了一... 目录一、首先 npm 安装插件二、创建一个vue组件三、业务页面内 引用自定义组件:四、dhtmlx

Vue ElementUI中Upload组件批量上传的实现代码

《VueElementUI中Upload组件批量上传的实现代码》ElementUI中Upload组件批量上传通过获取upload组件的DOM、文件、上传地址和数据,封装uploadFiles方法,使... ElementUI中Upload组件如何批量上传首先就是upload组件 <el-upl

前端知识点之Javascript选择输入框confirm用法

《前端知识点之Javascript选择输入框confirm用法》:本文主要介绍JavaScript中的confirm方法的基本用法、功能特点、注意事项及常见用途,文中通过代码介绍的非常详细,对大家... 目录1. 基本用法2. 功能特点①阻塞行为:confirm 对话框会阻塞脚本的执行,直到用户作出选择。②

如何使用CSS3实现波浪式图片墙

《如何使用CSS3实现波浪式图片墙》:本文主要介绍了如何使用CSS3的transform属性和动画技巧实现波浪式图片墙,通过设置图片的垂直偏移量,并使用动画使其周期性地改变位置,可以创建出动态且具有波浪效果的图片墙,同时,还强调了响应式设计的重要性,以确保图片墙在不同设备上都能良好显示,详细内容请阅读本文,希望能对你有所帮助...

CSS3 最强二维布局系统之Grid 网格布局

《CSS3最强二维布局系统之Grid网格布局》CS3的Grid网格布局是目前最强的二维布局系统,可以同时对列和行进行处理,将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局,本文介... 深入学习 css3 目前最强大的布局系统 Grid 网格布局Grid 网格布局的基本认识Grid 网

HTML5中下拉框<select>标签的属性和样式详解

《HTML5中下拉框<select>标签的属性和样式详解》在HTML5中,下拉框(select标签)作为表单的重要组成部分,为用户提供了一个从预定义选项中选择值的方式,本文将深入探讨select标签的... 在html5中,下拉框(<select>标签)作为表单的重要组成部分,为用户提供了一个从预定义选项中

前端 CSS 动态设置样式::class、:style 等技巧(推荐)

《前端CSS动态设置样式::class、:style等技巧(推荐)》:本文主要介绍了Vue.js中动态绑定类名和内联样式的两种方法:对象语法和数组语法,通过对象语法,可以根据条件动态切换类名或样式;通过数组语法,可以同时绑定多个类名或样式,此外,还可以结合计算属性来生成复杂的类名或样式对象,详细内容请阅读本文,希望能对你有所帮助...

Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)

《Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)》本文介绍了如何使用Python和Selenium结合ddddocr库实现图片验证码的识别和点击功能,感兴趣的朋友一起看... 目录1.获取图片2.目标识别3.背景坐标识别3.1 ddddocr3.2 打码平台4.坐标点击5.图

禁止HTML页面滚动的操作方法

《禁止HTML页面滚动的操作方法》:本文主要介绍了三种禁止HTML页面滚动的方法:通过CSS的overflow属性、使用JavaScript的滚动事件监听器以及使用CSS的position:fixed属性,每种方法都有其适用场景和优缺点,详细内容请阅读本文,希望能对你有所帮助... 在前端开发中,禁止htm