topfans/frontend/pages/support-activity/components/VerticalProgressBar.vue

147 lines
3.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="v-progress">
<!-- 目标数字位于目标圆上方 -->
<text v-if="showText" class="v-target-text">{{ formattedTarget }}</text>
<!-- 轨道 + 进度层 -->
<view
class="v-track"
:style="{ width: barWidth, height: barHeight }"
>
<!-- 已填充(从底部起) -->
<view class="v-fill" :style="{ height: fillHeight }" />
<!-- 目标圆(固定在顶部) -->
<view
class="v-circle v-target-circle"
:style="{ width: circleSize, height: circleSize }"
/>
<!-- 当前圆(随 progress 上移) -->
<view
class="v-circle v-current-circle"
:style="{ top: circleTop, width: circleSize, height: circleSize }"
>
<text v-if="showText" class="v-current-text">{{ formattedCurrent }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
const props = defineProps({
current: { type: Number, default: 0 },
target: { type: Number, default: 100 },
barHeight: { type: String, default: '200rpx' },
barWidth: { type: String, default: '16rpx' },
circleSize: { type: String, default: '60rpx' },
fillColor: {
type: String,
default: 'linear-gradient(180deg, #FFD700, #FFA500)'
},
trackColor: {
type: String,
default: 'rgba(255, 255, 255, 0.2)'
},
textColor: { type: String, default: '#FFD700' },
targetColor: { type: String, default: 'rgba(255, 255, 255, 0.8)' },
showText: { type: Boolean, default: true }
})
// 进度比例0~1将来要换公式只改这一处
const ratio = computed(() => {
if (props.target === 0) return 0
return Math.min(props.current / props.target, 1)
})
// 已填充高度ratio × 100%(从底部起)
const fillHeight = computed(() => ratio.value * 100 + '%')
// 当前圆位置:(1 - ratio) × 100%(从顶部起)
const circleTop = computed(() => (1 - ratio.value) * 100 + '%')
// 数字本地化:用 ref + watch 避免每帧调用 toLocaleString
const formattedCurrent = ref(props.current.toLocaleString())
const formattedTarget = ref(props.target.toLocaleString())
watch(() => props.current, (v) => {
formattedCurrent.value = v.toLocaleString()
})
watch(() => props.target, (v) => {
formattedTarget.value = v.toLocaleString()
})
</script>
<style scoped>
.v-progress {
position: fixed;
left: 24rpx;
top: 50%;
transform: translateY(-50%);
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
/* 其它样式(背景框、阴影等)留空,方便用户自行调 */
}
.v-target-text {
color: v-bind(targetColor);
font-size: 20rpx;
line-height: 1;
margin-bottom: 8rpx;
white-space: nowrap;
}
.v-track {
position: relative;
background: v-bind(trackColor);
border-radius: 999rpx;
/* 关键:让圆能露出轨道边界 */
overflow: visible;
}
.v-fill {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background: v-bind(fillColor);
border-radius: 999rpx;
transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.v-circle {
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.v-target-circle {
top: 0;
background: rgba(255, 255, 255, 0.3);
/* 目标圆不显示数字 */
}
.v-current-circle {
background: v-bind(fillColor);
color: v-bind(textColor);
transition: top 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.v-current-text {
font-size: 20rpx;
line-height: 1;
white-space: nowrap;
color: v-bind(textColor);
}
</style>