<template>
<div class="calendar">
<section class="header">
<van-icon
name="arrow-left"
:style="{ color: this.index === 0 ? '#bbbbbb' : '#222222' }"
@click="
() => {
this.isTouching = true
this.needAnimation = true
touchEnd('pre')
}
"
/>
{{ selectData.year }}年{{ selectData.month }}月
<van-icon
name="arrow"
:style="{ color: this.index === 1 ? '#bbbbbb' : '#222222' }"
@click="
() => {
this.isTouching = true
this.needAnimation = true
touchEnd('next')
}
"
/>
section>
<ul class="week-area">
<li class="week-item" v-for="(item, index) in weekArr" :key="index">
<span class="week-font calendar-item" :style="{ color: ['日', '六'].includes(item) ? '#FF7300' : '#555555' }">
{{ item }}
span>
li>
ul>
<section
ref="calendar"
class="data-container"
:style="{
transitionDuration: `${needHeightAnimation ? transitionDuration : 0}s`
}"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
>
<section
class="month-area"
:style="{
transform: `translateX(${-(translateIndex + 1) * 100}%)`,
transitionDuration: `${needAnimation ? transitionDuration : 0}s`
}"
>
<div
class="banner-area"
:style="{
transform: `translateY(${offsetY}px)`,
transitionDuration: `${needHeightAnimation ? transitionDuration : 0}s`
}"
>
<ul
v-for="(monthItem, monthIndex) in allDataArr"
:key="monthIndex"
class="data-area"
:style="{
transform: `translateX(${(translateIndex + isTouching ? touch.x : 0) * 100}%)`,
transitionDuration: `${isTouching ? 0 : transitionDuration}s`,
height: `${itemHeight * lineNum}px`
}"
>
<li
v-for="(item, index) in monthItem"
:key="index"
:class="[
'data-item',
{ selected: item.days === checkedDay },
{ 'other-item': item.type !== 'normal' && !isWeekView },
{ dayHide: item.dayHide }
]"
:style="`height: ${itemHeight}px`"
@click.stop="checkoutDate(item)"
>
<div class="data-font calendar-item" v-if="item.day">
{{ item.day }}
<div class="calendar-item-tip">
{{ item.tip }}
div>
div>
li>
ul>
div>
section>
section>
div>
template>
<script>
import dayjs from 'dayjs'
import { tools } from '@/utils/tool.js'
export default {
name: 'Calender',
props: {
checkedDay: {
type: String,
default: ''
},
agoDayHide: {
type: Number,
default: dayjs()
.subtract(1, 'days')
.valueOf()
},
futureDayHide: {},
useAbleTime: {
type: Array,
default: () => []
}
},
data() {
return {
weekArr: ['日', '一', '二', '三', '四', '五', '六'],
dataArr: [],
allDataArr: [],
selectData: {},
isSelectedCurrentDate: false,
translateIndex: 0,
transitionDuration: 0.3,
needAnimation: true,
isTouching: false,
touchStartPositionX: null,
touchStartPositionY: null,
touch: {
x: 0,
y: 0
},
isWeekView: false,
itemHeight: 52,
needHeightAnimation: false,
offsetY: 0,
lineNum: 0,
lastWeek: [],
nextWeek: [],
isDelay: true,
touchAreaHeight: 40,
touchAreaPadding: 10,
isClicked: false,
index: 0
}
},
created() {
this.checkoutCurrentDate()
},
mounted() {
setTimeout(() => {
const useableDate1 = this.useAbleTime.find(item => item.usableBookNum > 0 && item.bookDate)?.bookDate
const useableMonth = dayjs(useableDate1).month() + 1
if (this.selectData.month !== useableMonth) {
this.isTouching = true
this.needAnimation = true
this.touchEnd('next')
}
}, 300)
},
watch: {
dataArr: {
handler(val) {
this.changeAllData(val)
},
deep: true
},
isWeekView(val) {
if (!val) {
this.isSelectedCurrentDate = false
this.changeAllData(this.dataArr)
}
}
},
methods: {
changeAllData(val) {
if (this.isSelectedCurrentDate && !this.isWeekView) return
const preDate = this.getPreMonth()
console.log('preDate', preDate)
const preDataArr = this.getMonthData(preDate, true)
const nextDate = this.getNextMonth()
const nextDataArr = this.getMonthData(nextDate, true)
if (this.isWeekView) {
const sliceStart = this.dealWeekViewSliceStart()
preDataArr.splice(sliceStart, 7, ...this.lastWeek)
nextDataArr.splice(sliceStart, 7, ...this.nextWeek)
}
const delayHandle = isDelay => {
this.allDataArr = [preDataArr, val, nextDataArr]
this.needAnimation = false
this.translateIndex = 0
if (isDelay) this.isDelay = false
}
if (this.isDelay) {
delayHandle(this.isDelay)
return
}
setTimeout(
() => {
delayHandle()
},
this.isClicked && this.isWeekView ? 0 : this.transitionDuration * 1000
)
},
getCurrentDate() {
const year = new Date().getFullYear()
const month = new Date().getMonth() + 1
const day = new Date().getDate()
this.selectData = {
year,
month,
day,
days: tools.timeFormat(year, month, day)
}
},
getMonthData(date, unSelected = false) {
console.log('unSelected', unSelected)
const { year, month, day } = date
let dataArr = []
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
daysInMonth[1] = 29
}
const monthStartWeekDay = new Date(year, month - 1, 1).getDay()
const monthEndWeekDay = new Date(year, month, 1).getDay() || 7
const preInfo = this.getPreMonth(date)
const nextInfo = this.getNextMonth()
for (let i = 0; i < monthStartWeekDay; i++) {
let preObj = {
type: 'pre',
day: '',
month: preInfo.month,
year: preInfo.year
}
dataArr.push(preObj)
}
for (let i = 0; i < daysInMonth[month - 1]; i++) {
let days = tools.timeFormat(year, month, i + 1)
let t = this.$dayjs(days).valueOf()
const useAbleItem = this.useAbleTime?.find(v => v.bookDate === days)
let dayHide = false
let tip = ''
if (!useAbleItem) {
tip = '未开放'
dayHide = true
}
if (useAbleItem?.usableBookNum === 0) {
tip = '已约满'
}
if (this.agoDayHide > t) {
tip = ''
dayHide = true
}
if (useAbleItem?.usableBookNum > 0) {
tip = `余${useAbleItem?.usableBookNum}`
}
let itemObj = {
type: 'normal',
day: i + 1,
month,
year,
isSelected: false,
days,
dayHide,
tip
}
dataArr.push(itemObj)
}
for (let i = 0; i < 7 - monthEndWeekDay; i++) {
let nextObj = {
type: 'next',
day: '',
month: nextInfo.month,
year: nextInfo.year
}
dataArr.push(nextObj)
}
return dataArr
},
checkoutDate(selectData) {
if (selectData.dayHide || !selectData.day) return
this.isSelectedCurrentDate = true
this.isClicked = true
if (this.isWeekView && selectData.type !== 'normal') {
this.needAnimation = false
this.needHeightAnimation = false
}
if (selectData.type === 'next') {
this.translateIndex += 1
this.dealMonthData('NEXT_MONTH', selectData.day)
return
}
if (selectData.type === 'pre') {
this.translateIndex -= 1
this.dealMonthData('PRE_MONTH', selectData.day)
return
}
this.selectData.day = selectData.day
this.$emit('choseDay', selectData)
const oldSelectIndex = this.dataArr.findIndex(item => item.isSelected && item.type === 'normal')
const newSelectIndex = this.dataArr.findIndex(item => item.day === selectData.day && item.type === 'normal')
if (this.dataArr[oldSelectIndex]) this.$set(this.dataArr[oldSelectIndex], 'isSelected', false)
if (this.dataArr[newSelectIndex]) this.$set(this.dataArr[newSelectIndex], 'isSelected', true)
},
getPreMonth(date, appointDay = 1) {
let { year, month } = date || this.selectData
if (month === 1) {
year -= 1
month = 12
} else {
month -= 1
}
return { year, month, day: appointDay }
},
getNextMonth(appointDay = 1) {
let { year, month } = this.selectData
if (month === 12) {
year += 1
month = 1
} else {
month += 1
}
return { year, month, day: appointDay }
},
handlePreMonth() {
this.dealMonthData('PRE_MONTH')
},
handleNextMonth() {
this.dealMonthData('NEXT_MONTH')
},
dealMonthData(type, appointDay = 1) {
this.isSelectedCurrentDate = false
switch (type) {
case 'PRE_MONTH':
this.selectData = this.getPreMonth('', appointDay)
break
case 'NEXT_MONTH':
this.selectData = this.getNextMonth(appointDay)
break
default:
break
}
this.dataArr = this.getMonthData(this.selectData)
this.lineNum = Math.ceil(this.dataArr.length / 7)
},
checkoutCurrentDate() {
this.isDelay = true
this.getCurrentDate()
this.dealMonthData()
},
touchStart(event) {
this.isTouching = true
this.needAnimation = true
this.isClicked = false
this.touchStartPositionX = event.touches[0].clientX
this.touchStartPositionY = event.touches[0].clientY
this.touch = {
x: 0
}
},
touchMove(event) {
const moveX = event.touches[0].clientX - this.touchStartPositionX
const moveY = event.touches[0].clientY - this.touchStartPositionY
if (Math.abs(moveX) > Math.abs(moveY)) {
this.needHeightAnimation = false
this.touch = {
x: moveX / this.$refs.calendar.offsetWidth,
y: 0
}
} else {
this.needHeightAnimation = true
this.touch = {
x: 0,
y: moveY / this.$refs.calendar.offsetHeight
}
}
},
touchEnd(type) {
this.isTouching = false
const { x, y } = this.touch
if ((Math.abs(x) > Math.abs(y) && Math.abs(x) > 0.3) || ['pre', 'next'].includes(type)) {
if (x > 0 || type === 'pre') {
this.index -= 1
if (this.index < 0) {
this.index = 0
} else {
this.translateIndex -= 1
console.log('translateIndex', this.translateIndex)
this.isWeekView ? this.handlePreWeek() : this.handlePreMonth()
}
}
if (x < 0 || type === 'next') {
this.index += 1
if (this.index > 1) {
this.index = 1
} else {
this.translateIndex += 1
console.log('translateIndex', this.translateIndex)
this.isWeekView ? this.handleNextWeek() : this.handleNextMonth()
}
}
}
this.touch = {
x: 0,
y: 0
}
},
getInfoOfWeekView(selectedIndex, length) {
const indexOfLine = Math.ceil((selectedIndex + 1) / 7)
const totalLine = Math.ceil(length / 7)
const sliceStart = (indexOfLine - 1) * 7
const sliceEnd = sliceStart + 7
return { indexOfLine, totalLine, sliceStart, sliceEnd }
},
dealWeekViewSliceStart() {
const selectedIndex = this.dataArr.findIndex(item => item.isSelected)
const { indexOfLine, totalLine, sliceStart, sliceEnd } = this.getInfoOfWeekView(
selectedIndex,
this.dataArr.length
)
this.offsetY = -((indexOfLine - 1) * this.itemHeight)
if (indexOfLine === 1) {
const preInfo = this.getPreMonth()
const preDataArr = this.getMonthData(preInfo, true)
const preDay = this.dataArr[0].day - 1 || preDataArr[preDataArr.length - 1].day
const preIndex = preDataArr.findIndex(item => item.day === preDay && item.type === 'normal')
const { sliceStart: preSliceStart, sliceEnd: preSliceEnd } = this.getInfoOfWeekView(preIndex, preDataArr.length)
this.lastWeek = preDataArr.slice(preSliceStart, preSliceEnd)
} else {
this.lastWeek = this.dataArr.slice(sliceStart - 7, sliceEnd - 7)
}
if (indexOfLine >= totalLine) {
const nextInfo = this.getNextMonth()
const nextDataArr = this.getMonthData(nextInfo, true)
const nextDay =
this.dataArr[this.dataArr.length - 1].type === 'normal' ? 1 : this.dataArr[this.dataArr.length - 1].day + 1
const nextIndex = nextDataArr.findIndex(item => item.day === nextDay)
const { sliceStart: nextSliceStart, sliceEnd: nextSliceEnd } = this.getInfoOfWeekView(
nextIndex,
nextDataArr.length
)
this.nextWeek = nextDataArr.slice(nextSliceStart, nextSliceEnd)
} else {
this.nextWeek = this.dataArr.slice(sliceStart + 7, sliceEnd + 7)
}
return sliceStart
},
handlePreWeek() {
this.dealWeekData('PRE_WEEK')
},
handleNextWeek() {
this.dealWeekData('NEXT_WEEK')
},
dealWeekData(type) {
const { year, month, day } =
type === 'PRE_WEEK' ? this.lastWeek.find(item => item.type === 'normal') : this.nextWeek[0]
this.selectData = { year, month, day }
this.dataArr = this.getMonthData(this.selectData)
this.lineNum = Math.ceil(this.dataArr.length / 7)
this.offsetY -= this.itemHeight
this.dealWeekViewData()
},
dealWeekViewData() {
const sliceStart = this.dealWeekViewSliceStart()
this.allDataArr[0].splice(sliceStart, 7, ...this.lastWeek)
this.allDataArr[2].splice(sliceStart, 7, ...this.nextWeek)
}
}
}
</script>
<style lang="scss" scoped>
.calendar {
overflow-x: hidden;
padding: 12px 10px 14px;
.header {
color: #222222;
text-align: center;
height: 23px;
font-size: 16px;
font-weight: 500;
line-height: 23px;
padding: 0 10px;
@include flexVar(space-between, center);
}
.calendar-item {
display: block;
min-width: 44px;
height: auto;
width: auto;
text-align: center;
padding: 4px;
&-tip {
font-size: 12px;
min-height: 16px;
font-weight: 400;
transform: scale(0.8);
}
}
.selected .calendar-item {
color: #fff;
background: #2689ff;
box-shadow: 0px 2px 4px 0px rgba(38, 137, 255, 0.3);
border-radius: 8px;
}
.week-area {
width: 100%;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.week-item {
flex: 0 0 44px;
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.week-font {
font-size: 15px;
color: #222222;
font-weight: 500;
}
.data-container {
overflow: hidden;
position: relative;
}
.banner-area {
width: 300%;
display: flex;
}
.data-area {
width: 100%;
height: 100%;
display: flex;
flex-flow: row wrap;
}
.data-item {
flex: 0 0 14.285%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.data-font {
color: #222222;
font-size: 18px;
font-weight: 600;
}
.other-item .data-font {
color: #ccc;
}
.dayHide .data-font {
color: #bbbbbb;
}
.touch-area {
width: 100%;
box-sizing: border-box;
background-color: #fff;
position: absolute;
left: 0;
bottom: 0;
}
.touch-container {
width: 100%;
box-sizing: border-box;
border-top: 0.5px solid #eee;
display: flex;
align-items: center;
justify-content: center;
}
.touch-item {
width: 40px;
height: 5px;
background: #222222;
border-radius: 100px;
opacity: 0.6;
}
}
</style>
timeFormat(y, m, d) {
let month = m
let day = d
if (m < 10) month = `0${m}`
if (d < 10) day = `0${day}`
return `${y}-${month}-${day}`
}