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