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.
231 lines
6.6 KiB
231 lines
6.6 KiB
|
3 years ago
|
<template>
|
||
|
|
<view
|
||
|
|
:id="$sUid"
|
||
|
|
class="s-rate"
|
||
|
|
:class="c_class"
|
||
|
|
:style="custom_style"
|
||
|
|
@touchstart="onTouchStart"
|
||
|
|
@touchmove.prevent="onTouchMove"
|
||
|
|
>
|
||
|
|
<view
|
||
|
|
v-for="(item, index) of list"
|
||
|
|
:key="index"
|
||
|
|
class="s-rate__item"
|
||
|
|
:style="itemStyleList[index]"
|
||
|
|
@click="onItemClick($event, index)"
|
||
|
|
>
|
||
|
|
<s-icon
|
||
|
|
:class-prefix="iconPrefix"
|
||
|
|
:size="size"
|
||
|
|
:name="item.status === 'full' ? fullIcon : voidIcon"
|
||
|
|
:color="disabled ? disabledColor : item.status === 'full' ? fullColor : voidColor"
|
||
|
|
:img-width="imgWidth"
|
||
|
|
:img-height="imgHeight"
|
||
|
|
:custom-class="
|
||
|
|
$mergeClass('s-rate__icon', {
|
||
|
|
's-rate__icon--full': item.status === 'full',
|
||
|
|
's-rate__icon--disabled': disabled,
|
||
|
|
})
|
||
|
|
"
|
||
|
|
/>
|
||
|
|
<s-icon
|
||
|
|
v-if="allowHalf && item.value > 0 && item.value < 1"
|
||
|
|
:class-prefix="iconPrefix"
|
||
|
|
:size="size"
|
||
|
|
:name="item.status === 'void' ? voidIcon : fullIcon"
|
||
|
|
:color="disabled ? disabledColor : item.status === 'void' ? voidColor : fullColor"
|
||
|
|
mode="heightFix"
|
||
|
|
:img-width="imgWidth"
|
||
|
|
:img-height="imgHeight"
|
||
|
|
:custom-class="
|
||
|
|
$mergeClass('s-rate__icon s-rate__icon--half', {
|
||
|
|
's-rate__icon--full': item.status !== 'void',
|
||
|
|
's-rate__icon--disabled': disabled,
|
||
|
|
})
|
||
|
|
"
|
||
|
|
:custom-style="{ width: item.value * 100 + '%' }"
|
||
|
|
/>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import componentMixin from '../../mixins/componentMixin';
|
||
|
|
import useTouch from '../../common/useTouch';
|
||
|
|
import getPercentRangeValue from '../../utils/getPercentRangeValue';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* s-rate 评分
|
||
|
|
* @description 用于对事物进行评级操作
|
||
|
|
* @property {Number} value 当前分值
|
||
|
|
* @property {Number|String} count 图标总数
|
||
|
|
* @property {Number|String} size 图标大小
|
||
|
|
* @property {String|Array|Object} color 选中时的颜色
|
||
|
|
* @property {String} voidColor 未选中时的颜色
|
||
|
|
* @property {String} icon 选中时的图标名称或图片链接
|
||
|
|
* @property {Number|String} gutter 图标间距(默认单位为 rpx)
|
||
|
|
* @property {String} disabledColor 禁用时的颜色
|
||
|
|
* @property {String} voidIcon 未选中时的图标名称或图片链接
|
||
|
|
* @property {String} iconPrefix 图标类名前缀,等同于 Icon 组件的 class-prefix 属性
|
||
|
|
* @property {Boolean} allowHalf 是否允许半选
|
||
|
|
* @property {Boolean} readonly 是否为只读状态,只读状态下无法修改评分
|
||
|
|
* @property {Boolean} disabled 是否禁用评分
|
||
|
|
* @property {Boolean} touchable 是否可以通过滑动手势选择评分
|
||
|
|
* @event {Function} change (value) 改变后的值
|
||
|
|
* @example <s-rate v-model="value" />
|
||
|
|
*/
|
||
|
|
export default {
|
||
|
|
name: 'SRate',
|
||
|
|
mixins: [componentMixin],
|
||
|
|
props: {
|
||
|
|
value: {
|
||
|
|
type: Number,
|
||
|
|
default: 0,
|
||
|
|
},
|
||
|
|
count: {
|
||
|
|
type: [Number, String],
|
||
|
|
default: 5,
|
||
|
|
},
|
||
|
|
size: [Number, String],
|
||
|
|
color: [String, Array, Object],
|
||
|
|
icon: {
|
||
|
|
type: [String, Array, Object],
|
||
|
|
default: 'star',
|
||
|
|
},
|
||
|
|
voidIcon: {
|
||
|
|
type: String,
|
||
|
|
default: 'star-o',
|
||
|
|
},
|
||
|
|
gutter: [Number, String],
|
||
|
|
readonly: Boolean,
|
||
|
|
disabled: Boolean,
|
||
|
|
allowHalf: Boolean,
|
||
|
|
voidColor: String,
|
||
|
|
touchable: {
|
||
|
|
type: Boolean,
|
||
|
|
default: true,
|
||
|
|
},
|
||
|
|
iconPrefix: String,
|
||
|
|
disabledColor: String,
|
||
|
|
imgWidth: [Number, String],
|
||
|
|
imgHeight: [Number, String],
|
||
|
|
},
|
||
|
|
computed: {
|
||
|
|
c_class() {
|
||
|
|
return this.$mergeClass({
|
||
|
|
's-rate--readonly': this.readonly,
|
||
|
|
's-rate--disabled': this.disabled,
|
||
|
|
}, this.custom_class);
|
||
|
|
},
|
||
|
|
list() {
|
||
|
|
const list = [];
|
||
|
|
const len = +this.count;
|
||
|
|
for (let i = 0; i < len; i++) {
|
||
|
|
list.push(this.getRateStatus(
|
||
|
|
this.value,
|
||
|
|
i + 1,
|
||
|
|
this.allowHalf,
|
||
|
|
this.readonly,
|
||
|
|
));
|
||
|
|
}
|
||
|
|
return list;
|
||
|
|
},
|
||
|
|
disabledTouch() {
|
||
|
|
return this.readonly || this.disabled || !this.touchable;
|
||
|
|
},
|
||
|
|
fullColor() {
|
||
|
|
return getPercentRangeValue(this.color, this.value, 'value', 'color');
|
||
|
|
},
|
||
|
|
fullIcon() {
|
||
|
|
return getPercentRangeValue(this.icon, this.value, 'value', 'icon');
|
||
|
|
},
|
||
|
|
itemStyleList() {
|
||
|
|
const { gutter } = this;
|
||
|
|
return this.list.map((item, index) => {
|
||
|
|
const style = {};
|
||
|
|
if (gutter && index > 0) {
|
||
|
|
style.marginLeft = this.$addUnit(gutter);
|
||
|
|
}
|
||
|
|
return this.$mergeStyle(style);
|
||
|
|
});
|
||
|
|
},
|
||
|
|
},
|
||
|
|
created() {
|
||
|
|
this.ranges = [];
|
||
|
|
this.touch = useTouch();
|
||
|
|
},
|
||
|
|
mounted() {
|
||
|
|
this.updateRanges();
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
getRateStatus(value, index, allowHalf, readonly) {
|
||
|
|
if (value >= index) {
|
||
|
|
return { status: 'full', value: 1 };
|
||
|
|
}
|
||
|
|
if (value + 0.5 >= index && allowHalf && !readonly) {
|
||
|
|
return { status: 'half', value: 0.5 };
|
||
|
|
}
|
||
|
|
if (value + 1 >= index && allowHalf && readonly) {
|
||
|
|
const cardinal = 10 ** 10;
|
||
|
|
return {
|
||
|
|
status: 'half',
|
||
|
|
value: Math.round((value - index + 1) * cardinal) / cardinal,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return { status: 'void', value: 0 };
|
||
|
|
},
|
||
|
|
onTouchStart(e) {
|
||
|
|
if (this.disabledTouch) return;
|
||
|
|
this.updateRanges();
|
||
|
|
this.touch.start(e);
|
||
|
|
},
|
||
|
|
onTouchMove(e) {
|
||
|
|
if (this.disabledTouch) return;
|
||
|
|
this.touch.move(e);
|
||
|
|
if (this.touch.isHorizontal()) {
|
||
|
|
const { clientX } = e.touches[0];
|
||
|
|
this.select(this.getScoreByPosition(clientX));
|
||
|
|
}
|
||
|
|
},
|
||
|
|
updateRanges() {
|
||
|
|
this.$nextTick(() => {
|
||
|
|
this.$getRect(`#${this.$sUid} .s-rate__item`, true).then(rects => {
|
||
|
|
this.ranges = [];
|
||
|
|
rects.forEach((rect, index) => {
|
||
|
|
if (this.allowHalf) {
|
||
|
|
this.ranges.push(
|
||
|
|
{ score: index + 0.5, left: rect.left },
|
||
|
|
{ score: index + 1, left: rect.left + rect.width / 2 },
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
this.ranges.push({ score: index + 1, left: rect.left });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
},
|
||
|
|
getScoreByPosition(x) {
|
||
|
|
for (let i = this.ranges.length - 1; i > 0; i--) {
|
||
|
|
if (x > this.ranges[i].left) {
|
||
|
|
return this.ranges[i].score;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return this.allowHalf ? 0.5 : 1;
|
||
|
|
},
|
||
|
|
select(value) {
|
||
|
|
if (!this.disabled && !this.readonly && value !== this.value) {
|
||
|
|
this.$emit('input', value);
|
||
|
|
this.$emit('change', value);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onItemClick(e, index) {
|
||
|
|
const { clientX } = this.touch.getTouch(e);
|
||
|
|
this.updateRanges();
|
||
|
|
this.select(this.allowHalf ? this.getScoreByPosition(clientX) : index + 1);
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" src="./index.scss"></style>
|