part4 - 模块1 - 05 - Vue基础综合案例 - Todomvc

359 阅读12分钟

Vue基础综合案例 - Todomvc

官网:todomvc.com/

准备工作

  • 从github克隆项目模版(模版中已经写好结构)
  • 进入项目目录 cd ...,安装项目依赖 npm i
  • 安装vue.js npm i vue

需求分析

  • 事项列表展示
    • 有事项的情况
    • 没有事项的情况
  • 状态栏展示
    • 个数展示
    • 单位展示
  • 事项状态切换
    • 单个事项切换
    • 多个事项切换
  • 事项新增
    • 内容检测
    • 回车新增
  • 事项删除
    • 单个事项点击X删除
    • 清除全部完成事项和删除按钮的显示隐藏
  • 事项编辑
    • 触发编辑(双击触发)
    • 取消编辑(ESC取消编辑并还原为未编辑状态)
    • 保存编辑(回车或者失去焦点)
  • 事项筛选
    • 点击切换类别
    • 更新渲染事项内容
  • 事项数据持久化
    • 读取本地存储数据
    • 更新本地存储数据

项目目录结构分析

y5Je8x.png

  • 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
  • 其他:对于本项目不是很重要,暂时可不考虑

本项目中,我们只需要在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);