6.4 KiB
6.4 KiB
VerticalProgressBar 组件设计
日期:2026-06-02 作者:zerosaturation 状态:设计中
背景与目标
support-activity 页面(生日应援活动)目前仅在 ThemeBanner.vue 右下角以纯文字形式展示进度:
当前进度 19,901,123 / 19,911,005
缺少一个直观的、可视化的进度呈现。本次新增一个独立的竖向进度条组件 VerticalProgressBar,承担"展示当前活动进度"这一职责,并实时反映后端轮询数据。
范围
- In:
- 新增可复用组件
VerticalProgressBar.vue - 包含竖向进度条 + 当前/目标数字
- 当前进度圆随实时数据沿轨道自下而上移动
- 新增可复用组件
- Out:
- 不修改
ThemeBanner.vue - 不在
ThemeBanner.vue中嵌入此组件 - 视觉细节(颜色、圆角、阴影等)不调优,由用户后续自行调整
- 不修改
组件设计
文件位置
frontend/pages/support-activity/components/VerticalProgressBar.vue
Props
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
current |
Number | 0 | 当前进度值 |
target |
Number | 100 | 目标进度值 |
barHeight |
String | '200rpx' |
轨道总高度 |
barWidth |
String | '16rpx' |
轨道宽度 |
circleSize |
String | '60rpx' |
进度圆直径 |
fillColor |
String | 'linear-gradient(180deg, #FFD700, #FFA500)' |
填充渐变 |
trackColor |
String | 'rgba(255,255,255,0.2)' |
轨道底色 |
textColor |
String | '#FFD700' |
当前数字颜色 |
targetColor |
String | 'rgba(255,255,255,0.8)' |
目标数字颜色 |
showText |
Boolean | true |
是否显示数字(方便单测与单用) |
视觉结构
19,911,005 ← 目标数字(位于目标圆上方)
┌─┐
│ │ ← 目标圆(固定在 bar 顶部)
├─┤
│ │ ← 未填充轨道(trackColor)
│ │
│ │
┌─┴─┐
│ │
│19,│ ← 当前圆(带数字在内部,随 progress 上移)
│901│
│ │
├─==┤ ← 已填充(fillColor,从底部至当前圆底部)
│ │
│ │
└───┘
模板
<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 }" />
<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>
计算公式
// 进度比例(0~1)
const ratio = computed(() => {
if (props.target === 0) return 0
return Math.min(props.current / props.target, 1)
})
// 填充高度:从底部起,占 bar 的 ratio × 100%
const fillHeight = computed(() => ratio.value * 100 + '%')
// 当前圆位置:从顶部起,(1 - ratio) × 100%
const circleTop = computed(() => (1 - ratio.value) * 100 + '%')
// 数字本地化(避免每帧重建)
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() })
公式可替换点:未来若要换成非线性/缓动/阈值公式,仅需替换
ratio这一处计算。
CSS 关键点
.v-progress {
position: fixed;
left: 24rpx;
top: 50%;
transform: translateY(-50%);
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
/* 其它样式留空,方便用户自行调 */
}
.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;
}
.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,
.v-target-text {
font-size: 20rpx;
line-height: 1;
white-space: nowrap;
}
.v-target-text {
color: v-bind(targetColor);
margin-bottom: 8rpx;
}
行为
- 组件挂载后,初始
ratio决定当前圆位置和填充高度 - 父组件更新
current时,formattedCurrent通过 watch 重算(避免每帧toLocaleString) - 当前圆
top与.v-fill高度过渡时长 0.4s,使用 ease 曲线 target = 0时ratio = 0,当前圆停在底部
边界与错误处理
| 场景 | 行为 |
|---|---|
target = 0 |
ratio = 0,圆停在底部,无报错 |
current > target |
ratio 被 Math.min 截断为 1,圆停在顶部 |
current < 0 |
透传,UI 表现圆停在 ratio < 0 的位置(视觉上圆略微被遮挡) |
| 父组件不传任何 prop | 使用全部默认值(current=0, target=100) |
使用方式
不修改 ThemeBanner.vue,在 support-activity/index.vue 中以兄弟节点方式使用:
<VerticalProgressBar
:current="progressData.current"
:target="progressData.target"
/>
页面已通过 progressManager 实时更新 progressData.current,因此组件会自动响应。
测试
单元测试不在本次范围。后续可补:
ratio计算(target=0、current>target、current=负数等)- watch 触发
toLocaleString次数- 视觉回归(手动)
待办与未来扩展
- 替换为非线性/缓动公式(按用户后续设计)
- 加入 stage 段位刻度(沿 bar 显示 N 段)
- 完成后切换为"庆祝态"动画
- 提取公共 composable(
useRatio)便于其他页面复用