这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战
TIP 👉 呜呼!楚虽三户能亡秦,岂有堂堂中国空无人!____陆游《金错刀行》
前言
Web Component是前端界一直非常热衷的一个领域,用第三方组件化的框架去实现的话,你需要依赖框架本身很多东西,很多时候我们只是简单的几个组件,不是很大,也不是很多,所以为了保证组件的`轻量,简单`,其实这个时候我们并不想采用第三方的框架。日期选择器组件
import
import DatePicker from '@/components/DatePicker/DatePicker';
Props
1. onChange
- 类型:func (必填)
- 默认值:无
- 说明:选中日期后的回调函数,入参:
- {Moment | Date | String | Number} value 选中日期值(与valueType对应)
- {Moment} momentValue 选中日期的Moment值
2. valueType
- 类型:DatePicker.VALUE_TYPE 中的一种
- 默认值:DatePicker.VALUE_TYPE.string
- 说明:日期值类型
3. value
- 类型:valueType指定的类型
- 默认值:无
- 说明:日期值
<DatePicker value={'2019-12-12'}
valueType={DatePicker.VALUE_TYPE.string}
onChange={this.dateChange} />
<DatePicker value={new Date()}
valueType={DatePicker.VALUE_TYPE.date}
onChange={this.dateChange} />
<DatePicker value={1576049926055}
valueType={DatePicker.VALUE_TYPE.millisecond}
onChange={this.dateChange} />
<DatePicker value={moment()}
valueType={DatePicker.VALUE_TYPE.moment}
onChange={this.dateChange} />
4. format
- 类型:string
- 默认值:'YYYY-MM-DD'(showTime为true时为'YYYY-MM-DD HH:mm:ss')
- 说明:日期字符串的格式
5. showTime
- 类型:bool
- 默认值:false
- 说明:是否显示时间(时分秒)
6. defaultTime
- 类型:string
- 默认值:无(未设置时,默认为当前时间)
- 说明:默认时间(时分秒),如:'00:00:00'
7. minValue
- 类型:DatePicker.VALUE_TYPE 中的任意一种,与valueType无关
- 默认值:无
- 说明:可选日期的最小值
8. maxValue
- 类型:DatePicker.VALUE_TYPE 中的任意一种,与valueType无关
- 默认值:无
- 说明:可选日期的最大值
9. placeholder
- 类型:string
- 默认值:'请选择日期'(showTime为true时为'请选择日期时间')
- 说明:输入提示信息
10. showClear
- 类型:bool
- 默认值:true
- 说明:输入框右侧是否显示清空按钮
11. disabled
- 类型:bool
- 默认值:false
- 说明:是否不可用,true表示日期选择器不可用
12. disabledDate
- 类型:func (必填)
- 默认值:无
- 说明:判断日期是否可选
- 入参:
- {Moment} current 当前日期
- 返回:
- {Boolen} 是否为不可选,true表示当前日期不可选
- 入参:
13. disabledHours
- 类型:func (必填)
- 默认值:无
- 说明:获取不可用小时的数组(showTime为true时有效)
- 入参:
- {Moment} current 当前日期
- 返回:
- {Array} 不可用的小时数组
- 入参:
14. disabledMinutes
- 类型:func (必填)
- 默认值:无
- 说明:获取不可用分钟的数组(showTime为true时有效)
- 入参:
- {Number} selectedHour 当前选中的小时
- {Moment} current 当前日期
- 返回:
- {Array} 不可用的分钟数组
- 入参:
15. disabledSeconds
- 类型:func (必填)
- 默认值:无
- 说明:获取不可用秒的数组(showTime为true时有效)
- 入参:
- {Number} selectedHour 当前选中的小时
- {Number} selectedMinute 当前选中的分钟
- {Moment} current 当前日期
- 返回:
- {Array} 不可用的秒数组
- 入参:
实现DatePicker.js
import React from 'react';
import PropTypes from 'prop-types';
import Calendar from 'rc-calendar';
import Picker from 'rc-calendar/lib/Picker';
import zhCN from 'rc-calendar/lib/locale/zh_CN';
import TimePickerPanel from 'rc-time-picker/lib/Panel';
import moment from 'moment';
import valueTypes from './utils/value-types';
import PickerPropTypes from './utils/picker-prop-types.js';
import valueConvertUtil from './utils/value-convert-util.js';
import 'moment/locale/zh-cn';
import './datePicker.scss';
/**
* 日期选择器
*/
export default class DatePicker extends React.Component {
// 值类型常量
static VALUE_TYPE = valueTypes;
// 入参类型检查
static propTypes = {
/**
* 选中日期后的回调函数
* @param {Moment | Date | String | Number} value 选中日期值(与valueType对应)
* @param {Moment} momentValue 选中日期的Moment值
*/
onChange: PropTypes.func.isRequired,
// 日期值类型:DatePicker.VALUE_TYPE 中的一种
valueType: PropTypes.oneOf(Object.keys(DatePicker.VALUE_TYPE).map( k => DatePicker.VALUE_TYPE[k]) ),
// 日期值(必须是 valueType 指定类型的数值)
value: PickerPropTypes.dateValue,
// 日期字符串的格式
format: PropTypes.string,
// 是否显示时间(时分秒)
showTime: PropTypes.bool,
// 默认时间(时分秒),showTime为true时有效,如:'00:00:00',为空则会是当前时间
defaultTime: PropTypes.string,
// 可选日期的最小值(DatePicker.VALUE_TYPE 中的任意一种,与valueType无关)
minValue: PickerPropTypes.looseDateValue,
// 可选日期的最大值(DatePicker.VALUE_TYPE 中的任意一种,与valueType无关)
maxValue: PickerPropTypes.looseDateValue,
// 输入提示信息
placeholder: PropTypes.string,
// 是否显示清空按钮
showClear: PropTypes.bool,
// 是否不可用
disabled: PropTypes.bool,
/**
* 判断日期是否可选
* @param {Moment} current 当前日期
* @return {Boolen} 是否为不可选,true表示当前日期不可选
*/
disabledDate: PropTypes.func,
/**
* 获取不可用小时的数组(showTime为true时有效)
* @param {Moment} current 当前日期
* @return {Array<Number>} 不可用的小时数组
*/
disabledHours: PropTypes.func,
/**
* 获取不可用分钟的数组(showTime为true时有效)
* @param {Number} selectedHour 当前选中的小时
* @param {Moment} current 当前日期
* @return {Array<Number>} 不可用的分钟数组
*/
disabledMinutes: PropTypes.func,
/**
* 获取不可用秒的数组(showTime为true时有效)
* @param {Number} selectedHour 当前选中的小时
* @param {Number} selectedMinute 当前选中的分钟
* @param {Moment} current 当前日期
* @return {Array<Number>} 不可用的秒数组
*/
disabledSeconds: PropTypes.func
}
// 入参默认值
static defaultProps = {
// 日期值类型,默认为字符串
valueType: DatePicker.VALUE_TYPE.string,
// 是否显示时间(时分秒),默认为不显示
showTime: false,
// 是否显示清空按钮
showClear: true,
// 是否不可用,默认为可用
disabled: false
}
constructor(props) {
super(props);
const format = this.props.format || (this.props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD');
const momentValue = valueConvertUtil.convertToMoment(this.props.value, this.props.valueType, this.props.showTime, format);
const minMomentValue = valueConvertUtil.convertToMoment(this.props.minValue, null, this.props.showTime, format);
const maxMomentValue = valueConvertUtil.convertToMoment(this.props.maxValue, null, this.props.showTime, format);
const defaultTimeValue = this.props.defaultTime ? moment(this.props.defaultTime, 'HH:mm:ss') : moment();
const placeholder = this.props.placeholder ? this.props.placeholder : (this.props.showTime ? '请选择日期时间' : '请选择日期');
this.state = {
// moment类型的值
momentValue,
// 时间字符串格式
format,
// 可选时间最小值的moment对象
minMomentValue,
// 可选时间最大值的moment对象
maxMomentValue,
// 默认时分秒
defaultTimeValue,
// 输入提示信息
placeholder
};
}
componentDidUpdate(prevProps, prevState) {
this.receivePropsResetState(prevProps);
}
// 根据 props 的变化重新设置 state
receivePropsResetState (prevProps) {
const format = this.props.format || (this.props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD');
if (prevProps.format !== this.props.format
|| prevProps.showTime !== this.props.showTime) {
const momentValue = valueConvertUtil.convertToMoment(this.props.value, this.props.valueType, this.props.showTime, format);
const minMomentValue = valueConvertUtil.convertToMoment(this.props.minValue, null, this.props.showTime, format);
const maxMomentValue = valueConvertUtil.convertToMoment(this.props.maxValue, null, this.props.showTime, format);
const placeholder = this.props.placeholder ? this.props.placeholder : (this.props.showTime ? '请选择日期时间' : '请选择日期');
this.setState({
// moment类型的值
momentValue,
// 时间字符串格式
format,
// 可选时间最小值的moment对象
minMomentValue,
// 可选时间最大值的moment对象
maxMomentValue,
// 输入提示信息
placeholder
});
} else {
if (prevProps.valueType !== this.props.valueType) {
console.warn('DatePicker组件 props.valueType 的值不能随意改变');
}
if (!valueConvertUtil.isSameValue(prevProps.value, this.props.value)) {
const momentValue = valueConvertUtil.convertToMoment(this.props.value, this.props.valueType, this.props.showTime, format);
this.setState({ momentValue });
}
if (prevProps.minValue !== this.props.minValue) {
const minMomentValue = valueConvertUtil.convertToMoment(this.props.minValue, null, this.props.showTime, format);
this.setState({ minMomentValue });
}
if (prevProps.maxValue !== this.props.maxValue) {
const maxMomentValue = valueConvertUtil.convertToMoment(this.props.maxValue, null, this.props.showTime, format);
this.setState({ maxMomentValue });
}
if (prevProps.defaultTime !== this.props.defaultTime) {
const defaultTimeValue = this.props.defaultTime ? moment(this.props.defaultTime, 'HH:mm:ss') : moment();
this.setState({ defaultTimeValue });
}
if (prevProps.placeholder !== this.props.placeholder) {
const placeholder = this.props.placeholder ? this.props.placeholder : (this.props.showTime ? '请选择日期时间' : '请选择日期');
this.setState({ placeholder });
}
}
}
/**
* 将其他类型转换为moment对象
* @param value 其他类型时间值
*/
convertToMoment (value) {
return valueConvertUtil.convertToMoment(value, this.props.valueType, this.props.showTime, this.state.format);
}
/**
* 将moment对象转换为其他类型
* @param obj moment对象
*/
convertFromMoment (obj) {
return valueConvertUtil.convertFromMoment(obj, this.props.valueType, this.props.showTime, this.state.format);
}
// 日期选择器选择后触发的事件
onPickerChange = (value) => {
this.setState({ momentValue: value })
if (!this.props.showTime || value === null) {
this.props.onChange && this.props.onChange(this.convertFromMoment(value), value);
}
}
// 日期选择器打开状态改变时触发的事件
onOpenChange = (open) => {
if (this.props.showTime && !open) {
let value = this.state.momentValue;
this.props.onChange && this.props.onChange(this.convertFromMoment(value), value);
}
}
// 清空
onClear = (event) => {
this.props.onChange && this.props.onChange(null, null);
event.stopPropagation();
};
// 创建时间选择器组件(时分秒选择器组件)
createTimePicker = (value) => {
if (this.props.showTime) {
return <TimePickerPanel value={value} defaultValue={this.state.defaultTimeValue}/>;
}
return null;
}
/**
* 判断日期是否可选
* @param {Moment} current moment对象
* @returns {boolean}
*/
disabledDate = (current) => {
if (!this.props.showTime) { // 不显示时间(时分秒)时
let curValue = moment(current.format('YYYY-MM-DD'), 'YYYY-MM-DD');
let { minMomentValue, maxMomentValue } = this.state;
if (minMomentValue && curValue.isBefore(minMomentValue)) return true;
if (maxMomentValue && curValue.isAfter(maxMomentValue)) return true;
} else { // 显示时间(时分秒)时
let curValue = this.state.momentValue;
if ( curValue ) {
curValue = moment(`${current.format('YYYY-MM-DD')} ${this.convertToMoment(curValue).format('HH:mm:ss')}`, 'YYYY-MM-DD HH:mm:ss');
} else {
curValue = moment(`${current.format('YYYY-MM-DD')} ${this.state.defaultTimeValue.format('HH:mm:ss')}`, 'YYYY-MM-DD HH:mm:ss');
}
let { minMomentValue, maxMomentValue } = this.state;
let minVal, maxVal;
if (minMomentValue) {
minVal = moment(minMomentValue.format('YYYY-MM-DD'), 'YYYY-MM-DD')
if (curValue.valueOf() < minVal.valueOf()) return true;
}
if (maxMomentValue) {
if (maxMomentValue.format('HH:mm:ss') === '00:00:00') {
maxVal = maxMomentValue.clone();
} else {
maxVal = moment(maxMomentValue.format('YYYY-MM-DD'), 'YYYY-MM-DD').add(moment.duration({'day' : 1}));
}
if (curValue.valueOf() >= maxVal.valueOf()) return true;
}
}
if (this.props.disabledDate) {
return this.props.disabledDate(current);
}
return false;
}
/**
* 判断时间(时、分、秒)是否可选
* @param {Moment} current moment对象
* @returns {Object} 返回包含disabledHours、disabledMinutes、disabledSeconds函数的对象
*/
disabledTime = (current) => {
// 根据最小值和最大值创建连续数字数组方法
function createNumArray (minNum, maxNum) {
let arr = [];
for (let i = minNum; i <= maxNum; i++) {
arr.push(i);
}
return arr;
}
// 可选日期的最大值和最小值
let { minMomentValue, maxMomentValue } = this.state;
// 如果已选择日期
if (current) {
// 获取不可用小时数组的方法
let disabledHours = () => {
let hours = [];
if (minMomentValue && current.isSame(minMomentValue, 'day')) {
hours = createNumArray(0, minMomentValue.hour() - 1);
}
if (maxMomentValue && current.isSame(maxMomentValue, 'day')) {
hours = hours.concat(createNumArray(maxMomentValue.hour() + 1, 23));
}
if (this.props.disabledHours) {
hours = hours.concat(this.props.disabledHours(current));
}
return hours;
}
// 获取不可用分钟数组的方法
let disabledMinutes = (selectedHour) => {
let minutes = [];
if (minMomentValue && current.isSame(minMomentValue, 'day') && selectedHour === minMomentValue.hour()) {
minutes = createNumArray(0, minMomentValue.minute() - 1);
}
if (maxMomentValue && current.isSame(maxMomentValue, 'day') && selectedHour === maxMomentValue.hour()) {
minutes = minutes.concat(createNumArray(maxMomentValue.minute() + 1, 59));
}
if (this.props.disabledMinutes) {
minutes = minutes.concat(this.props.disabledMinutes(selectedHour, current));
}
return minutes;
}
// 获取不可用秒数组的方法
let disabledSeconds = (selectedHour, selectedMinute) => {
let seconds = [];
if (minMomentValue && current.isSame(minMomentValue, 'day') && selectedHour === minMomentValue.hour() && selectedMinute === minMomentValue.minute()) {
seconds = createNumArray(0, minMomentValue.second() - 1);
}
if (maxMomentValue && current.isSame(maxMomentValue, 'day') && selectedHour === maxMomentValue.hour() && selectedMinute === maxMomentValue.minute()) {
seconds = seconds.concat(createNumArray(maxMomentValue.second() + 1, 59));
}
if (this.props.disabledSeconds) {
seconds = seconds.concat(this.props.disabledSeconds(selectedHour, selectedMinute, current));
}
return seconds;
}
return {
disabledHours,
disabledMinutes,
disabledSeconds
}
} else {
// 如果未选择日期,且限制了日期可选范围,则时分秒不可选
if (minMomentValue || maxMomentValue) {
return {
disabledHours: () => createNumArray(0, 23),
disabledMinutes: () => createNumArray(0, 59),
disabledSeconds: () => createNumArray(0, 59)
};
} else { // 如果未选择日期,且未限制日期范围,则使用入参的disabledHours、disabledMinutes、disabledSeconds进行过滤
return {
disabledHours: () => {
return this.props.disabledHours ? this.props.disabledHours(current) : [];
},
disabledMinutes: (selectedHour) => {
return this.props.disabledMinutes ? this.props.disabledMinutes(selectedHour, current) : [];
},
disabledSeconds: (selectedHour, selectedMinute) => {
return this.props.disabledSeconds ? this.props.disabledSeconds(selectedHour, selectedMinute, current) : [];
}
};
}
}
}
render() {
const dateValue = this.state.momentValue;
const showClear = this.props.showClear && this.props.value;
const calendar = (<Calendar locale={ zhCN }
dateInputPlaceholder={this.state.placeholder}
format={this.state.format}
timePicker={this.createTimePicker(dateValue)}
showDateInput={false}
disabledDate={this.disabledDate}
disabledTime={this.disabledTime} />);
return (
<Picker animation="slide-up"
calendar={calendar}
value={dateValue}
onChange={this.onPickerChange}
onOpenChange={this.onOpenChange}
disabled={this.props.disabled}>
{
({ value }) => {
return (
<div className={'calendar-picker-box' + (showClear ? ' calendar-picker-box-show-clear' : '')}>
<input placeholder={this.state.placeholder}
disabled={this.props.disabled}
className="date-picker-input"
value={value && value.format && value.format(this.state.format) || ''}
readOnly />
{showClear ?
<span className="date-picker-clear" onClick={this.onClear} title="清空">
<i className="icon-guanbishibaimianxing clear-icon"></i>
</span>
: null}
<i className="icon icon-rili"></i>
</div>);
}
}
</Picker>);
}
}
样式这块就先不放了
「欢迎在评论区讨论」