diff --git a/src/common/util.ts b/src/common/util.ts index 583ccfa..ea5bc51 100644 --- a/src/common/util.ts +++ b/src/common/util.ts @@ -1,7 +1,7 @@ import { formatImgUrl } from './format' import { analysisShortCodeApi } from './shortCode' import Taro from '@tarojs/taro' - +import { SelectorQuery } from '@tarojs/taro/types/index' /** * 防抖 * @param {*} fn @@ -107,6 +107,33 @@ export const dataLoadingStatus = ({ list = [], total = 0, status = false }: { li } } + +function delay(delayTime = 25): Promise { + return new Promise(resolve => { + setTimeout(() => { + resolve() + }, delayTime) + }) +} + +function delayQuerySelector( + selectorStr: string, + delayTime = 500 +): Promise { + return new Promise(resolve => { + const selector: SelectorQuery = Taro.createSelectorQuery() + delay(delayTime).then(() => { + selector + .select(selectorStr) + .boundingClientRect() + .exec((res: any[]) => { + resolve(res) + }) + }) + }) +} + + //全局分享监听 export const shareShop = () => { const page = Taro.getCurrentInstance().page @@ -142,3 +169,6 @@ export const shareShop = () => { } } } +export { + delayQuerySelector +} \ No newline at end of file diff --git a/src/components/calendar/body/index.tsx b/src/components/calendar/body/index.tsx new file mode 100644 index 0000000..3ff6257 --- /dev/null +++ b/src/components/calendar/body/index.tsx @@ -0,0 +1,365 @@ +import classnames from 'classnames' +import dayjs from 'dayjs' +import React from 'react' +import { Swiper, SwiperItem, View } from '@tarojs/components' +import { + BaseEventOrig, + ITouch, + ITouchEvent +} from '@tarojs/components/types/common' +import { + AtCalendarBodyListGroup, + AtCalendarBodyProps, + AtCalendarBodyState, + Calendar +} from '../../../types/calendar' +import { delayQuerySelector } from '@/common/util' +import generateCalendarGroup from '../common/helper' +import AtCalendarDateList from '../ui/date-list/index' +import AtCalendarDayList from '../ui/day-list/index' + +const ANIMTE_DURATION = 300 + +const defaultProps: Partial = { + marks: [], + selectedDate: { + end: Date.now(), + start: Date.now() + }, + format: 'YYYY-MM-DD', + generateDate: Date.now() +} + +export default class AtCalendarBody extends React.Component< + AtCalendarBodyProps, + Readonly +> { + static defaultProps: Partial = defaultProps + + public constructor(props: AtCalendarBodyProps) { + super(props) + const { + validDates, + marks, + format, + minDate, + maxDate, + generateDate, + selectedDate, + selectedDates + } = props + + this.generateFunc = generateCalendarGroup({ + validDates, + format, + minDate, + maxDate, + marks, + selectedDates + }) + const listGroup = this.getGroups(generateDate, selectedDate) + + this.state = { + listGroup, + offsetSize: 0, + isAnimate: false + } + } + + public componentDidMount(): void { + delayQuerySelector('.at-calendar-slider__main').then(res => { + this.maxWidth = res[0].width + }) + } + + public UNSAFE_componentWillReceiveProps( + nextProps: AtCalendarBodyProps + ): void { + const { + validDates, + marks, + format, + minDate, + maxDate, + generateDate, + selectedDate, + selectedDates + } = nextProps + + this.generateFunc = generateCalendarGroup({ + validDates, + format, + minDate, + maxDate, + marks, + selectedDates + }) + const listGroup = this.getGroups(generateDate, selectedDate) + + this.setState({ + offsetSize: 0, + listGroup + }) + } + + private changeCount = 0 + private currentSwiperIndex = 1 + private startX = 0 + private swipeStartPoint = 0 + private isPreMonth = false + private maxWidth = 0 + private isTouching = false + + private generateFunc: ( + generateDate: number, + selectedDate: Calendar.SelectedDate, + isShowStatus?: boolean + ) => Calendar.ListInfo + + private getGroups = ( + generateDate: number, + selectedDate: Calendar.SelectedDate + ): AtCalendarBodyListGroup => { + const dayjsDate = dayjs(generateDate) + const arr: AtCalendarBodyListGroup = [] + const preList: Calendar.ListInfo = this.generateFunc( + dayjsDate.subtract(1, 'month').valueOf(), + selectedDate + ) + + const nowList: Calendar.ListInfo = this.generateFunc( + generateDate, + selectedDate, + true + ) + + const nextList: Calendar.ListInfo = this.generateFunc( + dayjsDate.add(1, '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 + } + + private handleTouchStart = (e: ITouchEvent): void => { + if (!this.props.isSwiper) { + return + } + this.isTouching = true + this.startX = e.touches[0].clientX + } + + private handleTouchMove = (e: ITouchEvent): void => { + 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): void => { + this.setState( + { + isAnimate: true + }, + () => { + this.setState({ + offsetSize: offset + }) + setTimeout(() => { + this.setState( + { + isAnimate: false + }, + () => { + callback && callback() + } + ) + }, ANIMTE_DURATION) + } + ) + } + + private handleTouchEnd = (): void => { + 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) + } + + private handleChange = ( + e: BaseEventOrig<{ + current: number + source: string + }> + ): void => { + const { current, source } = e.detail + + if (source === 'touch') { + this.currentSwiperIndex = current + this.changeCount += 1 + } + } + + private handleAnimateFinish = (): void => { + if (this.changeCount > 0) { + this.props.onSwipeMonth( + this.isPreMonth ? -this.changeCount : this.changeCount + ) + this.changeCount = 0 + } + } + + private handleSwipeTouchStart = ( + e: ITouchEvent & { changedTouches: Array } + ): void => { + const { clientY, clientX } = e.changedTouches[0] + this.swipeStartPoint = this.props.isVertical ? clientY : clientX + } + + private handleSwipeTouchEnd = ( + e: ITouchEvent & { changedTouches: Array } + ): void => { + const { clientY, clientX } = e.changedTouches[0] + this.isPreMonth = this.props.isVertical + ? clientY - this.swipeStartPoint > 0 + : clientX - this.swipeStartPoint > 0 + } + + public render(): JSX.Element { + const { isSwiper } = 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) => ( + + + + ))} + + + ) + } +} diff --git a/src/components/calendar/common/constant.ts b/src/components/calendar/common/constant.ts new file mode 100644 index 0000000..237d035 --- /dev/null +++ b/src/components/calendar/common/constant.ts @@ -0,0 +1,5 @@ +export const TYPE_PRE_MONTH = -1 + +export const TYPE_NOW_MONTH = 0 + +export const TYPE_NEXT_MONTH = 1 diff --git a/src/components/calendar/common/helper.ts b/src/components/calendar/common/helper.ts new file mode 100644 index 0000000..5194466 --- /dev/null +++ b/src/components/calendar/common/helper.ts @@ -0,0 +1,118 @@ +import dayjs, { Dayjs } from 'dayjs' +import _flow from 'lodash/flow' +import { Calendar } from '../../../types/calendar' +import * as constant from './constant' +import plugins from './plugins' + +const TOTAL = 7 * 6 + +function getFullItem( + item: Partial, + options: Calendar.GroupOptions, + selectedDate: Calendar.SelectedDate, + isShowStatus?: boolean +): any { + if (options.marks.find(x => x.value === item.value)) { + (item.marks as Array) = [{ + value: item.value as string + }] + } + if (!isShowStatus) return item + + const bindedPlugins = plugins.map(fn => + fn.bind(null, { + options, + selectedDate + }) + ) + return _flow(bindedPlugins)(item) +} + +export default function generateCalendarGroup( + options: Calendar.GroupOptions +): ( + generateDate: number, + selectedDate: Calendar.SelectedDate, + isShowStatus?: boolean +) => Calendar.ListInfo { + return function ( + generateDate: number, + selectedDate: Calendar.SelectedDate, + isShowStatus?: boolean + ): Calendar.ListInfo { + const date = dayjs(generateDate) + + const { format } = options + + // 获取生成日期的第一天 和 最后一天 + const firstDate = date.startOf('month') + const lastDate = date.endOf('month') + + const preMonthDate = date.subtract(1, 'month') + + const list: Calendar.List = [] + + const nowMonthDays: number = date.daysInMonth() // 获取这个月有多少天 + const preMonthLastDay = preMonthDate.endOf('month').day() // 获取上个月最后一天是周几 + + // 生成上个月的日期 + for (let i = 1; i <= preMonthLastDay + 1; i++) { + const thisDate = firstDate.subtract(i, 'day').startOf('day') + + let item = { + marks: [], + _value: thisDate, + text: thisDate.date(), + type: constant.TYPE_PRE_MONTH, + value: thisDate.format(format) + } + + item = getFullItem(item, options, selectedDate, isShowStatus) + + list.push(item) + } + list.reverse() + + // 生成这个月的日期 + for (let i = 0; i < nowMonthDays; i++) { + const thisDate = firstDate.add(i, 'day').startOf('day') + let item = { + marks: [], + _value: thisDate, + text: thisDate.date(), + type: constant.TYPE_NOW_MONTH, + value: thisDate.format(format) + } + + item = getFullItem(item, options, selectedDate, isShowStatus) + + list.push(item) + } + + // 生成下个月的日期 + let i = 1 + while (list.length < TOTAL) { + const thisDate = lastDate.add(i++, 'day').startOf('day') + let item = { + marks: [], + _value: thisDate, + text: thisDate.date(), + type: constant.TYPE_NEXT_MONTH, + value: thisDate.format(format) + } + + item = getFullItem(item, options, selectedDate, isShowStatus) + + list.push(item) + } + + return { + list, + value: generateDate + } + } +} + +export function getGenerateDate(date: Calendar.DateArg | undefined): Dayjs { + return dayjs(date).startOf('month') +} diff --git a/src/components/calendar/common/plugins.ts b/src/components/calendar/common/plugins.ts new file mode 100644 index 0000000..bf4cd9e --- /dev/null +++ b/src/components/calendar/common/plugins.ts @@ -0,0 +1,124 @@ +import dayjs from 'dayjs' +import _isEmpty from 'lodash/isEmpty' +import { Calendar } from '../../../types/calendar' + +interface PluginArg { + options: Calendar.GroupOptions + + selectedDate: Calendar.SelectedDate +} + +export function handleActive( + args: PluginArg, + item: Calendar.Item +): Calendar.Item { + const { selectedDate } = args + const { _value } = item + + const { start, end } = selectedDate + + const dayjsEnd = dayjs(end) + const dayjsStart = start ? dayjs(start) : dayjsEnd + + item.isSelected = + _value.isSame(dayjsEnd) || + _value.isSame(dayjsStart) || + (_value.isAfter(dayjsStart) && _value.isBefore(dayjsEnd)) + + item.isSelectedHead = _value.isSame(dayjsStart) + item.isSelectedTail = _value.isSame(dayjsEnd) + + item.isToday = _value.diff(dayjs(Date.now()).startOf('day'), 'day') === 0 + + return item +} + +export function handleMarks( + args: PluginArg, + item: Calendar.Item +): Calendar.Item { + const { options } = args + const { _value } = item + const { marks } = options + + const markList = marks.filter(mark => + dayjs(mark.value).startOf('day').isSame(_value) + ) + + item.marks = markList.slice(0, 1) + + return item +} + +// export function handleSelectedDates (args: PluginArg): Calendar.Item { +// const { item, options } = args +// const { _value } = item +// const { selectedDates } = options + +// if (selectedDates.length === 0) return args + +// _forEach(selectedDates, date => { +// const { isSelected, isHead, isTail } = item + +// // 如果当前 Item 已经具备了 三种状态下 无需继续判断 跳出循环 +// if (isSelected) { +// return false +// } + +// const { start, end } = date + +// const dayjsEnd = dayjs(end).startOf('day') +// const dayjsStart = dayjs(start).startOf('day') + +// item.isSelected = +// item.isSelected || +// (_value.isAfter(dayjsStart) && _value.isBefore(dayjsEnd)) + +// item.isHead = item.isHead || _value.isSame(dayjsStart) + +// item.isTail = item.isTail || _value.isSame(dayjsEnd) +// }) + +// return item +// } + +export function handleDisabled( + args: PluginArg, + item: Calendar.Item +): Calendar.Item { + const { options } = args + const { _value } = item + const { minDate, maxDate } = options + + const dayjsMinDate = dayjs(minDate) + const dayjsMaxDate = dayjs(maxDate) + + item.isDisabled = + !!(minDate && _value.isBefore(dayjsMinDate)) || + !!(maxDate && _value.isAfter(dayjsMaxDate)) + + return item +} + +export function handleValid( + args: PluginArg, + item: Calendar.Item +): Calendar.Item { + const { options } = args + const { _value } = item + const { validDates } = options + + if (!_isEmpty(validDates)) { + const isInclude = validDates.some(date => + dayjs(date.value).startOf('day').isSame(_value) + ) + + item.isDisabled = !isInclude + } + + delete item._value + + return item +} + +export default [handleActive, handleMarks, handleDisabled, handleValid] diff --git a/src/components/calendar/controller/index.tsx b/src/components/calendar/controller/index.tsx new file mode 100644 index 0000000..2412b58 --- /dev/null +++ b/src/components/calendar/controller/index.tsx @@ -0,0 +1,76 @@ +import classnames from 'classnames' +import dayjs, { Dayjs } from 'dayjs' +import React from 'react' +import { Picker, Text, View } from '@tarojs/components' +import { + AtCalendarControllerProps, + AtCalendarControllerState +} from '../../../types/calendar' + +export default class AtCalendarController extends React.Component< + AtCalendarControllerProps, + AtCalendarControllerState +> { + public render(): JSX.Element { + const { + generateDate, + minDate, + maxDate, + monthFormat, + hideArrow + } = this.props + + const dayjsDate: Dayjs = dayjs(generateDate) + const dayjsMinDate: Dayjs | boolean = !!minDate && dayjs(minDate) + const dayjsMaxDate: Dayjs | boolean = !!maxDate && dayjs(maxDate) + + const isMinMonth: boolean = + dayjsMinDate && dayjsMinDate.startOf('month').isSame(dayjsDate) + + const isMaxMonth: boolean = + dayjsMaxDate && dayjsMaxDate.startOf('month').isSame(dayjsDate) + + const minDateValue: string = dayjsMinDate + ? dayjsMinDate.format('YYYY-MM') + : '' + const maxDateValue: string = dayjsMaxDate + ? dayjsMaxDate.format('YYYY-MM') + : '' + + return ( + + {hideArrow ? null : ( + + )} + + + {dayjsDate.format(monthFormat)} + + + {hideArrow ? null : ( + + )} + + ) + } +} diff --git a/src/components/calendar/index.tsx b/src/components/calendar/index.tsx new file mode 100644 index 0000000..6411d81 --- /dev/null +++ b/src/components/calendar/index.tsx @@ -0,0 +1,319 @@ +import classnames from 'classnames' +import dayjs, { Dayjs } from 'dayjs' +import React from 'react' +import { View } from '@tarojs/components' +import { BaseEventOrig } from '@tarojs/components/types/common' +import { + AtCalendarDefaultProps, + AtCalendarProps, + AtCalendarPropsWithDefaults, + AtCalendarState, + Calendar +} from '../../types/calendar' +import AtCalendarBody from './body/index' +import AtCalendarController from './controller/index' + +const defaultProps: AtCalendarDefaultProps = { + validDates: [], + marks: [], + isSwiper: true, + hideArrow: false, + isVertical: false, + selectedDates: [], + isMultiSelect: false, + format: 'YYYY-MM-DD', + currentDate: Date.now(), + monthFormat: 'YYYY年MM月' +} + +export default class AtCalendar extends React.Component< + AtCalendarProps, + Readonly +> { + static defaultProps: AtCalendarDefaultProps = defaultProps + + public constructor(props: AtCalendarProps) { + super(props) + + const { currentDate, isMultiSelect } = props as AtCalendarPropsWithDefaults + + this.state = this.getInitializeState(currentDate, isMultiSelect) + } + + public UNSAFE_componentWillReceiveProps(nextProps: AtCalendarProps): void { + const { currentDate, isMultiSelect } = nextProps + if (!currentDate || currentDate === this.props.currentDate) return + + if (isMultiSelect && this.props.isMultiSelect) { + const { start, end } = currentDate as Calendar.SelectedDate + const { start: preStart, end: preEnd } = this.props + .currentDate as Calendar.SelectedDate + + if (start === preStart && preEnd === end) { + return + } + } + + const stateValue: AtCalendarState = this.getInitializeState( + currentDate, + isMultiSelect + ) + + this.setState(stateValue) + } + + private getSingleSelectdState = (value: Dayjs): Partial => { + const { generateDate } = this.state + + const stateValue: Partial = { + selectedDate: this.getSelectedDate(value.valueOf()) + } + + const dayjsGenerateDate: Dayjs = value.startOf('month') + const generateDateValue: number = dayjsGenerateDate.valueOf() + + if (generateDateValue !== generateDate) { + this.triggerChangeDate(dayjsGenerateDate) + stateValue.generateDate = generateDateValue + } + + return stateValue + } + + private getMultiSelectedState = ( + value: Dayjs + ): Pick => { + const { selectedDate } = this.state + const { end, start } = selectedDate + + const valueUnix: number = value.valueOf() + const state: Pick = { + selectedDate + } + + if (end) { + state.selectedDate = this.getSelectedDate(valueUnix, 0) + } else { + state.selectedDate.end = Math.max(valueUnix, +start) + state.selectedDate.start = Math.min(valueUnix, +start) + } + + return state + } + + private getSelectedDate = ( + start: number, + end?: number + ): Calendar.SelectedDate => { + const stateValue: Calendar.SelectedDate = { + start, + end: start + } + + if (typeof end !== 'undefined') { + stateValue.end = end + } + + return stateValue + } + + private getInitializeState( + currentDate: Calendar.DateArg | Calendar.SelectedDate, + isMultiSelect?: boolean + ): AtCalendarState { + let end: number + let start: number + let generateDateValue: number + + if (!currentDate) { + const dayjsStart = dayjs() + start = dayjsStart.startOf('day').valueOf() + generateDateValue = dayjsStart.startOf('month').valueOf() + return { + generateDate: generateDateValue, + selectedDate: { + start: '' + } + } + } + + if (isMultiSelect) { + const { start: cStart, end: cEnd } = currentDate as Calendar.SelectedDate + + const dayjsStart = dayjs(cStart) + + start = dayjsStart.startOf('day').valueOf() + generateDateValue = dayjsStart.startOf('month').valueOf() + + end = cEnd ? dayjs(cEnd).startOf('day').valueOf() : start + } else { + const dayjsStart = dayjs(currentDate as Calendar.DateArg) + + start = dayjsStart.startOf('day').valueOf() + generateDateValue = dayjsStart.startOf('month').valueOf() + + end = start + } + + return { + generateDate: generateDateValue, + selectedDate: this.getSelectedDate(start, end) + } + } + + private triggerChangeDate = (value: Dayjs): void => { + const { format } = this.props + + if (typeof this.props.onMonthChange !== 'function') return + + this.props.onMonthChange(value.format(format)) + } + + private setMonth = (vectorCount: number): void => { + const { format } = this.props + const { generateDate } = this.state + + const _generateDate: Dayjs = dayjs(generateDate).add(vectorCount, 'month') + this.setState({ + generateDate: _generateDate.valueOf() + }) + + if (vectorCount && typeof this.props.onMonthChange === 'function') { + this.props.onMonthChange(_generateDate.format(format)) + } + } + + private handleClickPreMonth = (isMinMonth?: boolean): void => { + if (isMinMonth === true) { + return + } + + this.setMonth(-1) + + if (typeof this.props.onClickPreMonth === 'function') { + this.props.onClickPreMonth() + } + } + + private handleClickNextMonth = (isMaxMonth?: boolean): void => { + if (isMaxMonth === true) { + return + } + + this.setMonth(1) + + if (typeof this.props.onClickNextMonth === 'function') { + this.props.onClickNextMonth() + } + } + + // picker 选择时间改变时触发 + private handleSelectDate = (e: BaseEventOrig<{ value: string }>): void => { + const { value } = e.detail + + const _generateDate: Dayjs = dayjs(value) + const _generateDateValue: number = _generateDate.valueOf() + + if (this.state.generateDate === _generateDateValue) return + + this.triggerChangeDate(_generateDate) + this.setState({ + generateDate: _generateDateValue + }) + } + + private handleDayClick = (item: Calendar.Item): void => { + const { isMultiSelect } = this.props + const { isDisabled, value } = item + + if (isDisabled) return + + const dayjsDate: Dayjs = dayjs(value) + + let stateValue: Partial = {} + + if (isMultiSelect) { + stateValue = this.getMultiSelectedState(dayjsDate) + } else { + stateValue = this.getSingleSelectdState(dayjsDate) + } + + this.setState(stateValue as AtCalendarState, () => { + this.handleSelectedDate() + }) + + if (typeof this.props.onDayClick === 'function') { + this.props.onDayClick({ value: item.value }) + } + } + + private handleSelectedDate = (): void => { + const selectDate = this.state.selectedDate + if (typeof this.props.onSelectDate === 'function') { + const info: Calendar.SelectedDate = { + start: dayjs(selectDate.start).format(this.props.format) + } + + if (selectDate.end) { + info.end = dayjs(selectDate.end).format(this.props.format) + } + + this.props.onSelectDate({ + value: info + }) + } + } + + private handleDayLongClick = (item: Calendar.Item): void => { + if (typeof this.props.onDayLongClick === 'function') { + this.props.onDayLongClick({ value: item.value }) + } + } + + public render(): JSX.Element { + const { generateDate, selectedDate } = this.state + const { + validDates, + marks, + format, + minDate, + maxDate, + isSwiper, + className, + hideArrow, + isVertical, + monthFormat, + selectedDates + } = this.props as AtCalendarPropsWithDefaults + + return ( + + + + + ) + } +} diff --git a/src/components/calendar/ui/date-list/index.tsx b/src/components/calendar/ui/date-list/index.tsx new file mode 100644 index 0000000..6f807d7 --- /dev/null +++ b/src/components/calendar/ui/date-list/index.tsx @@ -0,0 +1,80 @@ +import classnames from 'classnames' +import React from 'react' +import { Text, View } from '@tarojs/components' +import { Calendar } from '../../../../../types/calendar' +import * as constant from '../../common/constant' + +const MAP: { [key: number]: string } = { + [constant.TYPE_PRE_MONTH]: 'pre', + [constant.TYPE_NOW_MONTH]: 'now', + [constant.TYPE_NEXT_MONTH]: 'next' +} + +export interface Props { + list: Calendar.List + + onClick?: (item: Calendar.Item) => void + + onLongClick?: (item: Calendar.Item) => void +} + +export default class AtCalendarList extends React.Component { + private handleClick = (item: Calendar.Item): void => { + if (typeof this.props.onClick === 'function') { + this.props.onClick(item) + } + } + + private handleLongClick = (item: Calendar.Item): void => { + if (typeof this.props.onLongClick === 'function') { + this.props.onLongClick(item) + } + } + + public render(): JSX.Element | null { + const { list } = this.props + if (!list || list.length === 0) return null + + return ( + + {list.map((item: Calendar.Item) => ( + + + {item.text} + + + {item.marks && item.marks.length > 0 ? ( + + {item.marks.map((mark, key) => ( + + {mark.value} + + ))} + + ) : null} + + + ))} + + ) + } +} diff --git a/src/components/calendar/ui/day-list/index.tsx b/src/components/calendar/ui/day-list/index.tsx new file mode 100644 index 0000000..6b7b633 --- /dev/null +++ b/src/components/calendar/ui/day-list/index.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { View } from '@tarojs/components' + +export default class AtCalendarHeader extends React.Component { + public render(): JSX.Element { + return ( + + + + + + + + + + + + ) + } +} diff --git a/src/pages/refundPage/components/pickerTime/index.scss b/src/pages/refundPage/components/pickerTime/index.scss new file mode 100644 index 0000000..4637656 --- /dev/null +++ b/src/pages/refundPage/components/pickerTime/index.scss @@ -0,0 +1,182 @@ +@import '../variables/default.scss'; +@import '../mixins/index.scss'; + +.at-calendar { + overflow: hidden; + + /* elements */ + &__header { + .header__flex { + @include display-flex; + @include align-items(center); + + height: 72px; + color: $at-calendar-header-color; + text-align: center; + + &-item { + @include flex(0 0 calc(100% / 7)); + + font-size: 30px; + } + } + } + + &__list { + &.flex { + @include display-flex; + @include align-items(); + @include flex-wrap(wrap); + + color: $at-calendar-day-color; + + .flex__item { + @include flex(0 0 calc(100% / 7)); + + font-size: 30px; + text-align: center; + position: relative; + margin: 5px 0; + + &-container { + @include align-items(center); + @include display-flex; + + width: $at-calendar-day-size; + height: $at-calendar-day-size; + margin-left: auto; + margin-right: auto; + border-radius: 50%; + + .container-text { + @include flex; + } + } + + &-extra { + .extra-marks { + position: absolute; + bottom: 5px; + line-height: 0; + left: 50%; + transform: translateX(-50%); + + .mark { + width: $at-calendar-mark-size; + height: $at-calendar-mark-size; + margin-right: 4px; + display: inline-block; + background-color: $at-calendar-main-color; + border-radius: 50%; + overflow: hidden; + + &:last-child { + margin-right: 0; + } + } + } + } + + &--today { + color: $at-calendar-main-color; + font-weight: bolder; + } + + &--blur { + color: #e1e4e7; + } + + &--selected { + color: white; + background-color: rgba($color: $at-calendar-main-color, $alpha: 0.7); + + &-head { + border-top-left-radius: 40px; + border-bottom-left-radius: 40px; + } + + &-tail { + border-top-right-radius: 40px; + border-bottom-right-radius: 40px; + } + + /* stylelint-disable-next-line */ + .extra-marks .mark { + background-color: white; + } + + &-head.flex__item--selected-tail { + background-color: transparent; + + .flex__item-container { + background-color: rgba( + $color: $at-calendar-main-color, + $alpha: 0.7 + ); + } + } + } + } + } + } + + &__controller { + @include display-flex; + @include align-items(center); + @include justify-content(center); + + margin-bottom: 20px; + + .controller__arrow { + @include flex(0 0 40px); + + height: 40px; + border-radius: 12px; + display: inline-block; + background-size: 16px 24px; + background-position: center; + background-color: #f7f8fc; + background-repeat: no-repeat; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAYCAYAAADzoH0MAAAAAXNSR0IArs4c6QAAAnFJREFUOBGVVF1rE0EUnXt3tzFtWmqjKYKfqIhVa1L8FQVRWtwnXwRhidXGDwQf81oCUQMioZRCHwNSgiD4lD9QSYVKsA8KbaW1jbamX8adnWsmMnESbYz7cs6ee8/ZnZm7y9h/Xk/Gs70TE9lOZQNFWsGx1IvDJoozxNDttNpmHOfyTssBj59PHxceP6keREDlYPvBGUMJzTD5LHuKhHtC70EEQe72atMAIoLu0MWzRPxInZnEdxZib2I37L2XEI/HsSvYd44AQrqZIW5b3J8fHR0sS/2ve5DJZIzFFexnSD262QAs+c1js45zyVU6KqIwnU5bS58x0mhGhusbaz153Sw9dW+QSr3yCdwJe4wCKlCigbAWiw7PAYDQdclrAclkxk8+iDBifr3JMq3lO86VQsVMuq549RQSU687mOcNANE+VfiFxuLd6NX3e5llD8qjskqb54E8n24mk5Yf3B6ab2auBsgGC8Q7QOJ1AS6ExrSZ12s6r57CyIi99cNgswywtkkIzDB2eSSdftmuGxp57RgfOfY38HlvRWVNqgmYsDb57sDkZK5hb1RHZQ9+U8bu37S/MtOc0zUg8G2U1yOV4WrTdcXrAqT4MDq0yokXVINEwb32pS9WOJfLmboueW0OGgtP05mj3IXTum6iuXHogDtr27an9D/eQBVijr2AiB/VvUQuePenNXZBfmhKrxEl6Hjv1vAHA2lJ1wRBcH9vf5+cH6k3DZANsei1eWCwIrm6uOf1Jsenq8v7Z4ActFJxrsBMo6gC0GAebPHq/Z6bqJoVyn/EQpGFK08MmF2B/Oj1wZKqtYzxeM5MJKY6dMNPQnnePR8FubkAAAAASUVORK5CYII="); + + &--right { + transform: rotate(180deg); + } + + &--disabled { + opacity: 0.5; + } + } + + .controller__info { + @include flex(0 0 auto); + + font-size: 30px; + margin-left: 40px; + margin-right: 40px; + } + } +} + +.at-calendar-slider__main { + .main__body { + @include display-flex; + + width: 100%; + + &--animate { + transition: transform 300ms cubic-bezier(0.36, 0.66, 0.04, 1); + } + + .body__slider { + @include flex(0 0 100%); + } + } + + &--weapp, + &--swan { + .main__body { + height: 480px; + } + } +} diff --git a/src/pages/refundPage/components/pickerTime/index.tsx b/src/pages/refundPage/components/pickerTime/index.tsx new file mode 100644 index 0000000..b8ff636 --- /dev/null +++ b/src/pages/refundPage/components/pickerTime/index.tsx @@ -0,0 +1,146 @@ +import Popup from '@/components/popup' +import React, { useState, memo, useEffect } from "react"; +import Taro from "@tarojs/taro"; +import { View } from "@tarojs/components"; +// import { AtIcon } from 'taro-ui' +import './index.scss' + + + + +interface Props { + showTime: true | false, + closePopup?: () => void +} +export default memo((props: Props) => { + const { + showTime = false, + closePopup + } = props + //每月多少天 + let MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + const WEEK_NAMES = ['日', '一', '二', '三', '四', '五', '六']; + const LINES = [1, 2, 3, 4, 5, 6]; + const [year, setLoinsYear] = useState(0); + let nowTime = new Date(Date.parse(new Date().toString())); + const [month, seLoinstMonth] = useState(nowTime.getMonth()); + const [currentDate, setcurrentDate] = useState(new Date()); + const [tag, setTag] = useState(false); + //获取当前月份 + const getMonth = (date: Date): number => { + return date.getMonth(); + } + //获取当前年份 + const getFullYear = (date: Date): number => { + // console.log(date.getFullYear()) + return date.getFullYear(); + } + + const getCurrentMonthDays = (month: number, year: number): number => { + let _year = year + currentDate.getFullYear(); + if (_year % 100 != 0 && _year % 4 == 0 || _year % 400 == 0) { + MONTH_DAYS = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + } + return MONTH_DAYS[month] + } + //当前月第一天是周几 + const getDateByYearMonth = (year: number, month: number, day: number = 1): Date => { + var date = new Date() + date.setFullYear(year) + date.setMonth(month, day) + return date + } + const getWeeksByFirstDay = (year: number, month: number): number => { + var date = getDateByYearMonth(year, month) + return date.getDay() + } + const getDayText = (line: number, weekIndex: number, weekDay: number, monthDays: number): any => { + var number = line * 7 + weekIndex - weekDay + 1 + if (number <= 0 || number > monthDays) { + return   + } + return + clickItem(line)}>{number} + + } + + const setCurrentYearMonth = (date) => { + var month = getMonth(date) + var year = getFullYear(date) + setLoinsYear(year); + seLoinstMonth(month) + setTag(false) + } + + const monthChange = (monthChanged: number) => { + if (tag) { + return; + } else { + setTag(true) + } + + var monthAfter = month + monthChanged + var date = getDateByYearMonth(year, monthAfter) + setCurrentYearMonth(date) + } + const formatNumber = (num: number): string => { + var _num = num + 1 + return _num < 10 ? `0${_num}` : `${_num}` + } + + // let monthDays = getCurrentMonthDays(month); + let weekDay = getWeeksByFirstDay(year, month); + + let _startX = 0; + const clickItem = (item) => { + console.log(item, 666) + } + return ( + closePopup?.()}> + { + if (_startX > val.changedTouches[0]['clientX'] + 30) { + monthChange(1); + } + if (_startX < val.changedTouches[0]['clientX'] - 30) { + monthChange(-1); + } + }} onTouchStart={(val) => { + _startX = val.changedTouches[0]['clientX'] + + }} + > + + + {/* { + monthChange(-1); + }}> + */} + + {year + currentDate.getFullYear()} 年 {formatNumber(month)}月 + + {/* { + monthChange(1); + }}> */} + + + { + WEEK_NAMES.map((week, key) => { + return {week} + }) + } + { + LINES.map((l, key) => { + return + { + WEEK_NAMES.map((week, index) => { + return getDayText(key, index, weekDay, getCurrentMonthDays(month, year)) + }) + } + + }) + } + + + ) +}) \ No newline at end of file diff --git a/src/pages/refundPage/components/timePicker/index.scss b/src/pages/refundPage/components/timePicker/index.scss index fd9a07a..2610515 100644 --- a/src/pages/refundPage/components/timePicker/index.scss +++ b/src/pages/refundPage/components/timePicker/index.scss @@ -1,64 +1,182 @@ -.loins-calendar { +@import '../../../../styles/variables/default.scss'; +@import '../../../../styles/mixins/index.scss'; - background-color: #ffffff; +.at-calendar { + overflow: hidden; - &-tabbar { - padding: 43px 100px 30px 100px; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - } + /* elements */ + &__header { + .header__flex { + @include display-flex; + @include align-items(center); - &-title { - font-size: 36px; - font-family: Helvetica; - color: #333333; - line-height: 48px; - } + height: 72px; + color: $at-calendar-header-color; + text-align: center; + + &-item { + @include flex(0 0 calc(100% / 7)); - .title-c { - display: inline-block; - width: calc(100% / 7); - text-align: center; font-size: 30px; - font-weight: 400; - color: #606060; - line-height: 40px; - margin-bottom: 25px; + } } + } - width: 662rpx; - margin-left: 44rpx; + &__list { + &.flex { + @include display-flex; + @include align-items(); + @include flex-wrap(wrap); - .day-content { + color: $at-calendar-day-color; + + .flex__item { + @include flex(0 0 calc(100% / 7)); + + font-size: 30px; text-align: center; + position: relative; + margin: 5px 0; - .day-c { - display: inline-block; - width: calc(100% / 7); - text-align: center; - margin-bottom: 30px; + &-container { + @include align-items(center); + @include display-flex; - .day { - width: 80px; - height: 80px; - // background: #337FFF; - // border-radius: 16px; - line-height: 80px; - font-size: 32px; - font-weight: 500; - color: #000; - text-align: center; - } + width: $at-calendar-day-size; + height: $at-calendar-day-size; + margin-left: auto; + margin-right: auto; + border-radius: 50%; - .desc { - height: 28px; - font-size: 20px; - font-weight: 400; - color: #202020; - text-align: center; - } + .container-text { + @include flex; + } } + + &-extra { + .extra-marks { + position: absolute; + bottom: 5px; + line-height: 0; + left: 50%; + transform: translateX(-50%); + + .mark { + width: $at-calendar-mark-size; + height: $at-calendar-mark-size; + margin-right: 4px; + display: inline-block; + background-color: $at-calendar-main-color; + border-radius: 50%; + overflow: hidden; + + &:last-child { + margin-right: 0; + } + } + } + } + + &--today { + color: $at-calendar-main-color; + font-weight: bolder; + } + + &--blur { + color: #e1e4e7; + } + + &--selected { + color: white; + background-color: rgba($color: $at-calendar-main-color, $alpha: 0.7); + + &-head { + border-top-left-radius: 40px; + border-bottom-left-radius: 40px; + } + + &-tail { + border-top-right-radius: 40px; + border-bottom-right-radius: 40px; + } + + /* stylelint-disable-next-line */ + .extra-marks .mark { + background-color: white; + } + + &-head.flex__item--selected-tail { + background-color: transparent; + + .flex__item-container { + background-color: rgba( + $color: $at-calendar-main-color, + $alpha: 0.7 + ); + } + } + } + } } -} \ No newline at end of file + } + + &__controller { + @include display-flex; + @include align-items(center); + @include justify-content(center); + + margin-bottom: 20px; + + .controller__arrow { + @include flex(0 0 40px); + + height: 40px; + border-radius: 12px; + display: inline-block; + background-size: 16px 24px; + background-position: center; + background-color: #f7f8fc; + background-repeat: no-repeat; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAYCAYAAADzoH0MAAAAAXNSR0IArs4c6QAAAnFJREFUOBGVVF1rE0EUnXt3tzFtWmqjKYKfqIhVa1L8FQVRWtwnXwRhidXGDwQf81oCUQMioZRCHwNSgiD4lD9QSYVKsA8KbaW1jbamX8adnWsmMnESbYz7cs6ee8/ZnZm7y9h/Xk/Gs70TE9lOZQNFWsGx1IvDJoozxNDttNpmHOfyTssBj59PHxceP6keREDlYPvBGUMJzTD5LHuKhHtC70EEQe72atMAIoLu0MWzRPxInZnEdxZib2I37L2XEI/HsSvYd44AQrqZIW5b3J8fHR0sS/2ve5DJZIzFFexnSD262QAs+c1js45zyVU6KqIwnU5bS58x0mhGhusbaz153Sw9dW+QSr3yCdwJe4wCKlCigbAWiw7PAYDQdclrAclkxk8+iDBifr3JMq3lO86VQsVMuq549RQSU687mOcNANE+VfiFxuLd6NX3e5llD8qjskqb54E8n24mk5Yf3B6ab2auBsgGC8Q7QOJ1AS6ExrSZ12s6r57CyIi99cNgswywtkkIzDB2eSSdftmuGxp57RgfOfY38HlvRWVNqgmYsDb57sDkZK5hb1RHZQ9+U8bu37S/MtOc0zUg8G2U1yOV4WrTdcXrAqT4MDq0yokXVINEwb32pS9WOJfLmboueW0OGgtP05mj3IXTum6iuXHogDtr27an9D/eQBVijr2AiB/VvUQuePenNXZBfmhKrxEl6Hjv1vAHA2lJ1wRBcH9vf5+cH6k3DZANsei1eWCwIrm6uOf1Jsenq8v7Z4ActFJxrsBMo6gC0GAebPHq/Z6bqJoVyn/EQpGFK08MmF2B/Oj1wZKqtYzxeM5MJKY6dMNPQnnePR8FubkAAAAASUVORK5CYII="); + + &--right { + transform: rotate(180deg); + } + + &--disabled { + opacity: 0.5; + } + } + + .controller__info { + @include flex(0 0 auto); + + font-size: 30px; + margin-left: 40px; + margin-right: 40px; + } + } +} + +.at-calendar-slider__main { + .main__body { + @include display-flex; + + width: 100%; + + &--animate { + transition: transform 300ms cubic-bezier(0.36, 0.66, 0.04, 1); + } + + .body__slider { + @include flex(0 0 100%); + } + } + + &--weapp, + &--swan { + .main__body { + height: 480px; + } + } +} diff --git a/src/pages/refundPage/components/timePicker/index.tsx b/src/pages/refundPage/components/timePicker/index.tsx index b8ff636..d32e280 100644 --- a/src/pages/refundPage/components/timePicker/index.tsx +++ b/src/pages/refundPage/components/timePicker/index.tsx @@ -1,11 +1,11 @@ import Popup from '@/components/popup' import React, { useState, memo, useEffect } from "react"; -import Taro from "@tarojs/taro"; -import { View } from "@tarojs/components"; +import Taro, { getCurrentInstance } from "@tarojs/taro"; +import { View, Swiper, SwiperItem, Text } from "@tarojs/components"; // import { AtIcon } from 'taro-ui' import './index.scss' - - +import classnames from "classnames"; +import AtCalendar from "@/components/calendar/index" interface Props { @@ -13,134 +13,15 @@ interface Props { closePopup?: () => void } export default memo((props: Props) => { - const { + let { showTime = false, closePopup } = props - //每月多少天 - let MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - const WEEK_NAMES = ['日', '一', '二', '三', '四', '五', '六']; - const LINES = [1, 2, 3, 4, 5, 6]; - const [year, setLoinsYear] = useState(0); - let nowTime = new Date(Date.parse(new Date().toString())); - const [month, seLoinstMonth] = useState(nowTime.getMonth()); - const [currentDate, setcurrentDate] = useState(new Date()); - const [tag, setTag] = useState(false); - //获取当前月份 - const getMonth = (date: Date): number => { - return date.getMonth(); - } - //获取当前年份 - const getFullYear = (date: Date): number => { - // console.log(date.getFullYear()) - return date.getFullYear(); - } - const getCurrentMonthDays = (month: number, year: number): number => { - let _year = year + currentDate.getFullYear(); - if (_year % 100 != 0 && _year % 4 == 0 || _year % 400 == 0) { - MONTH_DAYS = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - } - return MONTH_DAYS[month] - } - //当前月第一天是周几 - const getDateByYearMonth = (year: number, month: number, day: number = 1): Date => { - var date = new Date() - date.setFullYear(year) - date.setMonth(month, day) - return date - } - const getWeeksByFirstDay = (year: number, month: number): number => { - var date = getDateByYearMonth(year, month) - return date.getDay() - } - const getDayText = (line: number, weekIndex: number, weekDay: number, monthDays: number): any => { - var number = line * 7 + weekIndex - weekDay + 1 - if (number <= 0 || number > monthDays) { - return   - } - return - clickItem(line)}>{number} - - } - const setCurrentYearMonth = (date) => { - var month = getMonth(date) - var year = getFullYear(date) - setLoinsYear(year); - seLoinstMonth(month) - setTag(false) - } - - const monthChange = (monthChanged: number) => { - if (tag) { - return; - } else { - setTag(true) - } - - var monthAfter = month + monthChanged - var date = getDateByYearMonth(year, monthAfter) - setCurrentYearMonth(date) - } - const formatNumber = (num: number): string => { - var _num = num + 1 - return _num < 10 ? `0${_num}` : `${_num}` - } - - // let monthDays = getCurrentMonthDays(month); - let weekDay = getWeeksByFirstDay(year, month); - - let _startX = 0; - const clickItem = (item) => { - console.log(item, 666) - } return ( closePopup?.()}> - { - if (_startX > val.changedTouches[0]['clientX'] + 30) { - monthChange(1); - } - if (_startX < val.changedTouches[0]['clientX'] - 30) { - monthChange(-1); - } - }} onTouchStart={(val) => { - _startX = val.changedTouches[0]['clientX'] - - }} - > - - - {/* { - monthChange(-1); - }}> - */} - - {year + currentDate.getFullYear()} 年 {formatNumber(month)}月 - - {/* { - monthChange(1); - }}> */} - - - { - WEEK_NAMES.map((week, key) => { - return {week} - }) - } - { - LINES.map((l, key) => { - return - { - WEEK_NAMES.map((week, index) => { - return getDayText(key, index, weekDay, getCurrentMonthDays(month, year)) - }) - } - - }) - } - - + + ) }) \ No newline at end of file diff --git a/src/styles/iconfont.scss b/src/styles/iconfont.scss index b109443..aa8004e 100644 --- a/src/styles/iconfont.scss +++ b/src/styles/iconfont.scss @@ -3,7 +3,7 @@ /* Project id 3619513 */ // url('/src/styles/iconfont.ttf') format('truetype'); src: - url('/src/styles/iconfont.ttf?t=1663556335905') format('truetype'); + url('iconfont.ttf?t=1663556335905') format('truetype'); } .iconfont { diff --git a/src/styles/mixins/index.scss b/src/styles/mixins/index.scss new file mode 100644 index 0000000..e0a243b --- /dev/null +++ b/src/styles/mixins/index.scss @@ -0,0 +1,18 @@ +/** + * Mixins + */ + +/* library */ +// @import './libs/absolute-center'; +// @import './libs/clearfix'; +// @import './libs/line'; +// @import './libs/overlay'; +// @import './libs/shade'; +@import './libs/tint'; +@import './libs/flex'; +// @import './libs/border'; +// @import './libs/active'; +// @import './libs/disabled'; +// @import './libs/placeholder'; +// @import './libs/alignhack'; +// @import './libs/hairline'; diff --git a/src/styles/mixins/libs/flex.scss b/src/styles/mixins/libs/flex.scss new file mode 100644 index 0000000..576204a --- /dev/null +++ b/src/styles/mixins/libs/flex.scss @@ -0,0 +1,50 @@ +@mixin display-flex { + display: flex; +} + +@mixin flex-wrap($value: nowrap) { + flex-wrap: $value; +} + +@mixin align-items($value: stretch) { + align-items: $value; + @if $value == flex-start { + -webkit-box-align: start; + } @else if $value == flex-end { + -webkit-box-align: end; + } @else { + -webkit-box-align: $value; + } +} + +@mixin align-content($value: flex-start) { + align-content: $value; +} + +@mixin justify-content($value: flex-start) { + justify-content: $value; + @if $value == flex-start { + -webkit-box-pack: start; + } @else if $value == flex-end { + -webkit-box-pack: end; + } @else if $value == space-between { + -webkit-box-pack: justify; + } @else { + -webkit-box-pack: $value; + } +} + +/* Flex Item */ +@mixin flex($fg: 1, $fs: null, $fb: null) { + flex: $fg $fs $fb; + -webkit-box-flex: $fg; +} + +@mixin flex-order($n) { + order: $n; + -webkit-box-ordinal-group: $n; +} + +@mixin align-self($value: auto) { + align-self: $value; +} diff --git a/src/styles/mixins/libs/tint.scss b/src/styles/mixins/libs/tint.scss new file mode 100644 index 0000000..ca201ea --- /dev/null +++ b/src/styles/mixins/libs/tint.scss @@ -0,0 +1,23 @@ +/** + * Mixes a color with white. It's different from lighten() + * + * @param {color} $color + * @param {number (percentage)} $percent [The amout of white to be mixed in] + * @return {color} + * + * @example + * .element { + * background-color: tint(#6ecaa6 , 40%); + * } + * + * // CSS Output + * .element { + * background-color: #a8dfc9; + * } + */ +@function tint( + $color, + $percent +) { + @return mix(#FFF, $color, $percent); +} diff --git a/src/styles/variables/default.scss b/src/styles/variables/default.scss new file mode 100644 index 0000000..091bbe8 --- /dev/null +++ b/src/styles/variables/default.scss @@ -0,0 +1,457 @@ +/** + * Default variables + */ + +@import '../mixins/libs/tint'; + +$hd: 2 !default; // 基本单位 + +/* The Color of O2Team Brand */ +$color-brand: #6190E8 !default; +$color-brand-light: #78A4F4 !default; +$color-brand-dark: #346FC2 !default; + +/* Color */ +$color-success: #13CE66 !default; +$color-error: #FF4949 !default; +$color-warning: #FFC82C !default; +$color-info: #78A4FA !default; + +/* Color Palette */ +$color-black-0: #000 !default; +$color-black-1: #333 !default; +$color-black-2: #7F7F7F !default; +$color-black-3: #B2B2B2 !default; + +$color-grey-0: #333 !default; +$color-grey-1: #666 !default; +$color-grey-2: #999 !default; +$color-grey-3: #CCC !default; +$color-grey-4: #E5E5E5 !default; +$color-grey-5: #F0F0F0 !default; +$color-grey-6: #F7F7F7 !default; + +$color-white: #FFF !default; + +/* Text Color */ +$color-text-base: #333 !default; // 文字的基本色 +$color-text-base-inverse: #FFF !default; // 反色 +$color-text-secondary: #36D57D !default; // 辅助色 +$color-text-placeholder: #C9C9C9 !default; +$color-text-disabled: #CCC !default; +$color-text-title: #2C405A !default; // 文章标题 +$color-text-paragraph: #3F536E !default; // 文章段落 + +/* Link */ +$color-link: #6190E8 !default; +$color-link-hover: #79A1EB !default; +$color-link-active: #4F7DE2 !default; +$color-link-disabled: #BFBFBF !default; + +/* 背景色 */ +$color-bg: #FFF !default; +$color-bg-base: #FAFBFC !default; +$color-bg-light: #ECF5FD !default; +$color-bg-lighter: tint($color-bg-light, 50%) !default; +$color-bg-grey: #F7F7F7 !default; + +/* 边框颜色 */ +$color-border-base: #C5D9E8 !default; +$color-border-split: tint($color-border-base, 20%) !default; // 分割线 +$color-border-light: tint($color-border-base, 30%) !default; +$color-border-lighter: tint($color-border-base, 50%) !default; +$color-border-lightest: tint($color-border-base, 80%) !default; +$color-border-grey: #CCC !default; + +/* 图标颜色 */ +$color-icon-base: #CCC !default; + +/* Border Radius */ +$border-radius-sm: 2px * $hd !default; +$border-radius-md: 4px * $hd !default; +$border-radius-lg: 6px * $hd !default; +$border-radius-circle: 50% !default; + +/* 透明度 */ +$opacity-active: 0.6 !default; // Button 等组件点击态额透明度 +$opacity-disabled: 0.3 !default; // Button 等组件禁用态的透明度 + +/* Font */ +$font-size-xs: 10px * $hd !default; // 非常用字号,用于标签 +$font-size-sm: 12px * $hd !default; // 用于辅助信息 +$font-size-base: 14px * $hd !default; // 常用字号 +$font-size-lg: 16px * $hd !default; // 常规标题 +$font-size-xl: 18px * $hd !default; // 大标题 +$font-size-xxl: 20px * $hd !default; // 用于大号的数字 + +/* Line Height */ +$line-height-base: 1 !default; // 单行 +$line-height-en: 1.3 !default; // 英文多行 +$line-height-zh: 1.5 !default; // 中文多行 + +/* 水平间距 */ +$spacing-h-sm: 5px * $hd !default; +$spacing-h-md: 8px * $hd !default; +$spacing-h-lg: 12px * $hd !default; +$spacing-h-xl: 16px * $hd !default; + +/* 垂直间距 */ +$spacing-v-xs: 3px * $hd !default; +$spacing-v-sm: 6px * $hd !default; +$spacing-v-md: 9px * $hd !default; +$spacing-v-lg: 12px * $hd !default; +$spacing-v-xl: 15px * $hd !default; + +/* 图标尺寸 */ +$icon-size-sm: 18px * $hd !default; +$icon-size-md: 22px * $hd !default; +$icon-size-lg: 36px * $hd !default; + +/* z-index */ +$zindex-divider: 100 !default; +$zindex-steps: 500 !default; +$zindex-tab: 600 !default; +$zindex-form: 700 !default; +$zindex-nav: 800 !default; +$zindex-search-bar: 800 !default; +$zindex-indexes: 805 !default; +$zindex-flot-layout: 810 !default; +$zindex-drawer: 900 !default; +$zindex-modal: 1000 !default; +$zindex-action-sheet: 1010 !default; +$zindex-picker: 1010 !default; +$zindex-curtain: 1080 !default; +$zindex-message: 1090 !default; +$zindex-toast: 1090 !default; + +/* timing function */ +$timing-func: cubic-bezier(0.36, 0.66, 0.04, 1) !default; + +/** +* CSS cubic-bezier timing functions +* http://bourbon.io/docs/#timing-functions +*/ +$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530) !default; +$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190) !default; +$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220) !default; +$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060) !default; +$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715) !default; +$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035) !default; +$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335) !default; +$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045) !default; + +$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940) !default; +$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000) !default; +$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000) !default; +$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000) !default; +$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000) !default; +$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000) !default; +$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000) !default; +$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275) !default; + +$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955) !default; +$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000) !default; +$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000) !default; +$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000) !default; +$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950) !default; +$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000) !default; +$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860) !default; +$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550) !default; + +/** + * 组件变量 + */ + +/* Accordion */ +$at-accordion-color-arrow: $color-grey-3 !default; + +/* Activity Indicator */ +$at-activity-indicator-font-size: 28px !default; +$at-activity-indicator-font-color: $color-grey-2 !default; + +/* Avatar */ +$at-avatar-color: $color-white !default; +$at-avatar-bg-color: $color-grey-4 !default; +$at-avatar-size-sm: 80px !default; +$at-avatar-size-md: 100px !default; +$at-avatar-size-lg: 120px !default; + +/* Badge */ +$at-badge-color: $color-white !default; +$at-badge-bg-color: $color-error !default; +$at-badge-bg: $at-badge-bg-color !default; +$at-badge-font-size: $font-size-xs !default; +$at-badge-dot-size: 20px !default; + +/* Button */ +$at-button-height: 92px !default; +$at-button-height-sm: 60px !default; +$at-button-color: $color-brand !default; +$at-button-border-color-primary: $color-brand !default; +$at-button-border-color-secondary: $color-brand !default; +$at-button-bg: $at-button-color !default; + +/* Float Button */ +$at-fab-size: 56px * $hd !default; +$at-fab-size-sm: 40px * $hd !default; +$at-fab-icon-size: 24px * $hd !default; +$at-fab-bg-color: $color-brand; +$at-fab-bg-color-active: $color-brand-dark; +$at-fab-box-shadow: + 0 6px 10px -2px rgba(0, 0, 0, 0.2), + 0 12px 20px 0 rgba(0, 0, 0, 0.14), + 0 2px 36px 0 rgba(0, 0, 0, 0.12) !default; +$at-fab-box-shadow-active: + 0 14px 16px -8px rgba(0, 0, 0, 0.2), + 0 24px 34px 4px rgba(0, 0, 0, 0.14), + 0 10px 44px 8px rgba(0, 0, 0, 0.12) !default; + +/* Calendar */ +$at-calendar-day-size: 72px !default; +$at-calendar-mark-size: 8px !default; +$at-calendar-header-color: #B8BFC6 !default; +$at-calendar-main-color: $color-brand !default; +$at-calendar-day-color: #7C86A2 !default; + +/* Card */ +$at-card-thumb-size: 32px !default; +$at-card-icon-size: 32px !default; +$at-card-title-color: $color-text-title !default; +$at-card-extra-color: $color-text-title !default; +$at-card-info-color: $color-text-base !default; +$at-card-note-color: $color-grey-2 !default; + +/* Checkbox */ +$at-checkbox-circle-size: 40px !default; +$at-checkbox-icon-size: $font-size-sm !default; +$at-checkbox-icon-color: $color-brand !default; +$at-checkbox-icon-color-checked: $color-white !default; +$at-checkbox-title-color: $color-text-base !default; +$at-checkbox-title-font-size: $font-size-lg !default; +$at-checkbox-desc-font-size: $font-size-sm !default; +$at-checkbox-desc-color: $color-grey-2 !default; + +/* Countdown */ +$at-countdown-font-size: $font-size-lg !default; +$at-countdown-num-color: $color-text-base !default; +$at-countdown-card-num-color: #FF4949 !default; +$at-countdown-card-num-bg-color: $color-white !default; + +/* Curtain */ +$at-curtain-btn-color: $color-white !default; + +/* Divider */ +$at-divider-height: 112px; +$at-divider-content-color: $color-brand !default; +$at-divider-font-size: $font-size-lg !default; +$at-divider-line-color: $color-grey-3 !default; + +/* Drawer */ +$at-drawer-content-width: 460px !default; + +/* FloatLayout */ +$float-layout-height-min: 600px !default; +$float-layout-height-max: 950px !default; +$float-layout-header-bg-color: $color-bg-grey !default; +$float-layout-title-color: $color-text-base !default; +$float-layout-title-font-size: $font-size-lg !default; +$float-layout-btn-color: $color-grey-3 !default; + +/* Grid */ +$at-grid-text-color: $color-text-base !default; +$at-grid-font-size: $font-size-lg !default; +$at-grid-img-size: 80px !default; +$at-gird-img-size-sm: 50px !default; + +/* ImagePicker */ +$at-image-picker-btn-add-color: $color-grey-3 !default; +$at-image-picker-btn-remove-color: $color-white !default; +$at-image-picker-btn-remove-bg-color: $color-grey-2 !default; + +/* Indexes */ +$at-indexes-nav-color: $color-link !default; +$at-indexes-nav-font-size: $font-size-sm !default; +$at-indexes-title-color: $color-black-2 !default; +$at-indexes-title-font-size: $font-size-sm !default; +$at-indexes-title-bg-color: $color-grey-6 !default; + +/* InputNumber */ +$at-input-number-text-color: $color-text-base !default; +$at-input-number-font-size: $font-size-base !default; +$at-input-number-font-size-lg: $font-size-xl !default; +$at-input-number-btn-color: $color-brand !default; +$at-input-number-btn-size: 30px !default; +$at-input-number-btn-size-lg: 36px !default; +$at-input-number-width-min: 80px !default; +$at-input-number-width-min-lg: 120px !default; + +/* Input */ +$at-input-label-color: $color-text-base !default; +$at-input-text-color: $color-text-base !default; +$at-input-font-size: $font-size-lg !default; +$at-input-placeholder-color: $color-grey-3 !default; + +/* List */ +$at-list-thumb-size: 56px !default; +$at-list-arrow-color: $color-grey-3 !default; +$at-list-text-color: $color-text-base !default; +$at-list-content-color: $color-grey-2 !default; +$at-list-extra-color: $color-grey-2 !default; +$at-list-extra-width: 235px !default; + +/* LoadMore */ +$at-load-more-height: 80PX !default; +$at-load-more-tips-color: $color-grey-1 !default; +$at-load-more-tips-size: $font-size-lg !default; + +/* Loading */ +$at-loading-size: 36px !default; +$at-loading-color: $color-brand !default; + +/* Message */ +$at-message-color: $color-white !default; +$at-message-font-size: $font-size-base !default; +$at-message-bg-color: $color-info !default; + +/* Modal */ +$at-modal-width: 540px !default; +$at-modal-header-text-color: $color-text-base !default; +$at-modal-content-text-color: $color-text-base !default; +$at-modal-btn-default-color: $color-text-base !default; +$at-modal-btn-confirm-color: $color-brand !default; +$at-modal-bg-color: $color-white !default; + +/* NavBar */ +$at-nav-bar-title-color: $color-text-base !default; +$at-nav-bar-link-color: $color-brand !default; + +/* NoticeBar */ +$at-noticebar-text-color: #DE8C17 !default; +$at-noticebar-bg-color: #FCF6ED !default; +$at-noticebar-font-size: $font-size-sm !default; +$at-noticebar-icon-size: 30px !default; +$at-noticebar-btn-close-size: 32px !default; +$at-noticebar-btn-close-color: $color-grey-3 !default; + +/* Pagination */ +$at-pagination-margin: 40px !default; +$at-pagination-num-color: $color-text-base !default; +$at-pagination-num-font-size: $font-size-base !default; +$at-pagination-current-num-color: $color-brand !default; +$at-pagination-icon-color: $color-text-base !default; +$at-pagination-icon-font-size: 32px !default; + +/* Progress */ +$at-progress-height: 16px !default; +$at-progress-text-size: $font-size-sm !default; +$at-progress-icon-size: $font-size-xl !default; +$at-progress-inner-bg-color: $color-grey-6 !default; +$at-progress-bar-bg-color: $color-brand-light !default; +$at-progress-bar-bg-color-active: $color-white !default; + +/* Radio */ +$at-radio-title-color: $color-text-base !default; +$at-radio-title-size: $font-size-lg !default; +$at-radio-desc-color: $color-grey-2 !default; +$at-radio-desc-size: $font-size-sm !default; +$at-radio-check-color: $color-brand !default; + +/* Range */ +$at-range-slider-size: 28PX !default; +$at-range-rail-height: 2PX !default; +$at-range-rail-bg-color: #E9E9E9 !default; +$at-range-track-bg-color: $color-brand !default; +$at-range-slider-color: $color-white !default; +$at-range-slider-shadow: 0 0 4PX 0 rgba(0, 0, 0, 0.2) !default; + +/* Rate */ +$at-rate-icon-size: 20PX !default; +$at-rate-star-color: #ECECEC !default; +$at-rate-star-color-on: #FFCA3E !default; + +/* SearchBar */ +$at-search-bar-btn-color: $color-white !default; +$at-search-bar-btn-bg-color: $color-brand !default; + +/* SegmentedControl */ +$at-segmented-control-color: $color-brand !default; +$at-segmented-control-color-active: $color-white !default; +$at-segmented-control-bg-color: transparent !default; +$at-segmented-control-font-size: $font-size-base !default; + +/* Slider */ +$at-slider-text-color: $color-grey-2 !default; +$at-slider-text-size: $font-size-base !default; + +/* Steps */ +$at-steps-circle-size: 56px !default; +$at-steps-icon-size: $font-size-sm !default; +$at-steps-color: $color-white !default; +$at-steps-color-active: $color-grey-2 !default; +$at-steps-bg-color: $color-grey-4 !default; +$at-steps-bg-color-active: $color-brand !default; +$at-steps-line-color: $color-grey-3 !default; +$at-steps-title-color: $color-black-0 !default; +$at-steps-title-size: $font-size-lg !default; +$at-steps-desc-color: $color-grey-3 !default; +$at-steps-desc-size: $font-size-sm !default; + +/* SwipeAction */ +$at-swipe-action-color: $color-white !default; +$at-swipe-action-font-size: $font-size-base !default; +$at-swipe-action-bg-color: $color-white !default; +$at-swipe-action-option-bg-color: $color-grey-2 !default; + +/* Switch */ +$at-switch-title-color: $color-text-base !default; +$at-switch-title-size: $font-size-base !default; + +/* TabBar */ +$at-tab-bar-bg-color: $color-white !default; +$at-tab-bar-color: $color-text-base !default; +$at-tab-bar-color-active: $color-brand !default; +$at-tab-bar-font-size: $font-size-base !default; +$at-tab-bar-icon-color: $color-grey-0 !default; +$at-tab-bar-icon-font-size: 48px !default; +$at-tab-bar-icon-image-size: 50px !default; + +/* Tabs */ +$at-tabs-color: $color-text-base !default; +$at-tabs-color-active: $color-brand !default; +$at-tabs-font-size: $font-size-base !default; +$at-tabs-line-height: 1PX !default; +$at-tabs-underline-color: $color-grey-5 !default; +$at-tabs-bg-color: $color-bg !default; + +/* Tag */ +$at-tag-height: 60px !default; +$at-tag-height-sm: 40px !default; +$at-tag-color: $color-grey-1 !default; +$at-tag-color-primary: $color-grey-1 !default; +$at-tag-color-active: $color-brand-light !default; +$at-tag-color-primary-active: $color-text-base-inverse !default; +$at-tag-font-size: $font-size-base !default; +$at-tag-font-size-sm: $font-size-xs !default; +$at-tag-bg-color: $color-bg-grey !default; +$at-tag-bg-color-primary: $color-bg-grey !default; +$at-tag-bg-color-active: $color-white !default; +$at-tag-bg-color-primary-active: $at-tag-color-active !default; +$at-tag-border-color: $at-tag-bg-color !default; +$at-tag-border-color-primary: $at-tag-bg-color !default; +$at-tag-border-color-active: $at-tag-color-active !default; + +/* Textarea */ +$at-textarea-font-size: $font-size-lg !default; +$at-textarea-tips-color: $color-text-placeholder !default; +$at-textarea-tips-size: $font-size-base !default; + +/* Timeline */ +$at-timeline-offset-left: 40px !default; +$at-timeline-title-color: $color-grey-0 !default; +$at-timeline-title-font-size: $font-size-base !default; +$at-timeline-desc-color: $color-grey-1 !default; +$at-timeline-desc-font-size: $font-size-sm !default; +$at-timeline-dot-size: 24px !default; +$at-timeline-dot-color: $color-bg !default; +$at-timeline-dot-border-color: $color-brand-light !default; +$at-timeline-line-color: $color-border-lighter !default; diff --git a/src/types/calendar.d.ts b/src/types/calendar.d.ts new file mode 100644 index 0000000..2bc8d32 --- /dev/null +++ b/src/types/calendar.d.ts @@ -0,0 +1,225 @@ +import dayjs from 'dayjs' +import { BaseEvent } from '@tarojs/components/types/common' + +// #region Calendar +declare namespace Calendar { + export type DateArg = string | number | Date + + export type classNameType = + | string + | Array + | { [key: string]: boolean } + + export interface Mark { + value: DateArg + } + + export interface ValidDate { + value: DateArg + } + + export interface Item { + value: string + + _value: dayjs.Dayjs + + text: number + + type: number + + marks: Array + + isActive?: boolean + + isToday?: boolean + + isBeforeMin?: boolean + + isAfterMax?: boolean + + isDisabled?: boolean + + isSelected?: boolean + + isSelectedHead?: boolean + + isSelectedTail?: boolean + } + + export interface GroupOptions { + validDates: Array + + marks: Array + + format: string + + selectedDates: Array + + minDate?: DateArg + + maxDate?: DateArg + } + + export type List = Array + + export type ListInfo = { + value: number + + list: List + } + + export interface SelectedDate { + end?: Calendar.DateArg + + start: Calendar.DateArg + } +} + +export default Calendar +export { Calendar } +// #endregion + +// #region AtCalendar +export interface AtCalendarPropsBase { + format?: string + + validDates?: Array + + minDate?: Calendar.DateArg + + maxDate?: Calendar.DateArg + + isSwiper?: boolean + + marks?: Array + + monthFormat?: string + + hideArrow?: boolean + + isVertical?: boolean + + className?: Calendar.classNameType + + onClickPreMonth?: () => void + + onClickNextMonth?: () => void + + onSelectDate?: (item: { value: Calendar.SelectedDate }) => void + + onDayClick?: (item: { value: string }) => void + + onDayLongClick?: (item: { value: string }) => void + + onMonthChange?: (value: string) => void +} + +export interface AtCalendarSingleSelectedProps extends AtCalendarPropsBase { + isMultiSelect?: false + + currentDate?: Calendar.DateArg +} + +export interface AtCalendarMutilSelectedProps extends AtCalendarPropsBase { + isMultiSelect?: true + + currentDate?: Calendar.SelectedDate +} + +export type AtCalendarProps = + | AtCalendarSingleSelectedProps + | AtCalendarMutilSelectedProps + +export interface AtCalendarDefaultProps { + format: string + + isSwiper: boolean + + validDates: Array + + marks: Array + + currentDate: Calendar.DateArg | Calendar.SelectedDate + + monthFormat: string + + hideArrow: boolean + + isVertical: boolean + + isMultiSelect: boolean + + selectedDates: Array +} + +export interface AtCalendarState { + generateDate: number + + selectedDate: Calendar.SelectedDate +} + +export type AtCalendarPropsWithDefaults = AtCalendarProps & + AtCalendarDefaultProps +// #endregion + +// #region AtCalendarController +export interface AtCalendarControllerProps { + generateDate: Calendar.DateArg + + minDate?: Calendar.DateArg + + maxDate?: Calendar.DateArg + + hideArrow: boolean + + monthFormat: string + + onPreMonth: () => void + + onNextMonth: () => void + + onSelectDate: (e: BaseEvent) => void +} + +export interface AtCalendarControllerState {} +// #endregion + +// #region AtCalendarBody +export type AtCalendarBodyListGroup = Array> + +export interface AtCalendarBodyProps { + format: string + + validDates: Array + + marks: Array + + isSwiper: boolean + + minDate?: Calendar.DateArg + + maxDate?: Calendar.DateArg + + isVertical: boolean + + generateDate: number + + selectedDate: Calendar.SelectedDate + + selectedDates: Array | [] + + onDayClick: (item: Calendar.Item) => void + + onSwipeMonth: (vectorCount: number) => void + + onLongClick: (item: Calendar.Item) => void +} + +export interface AtCalendarBodyState { + isAnimate: boolean + + offsetSize: number + + listGroup: AtCalendarBodyListGroup +} +// #endregion