Vue基础综合案例 - Todomvc
官网:todomvc.com/
准备工作
- 从github克隆项目模版(模版中已经写好结构)
- 进入项目目录
cd ...,安装项目依赖npm i - 安装vue.js
npm i vue
需求分析
- 事项列表展示
- 有事项的情况
- 没有事项的情况
- 状态栏展示
- 个数展示
- 单位展示
- 事项状态切换
- 单个事项切换
- 多个事项切换
- 事项新增
- 内容检测
- 回车新增
- 事项删除
- 单个事项点击X删除
- 清除全部完成事项和删除按钮的显示隐藏
- 事项编辑
- 触发编辑(双击触发)
- 取消编辑(ESC取消编辑并还原为未编辑状态)
- 保存编辑(回车或者失去焦点)
- 事项筛选
- 点击切换类别
- 更新渲染事项内容
- 事项数据持久化
- 读取本地存储数据
- 更新本地存储数据
项目目录结构分析
- css文件夹:存放css样式文件
- js文件夹:存放js文件,我们在内部的app.js文件书写Vue代码
- node_modules:依赖文件夹
- index.html文件:结构文件,我们暂时在index中书写Vue代码
- 注意:在index.html文件中我们注释掉
<script src="node_modules/todomvc-common/base.js"></script>因为这个文件我们用不上而且会在控制台报错,然后我们引入vue.js文件,地址为node_modules/vue/dist/vue.js
- 注意:在index.html文件中我们注释掉
- 其他:对于本项目不是很重要,暂时可不考虑
本项目中,我们只需要在app.js和index.html书写内容即可,其他暂时不需要
事项列表展示实现
需求:
- 列表动态更新(根据数据变化)
- 实现实现展示列表和底部功能区根据事项是否为0现实和隐藏切换
<!-- index.html文件 -->
<!-- 挂载元素 -->
<section class="todoapp" id="app">
<!-- 顶部事项输入框 -->
<header class="header">
......
</header>
<!-- 中间事项展示区域,在这里使用v-show控制一下显示和隐藏,当事项列表为空则隐藏该区域,todos.length会自动转换为布尔值 -->
<section class="main" v-show="todos.length">
<!-- 事项多选按钮 -->
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<!-- 事项列表,所有事项写在ul的li中 -->
<ul class="todo-list">
<!-- 事项,这里使用v-for循环遍历todos事项列表,并且根据todos的completed动态修改类名 -->
<li v-for="item in todos" :key="item.id" :class="{completed:item.completed}">
<!-- 事项本体 -->
<div class="view">
<!-- 事项单选框,使用v-model双向绑定数据completed -->
<input class="toggle" type="checkbox" v-model="item.completed">
<!-- 这里书写事项内容文本 -->
<label>{{item.content}}</label>
<!-- 右侧删除按钮 -->
<button class="destroy"></button>
</div>
<!-- 这里是双击修改事项内容的输入框,现在还没有用,但是也需要先书写上,后面有用 -->
<input class="edit" value="Create a TodoMVC template / Rule the web">
</li>
</ul>
</section>
<!-- 底部功能区,在这里使用v-show控制一下显示和隐藏,当事项列表为空则隐藏该区域,todos.length会自动转换为布尔值 -->
<footer class="footer" v-show="todos.length">
......
</footer>
</section>
//app.js文件
new Vue({
// 挂载实例,这里使用类名挂载
el: '.todoapp',
// 数据
data: {
// 事项列表数据,内部每一个对象都是一个事项
todos: [{
// 事项id,用在v-for中标示数据
id: 1,
// 事项内容
content: '内容1',
// 事项是否选中
completed: true
},
{
id: 2, content: '内容2', completed: false
},
{
id: 3, content: '内容3', completed: true
}
]
},
})
状态栏信息展示实现
需求:
- 实现底部状态栏动态显示未完成事项数目,并且当未完成事项为1时显示item,>1是现实items
- 实现清楚完成事项按钮在有完成事项时显示,没有时隐藏的效果
<!-- index.html文件,这里只涉及到底部功能区,所以只贴出footer的代码 -->
<!-- 底部功能区
在这里使用v-show控制一下显示和隐藏,当事项列表为空则隐藏该区域-->
<footer class="footer" v-show="todos.length">
<!-- 左侧待完成事项统计区
使用计算属性itemLeft
第一个直接使用
第二个判断未完成任务是否为1,是则直接使用单位,否则单位后加s
-->
<span class="todo-count"><strong>{{itemLeft}}</strong> {{itemLeft === 1 ? item : item + 's'}} left</span>
<!-- 中间功能切换区域 -->
<ul class="filters">
<li>
<a class="selected" href="#/">全部</a>
</li>
<li>
<a href="#/active">未完成</a>
</li>
<li>
<a href="#/completed">已完成</a>
</li>
</ul>
<!-- 右侧清除已完成按钮的展示
在这里使用计算属性itemLeft
使用全部事项 - 未完成事项就 = 已完成事项个数
已完成事项 > 0显示此按钮 -->
<button class="clear-completed" v-show="todos.length - itemLeft > 0">清除已完成事项</button>
</footer>
<!-- 老师使用了methods函数对数值进行了计算,我这里直接使用itemLeft,这样个人认为更简单 -->
//app.js文件
//data中添加
// 用于动态传入底部数值统计区域的单位
item: 'item'
//Vue实例添加计算属性
// 计算属性
computed: {
// 用于统计未完成项目个数
itemLeft() {
let num = 0
for (const i of this.todos) {
if (i.completed === false) {
num++
}
}
return num
}
}
事项状态切换
需求:
单个事项状态切换(我们之前v-model已经实现了)
多个事项切换效果
<!-- index.html文件,这里只涉及到中间列表区域多选按钮,所以只贴出toggle-all相关代码 -->
<!-- 事项多选按钮
在按钮上进行v-model双向绑定,绑定到计算属性toggleAll
toggleAll会自动判断是否当前任务全部勾选了,如果全部勾选了此按钮也会选中,否则不会选中
同时,因为双向绑定了,我们点击按钮更改按钮状态也会传到计算属性toggleAll中,所以我们要设置计算属性set
-->
<input id="toggle-all" class="toggle-all" type="checkbox" v-model="toggleAll">
<label for="toggle-all">Mark all as complete</label>
//app.js文件,在Vue计算属性中新增加一条
// 用于控制多选按钮多选效果
toggleAll: {
// 根据itemLeft判断是否全部事项都完成了,都完成了返回ture
get() {
return this.itemLeft === 0
},
// 用于多选按钮修改数据
// 因为toggleAll绑定了按钮的value,所以当我们点击按钮的时候按钮会变化,从而传回这里
set(value) {
// value为传回的数值,我们在这里把所有事项的completed修改为value即可
for (const i of this.todos) {
i.completed = value
}
}
}
事项新增
需求:
- 输入框内容绑定
- 新增回车按钮事件
<!-- index.html文件
这里只涉及到顶部输入框功能,所以只贴header代码
-->
<!-- 顶部事项输入框 -->
<header class="header">
<!-- 标题 -->
<h1>todos</h1>
<!-- 输入框
使用v-model双向绑定输入框value
添加回车键盘抬起事件(@keyUp.enter),当回车键抬起执行操作
-->
<input class="new-todo" placeholder="What needs to be done?" autofocus v-model.trim="newTodo"
@keyUp.enter="addNewTodo">
</header>
//app.js文件
// data中 新增事项输入框内容
newTodo: ''
// 函数
methods: {
// 输入框抬起键盘事件
addNewTodo() {
// 判断newTodo是否为空
if (this.newTodo !== '') {
// 如果不为空,向todos添加一条事项
this.todos.push({
// 为了简便,这里简单设置一个id,如果想保证id绝对不重复,可以使用时间戳
id: this.todos.length + 1,
// 内容就是newTodo内容
content: this.newTodo,
// 默认flase
completed: false
})
// 清空输入框
this.newTodo = ''
}
}
}
注意
我们这里使用.trim修饰符可以在改变内容的时候自动去除两侧空白,但是这样做可能会出现很多不必要的重复工作,我们也可以把去掉两侧空白的工作放在addNewTodo中进行,使用.trim()方法
事件删除
需求:
- 单个事项删除
- 已完成时间的批量删除
<!-- index.html文件,这里贴出列表v-for循环和底部功能区clear-completed按钮-->
<li v-for="item in todos" :key="item.id" :class="{completed:item.completed}">
<div class="view">
...
<!-- 右侧删除按钮
给按钮添加点击事件(事件内部把数据带进去)-->
<button class="destroy" @click="removeLi(item)"></button>
</div>
...
</li>
<!-- 右侧清除已完成按钮的展示
在这里使用计算属性itemLeft,使用全部事项 - 未完成事项就 = 已完成事项个数,已完成事项 > 0显示此按钮
这里添加事件clearCompleted()用于删除所有已完成事件
-->
<button class="clear-completed" v-show="todos.length - itemLeft > 0" @click="clearCompleted()">清除已完成事项</button>
//app.js 文件
// 添加两个函数
// 删除单个事项按钮点击事件
removeLi(todo) {
// 获取点击的按钮对应的事项在todos中的下标
const i = this.todos.indexOf(todo)
// 调用数组的splice方法删除指定下标
this.todos.splice(i, 1)
},
// 批量删除已完成事项
clearCompleted() {
// 过滤todos,把所有completed = true的全部去掉
// !todo.completed全部取反,如果结果是true才能得以保留
this.todos = this.todos.filter(todo => !todo.completed)
}
事项编辑
需求
- 触发编辑操作(双击事项输入获取焦点内容为事项内容)
- 取消编辑(编辑过程中按ESC取消编辑把事项内容恢复为原始内容)
- 确认编辑(编辑状态去除,自动删除内容两侧空格,如果为空删除该事项)
<!-- index.html文件,这里贴出li标签(事项v-for循环),!标记为本次修改内容-->
<!-- 事项v-for结构
这里使用v-for循环便利todos事项列表
根据todos的completed动态修改类名
用item和当前编辑事项做对比,如果却认为当前编辑事项,则添加editing类名!!!!!!!
-->
<li v-for="item in todos" :key="item.id" :class="{completed:item.completed,editing:item === editing}">
<!-- 事项本体 -->
<div class="view">
<!-- 事项单选框,使用v-model双向绑定数据completed -->
<input class="toggle" type="checkbox" v-model="item.completed">
<!-- 这里书写事项内容文本!!!!!!!!
这里添加鼠标双击,实现鼠标双击把当前的item设置为当前编辑item,用来控制li是否添加editing类名 -->
<label @dblclick="dbclickLabel(item)">{{item.content}}</label>
<!-- 右侧删除按钮
给按钮添加点击事件(事件内部把数据带进去)-->
<button class="destroy" @click="removeLi(item)"></button>
</div>
<!-- 这里是双击修改事项内容的输入框!!!!!!!!!!!
使用v-model双向绑定事项内容
添加@keyUp.esc事件用于取消编辑
添加@keyUp.enter和@blur事件用于保存编辑
使用自定义事件,判断当前编辑事项是否是我-->
<input class="edit" v-model="item.content" v-autofocus="item === editing" @keyUp.esc="cancelEditing(item)"
@keyUp.enter="saveEdit(item)" @blur="saveEdit(item)">
</li>
// app.js文件,改动比较多,全贴上来了,!为改动内容
(function (window) {
'use strict';
// Your starting point. Enjoy the ride!
// 新建Vue实例
new Vue({
// 挂载实例,这里使用类名挂载
el: '.todoapp',
// 数据
data: {
// 事项列表数据,内部每一个对象都是一个事项
todos: [{
// 事项id,用在v-for中标示数据
id: 1,
// 事项内容
content: '内容1',
// 事项是否选中
completed: true
},
{
id: 2,
content: '内容2',
completed: false
},
{
id: 3,
content: '内容3',
completed: true
}
],
// 用于动态传入底部数值统计区域的单位
item: 'item',
// 新增事项输入框内容
newTodo: '',
// 用于保存当前编辑的事项信息,用于对比 !!!!!
editing: null,
// 保存编辑的事项原始的内容,以便后期取消保存可能找到 !!!!
oldTodoContent: ''
},
// 函数
methods: {
// 输入框抬起键盘事件
addNewTodo() {
// 判断newTodo是否为空
if (this.newTodo !== '') {
// 如果不为空,向todos添加一条事项
this.todos.push({
// 为了简便,这里简单设置一个id,如果想保证id绝对不重复,可以使用时间戳
id: this.todos.length + 1,
// 内容就是newTodo内容
content: this.newTodo,
// 默认flase
completed: false
})
// 清空输入框
this.newTodo = ''
}
},
// 删除单个事项按钮点击事件
removeLi(todo) {
// 获取点击的按钮对应的事项在todos中的下标
const i = this.todos.indexOf(todo)
// 调用数组的splice方法删除指定下标
this.todos.splice(i, 1)
},
// 批量删除已完成事项
clearCompleted() {
// 过滤todos,把所有completed = true的全部去掉
// !todo.completed全部取反,如果结果是true才能得以保留
this.todos = this.todos.filter(todo => !todo.completed)
},
// 双击事项事件函数,将要编辑的事项信息保存起来,vue会自动进行处理 !!!!!
dbclickLabel(item) {
this.editing = item
this.oldTodoContent = item.content
},
// 取消编辑事件函数 !!!!!
cancelEditing(todo) {
// 清除当前编辑事项信息
this.editing = null
// 将旧的事项内容替换回来
todo.content = this.oldTodoContent
},
// 保存编辑事件函数 !!!!!
saveEdit(todo) {
// (这一步是为了后面 if (!todo.content) 作准备的)判断是否当前有编辑的事项,如果没有直接返回,因为enter事件会再次触发blur事件,导致事件触发两次,会发生两次删除,而第二次删除找不到当前编辑事项会默认删除最后一个事项,导致重复删除
if (this.editing === null) return
// 清除编辑事项
this.editing = null
// 获取我们输入的内容并去掉前后空格
todo.content = todo.content.trim()
// 判断内容是否为空,如果为空删除编辑的事件
if (!todo.content) {
this.removeLi(todo)
}
}
},
// 计算属性
computed: {
// 用于统计未完成项目个数
itemLeft() {
let num = 0
for (const i of this.todos) {
if (i.completed === false) {
num++
}
}
return num
},
// 用于控制多选按钮多选效果
toggleAll: {
// 根据itemLeft判断是否全部事项都完成了,都完成了返回ture
get() {
return this.itemLeft === 0
},
// 用于多选按钮修改数据
// 因为toggleAll绑定了按钮的value,所以当我们点击按钮的时候按钮会变化,从而传回这里
set(value) {
// value为传回的数值,我们在这里把所有事项的completed修改为value即可
for (const i of this.todos) {
i.completed = value
}
}
}
},
// 自定义指令 !!!!!
directives: {
// 编辑状态下输入框自动获取焦点
'autofocus'(el, binding) {
// 判断自定义事项条件是否成立
if (binding.value) {
// 如果成立,编辑事项的输入框获得焦点
el.focus();
}
}
}
})
})(window);
事项筛选
需求
记录筛选类别
点击更改类别
<!-- app.html文件,贴出底部功能区数据筛选切换按钮,另外注意,把中间的v-for循环依赖的数据更改为returnTodos,因为我们在js中会根据当前模式的不同输出不同的数据展示-->
<!-- 中间功能切换区域
使用fliter值判断真假,实现类名的切换
添加点击事件,修改当前展示模式 -->
<ul class="filters">
<li>
<a href="javascript:;" :class="{selected:fliter ==='all'}" @click="fliter = 'all'">全部</a>
</li>
<li>
<a href="javascript:;" :class="{selected:fliter ==='active'}" @click="fliter = 'active'">未完成</a>
</li>
<li>
<a href="javascript:;" :class="{selected:fliter ==='completed'}" @click="fliter = 'completed'">已完成</a>
</li>
</ul>
//app.js文件
//在data中添加
// 当前展示模式,默认为全部,另外还有active(未完成)、completed(已完成)
fliter: 'all',
// 存储不同时模式下输出的不同数据的函数
fliterFn: {
// all模式下直接返回全部todos
all: (todos) => todos,
// active模式下筛选全部completed为flase的事项
active: (todos) => {
return todos.filter(function (todo) {
return !todo.completed
})
},
// completed模式下筛选全部completed为true的事项,这个是上面的ES6简写
completed: (todos) => {
return todos.filter(todo => todo.completed)
}
}
// 在计算属性中添加
// 用于输出上面fliterFn不同函数的结果
returnTodos() {
// 读取fliter模式,调取fliterFn对象相应方法,然后返回结果
return this.fliterFn[this.fliter](this.todos)
}
我们可以发现这里面有很多相同的代码,我们可以吧这些代码存在一个对象或者函数中,可以自己尝试下
数据持久化
需求
- 获取本地数据存储
- 更新本地数据存储
// js文件
//Vue实例外部创建变量
// 创建一个指针,用于本地持久化标志
const TODOS_KEY = 'todos-vue'
// 创建一个对象,内部使用localStorage的getItem(数据读取)和setItem(数据写入)方法
let todoStorage = {
get() {
// 返回数据,我们需要js代码数据,需要使用JSON.parse转换一下
// 使用[]用于备用,如果现在没有数据使用空数组
return JSON.parse(localStorage.getItem(TODOS_KEY)) || []
},
set(todos) {
// 调用setItem方法
// 参数1:指针名字
// 参数2:数据,这里我们需要转为json数据才能存储(JSON.stringify)
localStorage.setItem(TODOS_KEY, JSON.stringify(todos))
}
}
// data内添加(把原始的todos删掉即可)
// 在这里使用持久化数据,因为当前什么都没有,所以是数据
todos: todoStorage.get(),
// 新建侦听
// 监听器
watch: {
// 侦听todos数据,一旦变化就跟新持久化数据
todos: {
// 监听对象内部变化
deep: true,
// 可简写为handler:todoStorage.set
// 把调用set方法更改数据
handler(value) {
todoStorage.set(value)
}
}
}
完整代码
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Template • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
<!-- CSS overrides - remove if you don't need it -->
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<!-- 挂载元素 -->
<section class="todoapp" id="app">
<!-- 顶部事项输入框 -->
<header class="header">
<!-- 标题 -->
<h1>todos</h1>
<!-- 输入框
使用v-model双向绑定输入框value
添加回车键盘抬起事件,当回车键抬起执行操作
-->
<input class="new-todo" placeholder="What needs to be done?" autofocus v-model.trim="newTodo"
@keyUp.enter="addNewTodo">
</header>
<!-- 中间事项展示区域,在这里使用v-show控制一下显示和隐藏,当事项列表为空则隐藏该区域 -->
<section class="main" v-show="todos.length">
<!-- 事项多选按钮
在按钮上进行v-model双向绑定,绑定到计算属性toggleAll
toggleAll会自动判断是否当前任务全部勾选了
如果全部勾选了此按钮也会选中,否则不会选中
-->
<input id="toggle-all" class="toggle-all" type="checkbox" v-model="toggleAll">
<label for="toggle-all">Mark all as complete</label>
<!-- 事项列表,所有事项寻出在ul的li中 -->
<ul class="todo-list">
<!-- 事项v-for结构
这里使用v-for循环便利todos事项列表
根据todos的completed动态修改类名
用item和当前编辑事项做对比,如果却认为当前编辑事项,则添加editing类名
-->
<li v-for="item in returnTodos" :key="item.id" :class="{completed:item.completed,editing:item === editing}">
<!-- 事项本体 -->
<div class="view">
<!-- 事项单选框,使用v-model双向绑定数据completed -->
<input class="toggle" type="checkbox" v-model="item.completed">
<!-- 这里书写事项内容文本 , 这里添加鼠标双击,实现鼠标双击把当前的item设置为当前编辑item,用来控制li是否添加editing类名 -->
<label @dblclick="dbclickLabel(item)">{{item.content}}</label>
<!-- 右侧删除按钮
给按钮添加点击事件(事件内部把数据带进去)-->
<button class="destroy" @click="removeLi(item)"></button>
</div>
<!-- 这里是双击修改事项内容的输入框
使用v-model双向绑定事项内容
添加@keyUp.esc事件用于取消编辑
添加@keyUp.enter和@blur事件用于保存编辑
使用自定义事件,判断当前编辑事项是否是我-->
<input class="edit" v-model="item.content" v-autofocus="item === editing" @keyUp.esc="cancelEditing(item)"
@keyUp.enter="saveEdit(item)" @blur="saveEdit(item)">
</li>
</ul>
</section>
<!-- 底部功能区
在这里使用v-show控制一下显示和隐藏,当事项列表为空则隐藏该区域
-->
<footer class="footer" v-show="todos.length">
<!-- 左侧待完成事项统计区
使用计算属性itemLeft
第一个直接使用
第二个判断未完成任务是否为1,是则直接使用单位,否则单位后加s
-->
<span class="todo-count"><strong>{{itemLeft}}</strong> {{itemLeft === 1 ? item : item + 's'}} left</span>
<!-- 中间功能切换区域
使用fliter值判断真假,实现类名的切换
添加点击事件,修改当前展示模式 -->
<ul class="filters">
<li>
<a href="javascript:;" :class="{selected:fliter ==='all'}" @click="fliter = 'all'">全部</a>
</li>
<li>
<a href="javascript:;" :class="{selected:fliter ==='active'}" @click="fliter = 'active'">未完成</a>
</li>
<li>
<a href="javascript:;" :class="{selected:fliter ==='completed'}" @click="fliter = 'completed'">已完成</a>
</li>
</ul>
<!-- 右侧清除已完成按钮的展示
在这里使用计算属性itemLeft,使用全部事项 - 未完成事项就 = 已完成事项个数,已完成事项 > 0显示此按钮
这里添加事件clearCompleted()用于删除所有已完成事件
-->
<button class="clear-completed" v-show="todos.length - itemLeft > 0" @click="clearCompleted()">清除已完成事项</button>
</footer>
</section>
<!-- 底部固定显示区域 -->
<footer class="info">
<p>Double-click to edit a todo</p>
<!-- Remove the below line ↓ -->
<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<!-- Change this out with your name and url ↓ -->
<p>Created by <a href="http://todomvc.com">you</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<!-- Scripts here. Don't remove ↓ -->
<!-- <script src="node_modules/todomvc-common/base.js"></script> -->
<script src="./node_modules/vue/dist/vue.js"></script>
<script src="js/app.js"></script>
</body>
</html>
(function (window) {
'use strict';
// Your starting point. Enjoy the ride!
// 创建一个指针,用于本地持久化标志
const TODOS_KEY = 'todos-vue'
// 创建一个对象,内部使用localStorage的getItem(数据读取)和setItem(数据写入)方法
let todoStorage = {
get() {
// 返回数据,我们需要js代码数据,需要使用JSON.parse转换一下
// 使用[]用于备用,如果现在没有数据使用空数组
return JSON.parse(localStorage.getItem(TODOS_KEY)) || []
},
set(todos) {
// 调用setItem方法
// 参数1:指针名字
// 参数2:数据,这里我们需要转为json数据才能存储(JSON.stringify)
localStorage.setItem(TODOS_KEY, JSON.stringify(todos))
}
}
// 新建Vue实例
new Vue({
// 挂载实例,这里使用类名挂载
el: '.todoapp',
// 数据
data: {
// 事项列表数据,内部每一个对象都是一个事项
// todos: [{
// // 事项id,用在v-for中标示数据
// id: 1,
// // 事项内容
// content: '内容1',
// // 事项是否选中
// completed: true
// },
// {
// id: 2,
// content: '内容2',
// completed: false
// },
// {
// id: 3,
// content: '内容3',
// completed: true
// }
// ],
// 在这里使用持久化数据,因为当前什么都没有,所以是数据
todos: todoStorage.get(),
// 用于动态传入底部数值统计区域的单位
item: 'item',
// 新增事项输入框内容
newTodo: '',
// 用于保存当前编辑的事项信息,用于对比
editing: null,
// 保存编辑的事项原始的内容,以便后期取消保存可能找到
oldTodoContent: '',
// 当前展示模式,默认为全部,另外还有active(未完成)、completed(已完成)
fliter: 'all',
// 存储不同时模式下输出的不同数据的函数
fliterFn: {
// all模式下直接返回全部todos
all: (todos) => todos,
// active模式下筛选全部completed为flase的事项
active: (todos) => {
return todos.filter(function (todo) {
return !todo.completed
})
},
// completed模式下筛选全部completed为true的事项,这个是上面的ES6简写
completed: (todos) => {
return todos.filter(todo => todo.completed)
}
}
},
// 函数
methods: {
// 输入框抬起键盘事件
addNewTodo() {
// 判断newTodo是否为空
if (this.newTodo !== '') {
// 如果不为空,向todos添加一条事项
this.todos.push({
// 为了简便,这里简单设置一个id,如果想保证id绝对不重复,可以使用时间戳
id: this.todos.length + 1,
// 内容就是newTodo内容
content: this.newTodo,
// 默认flase
completed: false
})
// 清空输入框
this.newTodo = ''
}
},
// 删除单个事项按钮点击事件
removeLi(todo) {
// 获取点击的按钮对应的事项在todos中的下标
const i = this.todos.indexOf(todo)
// 调用数组的splice方法删除指定下标
this.todos.splice(i, 1)
},
// 批量删除已完成事项
clearCompleted() {
// 过滤todos,把所有completed = true的全部去掉
// !todo.completed全部取反,如果结果是true才能得以保留
this.todos = this.todos.filter(todo => !todo.completed)
},
// 双击事项事件函数,将要编辑的事项信息保存起来,vue会自动进行处理
dbclickLabel(item) {
this.editing = item
this.oldTodoContent = item.content
},
// 取消编辑事件函数
cancelEditing(todo) {
// 清除当前编辑事项信息
this.editing = null
// 将旧的事项内容替换回来
todo.content = this.oldTodoContent
},
// 保存编辑事件函数
saveEdit(todo) {
// (这一步是为了后面 if (!todo.content) 作准备的)判断是否当前有编辑的事项,如果没有直接返回,因为enter事件会再次触发blur事件,导致事件触发两次,会发生两次删除,而第二次删除找不到当前编辑事项会默认删除最后一个事项,导致重复删除
if (this.editing === null) return
// 清除编辑事项
this.editing = null
// 获取我们输入的内容并去掉前后空格
todo.content = todo.content.trim()
// 判断内容是否为空,如果为空删除编辑的事件
if (!todo.content) {
this.removeLi(todo)
}
}
},
// 计算属性
computed: {
// 用于统计未完成项目个数
itemLeft() {
let num = 0
for (const i of this.todos) {
if (i.completed === false) {
num++
}
}
return num
},
// 用于控制多选按钮多选效果
toggleAll: {
// 根据itemLeft判断是否全部事项都完成了,都完成了返回ture
get() {
return this.itemLeft === 0
},
// 用于多选按钮修改数据
// 因为toggleAll绑定了按钮的value,所以当我们点击按钮的时候按钮会变化,从而传回这里
set(value) {
// value为传回的数值,我们在这里把所有事项的completed修改为value即可
for (const i of this.todos) {
i.completed = value
}
}
},
// 用于输出上面fliterFn不同函数的结果
returnTodos() {
// 读取fliter模式,调取fliterFn对象相应方法,然后返回结果
return this.fliterFn[this.fliter](this.todos)
}
},
// 自定义指令
directives: {
// 编辑状态下输入框自动获取焦点
'autofocus'(el, binding) {
// 判断自定义事项条件是否成立
if (binding.value) {
// 如果成立,编辑事项的输入框获得焦点
el.focus();
}
}
},
// 监听器
watch: {
// 侦听todos数据,一旦变化就跟新持久化数据
todos: {
// 监听对象内部变化
deep: true,
// 可简写为handler:todoStorage.set
// 把调用set方法更改数据
handler(value) {
todoStorage.set(value)
}
}
}
})
})(window);