270 lines
6.6 KiB
Vue
270 lines
6.6 KiB
Vue
<template>
|
|
<div ref="chartContainer" :style="{ height: '100%' }"></div>
|
|
</template>
|
|
|
|
<script>
|
|
import * as echarts from 'echarts';
|
|
|
|
export default {
|
|
name: "ReaTree",
|
|
props: {
|
|
// 图表高度
|
|
height: {
|
|
type: String,
|
|
default: '600px'
|
|
},
|
|
// 库存数据
|
|
stockData: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
// 仓库数据树
|
|
warehouseTreeData: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
chart: null
|
|
};
|
|
},
|
|
mounted() {
|
|
this.initChart();
|
|
this.updateChartData();
|
|
},
|
|
watch: {
|
|
stockData: {
|
|
handler: 'updateChartData',
|
|
deep: true
|
|
},
|
|
warehouseTreeData: {
|
|
handler: 'updateChartData',
|
|
deep: true
|
|
}
|
|
},
|
|
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();
|
|
},
|
|
// 创建树形数据
|
|
createTreeData() {
|
|
// 递归构建仓库树
|
|
const buildWarehouseTree = (warehouseNode, parentId = '') => {
|
|
const stocks = this.stockData.filter(stock => stock.warehouseId === warehouseNode.warehouseId);
|
|
const children = [];
|
|
let totalQuantity = 0;
|
|
|
|
// 添加库存物品
|
|
stocks.forEach(stock => {
|
|
const quantity = Number(stock.quantity) || 0;
|
|
totalQuantity += quantity;
|
|
children.push({
|
|
id: `${warehouseNode.warehouseId}_${stock.itemCode || stock.itemName}`,
|
|
name: stock.itemName,
|
|
value: quantity,
|
|
itemInfo: {
|
|
type: stock.itemType,
|
|
code: stock.itemCode,
|
|
unit: stock.unit,
|
|
batchNo: stock.batchNo
|
|
}
|
|
});
|
|
});
|
|
|
|
// 递归处理子仓库
|
|
if (warehouseNode.children && warehouseNode.children.length > 0) {
|
|
warehouseNode.children.forEach(child => {
|
|
const childNode = buildWarehouseTree(child, warehouseNode.warehouseId);
|
|
if (childNode.value > 0) {
|
|
children.push(childNode);
|
|
totalQuantity += childNode.value;
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
id: String(warehouseNode.warehouseId),
|
|
name: warehouseNode.warehouseName,
|
|
value: totalQuantity,
|
|
warehouseInfo: {
|
|
code: warehouseNode.warehouseCode
|
|
},
|
|
children: children.length > 0 ? children : undefined
|
|
};
|
|
};
|
|
|
|
return this.warehouseTreeData.map(warehouse => buildWarehouseTree(warehouse));
|
|
},
|
|
// 获取层级样式配置
|
|
getLevelOption() {
|
|
return [
|
|
{
|
|
itemStyle: {
|
|
borderColor: '#555',
|
|
borderWidth: 4,
|
|
gapWidth: 3
|
|
},
|
|
emphasis: {
|
|
itemStyle: {
|
|
borderColor: '#333'
|
|
}
|
|
},
|
|
upperLabel: {
|
|
show: true,
|
|
height: 35,
|
|
fontSize: 16,
|
|
fontWeight: 'bold',
|
|
color: '#333'
|
|
}
|
|
},
|
|
{
|
|
itemStyle: {
|
|
borderColor: '#777',
|
|
borderWidth: 3,
|
|
gapWidth: 2
|
|
},
|
|
emphasis: {
|
|
itemStyle: {
|
|
borderColor: '#555'
|
|
}
|
|
},
|
|
upperLabel: {
|
|
show: true,
|
|
height: 28,
|
|
fontSize: 14
|
|
}
|
|
},
|
|
{
|
|
itemStyle: {
|
|
borderColor: '#999',
|
|
borderWidth: 2,
|
|
gapWidth: 1
|
|
},
|
|
emphasis: {
|
|
itemStyle: {
|
|
borderColor: '#777'
|
|
}
|
|
}
|
|
}
|
|
];
|
|
},
|
|
// 获取物料单位
|
|
getItemUnit(data) {
|
|
return data.itemInfo?.unit || '';
|
|
},
|
|
// 获取物料类型名称
|
|
getItemTypeName(type) {
|
|
const typeMap = {
|
|
raw_material: '原材料',
|
|
product: '产品',
|
|
semi_product: '半成品'
|
|
};
|
|
return typeMap[type] || type || '未分类';
|
|
},
|
|
// 更新图表数据
|
|
updateChartData() {
|
|
if (!this.chart) return;
|
|
|
|
const treeData = this.createTreeData();
|
|
const option = {
|
|
tooltip: {
|
|
formatter: (info) => {
|
|
const value = info.value || 0;
|
|
const treePath = info.treePathInfo || [];
|
|
let path = '';
|
|
|
|
for (let i = 0; i < treePath.length; i++) {
|
|
if (treePath[i].name) {
|
|
path += treePath[i].name;
|
|
if (i < treePath.length - 1) path += '/';
|
|
}
|
|
}
|
|
|
|
const content = [];
|
|
content.push(`<div class="tooltip-title">${echarts.format.encodeHTML(path)}</div>`);
|
|
content.push(`库存数量: ${echarts.format.addCommas(value)} ${this.getItemUnit(info.data)}`);
|
|
|
|
if (info.data.itemInfo) {
|
|
const item = info.data.itemInfo;
|
|
content.push(`物料类型: ${this.getItemTypeName(item.type)}`);
|
|
content.push(`物料编码: ${item.code || '无'}`);
|
|
if (item.batchNo) content.push(`批次号: ${item.batchNo}`);
|
|
}
|
|
|
|
return content.join('<br>');
|
|
}
|
|
},
|
|
series: [{
|
|
name: '库存',
|
|
type: 'treemap',
|
|
visibleMin: 300,
|
|
leafDepth: 2,
|
|
label: {
|
|
show: true,
|
|
fontSize: 12,
|
|
formatter: (params) => {
|
|
if (params.data.itemInfo) {
|
|
const unit = params.data.itemInfo.unit || '';
|
|
return `${params.name}\n${params.value}${unit}`;
|
|
}
|
|
return params.name;
|
|
},
|
|
ellipsis: true
|
|
},
|
|
upperLabel: {
|
|
show: true,
|
|
fontWeight: 'bold'
|
|
},
|
|
itemStyle: {
|
|
borderColor: '#fff',
|
|
borderWidth: 1
|
|
},
|
|
levels: this.getLevelOption(),
|
|
data: treeData
|
|
}]
|
|
};
|
|
|
|
this.chart.setOption(option, true);
|
|
},
|
|
// 高亮指定节点
|
|
highlightNode(id) {
|
|
if (!this.chart) return;
|
|
|
|
this.chart.dispatchAction({
|
|
type: 'highlight',
|
|
seriesIndex: 0,
|
|
targetNodeId: id
|
|
});
|
|
|
|
this.chart.dispatchAction({
|
|
type: 'treemapRootToNode',
|
|
seriesIndex: 0,
|
|
targetNodeId: id
|
|
});
|
|
}
|
|
},
|
|
beforeDestroy() {
|
|
window.removeEventListener('resize', this.handleResize);
|
|
if (this.chart) {
|
|
this.chart.dispose();
|
|
this.chart = null;
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.tooltip-title {
|
|
font-weight: bold;
|
|
margin-bottom: 5px;
|
|
}
|
|
</style>
|