HTML例假周期树【女朋友生理期记录】
!!!页面样式参考CSDN博主【涔涔OVER】 HTML+PHP女友经期记录情侣月经生理周期记录 博文中页面效果
初始页面效果
运行效果展示
Menstrual_cycle
一、存储说明
1.本案例采用浏览器LocalStorage本地存储实现,相关数据内容的存储,刷新页面数据不会丢失。
提示:数据不可跨浏览器获取,不同浏览器之间本地存储空间相互独立。
提示:删除数据,点击页面右上方删除按钮,点击后出现弹窗,该操作可删除所有记录数据,删除后无法恢复!
2.为了运行便捷,建议将所有代码放到一个.html文件,只需直接点击html文件运行即可。
二、数据记录限制说明
- 【开始时间】选择限制
第一次记录(页面中不存在例假记录)例假数据时,【开始时间】的选择为无限制;当页面中存在例假记录时,【开始时间】选择限制为不早于页面中已有例假记录的时间。
- 【结束时间】选择限制
记录录入顺序限制为先选择【开始时间】,后选择【结束时间】。根据正常经期时间为7天以内的原则,【结束时间】选择限制为当前选择的【开始时间】往后推7天以内。
- 【经期情况记录】选填
记录有效字符为除空格外120字以内,生成记录时自动去除文本空格。
三、具体说明
-
周期树根据记录开始时间从上到下有序创建及排列。可点击页面右上方按钮,滚动到页面顶部或底部。
-
例假周期为自动计算。运算规则:上一条记录的开始时间与当前记录的开始时间的时间差。注:第一条记录因无前序记录无法推算显示为 斜杠 / 。
-
异常周期判定提示。根据正常例假周期时间为21~35天,若例假周期天数小于21或大于35判定为异常周期,则提醒显示为红色方框。
-
新增记录时【经期记录】若不填,生成的例假记录则显示为灰色不可点击的——无—— 字样,若填写了【经期记录】,生成的例假记录显示为蓝色可点击的 ——查看—— 字样,点击显示当前经期记录的文本框(不可编辑)(默认隐藏)。
见下图
四、代码部分
1.HTML
<div class="title">Menstrual Cycle Tree</div>
<!-- 操作按钮 -->
<div class="controller">
<button class="toTop">↑顶部</button>
<button class="toBottom">底部↓</button>
<button class="add">新增记录</button>
<button class="del">删除记录</button>
</div>
<!-- 新增记录滑窗 -->
<div class="info_add">
<div>
<span>开始日期*</span>
<input type="date" class="startTime">
</div>
<div>
<span>结束日期*</span>
<input type="date" class="endTime">
</div>
<div>
<textarea name="" id="" placeholder="经期情况记录(选填)最多120个字符" maxlength="120"></textarea>
</div>
<p class="text_num">0/120字</p>
<button class="data_add">添加</button>
<button class="content_clean">清空</button>
</div>
<!-- 删除记录滑窗 -->
<div class="info_del">
确定要删除所有记录吗?删除后无法恢复哦~
<button class="sure_del">确定(8)</button>
</div>
<!-- 周期树 -->
<div class="Menstrual_tree">
<!-- 周期树干 -->
<div class="center_trunk"></div>
<!-- 周期数据 -->
<div class="data_box"></div>
</div>
2.CSS
* {
margin: 0;
padding: 0;
border: 0;
list-style: none;
}
html {
scroll-behavior: smooth;
}
body {
background: linear-gradient(to right, rgb(143, 141, 252) 40%, rgb(142, 199, 248) 100%);
overflow-x: hidden;
}
.title {
width: 30%;
font-size: 40px;
color: #fff;
font-family: "Comic Sans MS";
position: fixed;
left: 0;
text-align: center;
pointer-events: none;
}
.controller {
top: 0;
right: 0;
width: 30%;
height: 50px;
position: fixed;
border-bottom-right-radius: 35px;
border-bottom-left-radius: 35px;
overflow: hidden;
}
.controller button {
background: #fff;
width: 25%;
height: 100%;
cursor: pointer;
box-sizing: border-box;
float: left;
letter-spacing: 2px;
}
.controller button:hover {
background: #f4ebeb;
}
/* 周期树 */
.Menstrual_tree {
width: 70%;
overflow: hidden;
position: relative;
padding-bottom: 200px;
}
.Menstrual_data {
width: 350px;
height: 100px;
background: rgb(255, 249, 249);
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
border-radius: 5px;
position: relative;
margin-left: 100px;
margin-top: 40px;
}
.Menstrual_data ul {
width: 30%;
height: 80px;
background: #fff;
border-radius: 5px;
box-shadow: 0 0 5px 0 #ccc;
}
.Menstrual_data li {
width: 100%;
height: 30px;
line-height: 30px;
text-align: center;
}
.Menstrual_data span {
font-weight: 600;
color: rgb(250, 46, 148);
}
.Menstrual_data ul li:first-child {
font-size: 17px;
margin-top: 10px;
font-weight: 600;
}
.Menstrual_data ul li:nth-child(2) {
font-size: 15px;
}
.Menstrual_data .start_time {
width: 85px;
height: 25px;
position: absolute;
left: 10px;
top: -18px;
background: orange;
font-size: 14px;
line-height: 25px;
text-align: center;
border-radius: 5px;
color: #fff;
}
.Menstrual_data .content {
width: 100%;
height: 100%;
position: absolute;
top: 115px;
left: 0;
background: #ffffff86;
border-radius: 5px;
font-size: 13px;
letter-spacing: 1px;
box-sizing: border-box;
padding: 5px;
line-height: 1.5;
pointer-events: none;
opacity: 0;
transition: all .5s;
}
/* 周期树干 */
.center_trunk {
width: 50px;
position: absolute;
top: 0;
left: 500px;
}
.trunk_line {
width: 20px;
height: 150px;
}
.trunk_line .line {
width: 6px;
height: 140px;
background: rgb(255, 255, 255, .9);
margin: 0 auto;
}
.trunk_line .lozenge {
width: 20px;
height: 20px;
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
background: #fff;
margin: -5px auto;
}
/* 新增记录页 */
.info_add,
.info_del {
width: 30%;
position: fixed;
right: -30%;
top: 250px;
color: #fff;
padding-bottom: 50px;
transition: all .5s;
}
.info_add div:not(:nth-child(3)) {
width: 50%;
margin: 50px 0;
float: left;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.info_add span {
float: left;
width: 80%;
text-align: left;
margin-bottom: 10px;
}
textarea {
width: 90%;
height: 150px;
display: block;
border-radius: 5px;
padding: 5px 5px;
outline: none;
margin: 0 auto;
resize: none;
}
.text_num {
float: left;
width: 90%;
margin-top: 10px;
margin-left: 5%;
text-align: right;
}
.info_add button {
width: 40%;
height: 50px;
background: #fff;
box-shadow: 0 0 5px 0 #ccc;
font-size: 15px;
display: block;
border-radius: 10px;
float: left;
margin-left: 6.66%;
margin-top: 60px;
cursor: pointer;
}
button:hover {
background: #fbfbfbdb;
}
input[type="date"] {
width: 80%;
height: 40px;
outline: none;
border-radius: 10px;
cursor: pointer;
background: #fff;
text-align: center;
padding-right: 10px;
font-size: 20px;
}
/* 删除记录页 */
.info_del {
font-size: 20px;
color: red;
text-align: center;
padding-top: 150px;
font-family: "楷体";
}
.info_del button {
width: 25%;
height: 40px;
background: #fff;
box-shadow: 0 0 5px 0 #ccc;
font-size: 15px;
display: block;
border-radius: 10px;
cursor: pointer;
margin: 60px auto;
pointer-events: none;
}
::-webkit-datetime-edit-year-field {
color: rgb(5, 192, 220);
}
::-webkit-datetime-edit-month-field {
color: rgb(240, 196, 52);
}
::-webkit-datetime-edit-day-field {
color: chocolate;
}
/* 动态添加/移除类 */
.titlePosition {
top: 150px;
left: 70%;
}
.titleOpacity {
opacity: 0;
}
.marginLeft {
margin-left: 550px;
}
.width {
right: 0;
}
.warning {
border: 2px solid red;
}
.des_click {
pointer-events: auto !important;
opacity: 1 !important;
}
.Menstrual_data .desLight {
color: cornflowerblue;
cursor: pointer;
}
.Menstrual_data .desNone {
pointer-events: none;
color: #ccccccf3;
}
@media screen and (min-device-width:1800px) {
.center_trunk {
left: 600px;
}
.Menstrual_data {
margin-left: 200px;
}
.marginLeft {
margin-left: 650px;
}
}
3.JS
const dataList = []
let cycleData = JSON.parse(localStorage.getItem('cycleData'))
if (cycleData === null) {
localStorage.setItem('cycleData', JSON.stringify(dataList))
cycleData = JSON.parse(localStorage.getItem('cycleData'))
} else {
MenstrualDataRender()
}
const delSure = document.querySelector('.sure_del')
const dataAdd = document.querySelector('.data_add')
const startTime = document.querySelector('.startTime')
const endTime = document.querySelector('.endTime')
const description = document.querySelector('textarea')
const textLength = document.querySelector('.text_num')
const title = document.querySelector('.title')
let hide = 0
let delInter = 0
document.documentElement.scrollTop = 0
cycleData.length == 0 ? '' : title.classList.add('titlePosition')
//新增记录按钮
document.querySelector('.add').onclick = () => {
startTime.value = endTime.value = description.value = ''
textLength.innerHTML = `0/120字`
endTime.max = endTime.min = ''
//设置周期树下方记录不得早于已有记录
cycleData = JSON.parse(localStorage.getItem('cycleData'))
startTime.min = cycleData.length > 0 ? cycleData[cycleData.length - 1].start_T : ''
document.querySelector('.width') ? document.querySelector('.width').classList.remove('width') : ''
document.querySelector('.info_add').classList.add('width')
hide == 1 ? document.querySelector('.width').classList.remove('width') : ''
hide = hide == 1 ? 0 : 1
}
//清空记录按钮
document.querySelector('.del').onclick = () => {
let delTime = 8
delSure.disabled = true
delSure.style.pointerEvents = 'none'
clearInterval(delInter)
document.querySelector('.width') ? document.querySelector('.width').classList.remove('width') : ''
document.querySelector('.info_del').classList.add('width')
delSure.innerHTML = `确定(8)`
delInter = setInterval(() => {
delTime--
if (delTime == 0) {
clearInterval(delInter)
delSure.innerHTML = `确定`
delSure.style.pointerEvents = 'auto'
delSure.disabled = false
} else {
delSure.innerHTML = `确定(${delTime})`
}
}, 1000)
hide == 2 ? document.querySelector('.width').classList.remove('width') : ''
hide = hide == 2 ? 0 : 2
}
//清空记录页,确定按钮
delSure.onclick = () => {
localStorage.removeItem('cycleData')
localStorage.setItem('cycleData', JSON.stringify(dataList))
//重新渲染周期树
MenstrualDataRender()
document.querySelector('.del').click()
titlePositionRender('del')
}
//新增记录页,新增按钮
dataAdd.onclick = () => {
if (startTime.value == '' || endTime.value == '') {
alert('开始/结束时间都是必选信息哦~')
} else {
const localData = {
start_T: startTime.value,
end_T: endTime.value,
desc: description.value.replace(/\s*/g, "")
}
cycleData = JSON.parse(localStorage.getItem('cycleData'))
cycleData.push(localData)
localStorage.setItem('cycleData', JSON.stringify(cycleData))
MenstrualDataRender()
startTime.value = endTime.value = description.value = ''
textLength.innerHTML = `0/120字`
endTime.max = endTime.min = ''
//设置周期树下方记录不得早于已有记录
cycleData = JSON.parse(localStorage.getItem('cycleData'))
startTime.min = cycleData.length > 0 ? cycleData[cycleData.length - 1].start_T : ''
cycleData.length == 1 ? titlePositionRender('add') : ''
windowScroll()
}
}
//新增记录页,清空表单按钮
document.querySelector('.content_clean').onclick = () => {
startTime.value = endTime.value = description.value = ''
textLength.innerHTML = `0/120字`
endTime.max = endTime.min = ''
}
//选择开始时间,根据开始时间,限制结束时间的选择范围
startTime.oninput = () => {
endTime.value = ''
endTime.min = startTime.value == '' ? '' : startTime.value
endTime.max = iptTime(startTime)
}
//限制日期选择顺序
endTime.onclick = () => {
startTime.value == '' ? alert('请先选择——开始日期——哦~') : ''
}
//记录框字数记录
description.oninput = () => {
textLength.innerHTML = `${description.value.replace(/\s*/g, "").length}/120字`
if (description.value.replace(/\s*/g, "").length < 120) {
description.maxLength = description.value.length + 1
textLength.style.color = '#fff'
} else if (description.value.replace(/\s*/g, "").length == 120) {
description.maxLength = description.value.length
textLength.style.color = 'red'
} else {
textLength.innerHTML = `120/120字`
textLength.style.color = 'red'
}
}
//点击回到页面顶部
document.querySelector('.toTop').onclick = () => {
document.documentElement.scrollTop = 0
}
//点击滚动到页面底部
document.querySelector('.toBottom').onclick = () => {
windowScroll()
}
//周期树渲染函数
function MenstrualDataRender() {
let cycleData = JSON.parse(localStorage.getItem('cycleData'))
const data = document.querySelector('.data_box')
const tree = document.querySelector('.center_trunk')
data.innerHTML = tree.innerHTML = ''
cycleData.forEach((item, index) => {
//例假周期计算
let cycle_days = index == 0 ? '/' : MenstrualComputed(cycleData[index - 1].start_T, cycleData[index].start_T)
//计算经期
const duration = MenstrualComputed(item.start_T, item.end_T)
data.innerHTML += `
<div class="Menstrual_data ${index % 2 == 0 ? 'marginLeft' : ''}">
<div class="start_time">${item.start_T}</div>
<ul class="${cycle_days > 35 || cycle_days < 21 ? 'warning' : ''}">
<li>例假周期</li>
<li><span class="cycle_days">${cycle_days}</span>天</li>
</ul>
<ul>
<li>经期</li>
<li><span class="duration">${duration}</span>天</li>
</ul>
<ul>
<li>记录</li>
<li class="description"><span id="descShow" class="${item.desc.length == 0 ? 'desNone' : 'desLight'}">${item.desc.length == 0 ? '无' : '查看'}</span></li>
</ul>
<p class="content">${item.desc}</p>
</div>
`
tree.innerHTML += `
<div class="trunk_line">
<div class="line" ></div>
<div class="lozenge"></div>
</div>
`
})
//点击查看记录
document.querySelectorAll('#descShow').forEach((item, index) => {
item.onclick = () => {
document.querySelectorAll('.content')[index].classList.toggle('des_click')
}
})
}
//周期数计算函数
function MenstrualComputed(s_t, e_t) {
const start = +new Date(`${s_t} 00:00:00`)
const end = +new Date(`${e_t} 00:00:00`)
return parseInt((end - start) / 1000 / 3600 / 24)
}
//页面滚动
function windowScroll() {
const position = document.body.scrollHeight
document.documentElement.scrollTop = position
}
//根据选择的开始日期,限制结束日期选择区间
function iptTime(lis_E) {
const time_1 = lis_E.value == '' ? '' : lis_E.value
let timeStamp = +new Date(`${time_1} 00:00:00`) + (7 * 1000 * 3600 * 24)
const date = new Date(timeStamp)
let y = date.getFullYear()
let m = date.getMonth() + 1
let d = date.getDate()
m = m < 10 ? '0' + m : m
d = d < 10 ? '0' + d : d
return `${y}-${m}-${d}`
}
//title渲染
function titlePositionRender(operate) {
title.style.transition = 'all .3s'
title.classList.add('titleOpacity')
setTimeout(() => {
operate == 'del' ? title.classList.remove('titlePosition') : title.classList.add('titlePosition')
setTimeout(() => {
title.classList.remove('titleOpacity')
}, 300)
}, 300)
}
总结
如有问题或者对本案例有任何的改进建议,欢迎评论区留言讨论!
上一篇: PHP心得体会
下一篇: HTML5期末考核大