From ed528e68b74da7eff8b8af7242197b05d5f8c5f9 Mon Sep 17 00:00:00 2001 From: tanjinzhou <415800467@qq.com> Date: Wed, 29 Apr 2020 18:44:11 +0800 Subject: [PATCH] feat: datepicker timepicker calendar support string value #718 --- components/_util/moment-util.js | 75 +++++++++++++++++++++++++++ components/calendar/index.jsx | 36 ++++++------- components/date-picker/interface.js | 40 +++++--------- components/date-picker/wrapPicker.js | 47 +++++++++-------- components/time-picker/index.jsx | 43 +++++++-------- components/vc-calendar/src/Picker.jsx | 27 +++++----- types/calendar.d.ts | 5 +- types/date-picker/common.d.ts | 1 + types/date-picker/date-picker.d.ts | 6 +-- types/date-picker/month-picker.d.ts | 6 +-- types/date-picker/range-picker.d.ts | 6 +-- types/date-picker/week-picker.d.ts | 6 +-- types/time-picker.d.ts | 4 +- 13 files changed, 184 insertions(+), 118 deletions(-) create mode 100644 components/_util/moment-util.js diff --git a/components/_util/moment-util.js b/components/_util/moment-util.js new file mode 100644 index 000000000..163effc36 --- /dev/null +++ b/components/_util/moment-util.js @@ -0,0 +1,75 @@ +import interopDefault from './interopDefault'; +import * as moment from 'moment'; +import warning from './warning'; +import isNil from 'lodash/isNil'; + +export const TimeType = { + validator(value) { + return typeof value === 'string' || isNil(value) || moment.isMoment(value); + }, +}; + +export const TimesType = { + validator(value) { + if (Array.isArray(value)) { + return ( + value.length === 0 || + value.findIndex(val => typeof val !== 'string') === -1 || + value.findIndex(val => !isNil(val) && !moment.isMoment(val)) === -1 + ); + } + return false; + }, +}; + +export const TimeOrTimesType = { + validator(value) { + if (Array.isArray(value)) { + return ( + value.length === 0 || + value.findIndex(val => typeof val !== 'string') === -1 || + value.findIndex(val => !isNil(val) && !moment.isMoment(val)) === -1 + ); + } else { + return typeof value === 'string' || isNil(value) || moment.isMoment(value); + } + }, +}; + +export function checkValidate(componentName, value, propName, valueFormat) { + const values = Array.isArray(value) ? value : [value]; + values.forEach(val => { + if (!val) return; + valueFormat && + warning( + interopDefault(moment)(val, valueFormat).isValid(), + componentName, + `When set \`valueFormat\`, \`${propName}\` should provides invalidate string time. `, + ); + !valueFormat && + warning( + interopDefault(moment).isMoment(val) && val.isValid(), + componentName, + `\`${propName}\` provides invalidate moment time. If you want to set empty value, use \`null\` instead.`, + ); + }); +} +export const stringToMoment = (value, valueFormat) => { + if (Array.isArray(value)) { + return value.map(val => + typeof val === 'string' && val ? interopDefault(moment)(val, valueFormat) : val || null, + ); + } else { + return typeof value === 'string' && value + ? interopDefault(moment)(value, valueFormat) + : value || null; + } +}; + +export const momentToString = (value, valueFormat) => { + if (Array.isArray(value)) { + return value.map(val => (interopDefault(moment).isMoment(val) ? val.format(valueFormat) : val)); + } else { + return interopDefault(moment).isMoment(value) ? value.format(valueFormat) : value; + } +}; diff --git a/components/calendar/index.jsx b/components/calendar/index.jsx index eabf79009..e793a4b56 100644 --- a/components/calendar/index.jsx +++ b/components/calendar/index.jsx @@ -9,6 +9,7 @@ import interopDefault from '../_util/interopDefault'; import { ConfigConsumerProps } from '../config-provider'; import enUS from './locale/en_US'; import Base from '../base'; +import { checkValidate, stringToMoment, momentToString, TimeType } from '../_util/moment-util'; function noop() { return null; @@ -20,12 +21,6 @@ function zerofixed(v) { } return `${v}`; } -export const MomentType = { - type: Object, - validator(value) { - return moment.isMoment(value); - }, -}; function isMomentArray(value) { return Array.isArray(value) && !!value.find(val => moment.isMoment(val)); } @@ -33,8 +28,8 @@ export const CalendarMode = PropTypes.oneOf(['month', 'year']); export const CalendarProps = () => ({ prefixCls: PropTypes.string, - value: MomentType, - defaultValue: MomentType, + value: TimeType, + defaultValue: TimeType, mode: CalendarMode, fullscreen: PropTypes.bool, // dateCellRender: PropTypes.func, @@ -47,6 +42,7 @@ export const CalendarProps = () => ({ disabledDate: PropTypes.func, validRange: PropTypes.custom(isMomentArray), headerRender: PropTypes.func, + valueFormat: PropTypes.string, }); const Calendar = { @@ -64,20 +60,21 @@ const Calendar = { configProvider: { default: () => ConfigConsumerProps }, }, data() { - const value = this.value || this.defaultValue || interopDefault(moment)(); - if (!interopDefault(moment).isMoment(value)) { - throw new Error('The value/defaultValue of Calendar must be a moment object, '); - } + const { value, defaultValue, valueFormat } = this; + const sValue = value || defaultValue || interopDefault(moment)(); + checkValidate('Calendar', defaultValue, 'defaultValue', valueFormat); + checkValidate('Calendar', value, 'value', valueFormat); this._sPrefixCls = undefined; return { - sValue: value, + sValue: stringToMoment(sValue, valueFormat), sMode: this.mode || 'month', }; }, watch: { value(val) { + checkValidate('Calendar', val, 'value', this.valueFormat); this.setState({ - sValue: val, + sValue: stringToMoment(val, this.valueFormat), }); }, mode(val) { @@ -95,9 +92,10 @@ const Calendar = { this.onPanelChange(this.sValue, mode); }, onPanelChange(value, mode) { - this.$emit('panelChange', value, mode); + const val = this.valueFormat ? momentToString(value, this.valueFormat) : value; + this.$emit('panelChange', val, mode); if (value !== this.sValue) { - this.$emit('change', value); + this.$emit('change', val); } }, @@ -105,8 +103,8 @@ const Calendar = { this.setValue(value, 'select'); }, setValue(value, way) { - const prevValue = this.value || this.sValue; - const { sMode: mode } = this; + const prevValue = this.value ? stringToMoment(this.value, this.valueFormat) : this.sValue; + const { sMode: mode, valueFormat } = this; if (!hasProp(this, 'value')) { this.setState({ sValue: value }); } @@ -114,7 +112,7 @@ const Calendar = { if (prevValue && prevValue.month() !== value.month()) { this.onPanelChange(value, mode); } - this.$emit('select', value); + this.$emit('select', valueFormat ? momentToString(value, valueFormat) : value); } else if (way === 'changePanel') { this.onPanelChange(value, mode); } diff --git a/components/date-picker/interface.js b/components/date-picker/interface.js index 25f2d7ab0..4791c2a5a 100644 --- a/components/date-picker/interface.js +++ b/components/date-picker/interface.js @@ -1,13 +1,6 @@ -import * as moment from 'moment'; // import { TimePickerProps } from '../time-picker' import PropTypes from '../_util/vue-types'; - -export const MomentType = { - type: Object, - validator(value) { - return value === undefined || moment.isMoment(value); - }, -}; +import { TimesType, TimeType } from '../_util/moment-util'; export const PickerProps = () => ({ name: PropTypes.string, @@ -37,12 +30,13 @@ export const PickerProps = () => ({ tabIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), align: PropTypes.object.def(() => ({})), inputReadOnly: PropTypes.bool, + valueFormat: PropTypes.string, }); export const SinglePickerProps = () => ({ - value: MomentType, - defaultValue: MomentType, - defaultPickerValue: MomentType, + value: TimeType, + defaultValue: TimeType, + defaultPickerValue: TimeType, renderExtraFooter: PropTypes.any, placeholder: PropTypes.string, // onChange?: (date: moment.Moment, dateString: string) => void; @@ -65,27 +59,17 @@ export const MonthPickerProps = () => ({ placeholder: PropTypes.string, monthCellContentRender: PropTypes.func, }); -function isMomentArray(value) { - if (Array.isArray(value)) { - return ( - value.length === 0 || value.findIndex(val => val === undefined || moment.isMoment(val)) !== -1 - ); - } - return false; -} - -export const RangePickerValue = PropTypes.custom(isMomentArray); -// export const RangePickerPresetRange = PropTypes.oneOfType([RangePickerValue, PropTypes.func]) +// export const RangePickerPresetRange = PropTypes.oneOfType([TimesType, PropTypes.func]) export const RangePickerProps = () => ({ ...PickerProps(), tagPrefixCls: PropTypes.string, - value: RangePickerValue, - defaultValue: RangePickerValue, - defaultPickerValue: RangePickerValue, + value: TimesType, + defaultValue: TimesType, + defaultPickerValue: TimesType, timePicker: PropTypes.any, - // onChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void; - // onCalendarChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void; + // onChange?: (dates: TimesType, dateStrings: [string, string]) => void; + // onCalendarChange?: (dates: TimesType, dateStrings: [string, string]) => void; // onOk?: (selectedTime: moment.Moment) => void; showTime: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]), ranges: PropTypes.object, @@ -95,7 +79,7 @@ export const RangePickerProps = () => ({ disabledTime: PropTypes.func, showToday: PropTypes.bool, renderExtraFooter: PropTypes.any, - // onPanelChange?: (value?: RangePickerValue, mode?: string | string[]) => void; + // onPanelChange?: (value?: TimesType, mode?: string | string[]) => void; }); export const WeekPickerProps = () => ({ diff --git a/components/date-picker/wrapPicker.js b/components/date-picker/wrapPicker.js index 24e54cc8e..b785c8811 100644 --- a/components/date-picker/wrapPicker.js +++ b/components/date-picker/wrapPicker.js @@ -1,26 +1,11 @@ import TimePickerPanel from '../vc-time-picker/Panel'; import classNames from 'classnames'; -import * as moment from 'moment'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; import { generateShowHourMinuteSecond } from '../time-picker'; import enUS from './locale/en_US'; -import interopDefault from '../_util/interopDefault'; import { getOptionProps, initDefaultProps, getListeners } from '../_util/props-util'; import { ConfigConsumerProps } from '../config-provider'; -import warning from '../_util/warning'; - -function checkValidate(value, propName) { - const values = Array.isArray(value) ? value : [value]; - values.forEach(val => { - if (!val) return; - - warning( - !interopDefault(moment).isMoment(val) || val.isValid(), - 'DatePicker', - `\`${propName}\` provides invalidate moment time. If you want to set empty value, use \`null\` instead.`, - ); - }); -} +import { checkValidate, stringToMoment, momentToString } from '../_util/moment-util'; const DEFAULT_FORMAT = { date: 'YYYY-MM-DD', @@ -74,9 +59,9 @@ export default function wrapPicker(Picker, props, pickerType) { }; }, mounted() { - const { autoFocus, disabled, value, defaultValue } = this; - checkValidate(defaultValue, 'defaultValue'); - checkValidate(value, 'value'); + const { autoFocus, disabled, value, defaultValue, valueFormat } = this; + checkValidate('DatePicker', defaultValue, 'defaultValue', valueFormat); + checkValidate('DatePicker', value, 'value', valueFormat); if (autoFocus && !disabled) { this.$nextTick(() => { this.focus(); @@ -85,7 +70,7 @@ export default function wrapPicker(Picker, props, pickerType) { }, watch: { value(val) { - checkValidate(val, 'value'); + checkValidate('DatePicker', val, 'value', this.valueFormat); }, }, methods: { @@ -123,7 +108,13 @@ export default function wrapPicker(Picker, props, pickerType) { handleMouseLeave(e) { this.$emit('mouseleave', e); }, - + handleChange(date, dateString) { + this.$emit( + 'change', + this.valueFormat ? momentToString(date, this.valueFormat) : date, + dateString, + ); + }, focus() { this.$refs.picker.focus(); }, @@ -132,8 +123,21 @@ export default function wrapPicker(Picker, props, pickerType) { this.$refs.picker.blur(); }, + transformValue(props) { + if ('value' in props) { + props.value = stringToMoment(props.value, this.valueFormat); + } + if ('defaultValue' in props) { + props.defaultValue = stringToMoment(props.defaultValue, this.valueFormat); + } + if ('defaultPickerValue' in props) { + props.defaultPickerValue = stringToMoment(props.defaultPickerValue, this.valueFormat); + } + }, + renderPicker(locale, localeCode) { const props = getOptionProps(this); + this.transformValue(props); const { prefixCls: customizePrefixCls, inputPrefixCls: customizeInputPrefixCls, @@ -203,6 +207,7 @@ export default function wrapPicker(Picker, props, pickerType) { blur: this.handleBlur, mouseenter: this.handleMouseEnter, mouseleave: this.handleMouseLeave, + change: this.handleChange, }, ref: 'picker', scopedSlots: this.$scopedSlots || {}, diff --git a/components/time-picker/index.jsx b/components/time-picker/index.jsx index 8cb409249..cda3bed3b 100644 --- a/components/time-picker/index.jsx +++ b/components/time-picker/index.jsx @@ -1,4 +1,3 @@ -import * as moment from 'moment'; import omit from 'omit.js'; import VcTimePicker from '../vc-time-picker'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; @@ -7,7 +6,6 @@ import PropTypes from '../_util/vue-types'; import warning from '../_util/warning'; import Icon from '../icon'; import enUS from './locale/en_US'; -import interopDefault from '../_util/interopDefault'; import { initDefaultProps, hasProp, @@ -19,6 +17,12 @@ import { import { cloneElement } from '../_util/vnode'; import { ConfigConsumerProps } from '../config-provider'; import Base from '../base'; +import { + checkValidate, + stringToMoment, + momentToString, + TimeOrTimesType, +} from '../_util/moment-util'; export function generateShowHourMinuteSecond(format) { // Ref: http://momentjs.com/docs/#/parsing/string-format/ @@ -28,20 +32,11 @@ export function generateShowHourMinuteSecond(format) { showSecond: format.indexOf('s') > -1, }; } -function isMoment(value) { - if (Array.isArray(value)) { - return ( - value.length === 0 || value.findIndex(val => val === undefined || moment.isMoment(val)) !== -1 - ); - } else { - return value === undefined || moment.isMoment(value); - } -} -const MomentType = PropTypes.custom(isMoment); + export const TimePickerProps = () => ({ size: PropTypes.oneOf(['large', 'default', 'small']), - value: MomentType, - defaultValue: MomentType, + value: TimeOrTimesType, + defaultValue: TimeOrTimesType, open: PropTypes.bool, format: PropTypes.string, disabled: PropTypes.bool, @@ -72,6 +67,7 @@ export const TimePickerProps = () => ({ addon: PropTypes.any, clearIcon: PropTypes.any, locale: PropTypes.object, + valueFormat: PropTypes.string, }); const TimePicker = { @@ -104,22 +100,23 @@ const TimePicker = { configProvider: { default: () => ConfigConsumerProps }, }, data() { - const value = this.value || this.defaultValue; - if (value && !interopDefault(moment).isMoment(value)) { - throw new Error('The value/defaultValue of TimePicker must be a moment object, '); - } + const { value, defaultValue, valueFormat } = this; + + checkValidate('TimePicker', defaultValue, 'defaultValue', valueFormat); + checkValidate('TimePicker', value, 'value', valueFormat); warning( !hasProp(this, 'allowEmpty'), 'TimePicker', '`allowEmpty` is deprecated. Please use `allowClear` instead.', ); return { - sValue: value, + sValue: stringToMoment(value || defaultValue, valueFormat), }; }, watch: { value(val) { - this.setState({ sValue: val }); + checkValidate('TimePicker', val, 'value', this.valueFormat); + this.setState({ sValue: stringToMoment(val, this.valueFormat) }); }, }, methods: { @@ -155,7 +152,11 @@ const TimePicker = { this.setState({ sValue: value }); } const { format = 'HH:mm:ss' } = this; - this.$emit('change', value, (value && value.format(format)) || ''); + this.$emit( + 'change', + this.valueFormat ? momentToString(value, this.valueFormat) : value, + (value && value.format(format)) || '', + ); }, handleOpenClose({ open }) { diff --git a/components/vc-calendar/src/Picker.jsx b/components/vc-calendar/src/Picker.jsx index 53695c7b8..f1f9cc1a6 100644 --- a/components/vc-calendar/src/Picker.jsx +++ b/components/vc-calendar/src/Picker.jsx @@ -7,17 +7,18 @@ import KeyCode from '../../_util/KeyCode'; import placements from './picker/placements'; import Trigger from '../../vc-trigger'; import moment from 'moment'; -import { setTimeout } from 'timers'; -function isMoment(value) { - if (Array.isArray(value)) { - return ( - value.length === 0 || value.findIndex(val => val === undefined || moment.isMoment(val)) !== -1 - ); - } else { - return value === undefined || moment.isMoment(value); - } -} -const MomentType = PropTypes.custom(isMoment); +import isNil from 'lodash/isNil'; +const TimeType = { + validator(value) { + if (Array.isArray(value)) { + return ( + value.length === 0 || value.findIndex(val => !isNil(val) && !moment.isMoment(val)) === -1 + ); + } else { + return isNil(value) || moment.isMoment(value); + } + }, +}; const Picker = { name: 'Picker', props: { @@ -34,8 +35,8 @@ const Picker = { defaultOpen: PropTypes.bool.def(false), prefixCls: PropTypes.string.def('rc-calendar-picker'), placement: PropTypes.any.def('bottomLeft'), - value: PropTypes.oneOfType([MomentType, PropTypes.arrayOf(MomentType)]), - defaultValue: PropTypes.oneOfType([MomentType, PropTypes.arrayOf(MomentType)]), + value: TimeType, + defaultValue: TimeType, align: PropTypes.object.def(() => ({})), dropdownClassName: PropTypes.string, dateRender: PropTypes.func, diff --git a/types/calendar.d.ts b/types/calendar.d.ts index 486f7c95a..5fb2bdb4c 100644 --- a/types/calendar.d.ts +++ b/types/calendar.d.ts @@ -31,7 +31,7 @@ export declare class Calendar extends AntdComponent { * @default default date * @type Moment */ - defaultValue: Moment; + defaultValue: Moment | string; /** * Function that specifies the dates that cannot be selected @@ -84,5 +84,6 @@ export declare class Calendar extends AntdComponent { * @default current date * @type Moment */ - value: Moment; + value: Moment | string; + valueFormat: string; } diff --git a/types/date-picker/common.d.ts b/types/date-picker/common.d.ts index 7f6ffdb37..0bbe61fd5 100644 --- a/types/date-picker/common.d.ts +++ b/types/date-picker/common.d.ts @@ -88,4 +88,5 @@ export declare class DatepickerCommon extends AntdComponent { suffixIcon: any; inputReadOnly: boolean; + valueFormat: string; } diff --git a/types/date-picker/date-picker.d.ts b/types/date-picker/date-picker.d.ts index a300f8546..83f7d0d20 100644 --- a/types/date-picker/date-picker.d.ts +++ b/types/date-picker/date-picker.d.ts @@ -19,7 +19,7 @@ export declare class DatePicker extends DatepickerCommon { * @default undefined * @type Moment */ - defaultValue: Moment; + defaultValue: Moment | string; /** * Default Picker Value @@ -27,7 +27,7 @@ export declare class DatePicker extends DatepickerCommon { * @default undefined * @type Moment */ - defaultPickerValue: Moment; + defaultPickerValue: Moment | string; /** * Disabled Time @@ -77,5 +77,5 @@ export declare class DatePicker extends DatepickerCommon { * @default undefined * @type Moment */ - value: Moment; + value: Moment | string; } diff --git a/types/date-picker/month-picker.d.ts b/types/date-picker/month-picker.d.ts index f7d2d7377..9034747cc 100644 --- a/types/date-picker/month-picker.d.ts +++ b/types/date-picker/month-picker.d.ts @@ -10,13 +10,13 @@ export declare class MonthPicker extends DatepickerCommon { * to set default date * @type Moment */ - defaultValue: Moment; + defaultValue: Moment | string; /** * to set default picker date * @type Moment */ - defaultPickerValue: Moment; + defaultPickerValue: Moment | string; /** * to set the date format, When an array is provided, all values are used for parsing and first value for display. refer to moment.js @@ -42,5 +42,5 @@ export declare class MonthPicker extends DatepickerCommon { * to set date * @type Moment */ - value: Moment; + value: Moment | string; } diff --git a/types/date-picker/range-picker.d.ts b/types/date-picker/range-picker.d.ts index b8e994fb2..565cc50e8 100644 --- a/types/date-picker/range-picker.d.ts +++ b/types/date-picker/range-picker.d.ts @@ -10,13 +10,13 @@ export declare class RangePicker extends DatepickerCommon { * to set default date * @type [Moment, Moment] */ - defaultValue: [Moment, Moment]; + defaultValue: [Moment, Moment] | [string, string]; /** * to set default picker date * @type [Moment, Moment] */ - defaultPickerValue: [Moment, Moment]; + defaultPickerValue: [Moment, Moment] | [string, string]; /** * to specify the time that cannot be selected @@ -56,5 +56,5 @@ export declare class RangePicker extends DatepickerCommon { * to set date * @type [Moment, Moment] */ - value: [Moment, Moment]; + value: [Moment, Moment] | [string, string]; } diff --git a/types/date-picker/week-picker.d.ts b/types/date-picker/week-picker.d.ts index bf5c83ffa..2921afa51 100644 --- a/types/date-picker/week-picker.d.ts +++ b/types/date-picker/week-picker.d.ts @@ -10,13 +10,13 @@ export declare class WeekPicker extends DatepickerCommon { * to set default date * @type Moment */ - defaultValue: Moment; + defaultValue: Moment | string; /** * to set default picker date * @type Moment */ - defaultPickerValue: Moment; + defaultPickerValue: Moment | string; /** * to set the date format, refer to moment.js @@ -30,5 +30,5 @@ export declare class WeekPicker extends DatepickerCommon { * to set date * @type Moment */ - value: Moment; + value: Moment | string; } diff --git a/types/time-picker.d.ts b/types/time-picker.d.ts index 4dc57e008..c5cde3428 100644 --- a/types/time-picker.d.ts +++ b/types/time-picker.d.ts @@ -44,7 +44,7 @@ export declare class TimePicker extends AntdComponent { * to set default time * @type Moment */ - defaultValue: Moment; + defaultValue: Moment | string; /** * determine whether the TimePicker is disabled @@ -158,7 +158,7 @@ export declare class TimePicker extends AntdComponent { * to set time * @type Moment */ - value: Moment; + value: Moment | string; /** * Time picker size