78 lines
2.5 KiB
Vue
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>
|