list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../bower_components/vue/dist/vue.min.js"></script>
<link rel="stylesheet" href="font/iconfont.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<!--emmet写法:section.list>h3.title{列表}+h6.tip.data-null-tip{没有数据}+(ul.list-inner>li.list-item>(div.opt-wrapper>input.opt[type="checkbox"])+(span.title{一条内容})+(div.operate>(a.btn.btn-edit[href="javascript:void(0)"]>i.icon.iconfont.icon-edit[title="编辑"])+(a.btn.btn-del[href="javascript:void(0)"]>i.icon.iconfont.icon-del[title="删除"])+(a.btn.btn-mark[href="javascript:void(0)"]>i.icon.iconfont.icon-mark[title="标记已读"])))-->
<!--div.list-statistical>(span.count{1条数据未读})+(ul.filter>li.list-item{所有数据}*3)-->
<!--section.data-entry>h3.title{添加数据}+(.form-wrapper>lable.hidden[for="dataInput"]+input.form-input.data-input[name="dataInput" placeholder="支持Enter键入"]+btn.btn.btn-add{添加})-->
<article class="data">
<!--添加数据-->
<section class="data-entry">
<h3 class="title">添加数据</h3>
<div class="form-wrapper">
<lable class="hidden" for="dataInput"></lable>
<input type="text" class="form-input data-input" name="dataInput" placeholder="支持Enter键入" v-on:keyup.enter="addData" v-model="inputItem">
<button class="btn btn-add" v-on:click="addData">添加</button>
</div>
</section>
<!--筛选数据-->
<section class="data-filter clearfix" v-show="list.length">
<span class="count">{{noCheckedLength}} 条数据未读</span>
<div class="filter">
<a class="list-item" v-bind:class="{active:visibility === 'all'}" href="#all">所有数据</a>
<a class="list-item" v-bind:class="{active:visibility === 'notmarked'}" href="#notmarked">未读数据</a>
<a class="list-item" v-bind:class="{active:visibility === 'marked'}" href="#marked">已读数据</a>
</div>
</section>
<!--数据列表-->
<section class="data-list">
<h3 class="title">列表</h3>
<!--条件渲染-->
<h6 class="tip data-null-tip" v-show="!list.length">没有数据</h6>
<ul class="list-inner">
<li class="list-item" v-bind:class="{active:item.isChecked, edit:item === editingItem}" v-for="item in filterList">
<div class="opt-wrapper">
<input id="CheckState" name="CheckState" type="checkbox" class="opt" v-model="item.isChecked">
</div>
<span class="title" v-on:dblclick="editData(item)"> {{item.title}} </span>
<div class="form-wrapper">
<lable class="hidden" for="DataEdit"></lable>
<input class="form-input" name="DataEdit" type="text" v-focus="editingItem === item" v-model="item.title" v-on:blur="updateData(item)"
v-on:keyup.enter="updateData(item)" v-on:keyup.esc="cancelEdit(item)">
<button class="btn btn-update" v-on:click="updateData(item)">更新</button>
<button class="btn btn-cancel" v-on:click="cancelEdit(item)">取消</button>
</div>
<div class="operate">
<a href="javascript:void(0)" class="btn btn-edit" v-on:click="editData(item)"><i class="icon iconfont icon-edit" title="编辑"></i></a>
<a href="javascript:void(0)" class="btn btn-del" v-on:click="deleteData(item)"><i class="icon iconfont icon-del" title="删除"></i></a>
</div>
</li>
</ul>
</section>
</article>
<script src="js/app.js"></script>
</body>
</html>
app.js
// 存取 localStorage 中的数据
let store = {
save(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
fetch(key) {
return JSON.parse(localStorage.getItem(key)) || [];
}
};
let list = store.fetch('DataList');
// 数据过滤
let filterData = {
// 所有数据
all: function (list) {
return list;
},
// 未操作数据
notmarked: function (list) {
return list.filter(function (item) {
return !item.isChecked;
})
},
// 已操作数据
marked: function (list) {
return list.filter(function (item) {
return item.isChecked;
})
}
};
let vm = new Vue({
el: ".data", // 挂载点
data: {
list: list, // 数据源
inputItem: "", // 新添加数据
editingItem: null, // 当前编辑项
originalTitle: "", // 正在编辑的数据的原值,取消时候,以恢复
visibility: "all" // 通过属性值变化对数据进行筛选
},
computed: {
// [计算属性](https://cn.vuejs.org/v2/guide/computed.html)
noCheckedLength() {
return this.list.filter(function (item) {
return !item.isChecked
}).length
},
filterList:function() {
// 过滤数据
return filterData[this.visibility] ? filterData[this.visibility](list) : list;
}
},
methods: {
// [方法事件处理器](https://cn.vuejs.org/v2/guide/events.html)
// 添加项至 list .
// ES6 addData(){}
addData: function (ev) {
// ev,执行函数无参,默认是事件对象,有参,需要添加事件对象 "$event"
// 事件处理函数中的this指向是当前根实例
// if (ev.keyCode === 13) {
this.list.push({
// DOM触发
// title: ev.target.value
// 数据驱动
title: this.inputItem,
isChecked: false
});
// }
this.inputItem = '';
},
deleteData(itemTemp) {
let index = this.list.indexOf(itemTemp);
this.list.splice(index, 1);
},
editData(itemTemp) {
this.originalTitle = itemTemp.title;
this.editingItem = itemTemp;
},
updateData(itemTemp) {
this.editingItem = null;
this.originalTitle = '';
},
cancelEdit(itemTemp) {
itemTemp.title = this.originalTitle;
this.editingItem = null;
this.originalTitle = '';
}
},
directives: {
// 除了默认设置的核心指令( v-model 和 v-show ),Vue 也允许注册自定义指令。
// 对纯 DOM 元素进行底层操作
// 注册局部指令,在模板中任何元素上使用新的 v-focus 属性
"focus": {
// 钩子函数:bind inserted update componentUpdated unbind
// 钩子函数的参数:el,binding,vnode,oldVnode
update(el, binding) {
if (binding.value) {
el.focus();
}
}
}
},
watch: { // 浅监控 深监控
/*
list:function(){
store.save("TodoList",this.list);
}
*/
list: {
handler: function (list) {
store.save("DataList", list);
},
deep: true
}
}
});
// 监听Hash
function watchHashChange() {
let hash = window.location.hash.slice(1);
vm.visibility = hash || 'all';
}
watchHashChange();
// https://developer.mozilla.org/zh-CN/docs/Web/Events/hashchange
window.addEventListener("hashchange", watchHashChange);
app.scss(这里上编译后的样式)
body, div, ul { margin: 0; padding: 0; }
body { font-size: 16px; }
ul { list-style: none; }
a { color: #777777; }
a:link { text-decoration: none; }
a:hover, a:focus { color: #333333; }
section { margin-top: 18px; margin-bottom: 18px; }
.clearfix:before, .clearfix:after { content: " "; display: table; }
.clearfix:after { clear: both; overflow: hidden; }
.clearfix { *zoom: 1; }
.hidden { display: none; }
.data { width: 640px; margin-right: auto; margin-left: auto; }
.form-wrapper { position: relative; height: 30px; padding-right: 55px; padding-left: 5px; border: 1px solid #333333; overflow: hidden; box-sizing: border-box; }
.form-wrapper .form-input { width: 100%; height: 28px; line-height: 28px; border: 0; }
.form-wrapper .btn { display: inline-block; position: absolute; right: 0; top: -1px; width: 50px; height: 32px; border: 0; text-align: center; vertical-align: middle; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; background-color: #000; color: #fff; box-sizing: border-box; }
.form-wrapper .btn:active { outline: 0; }
.tip { margin-top: 6px; margin-bottom: 6px; font-size: 1em; color: #777777; }
.data-entry .form-wrapper { width: 100%; }
.data-filter .count { float: left; }
.data-filter .filter { float: right; }
.data-filter .filter .list-item { display: inline-block; color: #777777; cursor: pointer; }
.data-filter .filter .list-item:hover, .data-filter .filter .active { color: #333333; }
.data-list .list-item { clear: both; height: 30px; padding-top: 6px; padding-bottom: 6px; line-height: 30px; border-bottom: 1px solid #eeeeee; }
.data-list .list-item .form-wrapper { display: none; width: 60%; padding-right: 110px; }
.data-list .list-item .form-wrapper .btn { top: 2px; width: 48px; height: 24px; line-height: 24px; border-radius: 12px; opacity: .5; }
.data-list .list-item .form-wrapper .btn-update { right: 52px; }
.data-list .list-item .form-wrapper .btn-cancel { right: 2px; background-color: #555555; }
.data-list .list-item .opt-wrapper, .data-list .list-item .title { float: left; }
.data-list .list-item .opt-wrapper { padding-right: 12px; }
.data-list .list-item .operate { float: right; }
.data-list .list-item .operate .btn .icon { font-size: 1.5em; }
.data-list .active { color: #777777; }
.data-list .edit .title { display: none; }
.data-list .edit .form-wrapper { float: left; display: block; }