215 lines
5.5 KiB
Vue
215 lines
5.5 KiB
Vue
|
|
<template>
|
|||
|
|
<div ref="chartContainer" style="height: 100%;"></div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import * as echarts from 'echarts';
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: "MaterialBar",
|
|||
|
|
props: {
|
|||
|
|
// 图表高度
|
|||
|
|
height: {
|
|||
|
|
type: String,
|
|||
|
|
default: '300px'
|
|||
|
|
},
|
|||
|
|
// 库存数据
|
|||
|
|
stockData: {
|
|||
|
|
type: Array,
|
|||
|
|
default: () => []
|
|||
|
|
},
|
|||
|
|
// 选中的仓库ID
|
|||
|
|
selectedWarehouseId: {
|
|||
|
|
type: [String, Number],
|
|||
|
|
default: null
|
|||
|
|
},
|
|||
|
|
// 仓库树数据
|
|||
|
|
warehouseTreeData: {
|
|||
|
|
type: Array,
|
|||
|
|
default: () => []
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
chart: null
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
watch: {
|
|||
|
|
stockData: {
|
|||
|
|
handler: 'updateChart',
|
|||
|
|
deep: true
|
|||
|
|
},
|
|||
|
|
selectedWarehouseId: {
|
|||
|
|
handler: 'updateChart',
|
|||
|
|
immediate: true
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
this.initChart();
|
|||
|
|
this.updateChart();
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
initChart() {
|
|||
|
|
if (this.chart) {
|
|||
|
|
this.chart.dispose();
|
|||
|
|
}
|
|||
|
|
this.chart = echarts.init(this.$refs.chartContainer);
|
|||
|
|
window.addEventListener('resize', this.handleResize);
|
|||
|
|
},
|
|||
|
|
handleResize() {
|
|||
|
|
this.chart && this.chart.resize();
|
|||
|
|
},
|
|||
|
|
// 获取所有子仓库ID(包括自身)
|
|||
|
|
getAllWarehouseIds(warehouseId) {
|
|||
|
|
const ids = new Set();
|
|||
|
|
|
|||
|
|
const findChildren = (treeData, targetId) => {
|
|||
|
|
for (const node of treeData) {
|
|||
|
|
if (String(node.warehouseId) === String(targetId)) {
|
|||
|
|
// 找到目标节点,收集它和它的所有子节点的ID
|
|||
|
|
const collectIds = (node) => {
|
|||
|
|
ids.add(String(node.warehouseId));
|
|||
|
|
if (node.children) {
|
|||
|
|
node.children.forEach(child => collectIds(child));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
collectIds(node);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
if (node.children && findChildren(node.children, targetId)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (warehouseId) {
|
|||
|
|
findChildren(this.warehouseTreeData, warehouseId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return ids;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理数据,获取前10的物料
|
|||
|
|
processData() {
|
|||
|
|
let filteredData;
|
|||
|
|
|
|||
|
|
if (this.selectedWarehouseId) {
|
|||
|
|
// 获取选中仓库及其所有子仓库的ID
|
|||
|
|
const warehouseIds = this.getAllWarehouseIds(this.selectedWarehouseId);
|
|||
|
|
// 过滤属于这些仓库的数据
|
|||
|
|
filteredData = this.stockData.filter(item => warehouseIds.has(String(item.warehouseId)));
|
|||
|
|
} else {
|
|||
|
|
filteredData = this.stockData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按物料分组并汇总数量
|
|||
|
|
const materialMap = new Map();
|
|||
|
|
filteredData.forEach(item => {
|
|||
|
|
const key = item.itemName;
|
|||
|
|
const quantity = Number(item.quantity) || 0;
|
|||
|
|
const currentQuantity = materialMap.get(key) || 0;
|
|||
|
|
materialMap.set(key, currentQuantity + quantity);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 转换为数组并排序
|
|||
|
|
const sortedData = Array.from(materialMap.entries())
|
|||
|
|
.map(([name, value]) => ({ name, value }))
|
|||
|
|
.sort((a, b) => b.value - a.value)
|
|||
|
|
.slice(0, 10); // 只取前10名
|
|||
|
|
|
|||
|
|
return sortedData;
|
|||
|
|
},
|
|||
|
|
updateChart() {
|
|||
|
|
if (!this.chart) return;
|
|||
|
|
|
|||
|
|
const data = this.processData();
|
|||
|
|
const names = data.map(item => item.name);
|
|||
|
|
const values = data.map(item => item.value);
|
|||
|
|
|
|||
|
|
// 计算最大值,用于设置图表的最大刻度
|
|||
|
|
const maxValue = Math.max(...values);
|
|||
|
|
const interval = Math.ceil(maxValue / 5); // 将刻度分为5份
|
|||
|
|
|
|||
|
|
const option = {
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
axisPointer: {
|
|||
|
|
type: 'shadow'
|
|||
|
|
},
|
|||
|
|
formatter: (params) => {
|
|||
|
|
const data = params[0];
|
|||
|
|
return `${data.name}<br/>数量: ${data.value}`;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
grid: {
|
|||
|
|
left: '3%',
|
|||
|
|
right: '4%',
|
|||
|
|
bottom: '3%',
|
|||
|
|
containLabel: true
|
|||
|
|
},
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
boundaryGap: [0, 0.01],
|
|||
|
|
max: Math.ceil(maxValue / interval) * interval,
|
|||
|
|
interval: interval
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
data: names,
|
|||
|
|
axisTick: {
|
|||
|
|
alignWithLabel: true
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
formatter: (value) => {
|
|||
|
|
// 如果名称太长,截断并添加省略号
|
|||
|
|
if (value.length > 10) {
|
|||
|
|
return value.substring(0, 10) + '...';
|
|||
|
|
}
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '数量',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: values,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|||
|
|
{ offset: 0, color: '#83bff6' },
|
|||
|
|
{ offset: 0.5, color: '#188df0' },
|
|||
|
|
{ offset: 1, color: '#188df0' }
|
|||
|
|
])
|
|||
|
|
},
|
|||
|
|
emphasis: {
|
|||
|
|
itemStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|||
|
|
{ offset: 0, color: '#2378f7' },
|
|||
|
|
{ offset: 0.7, color: '#2378f7' },
|
|||
|
|
{ offset: 1, color: '#83bff6' }
|
|||
|
|
])
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
label: {
|
|||
|
|
show: true,
|
|||
|
|
position: 'right',
|
|||
|
|
formatter: '{c}'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.chart.setOption(option, true);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
beforeDestroy() {
|
|||
|
|
window.removeEventListener('resize', this.handleResize);
|
|||
|
|
if (this.chart) {
|
|||
|
|
this.chart.dispose();
|
|||
|
|
this.chart = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|