| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 | <template>    <view        class="u-rate"        :id="elId"        ref="u-rate"        :style="[$u.addStyle(customStyle)]"    >        <view            class="u-rate__content"            @touchmove.stop="touchMove"            @touchend.stop="touchEnd"        >            <view                class="u-rate__content__item"                v-for="(item, index) in Number(count)"                :key="index"                :class="[elClass]"            >                <view                    class="u-rate__content__item__icon-wrap"                    ref="u-rate__content__item__icon-wrap"                    @tap.stop="clickHandler($event, index + 1)"                >                    <u-icon                        :name="                            Math.floor(activeIndex) > index                                ? activeIcon                                : inactiveIcon                        "                        :color="                            disabled                                ? '#c8c9cc'                                : Math.floor(activeIndex) > index                                ? activeColor                                : inactiveColor                        "                        :custom-style="{                            'padding-left': $u.addUnit(gutter / 2),							'padding-right': $u.addUnit(gutter / 2)                        }"                        :size="size"                    ></u-icon>                </view>                <view                    v-if="allowHalf"                    @tap.stop="clickHandler($event, index + 1)"                    class="u-rate__content__item__icon-wrap u-rate__content__item__icon-wrap--half"                    :style="[{                        width: $u.addUnit(rateWidth / 2),                    }]"                    ref="u-rate__content__item__icon-wrap"                >                    <u-icon                        :name="                            Math.ceil(activeIndex) > index                                ? activeIcon                                : inactiveIcon                        "                        :color="                            disabled                                ? '#c8c9cc'                                : Math.ceil(activeIndex) > index                                ? activeColor                                : inactiveColor                        "                        :custom-style="{							'padding-left': $u.addUnit(gutter / 2),							'padding-right': $u.addUnit(gutter / 2)                        }"                        :size="size"                    ></u-icon>                </view>            </view>        </view>    </view></template><script>	import props from './props.js';	// #ifdef APP-NVUE	const dom = weex.requireModule("dom");	// #endif	/**	 * rate 评分	 * @description 该组件一般用于满意度调查,星型评分的场景	 * @tutorial https://www.uviewui.com/components/rate.html	 * @property {String | Number}	value			用于v-model双向绑定选中的星星数量 (默认 1 )	 * @property {String | Number}	count			最多可选的星星数量 (默认 5 )	 * @property {Boolean}			disabled		是否禁止用户操作 (默认 false )	 * @property {Boolean}			readonly		是否只读 (默认 false )	 * @property {String | Number}	size			星星的大小,单位px (默认 18 )	 * @property {String}			inactiveColor	未选中星星的颜色 (默认 '#b2b2b2' )	 * @property {String}			activeColor		选中的星星颜色 (默认 '#FA3534' )	 * @property {String | Number}	gutter			星星之间的距离 (默认 4 )	 * @property {String | Number}	minCount		最少选中星星的个数 (默认 1 )	 * @property {Boolean}			allowHalf		是否允许半星选择 (默认 false )	 * @property {String}			activeIcon		选中时的图标名,只能为uView的内置图标 (默认 'star-fill' )	 * @property {String}			inactiveIcon	未选中时的图标名,只能为uView的内置图标 (默认 'star' )	 * @property {Boolean}			touchable		是否可以通过滑动手势选择评分 (默认 'true' )	 * @property {Object}			customStyle		组件的样式,对象形式	 * @event {Function} change 选中的星星发生变化时触发	 * @example <u-rate :count="count" :value="2"></u-rate>	 */	export default {		name: "u-rate",		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],		data() {			return {				// 生成一个唯一id,否则一个页面多个评分组件,会造成冲突				elId: uni.$u.guid(),				elClass: uni.$u.guid(),				rateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离				activeIndex: this.value,				rateWidth: 0, // 每个星星的宽度				// 标识是否正在滑动,由于iOS事件上touch比click先触发,导致快速滑动结束后,接着触发click,导致事件混乱而出错				moving: false,			};		},		watch: {			value(val) {				this.activeIndex = val;			},			activeIndex: 'emitEvent'		},		methods: {			init() {				uni.$u.sleep().then(() => {					this.getRateItemRect();					this.getRateIconWrapRect();				})			},			// 获取评分组件盒子的布局信息			async getRateItemRect() {				await uni.$u.sleep();				// uView封装的获取节点的方法,详见文档				// #ifndef APP-NVUE				this.$uGetRect("#" + this.elId).then((res) => {					this.rateBoxLeft = res.left;				});				// #endif				// #ifdef APP-NVUE				dom.getComponentRect(this.$refs["u-rate"], (res) => {					this.rateBoxLeft = res.size.left;				});				// #endif			},			// 获取单个星星的尺寸			getRateIconWrapRect() {				// uView封装的获取节点的方法,详见文档				// #ifndef APP-NVUE				this.$uGetRect("." + this.elClass).then((res) => {					this.rateWidth = res.width;				});				// #endif				// #ifdef APP-NVUE				dom.getComponentRect(					this.$refs["u-rate__content__item__icon-wrap"][0],					(res) => {						this.rateWidth = res.size.width;					}				);				// #endif			},			// 手指滑动			touchMove(e) {				// 如果禁止通过手动滑动选择,返回				if (!this.touchable) {					return;				}				this.preventEvent(e);				const x = e.changedTouches[0].pageX;				this.getActiveIndex(x);			},			// 停止滑动			touchEnd(e) {				// 如果禁止通过手动滑动选择,返回				if (!this.touchable) {					return;				}				this.preventEvent(e);				const x = e.changedTouches[0].pageX;				this.getActiveIndex(x);			},			// 通过点击,直接选中			clickHandler(e, index) {				// ios上,moving状态取消事件触发				if (uni.$u.os() === "ios" && this.moving) {					return;				}				this.preventEvent(e);				let x = 0;				// 点击时,在nvue上,无法获得点击的坐标,所以无法实现点击半星选择				// #ifndef APP-NVUE				x = e.changedTouches[0].pageX;				// #endif				// #ifdef APP-NVUE				// nvue下,无法通过点击获得坐标信息,这里通过元素的位置尺寸值模拟坐标				x = index * this.rateWidth + this.rateBoxLeft;				// #endif				this.getActiveIndex(x,true);			},			// 发出事件			emitEvent() {				// 发出change事件				this.$emit("change", this.activeIndex);				// 同时修改双向绑定的value的值				this.$emit("input", this.activeIndex);			},			// 获取当前激活的评分图标			getActiveIndex(x,isClick = false) {				if (this.disabled || this.readonly) {					return;				}				// 判断当前操作的点的x坐标值,是否在允许的边界范围内				const allRateWidth = this.rateWidth * this.count + this.rateBoxLeft;				// 如果小于第一个图标的左边界,设置为最小值,如果大于所有图标的宽度,则设置为最大值				x = uni.$u.range(this.rateBoxLeft, allRateWidth, x) - this.rateBoxLeft				// 滑动点相对于评分盒子左边的距离				const distance = x;				// 滑动的距离,相当于多少颗星星				let index;				// 判断是否允许半星				if (this.allowHalf) {					index = Math.floor(distance / this.rateWidth);					// 取余,判断小数的区间范围					const decimal = distance % this.rateWidth;					if (decimal <= this.rateWidth / 2 && decimal > 0) {						index += 0.5;					} else if (decimal > this.rateWidth / 2) {						index++;					}				} else {					index = Math.floor(distance / this.rateWidth);					// 取余,判断小数的区间范围					const decimal = distance % this.rateWidth;					// 非半星时,只有超过了图标的一半距离,才认为是选择了这颗星					if (isClick){						if (decimal > 0) index++;					} else {						if (decimal > this.rateWidth / 2) index++;					}				}				this.activeIndex = Math.min(index, this.count);				// 对最少颗星星的限制				if (this.activeIndex < this.minCount) {					this.activeIndex = this.minCount;				}				// 设置延时为了让click事件在touchmove之前触发				setTimeout(() => {					this.moving = true;				}, 10);				// 一定时间后,取消标识为移动中状态,是为了让click事件无效				setTimeout(() => {					this.moving = false;				}, 10);			},		},		mounted() {			this.init();		},	};</script><style lang="scss" scoped>@import "../../libs/css/components.scss";$u-rate-margin: 0 !default;$u-rate-padding: 0 !default;$u-rate-item-icon-wrap-half-top: 0 !default;$u-rate-item-icon-wrap-half-left: 0 !default;.u-rate {    @include flex;    align-items: center;    margin: $u-rate-margin;    padding: $u-rate-padding;    /* #ifndef APP-NVUE */    touch-action: none;    /* #endif */    &__content {        @include flex;		&__item {		    position: relative;		    &__icon-wrap {		        &--half {		            position: absolute;		            overflow: hidden;		            top: $u-rate-item-icon-wrap-half-top;		            left: $u-rate-item-icon-wrap-half-left;		        }		    }		}    }}.u-icon {    /* #ifndef APP-NVUE */    box-sizing: border-box;    /* #endif */}</style>
 |