日历面板为啥是6行7列呢?
7列:周一到周日共7天
6行:第一行肯定是显示当月的1号,如果这个月的1号为周六,这个月又有31天,那么必须是6行才能完全显示出当月的全部天数
思路
日历面板6*7共42天 由上个月天数+本月天数+下个月天数组成
下面以2022年7月为例子
1.上个月的天数
我们需要先获取本月1号为周几,算出上个月在本月所占的天数(例如7月1号为周五,那么就说明我们需要为上月留5个位子)
获取上个月的日期(例如6月一共有30天,那么我们循环5次并反转下数组,就可以获取[26,27,28,29,30]这5个日期)
2.本月
直接获取这个月的天数
3.下个月
日历面板共42天,42-(上个月天数+本月天数)然后用循环处理即可
代码如下:
在src目录下新增了utlis/index.js
//传入时间戳获取当前年月日
let getYearMonthDay = (timeStamp) => {
let date = new Date(timeStamp)
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
let weekDay = date.getDay()
return { year, month, day, weekDay }
}
//获取每个月1号为周几
let getStartMonthDay = (year, month) => {
return new Date(year, month - 1, 1).getDay()
}
//获取每个月的天数
let getMonthCount = (year, month) => {
return new Date(year, month, 0).getDate()
}
export { getYearMonthDay, getStartMonthDay, getMonthCount }
在src目录下新增了views/DemoPage.vue、views/el-calendar.vue
//DemoPage.vue
<template>
<div>
<elCalendar v-model="now" />
</div>
</template>
<script>
import elCalendar from "./el-calendar.vue";
export default {
components: {
elCalendar,
},
data() {
return {
now:new Date().getTime()
};
},
};
</script>
<style></style>
//el-calendar.vue
<template>
<div class="calendar" v-click-outside>
<input
type="text"
:value="formatDate"
placeholder="选择日期"
class="calendar_input"
ref="input"
/>
<div class="calendar-body" v-if="isShow">
<div class="calendar-header">
<span class="preYear" @click="handlePreYear"><<</span>
<span class="preMonth" @click="handlePreMonth"><</span>
<span class="header-time">{{ time[0] + "年" + time[1] + "月" }}</span>
<span class="nextYear" @click="handleNextMonth">></span>
<span class="nextMonth" @click="handleNextYear">>></span>
</div>
<div class="calendar-table">
<table class="calendar-inner-table">
<tr>
<th v-for="(item, index) in weekDay" :key="index">{{ item }}</th>
</tr>
<tr v-for="(item, index) in calendarList" :key="index">
<td
v-for="(i, ind) in item"
:key="ind"
:class="[
i.myClassName,
{ currentDay: isCurrentDayStyle(i) },
{ selected: i.selected },
]"
@click="selectExactDay(i)"
>
{{ i.day }}
</td>
</tr>
</table>
</div>
</div>
</div>
</template>
<script>
import * as util from "@/utlis/index";
export default {
props: {
value: {
// type: Date||Number,
type: Number,
default: () => new Date(),
},
},
directives: {
// 这里是为了解决点击的时候 元素是否展示的问题
clickOutside: {
bind(el, binding, vnode) {
let handler = (e) => {
if (el.contains(e.target)) {
if (!vnode.context.isShow) {
// 包括
vnode.context.isShow = true;
e.target.focus();
}
} else {
if (vnode.context.isShow) {
// 不包括
vnode.context.isShow = false;
e.target.blur();
}
}
};
document.addEventListener("click", handler);
},
unbind(el) {
document.removeEventListener("click", el.handler);
},
},
},
data() {
return {
time: [],
isShow: false,
weekDay: ["日", "一", "二", "三", "四", "五", "六"],
calendarList: [],
month: 0,
year: 0,
flag: false,
inputVal: [],
};
},
mounted() {
this.getExactCurrentMonthDay(new Date().getTime());
},
computed: {
formatDate() {
const year = this.inputVal[0];
const month =
this.inputVal[1] < 10 ? "0" + this.inputVal[1] : this.inputVal[1];
const day =
this.inputVal[2] < 10 ? "0" + this.inputVal[2] : this.inputVal[2];
//因为一开始没选中到时候 会出现undefined的问题所以多了一个判断
return !year && !month && !day ? "" : `${year}-${month}-${day}`;
},
},
methods: {
//上一年
handlePreYear() {
let year = this.time[0];
let month = this.time[1];
year--;
//这里得去更新全局time这个变量的值
this.$set(this.time, 0, year);
this.calendarList = this.combineData(year, month);
},
//下一年
handleNextYear() {
let year = this.time[0];
let month = this.time[1];
year++;
this.$set(this.time, 0, year);
this.calendarList = this.combineData(year, month);
},
//上个月
handlePreMonth() {
let year = this.time[0];
let month = this.time[1];
if (month > 1) {
month--;
} else {
year--;
month = 12;
}
//这里得去更新全局time这个变量的值
this.$set(this.time, 0, year);
this.$set(this.time, 1, month);
this.calendarList = this.combineData(year, month);
},
//下个月
handleNextMonth() {
let year = this.time[0];
let month = this.time[1];
if (month > 11) {
year++;
month = 1;
} else {
month++;
}
this.$set(this.time, 0, year);
this.$set(this.time, 1, month);
this.calendarList = this.combineData(year, month);
},
//获取当前面板的具体日期(上个月+本月+下个月) 6*7
getExactCurrentMonthDay(timeStamp) {
//当天日期
const { year, month, day } = util.getYearMonthDay(timeStamp);
this.time = [year, month, day];
//获取这个月显示的所有日期
this.calendarList = this.combineData(year, month);
//flag是为了还未选中时 做了个开关
if (this.flag) {
//选中日期的标识
this.calendarList.forEach((item) => {
item.forEach((i) => {
if (i.year == year && i.month == month && i.day == day) {
i.selected = true;
}
});
});
}
this.flag = true;
},
//抽成一个函数 集中处理数据
combineData(year, month) {
//上个月在本月占位的个数(即这个月1号是周几,如果是周五,那么就说明上个月在本月占位是5个)
let lastMonthDayCount = util.getStartMonthDay(year, month);
//这里需要注意的是周一到周六对应的是1-6,而周日是0
lastMonthDayCount = lastMonthDayCount == 0 ? 7 : lastMonthDayCount;
//获取上个月的具体天数(即上个月最后一天的日期)
const lastMonthDay = util.getMonthCount(year, month - 1);
//组装后的上个月日期的数组对象
const lastExactDate = this.getlastMonthDay(
lastMonthDay,
lastMonthDayCount,
"lastMonthDay",
year,
month
);
//本月天数
const currentMonthDayCount = util.getMonthCount(year, month);
//组装后的本月日期的数组对象
const currentExactDate = this.getMonthDay(
currentMonthDayCount,
"currentMothDay",
year,
month
);
//下个月占位的个数
const nextMonthDayCount = 42 - (lastMonthDayCount + currentMonthDayCount);
//组装后的下个月日期的数组对象
const nextExactDate = this.getMonthDay(
nextMonthDayCount,
"nextMothDay",
year,
month
);
//获取这个月显示的所有日期(上个月+本月+下个月)
const calendarData = [
...lastExactDate,
...currentExactDate,
...nextExactDate,
];
//变成二维数组 为了方便渲染6行7列的数据
const twoScaleArr = this.toTwoScaleArr(calendarData, 7);
return twoScaleArr;
},
//生成天数的数据
getMonthDay(count, className, year, month) {
let nextMonthArr = [];
for (let i = 1; i <= count; i++) {
nextMonthArr.push({
year: year,
month: className == "currentMothDay" ? month : month + 1,
day: i,
myClassName: className,
selected: false,
});
}
return nextMonthArr;
},
getlastMonthDay(lastMonthDay, lastMonthDayCount, className, year, month) {
let lastMonthDayArr = [];
while (lastMonthDayCount > 0) {
lastMonthDayArr.push({
year: year,
month: month - 1,
day: lastMonthDay--,
myClassName: className,
selected: false,
});
lastMonthDayCount--;
}
return lastMonthDayArr.reverse();
},
// 一维数组转二维数组
toTwoScaleArr(arr, num) {
const len = Math.ceil(arr.length / num);
let newArr = [];
for (let i = 0; i < len; i++) {
newArr.push(arr.slice(i * num, num * (i + 1)));
}
return newArr;
},
//判断是否为当天的样式
isCurrentDayStyle(item) {
const { year, month, day } = util.getYearMonthDay(new Date().getTime());
const { year: y, month: m, day: d } = util.getYearMonthDay(
new Date(item.year, item.month - 1, item.day).getTime()
);
return year == y && month == m && day == d;
},
//选中的样式
selectExactDay(item) {
const { year, month, day } = item;
this.time = [year, month, day];
this.inputVal = this.time;
this.getExactCurrentMonthDay(
new Date(this.time[0], this.time[1] - 1, this.time[2]).getTime()
);
this.isShow = false;
},
},
};
</script>
<style scoped>
.calendar {
width: 322px;
height: 312px;
text-align: center;
margin: 0 auto;
}
.calendar_input {
height: 40px;
line-height: 40px;
outline: none;
padding: 0 15px;
width: 320px;
outline: none;
margin-left: -4px;
text-indent: 5px;
border-radius: 4px;
border: 1px solid #dcdfe6;
color: #606266;
box-sizing: border-box;
}
.calendar_input:focus {
outline: none;
border-color: #409eff;
}
.calendar-body {
margin-top: 10px;
border: 1px solid #ddd;
width: 322px;
height: 312px;
box-sizing: border-box;
border-radius: 5px;
}
.calendar-header {
height: 30px;
line-height: 30px;
display: flex;
margin: 12px;
text-align: center;
cursor: pointer;
}
.calendar-header :hover {
color: #409eff;
}
.preYear,
.preMonth,
.nextYear,
.nextMonth {
color: #606266;
user-select: none;
}
.header-time {
display: inline-block;
width: 80%;
text-align: center;
color: #606266;
}
.preMonth {
margin-left: 20px;
}
.nextYear {
margin-right: 20px;
}
.calendar-table {
width: 100%;
border-collapse: collapse;
}
.calendar-table th {
padding: 5px;
width: 41px;
height: 41px;
box-sizing: border-box;
color: #606266;
}
.calendar-table td {
color: #606266;
user-select: none;
}
.calendar-inner-table {
width: 100%;
}
.calendar-inner-table td {
width: 32px;
height: 32px;
padding: 4px 0;
box-sizing: border-box;
text-align: center;
cursor: pointer;
}
.calendar-inner-table tr:nth-child(1) {
margin-bottom: 5px;
}
.calendar-inner-table .lastMonthDay,
.calendar-inner-table .nextMothDay {
color: #c0c4cc !important;
}
.calendar-table .currentDay {
color: #409eff;
}
.calendar-table .selected {
color: #fff;
background-color: #409eff;
border-radius: 50%;
display: block;
margin: 0 auto;
}
</style>