✨ feat(添加数据):

This commit is contained in:
2023-01-10 11:49:26 +08:00
parent 464746ef97
commit 23f4885111
58 changed files with 3520 additions and 326 deletions
+365
View File
@@ -0,0 +1,365 @@
import { Swiper, SwiperItem, View } from '@tarojs/components'
import classnames from 'classnames'
import dayjs from 'dayjs'
import React from 'react'
import type {
BaseEventOrig,
ITouch,
ITouchEvent,
} from '@tarojs/components/types/common'
import type {
AtCalendarBodyListGroup,
AtCalendarBodyProps,
AtCalendarBodyState,
Calendar,
} from '../../../types/calendar'
import generateCalendarGroup from '../common/helper'
import AtCalendarDateList from '../ui/date-list/index'
import AtCalendarDayList from '../ui/day-list/index'
import { delayQuerySelector } from '@/common/util'
const ANIMTE_DURATION = 300
const defaultProps: Partial<AtCalendarBodyProps> = {
marks: [],
selectedDate: {
end: Date.now(),
start: Date.now(),
},
format: 'YYYY/MM/DD',
generateDate: Date.now(),
}
export default class AtCalendarBody extends React.Component<
AtCalendarBodyProps,
Readonly<AtCalendarBodyState>
> {
static defaultProps: Partial<AtCalendarBodyProps> = 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<Calendar.Item>
private getGroups = (
generateDate: number,
selectedDate: Calendar.SelectedDate,
): AtCalendarBodyListGroup => {
const dayjsDate = dayjs(generateDate)
const arr: AtCalendarBodyListGroup = []
const preList: Calendar.ListInfo<Calendar.Item> = this.generateFunc(
dayjsDate.subtract(1, 'month').valueOf(),
selectedDate,
)
const nowList: Calendar.ListInfo<Calendar.Item> = this.generateFunc(
generateDate,
selectedDate,
true,
)
const nextList: Calendar.ListInfo<Calendar.Item> = 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<ITouch> },
): void => {
const { clientY, clientX } = e.changedTouches[0]
this.swipeStartPoint = this.props.isVertical ? clientY : clientX
}
private handleSwipeTouchEnd = (
e: ITouchEvent & { changedTouches: Array<ITouch> },
): 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 (
<View
className={classnames(
'main',
'at-calendar-slider__main',
`at-calendar-slider__main--${process.env.TARO_ENV}`,
)}
>
<AtCalendarDayList />
<View className="main__body body">
<View className="body__slider body__slider--now">
<AtCalendarDateList
list={listGroup[1].list}
onClick={this.props.onDayClick}
onLongClick={this.props.onLongClick}
/>
</View>
</View>
</View>
)
}
/* 需要 Taro 组件库维护 Swiper 使 小程序 和 H5 的表现保持一致 */
if (process.env.TARO_ENV === 'h5') {
return (
<View
className={classnames(
'main',
'at-calendar-slider__main',
`at-calendar-slider__main--${process.env.TARO_ENV}`,
)}
onTouchEnd={this.handleTouchEnd}
onTouchMove={this.handleTouchMove}
onTouchStart={this.handleTouchStart}
>
<AtCalendarDayList />
<View
className={classnames('main__body body', {
'main__body--slider': isSwiper,
'main__body--animate': isAnimate,
})}
style={{
transform: isSwiper
? `translateX(-100%) translate3d(${offsetSize},0,0)`
: '',
WebkitTransform: isSwiper
? `translateX(-100%) translate3d(${offsetSize}px,0,0)`
: '',
}}
>
<View className="body__slider body__slider--pre">
<AtCalendarDateList list={listGroup[0].list} />
</View>
<View className="body__slider body__slider--now">
<AtCalendarDateList
list={listGroup[1].list}
onClick={this.props.onDayClick}
onLongClick={this.props.onLongClick}
/>
</View>
<View className="body__slider body__slider--next">
<AtCalendarDateList list={listGroup[2].list} />
</View>
</View>
</View>
)
}
return (
<View
className={classnames(
'main',
'at-calendar-slider__main',
`at-calendar-slider__main--${process.env.TARO_ENV}`,
)}
>
<AtCalendarDayList />
<Swiper
circular
current={1}
skipHiddenItemLayout
className={classnames('main__body')}
onChange={this.handleChange}
vertical={this.props.isVertical}
onAnimationFinish={this.handleAnimateFinish}
onTouchEnd={this.handleSwipeTouchEnd}
onTouchStart={this.handleSwipeTouchStart}
>
{listGroup.map((item, key) => (
<SwiperItem key={key} itemId={key.toString()}>
<AtCalendarDateList
list={item.list}
onClick={this.props.onDayClick}
onLongClick={this.props.onLongClick}
/>
</SwiperItem>
))}
</Swiper>
</View>
)
}
}
@@ -0,0 +1,5 @@
export const TYPE_PRE_MONTH = -1
export const TYPE_NOW_MONTH = 0
export const TYPE_NEXT_MONTH = 1
+119
View File
@@ -0,0 +1,119 @@
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import _flow from 'lodash/flow'
import type { Calendar } from '../../../types/calendar'
import * as constant from './constant'
import plugins from './plugins'
const TOTAL = 7 * 6
function getFullItem(
item: Partial<Calendar.Item>,
options: Calendar.GroupOptions,
selectedDate: Calendar.SelectedDate,
isShowStatus?: boolean,
): any {
if (options.marks.find(x => x.value === item.value)) {
(item.marks as Array<Calendar.Mark>) = [{
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<Calendar.Item> {
return function(
generateDate: number,
selectedDate: Calendar.SelectedDate,
isShowStatus?: boolean,
): Calendar.ListInfo<Calendar.Item> {
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<Calendar.Item> = []
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')
}
+124
View File
@@ -0,0 +1,124 @@
import dayjs from 'dayjs'
import _isEmpty from 'lodash/isEmpty'
import type { 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]
@@ -0,0 +1,81 @@
import { Picker, Text, View } from '@tarojs/components'
import classnames from 'classnames'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import React from 'react'
import type {
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 (
<View className="at-calendar__controller controller">
{hideArrow
? null
: (
<View
className={classnames('controller__arrow controller__arrow--left', {
'controller__arrow--disabled': isMinMonth,
})}
onClick={this.props.onPreMonth.bind(this, isMinMonth)}
/>
)}
<Picker
mode="date"
fields="month"
end={maxDateValue}
start={minDateValue}
onChange={this.props.onSelectDate}
value={dayjsDate.format('YYYY-MM')}
>
<Text className="controller__info">
{dayjsDate.format(monthFormat)}
</Text>
</Picker>
{hideArrow
? null
: (
<View
className={classnames(
'controller__arrow controller__arrow--right',
{
'controller__arrow--disabled': isMaxMonth,
},
)}
onClick={this.props.onNextMonth.bind(this, isMaxMonth)}
/>
)}
</View>
)
}
}
+180
View File
@@ -0,0 +1,180 @@
@import '../../styles/variables/default.scss';
@import '../../styles/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: 1);
&-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;
}
}
}
+324
View File
@@ -0,0 +1,324 @@
import { View } from '@tarojs/components'
import classnames from 'classnames'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import React from 'react'
import type { BaseEventOrig } from '@tarojs/components/types/common'
import type {
AtCalendarDefaultProps,
AtCalendarProps,
AtCalendarPropsWithDefaults,
AtCalendarState,
Calendar,
} from '../../types/calendar'
import AtCalendarBody from './body/index'
import AtCalendarController from './controller/index'
import './index.scss'
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<AtCalendarState>
> {
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<AtCalendarState> => {
const { generateDate } = this.state
const stateValue: Partial<AtCalendarState> = {
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<AtCalendarState, 'selectedDate'> => {
const { selectedDate } = this.state
const { end, start } = selectedDate
const valueUnix: number = value.valueOf()
const state: Pick<AtCalendarState, 'selectedDate'> = {
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<AtCalendarState> = {}
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 (
<View className={classnames('at-calendar', className)}>
<AtCalendarController
minDate={minDate}
maxDate={maxDate}
hideArrow={hideArrow}
monthFormat={monthFormat}
generateDate={generateDate}
onPreMonth={this.handleClickPreMonth}
onNextMonth={this.handleClickNextMonth}
onSelectDate={this.handleSelectDate}
/>
<AtCalendarBody
validDates={validDates}
marks={marks}
format={format}
minDate={minDate}
maxDate={maxDate}
isSwiper={isSwiper}
isVertical={isVertical}
selectedDate={selectedDate}
selectedDates={selectedDates}
generateDate={generateDate}
onDayClick={this.handleDayClick}
onSwipeMonth={this.setMonth}
onLongClick={this.handleDayLongClick}
/>
</View>
)
}
}
+225
View File
@@ -0,0 +1,225 @@
import type 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<string>
| Record<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<Mark>
isActive?: boolean
isToday?: boolean
isBeforeMin?: boolean
isAfterMax?: boolean
isDisabled?: boolean
isSelected?: boolean
isSelectedHead?: boolean
isSelectedTail?: boolean
}
export interface GroupOptions {
validDates: Array<ValidDate>
marks: Array<Mark>
format: string
selectedDates: Array<SelectedDate>
minDate?: DateArg
maxDate?: DateArg
}
export type List<T> = Array<T>
export interface ListInfo<T> {
value: number
list: List<T>
}
export interface SelectedDate {
end?: Calendar.DateArg
start: Calendar.DateArg
}
}
export default Calendar
export { Calendar }
// #endregion
// #region AtCalendar
export interface AtCalendarPropsBase {
format?: string
validDates?: Array<Calendar.ValidDate>
minDate?: Calendar.DateArg
maxDate?: Calendar.DateArg
isSwiper?: boolean
marks?: Array<Calendar.Mark>
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<Calendar.ValidDate>
marks: Array<Calendar.Mark>
currentDate: Calendar.DateArg | Calendar.SelectedDate
monthFormat: string
hideArrow: boolean
isVertical: boolean
isMultiSelect: boolean
selectedDates: Array<Calendar.SelectedDate>
}
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: any) => void
}
export interface AtCalendarControllerState {}
// #endregion
// #region AtCalendarBody
export type AtCalendarBodyListGroup = Array<Calendar.ListInfo<Calendar.Item>>
export interface AtCalendarBodyProps {
format: string
validDates: Array<Calendar.ValidDate>
marks: Array<Calendar.Mark>
isSwiper: boolean
minDate?: Calendar.DateArg
maxDate?: Calendar.DateArg
isVertical: boolean
generateDate: number
selectedDate: Calendar.SelectedDate
selectedDates: Array<Calendar.SelectedDate> | []
onDayClick: (item: Calendar.Item) => void
onSwipeMonth: (vectorCount: number) => void
onLongClick: (item: Calendar.Item) => void
}
export interface AtCalendarBodyState {
isAnimate: boolean
offsetSize: number
listGroup: AtCalendarBodyListGroup
}
// #endregion
@@ -0,0 +1,82 @@
import { Text, View } from '@tarojs/components'
import classnames from 'classnames'
import React from 'react'
import type { Calendar } from '../../types/calendar'
import * as constant from '../../common/constant'
const MAP: Record<number, string> = {
[constant.TYPE_PRE_MONTH]: 'pre',
[constant.TYPE_NOW_MONTH]: 'now',
[constant.TYPE_NEXT_MONTH]: 'next',
}
export interface Props {
list: Calendar.List<Calendar.Item>
onClick?: (item: Calendar.Item) => void
onLongClick?: (item: Calendar.Item) => void
}
export default class AtCalendarList extends React.Component<Props> {
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 (
<View className="at-calendar__list flex">
{list.map((item: Calendar.Item) => (
<View
key={`list-item-${item.value}`}
onClick={this.handleClick.bind(this, item)}
onLongPress={this.handleLongClick.bind(this, item)}
className={classnames(
'flex__item',
`flex__item--${MAP[item.type]}`,
{
'flex__item--today': item.isToday,
'flex__item--active': item.isActive,
'flex__item--selected': item.isSelected,
'flex__item--selected-head': item.isSelectedHead,
'flex__item--selected-tail': item.isSelectedTail,
'flex__item--blur':
item.isDisabled
|| item.type === constant.TYPE_PRE_MONTH
|| item.type === constant.TYPE_NEXT_MONTH,
},
)}
>
<View className="flex__item-container">
<View className="container-text">{item.text}</View>
</View>
<View className="flex__item-extra extra">
{item.marks && item.marks.length > 0
? (
<View className="extra-marks">
{item.marks.map((mark, key) => (
<Text key={key} className="mark">
{mark.value as React.ReactNode}
</Text>
))}
</View>
)
: null}
</View>
</View>
))}
</View>
)
}
}
@@ -0,0 +1,20 @@
import { View } from '@tarojs/components'
import React from 'react'
export default class AtCalendarHeader extends React.Component {
public render(): JSX.Element {
return (
<View className="at-calendar__header header">
<View className="header__flex">
<View className="header__flex-item">日</View>
<View className="header__flex-item">一</View>
<View className="header__flex-item">二</View>
<View className="header__flex-item">三</View>
<View className="header__flex-item">四</View>
<View className="header__flex-item">五</View>
<View className="header__flex-item">六</View>
</View>
</View>
)
}
}
+14 -4
View File
@@ -8,16 +8,26 @@ import ViewCodeList from '@/components/viewCodeList/index'
interface param {
y: number
custom_print_id: number
sale_order_id: number
orderObj?: any
}
export default ({ y, orderObj = {} }: param) => {
export default ({ y, custom_print_id = 0, sale_order_id = 0, orderObj = {} }: param) => {
const [screenHeight, setScreenHeight] = useState({
customer_service_y: 0,
code_list_y: 0,
})
const [showCode, setShowCode] = useState(false)
const [showPopup, setshowPopup] = useState(false)
const onNavigateTo = () => {
if (custom_print_id) {
goLink(`/pages/codeSetting/index?custom_print_id=${custom_print_id}`)
}
else {
goLink(`/pages/codeSetting/index?sale_order_id=${sale_order_id}`)
}
}
return (<>
{showCode && <View className={styles.mask} catchMove onClick={() => setShowCode(!showCode)}></View>}
@@ -31,9 +41,9 @@ export default ({ y, orderObj = {} }: param) => {
<IconFont name="icon-zidingyimadanyulan" size={46} />
<Text>自定义码单预览</Text>
</View>
<View className={styles['code_list__card--item']} onClick={() => goLink('/pages/codeSetting/index')}>
<View className={styles['code_list__card--item']} onClick={onNavigateTo}>
<IconFont name="icon-bianjizidingyimadan" size={46} />
<Text>编辑自定义码单</Text>
<Text>{custom_print_id ? '编辑自定义码单' : '新建自定义码单'}</Text>
</View>
</View>}
<View className={styles['code_list--text']} onClick={() => setShowCode(!showCode)}>码单</View>
+3 -2
View File
@@ -21,8 +21,9 @@ interface param {
messagePath?: string
showCart?: false|true
orderObj?: any
orderInfo?: any
}
const MoveBtn = ({ orderObj = {}, children = null, onShopClick, showList = [], messageTitle = '', messagePath = '', showCart = false }: param) => {
const MoveBtn = ({ orderObj = {}, children = null, onShopClick, showList = [], messageTitle = '', messagePath = '', showCart = false, orderInfo = {} }: param) => {
const userInfo = useSelector(state => state.userInfo)
// 获取购物车数据数量
const { getShopCount, commonData } = useCommonData()
@@ -102,7 +103,7 @@ const MoveBtn = ({ orderObj = {}, children = null, onShopClick, showList = [], m
>
<Image mode="aspectFit" src={formatImgUrl('/mall/float_button_customer_service.png')} />
</MovableView>}
{onShow('code') && <CodeSelect orderObj={orderObj} y={screenHeight.code as number} />}
{onShow('code') && <CodeSelect orderObj={orderObj} y={screenHeight.code as number} custom_print_id={orderInfo?.custom_print_id} sale_order_id={orderInfo?.id} />}
<Customer messageTitle={messageTitle} messagePath={messageTitle} show={customer_service_show} showCard={showCart} onClose={customerClose} />
</MovableArea>
)
@@ -0,0 +1,74 @@
.popup_modal_main {
position: fixed;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 1000;
.popup_modal_con {
background-color: #fff;
border-radius: 16px;
width: 606px;
z-index: 1000;
.popup_modal_title {
height: 112px;
background: #ffffff;
border-radius: 16px 16px 0px 0px;
font-size: 32px;
color: #333333;
text-align: center;
line-height: 112px;
}
.popup_modal_input {
height: 144px;
background: #ffffff;
display: flex;
justify-content: center;
.input {
width: 526px;
height: 88px;
background: #f5f5f5;
padding: 20px;
border: 1px solid #337fff;
border-radius: 16px;
box-sizing: border-box;
}
input {
background: #f5f5f5;
}
}
.popup_modal_btn {
display: flex;
width: 606px;
height: 112px;
background: #ffffff;
border-radius: 0px 0px 16px 16px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
.popup_modal_btn_item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
&:nth-child(1) {
color: #333333;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
&:nth-child(2) {
color: #337fffff;
}
}
}
}
.mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: rgba($color: #000000, $alpha: 0.6);
z-index: 999;
}
}
+59
View File
@@ -0,0 +1,59 @@
import { Input, View } from '@tarojs/components'
import { useEffect, useState } from 'react'
import styles from './index.module.scss'
export interface Param {
show: boolean
title: string
onConfirm?: (val: string) => void
onCancel?: () => void
defaultValue?: string
onClose?: () => void
}
export default (props: Param) => {
const { show = false, title = '', onConfirm, onCancel, defaultValue = '', onClose } = props
const [value, setValue] = useState('')
const [openStatus, setOpenStatus] = useState(false)
useEffect(() => {
if (value !== defaultValue) { setValue(() => defaultValue) }
}, [defaultValue])
useEffect(() => {
if (show !== openStatus) { setOpenStatus(() => show) }
}, [show])
const onInputEven = (e) => {
const res = e.detail.value
setValue(res)
}
const onCancelEven = () => {
onCancel?.()
onCloseEven()
}
const onCloseEven = () => {
onClose?.()
setOpenStatus(false)
}
const onConfirmEven = (val) => {
onClose?.()
onConfirm?.(val)
}
return (
<>
{openStatus && <View className={styles.popup_modal_main}>
<View className={styles.popup_modal_con}>
<View className={styles.popup_modal_title}>{title}</View>
<View className={styles.popup_modal_input}>
<View className={styles.input}>
<Input onInput={onInputEven} value={value} />
</View>
</View>
<View className={styles.popup_modal_btn}>
<View className={styles.popup_modal_btn_item} onClick={onCancelEven}>取消</View>
<View className={styles.popup_modal_btn_item} onClick={() => onConfirmEven(value)}>确认修改</View>
</View>
</View>
<View className={styles.mask} catchMove onClick={onCloseEven}></View>
</View>}
</>
)
}
@@ -0,0 +1,12 @@
.time-box {
padding: 40px;
}
.sure-box {
margin-left: 102px;
margin-right: 102px;
font-size: 28px;
font-weight: 500;
}
+55
View File
@@ -0,0 +1,55 @@
import { View } from '@tarojs/components'
import { memo, useState } from 'react'
import dayjs from 'dayjs'
import NormalButton from '../normalButton'
import styles from './index.module.scss'
import AtCalendar from '@/components/calendar/index'
import Popup from '@/components/popup'
type DateArg = string | number | Date
interface Props {
end?: DateArg
start?: DateArg
onSelectDate?: (any) => void
}
const TimePicker = (props: Props) => {
const { start = '', end = '', onSelectDate } = props
const [time, setTime] = useState<any>({})
const handTime = (e) => {
const { start, end } = e.value
// 如果选的是同一天的日期, end 自动加一天
if (!end) {
// 判断如果没选下一天的时候
e.value.end = `${dayjs(new Date(start)).add(1, 'day').format('YYYY-MM-DD')} 00:00:00`
}
else
if (start === end) {
e.value.end = `${dayjs(new Date(start)).add(1, 'day').format('YYYY-MM-DD')} 00:00:00`
}
else {
e.value.start = `${dayjs(start).format('YYYY-MM-DD')} 00:00:00`
e.value.end = `${dayjs(end).format('YYYY-MM-DD')} 00:00:00`
}
console.log('e===>', e)
setTime(e)
}
// 由于小程序的bug,部分ios和安卓显示时间的时候会有问题,原因是格式化时有`-`这个横杠
return (
<>
<View className={styles['time-box']}>
<AtCalendar
isMultiSelect
format="YYYY-MM-DD 00:00:00"
currentDate={{
start,
end,
}}
onSelectDate={e => handTime(e)}
/>
</View>
<NormalButton type="primary" onClick={() => onSelectDate?.(time)} size="normal" round customClassName={styles['sure-box']}>确认</NormalButton>
</>
)
}
export default memo(TimePicker)
+19
View File
@@ -0,0 +1,19 @@
.time-box {
padding: 40px;
}
.sure-box {
// padding: 16px 102px 30px 102px;
margin-left: 102px;
margin-right: 102px;
height: 80px;
background: #337FFF;
border-radius: 44px;
font-size: 28px;
font-weight: 500;
color: #FFFFFF;
text-align: center;
line-height: 80px;
}
+23
View File
@@ -0,0 +1,23 @@
import { memo } from 'react'
import './index.scss'
import TimePicker from '../timePicker'
import Popup from '@/components/popup'
type DateArg = string | number | Date
interface Props {
showTime: boolean
closePopup?: () => void
end?: DateArg
start?: DateArg
onSelectDate?: (any) => void
}
const TimePickerPopup = (props: Props) => {
const { showTime = false, closePopup, start = '', end = '', onSelectDate } = props
return (
<Popup title="选择时间" show={showTime} onClose={() => closePopup?.()}>
<TimePicker start={start} end={end} onSelectDate={onSelectDate}></TimePicker>
</Popup>
)
}
export default memo(TimePickerPopup)