yunshangxie/tuniao-ui/components/tn-circle-progress/tn-circle-progress.vue

224 lines
6.2 KiB
Vue
Raw Normal View History

2023-12-25 17:56:30 +08:00
<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.$tn.uuid()形式动态生成id值否则会报错
// #ifdef MP-WEIXIN
elBgId: 'tCircleProgressBgId',
elId: 'tCircleProgressElId',
// #endif
// #ifndef MP-WEIXIN
elBgId: this.$tn.uuid(),
elId: this.$tn.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>