yunshangxie/tuniao-ui/components/tn-rate/tn-rate.vue

326 lines
8.4 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view :id="elId" class="tn-rate-class tn-rate" @touchmove.stop.prevent="touchMove">
<view class="tn-rate__wrap" :class="[elClass]" v-for="(item, index) in count" :key="index">
<view class="tn-rate__wrap__icon"
:class="[`tn-icon-${(allowHalf && halfIcon ? activeIndex > index + 1 : activeIndex > index) ? elActionIcon : elInactionIcon}`]"
:style="[iconStyle(index)]" @tap="click(index + 1, $event)">
<!-- 半图标 -->
<view v-if="showHalfIcon(index)" class="tn-rate__wrap__icon--half" :class="[`tn-icon-${elActionIcon}`]"
:style="[halfIconStyle]"></view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'tn-rate',
props: {
// 选中星星的数量
value: {
type: Number,
default: 0
},
// 显示的星星数
count: {
type: Number,
default: 5
},
// 最小能选择的星星数
minCount: {
type: Number,
default: 0
},
// 禁用状态
disabled: {
type: Boolean,
default: false
},
// 是否可以选择半星
allowHalf: {
type: Boolean,
default: false
},
// 星星大小
size: {
type: Number,
default: 32
},
// 被选中的图标
activeIcon: {
type: String,
default: 'star-fill'
},
// 未被选中的图标
inactiveIcon: {
type: String,
default: 'star'
},
// 被选中的颜色
activeColor: {
type: String,
default: '#01BEFF'
},
// 默认颜色
inactiveColor: {
type: String,
default: '#AAAAAA'
},
// 星星之间的距离
gutter: {
type: Number,
default: 10
},
// 自定义颜色
colors: {
type: Array,
default () {
return []
}
},
// 自定义图标
icons: {
type: Array,
default () {
return []
}
}
},
computed: {
// 图标显示的比例
showHalfIcon(index) {
return index => {
return this.allowHalf && Math.ceil(this.activeIndex) === index + 1 && this.halfIcon
}
},
// 被激活的图标
elActionIcon() {
const len = this.icons.length
// icons参数传递了3个图标当选中2个时用第一个图标4个时用第二个图标
// 5个时用第三个图标作为激活的图标
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.activeIcon
},
// 未被激活的图标
elInactionIcon() {
const len = this.icons.length
// icons参数传递了3个图标当选中2个时用第一个图标4个时用第二个图标
// 5个时用第三个图标作为激活的图标
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.inactiveIcon
},
// 被激活的颜色
elActionColor() {
const len = this.colors.length
// 如果有设置colors参数(此参数用于将图标分段比如一共5颗星colors传3个颜色值那么根据一定的规则2颗星可能为第一个颜色
// 4颗星为第二个颜色值5颗星为第三个颜色值)
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.colors[0]
if (step > len) return this.colors[len - 1]
return this.colors[step - 1]
}
return this.activeColor
},
// 图标的样式
iconStyle() {
return index => {
let style = {}
style.fontSize = this.$tn.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$tn.string.getLengthUnitValue(this.gutter / 2)}`
// 当前图标的颜色
if (this.allowHalf && this.halfIcon) {
style.color = this.activeIndex > index + 1 ? this.elActionColor : this.inactiveColor
} else {
style.color = this.activeIndex > index ? this.elActionColor : this.inactiveColor
}
return style
}
},
// 半图标样式
halfIconStyle() {
let style = {}
style.fontSize = this.$tn.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$tn.string.getLengthUnitValue(this.gutter / 2)}`
style.color = this.elActionColor
return style
}
},
data() {
return {
// 保证控件的唯一性
elId: this.$tn.uuid(),
elClass: this.$tn.uuid(),
// 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
starBoxLeft: 0,
// 当前激活的星星的序号
activeIndex: this.value,
// 每个星星的宽度
starWidth: 0,
// 每个星星最右边到盒子组件最左边的距离
starWidthArr: [],
// 标记是否为半图标
halfIcon: false,
}
},
watch: {
value: {
handler(newName, oldName) {
this.activeIndex = newName
if (this.allowHalf && (newName % 1 === 0.5)) {
this.halfIcon = true
} else {
this.halfIcon = false
}
},
immediate: true
},
size() {
// 当尺寸修改的时候重新获取布局尺寸信息
this.$nextTick(() => {
this.getElRectById()
this.getElRectByClass()
})
}
},
mounted() {
this.getElRectById()
this.getElRectByClass()
},
methods: {
// 获取评分组件盒子的布局信息
getElRectById() {
this._tGetRect('#' + this.elId).then(res => {
this.starBoxLeft = res.left
})
},
// 获取单个星星的尺寸
getElRectByClass() {
this._tGetRect('.' + this.elClass).then(res => {
this.starWidth = res.width
// 把每个星星最右边到盒子最左边的距离
for (let i = 0; i < this.count; i++) {
this.starWidthArr[i] = (i + 1) * this.starWidth
}
})
},
// 手指滑动
touchMove(e) {
if (this.disabled) return
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
// 滑动点相对于评分盒子左边的距离
const distance = movePageX - this.starBoxLeft
// 如果滑动到了评分盒子的左边界设置为0星
if (distance <= 0) {
this.activeIndex = 0
}
// 计算滑动的距离相当于点击多少颗星星
let index = Math.ceil(distance / this.starWidth)
if (this.allowHalf) {
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
index -= 0.5
} else {
this.halfIcon = false
}
}
this.activeIndex = index > this.count ? this.count : index
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
// 通过点击直接选中
click(index, e) {
if (this.disabled) return
// 半星选择
if (this.allowHalf) {
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
// 点击点相对于当前图标左边的距离
const distance = movePageX - this.starBoxLeft
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
} else {
this.halfIcon = false
}
}
// 对第一个星星特殊处理只有一个的时候点击可以取消否则无法作0星评价
if (index == 1) {
if (this.allowHalf && this.allowHalf) {
if ((this.activeIndex === 0.5 && this.halfIcon) ||
(this.activeIndex === 1 && !this.halfIcon)) {
this.activeIndex = 0
} else {
this.activeIndex = this.halfIcon ? 0.5 : 1
}
} else {
if (this.activeIndex == 1) {
this.activeIndex = 0
} else {
this.activeIndex = 1
}
}
} else {
this.activeIndex = (this.allowHalf && this.halfIcon) ? index - 0.5 : index
}
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
// 发送事件
emitEvent() {
this.$emit('change', this.activeIndex)
// 修改v-model的值
this.$emit('input', this.activeIndex)
}
}
}
</script>
<style lang="scss" scoped>
.tn-rate {
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
&__wrap {
&__icon {
position: relative;
box-sizing: border-box;
&--half {
position: absolute;
top: 0;
left: 0;
display: inline-block;
overflow: hidden;
width: 50%;
}
}
}
}
</style>