| <template> | 
|     <view> | 
|         <view ref="uni-rate" class="uni-rate"> | 
|             <view class="uni-rate__icon" :class="{'uni-cursor-not-allowed': disabled}" | 
|                 :style="{ 'margin-right': marginNumber + 'px' }" v-for="(star, index) in stars" :key="index" | 
|                 @touchstart.stop="touchstart" @touchmove.stop="touchmove" @mousedown.stop="mousedown" | 
|                 @mousemove.stop="mousemove" @mouseleave="mouseleave"> | 
|                 <uni-icons :color="color" :size="size" :type="isFill ? 'star-filled' : 'star'" /> | 
|                 <!-- #ifdef APP-NVUE --> | 
|                 <view :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}" class="uni-rate__icon-on"> | 
|                     <uni-icons style="text-align: left;" :color="disabled?'#ccc':activeColor" :size="size" | 
|                         type="star-filled" /> | 
|                 </view> | 
|                 <!-- #endif --> | 
|                 <!-- #ifndef APP-NVUE --> | 
|                 <view :style="{ width: star.activeWitch}" class="uni-rate__icon-on"> | 
|                     <uni-icons :color="disabled?disabledColor:activeColor" :size="size" type="star-filled" /> | 
|                 </view> | 
|                 <!-- #endif --> | 
|             </view> | 
|         </view> | 
|     </view> | 
| </template> | 
|   | 
| <script> | 
|     // #ifdef APP-NVUE | 
|     const dom = uni.requireNativePlugin('dom'); | 
|     // #endif | 
|     /** | 
|      * Rate 评分 | 
|      * @description 评分组件 | 
|      * @tutorial https://ext.dcloud.net.cn/plugin?id=33 | 
|      * @property {Boolean}     isFill = [true|false]         星星的类型,是否为实心类型, 默认为实心 | 
|      * @property {String}     color                         未选中状态的星星颜色,默认为 "#ececec" | 
|      * @property {String}     activeColor                 选中状态的星星颜色,默认为 "#ffca3e" | 
|      * @property {String}     disabledColor                 禁用状态的星星颜色,默认为 "#c0c0c0" | 
|      * @property {Number}     size                         星星的大小 | 
|      * @property {Number}     value/v-model                 当前评分 | 
|      * @property {Number}     max                         最大评分评分数量,目前一分一颗星 | 
|      * @property {Number}     margin                         星星的间距,单位 px | 
|      * @property {Boolean}     disabled = [true|false]     是否为禁用状态,默认为 false | 
|      * @property {Boolean}     readonly = [true|false]     是否为只读状态,默认为 false | 
|      * @property {Boolean}     allowHalf = [true|false]     是否实现半星,默认为 false | 
|      * @property {Boolean}     touchable = [true|false]     是否支持滑动手势,默认为 true | 
|      * @event {Function} change                         uniRate 的 value 改变时触发事件,e={value:Number} | 
|      */ | 
|   | 
|     export default { | 
|         name: "UniRate", | 
|         props: { | 
|             isFill: { | 
|                 // 星星的类型,是否镂空 | 
|                 type: [Boolean, String], | 
|                 default: true | 
|             }, | 
|             color: { | 
|                 // 星星未选中的颜色 | 
|                 type: String, | 
|                 default: "#ececec" | 
|             }, | 
|             activeColor: { | 
|                 // 星星选中状态颜色 | 
|                 type: String, | 
|                 default: "#ffca3e" | 
|             }, | 
|             disabledColor: { | 
|                 // 星星禁用状态颜色 | 
|                 type: String, | 
|                 default: "#c0c0c0" | 
|             }, | 
|             size: { | 
|                 // 星星的大小 | 
|                 type: [Number, String], | 
|                 default: 24 | 
|             }, | 
|             value: { | 
|                 // 当前评分 | 
|                 type: [Number, String], | 
|                 default: 0 | 
|             }, | 
|             modelValue: { | 
|                 // 当前评分 | 
|                 type: [Number, String], | 
|                 default: 0 | 
|             }, | 
|             max: { | 
|                 // 最大评分 | 
|                 type: [Number, String], | 
|                 default: 5 | 
|             }, | 
|             margin: { | 
|                 // 星星的间距 | 
|                 type: [Number, String], | 
|                 default: 0 | 
|             }, | 
|             disabled: { | 
|                 // 是否可点击 | 
|                 type: [Boolean, String], | 
|                 default: false | 
|             }, | 
|             readonly: { | 
|                 // 是否只读 | 
|                 type: [Boolean, String], | 
|                 default: false | 
|             }, | 
|             allowHalf: { | 
|                 // 是否显示半星 | 
|                 type: [Boolean, String], | 
|                 default: false | 
|             }, | 
|             touchable: { | 
|                 // 是否支持滑动手势 | 
|                 type: [Boolean, String], | 
|                 default: true | 
|             } | 
|         }, | 
|         data() { | 
|             return { | 
|                 valueSync: "", | 
|                 userMouseFristMove: true, | 
|                 userRated: false, | 
|                 userLastRate: 1 | 
|             }; | 
|         }, | 
|         watch: { | 
|             value(newVal) { | 
|                 this.valueSync = Number(newVal); | 
|             }, | 
|             modelValue(newVal) { | 
|                 this.valueSync = Number(newVal); | 
|             }, | 
|         }, | 
|         computed: { | 
|             stars() { | 
|                 const value = this.valueSync ? this.valueSync : 0; | 
|                 const starList = []; | 
|                 const floorValue = Math.floor(value); | 
|                 const ceilValue = Math.ceil(value); | 
|                 for (let i = 0; i < this.max; i++) { | 
|                     if (floorValue > i) { | 
|                         starList.push({ | 
|                             activeWitch: "100%" | 
|                         }); | 
|                     } else if (ceilValue - 1 === i) { | 
|                         starList.push({ | 
|                             activeWitch: (value - floorValue) * 100 + "%" | 
|                         }); | 
|                     } else { | 
|                         starList.push({ | 
|                             activeWitch: "0" | 
|                         }); | 
|                     } | 
|                 } | 
|                 return starList; | 
|             }, | 
|   | 
|             marginNumber() { | 
|                 return Number(this.margin) | 
|             } | 
|         }, | 
|         created() { | 
|             this.valueSync = Number(this.value || this.modelValue); | 
|             this._rateBoxLeft = 0 | 
|             this._oldValue = null | 
|         }, | 
|         mounted() { | 
|             setTimeout(() => { | 
|                 this._getSize() | 
|             }, 100) | 
|             // #ifdef H5 | 
|             this.PC = this.IsPC() | 
|             // #endif | 
|         }, | 
|         methods: { | 
|             touchstart(e) { | 
|                 // #ifdef H5 | 
|                 if (this.IsPC()) return | 
|                 // #endif | 
|                 if (this.readonly || this.disabled) return | 
|                 const { | 
|                     clientX, | 
|                     screenX | 
|                 } = e.changedTouches[0] | 
|                 // TODO 做一下兼容,只有 Nvue 下才有 screenX,其他平台式 clientX | 
|                 this._getRateCount(clientX || screenX) | 
|             }, | 
|             touchmove(e) { | 
|                 // #ifdef H5 | 
|                 if (this.IsPC()) return | 
|                 // #endif | 
|                 if (this.readonly || this.disabled || !this.touchable) return | 
|                 const { | 
|                     clientX, | 
|                     screenX | 
|                 } = e.changedTouches[0] | 
|                 this._getRateCount(clientX || screenX) | 
|             }, | 
|   | 
|             /** | 
|              * 兼容 PC @tian | 
|              */ | 
|   | 
|             mousedown(e) { | 
|                 // #ifdef H5 | 
|                 if (!this.IsPC()) return | 
|                 if (this.readonly || this.disabled) return | 
|                 const { | 
|                     clientX, | 
|                 } = e | 
|                 this.userLastRate = this.valueSync | 
|                 this._getRateCount(clientX) | 
|                 this.userRated = true | 
|                 // #endif | 
|             }, | 
|             mousemove(e) { | 
|                 // #ifdef H5 | 
|                 if (!this.IsPC()) return | 
|                 if (this.userRated) return | 
|                 if (this.userMouseFristMove) { | 
|                     console.log('---mousemove----', this.valueSync); | 
|                     this.userLastRate = this.valueSync | 
|                     this.userMouseFristMove = false | 
|                 } | 
|                 if (this.readonly || this.disabled || !this.touchable) return | 
|                 const { | 
|                     clientX, | 
|                 } = e | 
|                 this._getRateCount(clientX) | 
|                 // #endif | 
|             }, | 
|             mouseleave(e) { | 
|                 // #ifdef H5 | 
|                 if (!this.IsPC()) return | 
|                 if (this.readonly || this.disabled || !this.touchable) return | 
|                 if (this.userRated) { | 
|                     this.userRated = false | 
|                     return | 
|                 } | 
|                 this.valueSync = this.userLastRate | 
|                 // #endif | 
|             }, | 
|             // #ifdef H5 | 
|             IsPC() { | 
|                 var userAgentInfo = navigator.userAgent; | 
|                 var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; | 
|                 var flag = true; | 
|                 for (let v = 0; v < Agents.length - 1; v++) { | 
|                     if (userAgentInfo.indexOf(Agents[v]) > 0) { | 
|                         flag = false; | 
|                         break; | 
|                     } | 
|                 } | 
|                 return flag; | 
|             }, | 
|             // #endif | 
|   | 
|             /** | 
|              * 获取星星个数 | 
|              */ | 
|             _getRateCount(clientX) { | 
|                 this._getSize() | 
|                 const size = Number(this.size) | 
|                 if (isNaN(size)) { | 
|                     return new Error('size 属性只能设置为数字') | 
|                 } | 
|                 const rateMoveRange = clientX - this._rateBoxLeft | 
|                 let index = parseInt(rateMoveRange / (size + this.marginNumber)) | 
|                 index = index < 0 ? 0 : index; | 
|                 index = index > this.max ? this.max : index; | 
|                 const range = parseInt(rateMoveRange - (size + this.marginNumber) * index); | 
|                 let value = 0; | 
|                 if (this._oldValue === index && !this.PC) return; | 
|                 this._oldValue = index; | 
|                 if (this.allowHalf) { | 
|                     if (range > (size / 2)) { | 
|                         value = index + 1 | 
|                     } else { | 
|                         value = index + 0.5 | 
|                     } | 
|                 } else { | 
|                     value = index + 1 | 
|                 } | 
|   | 
|                 value = Math.max(0.5, Math.min(value, this.max)) | 
|                 this.valueSync = value | 
|                 this._onChange() | 
|             }, | 
|   | 
|             /** | 
|              * 触发动态修改 | 
|              */ | 
|             _onChange() { | 
|   | 
|                 this.$emit("input", this.valueSync); | 
|                 this.$emit("update:modelValue", this.valueSync); | 
|                 this.$emit("change", { | 
|                     value: this.valueSync | 
|                 }); | 
|             }, | 
|             /** | 
|              * 获取星星距离屏幕左侧距离 | 
|              */ | 
|             _getSize() { | 
|                 // #ifndef APP-NVUE | 
|                 uni.createSelectorQuery() | 
|                     .in(this) | 
|                     .select('.uni-rate') | 
|                     .boundingClientRect() | 
|                     .exec(ret => { | 
|                         if (ret) { | 
|                             this._rateBoxLeft = ret[0].left | 
|                         } | 
|                     }) | 
|                 // #endif | 
|                 // #ifdef APP-NVUE | 
|                 dom.getComponentRect(this.$refs['uni-rate'], (ret) => { | 
|                     const size = ret.size | 
|                     if (size) { | 
|                         this._rateBoxLeft = size.left | 
|                     } | 
|                 }) | 
|                 // #endif | 
|             } | 
|         } | 
|     }; | 
| </script> | 
|   | 
| <style lang="scss"> | 
|     .uni-rate { | 
|         /* #ifndef APP-NVUE */ | 
|         display: flex; | 
|         /* #endif */ | 
|         line-height: 1; | 
|         font-size: 0; | 
|         flex-direction: row; | 
|         /* #ifdef H5 */ | 
|         cursor: pointer; | 
|         /* #endif */ | 
|     } | 
|   | 
|     .uni-rate__icon { | 
|         position: relative; | 
|         line-height: 1; | 
|         font-size: 0; | 
|     } | 
|   | 
|     .uni-rate__icon-on { | 
|         overflow: hidden; | 
|         position: absolute; | 
|         top: 0; | 
|         left: 0; | 
|         line-height: 1; | 
|         text-align: left; | 
|     } | 
|   | 
|     .uni-cursor-not-allowed { | 
|         /* #ifdef H5 */ | 
|         cursor: not-allowed !important; | 
|         /* #endif */ | 
|     } | 
| </style> |