前言
第一篇博客居然进了热榜,太感动了。感谢大佬们的抬爱。这几天学着搞一下vue,数学课都在看vue的书(对不起数学老师)。好不容易搞懂一点,于是想着把之前跟着网课做的todolist用vue重写一遍,加了一点自己的想法。顺带一提,我工作室面试过了。接下来要沉寂下来搞数学和工作室的考核了
文章目录
一、vue的基本思想(自己的概括)
vue用的是Observer对象,每当内容发生改变以后,set方法会通知改变的对象,然后重新渲染一次(博主只是初学,可能陈述的不好望见谅)
最终样式
二、todoList分解
todolist样式:
1.todolist分为:以完成的任务和未完成的任务
既然有两组数据,我们就要想着要创建两个数组,分别用来储存doingList和doneList
doingList: [], // 准备做的列表
doneList: [], // 已经做完的列表
我们用的是vue框架,所以两个数组要存放在vue对象的data属性上(后文有详细代码)
2.刚输入时,应该将数据存放到未完成(doingList)中
3.存储数据
我们肯定希望todolist能保存我们写过的任务,JavaScript提供了一种简便的方法:localStorge方法。用于永久性将数据储存到本地上(还有sessionStorage方法,只能暂时性储存数据)这个方法应该用到哪里呢?localStorge储存的数据应该什么时候读取呢?后文会详细讲解
三、分解代码
开始时引入vue.js和animate.css
1.vue在HTML绑定数据
vue是将HTML上的数据进行数据绑定,在HTML里面使用v-for方法可以循环渲染;v-show方法可以判断是否将节点渲染在页面上(也可以使用v-if方法,v-if方法与v-show方法的区别在于v-show方法是判断是否将节点渲染在页面中——类似与display:none;而v-if是判断是否生成这个节点——相当于flase时删除节点)配合这HTML的代码,我们来康康vue是怎么个亚子
<div id="app">
<!-- 点击出现遮罩,datacontain里面放的是点击的item的信息 -->
<transition-group name="fade" enter-active-class="slowerAnimated bounceIn" leave-active-class="slowerAnimated bounceOut">
<div v-show="isClick" class="zhezhao" :key="1">
<div @click="removeZheZhao" class="shade"></div>
<div class="dataConatin" @click="noWriteDate">
<h3 class="dataNo">第{
{
clickItem.id + 1}}个</h3>
<div class="datalist"><span>完成情况:</span><span>{
{
clickItem.isDone? "已完成":"未完成"}}</span></div>
<div class="datalist"><span>设置闹钟情况:</span><span>{
{
clickItem.setClock? "已设置":"未设置"}}</span></div>
<div class="datalist"><span>发表时间:</span><span>{
{
clickItem.date}}</span></div>
<div class="datalist"><span>预期完成时间:</span><span><input @click="writeDate" v-model.trim="completeDate" v-show="isWrite" @keydown.enter="addData" type="text" placeholder="预计完成时间"><span v-show="!isWrite">{
{
clickItem.completeDate? clickItem.completeDate :"未设置"}}</span></span></div>
<div class="datalist"><span>内容:</span><span>{
{
clickItem.contain}}</span></div>
<div class="datalist Bottom"><span><img src="img/闹钟.svg"></span><span @click="writeDate"><img src="img/笔记.svg"></span></div>
</div>
</div>
</transition-group>
<div class="main">
<div class="header">
<div class="title">ToDoList</div>
<!-- 绑定回车事件 -->
<input type="text" v-model.trim="todoitem" @keydown.enter="pushArr" class="need" placeholder="请点老子" />
</div>
<div class="tail">
<div class="section">
<div class="doing">
<h1><span>正在进行</span><span class="num num1">{
{
doingNum}}</span></h1>
<div class="list">
<transition-group name="slide" enter-active-class="animated bounceInLeft" leave-active-class="animated bounceOutRight">
<div v-for="item,index in doingList" class="todoItem" :key="item.id">
<input onclick="return false" @click="pushToDoneList(item)" type="checkbox">
<!-- 这一步的目的是将多余的字符串缩短 -->
<div @click="showToDoDate(item)" class="content">{
{
(item.contain.length > 10)? item.contain.slice(0,5) + "..." : item.contain}}</div>
<div @click="clearDoingArr(item)" class="del">-</div>
</div>
</transition-group>
</div>
</div>
<div class="done">
<h1><span>已经完成</span><span class="num num2">{
{
doneNum}}</span></h1>
<div class="list">
<transition-group name="slide" enter-active-class="animated bounceInRight" leave-active-class="animated bounceOutLeft">
<div v-for="item,index in doneList" class="todoItem" :key="item.id">
<input onclick="return false" @click="pushToDoingList(item)" type="checkbox" checked="checked">
<div @click="showToDoDate(item)" class="content">{
{
(item.contain.length > 10)? item.contain.slice(0,5) + "..." : item.contain}}</div>
<div @click="clearDoneArr(item)" class="del">-</div>
</div>
</transition-group>
</div>
</div>
</div>
<div id="sijie">
<h6>@思杰出品</h6>
</div>
</div>
</div>
</div>
我们可以看到span标签后者是其他标签上有{ {balabalabala}}这个是vue的数据绑定的一种格式:通过vue对象上的data属性上的数据值渲染在HTML上,这句话可能有一点怪(主要是博主嘴太笨了)可以康康vue的官方文档查查官方的描述
vue的设置动画的方式有一个我特别喜欢:transition-group方法,就是渲染元素节点时动态的改变元素节点的class类名:enter-active-class为将元素渲染到页面中时给元素添加的类名;leave-active-class为将元素从页面中移出添加的类名。具体用法可以参照上面的代码。注意的一点是:需要将要用到过度动画的元素节点上添加一个key值绑定唯一元素。如上面代码所示。
最后的最后,不要忘记给自己的作品一个@的小小标志奥
2.css部分
老规矩直接上代码,基础的就不讲了,animated的代码在官网上可以下载到
html {
font-size: 20px;
}
* {
margin: 0;
padding: 0;
}
body {
width: 100%;
height: 100vh;
background: #CCCCCC;
}
table, th, tr, td{
border: none;
}
table{
height: 100%;
width: 100%;
}
th{
text-align: start;
}
/* 解决span标签超出父元素后自动换行的问题 */
span {
white-space: normal;
word-break: break-all;
}
.datalist span{
display: block;
flex: 1;
}
.header {
display: flex;
height: 2.5rem;
width: 100%;
background: black;
opacity: 0.8;
color: #ccc;
line-height: 2.5rem;
font-size: 1.2rem;
align-content: center;
align-items: center;
justify-content: space-between;
}
.header .title {
margin: 0 0.4rem;
}
.tail {
height: 100%;
width: 100%;
background: #CCCCCC;
}
.header>.need {
height: 40%;
width: 11rem;
border: 3px solid #CfCfcf;
border-radius: 6px;
margin-right: 1rem;
padding: 0 5px;
}
.doing h1,
.done h1 {
font-size: 1.2rem;
padding-top: 0.7rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
display: flex;
justify-content: space-between;
text-align: center;
}
.num {
font-size: 0.8rem;
color: #666;
background: #E6E6FA;
text-align: center;
height: 1rem;
line-height: 1rem;
width: 1rem;
border: 1px solid #ccc;
border-radius: 0.5rem;
}
#sijie {
margin-top: 0.5rem;
text-align: center;
color: #666666;
font-size: 0.5rem;
}
.list {
padding: 0.5rem 0;
}
.list .todoItem {
margin: 0.5rem 0.5rem;
display: flex;
font-size: 0.8rem;
height: 1.6rem;
line-height: 1.6rem;
text-align: center;
align-items: center;
background: #fff;
border-radius: 0.2rem;
border-left: 0.3rem solid cadetblue;
}
.done .todoItem {
opacity: 0.5;
border-left: 0.3rem solid #666666;
}
.doing .list .todoItem input,
.done .list .todoItem input {
width: 1rem;
height: 1rem;
margin: 0 1rem;
}
.list .content {
width: 15rem;
text-align: start;
}
.list .del {
background: #ccc;
color: #FFFFFF;
width: 1rem;
height: 1rem;
line-height: 1rem;
border: 6px double #FFFFFF;
border-radius: 1rem;
}
.PC{
display: none;
}
.zhezhao{
width: 100vw;
height: 100vh;
z-index: 5;
position: fixed;
top: 0;
left: 0;
}
.shade{
width: 100%;
height: 100%;
background: #000000;
opacity: 0.3;
}
.dataConatin{
position: fixed;
height: 300px;
width: 300px;
background: white;
border-radius: 20px;
top: calc(50% - 150px);
left: calc(50% - 150px);
}
.dataValue{
color: red;
}
.dataNo{
text-align: center;
}
.datalist{
display: flex;
justify-content: space-between;
font-size: 15px;
}
.datalist span:nth-child(2){
color: #0019ff;
}
.datalist input{
border: none;
outline: 1px solid #000000;
}
.Bottom{
position: absolute;
width: 100%;
bottom: 0;
height: 50px;
border-top: 1px solid #CCCCCC;
}
.Bottom span img{
display: block;
width: 40px;
height: 40px;
margin: 0 auto;
}
.Bottom span:nth-child(1){
border-right: 1px solid #CCCCCC;
}
对了这里提一嘴,很多时候要用到span标签,而span标签里面的文字内容是不会自动转行的,所以我们要在span的css上添加一些样式。上文代码部分我做了注释,这个博主觉得还是可以记一记的(rem布局也要学学,移动端很常用的,我还在努力从px过渡到rem中)
3.最最最重要的js
先上代码
<!-- js部分 -->
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
doingList: [], // 准备做的列表
doneList: [], // 已经做完的列表
todoitem: '', //绑定输入框的value值
clickItem: {
},// 储存点击的item的信息
isClick: false,// 标记是否有点击——标记是否要渲染遮罩
isWrite:false,// 标记是否写入数据
completeDate:''// 储存写入的完成时间的value值
},
computed: {
// 数据绑定
doingNum: function() {
return this.doingList.length;
},
doneNum: function() {
return this.doneList.length
}
},
methods: {
//保存数据到本地存储
saveDate: function() {
localStorage.doingList = JSON.stringify(this.doingList); // 将未完成的任务储存到本地内存中
localStorage.doneList = JSON.stringify(this.doneList); // 将已经完成的任务储存到本地内存中
},
pushArr: function(event) {
if (this.todoitem != '') {
this.doingList.push({
contain: this.todoitem,
id: this.doingList.length,
date: new Date().toLocaleDateString() + new Date().toLocaleTimeString(),
isDone: false,
setClock: false
})
}
this.todoitem = ''
//保存数据
this.saveDate()
},
//点击复选款改变item位置
pushToDoneList: function(item) {
this.doingList.splice(item.id, 1),
this.doingList.forEach((aitem, i) => {
aitem.id = i
})
item.id = this.doneList.length;
item.isDone = !item.isDone;
this.doneList.push(item)
//保存数据
this.saveDate()
},
pushToDoingList: function(item) {
console.log(item)
this.doneList.splice(item.id, 1),
this.doneList.forEach((aitem, i) => {
aitem.id = i
})
item.id = this.doingList.length;
item.isDone = !item.isDone;
this.doingList.push(item)
//保存数据
this.saveDate()
},
// 删除完成
clearDoneArr: function(item) {
this.doneList.splice(item.id, 1),
this.doneList.forEach((item, i) => {
item.id = i
})
this.saveDate()
},
// 删除未完成
clearDoingArr: function(item) {
this.doingList.splice(item.id, 1),
this.doingList.forEach((item, i) => {
item.id = i
})
this.saveDate()
},
// 点击任务,放大、察看任务
showToDoDate: function(item) {
this.clickItem = item;
this.isClick = true
},
// 移出遮罩
removeZheZhao: function() {
this.isClick = false;
this.clickItem = {
}
},
// 写入信息
writeDate:function(event){
this.isWrite = true;
event.cancelBubble = true;// 取消冒泡事件
},
// 写入信息
noWriteDate:function(){
this.isWrite = false;
},
// 添加信息
addData: function(){
this.clickItem.completeDate = this.completeDate;
this.completeDate = "";
this.isWrite = false;
//保存数据
this.saveDate()
}
},
mounted: function() {
//挂载事件,将本地存储的内容放在数组里面
this.doingList = localStorage.doingList ? JSON.parse(localStorage.doingList) : [];
this.doneList = localStorage.doneList ? JSON.parse(localStorage.doneList) : [];
}
})
</script>
专门抽出一个大块来聊一聊我写js遇到的一些问题
四、vue的问题
回到HTML部分,我们看到input复选框里有一段这样的代码input οnclick="return false"
这一段代码的作用是,让复选框不可选
为什么要写如这样一段代码?
我们来康康不加入这一段代码会怎么样
点击“啊撒飞洒地方”对应的复选款
哦吼哦,为什么点击的下一个元素会自己打勾勾?试一下用settimeout或者是debug延迟触发绑定事件康康(这里使用的是settimeout)
我们会发现:点击以后,点击的目标的复选款会被选中,但是当目标移到已完成列表时,下一个元素的复选款同时被打勾。我一开始以为是穿透问题——点击目标后,触发默认事件,将目标移到已完成列表中,目标的下一个节点往上移动到目标的位置,浏览器误以为用户点击的是下一个元素的复选款,所以才会出现这种情况。真是这样么?
查阅了资料我发现:js是在捕获阶段触发默认事件——换句话说,默认事件比绑定事件先触发,所以上面的推论不攻自破
到底是什么个原因呢?我想可能是因为vue的变化侦测的一些小问题:doingList数组改变后,vue的变化侦测重新渲染HTML时,会将原本的样式保留。即点击第一个目标,那么第一个目标的复选款就是选中的,vue重新渲染列表时,会默认第一个节点的复选款时选中状态!
我们试着加入一大堆列表,然后来验证一下
点击一下“3”所在目标
可以看到,第一个列表点击后的转台应该是变成未被选中,而这个验证实例恰恰号就是这样
也许有更正确的原因,上面的是博主的自己的猜测,希望有前端大牛能看到这篇小博文并且帮我们解决一下这问题。十分感谢
总结
这个demo还有好多地方没来得及完成,大家有兴趣可以康康在里面加一些自己的想法。比如点开看详细信息的时候,底部的两个可以当作是按钮,闹钟为设置闹钟(这个博主还不会),写字板为新增信息之类的。博主这里再次感谢大家的厚爱。我会继续努力的