Files
t/components/thorui/tui-round-progress/tui-round-progress.vue
T
2025-03-21 09:38:06 +08:00

293 lines
8.4 KiB
Vue

<template>
<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
<canvas :start="percent" :change:start="parse.initDraw" :data-width="diam" :data-height="height" :data-lineWidth="lineWidth"
:data-lineCap="lineCap" :data-fontSize="fontSize" :data-fontColor="fontColor" :data-fontShow="fontShow"
:data-percentText="percentText" :data-defaultShow="defaultShow" :data-defaultColor="defaultColor"
:data-progressColor="progressColor" :data-gradualColor="gradualColor" :data-sAngle="sAngle" :data-counterclockwise="counterclockwise"
:data-multiple="multiple" :data-speed="speed" :data-activeMode="activeMode" :data-cid="progressCanvasId" :canvas-id="progressCanvasId"
:class="[progressCanvasId]" :style="{ width: diam + 'px', height: (height || diam) + 'px' }"></canvas>
<slot></slot>
</view>
</template>
<script module="parse" lang="renderjs">
export default {
methods: {
format(str) {
if (!str) return str;
return str.replace(/\"/g, "");
},
bool(str) {
return str === 'true' || str == true ? true : false
},
//初始化绘制
initDraw(percentage, oldPercentage, owner, ins) {
let state = ins.getState();
let res = ins.getDataset();
const activeMode = this.format(res.activemode);
let start = activeMode === 'backwards' ? 0 : (state.startPercentage || 0);
//当start大于当前percentage时,start设置为0
start = start > percentage ? 0 : start;
if (!state.progressContext || !state.canvas) {
const width = res.width;
const height = res.height == 0 ? res.width : res.height;
let ele = `.${res.cid}>canvas`
const canvas = document.querySelectorAll(this.format(ele))[0];
const ctx = canvas.getContext('2d');
// const dpr =uni.getSystemInfoSync().pixelRatio;
// canvas.style.width=width+'px';
// canvas.style.height=height+'px';
// canvas.width = width * dpr;
// canvas.height = height * dpr;
// ctx.scale(dpr, dpr);
state.progressContext = ctx;
state.canvas = canvas;
this.drawProgressCircular(start, ctx, canvas, percentage, res, state, owner);
} else {
this.drawProgressCircular(start, state.progressContext, state.canvas, percentage, res, state, owner);
}
},
//默认(背景)圆环
drawDefaultCircular(ctx, canvas, res) {
//终止弧度
let sangle = Number(res.sangle) * Math.PI
let eAngle = Math.PI * (res.height != 0 ? 1 : 2) + sangle;
this.drawArc(ctx, eAngle, this.format(res.defaultcolor), res);
},
drawPercentage(ctx, percentage, res) {
ctx.save(); //save和restore可以保证样式属性只运用于该段canvas元素
ctx.beginPath();
ctx.fillStyle = this.format(res.fontcolor);
ctx.font = res.fontsize + "px Arial"; //设置字体大小和字体
ctx.textAlign = "center";
ctx.textBaseline = "middle";
let radius = res.width / 2;
let percenttext = this.format(res.percenttext)
if (!percenttext) {
let multiple = Number(res.multiple)
percentage = this.bool(res.counterclockwise) ? 100 - percentage * multiple : percentage * multiple;
percentage = percentage.toFixed(0) + "%"
} else {
percentage = percenttext
}
ctx.fillText(percentage, radius, radius);
ctx.stroke();
ctx.restore();
},
//进度圆环
drawProgressCircular(startPercentage, ctx, canvas, percentage, res, state, owner) {
if (!ctx || !canvas) return;
let that = this
let gradient = ctx.createLinearGradient(0, 0, Number(res.width), 0);
gradient.addColorStop(0, this.format(res.progresscolor));
let gradualColor = this.format(res.gradualcolor)
if (gradualColor) {
gradient.addColorStop('1', gradualColor);
}
let requestId = null
let renderLoop = () => {
drawFrame((res) => {
if (res) {
requestId = requestAnimationFrame(renderLoop)
} else {
cancelAnimationFrame(requestId)
requestId = null;
renderLoop = null;
}
})
}
requestId = requestAnimationFrame(renderLoop)
function drawFrame(callback) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (that.bool(res.defaultshow)) {
that.drawDefaultCircular(ctx, canvas, res)
}
if (that.bool(res.fontshow)) {
that.drawPercentage(ctx, startPercentage, res);
}
let isEnd = percentage === 0 || (that.bool(res.counterclockwise) && startPercentage === 100);
if (!isEnd) {
let sangle = Number(res.sangle) * Math.PI
let eAngle = ((2 * Math.PI) / 100) * startPercentage + sangle;
that.drawArc(ctx, eAngle, gradient, res);
}
owner.callMethod('change', {
percentage: startPercentage
})
if (startPercentage >= percentage) {
state.startPercentage = startPercentage;
owner.callMethod('end', {
canvasId: that.format(res.canvasid)
})
cancelAnimationFrame(requestId)
callback && callback(false)
return;
}
let num = startPercentage + Number(res.speed)
startPercentage = num > percentage ? percentage : num;
callback && callback(true)
}
},
//创建弧线
drawArc(ctx, eAngle, strokeStyle, res) {
ctx.save();
ctx.beginPath();
ctx.lineCap = this.format(res.linecap);
ctx.lineWidth = Number(res.linewidth);
ctx.strokeStyle = strokeStyle;
let radius = res.width / 2; //x=y
let sangle = Number(res.sangle) * Math.PI
ctx.arc(radius, radius, radius - res.linewidth, sangle, eAngle, this.bool(res.counterclockwise));
ctx.stroke();
ctx.closePath();
ctx.restore();
}
}
}
</script>
<script>
export default {
name: 'tuiRoundProgress',
props: {
/*
传值需使用rpx进行转换保证各终端兼容
px = rpx / 750 * wx.getSystemInfoSync().windowWidth
圆形进度条(画布)宽度,直径 [px]
*/
diam: {
type: Number,
default: 60
},
//圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
height: {
type: Number,
default: 0
},
//进度条线条宽度[px]
lineWidth: {
type: Number,
default: 4
},
/*
线条的端点样式
butt:向线条的每个末端添加平直的边缘
round 向线条的每个末端添加圆形线帽
square 向线条的每个末端添加正方形线帽
*/
lineCap: {
type: String,
default: 'round'
},
//圆环进度字体大小 [px]
fontSize: {
type: Number,
default: 12
},
//圆环进度字体颜色
fontColor: {
type: String,
default: '#5677fc'
},
//是否显示进度文字
fontShow: {
type: Boolean,
default: true
},
/*
自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
可以使用 slot自定义显示内容
*/
percentText: {
type: String,
default: ''
},
//是否显示默认(背景)进度条
defaultShow: {
type: Boolean,
default: true
},
//默认进度条颜色
defaultColor: {
type: String,
default: '#CCC'
},
//进度条颜色
progressColor: {
type: String,
default: '#5677fc'
},
//进度条渐变颜色[结合progressColor使用,默认为空]
gradualColor: {
type: String,
default: ''
},
//起始弧度,单位弧度 实际 Math.PI * sAngle
sAngle: {
type: Number,
default: -0.5
},
//指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
counterclockwise: {
type: Boolean,
default: false
},
//进度百分比 [10% 传值 10]
percentage: {
type: Number,
default: 0
},
//进度百分比缩放倍数[使用半弧为100%时,则可传2]
multiple: {
type: Number,
default: 1
},
//动画执行速度,值越大动画越快(0.1~100)
speed: {
type: [Number, String],
default: 1
},
//backwards: 动画从头播;forwards:动画从上次结束点接着播
activeMode: {
type: String,
default: 'backwards'
}
},
watch: {
percentage(val) {
this.percent = val;
}
},
mounted() {
setTimeout(() => {
this.percent = this.percentage;
}, 50);
},
data() {
return {
percent: -1,
progressCanvasId: this.getCanvasId()
};
},
methods: {
getCanvasId() {
return 'tui' + new Date().getTime() + (Math.random() * 100000).toFixed(0);
},
change(e) {
//绘制进度
this.$emit('change', e);
},
end(e) {
//绘制完成
this.$emit('end', e);
}
}
};
</script>
<style scoped>
.tui-circular-container {
position: relative;
}
</style>