若依后台数据大屏实战:用ECharts嵌套饼图展示‘计划 vs 实际’业务数据

在企业管理系统中,数据可视化是提升决策效率的关键。当我们需要直观展示"计划与实际"的业务对比时,嵌套饼图(Nested Pie Chart)往往能带来意想不到的效果——内环展示日维度数据,外环呈现月维度统计,形成天然的层级关系。本文将基于若依分离版框架,从后端SQL查询到前端ECharts配置,完整实现一个可复用的数据对比大屏模块。

1. 业务数据准备与后端接口设计

任何数据可视化的前提都是准确的数据源。在若依框架中,我们通常通过MyBatis操作数据库。假设我们需要统计某物流系统的日发货计划与实际完成量对比,首先需要构建合理的SQL查询:

-- 获取当月每日计划与实际数据
SELECT 
    DATE_FORMAT(shipment_date, '%Y-%m-%d') AS day,
    SUM(plan_quantity) AS daily_plan,
    SUM(actual_quantity) AS daily_actual
FROM sm_shipment_daily
WHERE DATE_FORMAT(shipment_date,'%Y-%m') = #{month}
GROUP BY day
ORDER BY day;

在Controller层,我们需要设计符合RESTful规范的API接口。考虑到数据大屏可能需要同时展示多组数据,建议采用如下结构返回:

@GetMapping("/api/shipment/compare/{month}")
public R<ShipmentCompareVO> getShipmentCompare(
    @PathVariable String month) {
    
    // 1. 查询日维度数据
    List<DailyShipment> dailyData = shipmentService.selectDailyCompare(month);
    
    // 2. 计算月维度汇总
    MonthlyShipment monthlyData = shipmentService.calculateMonthlyTotal(month);
    
    // 3. 封装VO对象
    ShipmentCompareVO vo = new ShipmentCompareVO();
    vo.setDailyData(dailyData);
    vo.setMonthlyData(monthlyData);
    
    return R.ok(vo);
}

返回的JSON数据结构示例:

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "dailyData": [
      {"day": "2024-08-01", "plan": 1200, "actual": 980},
      ...
    ],
    "monthlyData": {
      "month": "2024-08",
      "totalPlan": 30000,
      "totalActual": 27500
    }
  }
}

2. 前端数据预处理与ECharts组件集成

在若依分离版的前端Vue项目中,我们需要先确保正确引入了ECharts。对于Vue 2.x项目,推荐使用vue-echarts的5.x版本:

npm install echarts@4.9.0 vue-echarts@5.0.0-beta.0 @vue/composition-api --save

在main.js中进行全局注册:

import ECharts from 'vue-echarts'
import 'echarts'

Vue.component('v-chart', ECharts)

数据获取与预处理是可视化效果的关键。在Vue组件中,我们需要:

  1. 通过axios获取后端数据
  2. 将原始数据转换为ECharts需要的格式
  3. 处理可能的空值或异常情况
export default {
  data() {
    return {
      currentMonth: '2024-08',
      chartOption: null
    }
  },
  methods: {
    async fetchData() {
      try {
        const res = await getShipmentCompare(this.currentMonth)
        this.prepareChartOption(res.data)
      } catch (error) {
        console.error('数据获取失败:', error)
      }
    },
    prepareChartOption(apiData) {
      // 计算月完成率
      const monthlyCompletion = (
        apiData.monthlyData.totalActual / 
        apiData.monthlyData.totalPlan * 100
      ).toFixed(1)
      
      // 计算日平均完成率
      const dailyAvg = apiData.dailyData.reduce((sum, item) => {
        return sum + (item.actual / item.plan)
      }, 0) / apiData.dailyData.length * 100
      
      this.chartOption = {
        // 将在下一节详细配置
      }
    }
  },
  mounted() {
    this.fetchData()
  }
}

3. 嵌套饼图的高级配置解析

嵌套饼图的核心在于series数组中配置多个饼图系列,并通过radius属性控制它们的半径范围。以下是完整的option配置及其业务含义:

{
  tooltip: {
    trigger: 'item',
    formatter: params => {
      const { name, value, percent } = params
      const unit = params.seriesIndex === 0 ? '件/日' : '件/月'
      return `${name}<br/>${value}${unit} (${percent}%)`
    }
  },
  legend: {
    data: ['日计划', '日实际', '月计划', '月实际'],
    bottom: 10
  },
  series: [
    // 内环 - 日维度数据
    {
      name: '日维度',
      type: 'pie',
      radius: ['0%', '40%'],
      label: {
        position: 'inner',
        formatter: '{b}\n{c}件'
      },
      labelLine: { show: false },
      data: [
        { 
          value: apiData.dailyData.reduce((sum, d) => sum + d.plan, 0),
          name: '日计划',
          itemStyle: { color: '#5470C6' }
        },
        { 
          value: apiData.dailyData.reduce((sum, d) => sum + d.actual, 0),
          name: '日实际',
          itemStyle: { color: '#91CC75' }
        }
      ]
    },
    // 外环 - 月维度数据
    {
      name: '月维度',
      type: 'pie',
      radius: ['55%', '70%'],
      label: {
        formatter: '{b}: {c} ({d}%)',
        textStyle: { fontSize: 14 }
      },
      data: [
        { 
          value: apiData.monthlyData.totalPlan,
          name: '月计划',
          itemStyle: { color: '#EE6666' }
        },
        { 
          value: apiData.monthlyData.totalActual,
          name: '月实际',
          itemStyle: { color: '#FAC858' }
        }
      ]
    }
  ]
}

关键配置说明:

  1. radius控制饼图半径

    • 内环设置为 ['0%', '40%'] 表示从中心到40%的位置
    • 外环设置为 ['55%', '70%'] 形成环状效果
  2. label标签定制

    • 内环使用 position: 'inner' 将标签显示在内部
    • 外环标签显示更详细的信息
  3. 颜色方案

    • 使用互补色区分计划和实际数据
    • 日维度使用蓝色系,月维度使用红色系

4. 交互增强与性能优化

基础图表实现后,我们需要考虑实际业务场景中的交互需求:

动态数据刷新

// 在组件中添加定时刷新
setInterval(() => {
  this.fetchData()
}, 300000) // 每5分钟刷新一次

响应式调整

window.addEventListener('resize', () => {
  if (this.$refs.chart) {
    this.$refs.chart.resize()
  }
})

大数据量优化

当数据量较大时,可以启用ECharts的数据采样和动画优化:

{
  animation: true,
  animationThreshold: 2000,
  progressive: 200,
  progressiveThreshold: 3000
}

点击钻取交互

通过ECharts的点击事件实现下钻功能:

this.$refs.chart.on('click', params => {
  if (params.seriesName === '月维度') {
    this.currentMonth = params.name === '月计划' ? 
      this.currentMonth : 
      moment(this.currentMonth).subtract(1, 'month').format('YYYY-MM')
    this.fetchData()
  }
})

5. 业务场景扩展与实践建议

嵌套饼图不仅适用于计划与实际对比,还可应用于:

  1. 多层级占比分析 :如销售渠道分布(线上/线下)下的具体品类占比
  2. 目标进度监控 :年度目标与季度完成情况对比
  3. 资源分配评估 :预算分配与实际支出对比

实际项目中需要注意:

  • 数据准确性 :确保后端计算逻辑与业务定义一致
  • 颜色语义 :使用行业通用颜色编码(如红色常表示预警)
  • 移动端适配 :在小屏幕上可能需要简化图表元素
// 移动端简化配置示例
const isMobile = window.innerWidth < 768
if (isMobile) {
  this.chartOption.legend.orient = 'vertical'
  this.chartOption.legend.right = 10
  this.chartOption.series[0].radius = ['0%', '30%']
  this.chartOption.series[1].radius = ['40%', '60%']
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐