Files
cma-management/frontend/src/components/charts/BulletChart.vue
T

78 lines
2.5 KiB
Vue

<template>
<div :ref="el => containerRef = el" style="width:100%;height:100%;min-height:80px;"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import * as echarts from 'echarts'
const props = withDefaults(defineProps<{
actual: number
target: number
label?: string
unit?: string
}>(), { label: '', unit: '' })
const containerRef = ref<HTMLElement | null>(null)
let chart: echarts.ECharts | null = null
function render() {
if (!containerRef.value) return
if (!chart) chart = echarts.init(containerRef.value)
const maxVal = Math.max(props.actual, props.target, 1) * 1.3
const pct = props.target > 0 ? (props.actual / props.target * 100) : 0
chart.setOption({
tooltip: {
trigger: 'axis',
formatter: () =>
`${props.label}<br/>实际: <b>${props.actual?.toLocaleString()}${props.unit}</b><br/>目标: ${props.target?.toLocaleString()}${props.unit}<br/>完成率: <b>${pct.toFixed(1)}%</b>`,
},
grid: { left: 50, right: 50, top: 10, bottom: 5 },
xAxis: { type: 'category', data: [props.label], axisLabel: { show: false }, splitLine: { show: false } },
yAxis: { type: 'value', max: maxVal, splitLine: { show: false }, axisLabel: { fontSize: 10, color: '#bbb' } },
series: [
{
type: 'bar',
data: [props.actual],
barWidth: 16,
itemStyle: {
borderRadius: [4, 4, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: pct >= 100 ? '#52c41a' : pct >= 80 ? '#faad14' : '#ff4d4f' },
{ offset: 1, color: pct >= 100 ? '#73d13d' : pct >= 80 ? '#ffc53d' : '#ff7875' },
]),
},
label: {
show: true,
position: 'top',
formatter: `${props.actual?.toLocaleString()}${props.unit}`,
fontSize: 13,
fontWeight: 700,
color: pct >= 100 ? '#52c41a' : '#ff4d4f',
},
},
{
type: 'bar',
data: [props.target],
barWidth: 16,
barGap: '-100%',
itemStyle: { color: 'rgba(0,0,0,0.06)', borderRadius: [4, 4, 0, 0] },
label: {
show: true,
position: 'bottom',
formatter: `目标: ${props.target?.toLocaleString()}${props.unit}`,
fontSize: 10,
color: '#999',
},
},
],
}, true)
}
watch(() => [props.actual, props.target], render)
onMounted(render)
onUnmounted(() => { chart?.dispose(); chart = null })
</script>