vue3 封装通用 ECharts 组件

news/2025/2/27 8:29:02

我在项目中需要用到很多的图标,比如折线、饼图、柱状、关系等各种图标,我又比较懒,所以就封了一个基本的组件库,只需要传递options和canvasId,基本就可以了,代码如下:
 

javascript"><template>
  <div class="wrapper-charts">
    <div class="text-title" v-if="isShowTitle">
      <div class="title-h3">{{ title || '' }}</div>
      <div v-if="isShowSelect">
        <!-- <template #select> xxx </template> -->
        <span class="title-tip" v-if="showInstitutionName">{{ institutionName || '' }}</span>
        <slot name="select">
          <a-select size="small" style="width: 113px" :value="modelValue" class="selectType" @select="onChange"
            :fieldNames="labelProps">
            <a-select-option value="全部">全部</a-select-option>
            <a-select-option :value="item" v-for="(item, index) in selectOptions" :key="index">
              {{ item }}
            </a-select-option>
          </a-select>
        </slot>
      </div>
    </div>
    <div class="wrapper-charts-content" :style="bodyStyle">
      <div class="charts-styles" :class="className" v-if="chartOptOneSort">
        <div v-size-direct="resizeChart" :ref="chartId" :id="chartId" :style="styles"></div>
      </div>
      <div class="no-data-available-chart" v-else>
        <svg-icon name="noDataAvailable" width="121" height="130"></svg-icon>
        <div>暂无数据</div>
      </div>
    </div>
  </div>
</template>

<script setup>
  import { ref, onMounted, onBeforeUnmount, watch, nextTick, defineSlots, markRaw } from 'vue'
  import * as echarts from 'echarts'

  // Props
  const props = defineProps({
    modelValue: {
      type: [String, Number, null, Array],
      default: '全部'
    },
    isShowTitle: {
      type: Boolean,
      default: false
    },
    isShowSelect: {
      type: Boolean,
      default: true
    },
    loading: {
      type: Boolean,
      default: false
    },
    className: {
      type: String,
      default: ""
    },
    selectOptions: {
      type: Array,
      default: []
    },
    labelProps: {
      type: Object,
      default: {
        label: 'name',
        value: 'value',
        options: 'options'
      }
    },
    showInstitutionName: {
      type: Boolean,
      default: true
    },
    institutionName: {
      type: String,
      default: ''
    },
    title: {
      type: String,
      default: '高原病就珍'
    },
    // id 需要不一样,不然会覆盖
    chartId: {
      type: String,
      required: true,
      default: 'chartContainer'
    },

    option: {
      type: Object,
      required: true // option 是必须的
    },

    bodyStyle: {
      type: Object,
      default: {
        width: '100%',
        height: '100%'
      }
    },

    styles: {
      type: Object,
      default: {
        width: '100%',
        height: '100%'
      }
    },
    clickChart: {
      type: Function,
      required: false
    }
  })

  let chartInstance = ref(null)
  const emit = defineEmits(['update:modelValue', 'change', 'clickChart', 'finished']) // 用来触发事件
  const $solt = defineSlots()
  const onChange = (value) => {
    emit('update:modelValue', value)
    emit('change', value)
  }

  const chartOptOneSort = ref(false)
  // 初始化图表
  const initChart = () => {
    if (chartInstance.value) {
      chartInstance.value?.dispose() // 销毁已有实例,避免重复渲染
    }
    const dom = document.getElementById(props.chartId)
    chartInstance.value = markRaw(echarts.init(dom))
    if (props.loading) {
      chartInstance.value?.showLoading()
    }
    chartInstance.value?.off('click') // 移除旧的点击事件
    chartInstance.value?.setOption(props.option)
    // 监听图表渲染完成事件
    chartInstance.value?.on('finished', () => {
      chartInstance.value?.hideLoading()
      emit('finished')
    })
    // 监听图表点击事件
    chartInstance.value?.on('click', (params) => {
      // 通过 emit 触发 'click' 事件,传递参数
      emit('clickChart', params)
    })
  }

  // 监听 option 的变化并更新图表
  watch(
    () => props.option,
    (newOption) => {
      chartOptOneSort.value = newOption?.series?.length > 0;
      let isShowView = newOption?.series?.map(item => item.data.some(value => value > 0)) || [];
      chartOptOneSort.value = isShowView.some(item => item);
      
      if (chartOptOneSort.value) {
        nextTick(() => {
          if (chartInstance.value) {
            // chartInstance.value.clear();
            chartInstance.value?.dispose() // 销毁已有实例,避免重复渲染
          }
          initChart()
        })
      }
    },
    { deep: true, immediate: true } // 深度监听
  )

  // 处理窗口大小变化时,重新调整图表尺寸
  const resizeChart = (contentRect) => {
    if (chartInstance.value) {
      chartInstance.value?.resize()
    }
  }

  // 在组件挂载时初始化图表,并添加 resize 事件监听
  onMounted(() => {
    // initChart();
    // window.addEventListener('resize', handleResize)
  })

  // 在组件卸载时销毁图表,并移除 resize 事件监听
  onBeforeUnmount(() => {
    if (chartInstance.value) {
      chartInstance.value?.dispose()
    }
    // window.removeEventListener('resize', handleResize)
  })

  defineExpose({
    chartInstance,
    resizeChart,
    initChart,
    onChange,
    chartOptOneSort
  })
</script>

<style lang="less" scoped>
  .wrapper-charts {
    width: 100%;
    height: 100%;
  }

  /* 可以根据需要设置图表的样式 */

  .text-title {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;

    .title-h3 {
      font-size: 16px;
      font-style: normal;
      font-weight: 600;
      color: #333;
    }

    .title-tip {
      font-size: 14px;
      font-weight: 400;
      color: #5e6580;
      margin-right: 10px;
    }
  }

  .wrapper-charts-content {
    width: 100%;
    height: 100%;

    .charts-styles {
      width: 100%;
      height: calc(100% - 35px);
    }
  }

  .no-data-available-chart {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    flex-direction: column;
    justify-content: center;
    color: #5e6580;
    font-family: 'PingFang SC';
    font-size: 14px;
    font-style: normal;
    font-weight: 400;
  }
</style>

在使用的页面中引入或者在全局配置都是可以的,我是才页面中引入的,

javascript">import StatisticsCharts from '@/components/ECharts/lineTrend.vue'

配置文件 config.ts

javascript">import { ref } from 'vue'
import * as echarts from 'echarts'
// charts-config

// 折线图柱状数据配置
// https://echarts.apache.org/zh/option.html#tooltip.confine
export const colors = ['#405BC8', '#00CFBE', '#A96BFF']
export function convertToPercentage(decimal: number) {
  return (decimal * 100).toFixed(2) + ' %'
}
export const chartOptions = {
  color: colors,
  tooltip: {
    trigger: 'axis',
    triggerOn: 'mousemove',
    confine: true,
    height: 'auto',
    backgroundColor: 'rgba(40, 49, 67, 0.85);',
    borderColor: 'rgba(40, 49, 67, 0.85);',
    enterable: true,
    appendToBody: true,
    axisPointer: {
      type: 'cross',
      label: {
        backgroundColor: '#6a7985'
      }
      // crossStyle: {
      //   color: '#fff'
      // }
    },
    // 自定义提示框内容
    formatter: function (data) {
      let tooltipHtml = `
      <div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;max-height: 200px; overflow-y: auto;">
        <div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;">
          ${data[0].axisValue}
          </br>
        </div>
      `
      data.forEach((item) => {
        tooltipHtml += `
        <div style="font-size: 12px;padding: 4px 0;width: auto;max-height: 200px; overflow-y: auto;">
          <div style="display:inline-block;margin-right:4px;width:10px;height:10px;background-color: ${item.color};"></div>
          <span>${item.seriesName} :</span>
          
          <span style="color:#ffffff">${item.seriesType == 'line' ? convertToPercentage(Number(item.data)) : item.data + '元'}</span>
        </div>
       `
      })

      tooltipHtml += '</div>'
      return tooltipHtml
    },
    textStyle: {
      fontSize: 14
    }
  },
  toolbox: {
    top: '2%',
    right: '3%',
    show: false,
    feature: {
      // dataView: { show: true, readOnly: false },
      magicType: { show: true, type: ['line', 'bar'] },
      restore: { show: true }
      // saveAsImage: { show: true }
    }
  },
  legend: {
    width: '80%',
    itemHeight: 10,
    itemWidth: 10,
    top: '2%',
    left: '3%',
    itemGap: 15,
    type: 'scroll', // 数据过多时,分页显示
    icon: 'rect', // 设置图例的形状
    selected: [] //这里默认显示数组中前十个,如果不设置,则所有的数据都会显示在图表上
  },
  xAxis: [
    {
      type: 'category',
      boundaryGap: true, //坐标轴两边留白
      axisTick: {
        show: false
      },
      data: [],
      axisPointer: {
        type: 'shadow'
      },
      axisLabel: {
        show: true, //下方日期显示与否
        interval: 0, // 设置数据间隔
        rotate: 28, // 标题倾斜
        margin: 5, //刻度标签与轴线之间的距离
        textStyle: {
          fontSize: 12, //横轴字体大小
          color: '#8F94A7' //颜色
        }
      }
    }
  ],
  yAxis: [
    {
      name: '',
      // interval: 5,
      position: 'left',
      alignTicks: true,
      type: 'value',
      splitLine: {
        show: false
      },
      axisLine: {
        show: false,
        //  min: 0,
        //  max: 10,
        lineStyle: { color: '#8F94A7' }
      },
      axisLabel: {
        show: true,
        fontSize: 14,
        color: '#8F94A7',
        formatter: function (value: string) {
          return `${Number(value)} 元`
        }
      }
    },
    {
      // name: '温度',
      // interval: 5,
      type: 'value',
      position: 'right',
      alignTicks: true,
      axisLine: {
        show: false
        // lineStyle: {
        //   color: colors[2]
        // }
      },
      axisLabel: {
        show: true,
        fontSize: 14,
        formatter: function (value: string) {
          return `${(Number(value) * 100).toFixed(0)} %`
        },
        color: '#8F94A7'
      }
    }
  ],
  // X轴可滚动
  dataZoom: [
    {
      type: 'slider', // 设置为滑动条型式
      show: true, // 显示dataZoom组件
      bottom: 0,
      height: 10,
      showDetail: false,
      startValue: 0, //滚动条的起始位置
      endValue: 9 //滚动条的截止位置(按比例分割你的柱状图x轴长度)
      // xAxisIndex: [0] // 表示控制第一个x轴
      // start: 0, // 默认显示的起始位置为0
      // end: 20, // 默认显示的结束位置为100
      // handleSize: 8, // 滑动条的手柄大小
      // handleStyle: {
      //   color: '#DCE2E8' // 滑动条的手柄颜色
      // },
      // filterMode: 'filter' // 设置为filter模式,即数据超过范围时会被过滤掉
    },
    {
      type: 'inside', //设置鼠标滚轮缩放
      show: true,
      xAxisIndex: [0],
      startValue: 0,
      endValue: 9
    }
  ],
  grid: {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  series: [
    {
      name: '药品费用总额',
      type: 'bar',
      barWidth: '20',
      tooltip: {
        valueFormatter: function (value: string) {
          return `${Number(value)} 元`
        }
      },
      itemStyle: {
        normal: {
          barBorderRadius: [2, 2, 0, 0]
        }
      },
      data: []
    },
    {
      name: '医疗总费用',
      type: 'bar',
      barWidth: '20',
      itemStyle: {
        //柱形图圆角,鼠标移上去效果,如果只是一个数字则说明四个参数全部设置为那么多
        normal: {
          //柱形图圆角,初始化效果
          barBorderRadius: [2, 2, 0, 0]
        }
      },
      tooltip: {
        valueFormatter: function (value: string) {
          return `${Number(value)} 元`
        }
      },
      data: []
    },
    {
      name: '药占比',
      type: 'line',
      yAxisIndex: 1,
      smooth: true, //true 为平滑曲线,false为直线
      symbol: 'circle', //将小圆点改成实心 不写symbol默认空心 none 不显示
      symbolSize: 8, //小圆点的大小
      label: {
        show: true,
        position: 'top',
        formatter: function (params: any) {
          return convertToPercentage(Number(params.value))
        }
      },
      tooltip: {
        valueFormatter: function (value: string) {
          return convertToPercentage(Number(value))
        }
      },
      data: []
    }
  ]
}
// 折线配置
export const chartLineOptions = {
  color: ['#209E85'],
  // title: {
  //   text: 'Stacked Line'
  // },
  tooltip: {
    trigger: 'axis',
    triggerOn: 'mousemove',
    confine: true,
    height: 'auto',
    backgroundColor: 'rgba(40, 49, 67, 0.85);',
    borderColor: 'rgba(40, 49, 67, 0.85);',
    enterable: true,
    appendToBody: true,
    axisPointer: {
      type: 'cross',
      label: {
        backgroundColor: '#6a7985'
      }
      // crossStyle: {
      //   color: '#fff'
      // }
    },
    // 自定义提示框内容
    formatter: function (data) {
      let tooltipHtml = `
      <div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;max-height: 200px; overflow-y: auto;">
        <div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;">
          ${data[0].axisValue}
          </br>
        </div>
      `
      data.forEach((item) => {
        tooltipHtml += `
        <div style="font-size: 12px;padding: 4px 0;width: auto;max-height: 200px; overflow-y: auto;">
          <div style="display:inline-block;margin-right:4px;width:10px;height:10px;background-color: ${item.color};"></div>
          <span>${item.seriesName} :</span>
          
          <span style="color:#ffffff">${item.seriesType == 'line' ? convertToPercentage(Number(item.data)) : item.data + '元'}</span>
        </div>
       `
      })

      tooltipHtml += '</div>'
      return tooltipHtml
    },
    textStyle: {
      fontSize: 14
    }
  },
  legend: {
    data: []
  },
  grid: {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  xAxis: {
    type: 'category',
    boundaryGap: true,
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    name: '',
    // interval: 5,
    position: 'left',
    alignTicks: true,
    type: 'value',
    splitLine: {
      show: true // X 轴线
    },
    axisLine: {
      // Y 轴线
      show: false,
      //  min: 0,
      //  max: 10,
      lineStyle: { color: '#939495' }
    },
    axisLabel: {
      show: true,
      fontSize: 14,
      color: '#939495',
      formatter: '{value} 元'
    }
  },
  series: [
    {
      type: 'line',
      // symbol: 'none', //去掉折线图中的节点
      smooth: true, //true 为平滑曲线,false为直线
      symbol: 'circle', //将小圆点改成实心 不写symbol默认空心
      symbolSize: 8, //小圆点的大小
      itemStyle: {
        color: '#209E85' //小圆点和线的颜色
      },
      // areaStyle: {
      //   color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
      //     {
      //       offset: 0,
      //       color: 'rgba(213,72,120,0.8)' //靠上方的透明颜色
      //     },
      //     {
      //       offset: 1,
      //       color: 'rgba(213,72,120,0.3)' //靠下方的透明颜色
      //     }
      //   ])
      // },
      label: {
        show: true,
        position: 'top',
        formatter: function (params: any) {
          return convertToPercentage(Number(params.value))
        }
      },
      tooltip: {
        valueFormatter: function (value: string) {
          return convertToPercentage(Number(value))
        }
      },
      stack: 'Total',
      data: [120, 132, 101, 134, 90, 230, 210]
    }
  ]
}
// 柱状图配置
export const barChartOptions = {
  title: {
    textStyle: {
      //文字颜色
      color: '#07123C',
      fontWeight: 'bold',
      //字体系列
      fontFamily: 'sans-serif',
      //字体大小
      fontSize: 18
    }
  },
  toolbox: {
    top: '2%',
    right: '3%',
    show: false,
    feature: {
      // dataView: { show: true, readOnly: false },
      magicType: { show: true, type: ['line', 'bar'] },
      restore: { show: true }
      // saveAsImage: { show: true }
    }
  },
  tooltip: {
    trigger: 'axis',
    backgroundColor: 'rgba(40, 49, 67, 0.85);',
    borderColor: 'rgba(40, 49, 67, 0.85);',
    enterable: true,
    appendToBody: true,
    textStyle: {
      color: '#fff'
    }
  },
  legend: {
    // top: '2%',
    left: '3%',
    itemHeight: 10,
    itemWidth: 10,
    itemGap: 15,
    type: 'scroll', // 数据过多时,分页显示
    selected: [], //这里默认显示数组中前十个,如果不设置,则所有的数据都会显示在图表上
    icon: 'rect', // 设置图例的形状
    data: []
  },
  color: ['#13A89B'],
  barWidth: 20,
  calculable: true,
  grid: {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  xAxis: {
    type: 'category',
    data: ['急诊科', '门诊科', '儿科', '妇科', '神经外科', '神经内科', '皮肤科', '放射科'],
    axisTick: {
      show: false
    },

    axisLabel: {
      // 轴文字
      show: true,
      color: '#A6AAB2',
      fontSize: 12,
      interval: 0,
      rotate: 20
    },
    axisLine: {
      lineStyle: {
        type: 'solid',
        color: '#E6E6E8',
        width: '1'
      }
    }
  },
  yAxis: {
    type: 'value',
    axisLabel: {
      // 轴文字
      show: true,
      color: '#A6AAB2',
      fontSize: 12
    }
    // max:99999//给y轴设置最大值
  },
  series: [
    {
      type: 'bar',
      data: [120, 200, 150, 80, 70, 110, 130, 50],
      // barGap:'80%',/*多个并排柱子设置柱子之间的间距*/
      // barCategoryGap:'50%',/*多个并排柱子设置柱子之间的间距*/
      itemStyle: {
        //柱状图上方显示数值
        normal: {
          //柱形图圆角,初始化效果
          barBorderRadius: [2, 2, 0, 0],
          color: '#13A89B',
          label: {
            show: true, //开启显示
            position: 'top', //在上方显示
            textStyle: {
              //数值样式
              color: '#5E6580',
              fontSize: 12
            }
          }
        }
      },
      showBackground: true,
      backgroundStyle: {
        color: '#f2f8ff'
      },
      label: {
        show: true,
        position: 'top',
        formatter: '{c}'
      }
    }
  ],
  dataZoom: [
    {
      type: 'slider', //给x轴设置滚动条
      show: true, //flase直接隐藏图形
      xAxisIndex: [0],
      bottom: 0,
      height: 10,
      showDetail: false,
      startValue: 0, //滚动条的起始位置
      endValue: 9 //滚动条的截止位置(按比例分割你的柱状图x轴长度)
    },
    {
      type: 'inside', //设置鼠标滚轮缩放
      show: true,
      xAxisIndex: [0],
      startValue: 0,
      endValue: 9
    }
  ]
}


http://www.niftyadmin.cn/n/5869799.html

相关文章

【人工智能顶刊合集】CCF-A/B/C类推荐所有期刊目录,中科院1区审稿极速,81天录用!

本期盘点【人工智能】领域CCF-A/B/C类中科院1-2区期刊最新影响因子、分区、审稿周期参考&#xff01; CCF-A类 Artificial Intelligence • 影响因子&#xff1a;5.1 • 期刊分区&#xff1a;JCR1区&#xff0c;中科院2区 • 年发文量&#xff1a;126 • 自引率&#xff1…

Python Cookbook-2.13 使用C++的类iostream语法

任务 C的基于 ostream 和操纵符(插入了这种特定的对象后,它会在 stream 中产生特定的效果)的 I/O方式&#xff0c;并想将此形式用在自己的Python 程序中。 解决方案 Python 允许使用对特殊方法(即名字前后带有连续两个下划线的方法)进行了重定义的类来重载原有的操作符。为了…

半导体晶圆精控:ethercat转profient网关数据提升制造精度

数据采集系统通过网关连接离子注入机&#xff0c;精细控制半导体晶圆制造过程中的关键参数。 在半导体制造中&#xff0c;晶圆制造设备的精密控制是决定产品性能的关键因素。某半导体工厂采用耐达讯Profinet转EtherCAT协议网关NY-PN-ECATM&#xff0c;将其数据采集系统与离子注…

【人工智能】数据挖掘与应用题库(1-100)

1、涉及变化快慢的问题可以考虑使用导数来分析。 答案:对 2、导数的几何意义是曲线在某点处切线的斜率。 答案:对 3、函数在某点的左导数存在,则导数就存在。 答案:错 4、关于梯度下降算法,下列说法错误的是( ) 错误:梯度下降算法能找到函数精确的最小值。 5、正…

seacmsv9注入管理员账号密码+orderby+limit

一、seacmsv9 SQL注入漏洞 查看源码 <?php session_start(); require_once("include/common.php"); //前置跳转start $cs$_SERVER["REQUEST_URI"]; if($GLOBALS[cfg_mskin]3 AND $GLOBALS[isMobile]1){header("location:$cfg_mhost$cs");}…

缓存击穿、缓存穿透、缓存雪崩

在高并发、高流量的分布式系统中&#xff0c;缓存作为优化性能的利器&#xff0c;能够有效减少数据库压力&#xff0c;提高响应速度。然而&#xff0c;在实际使用过程中&#xff0c;缓存的使用并不是一帆风顺的&#xff0c;开发者在设计缓存系统时常常会遇到一些难题&#xff0…

SCIKIT-LEARN 决策树实现csv文档简单的推论预测

一.学习背景 原文来自scikit-learn的学习拓展&#xff0c;根据樱花分类示例衍生而来。源文开源地址&#xff1a;scikit-learn: machine learning in Python — scikit-learn 0.16.1 documentation&#xff0c;想学机器学习和数据挖掘的可以去瞧瞧&#xff01; 二.读取csv文档 …

Java高频面试之SE-23

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天又来了&#xff01;哈哈哈哈哈嗝&#x1f436; Java 中的 Stream 是 Java 8 引入的一种全新的数据处理方式&#xff0c;它基于函数式编程思想&#xff0c;提供了一种高效、简洁且灵活的方式来…