这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

工作中遇到一个需求是根据日历查看某一天/某一周/某一月的睡眠报告,但是找了好多日历组件都不是很符合需求,只好自己手写一个日历组件,顺便记录一下。

先看看UI给的设计图和,需求是有数据的日期做标记,可以查看某一周/某一月的数据,周数据不用自定义,就按照日历上的周数据截取.

实现效果

1.规划dom部分区块划分

2.页面实现

选择月份和选择年份与日期做了条件渲染,切换方式是点击顶部时间切换选项

<template>
<div class="calendar">
<div class="date-top">
<div class="left" @click="dateOperate('down')">
<div></div>
</div>
<div class="time" @click="selectDate">{{ date.join("/") }}</div>
<div class="right" @click="dateOperate('up')">
<div></div>
</div>
</div>
<!-- 日期列表 -->
<div class="date-list" v-if="show === 'date'">
<div class="date-content">
<!-- 日历头 -->
<div v-for="item in header" :key="item">
{{ item }}
</div>
<!-- 日列表 -->
<div
v-for="(s, k) in dayList"
:class="[
'date-item',
s.month !== date[1] ? 'other-day' : '',
s.day === date[2] && s.month === date[1] ? 'today' : '',
]"
:key="s + '-' + k"
@click="selectDay(s)"
>
{{ s.day }}
<div
:class="[
'check',
haveList.includes(`${s.year}-${s.month}-${s.day}`) ? 'have' : '',
]"
></div>
</div>
</div>
<!-- 操作栏 -->
<div class="date-btn">
<div
class="btn-item"
v-for="k in weeks + 1"
:key="k"
@click="weekReport(k)"
>
{{ k === 1 ? "" : "看周报" }}
</div>
</div>
</div>
<!-- 月份列表 -->
<div class="month-list" v-else-if="show === 'month'">
<div
:class="date[1] == i ? 'month-item active' : 'month-item'"
v-for="i in 12"
:key="i"
@click="selectMonth(i)"
>
{{ i }}月
</div>
</div>
<!-- 年份列表 -->
<div
class="year-list"
v-else
@touchmove="touchMove"
@touchstart="touchStart"
>
<div
:class="date[0] === i ? 'month-item active' : 'month-item'"
v-for="i in yearList"
:key="i"
@click="selectYear(i)"
>
{{ i }}
</div>
</div>
<!-- 底部操作栏 -->
<div class="date-bottom">
<div class="b-left">
<div class="tab"></div>
<div class="totip">代表有睡眠报告</div>
</div>
<div class="b-right">
<div class="cancel" @click="cancel">取消</div>
<div class="m-report" @click="changeReport">看月报</div>
</div>
</div>
</div>
</template>

css部分

<style lang="scss" scoped>
.calendar {
width: 100%;
background-color: #fff; .date-top {
width: 100%;
padding: 20px;
display: flex;
justify-content: space-around;
align-items: center;
.left,
.right {
width: 100px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
div {
width: 20px;
height: 20px;
background-color: #00b7ae;
}
}
.left > div {
clip-path: polygon(0% 50%, 100% 0%, 100% 100%);
} .right > div {
clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
} .time {
font-size: 38px;
font-weight: 500;
color: #333333;
}
}
.date-list,
.year-list,
.month-list {
width: 100%;
padding: 30px;
height: 540px;
}
.month-list,
.year-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto;
.month-item {
text-align: center;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
height: 122px;
}
.month-item:active {
background-color: #eee;
}
.active {
background-color: #dcf4f3;
}
}
.date-list {
padding-top: 0;
display: flex;
.date-content {
flex: 1;
height: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: auto;
grid-gap: 20px 20px;
div {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 10px;
}
.other-day {
color: rgba($color: #363636, $alpha: 0.6) !important;
}
.today {
background-color: #dcf4f3;
}
.date-item {
font-size: 28px;
font-weight: 400;
color: #363636;
.check {
width: 10px;
height: 10px;
margin-top: 6px;
border-radius: 50%;
background-color: #00b7ae;
opacity: 0;
}
.have {
opacity: 1;
}
}
}
.date-btn {
height: 100%;
width: 80px;
font-size: 22px;
color: #4eb9f5;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-gap: 20px 20px;
margin-left: 20px;
.btn-item {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
}
} .date-bottom {
width: calc(100% - 80px);
display: flex;
justify-content: space-between;
align-content: center;
padding: 20px;
margin: 0 auto;
border-top: 1px solid #eee;
.b-left,
.b-right {
display: flex;
align-items: center;
}
.b-left {
.tab {
width: 27px;
height: 26px;
background: #dcf4f3;
border-radius: 13px;
}
.totip {
font-size: 24px;
font-weight: 400;
color: #363636;
margin-left: 20px;
}
}
.b-right {
.cancel {
font-size: 26px;
font-weight: 500;
color: rgba($color: #000000, $alpha: 0.5);
}
.m-report {
width: 195px;
line-height: 70px;
color: #fff;
font-size: 26px;
background: linear-gradient(196deg, #50dcdc, #18b6b7);
border-radius: 20px;
margin-left: 50px;
text-align: center;
}
}
}
}
</style>

3.接下来是逻辑处理部分

日数据的显示一共42条数据,先获取当前月的总天数,将每个月的天数保存在一个数组里,然后根据传入的参数返回相应的天数, 因为有闰年的存在,2月会是29天,所以做了闰年的判断.然后获取每周的第一天是周几,使用new Date().getDay()获取某一天是周几,返回的是0-7,这里为了方便使用将日历表头用数组保存起来返回的数字刚好是日里头对应的下标,然后根据第一天是周几计算出需要补上个月的几天数据,通过new Date(y,m,0)可以获取到上个月最后一天的,然后向日数据中添加上个月最后几天的数据,补充下个月开始的几天数据,直接使用42减去当月的天数和补充的上个月的天数得到的就是需要补充的下月天数.

月数据的切换显示,前后翻动切换年数据,每年固定都是12月所以就直接写固定值12然后v-for遍历生成dom

年数据的切换显示,每页显示12条数据,保存每页数据的第一条和最后一条用于前后翻页计算显示的数据+12或者-12.

校验选择的月份和已选择的日期是否匹配,因为选择日期后再切换月份有可能切换到的月份没有选择的日期如31日30日29日,所以需要验证是否正确,若是没有的话就当前月的最后一天.

手势操作没有写完整,只写了年份选择的滑动事件逻辑.

为了方便js部分的代码每行都有写详细的注释

自定月选择日期范围只需要修改日期点击事件的逻辑,新增一个参数判断是单日期选择还是选择一个日期范围,在事件处理里面记录点击的两个日期并计算中间的日期保存返回.

<script>
import { formatTime } from "@/utils/format";
export default {
name: "calendar",
props: {
haveList: {
type: Array,
default: [],
},
},
data() {
return {
// 切换日期选择
show: "date",
// 日历头
header: ["日", "一", "二", "三", "四", "五", "六"],
// 选择日期
date: [],
// 年列表
yearList: [],
// 天列表
dayList: [],
// 定时器
timer: null,
// 手势操作数据
move: {
pageX: 0,
fNum: null,
lNum: null,
},
// 第一天是周几
weeks: 0,
};
},
created() {},
mounted() {
let time = new Date();
this.date.push(
time.getFullYear(),
formatTime(time.getMonth() + 1),
formatTime(time.getDate())
);
this.countDay();
},
methods: {
// 计算显示的天数据
countDay() {
console.log("chufa");
let [y, m, d] = this.date;
// 获取第一天是周几
let week = new Date(`${y}/${m}/1`).getDay(),
// 获取当前月的上个月多少天
lastDays = this.getDays(y, m - 1),
// 获取这个月有多少天
days = this.getDays(y, m);
// 计算这个月有多少周
this.weeks = Math.ceil((days - (7 - week)) / 7) + 1;
// 将当前月份的天数生成数组
this.dayList = Array.from({ length: this.getDays(y, m) }, (v, k) => {
return {
day: formatTime(k + 1),
month: m,
year: y,
};
});
// 将本月1日前的数据补齐
for (let i = lastDays; i > lastDays - week; i--) {
this.dayList.unshift({
day: i,
// 如果当前日期是1月补齐的是去年12月的数据
month: +m - 1 === 0 ? 12 : formatTime(+m - 1),
year: +m - 1 === 0 ? y - 1 : y,
});
}
// 计算需要补齐多少天
let length = this.weeks * 7 - this.dayList.length;
console.log("length", week, lastDays, days, this.weeks);
// 将本月最后一天的数据补齐
for (let i = 1; i <= length; i++) {
this.dayList.push({
day: i,
// 如果当前日期是12月补齐的是明年年1月的数据
month: +m + 1 > 12 ? 1 : formatTime(+m + 1),
year: +m + 1 > 12 ? y + 1 : y,
});
} console.log(this.dayList);
},
// 顶部时间点击事件
selectDate() {
let type = {
month: "year",
date: "month",
};
// 判断点击事件选择月份还是年份
if (this.show !== "year") {
this.show = type[this.show];
}
// 如果是月份就计算dateList数据
if (this.show === "month") {
// 清空每页显示的年份数据
this.yearList.length = 0;
// 计算页面显示的年份数据 每页显示12条数据
for (let i = this.date[0] - 4; i <= this.date[0] + 7; i++) {
this.yearList.push(i);
}
}
},
// 屏幕点击事件
touchStart(val) {
// 获取按下屏幕的x轴坐标
this.move.pageX = val.touches[0].pageX;
},
// 左右滑动切换事件
touchMove(val) {
// 获取按下屏幕移动结束的x轴坐标
let move = val.touches[0].pageX;
clearTimeout(this.timer);
// 判断往左滑动还是往右滑动
// 滑动结束x轴坐标减去最初按下坐标为负数就是往左滑动,翻看当前日期以后的年份
if (move - this.move.pageX < -20) {
console.log("右滑", this.move.lNum);
// 定时器防抖
this.timer = setTimeout(this.changeYear("right"), 100);
}
// 滑动结束x轴坐标减去最初按下坐标为正数就是往右滑动,翻看当前日期以前的年份
if (move - this.move.pageX > 20) {
// 定时器防抖
this.timer = setTimeout(this.changeYear("left"), 100);
}
},
// 年份选择切换
changeYear(type) {
// 清空每页显示的年份数据
this.yearList.length = 0;
if (type === "right") {
// 计算页面显示的年份数据 每页显示12条数据
for (let i = this.move.lNum + 1; i < this.move.lNum + 13; i++) {
this.yearList.push(i);
}
} else {
for (let i = this.move.fNum - 12; i < this.move.fNum; i++) {
this.yearList.push(i);
}
}
},
// 年份点击事件
selectYear(val) {
this.date[0] = val;
this.show = "month";
},
// 月份点击事件
selectMonth(val) {
this.date[1] = val;
this.show = "date";
this.countDay();
this.checkDay();
},
// 校验选择的月份和已选择的日期是否匹配
checkDay() {
// 获取选择的年月有多少天 防止这年不是闰年 就将日期跳转到28号,或者有的月份没有31号就跳到30号
let num = this.getDays(this.date[0], this.date[1]);
if (num < this.date[2]) {
this.date.splice(2, 1, num);
}
},
// 日期点击事件
selectDay(val) {
let oVal = this.date[1];
this.date.splice(1, 2, val.month, val.day);
if (val.month !== oVal) {
this.countDay();
}
this.$emit("change", this.date.join("-"));
},
// 获取某个月有多少天
getDays(year, month) {
// 一年中每个月的天数
let days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// 判断是不是闰年 2月29天
if (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)) {
days[1] = 29;
}
return days[month - 1];
},
//左右按钮点击事件
dateOperate(type) {
let [y, m, d] = this.date;
// 如果是向后翻
if (type === "up") {
// 日期向后翻 切换月份
if (this.show === "date") {
if (+m === 12) {
this.date.splice(0, 1, y + 1);
this.date.splice(1, 1, "01");
} else {
this.date.splice(1, 1, formatTime(+m + 1));
}
// 月份向后翻 切换年份
} else if (this.show === "month") {
this.date.splice(0, 1, y + 1);
// 年份向后翻 重组数据
} else {
this.changeYear("right");
} // 如果是前后翻
} else {
// 日期向前翻 切换月份
if (this.show === "date") {
if (+m === 1) {
this.date.splice(0, 1, y - 1);
this.date.splice(1, 1, 12);
} else {
this.date.splice(1, 1, formatTime(+m - 1));
}
// 月份向前翻 切换年份
} else if (this.show === "month") {
this.date.splice(0, 1, y - 1);
// 年份向前翻 重组数据
} else {
this.changeYear("left");
}
}
this.countDay();
this.checkDay();
},
// 右侧按钮点击事件
weekReport(i) {
if (i === 1) return;
let arr = [],
// 选择一周的数据 开始
s = 7 * (i - 1) - 7,
// 结束
e = 7 * (i - 1);
// 遍历日数据 截取选择的周数据
for (let k = s; k < e; k++) {
arr.push(
`${this.dayList[k].year}-${this.dayList[k].month}-${this.dayList[k].day}`
);
}
this.$emit("weekReport", arr);
}, // 看月报事件
changeReport() {
let [y, m, d] = this.date;
this.$emit("changeReport", `${y}-${m}`);
},
// 取消事件
cancel() {
this.$emit("cancel");
},
},
computed: {},
watch: {
yearList(nVal, oVal) {
// 记录每一页显示的数据第一位和最后一位 用于计算下一页或者上一页的数据
this.move.fNum = nVal[0];
this.move.lNum = nVal[11];
},
deep: true,
immediate: true,
},
};
</script>

formatTime是给月份和日期小于10的前面加0的方法

本文转载于:

https://juejin.cn/post/7218048201981853757

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录-Vue移动端日历设计与实现的更多相关文章

  1. vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图

    vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图 Mand Mobile是面向金融场景设计的移动端组件库,基于Vue.js实现.目前已实际应用于滴滴四大金融业务板块的1 ...

  2. TV端产品设计法则和分析

    对TV端产品设计的分析太特么少了.翻遍网络,大多也是针对UI设计的分析,这篇从产品设计的角度,梳理下现有的TV端产品设计法则,顺道做点分析.(前方多图,高能预警) 目录: 1. TV端产品使用场景 2 ...

  3. Egg + Vue 服务端渲染工程化实现

    在实现 egg + vue 服务端渲染工程化实现之前,我们先来看看前面两篇关于Webpack构建和Egg的文章: 在 Webpack工程化解决方案easywebpack 文章中我们提到了基于 Vue ...

  4. 记录vue中一些有意思的坑

    记录vue中一些有意思的坑 'message' handler took 401ms 在出现这个之前,我一直纠结于 是如何使用vue-router或者不使用它,通过类似的v-if来实现.结果却出现这个 ...

  5. 从服务端架构设计角度,深入理解大型APP架构升级

    随着智能设备普及和移动互联网发展,移动端应用逐渐成为用户新入口,重要性越来越突出.但企业一般是先有PC端应用,再推APP,APP 1.0版的功能大多从现有PC应用平移过来,没有针对移动自身特点考虑AP ...

  6. 了解css中px、em、rem的区别并使用Flexible实现vue移动端的适配

    本人java菜鸟一名,若有错误,还请见谅. 1.px和em和rem的定义和区别 px:px像素,是相对单位,相对于屏幕的分辨率而言,也就是说,当屏幕的分辨率不同那么px相同,实际看到的大小也会不同. ...

  7. [vue] vue服务端渲染nuxt.js

    初始化 使用脚手架工具 create-nuxt-app 快速创建 npx create-nuxt-app <项目名> npx create-nuxt-app 执行一些选择 在集成的服务器端 ...

  8. 那些过目不忘的无线端交互设计(DRIBBBLE GIF动态图)

    Dribbble精选:Dribbble上令人惊叹的无线端交互设计!来自全球牛人们的奇思妙想,新颖动人的交互在这一张张GIF动态图上一览无余!当然界面一样打动人心,腾出手点赞的同时!记得另存哟:) 作者 ...

  9. 回顾2017系列篇(二):移动端APP设计趋势

    移动端APP在2017年经历了诸多的变化, 人工智能.聊天式的界面.响应式设计.虚拟现实(VR)和增强现实(AR)让设计师不断面临新的挑战.研究表明,用户每天耗费在手机和平板上的平均时长为158分钟, ...

  10. Vue移动端项目模板

    一个集成移动端开发插件的Vue移动端模板包含1.css: 使用stylus开发css 集成reset样式文件 修改UI组件文件 统一样式处理(如主题色等)2.UI组件 使用热门的vant与mint-u ...

随机推荐

  1. 【译】使用.NET将WebAssembly扩展到云(一)

    原文 | Richard Lander 翻译 | 郑子铭 WebAssembly(Wasm)是一种令人兴奋的新虚拟机和(汇编)指令格式. Wasm 诞生于浏览器,是 Blazor 项目的重要组成部分. ...

  2. CF1913

    C 先用桶记录各个位数量. 对于每次询问,从低位到高位扫,先用掉一个当前位的,然后把当前位的全部转化成 \(cnt/2\) 个更高位的. D 有一个思路:其实删区间可以视作删相邻两个数.(然鹅这题不是 ...

  3. 教你用Rust实现Smpp协议

    本文分享自华为云社区<华为云短信服务教你用Rust实现Smpp协议>,作者: 张俭. 协议概述 SMPP(Short Message Peer-to-Peer)协议起源于90年代,最初由A ...

  4. 两个数组的交集II

    两个数组的交集II 给定两个数组,编写一个函数来计算它们的交集. 示例 输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2,2] 输入:nums1 = [4,9,5], ...

  5. RunnerGo低代码测试体验

    RunnerGo是基于go语言自研的一款企业级全栈式测试平台,采用Apache-2.0 license开源协议,涵盖接口测试.性能测试.UI测试和项目管理等功能,并独创"拖拉拽"的 ...

  6. win32-改变显示器的亮度

    调用SetMonitorBrightness 代码示例: #pragma comment(lib, "dxva2.lib") #include <windows.h> ...

  7. win32-localtime的使用

    下面的例子用于反映本地系统的日期格式变化 // locale test #include <stdio.h> #include <locale.h> #include < ...

  8. 【华为机试ACM基础#01】字符串最后一个单词长度、计算某字符出现次数、提取不重复的整数(熟悉字符/字符串/整数的输入)

    字符串最后一个单词的长度 描述 计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000.(注:字符串末尾不以空格为结尾) 输入描述: 输入一行,代表要计算的字符串,非空,长度小于5000 ...

  9. DataGear 制作支持表单交互和多图表联动的数据可视化看板

    对于数据可视化,有时需要根据用户输入的查询条件展示限定范围的数据图表,DataGear的看板表单功能可以快速方便地实现此类需求. 下面的看板示例,包含一个柱状图.一个饼图和一个地图,用户可以通过看板表 ...

  10. debian手册摘要

    apt-get source 包名 # 获取源码dpkg --info deb包名 # 查看包信息apt-cache show 包名 # 包信息(含有Depends.Suggests.Section. ...