经典实战项目-TodoMVC
TodoMVC 是一个非常经典的案例,功能非常丰富,并且针对多种不同技术分别都开发了此项目,比如React、AngularJS、JQuery等等,本文使用Vue开发。
1 项目介绍与演示
TodoMVC 是一个非常经典的案例,功能非常丰富,并且针对多种不同技术分别都开发了此项目,比如React、AngularJS、JQuery等等。
TodoMVC 案例官网:http://todomvc.com/
在官网首页右下角, 有 案例的模板下载 和 开发规范(需求文档),如下图:
2 需求说明
2.1 数据列表渲染
当任务列表(items )没有数据时, #main 和#footer 标识的标签应该被隐藏
任务涉及字段:id、任务名称( name),是否完成(completed true为已完成)
2.2 添加任务
-
在最上面的文本框中添加新的任务。
-
不允许添加非空数据。
-
按Enter键添加任务列表中,并清空文本框。
-
当加载页面后文本框自动获得焦点,在 input 上使用 autofocus 属性可获得。
2.3 显示所有未完成任务数
-
左下角要显示未完成的任务数量。确保数字是由标签包装的。
-
还要将item单词多元化( 1 没有s , 其他数字均有s ): 0 items , 1 item ,2 items
示例:
2.4切换所有任务状态
2.5 移除任务项
2.6 清除所有已完成任务
-
单击右下角Clear completed按钮时,移除所有已完成任务。
-
单击Clear completed按钮后,确保复选框清除了选中状态
-
当列表中没有已完成的任务时,应该隐藏Clear completed按钮。
2.7 编辑任务项
-
双击
- 上通过.editing进行切换状态)。
-
进入编辑状态后输入框显示原内容,并获取编辑焦点。
-
输入状态按Esc 取消编辑, editing 样式应该被移除。
-
按Enter键 或 失去焦点时 保存改变数据,移除editing 样式;
2.8 路由状态切换(过滤不同状态数据)
根据点击的不同状态( All / Active / Completed ),进行过滤出对应的任务,并进行样式的切换。
3 效果展示
4 完整源码
4.1 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="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus @keyup.enter="addItem">
</header>
<template v-if="items.length">
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main">
<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 v-for="(item,index) in filterItems"
v-bind:class="{completed:item.completed , editing:currentItem===item}">
<div class="view">
<input class="toggle" type="checkbox" v-model="item.completed">
<label @dblclick="toEdit(item)">{{item.content}}</label>
<button class="destroy" @click="removeItem(index)"></button>
</div>
<input class="edit" :value="item.content" @keyup.esc="canceEdit"
v-todofocus="currentItem===item" @keyup.enter="finishEdit(index,item,$event)" @blur="finishEdit(index,item,$event)">
</li>
</ul>
</section>
<!-- This footer should hidden by default and shown when there are todos -->
<footer class="footer">
<!-- This should be `0 items left` by default -->
<span class="todo-count"><strong>{{remaining}}</strong> item{{remaining === 1 ? "" : "s"}} left</span>
<!-- Remove this if you don't implement routing -->
<ul class="filters">
<li>
<a class="selected" href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<!-- Hidden if no completed items are left ↓ -->
<button class="clear-completed" v-show="items.length>remaining" @click="removeCompleted">Clear
completed</button>
</footer>
</template>
</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>
4.2 app.js
(function (Vue) {
var items = [
{
id:1,
content:'vue',
completed:true
},
{
id:2,
content:'js',
completed:false
},
{
id:3,
content:'node',
completed:true
},
{
id:4,
content:'ts',
completed:false
},
]
var vm = new Vue({
el:"#todoapp",
data:{
items:items,
currentItem:[],
filterStatus:'all',
},
directives:{
'todofocus':{
update(el,binding){
if(binding.value){
el.focus();
}
}
}
},
methods:{
addItem(){
var content = event.target.value.trim();
if(!content.length){
return
}
var id = this.items.length + 1;
this.items.push({
id:id,
content:content,
completed:false
})
event.target.value = '';
},
removeItem(index){
this.items.splice(index,1)
},
removeCompleted(){
this.items = this.items.filter(function(item){
return !item.completed
})
},
toEdit(item){
this.currentItem = item;
},
canceEdit(){
this.currentItem = ''
},
finishEdit(index,item,event){
if(!event.target.value.trim()){
return this.removeItem(index);
}
item.content = event.target.value.trim();
this.canceEdit()
}
},
computed:{
filterItems(){
switch(this.filterStatus){
case 'active' : return this.items.filter(item => !item.completed);
break;
case 'completed' : return this.items.filter(item => item.completed);
break;
default : return this.items;
}
},
remaining(){
return this.items.filter(function(item){
return !item.completed
}).length
},
toggleAll:{
get(){
return this.remaining === 0
},
set(newValue){
this.items.forEach(function(item){
item.completed = newValue;
})
}
}
}
})
window.onhashchange = function(){
var hash = window.location.hash.substr(2) || 'all';
vm.filterStatus = hash;
}
})(Vue);
更多推荐
所有评论(0)