import dayjs from 'dayjs' import classnames from 'classnames' import _chunk from 'lodash/chunk' import _throttle from 'lodash/throttle' import Taro from '@tarojs/taro' import bind from 'bind-decorator' import { View, Swiper, SwiperItem } from '@tarojs/components' import { ITouchEvent, BaseEvent, ITouch } from '@tarojs/components/types/common' import Calendar from '../types' import AtCalendarDayList from '../ui/day-list/index' import AtCalendarDateList from '../ui/date-list/index' import generateCalendarGroup from '../common/helper' import { Props, State, ListGroup } from './interface' import { delayQuerySelector } from '../../../common/utils' const ANIMTE_DURATION: number = 300 const defaultProps: Partial = { marks: [], selectedDate: { end: Date.now(), start: Date.now() }, format: 'YYYY-MM-DD', generateDate: Date.now(), } export default class AtCalendarBody extends Taro.Component< Props, Readonly > { static options = { addGlobalClass: true } static defaultProps: Partial = defaultProps private changeCount: number = 0 private currentSwiperIndex: number = 1 private startX: number = 0 private swipeStartPoint: number = 0 private isPreMonth: boolean = false private maxWidth: number = 0 private isTouching: boolean = false private generateFunc: ( generateDate: number, selectedDate: Calendar.SelectedDate, isShowStatus?: boolean ) => Calendar.ListInfo constructor(props) { super(...arguments) const { marks, format, minDate, maxDate, generateDate, selectedDate, selectedDates, collapse, } = props this.generateFunc = generateCalendarGroup({ format, minDate, maxDate, marks, selectedDates, collapse, }) const listGroup = this.getGroups(generateDate, selectedDate, collapse) this.state = { listGroup, offsetSize: 0, isAnimate: false } } @bind private getGroups( generateDate: number, selectedDate: Calendar.SelectedDate, collapse: boolean, ): ListGroup { const dayjsDate = dayjs(generateDate) const arr: ListGroup = [] const preList: Calendar.ListInfo = this.generateFunc( dayjsDate.subtract(1, collapse ? 'week' : 'month').valueOf(), selectedDate ) const nowList: Calendar.ListInfo = this.generateFunc( generateDate, selectedDate, true ) const nextList: Calendar.ListInfo = this.generateFunc( dayjsDate.add(1, collapse ? 'week' : 'month').valueOf(), selectedDate ) const preListIndex = this.currentSwiperIndex === 0 ? 2 : this.currentSwiperIndex - 1 const nextListIndex = this.currentSwiperIndex === 2 ? 0 : this.currentSwiperIndex + 1 arr[preListIndex] = preList arr[nextListIndex] = nextList arr[this.currentSwiperIndex] = nowList return arr } componentWillReceiveProps(nextProps: Props) { const { marks, format, minDate, maxDate, generateDate, selectedDate, selectedDates, collapse, } = nextProps this.generateFunc = generateCalendarGroup({ format, minDate, maxDate, marks, selectedDates, collapse }) const listGroup = this.getGroups(generateDate, selectedDate, collapse) this.setState({ offsetSize: 0, listGroup }) } componentDidMount() { delayQuerySelector(this, '.at-calendar-slider__main').then(res => { this.maxWidth = res[0].width }) } @bind private handleTouchStart(e: ITouchEvent) { if (!this.props.isSwiper) { return } this.isTouching = true this.startX = e.touches[0].clientX } private handleTouchMove = (e: ITouchEvent) => { if (!this.props.isSwiper) { return } if (!this.isTouching) return const { clientX } = e.touches[0] const offsetSize = clientX - this.startX this.setState({ offsetSize }) } private animateMoveSlide(offset: number, callback?: Function) { this.setState( { isAnimate: true }, () => { this.setState({ offsetSize: offset }) setTimeout(() => { this.setState( { isAnimate: false }, () => { callback && callback() } ) }, ANIMTE_DURATION) } ) } @bind private handleTouchEnd() { if (!this.props.isSwiper) { return } const { offsetSize } = this.state this.isTouching = false const isRight = offsetSize > 0 const breakpoint = this.maxWidth / 2 const absOffsetSize = Math.abs(offsetSize) if (absOffsetSize > breakpoint) { const res = isRight ? this.maxWidth : -this.maxWidth return this.animateMoveSlide(res, () => { this.props.onSwipeMonth(isRight ? -1 : 1) }) } this.animateMoveSlide(0) } @bind private handleChange(e: BaseEvent) { const { current, source } = e.detail if (source === 'touch') { this.currentSwiperIndex = current this.changeCount = this.changeCount + 1 } } @bind private handleAnimateFinish() { if (this.changeCount > 0) { this.props.onSwipeMonth( this.isPreMonth ? -this.changeCount : this.changeCount ) this.changeCount = 0 } } @bind private handleSwipeTouchStart( e: ITouchEvent & { changedTouches: Array } ) { const { clientY, clientX } = e.changedTouches[0] this.swipeStartPoint = this.props.isVertical ? clientY : clientX } @bind private handleSwipeTouchEnd( e: ITouchEvent & { changedTouches: Array } ) { const { clientY, clientX } = e.changedTouches[0] this.isPreMonth = this.props.isVertical ? clientY - this.swipeStartPoint > 0 : clientX - this.swipeStartPoint > 0 } render() { const { isSwiper, collapse } = this.props const { isAnimate, offsetSize, listGroup } = this.state if (!isSwiper) { return ( ) } /* 需要 Taro 组件库维护 Swiper 使 小程序 和 H5 的表现保持一致 */ if (process.env.TARO_ENV === 'h5') { return ( ) } return ( {listGroup.map((item, key) => ( ))} ) } }