UNPKG

veui

Version:

Baidu Enterprise UI for Vue.js.

308 lines (284 loc) 9.16 kB
import { padStart, map, includes, findIndex, findLastIndex, intersection, union } from 'lodash' const isArray = Array.isArray /** * @enum {number} */ const MergeOp = { NONE: 0, INTERSECTION: 1, UNION: 2 } const { NONE, INTERSECTION, UNION } = MergeOp export default class TimePickerUtil { datasource = null // [ [1, 2, 3], [1, 2, 3] ] min = null max = null available = null // [ [1, 2, 3], [1, 2, 3] ] /** * @typedef TimePickerUtilOptions * @type {Object} * @property {!Array<Array<number>>} datasource 时间选项的二维数组 * @property {Array<number>=} min 最小值 * @property {Array<number>=} max 最大值 */ constructor (options) { if (options) { this.setOptions(options) } } /** * 修改 util 的配置 * @param {TimePickerUtilOptions} options 配置 * @return {TimePickerUtil} this */ setOptions ({ datasource, min, max }) { if ( !isArray(datasource) || !datasource.length || datasource.some((i) => !isArray(i)) ) { throw new Error('[veui-time-picker] Time options are not valid.') } this.datasource = map(datasource, (i) => i.sort((a, b) => (a > b ? 1 : -1))) this.min = min || datasource.map((i) => -Infinity) this.max = max || datasource.map((i) => Infinity) // null 表示尚未计算 this.available = null return this } /** * 这个可以和 getMaxTime 合并到一起,但是感觉可读性下降很多 * 计算最小的时间, 这个时间有可能是超过的最大值: * 对于 datasource : [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ], min:[2, 3, 8] * 1:第一位选取 2,等于对应的最值 2 ,prevEdge 是 true * 2:因为 prevEdge 是 true, 所以选择大于 3 的值即 4, prevEdge是 false * 3:因为 prevEdge 是 false,直接选取 0 处值,即 7 * 得到结果:[2, 4, 7] * @param {number} index 内部递归的位置 * @param {boolean} prevEdge 内部递归的状态,表示之前选取是贴着最小值的 * @return {?Array<number>} 没有值返回 null */ getMinTime (index = 0, prevEdge) { if (!index) { prevEdge = true } let source = this.datasource[index] // 根据上层选取的情况来决定决定本次选取的位置 let start = prevEdge ? findIndex(source, (i) => i >= this.min[index]) : 0 if (start > -1) { let val = source[start] // 上层贴着,本层值和最值相等,那么本层才是贴着最值得 let selfEdge = prevEdge && val === this.min[index] if (index + 1 < this.datasource.length) { // 还有下层,递归下,下层返回 null 表示下层选取失败了 let childResult = this.getMinTime(index + 1, selfEdge) if (childResult) { return [source[start], ...childResult] } // 本层是贴着最小值,但下层没有找到,那么本层: // 1. 试着不贴,因为不贴了,下层会直接选取0处值,所以肯定会成功的 // 2. 本层贴着已经是可选值中最大的了,那么返回 undefined,让上层来处理 if (selfEdge && start + 1 < source.length) { return [source[start + 1], ...this.getMinTime(index + 1, false)] } } else { // 本层已经是最后一层了,直接返回本层的结果 return [source[start]] } } // 本次没有可选值,直接返回 null,让上层来处理 return null } getMaxTime (index = 0, prevEdge = false) { if (!index) { prevEdge = true } let source = this.datasource[index] let start = prevEdge ? findLastIndex(source, (i) => i <= this.max[index]) : source.length - 1 if (start > -1) { let val = source[start] let selfEdge = prevEdge && val === this.max[index] if (index + 1 < this.datasource.length) { let childResult = this.getMaxTime(index + 1, selfEdge) if (childResult) { return [source[start], ...childResult] } if (selfEdge && start - 1 >= 0) { return [source[start - 1], ...this.getMaxTime(index + 1, false)] } } else { return [source[start]] } } return null } updateAvailable () { let min = (this.aMin = this.getMinTime()) if (!min || !this.isAvailable(min)) { this.available = [] } else { // 最小值选取成功了,那么最大值也一定能取到 let max = this.getMaxTime() this.available = this.calcAvailable(min, max, NONE) } return this.available } /** * 获取可用的数据 * @public * @return {Array} 可用的数据 */ getAvailable () { if (!this.available) { this.updateAvailable() } return this.available } /** * 通过两个最值来计算每个部分的可选值 * @protected * @param {Array<number>} minTime getMinTime 计算出来的最小值(要保证合法) * @param {Array<number>} maxTime getMaxTime 计算出来的最大值(要保证合法) * @param {MergeOp} prevOp 上层对下层如何合并的要求 * @param {number} index 内部递归的位置 * @return {Array<Array<number>>} available */ calcAvailable (minTime, maxTime, prevOp, index = 0) { let min = minTime[index] let max = maxTime[index] let source = this.datasource[index] let op = NONE let result = [] let hasNext = index + 1 < this.datasource.length if (!index) { result = source.filter((i) => i >= min && i <= max) // 0处只有一个值,下层要取交集;0处有两个值,下层要取并集,否则下层取 datasource 中所有值 if (hasNext) { // eslint-disable-next-line no-sparse-arrays op = [, INTERSECTION, UNION][result.length] || NONE } } else if (prevOp === NONE) { result = source } else { let rangeFromMin = source.filter((i) => i >= min) let rangeFromMax = source.filter((i) => i <= max) if (prevOp === INTERSECTION) { result = intersection(rangeFromMin, rangeFromMax).sort((a, b) => a > b ? 1 : -1 ) if (hasNext) { // 1: *──* // 2: * ──* // \──* // eslint-disable-next-line no-sparse-arrays op = [, INTERSECTION, UNION][result.length] || NONE } } else { result = union(rangeFromMin, rangeFromMax).sort((a, b) => a > b ? 1 : -1 ) if (hasNext) { if (result.length === 1) { // *── * // *──/ op = UNION } else if ( intersection(result, rangeFromMin).length === 1 && intersection(result, rangeFromMax).length === 1 ) { // *──* // *──* op = UNION } } } } if (hasNext) { return [result, ...this.calcAvailable(minTime, maxTime, op, index + 1)] } else { return [result] } } /** * 验证时间的合法性:1. 满足最值限制 2. 必须在时间选项中(可选) * @public * @param {!Array<number>} time 被验证的时间 * @param {boolean} checkInDatasource 是否要验证在时间选项中 * @return {boolean} 结果 */ isAvailable (time, checkInDatasource) { let valid = true if (checkInDatasource) { if (!this.available) { this.updateAvailable() } valid = time.every((i, idx) => includes(this.available[idx], i)) } if (valid) { let timeNum = timeToNumber(time) valid = timeNum >= timeToNumber(this.min) && timeNum <= timeToNumber(this.max) } return valid } /** * 获取指定位置值固定的最小值,如在 available 中寻找 [*, 4, *] 1位是 4 的最小合法值 * @public * @param {number} index 指定位置 * @param {number} val 值 * @return {?Array<number>} 返回找到的最小值或null */ getMinimumTimeOfIndex (index, val) { if (!this.available) { this.updateAvailable() } if (!includes(this.available[index], val)) { return null } let min = [...this.aMin] min[index] = val if (val !== this.aMin[index]) { let idx = index while (++idx < this.datasource.length) { min[idx] = this.available[idx][0] } } if (val < this.aMin[index]) { while (index--) { let idx = findIndex( this.available[index], (i) => i === this.aMin[index] ) if (idx < this.available[index].length - 1) { min[index] = this.available[index][idx + 1] break } else if (index) { // 非0处的最后一个了 min[index] = this.available[index][0] } else { // 0处的最后一个了,找不到了 return null } } } if (this.isAvailable(min)) { return min } return null } } function timeToNumber (time) { return includes([-Infinity, Infinity], time[0]) ? time[0] : parseInt(time.map((i) => padStart(i, 2, '0')).join(''), 10) }