在之前的一个项目中,UI 给我画了一个横向叠加的柱状图,因为没有涉及到用
echarts
及其他的插件,我就自己写了一个公共的组件来实现,今儿就来分享一下我的思路
HTML 部分
样式这块我主要是采用HTML加css来处理的。
tooltip
部分使用了element-plus
组件, 采用了单例模式
<template>
<div
class="y-progress-wrap"
>
<div
ref="progressRef"
class="y-progress-main-wrap"
>
<transition-group
class="y-progress-main"
tag="div"
name="progress-transition"
>
<div
v-for="(item, index) in progressList"
:key="index"
class="y-progress-item"
:style="{
width: item.width + 'px',
'background-color': progressList.length === 1 ? 'none' : item.color,
'background-image': progressList.length === 1 ? 'linear-gradient( to left , rgba(145, 215, 255, 1),rgba(62, 139, 255, 1))' : 'none',
'transition-delay': 0.1 * index + 's'
}"
@mouseover="handleMouseover($event, item)"
@mouseleave="handleMouseleave"
/>
</transition-group>
</div>
<div
v-if="showSuffix"
class="y-progress-suffix"
>
<slot
name="suffix"
:progressValue="progressValue"
:maxProgress="maxProgress"
>
{{ progressValue }}/{{ maxProgress }}
</slot>
</div>
<el-tooltip
ref="progressTooltipRef"
v-model:visible="visible"
:virtual-ref="showProgressTooltipRef"
virtual-triggering
effect="dark"
:content="tooltipContent"
placement="top"
/>
</div>
</template>
<style lang="scss" scoped>
.y-progress-wrap {
display: flex;
align-items: center;
position: relative;
.y-progress-main-wrap {
flex: 1;
}
.y-progress-main {
width: 100%;
height: 8px;
border-radius: 4px;
background-color: rgba(64, 140, 255, .2);
overflow: hidden;
display: flex;
.y-progress-item {
cursor: pointer;
width: 0;
height: 100%;
transition: all 0.3s;
&:last-child {
border-radius: 0 4px 4px 0;
}
}
}
.y-progress-suffix {
margin-left: 8px;
color: rgba(29, 33, 41, 1);
font-size: 12px;
font-weight: 400;
}
.progress-circle {
transform: rotate(-90deg);
.progress {
transition: all .3s;
}
}
.progress-circle-content {
color: rgba(29, 33, 41, 1);
font-size: 16px;
font-weight: 400;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.progress-transition-enter-active {
opacity: 0;
transform-origin: left;
transform: scaleX(0);
}
.progress-transition-enter-to {
opacity: 1;
transform-origin: left;
transform: scaleX(1);
}
</style>
样式HTML部分大概就这样了,因为是涉及到多个的,我准备采用循环
<div class="y-progress-item"/>
然后计算每个的宽度来实现
JS
使用的是vue3 + TS的写法,我这块就直接使用了
setup
语法糖
<script lang="ts" setup>
import {
withDefaults,
defineProps,
ref,
nextTick,
watchEffect
} from 'vue'
// 定义props
type Props = {
showSuffix?: boolean // 是否显示后缀
progress: number[] | number // 进度或者数据
colors?: string[] | string // 显示的颜色
maxProgress?: number // 最大值
width?: string // 外层宽度
}
// 设置默认值
const props = withDefaults(defineProps<Props>(), {
showSuffix: true,
progress: () => ([]),
colors: () => ([]),
maxProgress: 100,
width: '100%'
})
const progressList = ref<any[]>([]) // 初始化progressList,用于生成每个柱子
const colorList = ref([ // 默认内置颜色
'#3e8bff',
'#19D4AE',
'#1593C5',
'#FA6E86',
'#FFB980',
'#69840A',
'#378256',
'#B79757',
'#8C73DD',
'#EFAAB5',
'#F272BE'
])
const progressValue = ref(0) // 累加数值
const progressRef = ref<HTMLElement | null>(null) // 外层ref
const progressTooltipRef = ref<HTMLElement | null>(null) // Tooltip
const showProgressTooltipRef = ref<HTMLElement | null>(null) // 显示 Tooltip 的柱子
const visible = ref<boolean>(false) // 控制显示 Tooltip
const tooltipContent = ref<string>('') // Tooltip里的内容
// 这里用到了 watchEffect。watchEffect内部自动追踪依赖
// 当 props.progress props.colors 发生变化就去更新
watchEffect(() => {
const progressList: number[] = []
// 处理 props.progress 类型差异
if (typeof props.progress === 'number') {
progressList.push(props.progress)
} else {
progressList.push(...props.progress)
}
const list = []
if (typeof props.colors === 'string') {
list.push(props.colors)
} else {
list.push(...props.colors)
}
colorList.value = [...new Set([...list, ...colorList.value])] // 覆盖颜色默认值
progressValue.value = progressList.reduce((val, next) => {
val = parseFloat((val + next).toFixed(2))
return val
}, 0)
}
nextTick(() => {
// 调用方法生成
initProgress(progressList)
})
})
const initProgress = (newVal: number[]) => {
const num = (newVal || []).length
const w = progressRef.value?.getBoundingClientRect().width ?? 0 // 获取最大宽度
progressList.value = []
for (let i = 0; i < num; i++) {
// 根据每个数值计算宽度,添加颜色
progressList.value.push({
width: newVal[i] / props.maxProgress * w,
progress: newVal[i],
color: colorList.value[i]
})
}
}
// 显示 Tooltip
const handleMouseover = (e: Event, item: any) => {
showProgressTooltipRef.value = e.currentTarget
visible.value = !visible.value
tooltipContent.value = item.progress.toString()
}
// 隐藏 Tooltip
const handleMouseleave = () => {
visible.value = !visible.value
tooltipContent.value = ''
}
</script>
动画实现
动画使用
transition-group
来做的,当里面的值发生变化的时候或颜色变化都会动画过渡到最新的值
有写得不好的地方,请大家指出一起探讨。