通辽市网站建设_网站建设公司_JSON_seo优化
2026/1/20 14:41:39 网站建设 项目流程

一、项目使用的技术以及框架:

技术栈: uniapp+vue3+vite+ts+uview-plus

框架地址:https://gitee.com/youyun-uu/uniapp-vue3-vue-cli/tree/master

 

二、使用canvas封装环形组件的问题

由于UI组件没有环形组件,使用canvas绘制环形真机运行出现如下问题:

image

基于以上问题进行修改,中途使用canvas-view换弹窗,这个方法是可以使用的,但是除了换掉当前弹窗,还有弹出层等样式也出现了该问题,所以最好换掉环形组件;

以下使用 hyh-progress来换之前的环形组件:

插件地址:https://ext.dcloud.net.cn/plugin?id=11196

 

三、利用hyh-progress插件实现环形

1、创建组件hyh-progress.vue

<template><view class="circle-container" :style="boxSize"><view class="circle" :style="circleStyle"><view v-if="fillet" class="border-start" :style="borderStyle"></view><view class="circle-left ab" :style="renderLeftRate(dispalyRate)"><view v-if="fillet && dispalyRate >= 50" class="border-left-end" :style="borderStyle"></view></view><view class="circle-right ab" :style="renderRightRate(dispalyRate)"><view v-if="fillet && dispalyRate < 50" class="border-right-end" :style="borderStyle"></view></view></view><view class="text-area" :style="textareaStyle"><slot></slot></view></view>
</template>
<script setup>
import { computed, getCurrentInstance, onMounted, ref } from 'vue';const props = defineProps({rate: {type: Number,require: true},width: {type: String,default: '20rpx'},activeColor: {type: String,default: '#54c4fd'},inactiveColor: {type: String,default: '#546063'},startAngle: {type: Number,default: 0},fillet: {type: Boolean,default: false}
});const dispalyRate = computed(() => {if (props.rate <= 0) {return 0;} else if (props.rate >= 100) {return 100;} else if (props.rate <= 3) {return 1;} else {return props.rate - 3;}
});
const circleStyle = computed(() => {const width = getNumberAndUnit(props.width).num - 0.3;const unit = getNumberAndUnit(props.width).unit;return `box-shadow: inset 0 0 0 ${width + unit} ${props.activeColor};transform:rotate(${props.startAngle}deg)`;
});
const borderStyle = computed(() => {if (dispalyRate.value == 0) return '';return `width:${props.width};height:${props.width};background-color:${props.activeColor};`;
});
const textareaStyle = computed(() => {return `width:calc(100% - ${props.width});height:calc(100% - ${props.width});`;
});onMounted(() => {getSize();
});const renderRightRate = rate => {const border = `border: ${props.width} solid ${props.inactiveColor};`;if (rate < 50) {return border + 'transform: rotate(' + 3.6 * rate + 'deg);';} else {return border + `transform: rotate(0);border-color: ${props.activeColor};`;}
};const renderLeftRate = rate => {const border = `border: ${props.width} solid ${props.inactiveColor};`;if (rate >= 50) {return border + 'transform: rotate(' + 3.6 * (rate - 50) + 'deg);';} else {return border;}
};const boxSize = ref('');
function getSize() {getWidth().then(res => {const { width, height } = res;const size = width < height ? width : height;boxSize.value = `width:${size}px;height:${size}px;`;});
}
function getWidth() {return new Promise((resolve, reject) => {try {const { ctx } = getCurrentInstance();uni.createSelectorQuery().in(ctx).select('.circle-container').boundingClientRect(res => {resolve(res);}).exec();} catch (e) {//TODO handle the exception
            reject(e);}});
}function getNumberAndUnit(str) {const numReg = /\d+/g;const unitReg = /[a-z]+/;const num = str.match(numReg);const unit = str.match(unitReg);return {num: num[0],unit: unit[0]};
}
</script><style lang="scss" scoped>
.circle-container {position: relative;width: 100%;height: 100%;.circle {position: relative;width: 100%;height: 100%;border-radius: 50%;.ab {position: absolute;left: 0;top: 0;width: 100%;height: 100%;}.circle-left {border-radius: 50%;clip-path: polygon(0% 0%, 50% 0%, 50% 100%, 0% 100%);}.circle-right {border-radius: 50%;clip-path: polygon(50% 0%, 100% 0%, 100% 100%, 50% 100%);}.border-start {position: absolute;top: 0;left: 50%;transform: translateX(-50%);z-index: 1;border-radius: 50%;}.border-left-end {position: absolute;bottom: 0px;left: 50%;transform: translate(-50%, 100%);z-index: 1;border-radius: 50%;}.border-right-end {position: absolute;top: 0;left: 50%;transform: translate(-50%, -100%);z-index: 1;border-radius: 50%;}}.text-area {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);width: 20px;height: 20px;border-radius: 50%;display: flex;flex-direction: column;align-items: center;justify-content: center;}
}
</style>

2、使用组件

1)引入组件

import * as hyhProgress from "@/components/hyhProgress/index.vue";

2)使用组件

<view class="circle-progress-area"><view class="test-container"><hyh-progress:rate="progressPercent"width="20rpx":fillet="false"activeColor="#6161d2"inactiveColor="#e5e9ff":startAngle="0"><view class="timer-circle-content"><text class="mode-text">{{currentMode ? `${currentMode + "模式"}` : "未选择模式"}}</text><text class="time-text">{{ modeDuration }}</text><text class="total-time">{{ totalDuration }}</text></view></hyh-progress></view>
</view>

3)初始化每个模式的时长

const initModeDuration = (modeName: string) => {const targetMode = modeList.value.find((item) => item.name === modeName);if (targetMode) {// 时长转换:分钟 → 秒const minutes = targetMode.times;totalSeconds.value = minutes * 60;remainingSeconds.value = minutes * 60;// 格式化时长为 "MM:SS"modeDuration.value = `${minutes.toString().padStart(2, "0")}:00`;totalDuration.value = `共${minutes}分钟`;} else {totalSeconds.value = 0;remainingSeconds.value = 0;modeDuration.value = "00:00";totalDuration.value = "共00分钟";}
};

4)时间格式化,倒计时需要转成秒

// 秒转时分格式
const updateDurationDisplay = () => {const minutes = Math.floor(remainingSeconds.value / 60);const seconds = remainingSeconds.value % 60;modeDuration.value = `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
};

5)启用倒计时

// 启动倒计时
const startCountdown = () => {// 清除已有定时器,避免重复计时if (countdownTimer.value) clearInterval(countdownTimer.value);updateDurationDisplay();countdownTimer.value = setInterval(() => {remainingSeconds.value--;// 进度 = 剩余时间 / 总时间 * 100(从100%递减到0)progressPercent.value =totalSeconds.value > 0? (remainingSeconds.value / totalSeconds.value) * 100: 0;updateDurationDisplay();// 倒计时结束处理if (remainingSeconds.value <= 0) {stopCountdown();resetModeState();}}, 1000);
};

6)停止倒计时

// 停止倒计时
const stopCountdown = () => {if (countdownTimer.value) {clearInterval(countdownTimer.value);countdownTimer.value = null;}
};

7)重置数据

// 重置模式状态
const resetModeState = () => {currentMode.value = null; // 回到未选择模式modeDuration.value = "00:00";totalDuration.value = "共00分钟";progressPercent.value = 0;totalSeconds.value = 0;remainingSeconds.value = 0;
};

8)选择不同的模式进行倒计时

// 模式选择核心逻辑(按你的需求重构)
const handleModeSelect = async (val: any) => {if (!validateToken()) return;if (!isOpen.value) {uni.showToast({ title: "请先打开开关", icon: "none" });return;}// 当前已有选中模式且和点击的模式一致,且倒计时正在进行if (currentMode.value === val.name && countdownTimer.value) {uni.showToast({title: "正在治疗中",icon: "none",});return;}await getModeDuration();currentMode.value = val.name;initModeDuration(val.name);const tempSelectedModelCode = modeList.value.find((item) => item.name === val.name)?.code;const res = await sfmCalendarKeepRecord(tempSelectedModelCode);if (res.code !== 200) return// 自动启动倒计时(无暂停,直接开始)if (totalSeconds.value > 0) {progressPercent.value = 100;startCountdown();} else {progressPercent.value = 0;stopCountdown(); // 自动模式无时长,停止定时器
  }
};

 

四、实现的倒计时

image

 

注:该文档为个人理解所写,有误可建议修改

 

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询