mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
feat:datepicker 移动化 (#3308)
This commit is contained in:
parent
16e3efd619
commit
c0f41f6215
@ -1096,11 +1096,12 @@
|
||||
--PickerColumns-bg: white;
|
||||
--PickerColumns-toolbar-height: #{px2rem(50px)};
|
||||
--PickerColumns-title-fontSize: var(--fontSizeLg);
|
||||
--PickerColumns-title-color: #222;
|
||||
--PickerColumns-title-lineHeight: 1.5;
|
||||
--PickerColumns-action-padding: 0 var(--gap-sm);
|
||||
--PickerColumns-action-fontSize: var(--fontSizeMd);
|
||||
--PickerColumns-confirmAction-color: #{lighten($text-color, 25%)};
|
||||
--PickerColumns-cancelAction-color: #{lighten($text-color, 50%)};
|
||||
--PickerColumns-action-padding: 0 var(--gap-md);
|
||||
--PickerColumns-action-fontSize: var(--fontSizeLg);
|
||||
--PickerColumns-confirmAction-color: #2468F2;
|
||||
--PickerColumns-cancelAction-color: #666;
|
||||
--PickerColumns-option-fontSize: var(--fontSizeLg);
|
||||
--PickerColumns-optionText-color: var(--text-color);
|
||||
--PickerColumns-optionDisabled-opacity: 0.3;
|
||||
|
@ -6,7 +6,11 @@
|
||||
overflow: hidden;
|
||||
font-size: var(--PickerColumns-option-fontSize);
|
||||
|
||||
&-toolbar {
|
||||
li:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@ -18,12 +22,12 @@
|
||||
height: 100%;
|
||||
padding: var(--PickerColumns-action-padding);
|
||||
font-size: var(--PickerColumns-action-fontSize);
|
||||
background-color: transparent;
|
||||
background-color: none !important;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
background-color: none !important;
|
||||
}
|
||||
&:hover {
|
||||
background-color: none !important;
|
||||
@ -44,6 +48,7 @@
|
||||
font-size: var(--PickerColumns-title-fontSize);
|
||||
line-height: var(--PickerColumns-title-lineHeight);
|
||||
text-align: center;
|
||||
color: var(--PickerColumns-title-color);
|
||||
}
|
||||
|
||||
&-columns {
|
||||
@ -127,4 +132,9 @@
|
||||
cursor: not-allowed;
|
||||
opacity: var(--PickerColumns-optionDisabled-opacity);
|
||||
}
|
||||
|
||||
&-columnItemis-selected {
|
||||
font-size: 18px;
|
||||
color: --PickerColumns-title-color;
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +125,18 @@
|
||||
.#{$ns}DatePicker-popup {
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
.#{$ns}DatePicker-popup.#{$ns}DatePicker-mobile {
|
||||
color: red;
|
||||
.rdt {
|
||||
width: 100%;
|
||||
.rdtPicker {
|
||||
width: 100%;
|
||||
padding: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override third-party styles
|
||||
.rdt {
|
||||
user-select: none;
|
||||
|
@ -628,6 +628,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
schedules={schedulesData}
|
||||
largeMode={largeMode}
|
||||
onScheduleClick={onScheduleClick}
|
||||
useMobileUI={useMobileUI}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -703,6 +704,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
locale={locale}
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
useMobileUI={useMobileUI}
|
||||
// utc={utc}
|
||||
/>
|
||||
</PopOver>
|
||||
@ -710,8 +712,9 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
) : null}
|
||||
{useMobileUI && isMobile() ? (
|
||||
<PopUp
|
||||
className={cx(`${ns}DatePicker-popup`)}
|
||||
className={cx(`${ns}DatePicker-popup DatePicker-mobile`)}
|
||||
isShow={isOpened}
|
||||
showClose={false}
|
||||
onHide={this.handleClick}
|
||||
>
|
||||
{this.renderShortCuts(shortcuts)}
|
||||
@ -730,6 +733,8 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
onClose={this.close}
|
||||
locale={locale}
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
useMobileUI={useMobileUI}
|
||||
// utc={utc}
|
||||
/>
|
||||
</PopUp>
|
||||
|
@ -2,7 +2,12 @@
|
||||
* @file Picker
|
||||
* @description 移动端列滚动选择器
|
||||
*/
|
||||
import React, {memo, ReactNode, useState, useEffect} from 'react';
|
||||
import React, {
|
||||
memo,
|
||||
ReactNode,
|
||||
useState,
|
||||
useEffect
|
||||
} from 'react';
|
||||
import {uncontrollable} from 'uncontrollable';
|
||||
|
||||
import {themeable, ThemeProps} from '../theme';
|
||||
@ -16,6 +21,7 @@ export type PickerValue = string | number;
|
||||
export interface PickerProps extends ThemeProps, LocaleProps {
|
||||
title?: String | ReactNode;
|
||||
labelField?: string;
|
||||
valueField?: string;
|
||||
className?: string;
|
||||
showToolbar?: boolean;
|
||||
defaultValue?: PickerValue[];
|
||||
@ -38,12 +44,14 @@ function fixToArray(data: any) {
|
||||
|
||||
const Picker = memo<PickerProps>(props => {
|
||||
const {
|
||||
title,
|
||||
labelField,
|
||||
valueField,
|
||||
visibleItemCount = 5,
|
||||
value = [],
|
||||
swipeDuration = 1000,
|
||||
columns = [],
|
||||
itemHeight = 30,
|
||||
itemHeight = 48,
|
||||
showToolbar = true,
|
||||
className = '',
|
||||
classnames: cx,
|
||||
@ -55,9 +63,11 @@ const Picker = memo<PickerProps>(props => {
|
||||
const [innerValue, setInnerValue] = useState<PickerValue[]>(
|
||||
fixToArray(props.value === undefined ? props.defaultValue || [] : value)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setInnerValue(value);
|
||||
}, [value]);
|
||||
if (value === innerValue) return
|
||||
setInnerValue(fixToArray(value));
|
||||
}, [value])
|
||||
|
||||
const close = () => {
|
||||
if (props.onClose) {
|
||||
@ -90,7 +100,8 @@ const Picker = memo<PickerProps>(props => {
|
||||
{...item}
|
||||
classnames={cx}
|
||||
classPrefix={ns}
|
||||
labelField={labelField}
|
||||
labelField={labelField || item.labelField}
|
||||
valueField={valueField || item.valueField}
|
||||
itemHeight={itemHeight}
|
||||
swipeDuration={swipeDuration}
|
||||
visibleItemCount={visibleItemCount}
|
||||
@ -109,25 +120,29 @@ const Picker = memo<PickerProps>(props => {
|
||||
const maskStyle = {
|
||||
backgroundSize: `100% ${(wrapHeight - itemHeight) / 2}px`
|
||||
};
|
||||
|
||||
const hasHeader = showToolbar || title;
|
||||
return (
|
||||
<div className={cx(className, 'PickerColumns', 'PickerColumns-popOver')}>
|
||||
{showToolbar && (
|
||||
<div className={cx('PickerColumns-toolbar')}>
|
||||
<Button
|
||||
{hasHeader && (<div className={cx('PickerColumns-header')}>
|
||||
{showToolbar && (<Button
|
||||
className="PickerColumns-cancel"
|
||||
level="default"
|
||||
onClick={close}
|
||||
>
|
||||
{__('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
</Button>)}
|
||||
{title && (
|
||||
<div className={cx('PickerColumns-title')}>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
{showToolbar && (<Button
|
||||
className="PickerColumns-confirm"
|
||||
level="primary"
|
||||
onClick={confirm}
|
||||
>
|
||||
{__('confirm')}
|
||||
</Button>
|
||||
</Button>)}
|
||||
</div>
|
||||
)}
|
||||
<div className={cx('PickerColumns-columns')} style={columnsStyle}>
|
||||
|
@ -21,6 +21,7 @@ import useTouch from '../hooks/use-touch';
|
||||
|
||||
export interface PickerColumnItem {
|
||||
labelField?: string;
|
||||
valueField?: string;
|
||||
readonly?: boolean;
|
||||
value?: PickerOption;
|
||||
swipeDuration?: number;
|
||||
@ -68,8 +69,9 @@ function isOptionDisabled(option: PickerOption) {
|
||||
const PickerColumn = forwardRef<{}, PickerColumnProps>((props, ref) => {
|
||||
const {
|
||||
visibleItemCount = 5,
|
||||
itemHeight = 30,
|
||||
itemHeight = 48,
|
||||
value,
|
||||
valueField = 'value',
|
||||
swipeDuration = 1000,
|
||||
labelField = 'text',
|
||||
options = [],
|
||||
@ -88,7 +90,24 @@ const PickerColumn = forwardRef<{}, PickerColumnProps>((props, ref) => {
|
||||
|
||||
const touch = useTouch();
|
||||
const count = options.length;
|
||||
const defaultIndex = options.findIndex(item => item === value);
|
||||
|
||||
const getOptionText = (option: [] | PickerOption) => {
|
||||
if (isObject(option) && labelField in option) {
|
||||
//@ts-ignore
|
||||
return option[labelField];
|
||||
}
|
||||
return option;
|
||||
};
|
||||
|
||||
const getOptionValue = (option: [] | PickerOption) => {
|
||||
if (isObject(option) && valueField in option) {
|
||||
//@ts-ignore
|
||||
return option[valueField];
|
||||
}
|
||||
return option;
|
||||
};
|
||||
|
||||
const defaultIndex = options.findIndex(item => getOptionValue(item) === value);
|
||||
|
||||
const baseOffset = useMemo(() => {
|
||||
// 默认转入第一个选项的位置
|
||||
@ -132,12 +151,11 @@ const PickerColumn = forwardRef<{}, PickerColumnProps>((props, ref) => {
|
||||
updateState({index});
|
||||
|
||||
if (emitChange && props.onChange) {
|
||||
requestAnimationFrame(() => {
|
||||
props.onChange?.(options[index], index, confirm);
|
||||
});
|
||||
// setTimeout(() => {
|
||||
// props.onChange?.(options[index], index, confirm);
|
||||
// }, 0);
|
||||
requestAnimationFrame(
|
||||
() => {
|
||||
props.onChange?.(getOptionValue(options[index]), index, confirm);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -154,7 +172,7 @@ const PickerColumn = forwardRef<{}, PickerColumnProps>((props, ref) => {
|
||||
const setOptions = (options: Array<PickerOption>) => {
|
||||
if (JSON.stringify(options) !== JSON.stringify(state.options)) {
|
||||
updateState({options});
|
||||
const index = options.findIndex(item => item === value) || 0;
|
||||
const index = options.findIndex(item => getOptionValue(item) === value) || 0;
|
||||
setIndex(index, true, true);
|
||||
}
|
||||
};
|
||||
@ -168,14 +186,6 @@ const PickerColumn = forwardRef<{}, PickerColumnProps>((props, ref) => {
|
||||
setIndex(index, true, true);
|
||||
};
|
||||
|
||||
const getOptionText = (option: [] | PickerOption) => {
|
||||
if (isObject(option) && labelField in option) {
|
||||
//@ts-ignore
|
||||
return option[labelField];
|
||||
}
|
||||
return option;
|
||||
};
|
||||
|
||||
const getIndexByOffset = (offset: number) =>
|
||||
range(Math.round(-offset / itemHeight), 0, count - 1);
|
||||
|
||||
@ -379,7 +389,7 @@ PickerColumn.defaultProps = {
|
||||
options: [],
|
||||
visibleItemCount: 5,
|
||||
swipeDuration: 1000,
|
||||
itemHeight: 30
|
||||
itemHeight: 48
|
||||
};
|
||||
|
||||
export default themeable(
|
||||
|
@ -7,7 +7,23 @@ import CustomCalendarContainer from './CalendarContainer';
|
||||
import cx from 'classnames';
|
||||
import moment from 'moment';
|
||||
import {themeable, ThemeOutterProps, ThemeProps} from '../../theme';
|
||||
import {convertDateArrayToDate} from '../../utils/helper';
|
||||
import {convertArrayValueToMoment, getRange} from "../../utils/helper";
|
||||
import {PickerOption} from '../PickerColumn';
|
||||
|
||||
export type DateType = 'year' | 'month' | 'date' | 'hours' | 'minutes' | 'seconds';
|
||||
export interface BoundaryObject {
|
||||
max: number;
|
||||
min: number;
|
||||
}
|
||||
|
||||
export interface DateBoundary {
|
||||
year: BoundaryObject;
|
||||
month: BoundaryObject;
|
||||
date: BoundaryObject;
|
||||
hours: BoundaryObject;
|
||||
minutes: BoundaryObject;
|
||||
seconds: BoundaryObject;
|
||||
}
|
||||
|
||||
interface BaseDatePickerProps
|
||||
extends Omit<ReactDatePicker.DatetimepickerProps, 'viewMode'> {
|
||||
@ -50,6 +66,16 @@ class BaseDatePicker extends ReactDatePicker {
|
||||
setState: (state: any) => void;
|
||||
getStateFromProps: any;
|
||||
|
||||
timeCellLength = {
|
||||
year: 4,
|
||||
month: 2,
|
||||
date: 2,
|
||||
hours: 2,
|
||||
minutes: 2,
|
||||
seconds: 2,
|
||||
milliseconds: 3
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const state = this.getStateFromProps(this.props);
|
||||
@ -104,7 +130,9 @@ class BaseDatePicker extends ReactDatePicker {
|
||||
'hideHeader',
|
||||
'updateOn',
|
||||
'useMobileUI',
|
||||
'showToolbar'
|
||||
'showToolbar',
|
||||
'embed',
|
||||
'onScheduleClick'
|
||||
].forEach(key => (props[key] = (this.props as any)[key]));
|
||||
|
||||
return props;
|
||||
@ -202,14 +230,58 @@ class BaseDatePicker extends ReactDatePicker {
|
||||
that.props.onChange(date);
|
||||
};
|
||||
|
||||
onConfirm = (value: number[], types: string[]) => {
|
||||
const currentDate = (
|
||||
this.state.selectedDate ||
|
||||
this.state.viewDate ||
|
||||
moment()
|
||||
).clone();
|
||||
getDateBoundary = (currentDate: moment.Moment) => {
|
||||
const {years, months} = currentDate.toObject();
|
||||
const maxDateObject = this.props.maxDate?.toObject();
|
||||
const minDateObject = this.props.minDate?.toObject();
|
||||
|
||||
const date = convertDateArrayToDate(value, types, currentDate);
|
||||
const yearBoundary = {
|
||||
max: maxDateObject ? maxDateObject.years : years + 100,
|
||||
min: minDateObject ? minDateObject.years : years - 100,
|
||||
};
|
||||
const monthBoundary = {
|
||||
max: years === maxDateObject?.years ? maxDateObject.months : 11,
|
||||
min: years === minDateObject?.years ? minDateObject.months : 0
|
||||
};
|
||||
const dateBoundary = {
|
||||
max: years === maxDateObject?.years && months === maxDateObject?.months ? maxDateObject.date : currentDate.daysInMonth(),
|
||||
min: years === minDateObject?.years && months === minDateObject?.months ? minDateObject.date : 1
|
||||
}
|
||||
return {
|
||||
year: yearBoundary,
|
||||
month: monthBoundary,
|
||||
date: dateBoundary,
|
||||
hours: {max: 23, min: 0},
|
||||
minutes: {max: 59, min: 0},
|
||||
seconds: {max: 59, min: 0}
|
||||
};
|
||||
};
|
||||
|
||||
timeCell = (value: number, type: DateType) => {
|
||||
let str = value + '';
|
||||
while (str.length < this.timeCellLength[type])
|
||||
str = '0' + str;
|
||||
return str;
|
||||
};
|
||||
|
||||
getColumns = (types: DateType[], dateBoundary: DateBoundary) => {
|
||||
const columns: { options: PickerOption[] }[] = [];
|
||||
types.map((type: DateType) => {
|
||||
const options = getRange(dateBoundary[type].min, dateBoundary[type].max, 1).map(item => {
|
||||
|
||||
return {
|
||||
text: type === 'month' ? this.timeCell(item+1, type) : this.timeCell(item, type),
|
||||
value: item
|
||||
};
|
||||
});
|
||||
columns.push({options})
|
||||
});
|
||||
return columns;
|
||||
};
|
||||
|
||||
onConfirm = (value: number[], types: string[]) => {
|
||||
const currentDate = (this.state.selectedDate || this.state.viewDate || moment()).clone();
|
||||
const date = convertArrayValueToMoment(value, types, currentDate);
|
||||
|
||||
if (!this.props.value) {
|
||||
this.setState({
|
||||
@ -221,6 +293,7 @@ class BaseDatePicker extends ReactDatePicker {
|
||||
this.props.onClose && this.props.onClose();
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
const Component = CustomCalendarContainer as any;
|
||||
const viewProps = this.getComponentProps();
|
||||
@ -239,6 +312,9 @@ class BaseDatePicker extends ReactDatePicker {
|
||||
}
|
||||
|
||||
viewProps.onConfirm = this.onConfirm;
|
||||
viewProps.getDateBoundary = this.getDateBoundary;
|
||||
viewProps.getColumns = this.getColumns;
|
||||
viewProps.timeCell = this.timeCell;
|
||||
|
||||
return (
|
||||
<div className={cx('rdt rdtStatic rdtOpen', this.props.className)}>
|
||||
|
@ -3,9 +3,13 @@ import moment from 'moment';
|
||||
import DaysView from 'react-datetime/src/DaysView';
|
||||
import React from 'react';
|
||||
import Downshift from 'downshift';
|
||||
import find from 'lodash/find';
|
||||
import {LocaleProps, localeable} from '../../locale';
|
||||
import {ClassNamesFn} from '../../theme';
|
||||
import find from 'lodash/find';
|
||||
import {isMobile, convertArrayValueToMoment} from "../../utils/helper";
|
||||
import Picker from '../Picker';
|
||||
import {PickerOption} from '../PickerColumn';
|
||||
import {DateType} from './Calendar';
|
||||
|
||||
interface CustomDaysViewProps extends LocaleProps {
|
||||
classPrefix?: string;
|
||||
@ -14,12 +18,16 @@ interface CustomDaysViewProps extends LocaleProps {
|
||||
viewDate: moment.Moment;
|
||||
selectedDate: moment.Moment;
|
||||
minDate: moment.Moment;
|
||||
maxDate: moment.Moment;
|
||||
useMobileUI: boolean;
|
||||
embed: boolean;
|
||||
timeFormat: string;
|
||||
requiredConfirm?: boolean;
|
||||
isEndDate?: boolean;
|
||||
renderDay?: Function;
|
||||
onClose?: () => void;
|
||||
onChange: (value: moment.Moment) => void;
|
||||
onConfirm?: (value: number[], types: DateType[]) => void;
|
||||
setDateTimeState: (state: any) => void;
|
||||
setTime: (type: string, amount: number) => void;
|
||||
subtractTime: (
|
||||
@ -49,13 +57,44 @@ interface CustomDaysViewProps extends LocaleProps {
|
||||
largeMode?: boolean;
|
||||
onScheduleClick?: (scheduleData: any) => void;
|
||||
hideHeader?: boolean;
|
||||
getColumns: (types: DateType[], dateBoundary: void) => any;
|
||||
getDateBoundary: (currentDate: moment.Moment) => any;
|
||||
}
|
||||
|
||||
export class CustomDaysView extends DaysView {
|
||||
props: CustomDaysViewProps;
|
||||
state: { columns: { options: PickerOption[] }[]; types: DateType[]; pickerValue: number[]};
|
||||
setState: (arg0: any) => () => any;
|
||||
getDaysOfWeek: (locale: any) => any;
|
||||
renderDays: () => JSX.Element;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
const {selectedDate, viewDate, timeFormat} = props;
|
||||
const currentDate = (selectedDate || viewDate || moment());
|
||||
|
||||
const types: DateType[] = ['year', 'month', 'date'];
|
||||
timeFormat.split(':').forEach((format: string) => {
|
||||
const type: DateType | '' = /h/i.test(format)
|
||||
? 'hours'
|
||||
: /m/.test(format)
|
||||
? 'minutes'
|
||||
: /s/.test(format)
|
||||
? 'seconds'
|
||||
: '';
|
||||
type && types.push(type)
|
||||
});
|
||||
|
||||
const dateBoundary = this.props.getDateBoundary(currentDate);
|
||||
const columns = this.props.getColumns(types, dateBoundary);
|
||||
this.state = {
|
||||
columns,
|
||||
types,
|
||||
pickerValue: currentDate.toArray()
|
||||
}
|
||||
}
|
||||
|
||||
updateSelectedDate = (event: React.MouseEvent<any>) => {
|
||||
// need confirm
|
||||
if (this.props.requiredConfirm) {
|
||||
@ -405,11 +444,56 @@ export class CustomDaysView extends DaysView {
|
||||
);
|
||||
};
|
||||
|
||||
onPickerConfirm = (value: number[]) => {
|
||||
this.props.onConfirm && this.props.onConfirm(value, this.state.types);
|
||||
}
|
||||
|
||||
onPickerChange = (value: number[], index: number) => {
|
||||
const {selectedDate, viewDate} = this.props;
|
||||
|
||||
// 变更年份、月份的时候,需要更新columns
|
||||
if (index === 1 || index === 0) {
|
||||
const currentDate = (selectedDate || viewDate || moment()).clone();
|
||||
|
||||
// 只需计算year 、month
|
||||
const selectDate = convertArrayValueToMoment(value, ['year', 'month'], currentDate);
|
||||
const dateBoundary = this.props.getDateBoundary(selectDate);
|
||||
this.setState({
|
||||
columns: this.props.getColumns(this.state.types, dateBoundary),
|
||||
pickerValue: value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderPicker = () => {
|
||||
const {translate: __} = this.props;
|
||||
const title = this.state.types.length > 3 ? __('Date.titleTime') : __('Date.titleDate');
|
||||
return (
|
||||
<Picker
|
||||
translate={this.props.translate}
|
||||
locale={this.props.locale}
|
||||
title={title}
|
||||
columns={this.state.columns}
|
||||
value={this.state.pickerValue}
|
||||
onChange={this.onPickerChange}
|
||||
onConfirm={this.onPickerConfirm}
|
||||
onClose={this.cancel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {viewDate: date, useMobileUI, embed} = this.props;
|
||||
const footer = this.renderFooter();
|
||||
const date = this.props.viewDate;
|
||||
const locale = date.localeData();
|
||||
const __ = this.props.translate;
|
||||
if (isMobile() && useMobileUI && !embed) {
|
||||
return (
|
||||
<div className="rdtYears">
|
||||
{this.renderPicker()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const tableChildren = [
|
||||
this.props.hideHeader ? null : <thead key="th">
|
||||
|
@ -4,7 +4,9 @@ import moment from 'moment';
|
||||
import React from 'react';
|
||||
import {LocaleProps, localeable, TranslateFn} from '../../locale';
|
||||
import Picker from '../Picker';
|
||||
import {convertDateToObject, getRange, isMobile} from '../../utils/helper';
|
||||
import {PickerOption} from '../PickerColumn';
|
||||
import {getRange, isMobile} from '../../utils/helper';
|
||||
import {DateType} from './Calendar';
|
||||
|
||||
export interface OtherProps {
|
||||
inputFormat?: string;
|
||||
@ -31,52 +33,31 @@ export class CustomMonthsView extends MonthsView {
|
||||
onChange?: () => void;
|
||||
onClose?: () => void;
|
||||
onConfirm?: (value: number[], types: string[]) => void;
|
||||
getColumns: (types: DateType[], dateBoundary: void) => any;
|
||||
timeCell: (value: number, type: DateType) => string;
|
||||
getDateBoundary: (currentDate: moment.Moment) => any;
|
||||
useMobileUI: boolean;
|
||||
} & LocaleProps &
|
||||
OtherProps;
|
||||
maxDateObject: {year: number; month: number; day?: number};
|
||||
minDateObject: {year: number; month: number; day?: number};
|
||||
state: {columns: {options: number[]}[]};
|
||||
state: { columns: { options: PickerOption[] }[]; pickerValue: number[]};
|
||||
setState: (arg0: any) => () => any;
|
||||
renderMonths: () => JSX.Element;
|
||||
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
const {minDate, maxDate, selectedDate, viewDate} = props;
|
||||
const currentDate = selectedDate || viewDate || moment();
|
||||
const year = currentDate.year();
|
||||
this.maxDateObject = maxDate
|
||||
? convertDateToObject(maxDate)
|
||||
: {
|
||||
year: year + 100,
|
||||
month: 12
|
||||
};
|
||||
this.minDateObject = minDate
|
||||
? convertDateToObject(minDate)
|
||||
: {
|
||||
year: year - 100,
|
||||
month: 1
|
||||
};
|
||||
|
||||
const columns = ['year', 'month'].map((type: 'year' | 'month') => {
|
||||
if (type === 'month') {
|
||||
const minMonth =
|
||||
year === this.minDateObject.year ? this.minDateObject.month : 1;
|
||||
const maxMonth =
|
||||
year === this.maxDateObject.year ? this.maxDateObject.month : 12;
|
||||
return {
|
||||
options: getRange(minMonth, maxMonth, 1)
|
||||
};
|
||||
}
|
||||
return {
|
||||
options: getRange(this.minDateObject[type], this.maxDateObject[type], 1)
|
||||
};
|
||||
});
|
||||
const {selectedDate, viewDate} = props;
|
||||
const currentDate = (selectedDate || viewDate || moment());
|
||||
|
||||
const dateBoundary = this.props.getDateBoundary(currentDate);
|
||||
const columns = this.props.getColumns(['year', 'month'], dateBoundary);
|
||||
this.state = {
|
||||
columns
|
||||
};
|
||||
columns,
|
||||
pickerValue: currentDate.toArray()
|
||||
}
|
||||
}
|
||||
|
||||
renderMonth = (props: any, month: number) => {
|
||||
@ -96,55 +77,66 @@ export class CustomMonthsView extends MonthsView {
|
||||
};
|
||||
|
||||
onConfirm = (value: number[]) => {
|
||||
// 将月份的值减1 ,月份是0-11
|
||||
value[1] && --value[1];
|
||||
this.props.onConfirm && this.props.onConfirm(value, ['year', 'month']);
|
||||
};
|
||||
|
||||
onPickerChange = (value: number[], index: number) => {
|
||||
const {maxDate, minDate} = this.props;
|
||||
const year = moment().year();
|
||||
const columns = [...this.state.columns];
|
||||
const maxDateObject = maxDate
|
||||
? maxDate.toObject()
|
||||
: {
|
||||
years: year + 100,
|
||||
months: 11
|
||||
};
|
||||
const minDateObject = minDate
|
||||
? minDate.toObject()
|
||||
: {
|
||||
years: year - 100,
|
||||
months: 0
|
||||
};
|
||||
let range = [];
|
||||
// 选择年份是最大值的年或者最小值的月时,需要重新计算月分选择的cloumn
|
||||
if (index === 0) {
|
||||
if (
|
||||
value[0] === this.minDateObject.year &&
|
||||
value[0] === this.maxDateObject.year
|
||||
value[0] === minDateObject.years &&
|
||||
value[0] === maxDateObject.years
|
||||
) {
|
||||
columns[1] = {
|
||||
options: getRange(
|
||||
this.minDateObject.month,
|
||||
this.maxDateObject.month,
|
||||
1
|
||||
)
|
||||
};
|
||||
} else if (value[0] === this.minDateObject.year) {
|
||||
columns[1] = {
|
||||
options: getRange(this.minDateObject.month, 12, 1)
|
||||
};
|
||||
} else if (value[0] === this.maxDateObject.year) {
|
||||
columns[1] = {
|
||||
options: getRange(1, this.maxDateObject.month, 1)
|
||||
};
|
||||
} else {
|
||||
columns[1] = {
|
||||
options: getRange(1, 12, 1)
|
||||
};
|
||||
range = getRange(minDateObject.months, maxDateObject.months, 1);
|
||||
}
|
||||
this.setState({columns});
|
||||
else if (value[0] === minDateObject.years) {
|
||||
range = getRange(minDateObject.months, 11, 1);
|
||||
}
|
||||
else if (value[0] === maxDateObject.years) {
|
||||
range = getRange(0, maxDateObject.months, 1);
|
||||
}
|
||||
else {
|
||||
range = getRange(0, 11, 1);
|
||||
}
|
||||
columns[1] = {
|
||||
options: range.map(i => {
|
||||
return {
|
||||
text: this.props.timeCell(i+1, 'month'),
|
||||
value: i
|
||||
};
|
||||
})
|
||||
};
|
||||
this.setState({columns, pickerValue: value});
|
||||
}
|
||||
};
|
||||
|
||||
renderPicker = () => {
|
||||
const {selectedDate, viewDate} = this.props;
|
||||
const currentDate = selectedDate || viewDate || moment();
|
||||
const year = currentDate.year();
|
||||
const month = parseInt(currentDate.format('MM'), 10);
|
||||
const {translate: __} = this.props;
|
||||
const title = __('Date.titleMonth');
|
||||
|
||||
return (
|
||||
<Picker
|
||||
translate={this.props.translate}
|
||||
locale={this.props.locale}
|
||||
title={title}
|
||||
columns={this.state.columns}
|
||||
value={[year, month]}
|
||||
value={this.state.pickerValue}
|
||||
onChange={this.onPickerChange}
|
||||
onConfirm={this.onConfirm}
|
||||
onClose={this.props.onClose}
|
||||
@ -156,6 +148,7 @@ export class CustomMonthsView extends MonthsView {
|
||||
const __ = this.props.translate;
|
||||
const showYearHead = !/^mm$/i.test(this.props.inputFormat || '') && !this.props.hideHeader;
|
||||
const canClick = /yy/i.test(this.props.inputFormat || '');
|
||||
|
||||
if (isMobile() && this.props.useMobileUI) {
|
||||
return <div className="rdtYears">{this.renderPicker()}</div>;
|
||||
}
|
||||
|
@ -9,35 +9,44 @@ import Picker from '../Picker';
|
||||
import {PickerColumnItem} from '../PickerColumn';
|
||||
import {getRange, isMobile} from '../../utils/helper';
|
||||
|
||||
interface State {
|
||||
daypart: any;
|
||||
counters: Array<string>;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
interface CustomTimeViewProps {
|
||||
viewDate: moment.Moment;
|
||||
selectedDate: moment.Moment;
|
||||
subtractTime: (
|
||||
amount: number,
|
||||
type: string,
|
||||
toSelected?: moment.Moment
|
||||
) => () => void;
|
||||
addTime: (
|
||||
amount: number,
|
||||
type: string,
|
||||
toSelected?: moment.Moment
|
||||
) => () => void;
|
||||
showView: (view: string) => () => void;
|
||||
timeFormat: string;
|
||||
classnames: ClassNamesFn;
|
||||
setTime: (type: string, value: any) => void;
|
||||
onClose?: () => void;
|
||||
onConfirm?: (value: number[], types: string[]) => void;
|
||||
useMobileUI: boolean;
|
||||
showToolbar?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
};
|
||||
|
||||
export class CustomTimeView extends TimeView {
|
||||
props: {
|
||||
viewDate: moment.Moment;
|
||||
subtractTime: (
|
||||
amount: number,
|
||||
type: string,
|
||||
toSelected?: moment.Moment
|
||||
) => () => void;
|
||||
addTime: (
|
||||
amount: number,
|
||||
type: string,
|
||||
toSelected?: moment.Moment
|
||||
) => () => void;
|
||||
showView: (view: string) => () => void;
|
||||
timeFormat: string;
|
||||
classnames: ClassNamesFn;
|
||||
setTime: (type: string, value: any) => void;
|
||||
onClose?: () => void;
|
||||
onConfirm?: (value: number[], types: string[]) => void;
|
||||
useMobileUI: boolean;
|
||||
showToolbar?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
} & LocaleProps;
|
||||
props: CustomTimeViewProps & LocaleProps;
|
||||
onStartClicking: any;
|
||||
disableContextMenu: any;
|
||||
updateMilli: any;
|
||||
renderHeader: any;
|
||||
pad: any;
|
||||
state: {daypart: any; counters: Array<string>; [propName: string]: any};
|
||||
state: State;
|
||||
timeConstraints: any;
|
||||
padValues = {
|
||||
hours: 2,
|
||||
@ -45,11 +54,22 @@ export class CustomTimeView extends TimeView {
|
||||
seconds: 2,
|
||||
milliseconds: 3
|
||||
};
|
||||
setState: (arg0: any) => () => any;
|
||||
calculateState: (props: CustomTimeViewProps) => () => any;
|
||||
|
||||
static defaultProps = {
|
||||
showToolbar: true
|
||||
};
|
||||
|
||||
|
||||
componentWillReceiveProps(nextProps: CustomTimeViewProps) {
|
||||
if (nextProps.viewDate !== this.props.viewDate
|
||||
|| nextProps.selectedDate !== this.props.selectedDate
|
||||
|| nextProps.timeFormat !== this.props.timeFormat) {
|
||||
this.setState(this.calculateState(nextProps));
|
||||
}
|
||||
}
|
||||
|
||||
renderDayPart = () => {
|
||||
const {translate: __, classnames: cx} = this.props;
|
||||
return (
|
||||
@ -154,8 +174,22 @@ export class CustomTimeView extends TimeView {
|
||||
return null;
|
||||
};
|
||||
|
||||
onConfirm = (value: number[]) => {
|
||||
this.props.onConfirm && this.props.onConfirm(value, this.state.counters);
|
||||
onConfirm = (value: (number | string)[]) => {
|
||||
// 修正am、pm
|
||||
const hourIndex = this.state.counters.indexOf('hours');
|
||||
if (
|
||||
hourIndex !== -1 &&
|
||||
this.state.daypart !== false &&
|
||||
this.props.timeFormat.toLowerCase().indexOf(' a') !== -1
|
||||
) {
|
||||
const amMode: string = value.splice(-1, 1)[0] as string;
|
||||
let hour = (value[hourIndex] as number) % 12;
|
||||
// 修正pm
|
||||
amMode.toLowerCase().indexOf('p') !== -1 && (hour = hour + 12);
|
||||
value[hourIndex] = hour;
|
||||
}
|
||||
|
||||
this.props.onConfirm && this.props.onConfirm(value as number[], this.state.counters);
|
||||
};
|
||||
|
||||
getDayPartOptions = () => {
|
||||
@ -171,13 +205,25 @@ export class CustomTimeView extends TimeView {
|
||||
}));
|
||||
};
|
||||
|
||||
onPickerChange = (value: (number | string)[], index: number) => {
|
||||
const time: {[prop:string]: any} = {};
|
||||
this.state.counters.forEach((type, i) => time[type] = value[i]);
|
||||
if (this.state.daypart !== false && index > this.state.counters.length -1) {
|
||||
time.daypart = value[value.length -1];
|
||||
}
|
||||
this.setState((prevState: State) => {
|
||||
return {...prevState, ...time}
|
||||
});
|
||||
}
|
||||
|
||||
renderTimeViewPicker = () => {
|
||||
const {translate: __} = this.props;
|
||||
const title = __('Date.titleTime');
|
||||
const columns: PickerColumnItem[] = [];
|
||||
const values = [];
|
||||
|
||||
this.state.counters.forEach(type => {
|
||||
if (type !== 'daypart') {
|
||||
const counterValue: number = this.getCounterValue(type);
|
||||
let {min, max, step} = this.timeConstraints[type];
|
||||
// 修正am pm时hours可选最大值
|
||||
if (
|
||||
@ -188,9 +234,14 @@ export class CustomTimeView extends TimeView {
|
||||
max = max > 12 ? 12 : max;
|
||||
}
|
||||
columns.push({
|
||||
options: getRange(min, max, step)
|
||||
options: getRange(min, max, step).map(item => {
|
||||
return {
|
||||
text: this.pad(type, item),
|
||||
value: item
|
||||
}
|
||||
})
|
||||
});
|
||||
values.push(counterValue);
|
||||
values.push(parseInt(this.state[type], 10));
|
||||
}
|
||||
});
|
||||
if (this.state.daypart !== false) {
|
||||
@ -204,12 +255,13 @@ export class CustomTimeView extends TimeView {
|
||||
<Picker
|
||||
translate={this.props.translate}
|
||||
locale={this.props.locale}
|
||||
title={title}
|
||||
columns={columns}
|
||||
value={values}
|
||||
onConfirm={this.onConfirm}
|
||||
onClose={this.props.onClose}
|
||||
showToolbar={this.props.showToolbar}
|
||||
onChange={this.props.onChange}
|
||||
onChange={this.onPickerChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import moment from 'moment';
|
||||
import React from 'react';
|
||||
import {LocaleProps, localeable} from '../../locale';
|
||||
import Picker from '../Picker';
|
||||
import {convertDateToObject, getRange, isMobile} from '../../utils/helper';
|
||||
import {getRange, isMobile} from '../../utils/helper';
|
||||
|
||||
export class CustomYearsView extends YearsView {
|
||||
props: {
|
||||
@ -28,6 +28,8 @@ export class CustomYearsView extends YearsView {
|
||||
onConfirm?: (value: number[], types: string[]) => void;
|
||||
useMobileUI: boolean;
|
||||
} & LocaleProps;
|
||||
state: {pickerValue: number[]};
|
||||
setState: (arg0: any) => () => any;
|
||||
renderYears: (year: number) => JSX.Element;
|
||||
renderYear = (props: any, year: number) => {
|
||||
return (
|
||||
@ -37,15 +39,31 @@ export class CustomYearsView extends YearsView {
|
||||
);
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
const {selectedDate, viewDate} = props;
|
||||
const currentDate = (selectedDate || viewDate || moment());
|
||||
|
||||
this.state = {
|
||||
pickerValue: currentDate.toObject().years
|
||||
}
|
||||
}
|
||||
|
||||
onConfirm = (value: number[]) => {
|
||||
this.props.onConfirm && this.props.onConfirm(value, ['year']);
|
||||
};
|
||||
|
||||
onPickerChange = (value: number[]) => {
|
||||
this.setState({pickerValue: value[0]});
|
||||
}
|
||||
|
||||
renderYearPicker = () => {
|
||||
const {minDate, maxDate, selectedDate, viewDate} = this.props;
|
||||
const {translate: __, minDate, maxDate, selectedDate, viewDate} = this.props;
|
||||
const year = (selectedDate || viewDate || moment()).year();
|
||||
const maxYear = maxDate ? convertDateToObject(maxDate)!.year : year + 100;
|
||||
const minYear = minDate ? convertDateToObject(minDate)!.year : year - 100;
|
||||
const maxYear = maxDate ? maxDate.toObject().years : year + 100;
|
||||
const minYear = minDate ? minDate.toObject().years : year - 100;
|
||||
const title = __('Date.titleYear');
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@ -57,9 +75,11 @@ export class CustomYearsView extends YearsView {
|
||||
<Picker
|
||||
translate={this.props.translate}
|
||||
locale={this.props.locale}
|
||||
title={title}
|
||||
columns={columns}
|
||||
value={[year]}
|
||||
value={this.state.pickerValue}
|
||||
onConfirm={this.onConfirm}
|
||||
onChange={this.onPickerChange}
|
||||
onClose={this.props.onClose}
|
||||
/>
|
||||
);
|
||||
@ -69,7 +89,6 @@ export class CustomYearsView extends YearsView {
|
||||
let year = this.props.viewDate.year();
|
||||
year = year - (year % 10);
|
||||
const __ = this.props.translate;
|
||||
|
||||
if (isMobile() && this.props.useMobileUI) {
|
||||
return <div className="rdtYears">{this.renderYearPicker()}</div>;
|
||||
}
|
||||
|
@ -43,6 +43,10 @@ register('de-DE', {
|
||||
'CRUD.paginationGoText': 'Wechseln zu',
|
||||
'CRUD.paginationPageText': 'Seite',
|
||||
'PaginationWrapper.placeholder': 'Textkörper konfigurieren',
|
||||
'Date.titleYear': '',
|
||||
'Date.titleMonth': '',
|
||||
'Date.titleDate': '',
|
||||
'Date.titleTime': '',
|
||||
'Date.daysago': 'Vor {{days}} Tag(en)',
|
||||
'Date.dayslater': '{{days}} Tag(e) später',
|
||||
'Date.endOfMonth': 'Letzter Tag des Monats',
|
||||
|
@ -43,6 +43,10 @@ register('en-US', {
|
||||
'CRUD.paginationGoText': 'Go to',
|
||||
'CRUD.paginationPageText': 'page',
|
||||
'PaginationWrapper.placeholder': 'please config body',
|
||||
'Date.titleYear': 'select year',
|
||||
'Date.titleMonth': 'select month and year',
|
||||
'Date.titleDate': 'select month, year and day',
|
||||
'Date.titleTime': 'select time',
|
||||
'Date.daysago': '{{days}} day(s) ago',
|
||||
'Date.dayslater': '{{days}} day(s) later',
|
||||
'Date.endOfMonth': 'last day of the month',
|
||||
|
@ -43,6 +43,10 @@ register('zh-CN', {
|
||||
'CRUD.paginationGoText': '前往',
|
||||
'CRUD.paginationPageText': '页',
|
||||
'PaginationWrapper.placeholder': '请配置内容',
|
||||
'Date.titleYear': '选择年',
|
||||
'Date.titleMonth': '选择年月',
|
||||
'Date.titleDate': '选择年月日',
|
||||
'Date.titleTime': '选择时间',
|
||||
'Date.daysago': '{{days}}天前',
|
||||
'Date.dayslater': '{{days}}天后',
|
||||
'Date.endOfMonth': '本月最后一天',
|
||||
|
@ -1585,30 +1585,20 @@ export function JSONTraverse(
|
||||
});
|
||||
}
|
||||
|
||||
export function convertDateArrayToDate(
|
||||
export function convertArrayValueToMoment(
|
||||
value: number[],
|
||||
types: string[],
|
||||
date: moment.Moment
|
||||
): moment.Moment | null {
|
||||
if (value.length === 0) return date;
|
||||
mom: moment.Moment
|
||||
): moment.Moment {
|
||||
if (value.length === 0) return mom;
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
const type = types[i];
|
||||
// @ts-ignore
|
||||
date[type](value[i]);
|
||||
mom.set(type, value[i]);
|
||||
}
|
||||
return date;
|
||||
return mom;
|
||||
}
|
||||
|
||||
export function convertDateToObject(value: moment.Moment) {
|
||||
if (value) {
|
||||
return {
|
||||
year: value.year(),
|
||||
month: parseInt(value.format('MM'), 10),
|
||||
day: parseInt(value.format('DD'), 10)
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function getRange(min: number, max: number, step: number = 1) {
|
||||
const arr = [];
|
||||
|
Loading…
Reference in New Issue
Block a user