自己封装的目的:
使用场景:打卡,日期计划,日期选择,特别日期标志
根据自己的需求可以定制化何样式
不依赖任何第三方插件或者组件,
效果图:
1、日历组件封装
<template>
<div class="calendar">
<div class="year-month">
<div class="arrow up-page" @click="upPage"></div>
<span>{{year}}年{{month}}月</span>
<div class="arrow next-page" @click="nextPage"></div>
</div>
<div class="week">
<div class="item" v-for="item of weeks">{{item}}</div>
</div>
<template v-for="line of dates">
<div class="week">
<div v-for="item of line" @click="onClick(item)" :class="['item' ,
{'current-month':month==item.month},
{'today':today==`${item.month}-${item.date}`},
{'clickFlag':clickFlag===item.timeStamp},
{'select':selectDates.find(t=> t>item.timeStamp&&t<item.timeStamp+oneDayTimeStamp)}]">{{item.date}}</div>
</div>
</template>
</div>
</template>
<script setup>
import { watchEffect, ref, onBeforeMount } from "vue"
const today = `${new Date().getMonth()+1}-${new Date().getDate()}`
const weeks = ["日", '一', '二', '三', '四', '五', '六']
const oneDayTimeStamp = 86400 * 1000
const dates = ref([])
const month = ref("")
const year = ref("")
const clickFlag = ref("")
const emit = defineEmits(["onSelect"])
const props = defineProps({
defaultDate: {
type: [String, Number],
default: Date.now()
},
selectDates: {
type: Array,
default () {
const now = Date.now();
const oneDayTimeStamp = 86400 * 1000
return [now - oneDayTimeStamp, now, now + oneDayTimeStamp, now + oneDayTimeStamp * 3]
}
},
})
onBeforeMount(() => {
let firstTimestamp = getCurrentPageTimestampOf1(props.defaultDate)
getList(firstTimestamp)
})
function upPage() {
let firstTimestamp = getFirstTimestampByUp()
getList(firstTimestamp)
}
function nextPage() {
let firstTimestamp = getFirstTimestampByNext()
getList(firstTimestamp)
}
// 获取当前页日期列表
function getList(timestamp) {
const temp = []
const list = []
// 包含一个足月的最小周期为42天
const pageT = 42
for (let i = 0; i < pageT; i++) {
temp.push(coverToDate(timestamp, i))
}
while (temp.length > 0) {
list.push(temp.splice(0, 7))
}
removeLine(list)
month.value = list[1][1].month
year.value = list[1][1].year
dates.value = list
}
// 获取上一页的第一个元素的时间戳
function getFirstTimestampByUp() {
const firstObj = dates.value[0][0]
let timestamp = 0
// 第一行第一个元素是1号,返回firstObj前一天的时间戳 ,
// 否则直接返回firstObj的时间戳
if (firstObj.date === 1) {
timestamp = firstObj.timeStamp - oneDayTimeStamp
} else {
timestamp = firstObj.timeStamp
}
return getCurrentPageTimestampOf1(timestamp)
}
// 获取下一页的第一个元素的时间戳
function getFirstTimestampByNext() {
let len = dates.value.length
const lastLine = dates.value[len - 1]
// 最后一行包含1号,直接返回第一个元素的时间戳,
// 否则返回最后一个元素的时间戳 + 一天的时间戳
const includeDateOf1 = lastLine.find(item => item.date === 1)
if (includeDateOf1) {
return lastLine[0].timeStamp
} else {
return lastLine[6].timeStamp + oneDayTimeStamp
}
}
// 删除掉不是属于当前月的行
function removeLine(list) {
if (list.length > 5) {
let index = list.slice(1).findIndex(line => line.some(item => item.date == 1))
if (index != -1) {
const startIndex = index + 1
// 最后一行的第一天
const lastLineOf1 = list[startIndex][0].date
if (lastLineOf1 === 1) {
list.splice(startIndex)
} else {
list.splice(startIndex + 1)
}
}
}
}
// 获取当页的第一个日期的时间戳
function getCurrentPageTimestampOf1(timestamp) {
const time = new Date(timestamp)
const year = time.getFullYear()
const month = time.getMonth() + 1
// 当月的第一天
const dateOf1 = new Date(`${year}/${month}/1`)
// 当月的第一天,是星期几
const weekOf1 = dateOf1.getDay()
// 当页的第一天时间戳
const firstTimestampOfPage = dateOf1.getTime() - weekOf1 * oneDayTimeStamp
return firstTimestampOfPage
}
// 时间戳转成日期对象
function coverToDate(firstTimestampOfPage, index) {
const timeStamp = firstTimestampOfPage + index * oneDayTimeStamp
let obj = new Date(timeStamp)
let date = obj.getDate()
const week = obj.getDay()
const month = obj.getMonth() + 1
const year = obj.getFullYear()
return { year, month, date, week, timeStamp }
}
function onClick(e) {
clickFlag.value = e.timeStamp
emit("onSelect", e)
}
</script>
<style lang="scss" scoped>
.calendar {
height: 642rpx;
margin: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10rpx;
background-color: #FFF;
.year-month{
height: 88rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2rpx solid #d7d7d7;
.arrow{
height: 30rpx;
width: 30rpx;
border: 2rpx solid #666;
border-bottom: none;
border-right: none;
}
.up-page{
margin-left: 20rpx;
transform: rotate(-45deg);
}
.next-page{
margin-right: 20rpx;
transform: rotate(135deg);
}
}
.week {
height: 70rpx;
display: flex;
color: #CCCCCC;
font-size: 28rpx;
justify-content: space-around;
align-items: center;
.item {
position: relative;
width: 70rpx;
height: 70rpx;
line-height: 70rpx;
text-align: center;
box-sizing: border-box;
&.current-month {
color: #666;
}
&.today::before {
position: absolute;
bottom: 2rpx;
left: 29rpx;
content: "";
display: block;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #6C93FF;
}
&.clickFlag {
color: #6C93FF ;
font-weight: 600;
border: 2rpx solid #6C93FF;
border-radius: 50%;
}
&.select {
color: #FFF;
background: #6C93FF;
}
}
}
}
</style>
2、使用日历组件
<template>
<div>
<Calendar @onSelect="onSelect"></Calendar>
<div>日历选择:{{selectDate}}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Calendar from "@/components/calendar.vue"
const selectDate = ref("")
function onSelect(e) {
selectDate.value = e
}
</script>
<style>
</style>