榆山数据端小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

353 lines
10 KiB

import baseComponent from '../helpers/baseComponent'
import classNames from '../helpers/classNames'
import eventsMixin from '../helpers/eventsMixin'
import styleToCssString from '../helpers/styleToCssString'
import { getTouchPoints, getPointsNumber } from '../helpers/gestures'
/**
* 获取小数位数
*/
const getPrecision = (step) => {
const stepString = step.toString()
return stepString.indexOf('.') >= 0 ? stepString.length - stepString.indexOf('.') - 1 : 0
}
/**
* 返回精度正确的值
*/
const checkValuePrecision = (val, step, min) => {
const closestStep = Math.round((val - min) / step) * step + min
const precision = getPrecision(step)
return parseFloat(closestStep.toFixed(precision))
}
/**
* 获取样式
*/
const getStyles = (value) => {
return Array.isArray(value) ? value.map((n) => styleToCssString(n)) : styleToCssString(value)
}
const defaultEvents = {
onChange() {},
onAfterChange() {},
}
baseComponent({
behaviors: [eventsMixin({ defaultEvents })],
relations: {
'../field/index': {
type: 'ancestor',
},
},
properties: {
prefixCls: {
type: String,
value: 'wux-slider',
},
min: {
type: Number,
value: 0,
observer: 'getMarks',
},
max: {
type: Number,
value: 100,
observer: 'getMarks',
},
step: {
type: Number,
value: 1,
observer: 'getMarks',
},
defaultValue: {
type: Array,
value: [0],
},
value: {
type: Array,
value: [0],
observer(newVal) {
if (this.data.controlled) {
this.updated(newVal)
}
},
},
controlled: {
type: Boolean,
value: false,
},
disabled: {
type: Boolean,
value: false,
},
showMark: {
type: Boolean,
value: false,
},
showValue: {
type: [Boolean, Object],
value: false,
},
tipFormatter: {
type: String,
value: '{d}',
},
markStyle: {
type: [String, Object, Array],
value: '',
observer(newVal) {
this.setData({
extMarkStyle: getStyles(newVal),
})
},
},
handleStyle: {
type: [String, Object, Array],
value: '',
observer(newVal) {
this.setData({
extHandleStyle: getStyles(newVal),
})
},
},
trackStyle: {
type: [String, Object, Array],
value: '',
observer(newVal) {
this.setData({
extTrackStyle: getStyles(newVal),
})
},
},
railStyle: {
type: [String, Object],
value: '',
observer(newVal) {
this.setData({
extRailStyle: styleToCssString(newVal),
})
},
},
wrapStyle: {
type: [String, Object],
value: '',
observer(newVal) {
this.setData({
extWrapStyle: styleToCssString(newVal),
})
},
},
},
data: {
offsets: [],
inputValue: [],
extMarkStyle: '',
extHandleStyle: '',
extTrackStyle: '',
extRailStyle: '',
extWrapStyle: '',
isTouched: false,
swiping: false,
},
computed: {
classes: ['prefixCls, disabled, tipFormatter', function(prefixCls, disabled, tipFormatter) {
const wrap = classNames(prefixCls, {
[`${prefixCls}--disabled`]: disabled,
[`${prefixCls}--has-tip`]: !!tipFormatter,
})
const min = `${prefixCls}__min`
const rail = `${prefixCls}__rail`
const mark = `${prefixCls}__mark`
const track = `${prefixCls}__track`
const handle = `${prefixCls}__handle`
const max = `${prefixCls}__max`
return {
wrap,
min,
rail,
mark,
track,
handle,
max,
}
}],
},
observers: {
inputValue(newVal) {
const offsets = newVal.map((value) => this.calcOffset(this.checkValue(value)))
this.setData({ offsets })
},
},
methods: {
/**
* 更新选中值及偏移量
*/
updated(inputValue) {
if (this.hasFieldDecorator) return
if (this.data.inputValue !== inputValue) {
this.setData({ inputValue })
}
},
/**
* 手指触摸动作开始
*/
onTouchStart(e) {
if (this.data.disabled || getPointsNumber(e) > 1) return
const { index } = e.currentTarget.dataset
this.isMoved = false
this.startX = getTouchPoints(e).x
this.moveX = 0
// 记录选中值发生改变时的初始偏移量
this.startPos = this.data.offsets[index] || 0
// 记录最后一次选中项
this.setData({ last: index, isTouched: true, isMoved: false })
},
/**
* 手指触摸后移动
*/
onTouchMove(e) {
if (this.data.disabled || getPointsNumber(e) > 1) return
const { index } = e.currentTarget.dataset
const { prefixCls } = this.data
this.isMoved = true
this.setData({ isMoved: true })
this.moveX = getTouchPoints(e).x
this.getRect(`.${prefixCls}__rail`).then((rect) => {
if (!rect || !this.isMoved) return
const diffX = (this.moveX - this.startX) / rect.width * 100
const nextOffsets = [...this.data.offsets]
const offset = this.checkValue(this.startPos + diffX, 0, 100)
const { inputValue } = this.data
const currentValue = this.calcValue(offset)
const prevValue = inputValue[index - 1]
const nextValue = inputValue[index + 1]
// 通过合法的当前值反算偏移量
nextOffsets[index] = this.calcOffset(currentValue)
// 判断当前值是否小于前一值,是则重新计算偏移量
if (prevValue && prevValue > currentValue) {
nextOffsets[index] = this.calcOffset(prevValue)
}
// 判断当前值是否大于后一值,是则重新计算偏移量
if (nextValue && nextValue < currentValue) {
nextOffsets[index] = this.calcOffset(nextValue)
}
// 判断当前值是否发生变化,是则触发 change 事件
if (inputValue[index] !== currentValue) {
const value = this.getValue(nextOffsets)
if (!this.data.controlled) {
this.updated(value)
}
this.triggerEvent('change', { offsets: nextOffsets, value })
}
})
},
/**
* 手指触摸动作结束
*/
onTouchEnd(e) {
if (this.data.disabled || getPointsNumber(e) > 1 || !this.isMoved) return
this.isMoved = false
this.setData({ isTouched: false, isMoved: false })
const { offsets } = this.data
const value = this.getValue(offsets)
this.triggerEvent('afterChange', { offsets, value })
},
/**
* 获取界面上的节点信息
*/
getRect(selector, all) {
return new Promise((resolve) => {
wx
.createSelectorQuery()
.in(this)[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect) => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect)
}
if (!all && rect) {
resolve(rect)
}
})
.exec()
})
},
/**
* 计算选中值
*/
calcValue(ratio) {
const { min, max } = this.data
return this.trimValue(ratio * (max - min) / 100 + min)
},
/**
* 计算偏移量
*/
calcOffset(value) {
const { min, max } = this.data
const ratio = (value - min) / (max - min)
return ratio * 100
},
/**
* 判断元素是否在指定的范围内
*/
checkValue(val, min = this.data.min, max = this.data.max) {
if (val <= min) {
return min
}
if (val >= max) {
return max
}
return val
},
/**
* 验证选中值
*/
trimValue(val) {
return checkValuePrecision(this.checkValue(val), this.data.step, this.data.min)
},
/**
* 获取选中值
*/
getValue(offsets = this.data.offsets) {
return offsets.map((offset) => this.calcValue(offset))
},
/**
* 获取间断点位置
*/
getMarks() {
if (!this.data.showMark) return
const {min, max, step } = this.data
const count = (max - min) / step
const marks = []
const offset = 100 * step / (max - min)
for (let i = 1; i < count; i++) {
marks.push(i * offset)
}
this.setData({ marks })
},
noop() {},
},
attached() {
const { defaultValue, value, controlled } = this.data
const inputValue = controlled ? value : defaultValue
this.getMarks()
this.updated(inputValue)
},
})