You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3 Composition API下vue-chartjs响应式堆叠柱状图实现问题

问题描述

我想在Vue3组合式API中用vue-chartjs实现堆叠柱状图,用来区分同一时间点不同用户的CPU消耗。流程是通过API获取数据后,用quick-pivot处理数据,补全特定时间内未使用CPU用户的0值,然后把处理后的数据绑定到图表的:data="cpuReactive"属性,实现数据更新时图表响应式重渲染。但把cpu对象设为响应式时会触发mounted钩子重复执行,所以暂时把它设成了静态对象,现在实现达不到预期效果,求可行的解决方案。

原实现代码

<script lang="ts" setup>
import axios from 'axios'
import { onMounted, reactive, ref } from 'vue'
import Pivot from 'quick-pivot'

import {
  Chart as ChartJS,
  Title,
  Tooltip,
  Legend,
  BarElement,
  CategoryScale,
  LinearScale,
  type ChartData
} from 'chart.js'
import { Bar } from 'vue-chartjs'

ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)

const cpu = {
  labels: [''],
  datasets: [
    {
      label: 'Consumo',
      backgroundColor: '#f87979',
      barThickness: 40,
      borderRadius: 5,
      data: [{}]
    }
  ]
}

const cpuReactive = ref<ChartData<'bar'>>({
  datasets: []
})

let pivot: any
const initalDataSet: string[][] = [['cuando', 'quien', 'cuanto']]

const urlGraficas: string =
  import.meta.env.VITE_APP_ROOT_API + import.meta.env.VITE_BACKEND_DASHBOARD_DESA + '/graficas'

async function pedirGraficas() {
  const { data } = await axios.get<any>(urlGraficas + 'Cpu')

  readyChartDataSet(data)
}

function readyChartDataSet(data: any) {
  data.forEach((element: { cuando: string; usu: string; cpu: number }) => {
    const object: any[] = [element.cuando, element.usu, element.cpu]

    initalDataSet.push(object)
  })

  pivot = new Pivot(initalDataSet, ['quien'], ['cuando'], 'cuanto', 'sum')

  cpu.labels = [...pivot.data.table[0].value]
  cpu.labels.shift()
  cpu.labels.pop()
 {label:string, data: any[]}
  let datasetChart = [...pivot.data.table]
  let finalDataSetChart: { label: string; data: any[] }[] = []
  datasetChart.shift()
  datasetChart.pop()

  datasetChart.forEach((element) => {
    for (let index = 0; index < element.value.length; index++) {
      if (element.value[index] == '') {
        element.value[index] = 0
      }
    }

    const item = {
      label: element.value[0],
      data: element.value
    }

    item.data.shift()
    item.data.pop()
    cpu.datasets[0].data.push(item)
  })
  console.log(cpu.datasets[0].data)
  cpu.datasets[0].data.shift()

}

const options = {
  type:'bar',
  data: cpu.datasets[0].data ,
  responsive: true,
  maintainAspectRatio: true,
  scales: {
    x: {
      stacked: true,
      gridLines: { display: false }
    },
    y:{
      stacked: true
    } 
  }
}

onMounted(async () => {
  await pedirGraficas()
  cpuReactive.value = cpu as any
})
</script>
<template>
  <v-container fluid>
    <v-row class="text-center" justify="center" no-gutters>
      <v-col cols="auto">
        <h2>CPU</h2>
        <Bar :data="cpuReactive" :options="options" />
      </v-col>
    </v-row>
  </v-container>
</template>
解决方案

核心思路

不需要将cpu设为响应式对象,直接通过修改已有的响应式变量cpuReactive的value值触发图表更新;同时修复数据处理逻辑的错误,确保符合Chart.js的数据集格式,避免mounted重复触发。

1. 修复数据处理逻辑

原代码存在数据集构造错误(将用户对象推入单个数据集的data数组),正确做法是为每个用户创建独立的数据集:

function readyChartDataSet(data: any) {
  // 用临时变量存储数据集,避免全局变量累加
  const tempDataSet: string[][] = [['cuando', 'quien', 'cuanto']]
  data.forEach((element: { cuando: string; usu: string; cpu: number }) => {
    tempDataSet.push([element.cuando, element.usu, element.cpu])
  })

  const pivot = new Pivot(tempDataSet, ['quien'], ['cuando'], 'cuanto', 'sum')
  // 提取时间标签,去掉首尾冗余项
  const labels = pivot.data.table[0].value.slice(1, -1)
  
  // 为每个用户生成独立数据集
  const datasets = pivot.data.table.slice(1, -1).map((element: any) => {
    // 空值替换为0,提取数值数据
    const dataValues = element.value.slice(1, -1).map((val: string | number) => val === '' ? 0 : Number(val))
    // 生成随机区分色(也可自定义颜色映射)
    const hue = Math.floor(Math.random() * 360)
    return {
      label: element.value[0], // 用户名
      backgroundColor: `hsl(${hue}, 70%, 60%)`,
      barThickness: 40,
      borderRadius: 5,
      data: dataValues
    }
  })

  // 直接赋值给响应式变量,触发图表更新
  cpuReactive.value = { labels, datasets }
}

2. 修正图表配置

移除options中多余的type字段(vue-chartjs已通过<Bar>组件指定),并适配Chart.js v3+的grid配置:

const options = {
  responsive: true,
  maintainAspectRatio: true,
  scales: {
    x: {
      stacked: true,
      grid: { display: false } // 替换旧版gridLines为grid
    },
    y: {
      stacked: true
    } 
  }
}

3. 精简组件逻辑

去掉全局cpu静态对象,避免冗余变量;确保mounted仅执行一次数据请求:

// 初始化响应式变量时补全默认结构
const cpuReactive = ref<ChartData<'bar'>>({
  labels: [],
  datasets: []
})

onMounted(async () => {
  await pedirGraficas()
  // 如需定时刷新数据,可添加定时器并在卸载时清理
  // const timer = setInterval(async () => await pedirGraficas(), 5 * 60 * 1000)
  // onUnmounted(() => clearInterval(timer))
})

最终完整代码

<script lang="ts" setup>
import axios from 'axios'
import { onMounted, ref } from 'vue'
import Pivot from 'quick-pivot'

import {
  Chart as ChartJS,
  Title,
  Tooltip,
  Legend,
  BarElement,
  CategoryScale,
  LinearScale,
  type ChartData
} from 'chart.js'
import { Bar } from 'vue-chartjs'

ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)

const cpuReactive = ref<ChartData<'bar'>>({
  labels: [],
  datasets: []
})

const urlGraficas: string =
  import.meta.env.VITE_APP_ROOT_API + import.meta.env.VITE_BACKEND_DASHBOARD_DESA + '/graficas'

async function pedirGraficas() {
  const { data } = await axios.get<any>(urlGraficas + 'Cpu')
  readyChartDataSet(data)
}

function readyChartDataSet(data: any) {
  const tempDataSet: string[][] = [['cuando', 'quien', 'cuanto']]
  data.forEach((element: { cuando: string; usu: string; cpu: number }) => {
    tempDataSet.push([element.cuando, element.usu, element.cpu])
  })

  const pivot = new Pivot(tempDataSet, ['quien'], ['cuando'], 'cuanto', 'sum')
  const labels = pivot.data.table[0].value.slice(1, -1)
  
  const datasets = pivot.data.table.slice(1, -1).map((element: any) => {
    const dataValues = element.value.slice(1, -1).map((val: string | number) => val === '' ? 0 : Number(val))
    const hue = Math.floor(Math.random() * 360)
    return {
      label: element.value[0],
      backgroundColor: `hsl(${hue}, 70%, 60%)`,
      barThickness: 40,
      borderRadius: 5,
      data: dataValues
    }
  })

  cpuReactive.value = { labels, datasets }
}

const options = {
  responsive: true,
  maintainAspectRatio: true,
  scales: {
    x: {
      stacked: true,
      grid: { display: false }
    },
    y: {
      stacked: true
    } 
  }
}

onMounted(async () => {
  await pedirGraficas()
})
</script>
<template>
  <v-container fluid>
    <v-row class="text-center" justify="center" no-gutters>
      <v-col cols="auto">
        <h2>CPU</h2>
        <Bar :data="cpuReactive" :options="options" />
      </v-col>
    </v-row>
  </v-container>
</template>

内容的提问来源于stack exchange,提问作者tsiPlus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 14:42:22