/** * @author chenjiajian * @property {Array} range mode为 selector 或 multiSelector 时,range 有效 * @property {String} rangeKey 当 range 是一个 Object Array 时,通过 rangeKey 来指定 Object 中 key 的值作为选择器显示内容 * @property {Number} value value 的值表示选择了 range 中的第几个(下标从 0 开始) * @property {EventHandle} onChange value 改变时触发 change 事件,event.detail = {value: value} * @property {Boolean} disabled 是否禁用 * @property {EventHandle} onCancel 取消选择或点遮罩层收起 picker 时触发 */ import 'weui'; import React from 'react'; import PickerGroup from './picker-group'; import classNames from 'classnames'; import { TOP, LINE_HEIGHT } from './constant'; import * as dateHandle from './date'; import './style/index.css'; // todos: // 1. 加入滚动惯性 // 2. shouldComponentUpdate // 3. 多指操控。。。 // 4. timePicker 样式问题:不在指定时间范围时,选项样式置灰。缩窄两列间宽度。 export default class Picker extends React.Component { /** @type {PickerProps} */ static defaultProps = { mode: 'selector', }; constructor(props) { super(props); this.index = []; this.handlePrpos(); this.state = { pickerValue: this.index, hidden: true, fadeOut: false, height: [], }; } handlePrpos(nextProps = this.props) { let { value, range, mode } = nextProps; if (mode === 'multiSelector') { if (!range) { range = []; this.props.range = []; } if (range === this.props.range && this.props.value !== value) { this.index = []; range.forEach((r, i) => { const v = value && value.length ? value[i] : undefined; this.index.push(this.verifyValue(v, r) ? Math.floor(value[i]) : 0); }); this.setState({ height: this.index.map((i) => TOP - i * LINE_HEIGHT), }); } else if (range.length !== this.index.length) { range.forEach((r, i) => { if (i >= this.index.length) { this.index.push(0); } }); this.setState({ height: this.index.map((i) => TOP - i * LINE_HEIGHT), }); } } else if (mode === 'time') { // check value... if (!this.verifyTime(value)) { console.warn('time picker value illegal'); value = '0:0'; } const time = value.split(':').map((n) => +n); this.index = time; } else if (mode === 'date') { const { start = '', end = '' } = nextProps; let _value = dateHandle.verifyDate(value); let _start = dateHandle.verifyDate(start); let _end = dateHandle.verifyDate(end); if (!_value) _value = new Date(new Date().setHours(0, 0, 0, 0)); // 没传值或值的合法性错误默认今天时间 if (!_start) _start = new Date('1970/01/01'); if (!_end) _end = new Date('2999/01/01'); // 时间区间有效性 if (_value.getTime() >= _start.getTime() && _value.getTime() <= _end.getTime()) { this.index = [_value.getFullYear(), _value.getMonth() + 1, _value.getDate()]; if ( !this.pickerDate || this.pickerDate._value.getTime() !== _value.getTime() || this.pickerDate._start.getTime() !== _start.getTime() || this.pickerDate._end.getTime() !== _end.getTime() ) { this.pickerDate = { _value, _start, _end, _updateValue: [_value.getFullYear(), _value.getMonth() + 1, _value.getDate()], }; } } else { throw new Error('Date Interval Error'); } // this.index = dateHandle. } else { if (!range) { range = []; this.props.range = []; } if (this.index.length >= 1) this.index = []; this.index.push(this.verifyValue(value, range) ? Math.floor(value) : 0); } } componentWillReceiveProps(nextProps) { this.handlePrpos(nextProps); } // 校验传入的 value 是否合法 verifyValue(value, range) { if (!isNaN(+value) && value >= 0 && value < range.length) return true; return false; } // 检验传入的 time value 是否合法 verifyTime(value) { if (!/^\d{1,2}:\d{1,2}$/.test(value)) return false; const time = value.split(':').map((num) => +num); if (time[0] < 0 || time[0] > 23) return false; if (time[1] < 0 || time[1] > 59) return false; return true; } // 比较时间 compareTime(t1, t2) { // logic: t1 <= t2: return true t1 = t1.split(':').map((i) => +i); t2 = t2.split(':').map((i) => +i); if (t1[0] < t2[0]) return true; if (t1[0] === t2[0]) { if (t1[1] <= t2[1]) return true; } return false; } getMonthRange(fields = '') { let _start = 1; let _end = 12; if (this.pickerDate._start.getFullYear() === this.pickerDate._end.getFullYear()) { _start = this.pickerDate._start.getMonth() + 1; _end = this.pickerDate._end.getMonth() + 1; } else if (this.pickerDate._start.getFullYear() === this.pickerDate._updateValue[0]) { _start = this.pickerDate._start.getMonth() + 1; _end = 12; } else if (this.pickerDate._end.getFullYear() === this.pickerDate._updateValue[0]) { _start = 1; _end = this.pickerDate._end.getMonth() + 1; } return this.getDateRange(_start, _end, fields); } getDayRange(fields = '') { let _start = 1; let _end = dateHandle.getMaxDay( this.pickerDate._updateValue[0], this.pickerDate._updateValue[1], ); if ( this.pickerDate._start.getFullYear() === this.pickerDate._updateValue[0] && this.pickerDate._start.getMonth() + 1 === this.pickerDate._updateValue[1] ) { _start = this.pickerDate._start.getDate(); } if ( this.pickerDate._end.getFullYear() === this.pickerDate._updateValue[0] && this.pickerDate._end.getMonth() + 1 === this.pickerDate._updateValue[1] ) { _end = this.pickerDate._end.getDate(); } return this.getDateRange(_start, _end, fields); } // 获取年月日下标或者下标对应的数 getDateArrIndex(value, fields, getIdx = false) { let year = this.getDateRange( this.pickerDate._start.getFullYear(), this.pickerDate._end.getFullYear(), ); let month = this.getMonthRange(); let day = this.getDayRange(); if (getIdx) { if (fields === 0) { return year[value]; } else if (fields === 1) { return month[value]; } else { return day[value]; } } else { if (fields === 0) { return year.indexOf(value + ''); } else if (fields === 1) { return month.indexOf(value + ''); } else { return day.indexOf(value + ''); } } } // 获取时间数组 getDateRange(begin, end, fields = '') { const range = []; for (let i = begin; i <= end; i++) { range.push(`${i}${fields}`); } return range; } // 隐藏 picker hidePicker() { this.setState({ fadeOut: true }); setTimeout(() => this.setState({ hidden: true, fadeOut: false }), 350); } componentWillUnmount() { this.index = []; } render() { // 展示 Picker const showPicker = () => { if (this.props.disabled) return; const height = this.index.map((i, idx) => { let factor = 0; if (this.props.mode === 'time') { factor = LINE_HEIGHT * 4; } if (this.props.mode === 'date') { return TOP - LINE_HEIGHT * this.getDateArrIndex(i, idx) - factor; } return TOP - LINE_HEIGHT * i - factor; }); this.setState({ hidden: false, height, }); }; // 点击确定 const onChange = (e) => { this.hidePicker(); // 除了 multiSeclector,都在点击确认时才改变记录的下标值 this.index = this.state.height.map((h) => (TOP - h) / LINE_HEIGHT); const eventObj = getEventObj(e, 'change', { value: this.index.length > 1 && this.props.mode !== 'selector' ? this.index : this.index[0], }); if (this.props.mode === 'time') { const range = [ ['20', '21', '22', '23', ...getTimeRange(0, 23), '00', '01', '02', '03'], ['56', '57', '58', '59', ...getTimeRange(0, 59), '00', '01', '02', '03'], ]; this.index = this.index.map((n, i) => range[i][n]); eventObj.detail.value = this.index.join(':'); } if (this.props.mode === 'date') { this.index = this.index.map((n, i) => this.getDateArrIndex(n, i, true)); if (this.props.fields === 'year') { eventObj.detail.value = [this.index[0]]; } else if (this.props.fields === 'month') { eventObj.detail.value = [this.index[0], this.index[1]]; } else { eventObj.detail.value = this.index; } eventObj.detail.value = eventObj.detail.value.join('-'); } this.setState({ pickerValue: eventObj.detail.value, }); this.props.onChange && this.props.onChange(eventObj); }; // 点击取消或蒙层 const onCancel = (e) => { this.hidePicker(); const eventObj = getEventObj(e, 'cancel', {}); this.props.onCancel && this.props.onCancel(eventObj); }; // 列改变 const onColumnChange = (height, columnId, e) => { // 获取 touchend 时的 index const index = this.state.height.map((h, i) => { if (columnId === i) h = height; return (TOP - h) / LINE_HEIGHT; }); // 前一级列改变时,后面所有级别的 index 都要设为 0, 并初始化高度 const rangeLen = this.props.range.length; if (columnId < rangeLen - 1) { for (let i = columnId + 1; i < rangeLen; i++) index[i] = 0; } this.setState({ height: index.map((i) => TOP - i * LINE_HEIGHT), }); // 改变 index 的值 this.index = index; const eventObj = getEventObj(e, 'columnChange', { column: columnId, value: index[columnId], }); this.props.onColumnChange && this.props.onColumnChange(eventObj); }; // 统一抛出的事件对象,和小程序对齐 const getEventObj = (e, type, detail) => { let value = detail; Object.defineProperties(e, { detail: { get: function () { return value; }, set: function (v) { value = v; }, enumerable: true, }, type: { value: type, enumerable: true, }, }); return e; }; // 供 PickerGroup 修改对应的 height 值 const updateHeight = (height, columnId, needRevise = false) => { this.setState( (prevState) => { prevState.height[columnId] = height; return { height: prevState.height }; }, () => { // time picker 必须在规定时间范围内,因此需要在 touchEnd 做修正 if (needRevise) { let { start, end } = this.props; if (!this.verifyTime(start)) start = '00:00'; if (!this.verifyTime(end)) end = '23:59'; if (!this.compareTime(start, end)) return; let time = this.state.height.map((h) => (TOP - h) / LINE_HEIGHT); const range = [ ['20', '21', '22', '23', ...getTimeRange(0, 23), '00', '01', '02', '03'], ['56', '57', '58', '59', ...getTimeRange(0, 59), '00', '01', '02', '03'], ]; time = time.map((n, i) => range[i][n]).join(':'); if (!this.compareTime(start, time)) { // 修正到 start const height = start.split(':').map((i) => TOP - LINE_HEIGHT * (+i + 4)); this.setState({ height }); } else if (!this.compareTime(time, end)) { // 修正到 end const height = end.split(':').map((i) => TOP - LINE_HEIGHT * (+i + 4)); this.setState({ height }); } } }, ); }; // 单列 const getSelector = () => { return ( ); }; // 多列 const getMultiSelector = () => { return this.props.range.map((range, index) => { return ( ); }); }; // 时间 const getTimeSelector = () => { const hourRange = ['20', '21', '22', '23', ...getTimeRange(0, 23), '00', '01', '02', '03']; const minRange = ['56', '57', '58', '59', ...getTimeRange(0, 59), '00', '01', '02', '03']; return [ , , ]; }; const getTimeRange = (begin, end) => { const range = []; for (let i = begin; i <= end; i++) { range.push(`${i < 10 ? '0' : ''}${i}`); } return range; }; /** * @author zhongxin * @description 时间选择 * */ // ======================= Date start =====================// const updateDay = (value, fields) => { this.pickerDate._updateValue[fields] = value; // 滚动年份 if (fields === 0) { let monthRange = this.getMonthRange(); updateDay(monthRange[0] * 1, 1); updateHeight(TOP, 1); } else if (fields === 1) { let dayRange = this.getDayRange(); updateDay(dayRange[0] * 1, 2); updateHeight(TOP, 2); } }; const gitDateSelector = () => { let year = this.getDateRange( this.pickerDate._start.getFullYear(), this.pickerDate._end.getFullYear(), '年', ); let month = this.getMonthRange('月'); let day = this.getDayRange('日'); let renderView = []; if (this.props.fields === 'year') { renderView.push( , ); } else if (this.props.fields === 'month') { renderView.push( , , ); } else { renderView = [ , , , ]; } return renderView; }; // ======================= Date end =====================// // 动画类名控制逻辑 const clsMask = classNames('weui-mask', 'weui-animate-fade-in', { 'weui-animate-fade-out': this.state.fadeOut, }); const clsSlider = classNames('weui-picker', 'weui-animate-slide-up', { 'weui-animate-slide-down': this.state.fadeOut, }); const shouldDivHidden = this.state.hidden ? 'display: none;' : ''; // // 给 children 绑定事件 // const children = Nerv.Children.map(this.props.children, child => { // return Nerv.cloneElement(child, { // onClick: showPicker // }) // }) // picker__group let pickerGroup; switch (this.props.mode) { case 'multiSelector': pickerGroup = getMultiSelector(); break; case 'time': pickerGroup = getTimeSelector(); break; case 'date': pickerGroup = gitDateSelector(); break; default: pickerGroup = getSelector(); } const { name = '' } = this.props; return (
{this.props.children}
{/*
取消
确定
{pickerGroup}
*/}
); } }