Vue 实现ToDoList
本文实现一个用Vue脚手架搭建项目、实现简单的ToDoList功能
computed:计算属性
filters:过滤器
localStorage:本地存储
技术点
vue+localStorage
功能
回车添加任务
双击编辑任务
编辑完成按回车保存 Esc撤销编辑
点击复选框表示已经完成任务
点击X删除任务
所有任务、已经完成任务、未完成任务可筛选
<template>
<div>
<header>
<section>
<label for="title"> ToDoList</label>
<input type="text" id="title" placeholder="添加ToDo" v-model="iptVal" @keydown.13="add">
</section>
</header>
<h3>未完成({{no}})</h3>
<ul>
<li v-for="(item,index) in list" :key="index" v-show="!item.done">
<input type="checkbox" @click.prevent="change(item,true)">
<span v-if="index !== iptIndex" @dblclick="update(item,index)">{{item.val}}</span>
<input
v-if="index === iptIndex"
type="text"
v-model="item.val"
@keydown.enter="enter(item,index)"
autofocus
@keydown.esc="esc(item,index)"
>
<span class="time">{{item.time | timeFilter}}</span>
<span @click="del(index)" class="del">×</span>
</li>
</ul>
<h3>已经完成({{yes}})</h3>
<ul>
<li v-for="(item,index) in list" :key="index" v-show="item.done">
<input type="checkbox" checked @click.prevent="change(item,false)">
<span v-if="index !== iptIndex" @dblclick="update(item,index)">{{item.val}}</span>
<input
v-if="index === iptIndex"
type="text"
v-model="item.val"
@keydown.enter="enter(item,index)"
autofocus
@keydown.esc="esc(item,index)"
>
<span class="time">{{item.time | timeFilter}}</span>
<span @click="del(index)" class="del">×</span>
</li>
</ul>
<footer>
<select v-model="select" @change="selected">
<option value="">请选择</option>
<option value="all">全部数据</option>
<option value="no">未完成</option>
<option value="yes">已完成</option>
</select>
<ul>
<li v-for="(item,index) in selist" :key="index">
{{item.val}}
</li>
</ul>
</footer>
</div>
</template>
<script>
export default {
//计算属性 用来计算已完成 未完成的数量
computed: {
no() {
let n = 0;
this.list.map(item => {
if (!item.done) {
n++;
}
});
return n;
},
yes() {
let n = 0;
this.list.map(item => {
if (item.done) {
n++;
}
});
return n;
}
},
//时间过滤
filters: {
timeFilter(ms) {
let time = new Date();
let nowTime = time.getTime(); //现在时间的
let cha = nowTime - ms; //差值
let months = cha / 1000 / 60 / 60 / 24 / 30;
let week = cha / 1000 / 60 / 60 / 24 / 7;
let days = cha / 1000 / 60 / 60 / 24;
let hours = cha / 1000 / 60 / 60;
let minutes = cha / 1000 / 60;
let str = '';
if (months >= 1) {
str = `${parseInt(months)}月前`
} else if (week >= 1) {
str = `${parseInt(week)}周前`
} else if (days >= 1) {
str = `${parseInt(days)}天前`
} else if (hours >= 1) {
str = `${parseInt(hours)}小时前`
} else if (minutes >= 1) {
str = `${parseInt(minutes)}分钟前`
} else {
str = '刚刚'
}
return str;
}
},
data() {
return {
list: JSON.parse(localStorage.getItem('six')) || [],
iptVal: "", //输入框的值
iptIndex: -1, //设置一个固定值 用来实现双击显示和隐藏
temp: '', //声明一个空值用来保存esc取消时的数据
select: '', //筛选 输入框改变时 存储 yes no all
selist: [], //筛选数据的展示
}
},
methods: {
add() {
this.list.push({
val: this.iptVal,
done: false, //每增加一条数据 done 为false
time: new Date().getTime() //每增加一条新数据,增加对应的时间戳
});
this.save(); // 同步保存到本地存储
this.iptVal = '';
},
save() { //本地存储
localStorage.six = JSON.stringify(this.list)
},
//删除
del(index) {
this.list.splice(index, 1);
this.save()
},
// 双击改变
update(item, index) {
this.iptIndex = index;
//给temp 赋值 值为item.val
this.temp = item.val;
},
//按回车 保存数据
enter(item, index) {
this.save(); //先保存到本地存储 顺序不能变
this.list.splice(index, 1, item); //页面展示的数据改变
this.iptIndex = -1; //iptIndex值还原,让input框隐藏
},
//按ESC取消
esc(item, index) {
item.val = this.temp; //现在temp里已经有值 然后再赋值给item.val
this.iptIndex = -1; //iptIndex值还原,让input框隐藏
},
//点击复选框改变 未完成改为已完成 已完成改为未完成
change(item, checked) {
item.done = !!checked;
this.save()
},
//筛选
selected() {
// console.log(this.select);
this.selist = []; //清空数组里的值,不然每次都追加
switch (this.select) {
case "all": //全部 展示
this.list.map(item => { //遍历
this.selist.push(item) //添加到selist数组
});
break;
case "no": //未完成展示
this.list.map(item => { //遍历
if (!item.done) {
this.selist.push(item) //添加到selist数组
}
});
break;
case "yes": //已完成展示
this.list.map(item => { //遍历
if (item.done) {
this.selist.push(item) //添加到selist数组
}
});
break;
}
}
}
}
</script>
<style>
header {
height: 50px;
background: rgba(47, 47, 47, 0.98);
}
.time {
color: #dddddd;
margin-left: 10px;
}
section {
width: 600px;
padding: 0 10px;
display: flex;
margin: 0 auto;
}
label {
float: left;
width: 100px;
line-height: 50px;
color: #DDD;
font-size: 24px;
cursor: pointer;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.del {
color: red;
margin-left: 50px;
}
header input {
float: right;
width: 60%;
height: 24px;
margin-top: 12px;
text-indent: 10px;
border-radius: 5px;
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.24), 0 1px 6px rgba(0, 0, 0, 0.45) inset;
border: none;
outline: none;
margin-left: 10px;
}
</style>