使用Vue+localStorage 完成一个任务列表(可添加、删除,不可更新)
效果图如下:
输入框代码,v-model记录输入信息,同时将值赋值到todo里,当回车时,用到@keyup.enter事件.enter事件修饰符,触发addTodo方法,此方法记录在methods对象里,这时通过addTodo方法将记录的todo值传到列表里,再存放到localStorage里,在经过v-for渲染到页面。
<input type="text" v-model="todo" @keyup.13="addTodo" placeholder="回车会添加任务" @keyup.esc="cancelTodo" />
然后通过v-for循环展示数据
<ul>
<li v-for="(item,index) in filteredList" :class="{completed:item.isChecked,editing:edtorTodos===item}"><!-- 在li上统一管理class -->
<div class="view" @dblclick="edtorTodo(item)">
<span class="icon-check"><label :class="{checked:item.isChecked}"><input type="checkbox" v-model="item.isChecked"></label></span>
<span class="text">{{item.title}}</span>
<span class="f-fr close" v-on:click="deleteTodo(index)">X</span>
</div>
</li>
</ul>
js实例化Vue和删除方法
var todoListView = new Vue({
el: "#my-Vue",
data: {
list: list,
todo: "", //要添加任务
edtorTodos: "", //记录正在编辑数据
beforeTitle: "", //编辑前数据
visibility: "all" //通过属性值变化对数据筛选
},
methods: {
addTodo: function() { //添加任务
this.list.push({
title: this.todo,
isChecked: false
});
this.todo = ""; //输入完,清空
this.total = this.list.length;
},
deleteTodo: function(index) { //删除任务
this.list.splice(index, 1);
this.total = this.list.length;
},
cancelTodo: function() { //当按esc时返回原先编辑数据
this.todo = "";
}
},
computed: {
noCheckedList: function() {
return list.filter(function(item) {
return !item.isChecked;
}).length;
},
filteredList: function() {
var filter = {
all: function(list) {
return list;
},
unfinished: function(list) {
return list.filter(function(item) {
return !item.isChecked;
});
},
finished: function(list) {
return list.filter(function(item) {
return item.isChecked;
});
}
};
return filter[this.visibility] ? filter[this.visibility](list) : list;
}
},
watch: {
list: {
handler: function() {
localStorage.setItem("_tmp_todolist", JSON.stringify(this.list));
},
deep: true
}
}
});
watch: {
list: {
handler: function() {
localStorage.setItem("_tmp_todolist", JSON.stringify(this.list));
},
deep: true
}
}
付上完成的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="./css/index.css">
<script src="./scripts/vue.js"></script>
</head>
<body>
<!-- 标题 -->
<div class="m- nav">
<div class="g-main">
<h2>任务列表</h2>
</div>
</div>
<div class="m-body" id="my-Vue">
<div class="g-main">
<!-- 添加任务栏 -->
<div class="u-add">
<h3>添加任务:</h3>
<input
type="text"
v-model="todo"
@keyup.13="addTodo"
placeholder="回车会添加任务"
@keyup.esc="cancelTodo"
>
<!-- 筛选栏 -->
<div class="message">
<span class="msg f-fl">共有{{noCheckedList}}个任务</span>
<a class="btn" href="#all">所有任务</a>
<a class="btn" href="#finished">已完成任务</a>
<a class="btn" href="#unfinished">未成任务</a>
</div>
</div>
<!-- 任务列表 -->
<div class="u-list">
<h3>任务列表:</h3>
<span class="msg" v-show="!list.length">未添加任务</span>
<ul>
<li v-for="(item,index) in filteredList" :class="{completed:item.isChecked,editing:edtorTodos===item}"><!-- 在li上统一管理class -->
<div class="view" @dblclick="edtorTodo(item)">
<span class="icon-check"><label :class="{checked:item.isChecked}"><input type="checkbox" v-model="item.isChecked"></label></span>
<span class="text">{{item.title}}</span>
<span class="f-fr close" v-on:click="deleteTodo(index)">X</span>
</div>
</li>
</ul>
</div>
</div>
</div>
<script src="./scripts/index.js"></script>
</body>
</html>
JS
var list = []
if (localStorage.getItem('_tmp_todolist'))
list = JSON.parse(localStorage.getItem('_tmp_todolist'));
var todoListView = new Vue({
el: "#my-Vue",
data: {
list: list,
todo: "", //要添加任务
edtorTodos: "", //记录正在编辑数据
beforeTitle: "", //编辑前数据
visibility: "all" //通过属性值变化对数据筛选
},
methods: {
addTodo: function() { //添加任务
this.list.push({
title: this.todo,
isChecked: false
});
this.todo = ""; //输入完,清空
this.total = this.list.length;
},
deleteTodo: function(index) { //删除任务
this.list.splice(index, 1);
this.total = this.list.length;
},
cancelTodo: function() { //当按esc时返回原先编辑数据
this.todo = "";
}
},
computed: {
noCheckedList: function() {
return list.filter(function(item) {
return !item.isChecked;
}).length;
},
filteredList: function() {
var filter = {
all: function(list) {
return list;
},
unfinished: function(list) {
return list.filter(function(item) {
return !item.isChecked;
});
},
finished: function(list) {
return list.filter(function(item) {
return item.isChecked;
});
}
};
return filter[this.visibility] ? filter[this.visibility](list) : list;
}
},
watch: {
list: {
handler: function() {
localStorage.setItem("_tmp_todolist", JSON.stringify(this.list));
},
deep: true
}
}
});
CSS
/* css reset iSoftStone */
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { margin: 0; padding: 0; }
body,input { font: 12px/1 Helvetica,Arial,STXihei,'Microsoft YaHei';-webkit-font-smoothing:antialiased; }/* STXihei:'华文细黑' */
html,body,section { height: 100%; overflow: hidden; }
select,input,img,textarea { border: 0 none; }/* vertical-align: middle; */
input,textarea { outline: none; font: inherit; }
textarea { width: 100%; resize: none; }
ol,ul,li { list-style: none; }
a,a:visited,a:hover { text-decoration: none; color: inherit; }
h2,
h3 {
line-height: 40px;
}
li {
list-style: none;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
a {
text-decoration: none;
color: #666;
}
.f-fl {
float: left;
}
.f-fr {
float: right;
}
.g-main {
width: 500px;
margin: 0 auto;
}
.m-nav {
background-color: #E8583C;
color: #fff;
}
.u-add input {
width: 100%;
margin-bottom: 10px;
line-height: 30px;
height: 30px;
padding-left: 10px;
}
.u-add .message {
text-align: right;
margin: 0 10px;
font: 14px '微软雅黑';
color: #666;
}
.u-add .message .btn {
display: inline-block;
border: 1px solid #efe;
padding: 0 10px;
}
.u-add .message .btn:hover {
border-color: #999;
background-color: #fff;
cursor: pointer;
}
.u-add .message .msg {
color: red;
}
.u-list .completed {
text-decoration: line-through;
color: #aaa;
}
.u-list .msg {
font: 14px '微软雅黑';
color: #666;
margin-left: 10px;
}
.u-list li {
background-color: #ebe;
margin-bottom: 1px;
padding: 10px 0;
position: relative;
}
.u-list li .edit {
display: none;
}
.u-list li.editing .edit {
display: block;
padding: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.u-list li.editing .view {
display: none;
}
.u-list .icon-check {
position: relative;
display: inline-block;
height: 20px;
width: 20px;
margin-left: 10px;
}
.u-list .icon-check input {
height: 20px;
width: 20px;
visibility: hidden;
margin: 0;
padding: 0;
}
.u-list .icon-check label {
position: absolute;
top: 4px;
display: inline-block;
height: 18px;
width: 18px;
border: 1px solid #666;
border-radius: 50% 50%;
}
.u-list .icon-check label.checked {
background: url(../images/success.png) no-repeat;
background-size: contain;
}
.u-list li:hover .close {
display: block;
}
.u-list .close {
display: none;
position: absolute;
width: 19px;
height: 19px;
top: 12px;
right: 10px;
color: #cc9a9a;
}
.u-list .close:hover {
cursor: pointer;
}