完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
177 lines
4.1 KiB
Vue
177 lines
4.1 KiB
Vue
<template>
|
|
<div class="chart-container">
|
|
<!-- 左侧 Y 轴 -->
|
|
<div class="y-axis">
|
|
<div v-for="(value, index) in yAxisLabels" :key="index" class="y-axis-label">
|
|
{{ value }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bars-container">
|
|
<!-- 柱状图组 -->
|
|
<div v-for="(item, index) in props.topData" :key="index" class="bar-group">
|
|
<div class="bars-wrapper">
|
|
<!-- 上半部分柱子 -->
|
|
<div
|
|
class="bar bar-top"
|
|
:style="{
|
|
height: `${getBarHeight(item, maxValue)}%`,
|
|
background: props.topColor || defaultTopColor
|
|
}"
|
|
></div>
|
|
|
|
<!-- 下半部分柱子 -->
|
|
<div
|
|
class="bar bar-bottom"
|
|
:style="{
|
|
height: `${getBarHeight(Math.abs(props.bottomData[index]), maxValue)}%`,
|
|
background: props.bottomColor || defaultBottomColor
|
|
}"
|
|
></div>
|
|
</div>
|
|
|
|
<!-- X轴标签 -->
|
|
<div class="x-axis-label">{{ props.xAxisData[index] }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface Props {
|
|
topData: number[]
|
|
bottomData: number[]
|
|
xAxisData: string[]
|
|
topColor?: string
|
|
bottomColor?: string
|
|
height?: string
|
|
barWidth?: number
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
topData: () => [2.5, 3, 2.8, 2.3, 2],
|
|
bottomData: () => [-2, -2.2, -1.8, -3, -2.5],
|
|
xAxisData: () => [],
|
|
height: '300px',
|
|
barWidth: 20
|
|
})
|
|
|
|
// 默认渐变色
|
|
const defaultTopColor = 'var(--el-color-primary-light-1)'
|
|
const defaultBottomColor = 'rgb(var(--art-secondary), 1)'
|
|
|
|
// 计算最大值用于缩放
|
|
const maxValue = computed(() => {
|
|
const allValues = [...props.topData, ...props.bottomData.map(Math.abs)]
|
|
return Math.max(...allValues)
|
|
})
|
|
|
|
// 生成Y轴刻度
|
|
const yAxisLabels = computed(() => {
|
|
const max = Math.ceil(maxValue.value)
|
|
return [max, max / 2, 0, max / 2, max]
|
|
})
|
|
|
|
// 计算柱子高度的百分比
|
|
const getBarHeight = (value: number, max: number) => {
|
|
return (value / max) * 40 // 40% 作为最大高度,留出空间给其他元素
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.chart-container {
|
|
position: relative;
|
|
display: flex;
|
|
width: 100%;
|
|
height: v-bind('props.height');
|
|
padding: 20px 0;
|
|
|
|
.y-axis {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
width: 40px;
|
|
padding-right: 10px;
|
|
|
|
&-label {
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.bars-container {
|
|
position: relative;
|
|
display: flex;
|
|
flex: 1;
|
|
align-items: center;
|
|
justify-content: space-around;
|
|
padding: 20px 0;
|
|
margin-left: -10px;
|
|
|
|
.bar-group {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
width: 40px;
|
|
height: 100%;
|
|
|
|
.bars-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
}
|
|
|
|
.bar {
|
|
position: absolute;
|
|
width: v-bind('props.barWidth + "px"');
|
|
border-radius: v-bind('props.barWidth / 2 + "px"');
|
|
transition: height 1s cubic-bezier(0.4, 0, 0.2, 1);
|
|
transform-origin: center;
|
|
|
|
&-top {
|
|
bottom: calc(50% + 2px);
|
|
transform: scaleY(0);
|
|
animation: growUp 1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
}
|
|
|
|
&-bottom {
|
|
top: calc(50% + 2px);
|
|
transform: scaleY(0);
|
|
animation: growDown 1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
}
|
|
}
|
|
|
|
.x-axis-label {
|
|
position: absolute;
|
|
bottom: -20px;
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes growUp {
|
|
from {
|
|
transform: scaleY(0);
|
|
}
|
|
|
|
to {
|
|
transform: scaleY(1);
|
|
}
|
|
}
|
|
|
|
@keyframes growDown {
|
|
from {
|
|
transform: scaleY(0);
|
|
}
|
|
|
|
to {
|
|
transform: scaleY(1);
|
|
}
|
|
}
|
|
</style>
|