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
相关产品推荐
相关产品推荐

