feat:datepicker 移动化 (#3308)

This commit is contained in:
ucasliyuan 2021-12-30 17:24:10 +08:00 committed by GitHub
parent 16e3efd619
commit c0f41f6215
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 442 additions and 163 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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}>

View File

@ -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(

View File

@ -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)}>

View File

@ -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">

View File

@ -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>;
}

View File

@ -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}
/>
);
};

View File

@ -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>;
}

View File

@ -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',

View File

@ -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',

View File

@ -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': '本月最后一天',

View File

@ -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 = [];