DoughnutChart.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <template>
  2. <div ref="chartRef" class="doughnut-chart" :style="{ width: width, height: height }"></div>
  3. </template>
  4. <script lang="ts" setup>
  5. import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
  6. import * as echarts from 'echarts'
  7. interface ChartData {
  8. name: string
  9. value: number
  10. itemStyle?: {
  11. color?: string
  12. }
  13. }
  14. interface Props {
  15. data: ChartData[]
  16. width?: string
  17. height?: string
  18. title?: string
  19. colors?: string[]
  20. center?: [string, string]
  21. radius?: [string, string]
  22. showLabel?: boolean
  23. showLegend?: boolean
  24. }
  25. const props = withDefaults(defineProps<Props>(), {
  26. width: '100%',
  27. height: '200px',
  28. title: '',
  29. colors: () => ['#167AF0', '#00BC71', '#FF6B35', '#FFD700', '#9C27B0'],
  30. center: () => ['50%', '50%'],
  31. radius: () => ['40%', '70%'],
  32. showLabel: true,
  33. showLegend: false
  34. })
  35. const chartRef = ref<HTMLElement>()
  36. let chartInstance: echarts.ECharts | null = null
  37. // 初始化图表
  38. const initChart = () => {
  39. if (!chartRef.value) return
  40. chartInstance = echarts.init(chartRef.value)
  41. updateChart()
  42. }
  43. // 更新图表数据
  44. const updateChart = () => {
  45. if (!chartInstance) return
  46. const option: echarts.EChartsOption = {
  47. title: props.title ? {
  48. text: props.title,
  49. left: 'center',
  50. top: '10%',
  51. textStyle: {
  52. fontSize: 14,
  53. fontWeight: 'normal',
  54. color: '#333'
  55. }
  56. } : undefined,
  57. tooltip: {
  58. trigger: 'item',
  59. formatter: '{a} <br/>{b}: {c} ({d}%)',
  60. position: ['50%', '50%']
  61. },
  62. legend: props.showLegend ? {
  63. orient: 'vertical',
  64. left: 'left',
  65. top: 'middle',
  66. textStyle: {
  67. fontSize: 12,
  68. color: '#666'
  69. }
  70. } : undefined,
  71. series: [
  72. {
  73. name: props.title || '数据',
  74. type: 'pie',
  75. radius: props.radius,
  76. center: props.center,
  77. avoidLabelOverlap: false,
  78. label: props.showLabel ? {
  79. show: true,
  80. position: 'outside',
  81. formatter: '{b}\n{d}%',
  82. fontSize: 12,
  83. color: '#333'
  84. } : {
  85. show: false
  86. },
  87. labelLine: props.showLabel ? {
  88. show: true,
  89. length: 10,
  90. length2: 10
  91. } : {
  92. show: false
  93. },
  94. data: props.data.map((item, index) => ({
  95. ...item,
  96. itemStyle: {
  97. borderRadius: 15,
  98. color: item.itemStyle?.color || props.colors[index % props.colors.length]
  99. }
  100. })),
  101. emphasis: {
  102. itemStyle: {
  103. shadowBlur: 5,
  104. shadowOffsetX: 0,
  105. shadowOffsetY: 0,
  106. shadowColor: 'rgba(0, 0, 0, 0.3)'
  107. }
  108. }
  109. }
  110. ]
  111. }
  112. chartInstance.setOption(option)
  113. }
  114. // 监听数据变化
  115. watch(() => props.data, () => {
  116. nextTick(() => {
  117. updateChart()
  118. })
  119. }, { deep: true })
  120. // 监听窗口大小变化
  121. const handleResize = () => {
  122. if (chartInstance) {
  123. chartInstance.resize()
  124. }
  125. }
  126. onMounted(() => {
  127. initChart()
  128. window.addEventListener('resize', handleResize)
  129. })
  130. onUnmounted(() => {
  131. if (chartInstance) {
  132. chartInstance.dispose()
  133. chartInstance = null
  134. }
  135. window.removeEventListener('resize', handleResize)
  136. })
  137. // 暴露方法给父组件
  138. defineExpose({
  139. getChartInstance: () => chartInstance,
  140. resize: handleResize
  141. })
  142. </script>
  143. <style scoped lang="scss">
  144. .doughnut-chart {
  145. display: inline-block;
  146. padding: 5px;
  147. box-sizing: border-box;
  148. }
  149. </style>