耶耶耶耶耶 发表于 2025-4-8 15:55:39

Vue3 实现进度条组件

样式如下,代码如下
https://i-blog.csdnimg.cn/direct/77dd3c63b8234ee2b880f65d8b9be61b.png
<script setup>
import { computed, defineEmits, defineProps, onMounted, ref, watch } from 'vue'

// 定义 props
const props = defineProps({
// 初始百分比
initialPercentage: {
    type: Number,
    default: 0,
},
})

// 定义 emits
const emits = defineEmits(['drag-percentage-change'])

// 定义圆圈数量
const circleCount = 5
// 进度条容器引用
const progressBarContainer = ref(null)
// 可拖动圆圈的位置(百分比)
const draggableCircleLeftPercentage = ref(props.initialPercentage)
// 可拖动圆圈的位置样式
const draggableCircleLeft = computed(() => `${draggableCircleLeftPercentage.value}%`)
// 进度条直线宽度
const progressLineWidth = computed(() => '100%')
// 已完成部分直线宽度
const completedLineWidth = computed(() => `${draggableCircleLeftPercentage.value}%`)

// 圆圈位置计算
const circles = ref([])
onMounted(() => {
if (progressBarContainer.value) {
    const containerWidth = progressBarContainer.value.offsetWidth
    for (let i = 0; i < circleCount; i++) {
      const leftPercentage = (i / (circleCount - 1)) * 100
      circles.value.push({
      left: `${leftPercentage}%`,
      })
    }
}
})

// 开始拖动
const isDragging = ref(false)
const startDragging = (e) => {
if (!progressBarContainer.value)
    return
isDragging.value = true
const containerRect = progressBarContainer.value.getBoundingClientRect()
const startX = e.clientX

const handleMouseMove = (e) => {
    if (isDragging.value) {
      const offsetX = e.clientX - containerRect.left
      const containerWidth = containerRect.width
      let newLeft = (offsetX / containerWidth) * 100
      // 取整并确保在 0 到 100 之间
      newLeft = Math.min(100, Math.max(0, Math.round(newLeft)))
      draggableCircleLeftPercentage.value = newLeft
      // 抛出事件,返回当前百分比
      emits('drag-percentage-change', newLeft)
    }
}

const handleMouseUp = () => {
    if (isDragging.value) {
      isDragging.value = false
      document.removeEventListener('mousemove', handleMouseMove)
      document.removeEventListener('mouseup', handleMouseUp)
    }
}

document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
}

// 点击进度条
const handleBarClick = (e) => {
if (!progressBarContainer.value)
    return
const containerRect = progressBarContainer.value.getBoundingClientRect()
const offsetX = e.clientX - containerRect.left
const containerWidth = containerRect.width
let clickPercentage = (offsetX / containerWidth) * 100
// 取整并确保在 0 到 100 之间
clickPercentage = Math.min(100, Math.max(0, Math.round(clickPercentage)))
draggableCircleLeftPercentage.value = clickPercentage
// 抛出事件,返回当前百分比
emits('drag-percentage-change', clickPercentage)
}

// 点击圆圈
const handleCircleClick = (index) => {
const percentage = (index / (circleCount - 1)) * 100
draggableCircleLeftPercentage.value = percentage
emits('drag-percentage-change', percentage)
}

// 监听 initialPercentage 的变化
watch(() => props.initialPercentage, (newValue) => {
// 取整并确保在 0 到 100 之间
const validValue = Math.min(100, Math.max(0, Math.round(newValue)))
draggableCircleLeftPercentage.value = validValue
})
</script>

<template>
<div ref="progressBarContainer" class="progress-bar-container" @mousedown="handleBarClick">
    <div class="progress-line" :style="{ width: progressLineWidth }" />
    <div class="completed-line" :style="{ width: completedLineWidth }" />
    <div
      v-for="(circle, index) in circles"
      :key="index"
      class="progress-circle"
      :class="{ active: index * (100 / (circleCount - 1)) <= draggableCircleLeftPercentage }"
      :style="{ left: `calc(${circle.left} - 5px)` }"
      @click="handleCircleClick(index)"
    />
    <div
      class="draggable-circle"
      :style="{ left: `calc(${draggableCircleLeft} - 7px)` }"
      @mousedown="startDragging"
    />
</div>
</template>

<style scoped>
.progress-bar-container {
position: relative;
width: 100%;
height: 20px;
}

.progress-line {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
width: 100%;
height: 2px;
background-color: gray;
z-index: 1;
}

.completed-line {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
height: 2px;
background-color: white;
z-index: 2;
}

.progress-circle {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #808080;
z-index: 3;
cursor: pointer;
}

.progress-circle.active {
background-color: white;
}

.draggable-circle {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 14px;
border-radius: 50%;
background-color: white;
z-index: 4;
cursor: pointer;
}
</style>

<template>
<div>
    <CircleProgressBar
      :initialPercentage="initialValue"
      @drag-percentage-change="handlePercentageChange"
    />
</div>
</template>

<script setup>
import { ref } from 'vue';
import CircleProgressBar from './CircleProgressBar.vue';

const initialValue = ref(20); // 初始百分比为 20%

const handlePercentageChange = (percentage) => {
console.log('当前百分比:', percentage);
// 你可以在这里处理接收到的百分比
};
</script>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Vue3 实现进度条组件