vue-todoMVC-经典框架入门项目
目录
文章目录
内容
1、前言
TodoMVC是一个示例项目,它使用目前流行的不同JavaScript框架的来实现同一个Demo,来帮助你熟悉和选择最合适的前端框架。官网地址:http://todomvc.com,学习框架最直接有效的方式就是上手练习,接下来我们将用Vue.js来完成TodoMVC的示例。
2、准备
2.1、下载模板
-
下载源码:官网http://todomvc.com或者git.hubhttps://github.com/tastejs/todomvc-app-template
-
下载和安装依赖:解压,进入目录,
npm i npm i vue
2.2、项目简介
- 目录结构示例:
需要我们关心的就2个文件,js/app.js 和index.html 。
- app.js :主要的逻辑代码,即vue部分
- index.html:项目首页
页面结构和样式都已经写好,我们只想用vuejs实现功能即可。
2.3、效果图
3、需求分析和实现
3.0、vue导入与创建vue实例
-
index.html导入vue模块与设置id
<-- 此处添加id用于vue实例绑定 --> <section class="todoapp" id="app"> ... <script src="node_modules/todomvc-common/base.js"></script> <-- 一定在导入app.js之前导入vue模块 --> <script src="./node_modules/vue/dist/vue.js"></script> <script src="js/app.js"></script>
-
app.js
(function () { const vm = new Vue({ el: '#app' }) })
3.1、列表渲染
-
每一个条目有三个状态:
未完成(没有样式) 已完成(.completed ) 编辑中( .editing ) // 编辑功能中完成
- 列表为空时:
-
准备示例数据
// app.js其余部分省略 const itemList = [ {id: 1, content: '晨跑', completed: true}, {id: 2, content: '吃早饭', completed: true}, {id: 3, content: '学习前端课程', completed: false}, {id: 4, content: '吃午饭', completed: false} ] // vue实例 ... data: { items: itemList } ...
-
改造index.html
<section class="main" v-show="items.length"> ... <ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <li :class="{completed: item.completed, editing: currentItem === item}" v-for="(item, index) in items" :key="item.id"> <div class="view"> <input class="toggle" type="checkbox" > <label >{{ item.content }}</label> <button class="destroy" ></button> </div> <input class="edit" > </li> </ul> ... <footer class="footer" v-show="items.length">
3.2、添加条目
- 功能:在最上面文本输入框添加键盘事件,敲击enter 添加新条目
- 条件:添加的新条目不能为空
-
index.html
<input class="new-todo" placeholder="What needs to be done?" v-focus @keyup.enter="addItem">
-
app.js
// 添加条目methods方法 addItem(event) { let content = event.target.value.trim() if(!content) { return } let id = this.items[this.items.length - 1].id + 1 this.items.push({ id, content, completed: false }) event.target.value = '' },
3.3、删除条目
- 功能:点击条目后面的X号,删除对应的条目
- 实现:X号对应的元素添加鼠标点击事件,完成上述功能
-
index.html
<button class="destroy" @click="deleteItem(item.id)"></button>
-
app.js
// 删除给定ID的条目 deleteItem(id) { this.items = this.items.filter(item => item.id !== id) },
3.4、统计未完成事项
-
图示:
-
功能:显示未完成事项数量,为1时,后面显示’item’;不为1时,后面显示’items’;
-
实现:添加一个计算属性 total,用来统计数组中事项completed为true的条目数量
-
index.html
<span class="todo-count"><strong>{{ total }}</strong> item{{ total === 1? '': 's'}} left</span>
-
app.js
computed: { total() { return this.items.filter(item => !item.completed).length } }
3.5、清除已完成事项
-
图示3.5-1:
-
功能:点击清除已完成事项,当没有已完成事项时,隐藏
-
实现:添加点击事件,用于删除已完成事项;添加v-show指令,当没有已完成事项时(总条目-上面未完成条目)> 0,隐藏
-
index.html
<button class="clear-completed" v-show="items.length > total" @click="clearCompleted">清除已完成事项</button>
-
app.js
// 清除已完成条目 clearCompleted() { this.items = this.items.filter(item => !item.completed) },
3.6、全选复选框切换状态
-
图示
-
事项全部选中,复选框状态改为选中:
-
点击复选框,复选框选中,下面条目选中;复选框未选中,下面条码全部未选中:
-
-
index.html
<input id="toggle-all" class="toggle-all" type="checkbox" v-model="toggleAll">
-
app.js
toggleAll: { get() { return this.total === 0 }, set(newStatus) { this.items.forEach(item => { item.completed = newStatus }) } },
3.7、路由状态切换
功能:点击图示3个不同路由,显示不同的内容
实现:给3个a标签href绑定不同的hash值,通过监听window.onhashchange事件,获取不同的filterStatus(属性)
,根据不同的filterStatus,渲染不同的数组内容filterItems(计算属性)
-
index.html
<li :class="{completed: item.completed}" v-for="(item, index) in filterItems" :key="item.id">
-
app.js
// vue实例data内 filterStatus: '', // vue实例vm外 window.onhashchange =function () { // console.log(location.hash.slice(1)); vm.filterStatus = location.hash.slice(1) } // 计算属性filterItems filterItems() { // console.log(this.filterStatus); let items switch(this.filterStatus) { case '/active': // console.log(1); items = this.items.filter(item => !item.completed) break case '/completed': // console.log(2); items = this.items.filter(item => item.completed) break default: // console.log(3); items = this.items break; } return items }
3.8、编辑事项
- 双击<label>标签,进入编辑状态
- 绑定双击事件
- 编辑状态下,按ESC,退出编辑
- 绑定keyup.esc事件
- 编辑状态下,按ENTER或者失去焦点,完成编辑
- 绑定keyup.enter,blur事件,执行相同的功能
- 内容为空,删除当前条目;内容不为空,更新当前条目
- 3执行的逻辑
-
index.html
<label @dblclick=toEdit(item)>{{ item.content }}</label> ... <input class="edit" :value="item.content" @keyup.esc="cancelEdit" @keyup.enter="$event.target.blur" @blur="finishEdit(item, index, $event)" v-focus="item === currentItem">
-
app.js
// vue实例data currentItem: {} // vue实例methods // 双击开始编辑 toEdit(item) { this.currentItem = item }, // 取消编辑 cancelEdit() { this.currentItem = {} }, // 完成编辑 finishEdit(item, index, event) { const content = event.target.value.trim() if(!content) { // console.log(item); // console.log(event); this.deleteItem(item.id) return } item.content = content this.currentItem = {} }
3.9、数据的本地持久存储
3.9.1、localStorage与sessionStorage简介
localStorage
- 浏览器本地持久化存储,如果不手动清空localStorage,默认永久存储
- 大小一般为5M,不同浏览器存储空间不同
- 类型为json字符串,值类型为字符串
- 不同浏览器不能共享
- 同一个浏览器不同页面或者标签共享相同属性名的数据
sessionStorage
- 当前会话的浏览器本地存储
- 关闭浏览器或者会话结束,数据消失
- 其他同lcalStorage
详细使用自行查阅相关文档。
3.9.2、查看方式
打开浏览器,右键查看或者f12点击开发者窗口->Application选项卡↓localStorage
- 图示:
3.9.3、javascript的localStorage常用API
- localStorage.setItem(key, value)
- localStorage.getItem(key)
- localStorage.removeItem(key)
- 解析:
- key,value必须为字符串
- getItem如果key不存在,结果:undefied
- 存取JSON对象
- setItem(key, JSON.stringify(object))
- JSON.parse(getItem(key))
3.9.4、本项目的本地持久存储
- 监听(深度)vue实例items的变化,一旦改变,存储到本地
- vue实例初始化的时候,调用created钩子函数读取本地存储的数据条目
-
app.js
// created钩子函数 created() { this.getItems() }, // methods // 获取条目 getItems() { this.items = JSON.parse(localStorage.getItem('item')) || [] }, // watch侦听器 watch: { items: { handler(val) { localStorage.setItem('item', JSON.stringify(val)) }, deep: true, immediate: true } }
-
查看存储图示:
3.10,其他功能
- 文本输入框,自动获取焦点
实现:自定义全局指令,添加到表单表上
-
index.html
... <input class="new-todo" placeholder="添加待办事项..." v-focus @keyup.enter="addItem"> ... <input class="edit" :value="item.content" @keyup.esc="cancelEdit" @keyup.enter="$event.target.blur" @blur="finishEdit(item, index, $event)" v-focus="item === currentItem">
-
app.js
Vue.directive('focus', { inserted(el, binding) { el.focus() }, update(el, binding) { // console.log(binding.value); if(binding.value){ el.focus() } } })
5、完整代码
- 需求分析
- 效果图:
-
index.html
<!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>简易备忘录</h1> <input class="new-todo" placeholder="添加待办事项..." v-focus @keyup.enter="addItem"> </header> <!-- This section should be hidden by default and shown when there are todos --> <section class="main" v-show="items.length"> <input id="toggle-all" class="toggle-all" type="checkbox" v-model="toggleAll"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <li :class="{completed: item.completed, editing: currentItem === item}" v-for="(item, index) in filterItems" :key="item.id"> <div class="view"> <input class="toggle" type="checkbox" v-model="item.completed"> <label @dblclick=toEdit(item)>{{ item.content }}</label> <button class="destroy" @click="deleteItem(item.id)"></button> </div> <input class="edit" :value="item.content" @keyup.esc="cancelEdit" @keyup.enter="$event.target.blur" @blur="finishEdit(item, index, $event)" v-focus="item === currentItem"> </li> </ul> </section> <!-- This footer should hidden by default and shown when there are todos --> <footer class="footer" v-show="items.length"> <!-- This should be `0 items left` by default --> <span class="todo-count"><strong>{{ total }}</strong> item{{ total === 1? '': 's'}} left</span> <!-- Remove this if you don't implement routing --> <ul class="filters"> <li> <a :class="{selected: filterStatus === '/'}" href="#/">全部</a> </li> <li> <a href="#/active" :class="{selected: filterStatus === '/active'}">未完成事项</a> </li> <li> <a href="#/completed" :class="{selected: filterStatus === '/completed'}">已完成事项</a> </li> </ul> <!-- Hidden if no completed items are left ↓ --> <button class="clear-completed" v-show="items.length > total" @click="clearCompleted">清除已完成事项</button> </footer> </section> <footer class="info"> <p>双击编辑事项</p> <!-- Remove the below line ↓ --> <p>Template by <a href="http://sindresorhus.com">gaogzhen</a></p> <!-- Change this out with your name and url ↓ --> <p>Created by <a href="http://todomvc.com">gaogzhern</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>
-
app.js
(function () { // 'use strict'; // Your starting point. Enjoy the ride! const itemList = [ { id: 1, content: '晨跑', completed: true }, { id: 2, content: '吃早饭', completed: true }, { id: 3, content: '学习前端课程', completed: false }, { id: 4, content: '吃午饭', completed: false } ] Vue.directive('focus', { inserted(el, binding) { el.focus() }, update(el, binding) { // console.log(binding.value); if(binding.value){ el.focus() } } }) const vm = new Vue({ el: '#app', data: { items: itemList, filterStatus: '', currentItem: {} }, directives: { // focus: { // inserted(el) { // el.focus() // } // } }, computed: { total() { return this.items.filter(item => !item.completed).length }, toggleAll: { get() { return this.total === 0 }, set(newStatus) { this.items.forEach(item => { item.completed = newStatus }) } }, filterItems() { // console.log(this.filterStatus); let items switch(this.filterStatus) { case '/active': // console.log(1); items = this.items.filter(item => !item.completed) break case '/completed': // console.log(2); items = this.items.filter(item => item.completed) break default: // console.log(3); items = this.items break; } return items } }, methods: { // 获取条目 getItems() { this.items = JSON.parse(localStorage.getItem('item')) || [] }, // 添加条目 addItem(event) { let content = event.target.value.trim() if(!content) { return } let id = this.items[this.items.length - 1].id + 1 this.items.push({ id, content, completed: false }) event.target.value = '' }, // 删除给定ID的条目 deleteItem(id) { this.items = this.items.filter(item => item.id !== id) }, // 清除已完成条目 clearCompleted() { this.items = this.items.filter(item => !item.completed) }, // 双击开始编辑 toEdit(item) { this.currentItem = item }, // 取消编辑 cancelEdit() { this.currentItem = {} }, // 完成编辑 finishEdit(item, index, event) { const content = event.target.value.trim() if(!content) { // console.log(item); // console.log(event); this.deleteItem(item.id) return } item.content = content this.currentItem = {} } }, mounted() { }, created() { this.getItems() }, watch: { items: { handler(val) { localStorage.setItem('item', JSON.stringify(val)) // console.log(newVal); // console.log(oldVal); // console.log(val); // console.log(this.items); }, deep: true, immediate: true } } }) window.onhashchange =function () { // console.log(location.hash.slice(1)); vm.filterStatus = location.hash.slice(1) } })();
至此本项目完结,做到到这里的小伙伴,说明你们vue基础已经掌握熟练,可以继续学习vue高级知识部分。
幸福是什么样子呢?
后记 :
vue官网地址:https://cn.vuejs.org/
本项目为参考某图培训开发,相关视频及配套资料可自行度娘或者联系本人。上面为自己编写的开发文档,持续更新。欢迎交流,本人QQ:806797785
前端项目源代码地址:https://gitee.com/gaogzhen/vue
后端JAVA源代码地址:https://gitee.com/gaogzhen/JAVA