话不多说,先上在线Demo
1.需求分析
电脑右下角的日历各位观众老爷再熟悉不过了(特别是看还有几天发工资的时候),今天来实现一个阉割版的日历!
2.核心代码
<template>
<div class="bk-date-picker">
<div class="date-picker">
<div class="prev-date" @click="pickPreDay(currentYear, currentMonth, selectDay)">
<span style="color: #d9d9d9"><</span>
</div>
<a-input class="today" v-model="searchDate" size="samll" style="height: 24px; border:none" @change="handlePickerDialog(searchDate)" />
<div class="next-date" @click="pickNextDay(currentYear, currentMonth, selectDay)">
<span style="color: #d9d9d9">></span>
</div>
</div>
<a-modal
class="picker-dialog"
:destroyOnClose="!0"
centered
:width="720"
:height="500"
:visible="visible"
@cancel="cancel"
footer
:closable="false"
>
<div class="picker-header">
<div></div>
<div class="picker-year-month">
<div class="picker-left">
<span @click="pickPre(currentYear - 1, currentMonth + 1)" :style="{ color: '#d9d9d9' }" ><<</span>
<span @click="pickPre(currentYear, currentMonth)" :style="{ color: '#d9d9d9' }" style="margin-left: 20px" ><</span>
</div>
<div class="current-month">{{ currentYear }}年 {{ currentMonth }}月</div>
<div class="picker-right">
<span @click="pickNext(currentYear, currentMonth)" :style="{ color: '#d9d9d9' }" style="margin-right: 20px" >></span>
<span @click="pickNext(currentYear + 1, currentMonth - 1)" :style="{ color: '#d9d9d9' }" >>></span>
</div>
</div>
</div>
<div class="picker-content">
<div class="picker-week">
<div class="week" v-for="week in weeks" :key="week">
<span>{{ week }}</span>
</div>
<template v-for="(dayObject, i) in days">
<div :class="[ 'day', newDate == dayObject.day || isSelectDate(dayObject.day) ? 'selected' : '', isActive(dayObject.day) ? 'active' : '', ]"
@click="getDayTime(dayObject.day, i)" :key="i" >
<!-- 非本月 文字加灰色 -->
<span v-if="dayObject.day.getMonth() + 1 != currentMonth" class="other-month" @click="getDayTime(dayObject.day)" >{{ dayObject.day.getDate() }}</span>
<!-- 本月 还需要判断是不是这一天 -->
<template v-else>
<div @click="getDayTime(dayObject.day, i)">
<div>{{ dayObject.day.getDate() }}</div>
<div class="appointment-num" v-if="dayObject.num">({{ dayObject.num }})</div>
</div>
</template>
</div>
</template>
</div>
</div>
</a-modal>
</div>
</template>
<script>
import moment from "moment";
export default {
name: "DatePicker",
model: {
prop: "searchDate",
event: "selectDate"
},
props: {
searchDate: {
type: String,
required: true
}
},
data() {
return {
visible: true,
weeks: ["一", "二", "三", "四", "五", "六", "日"],
days: [],
currentDay: 1,
currentMonth: 1,
currentYear: 2022,
currentWeek: 1,
currYearMonthDay: undefined,
selectDay: undefined,
newDate: moment(new Date()).format("YYYY-MM-DD")
};
},
created() {},
mounted() {
this.currYearMonthDay = this.searchDate; // 当前年月日
let dateArr = this.searchDate.split("-");
this.selectDay = dateArr[dateArr.length - 1]; // 当月的几号
this.initData(null);
},
methods: {
// 初始化日历(按月加载,月初1号前置灰部分根据当天是周几开始往前补,月末从下月第一天补)
initData(cur) {
let date;
if (cur) {
date = new Date(cur.replace(/-/g, "/"));
} else {
let now = new Date();
let d = new Date(
this.formatDate(now.getFullYear(), now.getMonth(), 1).replace(
/-/g,
"/"
)
);
d.setDate(41); // 每次加载6周,每周7天
date = new Date(
this.formatDate(d.getFullYear(), d.getMonth() + 1, 1).replace(
/-/g,
"/"
)
);
}
this.currentYear = date.getFullYear();
this.currentMonth = date.getMonth() + 1;
this.currentDay = date.getDate();
this.currentWeek = date.getDay() - 1;
// 如果本月1号为周日,往前补6天
if (this.currentWeek === -1) {
this.currentWeek = 6;
}
let str = this.formatDate(
this.currentYear,
this.currentMonth,
this.currentDay
);
this.days.length = 0;
let daysArr = [];
// 先获取本月1号为周几,再往前补满本周
for (let i = this.currentWeek; i >= 0; i--) {
let d2 = new Date(str.replace(/-/g, "/"));
d2.setDate(d2.getDate() - i);
let dayObjectSelf = {}; // 用一个对象包装Date对象,便为以后预定功能添加属性
dayObjectSelf.day = d2;
daysArr.push(dayObjectSelf); // 将日期放入data 中的days数组 供页面渲染使用
}
// 其他周
for (let j = 1; j <= 41 - this.currentWeek; j++) {
let d3 = new Date(str.replace(/-/g, "/"));
d3.setDate(d3.getDate() + j);
let dayObjectOther = {};
dayObjectOther.day = d3;
daysArr.push(dayObjectOther);
}
this.days = daysArr;
},
// 返回 类似 2022-10-15 格式的字符串
formatDate(year, month, day) {
let y = year;
let m = month;
if (m < 10) {
m = "0" + m;
}
let d = day;
if (d < 10) {
d = "0" + d;
}
return y + "-" + m + "-" + d;
},
getDayTime(el, index) {
let value = moment(el).format("YYYY-MM-DD");
this.$emit("selectDate", value);
this.cancel();
this.newDate = el;
this.currYearMonthDay = value;
this.selectDay = moment(el).format("DD");
},
// 上一天
pickPreDay(year, month, day) {
if (year && month && day) {
let value = "";
let daysArr = this.days; // 日历数组(仅展示当月)
let currDay = this.formatDate(year, month, Number(day)); // 当前日期
// 日历中加载到第一天时,初始化日历更新 days
if (moment(daysArr[0].day).format("YYYY-MM-DD") === currDay) {
// 日历第一天是周一并且为当月1号,则更新上月日历
if (daysArr[0].day.getDay() === 1 && daysArr[0].day.getDate() === 1) {
month--;
}
daysArr = this.initData(this.formatDate(year, month, 1));
}
// console.log(daysArr);
if (daysArr?.length > 0) {
for (let i = 1; i < daysArr.length; i++) {
if (moment(daysArr[i].day).format("YYYY-MM-DD") === currDay) {
this.currentYear = daysArr[i - 1].day.getFullYear();
this.currentMonth = daysArr[i - 1].day.getMonth() + 1;
this.selectDay = daysArr[i - 1].day.getDate();
value = this.formatDate(
this.currentYear,
this.currentMonth,
this.selectDay
);
// console.log(value);
this.$emit("selectDate", value);
}
}
}
}
},
// 下一天
pickNextDay(year, month, day) {
if (year && month && day) {
let value = "";
let daysArr = this.days; // 日历数组(仅展示当月)
let currDay = this.formatDate(year, month, Number(day)); // 当前日期
// 日历中加载到倒数第二天时,初始化日历更新 days
if (
moment(daysArr[daysArr.length - 2].day).format("YYYY-MM-DD") ===
currDay
) {
daysArr = this.initData(this.formatDate(year, month, 1));
}
// console.log(daysArr);
if (daysArr?.length > 0) {
for (let i = 1; i < daysArr.length; i++) {
if (moment(daysArr[i].day).format("YYYY-MM-DD") === currDay) {
this.currentYear = daysArr[i + 1].day.getFullYear();
this.currentMonth = daysArr[i + 1].day.getMonth() + 1;
this.selectDay = daysArr[i + 1].day.getDate();
value = this.formatDate(
this.currentYear,
this.currentMonth,
this.selectDay
);
// console.log(value);
this.$emit("selectDate", value);
}
}
}
}
},
// 上个月、去年
pickPre(year, month) {
// setDate(0); 上月最后一天 setDate(-1); 上月倒数第二天 setDate(dx) 参数dx为 上月最后一天的前后dx天
if (year && month) {
let d = new Date(this.formatDate(year, month, 1).replace(/-/g, "/"));
d.setDate(0);
this.currYearMonthDay = this.formatDate(
d.getFullYear(),
d.getMonth() + 1,
1
);
this.initData(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
}
},
// 下个月、明年
pickNext(year, month) {
if (year && month) {
let d = new Date(this.formatDate(year, month, 1).replace(/-/g, "/"));
d.setDate(41);
this.currYearMonthDay = this.formatDate(
d.getFullYear(),
d.getMonth() + 1,
1
);
this.initData(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
}
},
// 是否为查询日期
isSelectDate(day) {
let { searchDate } = this;
let ergodicDate = this.formatDate(
day.getFullYear(),
day.getMonth() + 1,
day.getDate()
);
return searchDate == ergodicDate;
},
// 是否为当天
isActive(day) {
return (
day.getFullYear() == new Date().getFullYear() &&
day.getMonth() == new Date().getMonth() &&
day.getDate() == new Date().getDate()
);
},
handlePickerDialog(searchDate) {
let strArr = searchDate.split("-");
this.visible = true;
this.currYearMonthDay = searchDate;
this.initData(this.formatDate(strArr[0], strArr[1], 1));
},
cancel() {
// this.visible = false;
}
}
};
</script>
<style lang="less">
.bk-date-picker {
.date-picker {
// width: 200px;
height: 32px;
width: 720px;
border: 1px solid #d9d9d9;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
transition: 0.3s;
.prev-date,
.next-date {
width: 30px;
height: 100%;
.icon {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
.prev-date {
border-right: 1px solid #d9d9d9;
}
.next-date {
border-left: 1px solid #d9d9d9;
}
.today {
line-height: 30px;
}
}
.date-picker:hover {
border: 1px solid #0074ff;
}
}
.picker-dialog {
.ant-modal-content {
.ant-modal-body {
padding: 0px;
.picker-header {
padding: 0 17px;
height: 64px;
border-bottom: 1px solid #eeeeee;
display: flex;
justify-content: space-between;
align-content: center;
.picker-year-month {
display: flex;
justify-content: space-between;
align-items: center;
.current-month {
margin: 0 40px;
font-size: 16px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
line-height: 22px;
}
}
}
.picker-content {
border: 1px solid #d9d9d9;
.picker-week {
display: grid;
margin: auto;
row-gap: 10px;
justify-content: space-evenly;
align-items: center;
text-align: center;
grid-template-columns: repeat(7, 50px);
grid-template-rows: repeat(7, 50px);
.week {
width: 50px;
}
.day {
width: 50px;
height: 50px;
font-size: 16px;
.other-month {
color: gainsboro;
}
.appointment-num {
font-size: 14px;
color: #0074ff;
}
.active {
width: 50px;
height: 50px;
background: rgba(0, 116, 255, 0.1);
border-radius: 4px;
border: 1px solid #0074ff;
color: #0074ff;
}
}
.lineFlex {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.active {
width: 50px;
height: 50px;
background: rgba(0, 116, 255, 0.1);
border-radius: 4px;
border: 1px solid #0074ff;
color: #0074ff;
}
.selected {
width: 50px;
height: 50px;
border-radius: 4px;
border: 1px solid #0074ff;
color: #0074ff;
}
.day:hover {
border-radius: 4px;
border: 1px solid #0074ff;
cursor: pointer;
}
}
}
}
}
}
</style>