干货满满的vue重写todolist(里面有一些细节的东西奥)

前言

第一篇博客居然进了热榜,太感动了。感谢大佬们的抬爱。这几天学着搞一下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还有好多地方没来得及完成,大家有兴趣可以康康在里面加一些自己的想法。比如点开看详细信息的时候,底部的两个可以当作是按钮,闹钟为设置闹钟(这个博主还不会),写字板为新增信息之类的。博主这里再次感谢大家的厚爱。我会继续努力的

猜你喜欢

转载自blog.csdn.net/weixin_53605215/article/details/115132444