| <template> | 
|   <view | 
|     class="tn-circle-progress-class tn-circle-progress" | 
|     :style="{ | 
|       width: widthPx + 'px', | 
|       height: widthPx + 'px' | 
|     }" | 
|   > | 
|     <!-- 支付宝小程序不支持canvas-id属性,必须用id属性 --> | 
|     <!-- 默认圆环 --> | 
|     <canvas | 
|       class="tn-circle-progress__canvas-bg" | 
|       :canvas-id="elBgId" | 
|       :id="elBgId" | 
|       :style="{ | 
|         width: widthPx + 'px', | 
|         height: widthPx + 'px' | 
|       }" | 
|     ></canvas> | 
|     <!-- 进度圆环 --> | 
|     <canvas | 
|       class="tn-circle-progress__canvas" | 
|       :canvas-id="elId" | 
|       :id="elId" | 
|       :style="{ | 
|         width: widthPx + 'px', | 
|         height: widthPx + 'px' | 
|       }" | 
|     ></canvas> | 
|     <view class="tn-circle-progress__content"> | 
|       <slot v-if="$slots.default || $slots.$default"></slot> | 
|       <view v-else-if="showPercent" class="tn-circle-progress__content__percent">{{ percent + '%' }}</view> | 
|     </view> | 
|   </view> | 
| </template> | 
|   | 
| <script> | 
|   export default { | 
|     name: 'tn-circle-progress', | 
|     props: { | 
|       // 进度(百分比) | 
|       percent: { | 
|         type: Number, | 
|         default: 0, | 
|         validator: val => { | 
|           return val >= 0 && val <= 100 | 
|         } | 
|       }, | 
|       // 圆环线宽 | 
|       borderWidth: { | 
|         type: Number, | 
|         default: 14 | 
|       }, | 
|       // 整体圆的宽度 | 
|       width: { | 
|         type: Number, | 
|         default: 200 | 
|       }, | 
|       // 是否显示条纹 | 
|       striped: { | 
|         type: Boolean, | 
|         default: false | 
|       }, | 
|       // 条纹是否运动 | 
|       stripedActive: { | 
|         type: Boolean, | 
|         default: true | 
|       }, | 
|       // 激活部分颜色 | 
|       activeColor: { | 
|         type: String, | 
|         default: '#01BEFF' | 
|       }, | 
|       // 非激活部分颜色 | 
|       inactiveColor: { | 
|         type: String, | 
|         default: '#f0f0f0' | 
|       }, | 
|       // 是否显示进度条内部百分比值 | 
|       showPercent: { | 
|         type: Boolean, | 
|         default: false | 
|       }, | 
|       // 圆环执行动画的时间,ms | 
|       duration: { | 
|         type: Number, | 
|         default: 1500 | 
|       } | 
|     }, | 
|     data() { | 
|       return { | 
|         // 微信小程序中不能使用this.$t.uuid()形式动态生成id值,否则会报错 | 
|         // #ifdef MP-WEIXIN | 
|         elBgId: 'tCircleProgressBgId', | 
|         elId: 'tCircleProgressElId', | 
|         // #endif | 
|         // #ifndef MP-WEIXIN | 
|         elBgId: this.$t.uuid(), | 
|         elId: this.$t.uuid(), | 
|         // #endif | 
|         // 活动圆上下文 | 
|         progressContext: null, | 
|         // 转换成px为单位的背景宽度 | 
|         widthPx: uni.upx2px(this.width || 200), | 
|         // 转换成px为单位的圆环宽度 | 
|         borderWidthPx: uni.upx2px(this.borderWidth || 14), | 
|         // canvas画圆的起始角度,默认为-90度,顺时针 | 
|         startAngle: -90 * Math.PI / 180, | 
|         // 动态修改进度值的时候,保存进度值的变化前后值 | 
|         newPercent: 0, | 
|         oldPercent: 0 | 
|       } | 
|     }, | 
|     watch: { | 
|       percent(newVal, oldVal = 0) { | 
|         if (newVal > 100) newVal = 100 | 
|         if (oldVal < 0) oldVal = 0 | 
|          | 
|         this.newPercent = newVal | 
|         this.oldPercent = oldVal | 
|         setTimeout(() => { | 
|             // 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值 | 
|             // 将此值减少或者新增到新的百分比值 | 
|             this.drawCircleByProgress(oldVal) | 
|         }, 50) | 
|       } | 
|     }, | 
|     created() { | 
|       // 赋值,用于加载后第一个画圆使用 | 
|       this.newPercent = this.percent; | 
|       this.oldPercent = 0; | 
|     }, | 
|     mounted() { | 
|       setTimeout(() => { | 
|           this.drawProgressBg() | 
|           this.drawCircleByProgress(this.oldPercent) | 
|       }, 50) | 
|     }, | 
|     methods: { | 
|       // 绘制进度条背景 | 
|       drawProgressBg() { | 
|         let ctx = uni.createCanvasContext(this.elBgId, this) | 
|         // 设置线宽 | 
|         ctx.setLineWidth(this.borderWidthPx) | 
|         // 设置颜色 | 
|         ctx.setStrokeStyle(this.inactiveColor) | 
|         ctx.beginPath() | 
|         let radius = this.widthPx / 2 | 
|         ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 360 * Math.PI / 180, false) | 
|         ctx.stroke() | 
|         ctx.draw() | 
|       }, | 
|       // 绘制圆弧的进度 | 
|       drawCircleByProgress(progress) { | 
|         // 如果已经存在则拿来使用 | 
|         let ctx = this.progressContext | 
|         if (!ctx) { | 
|           ctx =uni.createCanvasContext(this.elId, this) | 
|           this.progressContext = ctx | 
|         } | 
|         ctx.setLineCap('round') | 
|         // 设置线条宽度和颜色 | 
|         ctx.setLineWidth(this.borderWidthPx) | 
|         ctx.setStrokeStyle(this.activeColor) | 
|         // 将总过渡时间除以100,得出每修改百分之一进度所需的时间 | 
|         let preSecondTime = Math.floor(this.duration / 100) | 
|         // 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的 | 
|         let endAngle = ((360 * Math.PI / 180) / 100) * progress + this.startAngle | 
|         let radius = this.widthPx / 2 | 
|         ctx.beginPath() | 
|         ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false) | 
|         ctx.stroke() | 
|         ctx.draw() | 
|          | 
|         // 如果变更后新值大于旧值,意味着增大了百分比 | 
|         if (this.newPercent > this.oldPercent) { | 
|           // 每次递增百分之一 | 
|           progress++ | 
|           // 如果新增后的值,大于需要设置的值百分比值,停止继续增加 | 
|           if (progress > this.newPercent) return | 
|         } else { | 
|           progress-- | 
|           if (progress < this.newPercent) return | 
|         } | 
|         setTimeout(() => { | 
|           // 定时器,每次操作间隔为time值,为了让进度条有动画效果 | 
|           this.drawCircleByProgress(progress) | 
|         }, preSecondTime) | 
|       } | 
|     } | 
|   } | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
|    | 
|   .tn-circle-progress { | 
|     position: relative; | 
|     /* #ifndef APP-NVUE */ | 
|     display: inline-flex;         | 
|     /* #endif */ | 
|     align-items: center; | 
|     justify-content: center; | 
|     background-color: transparent; | 
|      | 
|     &__canvas { | 
|       position: absolute; | 
|        | 
|       &-bg { | 
|         position: absolute; | 
|       } | 
|     } | 
|      | 
|     &__content { | 
|       display: flex; | 
|       align-items: center; | 
|       justify-content: center; | 
|        | 
|       &__percent { | 
|         font-size: 28rpx; | 
|       } | 
|     } | 
|   } | 
| </style> |