2026-01-13 17:59:42 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="category-bar-chart-container" style="position: relative;">
|
|
|
|
|
|
<!-- 维度筛选下拉框 - 选择要汇总的维度 -->
|
|
|
|
|
|
<div class="chart-select" style="position: absolute; top: 10px; right: 10px; z-index: 10; text-align: right;">
|
|
|
|
|
|
<select v-model="selectedType" @change="renderChart">
|
|
|
|
|
|
<option value="itemName">按物料名称汇总</option>
|
|
|
|
|
|
<option value="specification">按规格汇总</option>
|
|
|
|
|
|
<option value="material">按材质汇总</option>
|
|
|
|
|
|
<option value="manufacturer">按厂家汇总</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 柱状图容器 -->
|
|
|
|
|
|
<div class="chart-content" ref="chartRef"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 引入ECharts,全局引入的项目可删除该行
|
|
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
props: {
|
|
|
|
|
|
data: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
chartInstance: null, // ECharts实例
|
2026-01-14 11:02:39 +08:00
|
|
|
|
selectedType: 'manufacturer', // 默认选中:按厂家汇总
|
2026-01-13 17:59:42 +08:00
|
|
|
|
// 维度名称映射 - 用于图表标题/提示框展示
|
|
|
|
|
|
typeLabel: {
|
|
|
|
|
|
itemName: '物料名称',
|
|
|
|
|
|
specification: '规格',
|
|
|
|
|
|
material: '材质',
|
|
|
|
|
|
manufacturer: '厂家'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
// 监听数据源变化,深度监听,数据更新重新渲染
|
|
|
|
|
|
data: {
|
|
|
|
|
|
deep: true,
|
|
|
|
|
|
handler() {
|
|
|
|
|
|
this.renderChart()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
// 挂载后初始化图表
|
|
|
|
|
|
this.renderChart()
|
|
|
|
|
|
// 窗口大小变化,图表自适应
|
|
|
|
|
|
window.addEventListener('resize', this.resizeChart)
|
|
|
|
|
|
},
|
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
|
// 销毁资源,防止内存泄漏
|
|
|
|
|
|
window.removeEventListener('resize', this.resizeChart)
|
|
|
|
|
|
this.chartInstance && this.chartInstance.dispose()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 核心处理方法:按选择的维度 + itemType自动匹配字段,汇总数据
|
|
|
|
|
|
* ✅ 核心规则:itemType=product → 取嵌套的product对象内的对应字段
|
|
|
|
|
|
* ✅ 其他itemType → 取根级的对应字段
|
|
|
|
|
|
*/
|
|
|
|
|
|
formatChartData() {
|
|
|
|
|
|
const sourceData = this.data || []
|
|
|
|
|
|
if (sourceData.length === 0) return { xData: [], yData: [] }
|
|
|
|
|
|
|
|
|
|
|
|
// 分组汇总的核心对象
|
|
|
|
|
|
const countObj = {}
|
|
|
|
|
|
sourceData.forEach(item => {
|
|
|
|
|
|
if (!item) return
|
|
|
|
|
|
let targetValue = ''
|
|
|
|
|
|
const { itemType } = item
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 核心判断:根据itemType决定取值来源
|
|
|
|
|
|
switch (this.selectedType) {
|
|
|
|
|
|
case 'itemName':
|
|
|
|
|
|
targetValue = itemType === 'product'
|
|
|
|
|
|
? (item.product?.productName || item.product?.name || '无名称')
|
|
|
|
|
|
: (item.itemName || '无名称')
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'specification':
|
|
|
|
|
|
targetValue = itemType === 'product'
|
|
|
|
|
|
? (item.product?.specification || '无规格')
|
|
|
|
|
|
: (item.specification || '无规格')
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'material':
|
|
|
|
|
|
targetValue = itemType === 'product'
|
|
|
|
|
|
? (item.product?.material || '无材质')
|
|
|
|
|
|
: (item.material || '无材质')
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'manufacturer':
|
|
|
|
|
|
targetValue = itemType === 'product'
|
|
|
|
|
|
? (item.product?.manufacturer || '无厂家')
|
|
|
|
|
|
: (item.manufacturer || '无厂家')
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 空值统一处理为【无数据】,避免图表展示空字符串
|
|
|
|
|
|
const key = targetValue || '无数据'
|
|
|
|
|
|
countObj[key] = countObj[key] ? countObj[key] + 1 : 1
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 转为数组并按【数量倒序】排列,数量多的柱子在前,更直观
|
|
|
|
|
|
const sortArr = Object.entries(countObj).sort((a, b) => b[1] - a[1])
|
|
|
|
|
|
// 分离x轴类目 和 y轴数量
|
|
|
|
|
|
const xData = sortArr.map(item => item[0])
|
|
|
|
|
|
const yData = sortArr.map(item => item[1])
|
|
|
|
|
|
|
|
|
|
|
|
return { xData, yData }
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化渲染柱状图
|
|
|
|
|
|
renderChart() {
|
|
|
|
|
|
const { xData, yData } = this.formatChartData()
|
|
|
|
|
|
const chartDom = this.$refs.chartRef
|
|
|
|
|
|
if (!chartDom) return
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化ECharts实例
|
|
|
|
|
|
this.chartInstance = echarts.init(chartDom)
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
grid: { left: '8%', right: '8%', bottom: '8%', top: '8%' },
|
|
|
|
|
|
// 图表标题,根据选中维度动态变化
|
|
|
|
|
|
title: {
|
|
|
|
|
|
text: `${this.typeLabel[this.selectedType]} 数据汇总`,
|
|
|
|
|
|
left: 'center',
|
|
|
|
|
|
textStyle: { fontSize: 16 }
|
|
|
|
|
|
},
|
|
|
|
|
|
// 悬浮提示框
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
formatter: `{b}<br/>数量:{c} 条`,
|
|
|
|
|
|
axisPointer: { type: 'shadow' }
|
|
|
|
|
|
},
|
|
|
|
|
|
// x轴:类目轴(名称/规格/材质/厂家)
|
|
|
|
|
|
xAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: xData,
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
rotate: 30, // 文字旋转,防止类目名称过长重叠
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
overflow: 'truncate'
|
|
|
|
|
|
},
|
|
|
|
|
|
axisTick: { alignWithLabel: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
// y轴:数量轴,最小值为0
|
|
|
|
|
|
yAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
name: '数据数量',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
axisLabel: { formatter: '{value}' }
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
// 柱状图核心配置
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '数据数量',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
barWidth: '60%', // 柱子宽度
|
|
|
|
|
|
data: yData,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
|
{ offset: 0, color: '#0088ff' },
|
|
|
|
|
|
{ offset: 1, color: '#0055bb' }
|
|
|
|
|
|
])
|
|
|
|
|
|
},
|
|
|
|
|
|
// 柱子上显示具体数值
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'top',
|
|
|
|
|
|
fontSize: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置配置项渲染
|
|
|
|
|
|
this.chartInstance.setOption(option, true)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 窗口大小变化,图表自适应
|
|
|
|
|
|
resizeChart() {
|
|
|
|
|
|
this.chartInstance && this.chartInstance.resize()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
/* 外层容器样式 */
|
|
|
|
|
|
.category-bar-chart-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 550px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 筛选下拉框样式 */
|
|
|
|
|
|
.chart-select {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chart-select select {
|
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
border: 1px solid #dcdcdc;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 图表容器样式 */
|
|
|
|
|
|
.chart-content {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: calc(100% - 40px);
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|