使用js写了一个任务管理列表,数据存储在本地,可以实现:自动计算任务完成时间、标记完成、录入、删除任务。主要是练习和熟悉一些DOM操作~而且日常记录任务也可以使用这个自己设计的网页,成就感upup!
实现效果如下图所示~
接下来就来跟我一起定制自己专属的任务列表吧~
一、代码结构
1.html部分
用html搭建一个简单的骨架,包括标题、实时时间显示模块、任务录入模块、任务清单的标题部分。
2.css部分
用css实现两个大盒子在屏幕上的布局,再设置相应的字体、边框等样式。
3.js部分
前面两个部分的代码都非常简单。js才是实现本项目的重头戏。js部分包括以下几个模块: (1)任务清单渲染模块
(2)任务完成勾选模块
(3)实时日期渲染模块
(4)表单录入模块
(5)删除操作模块
接下来跟着我完善各部分代码吧~
二、代码实现
1.html部分
分为两个大盒子:plus盒子用于新增任务,show盒子用于展示清单。
按照预想的架构,把标题和文本框,下拉表单,按钮,任务列表的骨架一个个摆上去就好了。
完成后的效果如图:
以下为源码:
xml
<div class="plus">
<h1>新增任务</h1>
<p class="myDate">今天是:2023/8/10 </p>
<form class="info" autocomplete="off">
任务:<input type="text" class="uname" name="uname">
完成期限:<select name="udate" class="udate">
<option value="六小时">六小时</option>
<option value="一天">一天</option>
<option value="两天">两天</option>
<option value="三天">三天</option>
<option value="一星期">一星期</option>
</select>
<button class="add">录入</button>
</form>
</div>
<div class="show">
<h1>任务清单</h1>
<table>
<thead>
<tr>
<th>全选<input type="checkbox" id="checkAll"></th>
<th>任务</th>
<th>完成期限</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- <tr>
<td><input type="checkbox" class="ck"></td>
<td>看仁医</td>
<td>今天</td>
<td>删除</td>
</tr> -->
</tbody>
</table>
</div> `
2.css部分
实现思路是: 1.首先用标准流布局加和浮动布局,将plus和show两个大盒子的宽度设置好并摆放好。
根据预期效果,plus盒子容纳的内容较少,只包含时间实时显示模块,标题,以及录入模块,因此宽度设置为400px;而show盒子容纳的清单大小会根据用户录入的量增加而增加,包含的内容较多,因此宽度设置为900px,高度不予设置,但设置了底部内边距padding-bottom为40px,来保证盒子的高度会根据需要自动伸缩,并且使内边距较为美观。
布局部分的代码如下:
css
* {
margin: 0;
padding: 0;
}
.plus {
margin: 30px 10px;
float: left;
width: 400px;
background-color: #e9f1f7;
border: 2px dotted #a5b9d2;
}
.show {
margin: 30px 10px;
float: left;
width: 900px;
padding-bottom: 40px;
background-color: #e9f1f7;
border: 2px dotted #a5b9d2;
}
2.用css选择器一个个选中其中的标题、表单等元素设置对应的样式即可。 这部分没什么严格要求,根据自己的喜好设置即可。注意各种基础选择器和复合选择器的配合使用哦~
实现代码如下:
css
.plus h1 {
text-align: center;
color: #333;
margin: 20px 0;
font-size: 20px;
}
.show h1 {
text-align: center;
color: #333;
margin: 20px 0;
}
table {
margin: 0 auto;
width: 850px;
border-collapse: collapse;
color: #004085;
}
th {
padding: 10px;
background: #cfe5ff;
font-size: 20px;
font-weight: 400;
}
td,
th {
border: 1px solid #b8daff;
}
td {
padding: 10px;
color: #666;
text-align: center;
font-size: 16px;
}
tbody tr {
background: #fff;
}
tbody tr:hover {
background: #e1ecf8;
}
.info {
width: 400px;
margin: 30px auto;
text-align: center;
}
.info input,
.info select {
width: 80px;
height: 27px;
outline: none;
border-radius: 5px;
border: 1px solid #b8daff;
padding-left: 5px;
box-sizing: border-box;
margin-right: 15px;
}
.info button {
width: 60px;
height: 27px;
background-color: #004085;
outline: none;
border: 0;
color: #fff;
cursor: pointer;
border-radius: 5px;
}
.plus p {
text-align: center;
}
a {
text-decoration: none;
color: #004085;
font-weight: 600;
}
.myDate {
color: #004085;
}
完成css部分后,页面效果如图,是不是变得美观了很多呢~
完成html和css代码后,我们得到了一个(比较)漂亮的空壳子。为了让这个空壳子具有实际的功能,我们还需要完成js代码~
3.js部分
重头戏来咯~让我们来一起分析各个模块的需求及实现思路。
本项目是将数据作为对象,存储在数组arr中,并及时在该数组发生变化时,更新到本地仓库localStorage中。arr数组是一个全局变量,我们在增、删、标记数据时,都是对arr数组中的数据进行处理。处理过后,根据数组中数据,实时渲染到网页中。
数组中的数据有三条属性值:uname(任务名),udate(截止时间),state(该任务是否被勾选完成?是的话为true,否则为false)。
arduino
const obj = {
uname: uname.value,//表单录入的任务名
udate: ddl,//根据udate.value计算得出ddl
state: false//该任务是否被勾选
}
(1)任务清单渲染模块:
什么时候需要渲染任务清单呢?当然是任务清单上的条目发生变化时咯~因此我们需要封装一个渲染函数render(),以便在页面刚加载完成时 、新增任务条目 、删除任务条目时调用。
渲染思路:
- 1.获取本地数据,转换为对象数组,存入arr。之后的渲染都是从arr中读取对象。
- 2.函数最开始需要清空页面以前渲染的内容,避免一条数据重复打印
- 3.利用循环,逐条生成tr,用模版字符串填入内容,再追加到tbody中
- 4.渲染完一条数据后,查看其state属性。如果为true,则用js将对应的字体属性修改为"划去"
- 5.所有数据渲染完成后,判断是否每个对象的state都为true。如果是,将'全选'勾上。
- 6.函数封装完成后要调用一次。这样每次页面重新加载后都会显示本地存储的数据了。
- 代码如下:
ini
//获取元素
const p = document.querySelector('p')
const tbody = document.querySelector('tbody')
const uname = document.querySelector('.uname')
const udate = document.querySelector('.udate')
// console.log(uname);
// console.log(udate);
const items = document.querySelectorAll('[name]')
//读取本地数据
const data = localStorage.getItem('myData')
//存入arr数组中,如果没有本地数据则为空数组
const arr = data ? JSON.parse(data) : []
const info = document.querySelector('.info')
//封装渲染函数
function render() {
//清空以前的避免重复打印
tbody.innerHTML = ''
let flag = true//代表全选被勾选
for (let i = 0; i < arr.length; i++) {
//生成tr
const tr = document.createElement('tr')
//用模版字符串填充
tr.innerHTML = `
<td><input type="checkbox" class="ck"></td>
<td>${arr[i].uname}</td>
<td>${arr[i].udate}</td>
<td>
<a href="javascript:" data-id=${i}>删除</a>
</td>
`
//追加到页面中
tbody.appendChild(tr)
//每一个删除键都设置了对应的data-id,以便后期点击时找到对应数据删除
const a = tr.querySelector('a')
console.log(a.dataset.id);
//根据state修改勾选框的状态
const input = tr.querySelector('input')
input.checked = arr[i].state
//根据state修改字体样式
if (arr[i].state) {
const td = tr.querySelector('td:nth-of-type(2)')
// console.log(td);
td.style.textDecoration = 'line-through'
const td2 = td.nextElementSibling
td2.style.textDecoration = 'line-through'
const td3 = td2.nextElementSibling
td3.style.textDecoration = 'line-through'
}
else {
flag = false//任意一个对象state=false则全选标记置为false
}
}
//修改全选框
document.querySelector('#checkAll').checked = flag
}
//别忘了调用
render()
(2)任务完成勾选模块
该模块由两个函数组成:
- 1、revise函数:在复选框发生点击事件时调用,可以根据任务清单中对应的复选框勾选情况,来修改arr[i]的state值,并更新到本地存储里。同时更改字体样式:勾选则划掉文字,未勾选则更改为不划掉。
ini
//修改属性函数
function revise(i, cks) {
//更改arr的state属性
arr[i].state = cks[i].checked ? true : false
// console.log(arr[i]);
//更新本地存储
localStorage.setItem('myData', JSON.stringify(arr))
// console.log(arr[i].state);
//修改样式:划掉还是不划掉?
const trs = document.querySelectorAll('tr')
console.log(trs[1]);
const td = trs[i + 1].querySelector('td:nth-of-type(2)')
if (arr[i].state) {
// console.log('我要划掉了');
// console.log(td);
td.style.textDecoration = 'line-through'
const td2 = td.nextElementSibling
td2.style.textDecoration = 'line-through'
const td3 = td2.nextElementSibling
td3.style.textDecoration = 'line-through'
}
else {
td.style.textDecoration = 'none'
const td2 = td.nextElementSibling
td2.style.textDecoration = 'none'
const td3 = td2.nextElementSibling
td3.style.textDecoration = 'none'
}
}
- 2.checkbox函数:用于在复选框发生点击事件时调用。如果点击了全选框,就将每个小复选框的状态更新为全选框的状态,并调用revise来更改样式和存储数据;如果点击了其他普通复选框,就调用revise来更改样式和存储数据,同时检查是否需要更新全选框勾选状态。
- 因为checkbox函数给复选框和全选框注册了点击事件,所以也要调用一下。在全局范围内调用一次就可以把需要的点击事件都注册上了~代码如下:
ini
function checkbox() {
const checkAll = document.querySelector('#checkAll')
const cks = document.querySelectorAll('.ck')
// console.log(checkAll);
// console.log(cks);
checkAll.addEventListener('click', function () {
for (let i = 0; i < cks.length; i++) {
cks[i].checked = this.checked
//修改属性
// console.log(cks[i].checked);
revise(i, cks)
}
})
for (let i = 0; i < cks.length; i++) {
cks[i].addEventListener('click', function () {
revise(i, cks)
checkAll.checked = document.querySelectorAll('.ck:checked').length === cks.length
})
}
}
checkbox()
(3)实时日期渲染模块
这部分调用了定时函数,来实时更新时间显示模块中的文字内容,比较简单,只要熟悉日期对象Date和定时函数setInterval就可以啦~
javascript
function getWeek(a) {
const arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
return arr[a % 7]
}
setInterval(function () {
const date = new Date()
p.innerHTML = ` ${getWeek(date.getDay())} ${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`
}, 1000)
(4)表单录入模块
该模块负责在点击"录入"按钮时,将表单中的内容经过处理后存储到本地,并调用render()重新渲染任务清单。
- 1.录入的时候会根据下拉表单选择的时间来计算ddl。这里封装了一个计算时间的函数。该函数可以传入当前的时间戳以及要经过的时间(ms),计算出截止时间。如下所示:
vbscript
function culTime(now, period) {
const ddl = now + period
const date = new Date(ddl)
return ` ${getWeek(date.getDay())} ${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}点`
}
// const now = +new Date()
// console.log(culTime(now, 21600000));
- 2.注册表单提交事件:首先要阻止提交后自动跳转的默认事件,再读取表单中的数据,处理后写入一个新对象里,再把该对象push进arr数组,再存入localStorage就可以了。
- 注意创建对象前要验证表单内容是否为空,空的话要return。
- 注意提交后要清空表单内容
- 注意要调用checkbox()函数。因为增加了新的复选框,这些新的复选框也需要用checkbox()注册点击事件,不然会出bug的!
javascript
//录入模块
info.addEventListener('submit', function (e) {
//阻止默认跳转
e.preventDefault()
cks = document.querySelectorAll('.ck')
//表单验证
for (let i = 0; i < items.length; i++) {
if (items[i].value == '') {
return alert('输入内容不能为空')
}
}
//获取当前时间戳
const now = +new Date()
let ddl = ''
//调用时间计算函数来计算ddl的值
switch (udate.value) {
case '六小时'://21600000
ddl = culTime(now, 21600000)
break
case '一天'://86400000
ddl = culTime(now, 86400000)
break
case '两天'://172800000
ddl = culTime(now, 172800000)
break
case '三天'://259200000
ddl = culTime(now, 259200000)
break
case '一星期':// 604800000
ddl = culTime(now, 604800000)
break
}
console.log(ddl);
//存入arr和本地
const obj = {
uname: uname.value,
udate: ddl,
state: false
}
//console.log(obj);
arr.push(obj)
localStorage.setItem('myData', JSON.stringify(arr))
//console.log(JSON.parse(localStorage.getItem('myData')));
//清空表单中内容
this.reset()
//渲染
render()
//重新注册一下事件
checkbox()
})
(5)删除操作模块
采取事件委托,给删除的'父亲'tbody注册点击事件。通过data-id属性能拿到相应的数据在arr中的下标号,将该条数据删除并更新到本地仓库即可。
- 其中也要调用checkbox()函数,因为删除时复选框的序列号发生变化,需要更新状态。
scss
//删除操作和勾选操作
tbody.addEventListener('click', function (e) {
console.log(e.target);
if (e.target.tagName == 'A') {
arr.splice(e.target.dataset.id, 1)
localStorage.setItem('myData', JSON.stringify(arr))
render()
checkbox()
}
})
三、总结
综上,将html,css,js代码放入他们该在的位置,我们的代码就全部完成了~获得了一个可以在日常生活中使用的任务列表,且数据存储在本地不会丢失(除非你手动删了)。笔者是个前端小白,独立完成此项目后感觉对代码的理解能力上升了,也注意到了一些以前没发现的点:
- 项目中要实时关注全局变量的状态,例如arr和document.ququerySelectorAll拿到的对象。假如我一开始将所有复选框存入了ck变量中,但是当数据新增或删除后,复选框就发生了改变,需要更新ck!不然就会出现奇怪的bug半天无法解决.....
- 本项目代码量差不多四百行,写的时候要思路清晰,先分析需求,写好大纲!不要写一布看一步,不然效率会很低下.....
- 代码永远都有可以简化的空间,继续学习更多的知识,回头再来看今天的任务,一定可以写出更高效简洁的代码的!