219 lines
4.2 KiB
Vue
219 lines
4.2 KiB
Vue
<template>
|
|
<view class="rate-box" :class="[{ animation }, containerClass]" @touchmove="ontouchmove" @touchend="touchMoving = false">
|
|
<view
|
|
v-for="(val, i) in list"
|
|
:key="val"
|
|
class="rate"
|
|
:style="{ fontSize, paddingLeft: i !== 0 ? rateMargin : 0, paddingRight: i < list.length - 1 ? rateMargin : 0 }"
|
|
:class="[
|
|
{ scale: !disabled && val <= rateValue && animation && touchMoving, 'color-base-text': val <= rateValue, defaultColor: val > rateValue },
|
|
`rate-${i}`,
|
|
rateClass
|
|
]"
|
|
:data-val="val"
|
|
@click="onItemClick"
|
|
>
|
|
<text class="iconfont icon-star"></text>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { getClientRect } from './common';
|
|
|
|
export default {
|
|
name: 'sx-rate',
|
|
props: {
|
|
// 当前值
|
|
value: {
|
|
type: [Number, String]
|
|
},
|
|
// 最大星星数量
|
|
max: {
|
|
type: Number,
|
|
default: 5
|
|
},
|
|
// 禁用
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 动画效果
|
|
animation: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
// 默认星星颜色
|
|
defaultColor: {
|
|
type: String,
|
|
default: '#ccc'
|
|
},
|
|
// 滑选后星星颜色
|
|
activeColor: {
|
|
type: String
|
|
// default: '#FFB700'
|
|
},
|
|
// 星星大小
|
|
fontSize: {
|
|
type: String,
|
|
default: 'inherit'
|
|
},
|
|
// 星星间距
|
|
margin: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 自定义类名-容器
|
|
containerClass: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 自定义类名-星星
|
|
rateClass: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
index: {
|
|
// 如果页面中存在多个该组件,通过该属性区分
|
|
type: [Number, String]
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
rateValue: 0,
|
|
touchMoving: false,
|
|
startX: [],
|
|
startW: 30
|
|
};
|
|
},
|
|
computed: {
|
|
list() {
|
|
return [...new Array(this.max)].map((_, i) => i + 1);
|
|
},
|
|
rateMargin() {
|
|
let margin = this.margin;
|
|
if (!margin) return 0;
|
|
|
|
switch (typeof margin) {
|
|
case 'number':
|
|
margin = margin * 2 + 'rpx';
|
|
case 'string':
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
let reg = /^(\d+)([^\d]*)/;
|
|
let result = reg.exec(margin);
|
|
if (!result) return 0;
|
|
|
|
let [_, num, unit] = result;
|
|
return num / 2 + unit;
|
|
}
|
|
},
|
|
watch: {
|
|
value: {
|
|
handler(val) {
|
|
this.rateValue = val;
|
|
},
|
|
immediate: true
|
|
}
|
|
},
|
|
methods: {
|
|
// 计算星星位置
|
|
async initStartX() {
|
|
let { max } = this;
|
|
this.startX = [];
|
|
|
|
for (let i = 0; i < max; i++) {
|
|
let selector = `.rate-${i}`;
|
|
let { left, width } = await getClientRect(selector, this);
|
|
|
|
this.startX.push(left);
|
|
this.startW = width;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 手指滑动事件回调
|
|
* https://github.com/sunxi1997/uni-app-sx-rate/pull/1
|
|
* 原本的触摸处理在自定了样式后可能会出现bug, 已解决
|
|
*/
|
|
async ontouchmove(e) {
|
|
if (!this.touchMoving) {
|
|
this.touchMoving = true;
|
|
// 开始手指滑动时重新计算星星位置,防止星星位置意外变化
|
|
await this.initStartX();
|
|
}
|
|
|
|
let { startX, startW, max } = this;
|
|
let { touches } = e;
|
|
|
|
// 触摸焦点停留的位置
|
|
let { pageX } = touches[touches.length - 1];
|
|
|
|
// 超出最左边, 0 星
|
|
if (pageX <= startX[0]) return this.toggle(0);
|
|
// 刚好在第一颗星
|
|
else if (pageX <= startX[0] + startW) return this.toggle(1);
|
|
// 超出最右边, 最大星
|
|
else if (pageX >= startX[max - 1]) return this.toggle(max);
|
|
|
|
//计算星星停留的位置
|
|
let startXHash = startX.concat(pageX).sort((a, b) => a - b);
|
|
this.toggle(startXHash.indexOf(pageX));
|
|
},
|
|
// 点击回调
|
|
onItemClick(e) {
|
|
let { val } = e.currentTarget.dataset;
|
|
this.toggle(val);
|
|
},
|
|
// 修改值
|
|
toggle(val) {
|
|
let { disabled } = this;
|
|
if (disabled) return;
|
|
if (this.rateValue !== val) {
|
|
this.rateValue = val;
|
|
this.$emit('update:value', val);
|
|
let data = {
|
|
index: this.index,
|
|
value: val
|
|
};
|
|
this.$emit('change', data);
|
|
}
|
|
}
|
|
},
|
|
mounted() {}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
@import './sx-rate/iconfont.css';
|
|
</style>
|
|
|
|
<style lang="scss">
|
|
.rate-box {
|
|
min-height: 1.4em;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.rate {
|
|
display: inline-flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: 1.2em;
|
|
transition: all 0.15s linear;
|
|
}
|
|
|
|
.rate.scale {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.defaultColor {
|
|
color: #ccc !important;
|
|
}
|
|
.activeColor {
|
|
}
|
|
</style>
|