阅读目录
运行效果
源码
html 代码中需要插入css 代码,在最下方需插入vue 代码即可运行。
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<!--插入css-->
</head>
<body>
<div id="app" class="main">
<h2>小目标列表</h2>
<div class="list">
<h3>添加小目标</h3>
<input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认" @keyup.13='addList' v-model="addText"/>
<!--如果noend等于0,就是全部完成了就显示‘全部完成了’,如果没有就是显示已完成多少条(prolist.length-noend)和未完成多少条(noend)-->
<p>共有{
{prolist.length}}个目标,{
{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',还有'+noend+'条未完成'}}</p>
<p>
<input type="radio" name="chooseType" checked="true" @click='chooseList(1)'/><label>所有目标</label>
<input type="radio" name="chooseType" @click='chooseList(2)'/><label>已完成目标</label>
<input type="radio" name="chooseType" @click='chooseList(3)'/><label>未完成目标</label>
</p>
</div>
<ul>
<li class="li1" v-for="(list,index) in newList" :class="{
'eidting':curIndex===index}">
<div>
<span class="status-span" @click="changeType(index)" :class="{
'status-end':list.status}"></span>
<span @dblclick="curIndex=index">{
{list.name}}</span>
<span class="close" @click='delectList(list)'>X</span>
</div>
<input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited' v-focus/>
</li>
</ul>
</div>
</body>
<!--插入js-->
</html>
CSS
<style>
body
{
font-family:微软雅黑;
font-size:14px;
}
input
{
font-size:14px;
}
body,ul,div,html
{
margin:0;
padding:0;
}
.main
{
width:800px;
margin:0 auto;
}
li
{
list-style-type:none;
line-height:40px;
position:relative;
border:1px solid transparent;
padding:0 20px;
}
li .status-span
{
display:block;
width:10px;
height:10px;
background:#ccc;
float:left;
margin:14px 10px 0 0;
}
li .status-span.status-end
{
background:#09f;
}
li .close
{
position:absolute;
color:red;
font-size:20px;
line-height:40px;
height:40px;
right:20px;
cursor:pointer;
display:none;
top:0;
}
li:hover
{
border:1px solid #09f;
}
.text-keyword
{
box-sizing:border-box;
width:100%;
height:40px;
padding-left:10px;
outline:none;
}
.hidden,li.eidting div
{
display:none;
}
li:hover .close,li div,li.eidting .text2
{
display:block;
}
li .text2,li .text-keyword
{
height:40px;
padding-left:10px;
box-sizing:border-box;
margin-left:10px;
width:80%;
display:none;
}
</style>
JS
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script type="text/javascript">
new Vue({
el: "#app",
data: {
addText:'',
//name-名称,status-完成状态
prolist:[
{
name:"HTML5",status:false},
{
name:"CSS3",status:false},
{
name:"vue",status:false},
{
name:"react",status:false}
],
newList:[],
curIndex:'',
beforeEditText:"",
curType:0
},
computed:{
//计算属性,返回未完成目标的条数,就是数组里面status=false的条数
noend:function(){
return this.prolist.filter(function(item){
return !item.status
}).length;
}
},
methods:{
addList(){
//添加进来默认status=false,就是未完成状态
this.prolist.push({
name:this.addText,
status:false
});
//添加后,清空addText
this.addText="";
},
chooseList(type){
//type=1时,选择所有目标
//type=2时,选择所有已完成目标
//type=3时,选择所有未完成目标
this.curType=type;
switch(type){
case 1:this.newList=this.prolist;break;
case 2:this.newList=this.prolist.filter(function(item){
return item.status});break;
case 3:this.newList=this.prolist.filter(function(item){
return !item.status});break;
}
},
/*改变单条数据的完成状态*/
changeType(index){
this.newList[index].status=!this.newList[index].status;
//更新数据
this.chooseList(this.curType);
},
delectList(list){
var index=this.prolist.indexOf(list);
//根据索引,删除数组某一项
this.prolist.splice(index,1);
//更新newList newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList 那么就要手动更新newList
//this.newList=this.prolist;
this.chooseList(this.curType);
},
//修改前
editBefore(name){
//先记录当前项(比如这一项,{name:"HTML5",status:false})
//beforeEditText="HTML5"
this.beforeEditText=name;
},
//修改完成后
edited(){
//修改完了,设置curIndex="",这样输入框就隐藏,其它元素就会显示。因为在li元素 写了::class="{'eidting':curIndex===index}" 当curIndex不等于index时,eidting类名就清除了!
//输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步只是清除eidting类名,隐藏输入框而已
//还有一个要注意的就是虽然li遍历的是newList,比如改了newList的这一项({name:"HTML5",status:false}),比如改成这样({name:"HTML",status:true})。实际上prolist的这一项({name:"HTML5",status:false}),也会被改成({name:"HTML",status:true})。因为这里是一个对象,而且公用一个堆栈!修改其中一个,另一个会被影响到
this.curIndex="";
},
//取消修改
cancelEdit(val){
//上面说了输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步就是把之前保存的beforeEditText赋值给当前项的name属性,起到一个恢复原来值得作用!
val.name=this.beforeEditText;
this.curIndex="";
}
},
mounted(){
//初始化,把prolist赋值给newList。默认显示所有目标
this.newList=this.prolist;
},
directives:{
"focus":{
update(el){
el.focus();
}
}
}
});
</script>
原理分析和实现
首先,还是先把布局写好,和引入vue,准备vue实例,这个不多说,代码如下。
步骤 1 布局和引入 vue
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
body {
font-family: "微软雅黑";
font-size: 14px;
}
input {
font-size: 14px;
}
body,
ul,
div,
html {
padding: 0;
margin: 0;
}
.hidden {
display: none;
}
.main {
width: 800px;
margin: 0 auto;
}
li {
list-style-type: none;
line-height: 40px;
position: relative;
border: 1px solid transparent;
padding: 0 20px;
}
li .type-span {
display: block;
width: 10px;
height: 10px;
background: #ccc;
margin: 14px 10px 0 0;
float: left;
}
li .close {
position: absolute;
color: #f00;
font-size: 20px;
line-height: 40px;
height: 40px;
right: 20px;
cursor: pointer;
display: none;
top: 0;
}
li:hover {
border: 1px solid #09f;
}
li:hover .close {
display: block;
}
li .text-keyword {
height: 40px;
padding-left: 10px;
box-sizing: border-box;
margin-left: 10px;
width: 80%;
display: none;
}
.text-keyword {
box-sizing: border-box;
width: 100%;
height: 40px;
padding-left: 10px;
outline: none;
}
</style>
</head>
<body>
<div id="app" class="main">
<h2>小目标列表</h2>
<div class="list">
<h3>添加小目标</h3>
<input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认" />
<p>共有N个目标</p>
<p>
<input type="radio" name="chooseType" checked="true" /><label>所有目标</label>
<input type="radio" name="chooseType" /><label>已完成目标</label>
<input type="radio" name="chooseType" /><label>未完成目标</label>
</p>
</div>
<ul>
<li class="li1">
<div>
<span class="type-span"></span>
<span>html5</span>
<span class="close">X</span>
</div>
</li>
<li class="li1">
<div>
<span class="type-span"></span>
<span>css3</span>
<span class="close">X</span>
</div>
</li>
</ul>
</div>
</body>
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script type="text/javascript">
new Vue({
el: "#app",
data: {
},
computed: {
},
methods: {
}
});
</script>
</html>
布局有了,相当于一个骨架就有了,下面实现功能,一个一个来。
步骤 2 输入框回车要添加纪录
输入并回车,多一条记录,下面的记录文字也会改变。
首先,大的输入框回车要添加纪录,那么输入框必须绑定一个值和一个添加纪录的方法。
然后,下面的记录也要改变,所以,下面的记录也要帮一个值,因为这个记录可能会有多个,这个值就是一个数组,也可以看到,记录除了名称,还有记录是否完成的状态,所以,绑定记录的这个值肯定是一个对象数组!
为了着重表示我修改了什么地方,代码我现在只贴出修改的部分,大家对着上面的布局,就很容易知道我改的是什么地方了!
<!--利用v-model把addText绑定到input-->
<input type="text"
class="text-keyword"
placeholder="输入小目标后,按回车确认"
@keyup.13='addList' v-model="addText"/>
<p>共有{
{prolist.length}}个目标</p>
<!--v-for遍历prolist-->
<li class="li1" v-for="list in prolist">
<div>
<span class="type-span"></span>
<span>{
{list.name}}</span>
<span class="close">X</span>
</div>
</li>
new Vue({
el: "#app",
data: {
addText:'',
//name-名称,status-完成状态
prolist:[
{
name:"HTML5",status:false},
{
name:"CSS3",status:false},
{
name:"vue",status:false},
{
name:"react",status:false}
]
},
computed:{
},
methods:{
addList(){
//添加进来默认status=false,就是未完成状态
this.prolist.push({
name:this.addText,
status:false
});
//添加后,清空addText
this.addText="";
}
}
});
测试一下,没问题
步骤 3 点击切换,下面记录会改变
看到三个选项,也很简单,无非就是三个选择,一个是所有的目标,一个是所有已经完成的目标,一个是所有没完成的目标。
首先,新建一个新的变量(newList),储存 prolist。遍历的时候不再遍历 prolist,而是遍历 newList。改变也是改变 newList。
然后选择所有目标的时候,显示全部 prolist,把 prolist 赋值给 newList。
然后选择所有已经完成目标的时候,只显示 prolist 中,status 为 true 的目标,把 prolist中,status 为 true 的项赋值给 newList,最后.选择所有未完成目标的时候,只显示 status 为 false 的目标,把 prolist 中,status 为 false 的项赋值给 newList。
<ul>
<li class="li1" v-for="list in newList">
<div>
<span class="status-span"></span>
<span>{
{list.name}}</span>
<span class="close" @click='delectList(index)'>X</span>
</div>
</li>
</ul>
new Vue({
el: "#app",
data: {
addText:'',
//name-名称,status-完成状态
prolist:[
{
name:"HTML5",status:false},
{
name:"CSS3",status:false},
{
name:"vue",status:false},
{
name:"react",status:false}
],
newList:[]
},
computed:{
noend:function(){
return this.prolist.filter(function(item){
return !item.status
}).length;
}
},
methods:{
addList(){
//添加进来默认status=false,就是未完成状态
this.prolist.push({
name:this.addText,
status:false
});
//添加后,清空addText
this.addText="";
},
chooseList(type){
//type=1时,选择所有目标
//type=2时,选择所有已完成目标
//type=3时,选择所有未完成目标
switch(type){
case 1:this.newList=this.prolist;break;
case 2:this.newList=this.prolist.filter(function(item){
return item.status});break;
case 3:this.newList=this.prolist.filter(function(item){
return !item.status});break;
}
},
delectList(index){
//根据索引,删除数组某一项
this.prolist.splice(index,1);
//更新newList newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList 那么就要手动更新newList
this.newList=this.prolist;
},
},
mounted(){
//初始化,把prolist赋值给newList。默认显示所有目标
this.newList=this.prolist;
}
});
运行结果
步骤 4 红色关闭标识,点击会删除该条记录
红色关闭标识,点击会删除该记录。前面按钮点击会切换该记录完成状态,颜色也改变,记录文字也跟着改变。
首先点击红色关闭标识,点击会删除该记录。
这个应该没什么问题,就是删除 prolist 的一条记录!
然后前面按钮点击会切换该记录完成状态。
这个也没什么,就是改变 prolist 的一条记录的 status 字段!
最后记录文字的改变,就是记录 prolist 中 status 为 false 的有多少条,prolist 中 status 为true 的有多少条而已。
<!--
如果 noend 等于0,就是全部完成了就显示‘全部完成了’,
如果没有就是显示已完成多少条(prolist.length-noend)和未完成多少条(noend)-->
<p>
共有{
{prolist.length}}个目标,
{
{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',
还有'+noend+'条未完成'}}
</p>
<ul>
<li class="li1" v-for="(list,index) in newList">
<div>
<span class="status-span"
@click="list.status=!list.status"
:class="{
'status-end':list.status}">
</span>
<span>{
{list.name}}</span>
<span class="close" @click='delectList(index)'>X</span>
</div>
</li>
</ul>
new Vue({
el: "#app",
data: {
addText:'',
//name-名称,status-完成状态
prolist:[
{
name:"HTML5",status:false},
{
name:"CSS3",status:false},
{
name:"vue",status:false},
{
name:"react",status:false}
],
newList:[]
},
computed:{
//计算属性,返回未完成目标的条数,就是数组里面status=false的条数
noend:function(){
return this.prolist.filter(function(item){
return !item.status
}).length;
}
},
methods:{
addList(){
//添加进来默认status=false,就是未完成状态
this.prolist.push({
name:this.addText,
status:false
});
//添加后,清空addText
this.addText="";
},
chooseList(type){
switch(type){
case 1:this.newList=this.prolist;break;
case 2:this.newList=this.prolist.filter(function(item){
return item.status});break;
case 3:this.newList=this.prolist.filter(function(item){
return !item.status});break;
}
},
delectList(index){
//根据索引,删除数组某一项
this.prolist.splice(index,1);
//更新newList newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList 那么就要手动更新newList
this.newList=this.prolist;
},
},
mounted(){
this.newList=this.prolist;
}
});
运行结果
步骤 5 文字双击会出现输入框,可输入文字
文字双击会出现输入框,可输入文字,如果回车或者失去焦点,就改变文字,如果按下ESC就恢复原来的文字。
首先.双击出现输入框,就是双击文字后,给当前的li设置一个类名(‘eidting’),然后写好样式。当li出现这个类名的时候,就出现输入框,并且隐藏其它内容。
然后回车或者失去焦点,就改变文字这个只需要操作一个,就是把类名(‘eidting’)清除掉。然后输入框就会隐藏,其它内容显示!
最后.按下ESC就恢复原来的文字,就是出现输入框的时候,用一个变量(‘beforeEditText’)先保存当前的内容,然后按下了ESC,就把变量(‘beforeEditText’)赋值给当前操作的值!
<ul>
<li class="li1"
v-for="(list,index) in newList"
:class="{
'eidting':curIndex===index}">
<div>
<span class="status-span" @click="list.status=!list.status" :class="{
'status-end':list.status}"></span>
<span @dblclick="curIndex=index">{
{list.name}}</span>
<span class="close" @click='delectList(index)'>X</span>
</div>
<input type="text"
class="text2"
v-model='list.name'
@keyup.esc='cancelEdit(list)'
@blur='edited'
@focus='editBefore(list.name)'
@keyup.enter='edited'/>
</li>
</ul>
li div{
display: block;}
li.eidting div{
display: none;}
li .text2{
height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
li.eidting .text2{
display: block;}
methods:{
addList(){
//添加进来默认status=false,就是未完成状态
this.prolist.push({
name:this.addText,
status:false
});
//添加后,清空addText
this.addText="";
},
chooseList(type){
//type=1时,选择所有目标
//type=2时,选择所有已完成目标
//type=3时,选择所有未完成目标
switch(type){
case 1:this.newList=this.prolist;break;
case 2:this.newList=this.prolist.filter(function(item){
return item.status});break;
case 3:this.newList=this.prolist.filter(function(item){
return !item.status});break;
}
},
delectList(index){
//根据索引,删除数组某一项
this.prolist.splice(index,1);
//更新newList newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList 那么就要手动更新newList
this.newList=this.prolist;
},
//修改前
editBefore(name){
//先记录当前项(比如这一项,{name:"HTML5",status:false})
//beforeEditText="HTML5"
this.beforeEditText=name;
},
//修改完成后
edited(){
//修改完了,设置curIndex="",这样输入框就隐藏,其它元素就会显示。因为在li元素 写了::class="{'eidting':curIndex===index}" 当curIndex不等于index时,eidting类名就清除了!
//输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步只是清除eidting类名,隐藏输入框而已
//还有一个要注意的就是虽然li遍历的是newList,比如改了newList的这一项({name:"HTML5",status:false}),比如改成这样({name:"HTML",status:true})。实际上prolist的这一项({name:"HTML5",status:false}),也会被改成({name:"HTML",status:true})。因为这里是一个对象,而且公用一个堆栈!修改其中一个,另一个会被影响到
this.curIndex="";
},
//取消修改
cancelEdit(val){
//上面说了输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步就是把之前保存的beforeEditText赋值给当前项的name属性,起到一个恢复原来值得作用!
val.name=this.beforeEditText;
this.curIndex="";
}
},
运行结果
还有一个小细节,大家可能注意到了,就是双击文字,出来输入框的时候,还要自己手动点击一下,才能获得焦点,我们想双击了,输入框出来的时候,自动获取焦点,怎么办?
自定义指令就行了!
computed:{
...},
methods:{
...},
mounted(){
...},
directives:{
"focus":{
update(el){
el.focus();
}
}
}
<input type="text"
class="text2"
v-model='list.name'
@keyup.esc='cancelEdit(list)'
@blur='edited'
@focus='editBefore(list.name)'
@keyup.enter='edited' v-focus/>