练习一
非常简单,用 if 指令,根据是否存在某类型的 todo 来决定该类型的按钮显示或者隐藏即可,具体的有:
- 如果不存在未完成的 todo,全部标为完成、进行中的按钮隐藏。
- 如果不存在已完成的 todo,已完成、清除已完成的按钮隐藏。
- 若没有 todo,筛选和清除等按钮全隐藏,显示“添加我的第一个 todo”,若不存在未完成的 todo,显示“全部完成,你真是太优秀了”,否则显示“剩余 x 项未完成”。
首先增加两个计算属性,返回全部已完成的 todo 和其数量,同时顺便把 filteredTodos
计算属性重构一下:
computed: {
...
completedTodos: function () {
return this.todos.filter(todo => todo.completed)
},
completedTodosCount: function () {
return this.completedTodos.length
},
filteredTodos: function () {
if (this.intention === 'ongoing') {
return this.leftTodos
} else if (this.intention === 'completed') {
return this.completedTodos
} else {
// 其它未定义的意图我们为其返回全部 todos,
// 这里面已经包含了 all 意图了
return this.todos
}
}
然后根据这些计算属性判断相应按钮和文字的显示和隐藏即可:
<input v-if="leftTodosCount"
type="button"
value="全部标为完成"
@click="markAllAsCompleted"/>
<span v-if="leftTodosCount">剩余 {{leftTodosCount}} 项未完成 ---</span>
<span v-else-if="completedTodosCount">全部完成,你真是太优秀了!</span>
<span v-else>添加我的第一个todo</span>
<span v-if="todos.length">筛选:
<input type="button"
class="selected"
value="全部"
@click="intention='all'"/>
<input v-if="leftTodosCount"
type="button"
value="进行中"
@click="intention='ongoing'"/>
<input v-if="completedTodosCount"
type="button"
value="已完成"
@click="intention='completed'"/>
<input v-if="completedTodosCount"
type="button"
value="清除已完成"
@click="clearCompleted"/>
<input type="button"
value="清除全部"
@click="clearAll"/>
</span>
练习二
清除时确认的功能和之前删除单个 todo 的功能类似,代码如下:
clearCompleted: function () {
if (!confirm('确认清除全部已完成的待办事项?')) {
return
}
this.todos = this.todos.filter(todo => !todo.completed)
},
clearAll: function () {
if (!confirm('确认清除全部待办事项?')) {
return
}
this.todos = []
}
回收站的功能略微复杂,我们来逐步分析。
首先我们需要改造一下 todo 模型,为其增加一个 removed 属性,这个属性用于标识 todo 是否已经被删除,注意这次删除 todo 时不再是直接从 todos 数组里删除,而是将其移到回收站(增加一个回收站数组用于存放删除的 todo),removed 属性的值会给我们今后为是否删除的 todo 执行不同操作时带来诸多方便,具体修改如下:
data: function () {
return {
todos: [],
recycleBin: [], // 用于存放已经删除的 todo
newTodoTitle: '',
editedTodo: null, // 用户暂存编辑前的 todo 状态
intention: 'all', // 默认为 all
}
},
methods: {
addTodo: function () {
this.todos.push(
// 修改后的 todo 模型
{id: id++, title: this.newTodoTitle, completed: false, removed: false}
);
this.newTodoTitle = '';
},
...
},
然后当我们删除 todo 或者清除 todo 时,应该把删除的 todo 插入 recycleBin 数组中,以便后续还原,即相应修改 removeTodo
、clearAll
和 clearCompleted
方法的逻辑:
removeTodo: function (todo) {
let removedTodo = this.todos.splice(this.todos.indexOf(todo), 1)[0];
removedTodo.removed = true;
this.recycleBin.unshift(removedTodo);
},
clearCompleted: function () {
if (!confirm('确认清除全部已完成的待办事项?')) {
return
}
this.completedTodos.map(todo => todo.removed = true);
this.recycleBin.unshift(...this.completedTodos);
this.todos = this.leftTodos;
},
clearAll: function () {
if (!confirm('确认清除全部待办事项?')) {
return
}
this.todos.map(todo => todo.removed = true);
this.recycleBin.unshift(...this.todos);
this.todos = [];
},
要注意熟悉 JavaScript 数组的插入删除等操作,具体请查看 js 的文档,这里不再详细说明。
然后增加一个回收站按钮,点击后进入回收站,显示全部已删除的 todo:
<span v-if="todos.length || recycleBin.length">筛选:
...
<input v-if="recycleBin.length"
type="button"
value="回收站"
@click="intention='removed'"/>
</span>
这里回收站这个按钮和已完成、进行中等筛选按钮是类似的,它对应的 intention 是 removed,相应的要修改计算属性 filteredTodos
的逻辑,当 intention 为 removed 是能返回已删除的数组列表。
filteredTodos: function () {
if (this.intention === 'ongoing') {
return this.leftTodos
} else if (this.intention === 'completed') {
return this.completedTodos
} else if (this.intention === 'removed') {
return this.recycleBin
} else {
// 其它未定义的意图我们为其返回全部 todos,
// 这里面已经包含了 all 意图了
return this.todos
}
}
现在看到回收站中的 todo 后面的按钮依然为删除,正确地应该显示为还原按钮,用于还原todo,可以根据 todo removed 属性的值来判断应该显示哪个按钮:
<li v-for='todo in filteredTodos' :key='todo.id'>
<span :class="{completed: todo.completed}"
@dblclick="editTodo(todo)">{{ todo.title }}</span>
<input type="button"
value="标为完成"
@click="markAsCompleted(todo)"/>
<input v-if="todo.removed" type="button" value="还原" @click="restoreTodo(todo)"/>
<input v-else="todo.removed" type="button" value="删除" @click="removeTodo(todo)"/>
...
</li>
还原按钮绑定了一个 restoreTodo
方法,来实现一下:
restoreTodo: function (todo) {
todo.removed = false;
this.todos.unshift(todo);
let pos = this.recycleBin.indexOf(todo);
this.recycleBin.splice(pos, 1);
},
逻辑很简单,就是将已删除的 todo,从回收站数组移除,然后插入 this.todos,同时要记得将 removed 设置回 false。
类似的,我们还可以实现一次还原全部 todo,清空回收站的功能,其实现方法都大同小异,主要就是对 this.todos 和 recycleBin 数组的操作,请自行实现。
-- EOF --