对于totos的操作
入口main.js文件
// 引入vue
import Vue from 'vue'
// 引入App
import App from './App'
// 配置vue对象
new Vue({
el:"#root",
// 注册组件,渲染组件
render : h=> h(App)
})
App组件
<template> <div class="todo-container"> <div class="todo-wrap"> <Header :todos="todos" :updateO="updateO"></Header> <Main :todos="todos" :changeStatus= "changeStatus" :dltO="dltO" ></Main> <Footer :todos="todos" :delQc="delQc"></Footer> </div> </div> </template> <script> // 引入组件 import Header from '@/components/Header' import Main from '@/components/Main' import Footer from '@/components/Footer' export default { name:'App', data() { return { todos:[ {id:1,content:'抽烟', isOver:false}, {id:2,content:'喝酒', isOver:true}, {id:3,content:'烫头', isOver:false}, ] }; }, methods:{ //接收header的数据 updateO(obj){ //新增的对象的id是最后一个id加1 let id = this.todos[this.todos.length-1].id+1 obj.id =id //数组往顶部新增一个对象 this.todos.unshift(obj) console.log(this.todos) }, //item组件点击单选框,改变状态 changeStatus(index){ //取反 this.todos[index].isOver = !this.todos[index].isOver }, //item组件点击删除按钮 dltO(index){ this.todos.splice(index,1) }, //对于footer组件中过滤的新数组赋值给原数组 delQc(newTodos){ this.todos =newTodos } }, // 注册组件 components:{ Header, Main, Footer, } }; </script> <style scoped lang="less"> /*app*/ .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
Header组件
<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="content" @keyup.enter="update" /> </div> </template> <script> export default { //输入框一个按键事件,添加li name:'Header', data() { return { content:'', }; }, // 接收父组件的数据 props:{ todos:Array, updateO:Function }, methods:{ //输入框输入数据,组成一个新对象,给父组件传递过去 update(){ let {content} =this if(content.trim()){ //新对象 let obj ={ content, isOver:false } //传递数据给父组件 this.updateO(obj) this.content='' } } } }; </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>
Main组件
<template> <ul class="todo-main"> <Item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index" :changeStatus= "changeStatus" :dltO="dltO" ></Item> </ul> </template> <script> import Item from './Item' export default { name:'Main', data() { return {}; }, props:{ todos:Array, changeStatus:Function, dltO:Function, }, //注册组件 components:{ Item } }; </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>
Item组件
<template> <li @mouseenter="isShow=true" @mouseleave="isShow=false" :class="{myClass:isShow}"> <label> <input type="checkbox" :checked="todo.isOver" @click="changeZt" /> <span>{{todo.content}}</span> </label> <button class="btn btn-danger" v-show="isShow" @click="deleteO" >删除</button> </li> </template> <script> export default { //1.点击li中的单选按钮,需要父组件的数据更改状态,需要传递一个index参数,定位是那个li //2.点击删除按钮,需要父组件操作数组的逻辑,需要传递一个index参数,定位是那个li //3.移入,移出事件,li变化颜色,而且删除按钮也会跟着改变 name:"Item", props:{ todo:Object, index:Number, changeStatus:Function, dltO:Function, }, data() { return { //定义一个状态 isShow:false }; }, methods:{ changeZt(){ //点击单选框,更改状态 this.changeStatus(this.index) }, //点击删除按钮 deleteO(){ //给组价传递index this.dltO(this.index) } } }; </script> <style scoped lang="less"> /*item*/ .myClass{ background-color: hotpink; } 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; } </style>
Footer组件
<template> <div class="todo-footer"> <label> <input type="checkbox" v-model="ischeck" /> <!-- isCheckAll的数据是和checked属性值进行关联的 --> <!-- v-model 如果input当中是有value的,那么v-model影响的就是value的值 对于文本输入框密码输入框来说一定是有value的 对于单选输入框和多选输入框,如果没有写value,那么影响的的是checked的值 --> </label> <span> <span>已完成{{qcNum}}</span> / 全部{{qbNum}} </span> <button class="btn btn-danger" @click="deleteQc">清除已完成任务</button> </div> </template> <script> export default { //1.父组件传递todos组件,计算已完成和全部数量 // 2.对于全选按钮,需要判断两种情况,一种是获取,一种是修改 // 对于获取逻辑,需要判断已完成数量和全部数量是否全等, //对于修改逻辑,则需要对遍历todos的每个对象,状态进行修改 //3.对于清除已完成的任务,需要判断已经状态为true的对象清除 name:'Footer', props:{ todos:Array, delQc:Function, }, data() { return {}; }, methods:{ deleteQc(){ //将新数组传递父组件,让父组件对原数组跟新 let newTodos= this.todos.filter(item =>!item.isOver) this.delQc(newTodos) } }, // 计算属性,组件中没有的属性 computed:{ //计算完成的数量 qcNum(){ return this.todos.filter(item =>item.isOver).length }, //计算全部的数量 qbNum(){ return this.todos.length }, ischeck:{ //获取方法 get(){ return this.qcNum ===this.qbNum && this.qbNum>0 }, //修改方法 set(value){ //遍历todos,将每一项的isOver都随着vlaue改变 this.todos.forEach(item =>item.isOver = value) } } } }; </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>
将todos数据保存在localStorage
localStorage的简介(永久存储,关闭和刷新浏览器,数据不会丢失)
目前所有的浏览器中都会把localStorage的值类型限定为string类型,
这个在对我们日常比较常见的JSON对象类型需要一些转换
1.我们将对todos深度监视,如果todos的数据变化了,就会立刻存储在localStorage
2.然后从data中获取存储在localStorage中的数据
<template> <div class="todo-container"> <div class="todo-wrap"> <Header :todos="todos" :recOne="recOne"></Header> <Main :todos="todos" :changeStaus="changeStaus" :deleteOne="deleteOne" ></Main> <Footer :todos="todos" :delet="delet" ></Footer> </div> </div> </template> <script> // 引入组件 import Header from '@/components/Header' import Main from '@/components/Main' import Footer from '@/components/Footer' let id = 0 export default { //自定义todos // todos:[ // {id:1,content:'抽烟', isOver:false}, // {id:2,content:'喝酒', isOver:true}, // {id:3,content:'烫头', isOver:false}, // ] name:'App', data(){ return { todos: JSON.parse(window.localStorage.getItem('TODOS_KEY')) || [] } }, // 一般监视,当todos的数据只要发生变化,就会存储到localStorage //修改isOver状态,并不能存储在localStorage, 因为他修改的数组才会存储到localStorage // watch:{ // todos(newVal, oldVal){ // localStorage.setItem("TODOS_KEY", JSON.stringify(newVal)) // } // }, // 深度监视,只有数组的数据变化了,包括对象元素的属性变化也会存储在localStorage watch:{ todos:{ deep:true, handler(newVal, oldVal){ //赋值给localStorage localStorage.setItem('TODOS_KEY', JSON.stringify(newVal)) }, // 代表在wacth里声明了todos这个方法之后立即先去执行handler方法 immediate: true } }, methods:{ //接收footer组件的数据,删除已有的数据 delet(newTodos){ this.todos= newTodos }, //接收header组件的数据 recOne(todo){ // const length = this.todos.length ? this.todos[this.todos.length - 1] : {id:1} // todo.id = ++length.id todo.id = new Date() //数组添加一个新对象 this.todos.unshift(todo) }, //接收item组件的数据,更改单选框状态 changeStaus(index){ this.todos[index].isOver = !this.todos[index].isOver }, //接收item组件数据,删除一个对象 deleteOne(index){ this.todos.splice(index,1) } }, // 注册组件 components:{ Header, Main, Footer, } }; </script> <style scoped lang="less"> /*app*/ .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
3.
todos: JSON.parse(localStorage.getItem("TODOS_KEY")) || []
如果是空的,浏览器会报错,那就搞个空数组