123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- <template>
- <div ref="chartRef" class="doughnut-chart" :style="{ width: width, height: height }"></div>
- </template>
- <script lang="ts" setup>
- import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
- import * as echarts from 'echarts'
- interface ChartData {
- name: string
- value: number
- itemStyle?: {
- color?: string
- }
- }
- interface Props {
- data: ChartData[]
- width?: string
- height?: string
- title?: string
- colors?: string[]
- center?: [string, string]
- radius?: [string, string]
- showLabel?: boolean
- showLegend?: boolean
- }
- const props = withDefaults(defineProps<Props>(), {
- width: '100%',
- height: '200px',
- title: '',
- colors: () => ['#167AF0', '#00BC71', '#FF6B35', '#FFD700', '#9C27B0'],
- center: () => ['50%', '50%'],
- radius: () => ['40%', '70%'],
- showLabel: true,
- showLegend: false
- })
- const chartRef = ref<HTMLElement>()
- let chartInstance: echarts.ECharts | null = null
- // 初始化图表
- const initChart = () => {
- if (!chartRef.value) return
- chartInstance = echarts.init(chartRef.value)
- updateChart()
- }
- // 更新图表数据
- const updateChart = () => {
- if (!chartInstance) return
- const option: echarts.EChartsOption = {
- title: props.title ? {
- text: props.title,
- left: 'center',
- top: '10%',
- textStyle: {
- fontSize: 14,
- fontWeight: 'normal',
- color: '#333'
- }
- } : undefined,
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c} ({d}%)',
- position: ['50%', '50%']
- },
- legend: props.showLegend ? {
- orient: 'vertical',
- left: 'left',
- top: 'middle',
- textStyle: {
- fontSize: 12,
- color: '#666'
- }
- } : undefined,
- series: [
- {
- name: props.title || '数据',
- type: 'pie',
- radius: props.radius,
- center: props.center,
- avoidLabelOverlap: false,
- label: props.showLabel ? {
- show: true,
- position: 'outside',
- formatter: '{b}\n{d}%',
- fontSize: 12,
- color: '#333'
- } : {
- show: false
- },
- labelLine: props.showLabel ? {
- show: true,
- length: 10,
- length2: 10
- } : {
- show: false
- },
- data: props.data.map((item, index) => ({
- ...item,
- itemStyle: {
- borderRadius: 15,
- color: item.itemStyle?.color || props.colors[index % props.colors.length]
- }
- })),
- emphasis: {
- itemStyle: {
- shadowBlur: 5,
- shadowOffsetX: 0,
- shadowOffsetY: 0,
- shadowColor: 'rgba(0, 0, 0, 0.3)'
- }
- }
- }
- ]
- }
- chartInstance.setOption(option)
- }
- // 监听数据变化
- watch(() => props.data, () => {
- nextTick(() => {
- updateChart()
- })
- }, { deep: true })
- // 监听窗口大小变化
- const handleResize = () => {
- if (chartInstance) {
- chartInstance.resize()
- }
- }
- onMounted(() => {
- initChart()
- window.addEventListener('resize', handleResize)
- })
- onUnmounted(() => {
- if (chartInstance) {
- chartInstance.dispose()
- chartInstance = null
- }
- window.removeEventListener('resize', handleResize)
- })
- // 暴露方法给父组件
- defineExpose({
- getChartInstance: () => chartInstance,
- resize: handleResize
- })
- </script>
- <style scoped lang="scss">
- .doughnut-chart {
- display: inline-block;
- padding: 5px;
- box-sizing: border-box;
- }
- </style>
|