<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> 
 |