模仿ToDoList代办事项列表:
目录
9、记录现有数据,使得下一次用这个浏览器打开还是可以看到现在的数据
1、渲染数据
arr:[
{
id:1,
content:"内容1",
isShowBorder:true,
isFinished:true
},
{
id:2,
content:"内容2",
isShowBorder:true,
isFinished:true
},
{
id:3,
content:"内容3",
isShowBorder:true,
isFinished:false
}
]
<div class="tit"><h3>正在进行</h3> <span>2</span></div>
<ul>
<li v-for="item in arr" v-if="!item.isFinished">
<label :for="item.id"><input type="checkbox" :id="item.id" :checked="item.ifCheck"></label>
<div v-if="item.isShowDiv">{{item.content}}</div>
<input v-else type="text" v-model="item.content">
<span>-</span>
</li>
</ul>
<div class="tit"><h3>已经完成</h3> <span>1</span></div>
<ul>
<li v-for="item in arr" v-if="item.isFinished">
<label :for="item.id"><input type="checkbox" :id="item.id" :checked="item.ifCheck"></label>
<div v-if="item.isShowDiv">{{item.content}}</div>
<input v-else type="text" v-model="item.content">
<span>-</span>
</li>
</ul>
2、把ul 抽离成组件
<div class="wrap">
<list-comp :arr="arr" title="正在进行" type="doing" :counts="counts"></list-comp>
<list-comp :arr="arr" title="已经完成" type="finished" :counts="counts"></list-comp>
</div>
...
<template id="tmpl">
<div class="con-list">
<div class="hd">
<h3>{{title}}</h3>
<div class="num">{{counts(type)}}</div>
</div>
<ul class="bd">
<li v-for="item in arr" v-show="type=='doing'?!item.isFinished:item.isFinished">
<div class="check-box"><input type="checkbox"></div>
<input type="text" v-model="item.content">
<div class="btn-box"><div class="del-btn">-</div></div>
</li>
</ul>
</div>
</template>
let listComp = {
template:"#tmpl",
props:["arr","title", "type", "counts"]
}
3、计算属性计算右上角数据条数
//Vue实例中
computed:{
counts(){
return type=>{
let ret = this.arr.filter(item=>{
return type=="doing"?!item.isFinished:item.isFinished
})
return ret.length
}
}
}
4、勾选复选框可以将进行的任务转移至已经完成列表中
<!--2、父组件传递事件函数给子组件 -->
<list-comp :arr="arr" title="正在进行" type="doing" :counts="counts" @fn="changeChecked"></list-comp>
<list-comp :arr="arr" title="已经完成" type="finished" :counts="counts" @fn="changeChecked"></list-comp>
<!-- 3、模板中绑定事件-->
<div class="check-box"><input type="checkbox" @click="chchecked(key)"></div>
// 4、 子组件中调用父组件传来的方法
methods:{
chchecked(index){
this.$emit("fn",index)
}
}
// 1、父组件中定义方法
// 点击修改选中状态
methods:{
changeChecked(index){
// alert(111)
this.arr[index].isFinished = !this.arr[index].isFinished
}
},
5、点击input切换标签边框颜色
<!--父组件-->
<list-comp
:arr="arr"
title="正在进行"
type="doing"
:counts="counts"
@fn="changeChecked"
@fn2="changeBorder"
></list-comp>
<list-comp :arr="arr" title="已经完成" type="finished" :counts="counts" @fn="changeChecked" @fn2="changeBorder"></list-comp>
<!--模板-->
<input type="text" v-model="item.content" @click="showBorder(key)" :class="item.isShowBorder?'show-border':''">
// 子组件
showBorder(index){
this.$emit("fn2",index)
}
// 父组件
changeBorder(index){
this.arr[index].isShowBorder = !this.arr[index].isShowBorder
}
6、设置失焦
<!--添加失去焦点的事件 @blur="showBorder(key)"-->
<input type="text" v-model="item.content" @click="showBorder(key)" @blur="showBorder(key)" :class="item.isShowBorder?'show-border':''">
7、删除某一项
<!--父模板中-->
<list-comp
:arr="arr"
title="正在进行"
type="doing"
:counts="counts"
@fn="changeChecked"
@fn2="changeBorder"
@fn3="del"
></list-comp>
<list-comp :arr="arr" title="已经完成" type="finished" :counts="counts" @fn="changeChecked" @fn2="changeBorder" @fn3="del"></list-comp>
<!--子模板中-->
<div class="btn-box"><div class="del-btn" @click="del(key)">-</div></div>
//子组件中
del(index){
this.$emit("fn3",index)
}
//父组件中:
del(index){
this.arr.splice(index,1)
}
8、输入后回车,可以往“正在进行”中添加一个元素
<input class="input-text" type="text" placeholder="请输入任务" @keyup.enter="add" v-model="txtVal">
//Vue实例中 定义数据num 用来记录每条数据的id, 同时也作为是否来过这个网站的依据想来保存在本地
methods:{
add(){
this.arr.push({
id:this.num,
isFinished:false,
content:this.txtVal,
isShowBorder:false
});
this.num++;
}
}
9、记录现有数据,使得下一次用这个浏览器打开还是可以看到现在的数据
//父组件中定义方法
setToLocalStorage(){
localStorage.setItem('num', this.num);
localStorage.setItem('arr', JSON.stringify(this.arr));
}
// 每次修改都要保存localStorage
this.setToLocalStorage();
// 组件每次创建完毕之后,可以获取这两个localStorage
created(){
this.num = localStorage.getItem("num")?localStorage.getItem("num"):0;
this.arr=localStorage.getItem("arr")?JSON.parse(localStorage.getItem('arr')):[];
},
10、完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<link rel="stylesheet" href="style/index.css">
</head>
<body>
<div id='app'>
<header>
<div class="wrap">
<h1>ToDoList</h1>
<input type="text" placeholder="请输入。。。" v-model='inpTxt' @keyup.enter='add'>
</div>
</header>
<con-comp title='正在进行' :arr='arr' type='doing' @fn="changeType" @fn2='borderFa' @fn3='delFa'></con-comp>
<con-comp title='已完成' :arr='arr' type='finished' @fn="changeType" @fn2='borderFa' @fn3='delFa'></con-comp>
<div class="footer">
Copyright 2021 todolist.cn clear
</div>
</div>
<template id="tmp">
<div class="wrap">
<div class="hd">
<h3>{{title}}</h3>
<div class="count">{{counts}}</div>
</div>
<ul class="bd">
<!-- li展示或者不展示看的是item.isFinished
对于未完成的任务 就是要 !item.isFinished 才能展示未完成的任务
对于已经完成的任务 就是要 item.isFinished 才能展示已经完成的任务
v-show 的值就要么 !item.isFinished 要么 item.isFinished
-->
<li v-for='item,key in arr' v-show="type=='doing'?!item.isFinished:item.isFinished">
<div class="l">
<input type="checkbox" @click="clickCheckbox(key)" :checked='item.isFinished'>
</div>
<input type="text" v-model='item.content' :class="item.isShowBorder?'showBorder':''"
@focus='borderChil(key)' @blur='borderChil(key)'>
<div class="r">
<div @click='del(key)'>-</div>
</div>
</li>
</ul>
</div>
</template>
<script>
let conComp = {
template: '#tmp',
props: ['title', 'arr', 'type'],
computed: {
// doingCounts() {
// 计算的是未完成的条数
// // 在arr数组中过滤出 isFinished为false的元素
// let newArr = this.arr.filter(item => {
// return item.isFinished == false
// })
// return newArr.length
// },
// finishedCounts(){
// 计算的是已经完成的条数
// // 在arr数组中过滤出 isFinished为true的元素
// let newArr = this.arr.filter(item => {
// return item.isFinished == true
// })
// return newArr.length
// }
counts() {
let newArr = this.arr.filter(item => {
return this.type == 'doing' ? !item.isFinished : item.isFinished
})
return newArr.length
}
},
methods: {
clickCheckbox(key) {
// 修改该数据的isFinished ,取反
this.$emit('fn', key)
},
borderChil(key) {
this.$emit('fn2', key)
},
del(key) {
this.$emit('fn3', key)
}
},
}
new Vue({
el: '#app',
data: {
inpTxt: '',
num: 3,
arr: [
// {
// id: 1,
// content: "内容1",
// isFinished: true, // 用来区分是否完成
// isShowBorder: false // 用来区分是否有边框
// },
// {
// id: 2,
// content: "内容2",
// isFinished: true, // 用来区分是否完成
// isShowBorder: false // 用来区分是否有边框
// },
// {
// id: 3,
// content: "内容3",
// isFinished: false, // 用来区分是否完成
// isShowBorder: false // 用来区分是否有边框
// }
]
},
components: {
conComp
},
created() {
// 生命周期函数,表示实例/组件刚创建的时候执行这里的代码
// 获取localstorage里面的 num 和 arr
// 并赋值给this.num 和 this.arr
this.num = localStorage.getItem('num') ? parseInt(localStorage.getItem('num')) : 0
this.arr =
localStorage.getItem("arr") ? JSON.parse(localStorage.getItem("arr")) : []
},
methods: {
changeType(index) {
this.arr[index].isFinished = !this.arr[index].isFinished
this.setLocalstorage()
},
borderFa(index) {
this.arr[index].isShowBorder = !this.arr[index].isShowBorder
this.setLocalstorage()
},
delFa(index) {
this.arr.splice(index, 1)
this.setLocalstorage()
},
add() {
if (!this.inpTxt) {
return
}
this.arr.push({
id: ++this.num,
content: this.inpTxt,
isFinished: false, // 用来区分是否完成
isShowBorder: false // 用来区分是否有边框
})
this.inpTxt = ''
this.setLocalstorage()
},
setLocalstorage() {
// 保存数据 num 和 arr
localStorage.setItem("num", this.num)
localStorage.setItem("arr", JSON.stringify(this.arr))
}
},
})
</script>
</body>
</html>
CSS文件代码:
*{
margin: 0;
padding: 0;
list-style: none;
outline: none;
}
body{
background-color: #ccc;
}
header{
height: 50px;
line-height: 50px;
background-color: #333;
}
h1{
color: #fff;
}
.wrap{
width: 600px;
margin: 0 auto;
}
header .wrap{
display: flex;
justify-content: space-between;
}
header input{
height: 40px;
margin-top: 3px;
width: 300px;
text-indent: 10px;
}
.hd{
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.hd .count{
width: 30px;
height: 30px;
color: #fff;
background-color: #666;
border-radius: 50%;
text-align: center;
line-height: 30px;
}
.bd{
margin-bottom: 20px;
}
.bd li{
display: flex;
background-color: #fff;
height: 40px;
margin-top: 8px;
}
.l,.r{
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.bd input[type=text]{
margin-top: 3px;
flex: 1;
height: 30px;
border-color: #fff;
border-width: 0px;
}
.bd input[type=text].showBorder{
border: 1px solid #000;
}
.l>input,.r>div{
width: 30px;
height: 30px;
}
.r>div{
background-color: pink;
border-radius: 50%;
text-align: center;
line-height: 30px;
}
.footer{
text-align: center;
margin-top: 20px;
}
.bd input[type=checkbox],.r>div{
cursor: pointer;
}