bug
jiangping
2023-11-07 64b432916af9c9218ab3f3eca614e26c542142ae
minipro_standard/uni_modules/uview-ui/components/u-rate/u-rate.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,306 @@
<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>