Home Reference Source Repository

src/DatePicker/index.js

import React, {
  Component,
  PropTypes
} from 'react';
import {classNames, createInstance} from '../utils';
import Select from '../Select';
import Popup from '../Popup';
import './style';

let apiInstance;
const now = new Date();

export default class DatePicker extends Component {
  static propTypes = {
    title: PropTypes.string,
    minDate: PropTypes.object,
    maxDate: PropTypes.object,
    selectedDate: PropTypes.object,
    onChange: PropTypes.func,
    onConfirm: PropTypes.func,
    onCancel: PropTypes.func,
    className: PropTypes.string
  };

  static defaultProps = {
    minDate: now,
    maxDate: now,
    selectedDate: now
  };

  static getInstance = (container) => {
    return createInstance(ApiContainer, container);
  };

  static show = (props, popupProps) => {
    apiInstance.show(props, popupProps);
  };

  // 方便 _onChange()
  yearOptions = [];
  monthOptions = [];
  dateOptions = [];

  _cancel = () => {
    let {onCancel, selectedDate} = this.props;

    onCancel && onCancel(selectedDate);
  };

  _confirm = () => {
    let {onConfirm, selectedDate} = this.props;

    onConfirm && onConfirm(selectedDate);
  };

  _changeYear = (yearIndex) => {
    let {selectedDate} = this.props;
    let year = this.yearOptions[yearIndex].value;
    let newDate = copyDate(selectedDate);
    newDate.setFullYear(year);

    this._change(newDate);
  };

  _changeMonth = (monthIndex) => {
    let {selectedDate} = this.props;
    let month = this.monthOptions[monthIndex].value - 1;  // 纠正 getMonth() 的 +1
    let newDate = copyDate(selectedDate);
    newDate.setMonth(month);

    // 当前 3-31
    // 调动月份,值为2,上面结果变为 3-2
    // 所以这里要校正
    if (newDate.getMonth() > month) {
      newDate.setDate(1);
      newDate.setMonth(month);
      newDate.setDate(getLastDate(newDate));
    }

    this._change(newDate);
  };

  _changeDate = (dateIndex) => {
    let {selectedDate} = this.props;
    let date = this.dateOptions[dateIndex].value;
    let newDate = copyDate(selectedDate);
    newDate.setDate(date);

    this._change(newDate);
  };

  _change(selectedDate) {
    let {onChange} = this.props;
    onChange && onChange(selectedDate);
  }

  render() {
    let {title, className, ...others} = this.props;
    let yearOptions = this.yearOptions = this.getYearOptions();
    let selectedYearIndex = this.getSelectedYearIndex(yearOptions);
    let monthOptions = this.monthOptions = this.getMonthOptions();
    let selectedMonthIndex = this.getSelectedMonthIndex(monthOptions);
    let dateOptions = this.dateOptions = this.getDateOptions();
    let selectedDateIndex = this.getSelectedDateIndex(dateOptions);

    className = classNames('date-picker', {_user: className});

    return (
      <div className={className} {...others}>
        <div className={classNames('date-picker-header')}>
          <button onClick={this._cancel}>取消</button>
          <em>{title}</em>
          <button onClick={this._confirm}>确定</button>
        </div>
        <div className={classNames('date-picker-body')}>
          <Select
            key='year'
            options={yearOptions}
            selectedIndex={selectedYearIndex}
            onChange={this._changeYear} />
          <Select
            key='month'
            options={monthOptions}
            selectedIndex={selectedMonthIndex}
            onChange={this._changeMonth} />
          <Select
            key='date'
            options={dateOptions}
            selectedIndex={selectedDateIndex}
            onChange={this._changeDate} />
        </div>
      </div>
    );
  }


  getYearOptions() {
    let {minDate, maxDate} = this.props;
    let min = minDate.getFullYear();
    let max = maxDate.getFullYear();
    let result = formatOptions(min, max, '年');

    return result;
  }

  getSelectedYearIndex(options) {
    let {selectedDate} = this.props;
    let result = indexOfOptions(options, selectedDate.getFullYear());

    return result;
  }

  getMonthOptions() {
    let {minDate, maxDate, selectedDate} = this.props;
    let min = 1;
    let max = 12;

    isSameYear(minDate, selectedDate) && (min = getMonth(minDate));
    isSameYear(maxDate, selectedDate) && (max = getMonth(maxDate));

    let result = formatOptions(min, max, '月');

    return result;
  }

  getSelectedMonthIndex(options) {
    let {selectedDate} = this.props;
    let result = indexOfOptions(options, getMonth(selectedDate));

    return result;
  }

  getDateOptions() {
    let {minDate, maxDate, selectedDate} = this.props;
    let min = 1;
    let max = getLastDate(selectedDate);

    isSameYearMonth(minDate, selectedDate) && (min = minDate.getDate());
    isSameYearMonth(maxDate, selectedDate) && (max = maxDate.getDate());

    let result = formatOptions(min, max, '日');

    return result;
  }

  getSelectedDateIndex(options) {
    let {selectedDate} = this.props;
    let result = indexOfOptions(options, selectedDate.getDate());

    return result;
  }

}



// 返回 option.value === value 的索引
function indexOfOptions(options, value) {
  let result = -1;
  for (var i = options.length - 1; i >= 0; i--) {
    if (options[i].value === value) {
      result = i;
      break;
    }
  }

  return result;
}

// 格式化 options
function formatOptions(min, max, suffix = '') {
  let result = [];

  while (min <= max) {
    result.push({
      name: (min + suffix),
      value: min
    });
    min++;
  }

  return result;
}

// 获取本月的最后一日的日期
function getLastDate(date) {
  let tmp = new Date(date.getTime());
  tmp.setDate(1);
  tmp.setMonth(tmp.getMonth() + 1);
  tmp.setDate(0);

  return tmp.getDate();
}

// 返回月份,补 +1
function getMonth(date) {
  return date.getMonth() + 1;
}

// 是否同年
function isSameYear(date1, date2) {
  return date1.getFullYear() === date2.getFullYear();
}

// 是否同年月
function isSameYearMonth(date1, date2) {
  return isSameYear(date1, date2) && date1.getMonth() === date2.getMonth();
}

// copy Date 实例
function copyDate(date) {
  let newDate = new Date(date.getTime());

  return newDate;
}



class ApiContainer extends Component {
  state = {
    popupProps: {
      show: false
    }
  };

  render() {
    let {props, popupProps} = this.state;

    return (
      <Popup {...popupProps}>
        <DatePicker {...props} />
      </Popup>
    );
  }

  show(props, popupProps) {
    props = this._decorateProps(props);

    let nextState = {
      props: {...this.state.props, ...props},
      popupProps: {...this.state.popupProps, ...popupProps, show: true}
    };

    this.setState(nextState);
  }

  hide() {
    let nextState = {
      ...this.state,
      popupProps: {...this.state.popupProps, show: false}
    };

    this.setState(nextState);
  }

  _decorateProps(props) {
    let onCancel = props.onCancel;
    let onConfirm = props.onConfirm;
    let onChange = props.onChange;

    props.onChange = (selectedDate) => {
      onChange && onChange(onChange);

      // update state.props
      let nextState = {
        ...this.state,
        props: {
          ...this.state.props,
          selectedDate
        }
      };

      this.setState(nextState);
    };

    props.onCancel = (selectedDate) => {
      onCancel && onCancel(selectedDate);
      this.hide();
    };

    props.onConfirm = (selectedDate) => {
      onConfirm && onConfirm(selectedDate);
      this.hide();
    };

    return props;
  }
}



apiInstance = createInstance(ApiContainer);