javascript
复制代码
<template>
<view class="home">
<view>
<!-- 标题tab -->
<view class="title">我是标题</view>
</view>
<scroll-view scroll-y style="height: calc(100vh - 360rpx)">
<view class="chartBox">
<view class="chartBox-progress">
<canvas canvas-id="progressCanvas"
:style="{width: canvasSize +iconSize*2 + 'px', height: canvasSize +iconSize*2 + 'px'}"></canvas>
</view>
<view class="content">
<view class="content-value">
<view class="value">{{info.progress}}</view>
<view class="text">%</view>
</view>
<view class="content-tips">
<view class="text">预计还需</view>
<view class="value">{{info.remainingTime}}分钟</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
ctx: null,
iconImage: '/static/monitor/cg.png',
canvasSize: 208,
iconSize: 34,
ringWidth: 9,
imagePath: '',
imageLoaded: false,
info: {
progress: 0, // 初始化进度
totalTime: 60, // 总时间(分钟)
remainingTime: 60, // 剩余时间
}
}
},
watch: { // 监听进度变化,重绘圆环
progress() {
this.drawProgress();
}
},
components: {},
onLoad() {
this.initCanvas();
this.loadImage();
this.simulateCharging();
},
methods: {
initCanvas() {
// 获取Canvas上下文
this.ctx = uni.createCanvasContext('progressCanvas', this);
},
loadImage() {
// 检查是否为本地资源
if (this.iconImage.startsWith('/') || this.iconImage.startsWith('./')) {
// 本地资源直接使用
this.imagePath = this.iconImage;
this.imageLoaded = true;
this.drawProgress();
} else {
// 网络资源需要先下载
uni.getImageInfo({
src: this.iconImage,
success: (res) => {
this.imagePath = res.path;
this.imageLoaded = true;
this.drawProgress();
},
fail: (err) => {
console.error('获取图片信息失败:', err);
this.drawProgress();
}
});
}
},
drawProgress() {
// 计算总画布尺寸(包含图标空间)
const totalSize = this.canvasSize + this.iconSize * 2;
// 计算真正的中心点
const center = totalSize / 2;
// 计算圆环半径
const radius = (this.canvasSize - this.ringWidth) / 2;
// 清空画布
this.ctx.clearRect(0, 0, totalSize, totalSize+ this.iconSize);
// 绘制背景圆环
this.drawInnerShadow(this.ctx, center, radius, this.ringWidth);
this.ctx.beginPath();
this.ctx.arc(center, center - this.iconSize / 2, radius, 0, Math.PI * 2);
this.ctx.setStrokeStyle('#ffd1bf');
this.ctx.setLineWidth(this.ringWidth);
this.ctx.stroke();
// 绘制进度圆环
this.ctx.beginPath();
const startAngle = -Math.PI / 2; // 从12点位置开始
const endAngle = startAngle + (this.info.progress / 100) * (Math.PI * 2);
this.ctx.arc(center, center - this.iconSize / 2, radius, startAngle, endAngle);
this.ctx.setStrokeStyle('#FF6D33');
this.ctx.setLineWidth(this.ringWidth);
this.ctx.stroke();
// 计算图标位置
const iconAngle = startAngle + (this.info.progress / 100) * (Math.PI * 2);
const iconX = center + Math.cos(iconAngle) * radius;
const iconY = center + Math.sin(iconAngle) * radius;
this.ctx.save();
if (this.imageLoaded) {
// 绘制图标(修正坐标计算)
this.ctx.drawImage(
this.imagePath,
iconX - this.iconSize / 2, // 左顶点X = 中心点X - 图标宽度/2
iconY - this.iconSize, // 上顶点Y = 中心点Y - 图标高度/2
this.iconSize,
this.iconSize
);
} else {
// 图标加载失败时显示默认图标
this.ctx.beginPath();
this.ctx.arc(iconX, iconY, this.iconSize / 2, 0, Math.PI * 2);
this.ctx.setFillStyle('#FF6D33');
this.ctx.fill();
}
this.ctx.restore();
this.ctx.draw();
// 更新剩余时间
this.info.remainingTime = Math.round((100 - this.info.progress) * this.info.totalTime / 100);
},
drawInnerShadow(ctx, center, radius, ringWidth) {
const shadowColor = 'rgba(255, 209, 191, 0.15)';
const shadowWidth = 6;
// 绘制外环阴影
ctx.beginPath();
ctx.arc(center, center- this.iconSize / 2, radius + ringWidth / 2 + shadowWidth / 2, 0, Math.PI * 2);
ctx.setFillStyle(shadowColor);
ctx.fill();
// 绘制内环阴影(覆盖内部区域)
ctx.beginPath();
ctx.arc(center, center- this.iconSize / 2, radius - ringWidth / 2 - shadowWidth / 2, 0, Math.PI * 2);
ctx.setFillStyle('#ffffff');
ctx.fill();
},
onImageLoad() {
// 图片加载成功
this.imageLoaded = true;
this.drawProgress();
},
// 模拟进度更新
simulateCharging() {
if (this.info.progress < 100) {
setTimeout(() => {
this.info.progress += 1;
this.drawProgress();
this.simulateCharging();
}, 100);
}
}
}
}
</script>
<style scoped>
.home {
position: relative;
width: 100vw;
height: 100vh;
background: #ecf0f1;
}
.home .chartBox {
position: relative;
left: 50%;
top: 128rpx;
transform: translateX(-50%);
width: 480rpx;
height: 480rpx;
background: linear-gradient(180deg, #FFFFFF 0%, #fff8f6 100%);
box-shadow: 0rpx 20rpx 20rpx 0rpx rgba(255, 209, 191, 0.1);
border-radius: 100%;
}
.home .chartBox-progress {
z-index: 2;
position: relative;
overflow: visible !important;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 480rpx;
}
.home .content {
z-index: 9;
position: absolute;
top: 144rpx;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
}
.home .content-value {
display: flex;
align-items: baseline;
flex-wrap: nowrap;
}
.home .content-value .value {
font-weight: 500;
font-size: 120rpx;
color: #ff8859;
}
.home .content-value .text {
margin-bottom: 8rpx;
font-weight: 400;
font-size: 32rpx;
color: #ffded1;
}
.home .content-tips {
margin: 0 0 20rpx;
}
.home .content-tips .text {
font-weight: 400;
font-size: 24rpx;
color: #3F5680;
}
.home .content-tips .value {
margin: 0 8rpx;
font-weight: 600;
font-size: 24rpx;
color: #ff8859;
}
</style>