基于vue2实现一个日历组件
华菱下水道二组_历飞宇 人气:0不用任何第三方库,只基于vue2实现一个日历组件,末尾附上我的代码,单文件,代码没有抽取,有点长。哪位大佬批评指正一下,末尾有效果图
- 样式是类似于window10日历
- 支持控制周一还是周日在第一列
- 支持鼠标滑动切换
- 支持单选,拖动鼠标多选,范围选择
- 支持年月日选择切换
- 支持传入选中数据
- 支持隐藏非本月日期
QCalendar.scss
.Q-calendar-change-enter-active, .Q-calendar-change-leave-active { transition: opacity 0.5s; } .Q-calendar-change-enter, .Q-calendar-change-leave-to { opacity: 0; } .Q-calendar { width: 100%; height: 100%; margin: 0 auto; overflow: hidden; background-color: #ffffff; color: #000; user-select: none; border: 1px solid #4152b3; .Q-calendar-title { height: 50px; width: 100%; box-sizing: border-box; display: flex; justify-content: space-between; div { align-self: center; } .Q-calendar-button, .top { align-self: center; } .Q-calendar-title-box { width: calc((100% / 7) * 2); display: flex; justify-content: space-around; cursor: default; .Q-calendar-title-box-text { width: 50%; text-align: center; align-self: center; } .Q-calendar-title-box-text:hover { color: #4152b3; font-weight: 700; } } .Q-calendar-title-box-padding{ padding-left: 18px; } .Q-calendar-title-box-center{ margin: 0 auto; font-weight: 700 } } .Q-calendar-day { height: calc(100% - 50px); /* 周末 */ .Q-calendar-week { display: flex; justify-content: inherit; cursor: default; p { display: flex; justify-content: center; width: calc(100% / 7); box-sizing: border-box; } } /* 日历内容 */ .Q-calendar-box { display: flex; justify-content: inherit; flex-wrap: wrap; width: 100%; height: 80%; div:hover { color: yellowgreen; font-weight: 700; } .Q-calendar-current-month { box-sizing: border-box; cursor: default; } .Q-calendar-current-month:hover { color: #4152b3; font-weight: 700; font-size: 20px; } div { display: flex; justify-content: center; width: calc(100% / 7); height: calc(100% / 7); span { margin: auto; } } p { display: flex; justify-content: center; width: calc(100% / 7); } } } .Q-calendar-years { height: calc(100% - 50px); .Q-calendar-years-box { // border: 1px solid pink; display: flex; justify-content: inherit; flex-wrap: wrap; height: 98%; div { display: flex; box-sizing: border-box; justify-content: center; width: calc(100% / 4); height: calc(100% / 4); span { margin: auto; } } div:hover { font-weight: 700; color: yellowgreen; } } } } /* //非本月时间内,或非本年内 */ .Q-calendar-surplus { color: #898989; } .nowCss { // border:1px solid pink; background: #f1f3f4; color: #40b8ff; } .Q-calendar-checked { span { color: var(--Q-calendar-color); background-color: var(--Q-calendar-background-color); border-radius: 10px; width: 50%; height: 50%; text-align: center; line-height: 24px; } }
getRangeDay.js
import { parseTime } from './formatTime' export function getRangeDay(startDate, endDate) { const result = []; const db = new Date(); db.setUTCFullYear(startDate.year, startDate.month - 1, startDate.day); const de = new Date(); de.setUTCFullYear(endDate.year, endDate.month - 1, endDate.day); let smallDate let bigDate if (db.getTime() > de.getTime()) { smallDate = de.getTime() bigDate = db.getTime() } else { smallDate = db.getTime() bigDate = de.getTime() } for (let k = smallDate; k <= bigDate;) { result.push({ year: parseTime(k, "{y}"), month: parseTime(k, "{m}").length===1?('0'+parseTime(k, "{m}")):parseTime(k, "{m}"), day: parseTime(k, "{d}").length===1?('0'+parseTime(k, "{d}")):parseTime(k, "{d}"), checked: true }); k = k + 24 * 60 * 60 * 1000; } return result; }
formatTime.js
export function parseTime(time, pattern) { if (arguments.length === 0 || !time) return null; const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'; let date; if (typeof time === 'object') { date = time; } else { if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { time = parseInt(time); } else if (typeof time === 'string') { time = time.replace(new RegExp(/-/gm), '/'); } if ((typeof time === 'number') && (time.toString().length === 10)) { time = time * 1000; } date = new Date(time); } const formatObj = { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate(), h: date.getHours(), i: date.getMinutes(), s: date.getSeconds(), a: date.getDay(), }; return format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { // @ts-ignore let value = formatObj[key]; // Note: getDay() returns 0 on Sundayday if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value]; } if (result.length > 0 && value < 10) { value = '0' + value; } return value || 0; }); }
QCalendar.vue
<template> <!-- 外部 --> <div class="Q-calendar" @mouseup.stop="onMouseUp" @mouseleave.stop="onMouseleave"> <!-- 头部 --> <div class="Q-calendar-title" v-if="hideTitle"> <div class="Q-calendar-title-box Q-calendar-title-box-padding"> <div @click="onClickYears" class="Q-calendar-title-box-text"> {{ this.currentYear }}年 </div> <div @click="onClickMonth" class="Q-calendar-title-box-text"> {{ this.currentMonth }}月 </div> <!-- <div>{{this.currentDay}}号</div> --> </div> <slot name="mkCalendarHeaderSlot"></slot> <div class="Q-calendar-title-box" v-if="isSwitch"> <div class="Q-calendar-title-box-text" @click="onClickUp">上</div> <div class="Q-calendar-title-box-text" @click="onClickDown">下</div> </div> </div> <div v-else class="Q-calendar-title"> <div class="Q-calendar-title-box Q-calendar-title-box-center"> <div class="Q-calendar-title-box-text">{{ this.currentYear }}年</div> <div class="Q-calendar-title-box-text">{{ this.currentMonth }}月</div> </div> </div> <!-- 日历 --> <transition name="Q-calendar-change"> <div class="Q-calendar-day" v-if="hide === 1" @mousewheel="onMousewheel"> <!-- 周一到周日 --> <div class="Q-calendar-week"> <p v-for="item in isMonday ? weekSort.Monday : weekSort.Sunday" :key="item">{{ item }}</p> </div> <div class="Q-calendar-box"> <!-- 上个月剩余天数 --> <div class="Q-calendar-surplus" v-for="item in lastMonthDays" :key="'dayLast' + item"> <span v-show="isOtherDate"> {{ item }}</span> </div> <!-- 当前月份天数 --> <div v-for="item in currentMonthDays" :key="'dayCur' + item.day" class="Q-calendar-current-month" :style="cssProps" @click="onChangeDay(item)" :class="{ 'Q-calendar-checked': item.checked, nowCss: new Date().getFullYear()+'' === item.year && (new Date().getMonth() + 1)+'' === item.month && new Date().getDate()+'' === item.day, }" @mouseover="dragDay(item)" @mousedown="onMouseDown(item)"> <span> {{ item.day}}</span> </div> <!-- 下月余出 --> <div class="Q-calendar-surplus" v-for="item in this.nextMonth()" :key="'dayNext' + item"> <span v-show="isOtherDate">{{ item }}</span> </div> </div> </div> </transition> <!-- 月 --> <transition name="Q-calendar-change"> <div class="Q-calendar-years" v-if="hide === 2" @mousewheel="onMousewheel"> <div class="Q-calendar-years-box"> <div v-for="item in clickMonth" :key="'monthCur' + item.value" @click="onChangeMonth(item)" :class="{ nowCss: isNowYear && new Date().getMonth() + 1 === item.value, }"> <span>{{ item.key }}</span> </div> <div class="Q-calendar-surplus" v-for="item in lastMonth" :key="'monthNext' + item"> <span>{{ item }}</span> </div> </div> </div> </transition> <!-- 年 --> <transition name="Q-calendar-change"> <div class="Q-calendar-years Q-calendar-year" v-if="hide === 3" @mousewheel="onMousewheel"> <div class="Q-calendar-years-box"> <div class="Q-calendar-surplus" v-for="item in lastYear" :key="'yearLast' + item"> <span>{{ item }}</span> </div> <div v-for="item in thisYear" :key="'yearCur' + item" @click="onChangeYear" :class="{ nowCss: new Date().getFullYear() === item }"> <span>{{ item }}</span> </div> </div> </div> </transition> </div> </template> <script> /** * 日历组件 * @description * @property {Boolean} isOtherDate false 是否展示非本月份的日期 * @property {Boolean} hideTitle true 是否展示title * @property {Boolean} multiSelect false 是否开启摁下鼠标进行多选 * @property {String} SelectedBackgroundColor "#4152b3" 选中的背景色 * @property {String} SelectedTextColor "#ffffff" 选中的文字色 * @property {Boolean} isMonday true 是否从周一在开头 * @property {Number} selectType 1 单选1,多选2,范围选3 * @property {Array} selectList [] 选中数据的数组 * @property {Boolean} isSwitch true 需要切换按钮传入true * */ // import { parseTime } from "./utils/formatTime"; import { getRangeDay } from "./utils/getRangeDay"; export default { name: "QCalendar", props: { isOtherDate: { type: Boolean, default: false, }, hideTitle: { type: Boolean, default: true, }, multiSelect: { type: Boolean, default: false, }, SelectedBackgroundColor: { type: String, default: "#4152b3", }, SelectedTextColor: { type: String, default: "#ffffff", }, isMonday: { type: Boolean, default: true, }, selectType: { type: Number, default: 1, }, selectList: { type: Array, default: () => [] }, isSwitch: { type: Boolean, default: true, } }, data() { return { isMouseDown: false, arr: this.selectList, isNowYear: true, isNowMOnth: true, hide: 1, weekSort:{ Sunday: ["日", "一", "二", "三", "四", "五", "六"], Monday: ["一", "二", "三", "四", "五", "六", "日"], }, clickMonth: [ { key: "一月", value: 1 }, { key: "二月", value: 2 }, { key: "三月", value: 3 }, { key: "四月", value: 4 }, { key: "五月", value: 5 }, { key: "六月", value: 6 }, { key: "七月", value: 7 }, { key: "八月", value: 8 }, { key: "九月", value: 9 }, { key: "十月", value: 10 }, { key: "十一月", value: 11 }, { key: "十二月", value: 12 }, ], lastMonth: ["一月", "二月", "三月", "四月"], lastYear: [], thisYear: [], // 当前日 currentDay: new Date().getDate(), // 当前月 currentMonth: new Date().getMonth() + 1, // 当前年 currentYear: new Date().getFullYear(), }; }, created() { this.initParameter(); }, computed: { cssProps() { return { "--Q-calendar-background-color": this.SelectedBackgroundColor, "--Q-calendar-color": this.SelectedTextColor, }; }, // 当前月的天数 currentMonthDays() { let dayLength = new Date( this.currentYear, this.currentMonth, 0 ).getDate(); let arr = []; for (let h = 0; h < dayLength; h++) { let dataObj = { year: this.currentYear + "", month: (this.currentMonth>0&&this.currentMonth<10)?'0'+ this.currentMonth :this.currentMonth+'', day: (h+1>0&&h+1<10)? '0'+ (h+1) : (h+1) + '', checked: false, }; arr[h] = dataObj; } for (let p = 0; p < this.arr.length; p++) { for (let k = 0; k < arr.length; k++) { if ((this.arr[p].year === arr[k].year + "") && (this.arr[p].month === arr[k].month + "") && (this.arr[p].day === arr[k].day + "") && this.arr[p].checked) { arr[k].checked = true } } } return arr; }, // 获取上个月的剩余多少天 lastMonthDays() { const lastLength = new Date( this.currentYear, this.currentMonth - 1, 0 ).getDate(); let cutLength; if (this.isMonday) { cutLength = new Date( this.currentYear, this.currentMonth - 1, 0 ).getDay(); } else { cutLength = new Date( this.currentYear, this.currentMonth - 1, 1 ).getDay(); } let arr = []; for (let h = lastLength - cutLength + 1; h <= lastLength; h++) { arr.push(h); } return arr; }, }, methods: { onMousewheel(e) { let evt = e || window.event; //考虑兼容性 evt.preventDefault(); if (evt.deltaY > 0) { this.onClickDown(); } else { this.onClickUp(); } //检查事件 // console.log(evt.type, evt.deltaX, evt.deltaY, evt.deltaZ); }, dragDay(dayObj) { if (!this.multiSelect) { return; } else { if (!this.isMouseDown) { return; } else { this.onChangeDay(dayObj); } } }, onMouseDown(dayObj) { if (!this.multiSelect) { return; } else { if (this.isMouseDown) this.onChangeDay(dayObj); this.isMouseDown = true; } }, onMouseUp() { this.isMouseDown = false; }, onMouseleave() { if (this.isMouseDown) { this.isMouseDown = false; } }, // 点击多选 onChangeDay(val) { // 判断单选,多选,还是范围选,对应值1.2.3. if (this.selectType === 1) { if (this.arr.length === 0) { val.checked = true; this.arr=[val] } else if (this.arr.length === 1) { if ((this.arr[0].year === val.year) && (this.arr[0].month === val.month) && (this.arr[0].day === val.day)) { this.arr = [] } else { this.arr = [] val.checked = true; this.arr.push(val); } } else { return } } else if (this.selectType === 2) { if (val.checked) { // 剔除 val.checked = false; this.arr = this.arr.filter((ele) => { return !( ele.year === val.year && ele.month === val.month && ele.day === val.day ); }); } else { // 添加 val.checked = true; this.arr.push(val); } } else if (this.selectType === 3) { // 范围选择, if (this.arr.length === 0) { val.checked = true; this.arr.push(val); } else if (this.arr.length === 1) { val.checked = true; this.arr.push(val); const arrS = getRangeDay(this.arr[0],this.arr[1]) this.arr = [] this.arr = arrS } else { this.arr = [] val.checked = true; this.arr.push(val); } } this.$emit("selectedData", this.arr); }, initParameter() { let currentYear = this.currentYear - 1; for (let p = 3; p >= 0; p--) { this.lastYear[p] = currentYear--; } currentYear = this.currentYear; for (let l = 0; l < 12; l++) { this.thisYear[l] = currentYear++; } }, onChangeYear(val) { this.hide = 2; let currentYear = new Date().getFullYear(); this.currentYear = val.srcElement.innerText; this.isNowYear = val.srcElement.innerText + "" === currentYear+'' }, onChangeMonth(val, ) { this.hide = 1; let currentMonth = new Date().getMonth() + 1; this.currentMonth = val.value; this.isNowMOnth=val.value + "" === currentMonth + "" }, // 点击年 onClickYears() { let currentYear = this.currentYear - 1; for (let p = 3; p >= 0; p--) { this.lastYear[p] = currentYear--; } this.hide = 3; }, // 点击月 onClickMonth() { this.hide = 2; }, // 获取上个月的剩余多少天 nextMonth() { const ac = 42 - this.currentMonthDays.length - this.lastMonthDays.length; return ac; }, // 上 onClickUp() { let currentYear = new Date().getFullYear(); if (this.hide === 1) { if (this.currentMonth === 1) { this.currentYear--, (this.currentMonth = 13); } this.currentMonth--; } else if (this.hide === 2) { this.currentYear--; this.isNowYear = this.currentYear+'' === currentYear+'' } else { this.switchingYear(1); } }, // 下 onClickDown() { let currentYear = new Date().getFullYear(); if (this.hide === 1) { // 日 if (this.currentMonth === 12) { this.currentYear++, (this.currentMonth = 0); } this.currentMonth++; } else if (this.hide === 2) { // 月默认切换年 this.currentYear++; this.isNowYear = this.currentYear+'' === currentYear+'' } else { // 切换年的选择 this.switchingYear(2); } }, switchingYear(type) { // 1上,2下 if (type === 1) { // last最后一个为this的最后一个 let thisAnchor = this.lastYear[3] - 11; let lastAnchor = this.lastYear[3] - 15; this.thisYear = []; for (let p = 0; p < 12; p++) { this.thisYear[p] = thisAnchor++; } this.lastYear = []; for (let l = 0; l < 4; l++) { this.lastYear[l] = lastAnchor++; } } else if (type === 2) { let anchor = this.thisYear[11] + 1; this.lastYear = []; for (let p = 3; p >= 0; p--) { this.lastYear[p] = this.thisYear[11]--; } this.thisYear = []; for (let l = 0; l < 12; l++) { this.thisYear[l] = anchor++; } } }, }, }; </script> <style scoped lang="scss"> @import './utils/QCalendar.scss'; </style>
日历组件效果图
2022-12-27日补充
日
月
年
tips
支持滑动切换年月日,具体功能请移步组件文档
总结
加载全部内容