效果图
image.png
组件代码
<template>
<div class="calendar">
<slot name="header">
<div class="top">
<div class="current">{{ time }}</div>
<div class="box">
<div class="btn" @click="btn('prevMonth')">上个月</div>
<div class="btn" @click="btn('currentMonth')">当前月</div>
<div class="btn" @click="btn('nextMonth')">下个月</div>
</div>
</div>
</slot>
<div class="date">
<div class="item">日</div>
<div class="item">一</div>
<div class="item">二</div>
<div class="item">三</div>
<div class="item">四</div>
<div class="item">五</div>
<div class="item">六</div>
<template v-for="(item,index) in list">
<div class="item" :class="{active:item.type === currentSelectDate,current:currentDate == item.type,grey:item.next||item.prve}" :key="item.type" @click="click(item)">
<slot :index="index" :item="item">{{ item.date }}</slot>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
name: "Calendar",
props: {
value: { type: Date, default: () => new Date() }
},
data() {
return {
list: [],
time: "",
currentSelectDate: "",
currentDate: ""
};
},
watch: {
value(a) {
if (a) this.time = a;
this.btn(0);
}
},
created() {
this.btn();
},
methods: {
click(item) {
if (this.currentSelectDate === item.type) {
// 取消选中
this.currentSelectDate = "";
// 选中日期
} else this.currentSelectDate = item.type;
// 选中日期是上个月
if (item.next) this.btn("nextMonth");
// 选中日期是下个月
if (item.prve) this.btn("prevMonth");
this.$emit("click", { ...item, selectDate: this.currentSelectDate });
this.$emit("update:select-date", this.currentSelectDate);
},
// 默认当前月
btn(type = "currentMonth") {
// 获取指定时间的年份和月份
let currentYear = new Date(this.time).getFullYear();
let currentMonth = new Date(this.time).getMonth() + 1;
// 点击上个月
if (type === "prevMonth") {
// 指定时间的月份为1月,上一月为上一年的12月
if (currentMonth === 1) (currentMonth = 13), (currentYear = currentYear - 1);
currentMonth--;
}
// 当前月
if (type === "currentMonth") {
// 获取当前时间的年月日
currentYear = new Date().getFullYear();
currentMonth = new Date().getMonth() + 1;
let currentDate = new Date().getDate();
this.currentDate = `${currentYear}-${currentMonth > 9 ? currentMonth : "0" + currentMonth}-${currentDate > 9 ? currentDate : "0" + currentDate}`;
}
// 下个月
if (type === "nextMonth") {
// 指定时间的月份为12月,下一月为下一年的1月
if (currentMonth === 12) (currentMonth = 0), (currentYear = currentYear + 1);
currentMonth++;
}
// 当前日历展示的年月
this.time = `${currentYear}-${currentMonth > 9 ? currentMonth : "0" + currentMonth}`;
let date = `${currentYear}-${currentMonth > 9 ? currentMonth : "0" + currentMonth}-01`;
this.init(date);
this.$emit("change-month", { currentYear, currentMonth, type, date });
this.$emit("update:current-date", this.currentDate);
},
// 生成日历日期,因每月开始和结束不是周日和周六,需要取上月月末和下月月初补满每周七天,所以生成的总天数共35-42天。
init(time) {
// 获得指定时间年月,月总天数
let date = new Date(time);
let currentYear = date.getFullYear(); // 年
let currentMonth = date.getMonth() + 1; // 月
let currentMonthDate = new Date(currentYear, currentMonth, 0).getDate(); // 当月总天数
let list = new Array(currentMonthDate).fill().map((_, i) => ({ year: currentYear, month: currentMonth, date: i + 1 }));
// 获取上月,需要处理跨年
let preMonth = currentMonth;
if (preMonth === 1) (preMonth = 13), (currentYear = currentYear - 1);
let beforeMonthDate = new Date(currentYear, preMonth - 1, 0).getDate(); // 上月总天数
let beforeDate = new Date(`${currentYear}-${preMonth - 1}-${beforeMonthDate}`).getDay();
for (let i = 0; i <= beforeDate; i++) list.unshift({ prve: true, year: currentYear, month: preMonth - 1, date: beforeMonthDate - i });
// 获取下月,需要处理跨年
let nextMonth = currentMonth;
if (nextMonth === 12) (nextMonth = 0), (currentYear = currentYear + 1);
let afterDate = 7 - ((currentMonthDate + beforeDate) % 7); // 下月月头
for (let i = 1; i < afterDate; i++) list.push({ next: true, year: currentYear, month: nextMonth + 1, date: i });
this.list = list.map((e, i) => {
let { year, month, date } = e;
return { ...e, week: Math.floor(i % 7), type: `${year}-${month > 9 ? month : "0" + month}-${date > 9 ? date : "0" + date}` };
});
}
}
};
</script>
<style lang='less' scoped>
.calendar {
min-height: 300px;
.top {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
margin: 0 0 20px 0;
padding: 0 10px;
}
.box {
display: flex;
justify-content: space-evenly;
width: 160px;
border: 1px solid #eee;
border-radius: 5px;
margin: 5px;
color: #666;
.btn {
padding: 3px 5px;
cursor: pointer;
transition: all 0.1s;
&:nth-child(2) {
border: 1px solid #eee;
border-top: none;
border-bottom: none;
}
&:hover {
color: #333;
background: #eee;
transition: all 0.1s;
}
}
}
.item {
flex: 0 0 13%;
height: 35px;
margin: 2px;
min-width: 20px;
text-align: center;
color: #444;
padding-top: 5px;
box-sizing: border-box;
border-radius: 5px;
transition: all 0.2s;
&.grey {
color: #999;
}
&:hover {
transition: all 0.2s;
cursor: pointer;
background: #5164e7a0;
color: #fff;
}
&.current {
transition: all 0.2s;
background: #5164e7a0;
color: #fff;
}
&.active {
transition: all 0.2s;
background: #5164e7;
color: #fff;
}
}
.date {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
}
</style>
<template>
<div class="calendar">
<div class="head">
<div class="top">
<el-date-picker class="pick" ref="time" v-model="Time" type="date" placeholder="选择日期" :editable="false"
:clearable="false" size="small" format="YYYY-MM-DD" value-format="YYYY-MM-DD" @change="handlerDate" />
<div class="box">
<div class="btn" @click="handlerDate('prevMonth')">上个月</div>
<div class="btn" @click="handlerDate('currentMonth')">当前月</div>
<div class="btn" @click="handlerDate('nextMonth')">下个月</div>
</div>
</div>
<div class="bottom">
<slot name="tips"> </slot>
</div>
</div>
<div class="date">
<div class="item">日</div>
<div class="item">一</div>
<div class="item">二</div>
<div class="item">三</div>
<div class="item">四</div>
<div class="item">五</div>
<div class="item">六</div>
<template v-for="(item, index) in List" :key="item.time">
<div class="item"
:class="{ active: item.time === Time, current: currentDate == item.time, grey: item.next || item.prve }"
@click="click(item)">
<slot :index="index" :item="item">{{ item.date }}</slot>
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts" name="Calendar">
import { watch, toRefs, computed, reactive } from 'vue';
const emit = defineEmits(['changeDate', 'update:currentDate', 'change-month'])
const props = defineProps({
currentDate: {
type: String,
default: ''
}
});
const Data = reactive<{
selectMonth: string,
List: Array<Object>,
}>({
selectMonth: '',
List: []
})
const { currentDate } = toRefs(props);
const { selectMonth, List } = toRefs(Data)
const Time = computed({
set: (val) => emit("update:currentDate", val),
get: () => currentDate.value
})
// 点击日历
function click(item: Object) {
let { year, month, date, week, time } = item;
if (time) Time.value = time;
if (month != selectMonth.value) {
emit("change-month", month)
handlerDate();
}
emit("changeDate");
}
// 处理日期时间
function handlerDate(type?: any) {
let date = Time.value ? new Date(Time.value) : new Date();
if (type == 'currentMonth') date = new Date();
// 获取指定时间的年份和月份
let currentYear: string | number = date.getFullYear();
let currentMonth: string | number = date.getMonth() + 1;
let currentDate: string | number = date.getDate();
// 点击上个月
if (type === "prevMonth") {
// 指定时间的月份为1月,上一月为上一年的12月
if (currentMonth === 1) {
currentMonth = 12;
currentYear = currentYear - 1;
} else {
currentMonth--;
}
}
// 下个月
if (type === "nextMonth") {
// 指定时间的月份为12月,下一月为下一年的1月
if (currentMonth === 12) {
currentMonth = 1;
currentYear = currentYear + 1;
} else {
currentMonth++;
}
}
currentMonth = currentMonth.toString().padStart(2, '0');
currentDate = currentDate.toString().padStart(2, '0');
// 当前日历展示的年月日
Time.value = `${currentYear}-${currentMonth}-${currentDate}`;
selectMonth.value = currentMonth;
init(currentYear, +currentMonth);
}
// 生成日历日期,因每月开始和结束不是周日和周六,需要取上月月末和下月月初补满每周七天,所以生成的总天数共35-42天。
function init(year: number, month: number) {
// 获得指定时间年月,月总天数
let date = new Date(year, month - 1);
let currentYear = date.getFullYear(); // 年
let currentMonth = date.getMonth() + 1; // 月
let currentMonthDate = new Date(currentYear, currentMonth, 0).getDate(); // 当月总天数
let list: Array<Object> = new Array(currentMonthDate).fill(1).map((_, i) => ({ year: currentYear, month: currentMonth, date: i + 1 }));
// 获取上月,需要处理跨年
let preMonth = currentMonth;
let preYear = currentYear;
if (preMonth === 1) (preMonth = 13), (preYear = preYear - 1);
let beforeMonthDate = new Date(currentYear, preMonth - 1, 0).getDate(); // 上月总天数
let beforeDate = new Date(`${currentYear}-${preMonth - 1}-${beforeMonthDate}`).getDay(); //获取上月最后一天是周几
for (let i = 0; i <= beforeDate; i++) list.unshift({ prve: true, year: preYear, month: preMonth - 1, date: beforeMonthDate - i });
// 获取下月,需要处理跨年
let nextMonth = currentMonth;
let nextYear = currentYear;
if (nextMonth === 12) (nextMonth = 0), (nextYear = nextYear + 1);
// let afterDate = 7 - ((currentMonthDate + beforeDate) % 7); // 下月月头,按周填充补满
let afterDate = 42 - (currentMonthDate + beforeDate); // 下月月头,按照每月5周填充
for (let i = 1; i < afterDate; i++) list.push({ next: true, year: nextYear, month: nextMonth + 1, date: i });
List.value = list.map((e, i) => {
let { year, month, date } = e;
return { year, month, date, week: Math.floor(i % 7), time: `${year}-${month.toString().padStart(2, 0)}-${date.toString().padStart(2, 0)}` };
});
}
handlerDate()
</script>
<style lang='scss' scoped>
.calendar {
min-height: 360px;
overflow: hidden;
.head {
margin: 0 0 15px 0;
padding: 0 10px;
.top {
display: flex;
justify-content: space-between;
align-items: center;
& :deep(.pick.el-input) {
width: 80px;
.el-input__prefix,
.el-input__suffix {
display: none;
}
.el-input__wrapper {
box-shadow: none;
text-align: left;
padding-left: 0;
padding-right: 0;
}
}
}
.bottom {
height: 35px;
line-height: 35px;
}
}
.box {
display: flex;
justify-content: space-evenly;
width: 160px;
border: 1px solid #eee;
border-radius: 5px;
margin: 5px;
color: #666;
.btn {
padding: 3px 5px;
cursor: pointer;
transition: all 0.1s;
&:nth-child(2) {
border: 1px solid #eee;
border-top: none;
border-bottom: none;
}
&:hover {
color: #333;
background: #eee;
transition: all 0.1s;
}
}
}
.item {
flex: 0 0 13%;
height: 35px;
margin: 2px;
min-width: 20px;
text-align: center;
color: #444;
padding-top: 5px;
box-sizing: border-box;
border-radius: 5px;
transition: all 0.2s;
&.grey {
color: #999;
}
&:hover {
transition: all 0.2s;
cursor: pointer;
background: #5164e7a0;
color: #fff;
}
&.current {
transition: all 0.2s;
background: #5164e7a0;
color: #fff;
}
&.active {
transition: all 0.2s;
background: #5164e7;
color: #fff;
}
}
.date {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
}
</style>
网友评论