基于vue2实现一个日历组件
短信预约 -IT技能 免费直播动态提醒
不用任何第三方库,只基于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>
// 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.class="lazy" data-srcElement.innerText;
this.isNowYear = val.class="lazy" data-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
支持滑动切换年月日,具体功能请移步组件文档
总结
到此这篇关于基于vue2实现一个日历组件的文章就介绍到这了,更多相关vue日历组件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341