tt-mp
Version:
一套组件化、可复用、易扩展的头条小程序 UI 组件库
215 lines (204 loc) • 5.37 kB
JavaScript
import baseComponent from '../helpers/baseComponent'
import classNames from '../helpers/classNames'
import eventsMixin from '../helpers/eventsMixin'
baseComponent({
behaviors: [eventsMixin()],
relations: {
'../field/index': {
type: 'ancestor',
},
},
properties: {
prefixCls: {
type: String,
value: 'wux-rater',
},
max: {
type: Number,
value: 5,
observer() {
this.setValue(this.data.inputValue)
},
},
icon: {
type: String,
value: '',
},
star: {
type: String,
value: '★',
},
defaultValue: {
type: Number,
value: 0,
},
value: {
type: Number,
value: 0,
observer(newVal) {
if (this.data.controlled) {
this.setValue(newVal)
}
},
},
activeColor: {
type: String,
value: '#ffc900',
},
margin: {
type: Number,
value: 2,
},
fontSize: {
type: Number,
value: 25,
},
disabled: {
type: Boolean,
value: false,
},
allowHalf: {
type: Boolean,
value: false,
},
allowClear: {
type: Boolean,
value: false,
},
allowTouchMove: {
type: Boolean,
value: false,
},
controlled: {
type: Boolean,
value: false,
},
},
data: {
inputValue: 0,
},
computed: {
classes: [
'prefixCls, disabled',
function (prefixCls, disabled) {
const wrap = classNames(prefixCls, {
[`${prefixCls}--disabled`]: disabled,
})
const star = `${prefixCls}__star`
const box = `${prefixCls}__box`
const inner = `${prefixCls}__inner`
const outer = `${prefixCls}__outer`
const icon = `${prefixCls}__icon`
return {
wrap,
star,
box,
inner,
outer,
icon,
}
},
],
},
observers: {
['inputValue, max, activeColor'](inputValue, max, activeColor) {
const stars = [...new Array(max)].map((_, i) => i)
const colors = stars.reduce(
(a, _, i) => [...a, i <= inputValue - 1 ? activeColor : '#ccc'],
[]
)
const _val = inputValue.toString().split('.')
const sliceValue = _val.length === 1 ? [_val[0], 0] : _val
this.setData({
stars,
colors,
cutIndex: sliceValue[0] * 1,
cutPercent: sliceValue[1] * 10,
})
},
},
methods: {
updated(inputValue) {
if (this.hasFieldDecorator) return
if (this.data.inputValue !== inputValue) {
this.setData({ inputValue })
}
},
setValue(value) {
const { max } = this.data
const inputValue = value <= 0 ? 0 : value > max ? max : value
this.updated(inputValue)
},
updateHalfStarValue(index, x, cb) {
const { prefixCls } = this.data
const query = tt.createSelectorQuery().in(this)
query
.selectAll(`.${prefixCls}__star`)
.boundingClientRect((rects) => {
if (rects.filter((n) => !n).length) return
const { left, width } = rects[index]
const has = x - left < width / 2
const value = has ? index + 0.5 : index + 1
cb.call(this, value, index)
})
.exec()
},
onTap(e) {
const { index } = e.currentTarget.dataset
const { inputValue, disabled, allowHalf, allowClear } = this.data
// 判断是否禁用
if (!disabled) {
// 判断是否支持选中半星
if (!allowHalf) {
const value = index + 1
const isReset = allowClear && value === inputValue
this.onChange(isReset ? 0 : value, index)
} else {
this.updateHalfStarValue(index, e.detail.x, (value, index) => {
const isReset = allowClear && value === inputValue
this.onChange(isReset ? 0 : value, index)
})
}
}
},
onChange(value, index) {
if (!this.data.controlled) {
this.setValue(value)
}
this.triggerEvent('change', { value, index })
},
onTouchMove(e) {
const { disabled, allowHalf, allowTouchMove } = this.data
if (!disabled && allowTouchMove) {
const x = e.changedTouches[0].pageX
const { prefixCls } = this.data
const query = tt.createSelectorQuery().in(this)
query
.selectAll(`.${prefixCls}__star`)
.boundingClientRect((rects) => {
if (rects.filter((n) => !n).length) return
const { left, width } = rects[0]
const maxWidth = rects.map((n) => n.width).reduce((a, b) => a + b)
const diff = x - left
let value = Math.ceil(diff / width)
// 判断是否在组件宽度范围内
if (diff > 0 && diff < maxWidth) {
const index = value - 1
if (allowHalf) {
const star = rects[index]
const has = x - star.left < star.width / 2
value = has ? value - 0.5 : value
}
this.onChange(value, index)
}
})
.exec()
}
},
},
attached() {
const { defaultValue, value, controlled } = this.data
const inputValue = controlled ? value : defaultValue
this.setValue(inputValue)
},
})