基于 Vue 和Echarts的高性能大屏显示组件实现

1,236 阅读4分钟

摘要

在企业级应用中,数据可视化是一个非常重要的需求。如何设计一个既灵活又易用的图表组件,能够满足不同业务场景的需求,同时又能保持良好的可维护性?本文将分享一个基于Vue + ECharts的企业级图表组件设计方案,通过该方案,我们可以轻松实现数据大屏、统计报表等多种业务场景的图表展示需求。

一、组件设计思路

1. 核心特点

  • 高度复用:通过配置化方式,支持折线图、柱状图、饼图等多种图表类型

  • 灵活扩展:支持顶部卡片、图表组合等多种布局方式

  • 数据联动:支持同比、环比等业务数据的联动展示

  • 交互友好:内置搜索、重置、加载等交互体验

2. 组件结构AskCopyApply to index.vue├── personnelChart/│   ├── common/             # 公共图表组件│   │   ├── index.vue      # 组件主文件│   │   └── index.scss     # 样式文件│   └── overview/          # 业务场景实现│       └── index.vue      # 概览页面

overview页面里面只用做参数配置和引用当前组件就可以

1737354495738.png 这就是这个页面的全部代码了,是不是很简洁?

<!-- 使用后:只需要配置数据 -->
<template>
  <Common 
    :propTopList="topList" 
    :propChartList="chartList" 
    type="overview" 
    @changeProp="changeProp"
  />
</template>

<script>
export default {
  data() {
    return {
      topList: [],
      chartList: [
        {
          id: 'statusEmployees',
          title: '在职员工情况',
          type: 'line',
          // ... 更多配置
        }
      ]
    }
  }
}
</script>

为什么要封装这个组件

需求高度相似的图表展示页面:

  • 都需要收索选择

  • 都有顶部数据卡片

  • 都有图表联动

1737354432151.png

1737354444009.png 仔细分析后让我觉得有偷懒的可乘之机。 通过配置化的方式,将图表定义转换为数据结构:

1737355267213.png

2. 配置灵活性,可扩展的数据结构,能定义图表,嵌套图表,支持搜索。

企业微信截图_17373553692059.png

  1. 支持多种图表类型
{
  type: 'line',    // 折线图
  type: 'bar',     // 柱状图
  type: 'pie',     // 饼图
  // 还可以扩展,支持更多图表类型
}

支持数据卡片

topList: [{
  type: 'num',     // 数字展示
  type: 'pie',     // 迷你图表
  // 支持更多展示类型
}]

支持图表嵌套

{
  id: 'parentChart',
  // 顶部嵌套图表
  topList: [{
    type: 'pie',
    // 子图表配置
  }],
  // 主图表配置
  type: 'line'
}

二、实现要点

1. 配置驱动设计

// 图表配置结构
{
  // 基础信息
  id: String,           // 图表唯一标识
  title: String,        // 图表标题
  type: String,         // 图表类型
  height: Number,       // 图表高度
  
  // 数据处理
  nonStandard: String,  // 特殊数据处理标识
  
  // 顶部卡片
  topList: Array,       // 顶部指标配置
  
  // 图表配置
  legend: Array,        // 图例配置
  xAxis: Array,         // X轴配置
  yAxis: Array,         // Y轴配置
  series: Array,        // 数据系列
  
  // 交互配置
  dataZoom: Array,      // 数据缩放
  select: Object        // 选择器配置
}

2. 组件接口设计

// Props定义
props: {
  propChartList: Array,  // 图表配置列表
  propTopList: Array,    // 顶部卡片配置
  type: String,          // 业务类型
},

// 事件定义
events: {
  changeProp: Function,  // 配置变更
  search: Function,      // 搜索触发
}

3. 性能优化

// 1. 图表实例缓存
const chartInstances = new Map()

// 2. 按需渲染
<el-card v-if="item.visible">
  <div :id="item.id" class="chart-box"></div>
</el-card>

// 3. 数据更新优化
updateChart(chart, data) {
  const options = this.getOptions(data)
  chart.setOption(options, true) // 只更新数据

三、核心实现逻辑

xport default {
  name: 'personnelMatters-personnelChart-common',
  
  props: {
    propChartList: Array,
    propTopList: Array,
    type: String
  },
  
  data() {
    return {
      loading: false,
      searchForm: {
        organizationId: undefined,
        startTime: '',
        endTime: '',
      },
      organizeIdTree: undefined,
      timeList: [],
      time: '',
      chartInstances: new Map(), // 图表实例缓存
    }
  },
  
  mounted() {
    this.initCharts()
    window.addEventListener('resize', this.resizeCharts)
  },
  
  beforeDestroy() {
    window.removeEventListener('resize', this.resizeCharts)
    this.destroyCharts()
  },
  
  methods: {
    // 1. 图表初始化
    initCharts() {
      this.$nextTick(() => {
        // 初始化顶部卡片图表
        this.propTopList.forEach(item => {
          if (item.type === 'pie') {
            const chart = this.createChart(item)
            this.chartInstances.set(item.id, chart)
          }
        })
        
        // 初始化主图表
        this.propChartList.forEach(item => {
          const chart = this.createChart(item)
          this.chartInstances.set(item.id, chart)
        })
      })
    },
    
    // 2. 创建图表实例
    createChart(config) {
      const el = document.getElementById(config.id)
      if (!el) return null
      
      const chart = echarts.init(el)
      const options = this.generateOptions(config)
      chart.setOption(options)
      
      return chart
    },
    
    // 3. 生成图表配置
    generateOptions(config) {
      const options = {
        tooltip: {
          trigger: 'axis'
        },
        legend: {
          data: config.legend || []
        },
        xAxis: config.xAxis,
        yAxis: config.yAxis,
        series: config.series,
      }
      
      // 处理数据缩放
      if (config.dataZoom) {
        options.dataZoom = config.dataZoom
      }
      
      // 处理饼图特殊配置
      if (config.type === 'pie') {
        options.series = [{
          type: 'pie',
          radius: config.fullDraw ? '100%' : '50%',
          data: config.data
        }]
      }
      
      return options
    },
    
    // 4. 更新图表数据
    updateChartData(id, data) {
      const chart = this.chartInstances.get(id)
      if (!chart) return
      
      const config = this.propChartList.find(item => item.id === id)
      if (!config) return
      
      // 处理数据
      const processedData = this.processData(data, config)
      
      // 更新配置
      const options = this.generateOptions({
        ...config,
        series: processedData
      })
      
      chart.setOption(options, true)
    },
    

单个图表的渲染流程:请求数据 --> 数据校对 --> 将数据导入图表校对 --> 生成图表渲染数据 --> 渲染图表 ,循环。 其中需要注意的是在一次渲染循环完成前不能重新点击搜索,防止重复请求。

    if (index == this.chartList.length - 1) {
            this.$nextTick(() => {
              //把新设置的值传回父组件,保持单项数据流。
              this.$emit('changeProp', this.chartList, this.topList)
              //用loading控制提交按钮。
              this.loading = false;
            })
            resolve()
          }

四、关键实现细节

1. 数据处理机制

因为对接的后端开发习惯不同,对返回值需要做特殊处理,以方便套用现在的模板。

// 标准数据处理
processStandardData(data, config) {
  return config.series.map(serie => {
    return {
      ...serie,
      data: data[serie.keyName] || []
    }
  })
}

// 非标准数据处理
processNonStandardData(data, config) {
  switch(config.nonStandard) {
    case 'wageCostSituation':
      return this.processWageCostData(data, config)
    case 'attendance':
      return this.processAttendanceData(data, config)
    default:
      return this.processStandardData(data, config)
  }
}

2. 图表缓存机制

// 图表实例管理
class ChartInstanceManager {
  constructor() {
    this.instances = new Map()
  }
  
  get(id) {
    return this.instances.get(id)
  }
  
  set(id, chart) {
    if (this.instances.has(id)) {
      this.instances.get(id).dispose()
    }
    this.instances.set(id, chart)
  }
  
  clear() {
    this.instances.forEach(chart => chart.dispose())
    this.instances.clear()
  }
}

五、收益总结

  • 开发效率

  • 新页面开发时间:从3天缩短到0.5天

  • 代码量:减少70%以上

  • 维护成本:显著降低

  • 统一的代码规范

  • 更少的Bug

  • 更好的可维护性

希望这次分享能像一杯浓咖啡一样,给你的代码实现带来灵感的提神效果!如果你觉得有用,别忘了给个赞哦,毕竟赞就是我编程的动力,像代码中的注释一样重要!😄👍