diff --git a/components/date-picker2/__docs__/demo/disabledDate/index.tsx b/components/date-picker2/__docs__/demo/disabledDate/index.tsx index ffedb21971..9a42a1daff 100644 --- a/components/date-picker2/__docs__/demo/disabledDate/index.tsx +++ b/components/date-picker2/__docs__/demo/disabledDate/index.tsx @@ -22,6 +22,13 @@ const disabledDate = function (date, mode) { } }; +const disabledCurrentTime = function (date) { + return ( + date.valueOf() < Number(dayjs().valueOf()) || + date.valueOf() > Number(dayjs().add(6, 'day').valueOf()) + ); +}; + ReactDOM.render(
console.log(val)} /> @@ -36,6 +43,13 @@ ReactDOM.render( console.log(val)} />

+ console.log(val)} + /> +
+
, mountNode ); diff --git a/components/date-picker2/__tests__/index-spec.js b/components/date-picker2/__tests__/index-spec.js index 5ea28490ae..c53d9041f6 100644 --- a/components/date-picker2/__tests__/index-spec.js +++ b/components/date-picker2/__tests__/index-spec.js @@ -1128,6 +1128,128 @@ describe('Picker', () => { wrapper = mount(); assert(wrapper.find('.next-icon-loading').length === 1); }); + + it('should support prohibit minutes and seconds', () => { + let value; + const disabledDate = date => { + return ( + date.valueOf() < Number(moment('2024-01-22 13:30:12').valueOf()) || + date.valueOf() > Number(moment('2024-01-28 18:30:12').valueOf()) + ); + }; + const handleChange = date => { + if (Array.isArray(date)) { + assert(date[0].valueOf() === Number(moment(value[0]).valueOf()), 'The start time should be the same') + assert(date[1].valueOf() === Number(moment(value[1]).valueOf()), 'The end time should be the same') + } else { + assert(date.valueOf() === Number(moment(value).valueOf())) + } + }; + + const clickDateTime = (date) => { + const m = moment(date); + clickDate(m.format('YYYY-MM-DD')); + clickTime(m.hour(), 'hour'); + clickTime(m.minute(), 'minute'); + clickTime(m.second(), 'second'); + } + + const isDisabledNode = name => { + return hasClassNames(wrapper.find(name), 'next-disabled'); + }; + + wrapper = mount( + , + ); + findInput(0).simulate('click'); + assert(wrapper.find('.next-date-picker2-wrapper').exists()); + [22, 23, 24, 25, 26, 27, 28].forEach(item => { + assert(!hasClassNames(findDate(`2024-01-${item}`), 'next-calendar2-cell-disabled')); + }) + assert(hasClassNames(findDate('2024-01-21'), 'next-calendar2-cell-disabled')); + assert(hasClassNames(findDate('2024-01-29'), 'next-calendar2-cell-disabled')); + + wrapper.setProps({ value: '2024-01-22 13:30:10' }); + assert(wrapper.find('button.next-date-picker2-footer-ok').prop('disabled'), 'Select date to 2024-01-22 13:30:10, confirm button should be disabled'); + wrapper.setProps({ value: '2024-01-28 18:30:13' }); + assert(wrapper.find('button.next-date-picker2-footer-ok').prop('disabled'), 'Select date to 2024-01-28 18:30:13, confirm button should be disabled'); + value = '2024-01-22 14:30:10'; + clickDateTime(value); + assert(!wrapper.find('button.next-date-picker2-footer-ok').prop('disabled'), 'Select date to 2024-01-22 14:30:10, confirm button should not be disabled'); + clickOk(); + + wrapper.setProps({ + disabledTime: { + disabledHours: i => i < 5, + disabledMinutes: i => i < 10, + disabledSeconds: i => i < 10 + }, + timePanelProps: { + disabledHours: i => i > 14, + disabledMinutes: i => i > 30, + disabledSeconds: i => i > 30 + } + }); + + clickDate('2024-01-23'); + wrapper.find('ul.next-time-picker2-menu-hour li.next-time-picker2-menu-item').forEach((item) => { + if (item.prop('title') > 14 || item.prop('title') < 5) { + assert(hasClassNames(item, 'next-disabled')); + } else { + assert(!hasClassNames(item, 'next-disabled')); + } + }); + wrapper.find('ul.next-time-picker2-menu-minute li.next-time-picker2-menu-item').forEach((item) => { + if (item.prop('title') > 30 || item.prop('title') < 10) { + assert(hasClassNames(item, 'next-disabled')); + } else { + assert(!hasClassNames(item, 'next-disabled')); + } + }); + wrapper.find('ul.next-time-picker2-menu-second li.next-time-picker2-menu-item').forEach((item) => { + if (item.prop('title') > 30 || item.prop('title') < 10) { + assert(hasClassNames(item, 'next-disabled')); + } else { + assert(!hasClassNames(item, 'next-disabled')); + } + }); + + clickDate('2024-01-22'); + [12, 15].forEach(i => { + assert(hasClassNames(wrapper.find(`ul.next-time-picker2-menu-hour [title=${i}]`), 'next-disabled')) + }) + assert(!hasClassNames(wrapper.find('ul.next-time-picker2-menu-hour [title=14]'), 'next-disabled')); + + wrapper = mount( + console.log(val)} + defaultPanelValue={dayjs('2024-01-22 14:30:10')} + />, + ); + findInput(0).simulate('click'); + assert(wrapper.find('.next-date-picker2-wrapper').exists()); + value = ['2024-01-22 13:30:12', '2024-01-28 18:30:12'] + clickDateTime(value[0]) + const startHour = isDisabledNode('.next-time-picker2-menu-hour > li[title=12]'); + const startMinute = isDisabledNode('.next-time-picker2-menu-minute > li[title=29]'); + const startSecond = isDisabledNode('.next-time-picker2-menu-second > li[title=11]'); + assert(startHour && startMinute && startSecond); + clickOk(); + + clickDateTime(value[1]) + const endHour = isDisabledNode('.next-time-picker2-menu-hour > li[title=19]'); + const endMinute = isDisabledNode('.next-time-picker2-menu-minute > li[title=31]'); + const endSecond = isDisabledNode('.next-time-picker2-menu-second > li[title=14]'); + assert(endHour && endMinute && endSecond); + clickOk(); + }); }); }); diff --git a/components/date-picker2/panels/date-panel.jsx b/components/date-picker2/panels/date-panel.jsx index 17b49f22ee..1280b68eea 100644 --- a/components/date-picker2/panels/date-panel.jsx +++ b/components/date-picker2/panels/date-panel.jsx @@ -72,6 +72,19 @@ class DatePanel extends React.Component { func.invoke(this.props, 'onPanelChange', [v, mode]); }; + checkValueDisabled = (v, mode) => { + const { showTime, disabledDate, panelMode } = this.props; + + if (showTime && disabledDate && mode === panelMode) { + return ( + disabledDate(v.hour(0).minute(0).second(0), mode) && + disabledDate(v.hour(23).minute(59).second(59), mode) + ); + } + + return disabledDate && disabledDate(v, mode); + }; + render() { const { mode, @@ -109,7 +122,7 @@ class DatePanel extends React.Component { colNum={showTime ? 6 : undefined} onSelect={this.handleSelect} onPanelChange={this.handlePanelChange} - disabledDate={disabledDate} + disabledDate={this.checkValueDisabled} dateCellRender={dateCellRender} /> {showTime && mode === panelMode ? ( @@ -119,6 +132,8 @@ class DatePanel extends React.Component { value={value || this.state.defaultTime} onSelect={this.onTimeSelect} disabledTime={disabledTime} + disabledDate={disabledDate} + panelMode={mode} timePanelProps={{ ..._disabledTime, ...timePanelProps }} /> ) : null} diff --git a/components/date-picker2/panels/range-panel.jsx b/components/date-picker2/panels/range-panel.jsx index c41eadca28..4377b3decb 100644 --- a/components/date-picker2/panels/range-panel.jsx +++ b/components/date-picker2/panels/range-panel.jsx @@ -145,7 +145,7 @@ class RangePanel extends React.Component { disabledDate, value: [begin, end], } = this.props; - + const unit = mode2unit(mode); return ( @@ -328,8 +328,17 @@ class RangePanel extends React.Component { }); }; + checkValueDisabled = (v, mode) => { + const disabledDate = this.props.justBeginInput ? this.props.disabledDate : this.disabledDate; + return ( + disabledDate(v.hour(0).minute(0).second(0), mode) && + disabledDate(v.hour(23).minute(59).second(59), mode) + ); + }; + + renderRangeTime = sharedProps => { - const { value, prefix, showTime, inputType, timePanelProps = {}, disabledTime } = this.props; + const { value, prefix, showTime, inputType, timePanelProps = {}, disabledTime, mode } = this.props; const className = classnames(`${prefix}range-picker2-panel`, { [`${prefix}range-picker2-panel-single`]: this.hasModeChanged, @@ -346,6 +355,7 @@ class RangePanel extends React.Component { @@ -356,6 +366,8 @@ class RangePanel extends React.Component { value={value[inputType] || this.state.defaultTime[inputType]} onSelect={this.onTimeSelect} disabledTime={disabledTime} + disabledDate={sharedProps.disabledDate} + panelMode={mode} timePanelProps={{ ..._disabledTime, ...timePanelProps }} /> ) : null} diff --git a/components/date-picker2/panels/time-panel.jsx b/components/date-picker2/panels/time-panel.jsx index acdb6bf65b..34c126a42e 100644 --- a/components/date-picker2/panels/time-panel.jsx +++ b/components/date-picker2/panels/time-panel.jsx @@ -4,9 +4,49 @@ import PT from 'prop-types'; import TimePickerPanel from '../../time-picker2/panel'; import SharedPT from '../prop-types'; import { func } from '../../util'; +import { getDisabledTime, isValueChanged } from '../util' const DECADE_TIME_FORMAT = 'HH:mm:ss'; +const WithTimePanel = function (WrappedComponent) { + return class extends React.Component { + static propTypes = { + ...WrappedComponent.propTypes, + disabledDate: PT.func, + panelMode: PT.string, + }; + + constructor(props) { + super(props); + const disabledTime = getDisabledTime(this.props); + this.state = { + ...disabledTime + } + } + + componentDidUpdate(prevProps) { + if (isValueChanged(this.props.value, prevProps.value)) { + const disabledTime = getDisabledTime(this.props); + this.setState({ + ...disabledTime + }); + } + } + + render() { + const { timePanelProps, ...rest } = this.props; + const { disabledHours, disabledMinutes, disabledSeconds } = this.state; + + return ( + + ); + } + }; +}; + class TimePanel extends React.PureComponent { static propTypes = { rtl: PT.bool, @@ -66,11 +106,16 @@ class TimePanel extends React.PureComponent { render() { const { prefix, rtl, locale, timePanelProps = {}, value } = this.props; const { showHour, showMinute, showSecond } = this.getShow(); - + return ( -
+
-
{value ? this.formater(value) : null}
+
+ {value ? this.formater(value) : null} +
); + const options = { + mode, + disabledDate, + disabledHours: timePanelProps && timePanelProps.disabledHours, + disabledMinutes: timePanelProps && timePanelProps.disabledMinutes, + disabledSeconds: timePanelProps && timePanelProps.disabledSeconds + } + // 底部节点 - const oKable = !!(isRange ? inputValue && inputValue[inputType] : inputValue); + const oKable = !disableDateTime(isRange ? inputValue && inputValue[inputType] : inputValue, options); const shouldShowFooter = showOk || preset || extraFooterRender; const footerNode = shouldShowFooter ? ( diff --git a/components/date-picker2/util.js b/components/date-picker2/util.js index 4058dac42c..f557a8cc09 100644 --- a/components/date-picker2/util.js +++ b/components/date-picker2/util.js @@ -49,6 +49,106 @@ export function fmtValue(value, fmt) { */ export function isValueChanged(newValue, oldValue) { return Array.isArray(newValue) - ? isValueChanged(newValue[0], oldValue && oldValue[0]) || isValueChanged(newValue[1], oldValue && oldValue[1]) + ? isValueChanged(newValue[0], oldValue && oldValue[0]) || + isValueChanged(newValue[1], oldValue && oldValue[1]) : newValue !== oldValue && !datejs(newValue).isSame(oldValue); } + +/** + * 判读时间是否被禁用 + * @param {dayjs.ConfigType} value + * @param {object} param + * @returns {boolean} + */ +export function disableDateTime(value, { + mode, + inputType, + disabledDate, + disabledHours, + disabledMinutes, + disabledSeconds, + disabledTime = {} +}) { + value = datejs.isDayjs(value) ? value : datejs(value); + + const _disabledTime = typeof disabledTime === 'function' ? disabledTime(value, inputType) : disabledTime; + + return ( + (typeof disabledDate === 'function' && disabledDate(value, mode)) || + (typeof disabledHours === 'function' && disabledHours(value.get('hour'))) || + (typeof disabledMinutes === 'function' && disabledMinutes(value.get('minute'))) || + (typeof disabledSeconds === 'function' && disabledSeconds(value.get('second'))) || + (typeof _disabledTime.disabledHours === 'function' && _disabledTime.disabledHours(value.get('hour'))) || + (typeof _disabledTime.disabledMinutes === 'function' && _disabledTime.disabledMinutes(value.get('minute'))) || + (typeof _disabledTime.disabledSeconds === 'function' && _disabledTime.disabledSeconds(value.get('second'))) + ); +} + +export function getDisabledTime(props) { + const { timePanelProps, value, disabledDate, panelMode, disabledTime = {}, inputType } = props; + const { disabledHours, disabledMinutes, disabledSeconds } = timePanelProps || {}; + + const _disabledTime = typeof disabledTime === 'function' ? disabledTime(value, inputType) : disabledTime; + + const disabledItems = (list) => index => list.indexOf(index) >= 0; + const executedFunc = (...args) => { + return (index) => { + return args.some(func => typeof func === 'function' ? func(index) : false) + } + } + + if (value && typeof disabledDate === 'function') { + let newDate = value.clone(); + const hours = 24; + const minutesAndSeconds = 60; + const _disabledHours = []; + const _disabledMinutes = []; + const _disabledSeconds = []; + const currentHour = value.get('hour'); + const currentMinute = value.get('minute'); + + for (let i = 0; i < hours; i++) { + // 禁用小时 + if ( + disabledDate(newDate.hour(i).minute(0).second(0), panelMode) && + disabledDate(newDate.hour(i).minute(59).second(59), panelMode) + ) { + _disabledHours.push(i); + } + } + + if (_disabledHours.length && _disabledHours.length < hours) { + for (let i = 0; i < minutesAndSeconds; i++) { + // 从当前小时开始遍历 + if ( + disabledDate(newDate.hour(currentHour).minute(i).second(0), panelMode) && + disabledDate(newDate.hour(currentHour).minute(i).second(59), panelMode) + ) { + _disabledMinutes.push(i); + } + } + } + + if (_disabledMinutes.length && _disabledMinutes.length < minutesAndSeconds) { + for (let i = 0; i < minutesAndSeconds; i++) { + // 从当前时分开始遍历 + newDate = newDate.hour(currentHour).minute(currentMinute).second(i); + if (disabledDate(newDate, panelMode)) { + _disabledSeconds.push(i); + } + } + } + return { + disabledHours: executedFunc(disabledItems(_disabledHours), disabledHours, _disabledTime.disabledHours), + disabledMinutes: executedFunc(disabledItems(_disabledMinutes), disabledMinutes, _disabledTime.disabledMinutes), + disabledSeconds: executedFunc(disabledItems(_disabledSeconds), disabledSeconds, _disabledTime.disabledSeconds), + }; + } + + return { + ..._disabledTime, + disabledHours, + disabledMinutes, + disabledSeconds, + }; +}