项目实战
筛选
- 有三种筛选:
- 全部:显示全部 todo
- 进行中:显示未完成的 todo
- 已完成:显示已完成的 todo
- 用户是想要进行中的 todo 还是已完成的todo?
- 可以发现用户会点击相应的按钮来表明他的意图
- 如果在用户点击按钮时把他的意图传递给 Vue 对象
- 给 data 增加一个属性 intention,来记录用户的意图
- 定义三种意图:
- all:想查看全部 todo
- ongoing:想查看未完成的 todo
- completed:想查看已完成的 todo
- 需要根据用户的意图从 todos 中筛选 todo
- 从 Vue 已绑定的数据中计算新的结果是计算属性的典型应用场景,所以加一个 filteredTodos 计算属性
- 会发现未完成的 todo 分别在 filteredTodos 和 leftTodosCount 两个计算属性中被计算了两次
- n 根据用户意图返回的结果:filteredTodos
- 用户点击按钮的意图需要传给 Vue 对象
- 设置让用户的意图在点击按钮时传给 Vue
- 给各个筛选按钮绑定一个 click 事件
- 之前在循环显示 todo 列表时使用的是 this.todos 的数据
- 把循环的内容改为 filteredTodos
- 循环过滤结果
<ul>
<li v-for='todo in filteredTodos' :key='todo.id'>
- 绑定事件
<span>筛选:
<input type="button" class="selected" value="全部"
@click="intention='all'">
<input type="button" value="进行中"
@click="intention='ongoing'">
<input type="button" value="已完成"
@click="intention='completed'">
- vue对象增加data属性和计算属性
data: function () {
...
intention: 'all'
...
computed: {
...
leftTodos(){
return this.todos.filter(todo => !todo.completed)
},
leftTodoCount(){
return this.leftTodos.length
},
filteredTodos(){
if(this.intention == 'ongoing'){
return this.leftTodos
}else if (this.intention == 'completed'){
return this.todos.filter(todo => todo.completed)
}else { //返回所有的
return this.todos
}
}
功能10:样式绑定
- 使用v-bind:class绑定
<span>筛选:
<input type="button" class="selected" value="全部"
@click="intention='all'"
v-bind:class="{selected: intention==='all'}">
<input type="button" value="进行中"
@click="intention='ongoing'"
v-bind:class="{selected: intention==='ongoing'}">
<input type="button" value="已完成"
@click="intention='completed'"
v-bind:class="{selected: intention==='completed'}">
批量清除
- 只要把筛选出来的 todo 从 todos 列表删除就可以了
- 先为按钮绑定事件
- 然后实现相应的绑定方法
- 绑定事件
<input type="button" value="清除已完成"
@click="clearCompleted">
<input type="button" value="清除全部"
@click="clearAll">
- 实现方法methods
clearCompleted(){
this.todos = this.todos.filter(todo => !todo.completed)
},
clearAll(){
this.todos = []
}
功能11:动态显示按钮
- 绑定v-if条件刷选
<span>筛选:
...
<input v-if="leftTodoCount" type="button" value="进行中"
@click="intention='ongoing'"
v-bind:class="{selected: intention==='ongoing'}">
<input v-if="completedTodoCount" type="button" value="已完成"
@click="intention='completed'"
v-bind:class="{selected: intention==='completed'}">
<input v-if="completedTodoCount" type="button" value="清除已完成"
@click="clearCompleted">
- 增加和修改计算属性
computed: {
...
completedTodos(){
return this.todos.filter(todo => todo.completed)
},
completedTodoCount(){
return this.completedTodos.length
},
功能12:用户体验
- 增加span的v-if
<div>
<span v-if="leftTodoCount">剩余 {{ leftTodoCount }} 项未完成 ---</span>
<span v-else-if="completedTodoCount">全部完成,你真是太优秀了</span>
<span v-else>添加我的第一个TODO</span>
- 增加清除确认
clearCompleted(){
if(!confirm('确定清除已完成的待办事项?')){
return
}
this.todos = this.todos.filter(todo => !todo.completed)
},
clearAll(){
if(!confirm('确定清除全部的待办事项?')){
return
}
this.todos = []
}
功能13:回收站
- 添加一个回收站data对象:recycleBin
data: function () {
return {
...
recycleBin:[], //回收站
- 添加删除标记:removed
addTodo: function () {
...
this.todos.push({id: id++, title: this.newTodoTitle,
completed:false, removed:false});
...
},
- 修改removedTodo,clearCompleted,clearAll,将删除数据移动到回收站
removeTodo(todo){
let pos = this.todos.indexOf(todo);
let removedTodo = this.todos.splice(pos, 1)[0];
removedTodo.removed = true;
this.recycleBin.unshift(removedTodo)
},
clearCompleted(){
if(!confirm('确定清除已完成的待办事项?')){
return
}
this.completedTodos.map(todo => todo.removed = true)
this.recycleBin.unshift(...this.completedTodos)
this.todos = this.leftTodos
},
clearAll(){
if(!confirm('确定清除全部的待办事项?')){
return
}
this.todos.map(todo => todo.removed = true)
this.recycleBin.unshift(...this.todos)
this.todos = []
}
- DOM增加回收站按钮,绑定事件
<input v-if="recycleBin.length" type="button" value="回收站"
@click="intention='removed'">
...
filteredTodos(){
...
}else if (this.intention == 'removed'){
return this.recycleBin
...
}
从回收站还原
- 还原按钮
<li v-for='todo in filteredTodos' :key='todo.id'>
...
<input v-if="todo.removed" type="button" value="还原"
@click="restoreTodo(todo)"/>
<input v-else="todo.removed" type="button" value="删除"
@click="removeTodo(todo)"/>
- 添加方法
restoreTodo: function(todo){
todo.removed = false
this.todos.unshift(todo) //恢复
pos = this.recycleBin.indexOf(todo)
this.recycleBin.splice(pos, 1)
},
本地存储
-
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性
-
现在只要一刷新浏览器,所有todo 都没了,因为的数据保存在内存中,页面刷新数据就会清除
-
可以使用浏览器的 LocalStorage 来实现数据的持续性存储
- 这样一来只要用户不删除浏览器缓存,todo 会一直在
- 当然浏览器清除缓存后数据也没有了
- 为了更加持久化存储,可以使用数据库,这里只是用 LocalStorage
-
首先来定义一个对象,用于 LocalStorage 存储和获取 todo 的相关操作
- STORAGE_KEY 用来区分存储到 LocalStorage 的内容,因为 LocalStorage 中可能存储其它应用的数据,使用这个 key 用于区分
- todoStorage 是一个 JavaScript 的对象,它的属性是两个方法
- save 方法:把 todos 转为 JSON 格式,然后将序列化的数据存入对应 key 为 STORAGE_KEY 的本地存储中
- fetch 方法:从对应 STORAGE_KEY 的本地存储将之前存入的 todo 数据取出并反序列化
-
为 todoStorage 对象绑定一个 uid 属性,作用是后续添加 todo 时,用于确定新添加todo 的 id
- 注意这里代码中的 localStorage 就代表了本地存储对象,在支持 HTLM5 的浏览器中会存在这个对象,直接引用即可
-
每当用户打开页面时,因为去 LocalStorage fetch 一下存储的数据
-
当添加 todo 时,由于可能已经存在从本地取出的 todo 数据,新的 todo id 不能是从 0 开始了,而应该从已有 todoStorage.uid 开始
- 一旦添加了新的 todo,应该及时将新的 todo 存到本地,防止用户不小心关闭页面而导致数据丢失
- 可以使用 Vue 的 watch 来监听用户添加 todo 的事件,即监视 this.todos 的变化,一旦改变,立即修改本地存储的 todos 的数据
-
定义localstorage存储和获取todo
<script>
let id = 0; // 用于 id 生成
var STORAGE_KEY = "dylan-vue2-todo"
var todoStorage = {
fetch(){ //读
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
todos.forEach(function(todo, index){
todo.id = index
});
todoStorage.uid = todos.length
return todos
},
save(todos){ //写
localStorage.setItem(STORAGE_KEY,JSON.stringify(todos))
}
};
- 修改data
data: function () {
return {
todos: todoStorage.fetch(),
...
- 修改addTodo
addTodo: function () {
...
this.todos.push({id: todoStorage.uid++, title: this.newTodoTitle,
...
- 增加监听
watch:{
todos:{
handler: function(todos){
todoStorage.save(todos)
},
deep: true
}
},