/**
* @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 (