Vue實現(xiàn)移動端日歷的示例代碼
工作中遇到一個需求是根據(jù)日歷查看某一天/某一周/某一月的睡眠報告,但是找了好多日歷組件都不是很符合需求,只好自己手寫一個日歷組件,順便記錄一下。
先看看UI給的設(shè)計圖和,需求是有數(shù)據(jù)的日期做標記,可以查看某一周/某一月的數(shù)據(jù),周數(shù)據(jù)不用自定義,就按照日歷上的周數(shù)據(jù)截取.

實現(xiàn)效果

1.規(guī)劃dom部分區(qū)塊劃分

2.頁面實現(xiàn)
選擇月份和選擇年份與日期做了條件渲染,切換方式是點擊頂部時間切換選項
<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.接下來是邏輯處理部分 日數(shù)據(jù)的顯示一共42條數(shù)據(jù),先獲取當前月的總天數(shù),將每個月的天數(shù)保存在一個數(shù)組里,然后根據(jù)傳入的參數(shù)返回相應(yīng)的天數(shù), 因為有閏年的存在,2月會是29天,所以做了閏年的判斷.然后獲取每周的第一天是周幾,使用new Date().getDay()獲取某一天是周幾,返回的是0-7,這里為了方便使用將日歷表頭用數(shù)組保存起來返回的數(shù)字剛好是日里頭對應(yīng)的下標,然后根據(jù)第一天是周幾計算出需要補上個月的幾天數(shù)據(jù),通過new Date(y,m,0)可以獲取到上個月最后一天的,然后向日數(shù)據(jù)中添加上個月最后幾天的數(shù)據(jù),補充下個月開始的幾天數(shù)據(jù),直接使用42減去當月的天數(shù)和補充的上個月的天數(shù)得到的就是需要補充的下月天數(shù).
月數(shù)據(jù)的切換顯示,前后翻動切換年數(shù)據(jù),每年固定都是12月所以就直接寫固定值12然后v-for遍歷生成dom
年數(shù)據(jù)的切換顯示,每頁顯示12條數(shù)據(jù),保存每頁數(shù)據(jù)的第一條和最后一條用于前后翻頁計算顯示的數(shù)據(jù)+12或者-12.
校驗選擇的月份和已選擇的日期是否匹配,因為選擇日期后再切換月份有可能切換到的月份沒有選擇的日期如31日30日29日,所以需要驗證是否正確,若是沒有的話就當前月的最后一天.
手勢操作沒有寫完整,只寫了年份選擇的滑動事件邏輯.
為了方便js部分的代碼每行都有寫詳細的注釋
自定月選擇日期范圍只需要修改日期點擊事件的邏輯,新增一個參數(shù)判斷是單日期選擇還是選擇一個日期范圍,在事件處理里面記錄點擊的兩個日期并計算中間的日期保存返回.
import { formatTime } from "@/utils/format";
export default {
name: "calendar",
props: {
haveList: {
type: Array,
default: [],
},
},
data() {
return {
// 切換日期選擇
show: "date",
// 日歷頭
header: ["日", "一", "二", "三", "四", "五", "六"],
// 選擇日期
date: [],
// 年列表
yearList: [],
// 天列表
dayList: [],
// 定時器
timer: null,
// 手勢操作數(shù)據(jù)
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: {
// 計算顯示的天數(shù)據(jù)
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;
// 將當前月份的天數(shù)生成數(shù)組
this.dayList = Array.from({ length: this.getDays(y, m) }, (v, k) => {
return {
day: formatTime(k + 1),
month: m,
year: y,
};
});
// 將本月1日前的數(shù)據(jù)補齊
for (let i = lastDays; i > lastDays - week; i--) {
this.dayList.unshift({
day: i,
// 如果當前日期是1月補齊的是去年12月的數(shù)據(jù)
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);
// 將本月最后一天的數(shù)據(jù)補齊
for (let i = 1; i <= length; i++) {
this.dayList.push({
day: i,
// 如果當前日期是12月補齊的是明年年1月的數(shù)據(jù)
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數(shù)據(jù)
if (this.show === "month") {
// 清空每頁顯示的年份數(shù)據(jù)
this.yearList.length = 0;
// 計算頁面顯示的年份數(shù)據(jù) 每頁顯示12條數(shù)據(jù)
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) {
// 獲取按下屏幕移動結(jié)束的x軸坐標
let move = val.touches[0].pageX;
clearTimeout(this.timer);
// 判斷往左滑動還是往右滑動
// 滑動結(jié)束x軸坐標減去最初按下坐標為負數(shù)就是往左滑動,翻看當前日期以后的年份
if (move - this.move.pageX < -20) {
console.log("右滑", this.move.lNum);
// 定時器防抖
this.timer = setTimeout(this.changeYear("right"), 100);
}
// 滑動結(jié)束x軸坐標減去最初按下坐標為正數(shù)就是往右滑動,翻看當前日期以前的年份
if (move - this.move.pageX > 20) {
// 定時器防抖
this.timer = setTimeout(this.changeYear("left"), 100);
}
},
// 年份選擇切換
changeYear(type) {
// 清空每頁顯示的年份數(shù)據(jù)
this.yearList.length = 0;
if (type === "right") {
// 計算頁面顯示的年份數(shù)據(jù) 每頁顯示12條數(shù)據(jù)
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() {
// 獲取選擇的年月有多少天 防止這年不是閏年 就將日期跳轉(zhuǎn)到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) {
// 一年中每個月的天數(shù)
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);
// 年份向后翻 重組數(shù)據(jù)
} 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);
// 年份向前翻 重組數(shù)據(jù)
} else {
this.changeYear("left");
}
}
this.countDay();
this.checkDay();
},
// 右側(cè)按鈕點擊事件
weekReport(i) {
if (i === 1) return;
let arr = [],
// 選擇一周的數(shù)據(jù) 開始
s = 7 * (i - 1) - 7,
// 結(jié)束
e = 7 * (i - 1);
// 遍歷日數(shù)據(jù) 截取選擇的周數(shù)據(jù)
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) {
// 記錄每一頁顯示的數(shù)據(jù)第一位和最后一位 用于計算下一頁或者上一頁的數(shù)據(jù)
this.move.fNum = nVal[0];
this.move.lNum = nVal[11];
},
deep: true,
immediate: true,
},
};formatTime是給月份和日期小于10的前面加0的方法
初次寫日歷組件,寫的比較潦草,也沒有寫動態(tài)效果,代碼可以優(yōu)化的地方還有很多,可能大家還有比我這更簡單的方法,歡迎大家指出,互相交流.
到此這篇關(guān)于Vue實現(xiàn)移動端日歷的示例代碼的文章就介紹到這了,更多相關(guān)Vue日歷內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue數(shù)據(jù)監(jiān)聽方法watch的使用
這篇文章主要介紹了Vue數(shù)據(jù)監(jiān)聽方法watch的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
vue draggable resizable gorkys與v-chart使用與總結(jié)
這篇文章主要介紹了vue draggable resizable gorkys與v-chart使用與總結(jié),本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09
Vue.js 實現(xiàn)數(shù)據(jù)展示全部和收起功能
這篇文章主要介紹了Vue.js 實現(xiàn)數(shù)據(jù)展示全部和收起功能,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-09-09

