index.vue 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. <template>
  2. <TechCard>
  3. <div class="card-header">
  4. <div class="title">跌倒与告警统计</div>
  5. </div>
  6. <div ref="chartRef" class="chart-container" />
  7. </TechCard>
  8. </template>
  9. <script lang="ts" setup>
  10. import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
  11. import * as echarts from 'echarts'
  12. import TechCard from '../TechCard/index.vue'
  13. defineOptions({ name: 'AlertFallCompareCard' })
  14. const chartRef = ref<HTMLDivElement | null>(null)
  15. const chartInstance = ref<echarts.ECharts | null>(null)
  16. interface Props {
  17. fallCount: number
  18. alertCount: number
  19. }
  20. const props = withDefaults(defineProps<Props>(), {
  21. fallCount: 0,
  22. alertCount: 0,
  23. })
  24. const createChart = () => {
  25. if (!chartRef.value) return
  26. if (chartInstance.value) {
  27. chartInstance.value.dispose()
  28. }
  29. chartInstance.value = echarts.init(chartRef.value)
  30. updateChart()
  31. }
  32. const updateChart = () => {
  33. if (!chartInstance.value) return
  34. chartInstance.value.setOption({
  35. tooltip: {
  36. trigger: 'axis',
  37. axisPointer: { type: 'shadow' },
  38. formatter: '{b}: {c} 次',
  39. },
  40. grid: {
  41. left: 40,
  42. right: 20,
  43. top: 20,
  44. bottom: 40,
  45. },
  46. xAxis: {
  47. type: 'category',
  48. data: ['跌倒', '告警'],
  49. axisLabel: { color: '#9cc5e0' },
  50. axisTick: { show: false },
  51. axisLine: { show: false },
  52. },
  53. yAxis: {
  54. type: 'value',
  55. axisLabel: { color: '#9cc5e0' },
  56. splitLine: { show: false },
  57. },
  58. series: [
  59. {
  60. type: 'bar',
  61. data: [props.fallCount, props.alertCount],
  62. barWidth: 40,
  63. itemStyle: {
  64. color: (params: { dataIndex: number }) => ['#ff4d6d', '#f39c12'][params.dataIndex],
  65. },
  66. label: {
  67. show: true,
  68. position: 'top',
  69. color: '#fff',
  70. fontSize: 14,
  71. },
  72. },
  73. ],
  74. })
  75. }
  76. const resizeChart = () => {
  77. if (chartInstance.value) {
  78. chartInstance.value.resize()
  79. }
  80. }
  81. onMounted(() => {
  82. nextTick(() => {
  83. createChart()
  84. window.addEventListener('resize', resizeChart)
  85. })
  86. })
  87. watch(
  88. () => [props.fallCount, props.alertCount],
  89. () => {
  90. updateChart()
  91. }
  92. )
  93. // 组件卸载时销毁图表实例和事件监听
  94. onUnmounted(() => {
  95. if (chartInstance.value) {
  96. chartInstance.value.dispose()
  97. chartInstance.value = null
  98. }
  99. window.removeEventListener('resize', resizeChart)
  100. })
  101. </script>
  102. <style lang="less" scoped>
  103. .card-header {
  104. text-align: center;
  105. margin-bottom: 12px;
  106. .title {
  107. font-size: 16px;
  108. font-weight: bold;
  109. color: #00f0ff;
  110. }
  111. }
  112. .chart-container {
  113. width: 100%;
  114. height: 200px;
  115. }
  116. </style>