Files
klp-oa/klp-ui/src/views/wms/coil/box3.vue
砂糖 6349328b76 feat(wms): 添加按实际库区查询库存和钢卷分布的功能
添加getStockByActual和getMaterialCoilDistributionByActualWarehouse API
修改库存和钢卷分布页面以支持按实际库区查询
移除合并页面中未使用的添加钢卷按钮
2025-11-03 17:22:02 +08:00

593 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="statistics-container" v-loading="loading">
<!-- 统计方式选择 -->
<el-form inline>
<MaterialSelect :itemType.sync="queryParams.itemType" :itemId.sync="queryParams.itemId" @change="getList" />
</el-form>
<!-- 图表展示区域 -->
<div class="charts-container">
<!-- 矩形树图 -->
<div class="chart-item">
<h3>{{ queryParams.statType === '1' ? '物料库存分布矩形树图' : '仓库库存分布矩形树图' }}</h3>
<div ref="treeChart" class="chart-box"></div>
</div>
<!-- 柱状图 -->
<div class="chart-item">
<h3>{{ queryParams.statType === '1' ? '物料卷数对比柱状图' : '仓库卷数对比柱状图' }}</h3>
<div ref="barChart" class="chart-box"></div>
</div>
<!-- 饼图 -->
<div class="chart-item">
<h3>{{ queryParams.statType === '1' ? '物料库存占比饼图' : '仓库库存占比饼图' }}</h3>
<div ref="pieChart" class="chart-box"></div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-container">
<el-table
max-height="400"
:data="list"
border
stripe
style="width: 100%; margin-top: 20px"
@row-click="handleTableRowClick"
row-class-name="table-row-hover"
>
<!-- 仓库相关列仅仓库统计时有效 -->
<el-table-column
v-if="queryParams.statType === '2'"
prop="actualWarehouseName"
label="仓库名称"
align="center"
min-width="150"
></el-table-column>
<!-- 物料类型列仅物料统计时有效 -->
<el-table-column
v-if="queryParams.statType === '1'"
prop="itemType"
label="物料类型"
align="center"
min-width="120"
:formatter="formatItemType"
></el-table-column>
<!-- 物料ID/名称列仅物料统计时有效 -->
<el-table-column
v-if="queryParams.statType === '1'"
label="物料信息"
align="center"
min-width="250"
>
<template slot-scope="scope">
<ProductInfo v-if="scope.row.itemType === 'product'" :productId="scope.row.itemId">
<template #default="{ product }">
{{ product.productName || '未知' }}({{ product.productCode || '无编码' }})
</template>
</ProductInfo>
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :materialId="scope.row.itemId">
<template #default="{ material }">
{{ material.rawMaterialName || '未知' }}({{ material.rawMaterialCode || '无编码' }})
</template>
</RawMaterialInfo>
</template>
</el-table-column>
<!-- 通用列 -->
<el-table-column prop="coilCount" label="卷数" align="center" min-width="80"></el-table-column>
<el-table-column prop="totalGrossWeight" label="总毛重(kg)" align="center" min-width="120"></el-table-column>
<el-table-column prop="totalNetWeight" label="总净重(kg)" align="center" min-width="120"></el-table-column>
</el-table>
<!-- 点击图表项或者表格行后弹出弹窗, 数据详情钻取 -->
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
width="80%"
:close-on-click-modal="false"
>
<!-- 使用封装的钻取表格组件 -->
<DrillDownTable
:query-params="drillDownQueryParams"
:item-name="drillDownParams.itemName"
:warehouse-name="drillDownParams.actualWarehouseName"
/>
</el-dialog>
</div>
</div>
</template>
<script>
import { getMaterialCoilDistributionByType, getMaterialCoilDistributionByActualWarehouse } from "@/api/wms/coil";
import MaterialSelect from "@/components/KLPService/MaterialSelect";
import * as echarts from 'echarts';
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import { findItemWithBom } from "@/store/modules/category";
// 导入封装的钻取表格组件
import DrillDownTable from './panels/DrillDownTable.vue';
export default {
components: {
MaterialSelect,
RawMaterialInfo,
ProductInfo,
DrillDownTable // 注册组件
},
data() {
return {
queryParams: {
statType: '2',
itemType: undefined,
itemId: undefined,
},
loading: false,
list: [],
// 科技风配色方案(蓝色系为主,体现科技感)
techColors: [
'#165DFF', // 主蓝色
'#36CFC9', // 青绿色
'#4080FF', // 中蓝色
'#0FC6C2', // 深青色
'#6AA1FF', // 浅蓝色
'#2BBEBA', // 浅青色
'#8CAAFF', // 极浅蓝色
'#08A8A5' // 暗青色
],
// 图表实例
treeChart: null,
barChart: null,
pieChart: null,
// 钻取相关数据
dialogVisible: false,
dialogTitle: '',
drillDownParams: {
itemType: null,
itemId: null,
itemName: '',
actualWarehouseId: null,
actualWarehouseName: '',
},
// 传给钻取表格组件的查询参数
drillDownQueryParams: {
actualWarehouseId: null,
itemType: null,
itemId: null
}
}
},
mounted() {
this.initCharts();
this.getList();
},
beforeDestroy() {
// 销毁图表实例
if (this.treeChart) this.treeChart.dispose();
if (this.barChart) this.barChart.dispose();
if (this.pieChart) this.pieChart.dispose();
window.removeEventListener('resize', this.handleResize);
},
methods: {
getList() {
this.loading = true;
if (this.queryParams.statType === '1') {
// 物料统计:仓库字段无效,以物料为核心
getMaterialCoilDistributionByType(this.queryParams).then(res => {
this.list = res.data || [];
this.updateCharts();
this.loading = false;
})
} else if (this.queryParams.statType === '2') {
// 仓库统计:物料字段无效,以仓库为核心
getMaterialCoilDistributionByActualWarehouse(this.queryParams).then(res => {
this.list = res.data || [];
this.updateCharts();
this.loading = false;
})
}
},
// 初始化图表
initCharts() {
this.treeChart = echarts.init(this.$refs.treeChart);
this.barChart = echarts.init(this.$refs.barChart);
this.pieChart = echarts.init(this.$refs.pieChart);
this.handleResize = () => {
this.treeChart.resize();
this.barChart.resize();
this.pieChart.resize();
};
window.addEventListener('resize', this.handleResize);
},
// 更新图表数据
updateCharts() {
this.updateTreeChart();
this.updateBarChart();
this.updatePieChart();
},
// 获取科技风颜色(循环使用配色方案)
getTechColor(index) {
return this.techColors[index % this.techColors.length];
},
// 更新矩形树图
updateTreeChart() {
let treeData;
if (this.queryParams.statType === '1') {
// 物料统计:一级为物料,二级为卷数信息
treeData = {
name: '物料库存总览',
children: this.list.map((item, index) => ({
name: this.getMaterialName(item), // 物料名称
value: item.coilCount,
itemType: item.itemType,
itemId: item.itemId,
itemStyle: { color: this.getTechColor(index) }
}))
};
} else {
// 仓库统计:一级为仓库,二级为卷数信息
treeData = {
name: '仓库库存总览',
children: this.list.map((item, index) => ({
name: item.actualWarehouseName || '未知仓库',
value: item.coilCount,
actualWarehouseId: item.actualWarehouseId,
actualWarehouseName: item.actualWarehouseName,
itemStyle: { color: this.getTechColor(index) }
}))
};
}
this.treeChart.setOption({
backgroundColor: 'transparent',
tooltip: {
formatter: params => `${params.data.name}<br>卷数: ${params.data.value}`
},
series: [{
type: 'treemap',
data: [treeData],
roam: false,
label: {
show: true,
color: '#fff', // 文字白色更贴合科技风深色背景
fontWeight: 'bold'
},
emphasis: {
focus: 'descendant',
itemStyle: {
borderColor: '#fff',
borderWidth: 2
}
},
breadcrumb: {
show: true,
itemStyle: { color: '#666' }
},
// 添加点击事件,实现数据钻取
itemStyle: {
cursor: 'pointer'
},
select: {
itemStyle: {
borderColor: '#fff',
borderWidth: 2,
shadowBlur: 10,
shadowColor: 'rgba(22, 93, 255, 0.5)'
}
}
}]
});
// 绑定点击事件
this.treeChart.off('click');
this.treeChart.on('click', params => {
// 确保点击的是一级节点而不是子节点
if (params.data.children && params.data.children.length > 0) {
this.handleChartItemClick(params.data);
}
});
},
// 更新柱状图
updateBarChart() {
let xAxisData, seriesData;
if (this.queryParams.statType === '1') {
// 物料统计x轴为物料名称为数据添加额外信息用于钻取
xAxisData = this.list.map(item => this.getMaterialName(item));
seriesData = this.list.map((item, index) => ({
value: item.coilCount,
itemType: item.itemType,
itemId: item.itemId
}));
} else {
// 仓库统计x轴为仓库名称为数据添加额外信息用于钻取
xAxisData = this.list.map(item => item.actualWarehouseName || '未知仓库');
seriesData = this.list.map((item, index) => ({
value: item.coilCount,
actualWarehouseId: item.actualWarehouseId,
actualWarehouseName: item.actualWarehouseName
}));
}
this.barChart.setOption({
backgroundColor: 'transparent',
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: 'category',
data: xAxisData,
axisLabel: { rotate: 30, color: '#666' },
axisLine: { lineStyle: { color: '#ddd' } }
}],
yAxis: [{
type: 'value',
name: '卷数',
minInterval: 1,
axisLabel: { color: '#666' },
axisLine: { lineStyle: { color: '#ddd' } },
splitLine: { lineStyle: { color: '#f0f0f0' } }
}],
series: [{
name: '卷数',
type: 'bar',
data: seriesData,
itemStyle: {
// 柱状图使用渐变色增强科技感
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#165DFF' },
{ offset: 1, color: '#6AA1FF' }
]),
cursor: 'pointer'
},
emphasis: {
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#0E42D2' },
{ offset: 1, color: '#4080FF' }
])
}
}
}]
});
// 绑定点击事件
this.barChart.off('click');
this.barChart.on('click', params => {
this.handleChartItemClick(params.data);
});
},
// 更新饼图
updatePieChart() {
let pieData;
if (this.queryParams.statType === '1') {
// 物料统计:按物料分组,为数据添加额外信息用于钻取
pieData = this.list.map((item, index) => ({
name: this.getMaterialName(item),
value: item.coilCount,
itemType: item.itemType,
itemId: item.itemId,
itemStyle: { color: this.getTechColor(index) }
}));
} else {
// 仓库统计:按仓库分组,为数据添加额外信息用于钻取
pieData = this.list.map((item, index) => ({
name: item.actualWarehouseName || '未知仓库',
value: item.coilCount,
actualWarehouseId: item.actualWarehouseId,
actualWarehouseName: item.actualWarehouseName,
itemStyle: { color: this.getTechColor(index) }
}));
}
this.pieChart.setOption({
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
// legend: {
// orient: 'vertical',
// left: 10,
// data: pieData.map(item => item.name),
// textStyle: { color: '#666' }
// },
series: [{
name: '库存分布',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 6,
borderColor: '#fff',
borderWidth: 2,
cursor: 'pointer',
// 饼图扇区添加光泽感
shadowBlur: 10,
shadowColor: 'rgba(22, 93, 255, 0.2)'
},
label: { show: false },
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold',
color: '#165DFF'
},
itemStyle: {
shadowBlur: 15,
shadowColor: 'rgba(22, 93, 255, 0.5)'
}
},
labelLine: { show: false },
data: pieData
}]
});
// 绑定点击事件
this.pieChart.off('click');
this.pieChart.on('click', params => {
this.handleChartItemClick(params.data);
});
},
// 生成物料名称
getMaterialName(item) {
if (item.itemType === 'product') {
return `成品(${item.itemId.slice(-6)})`; // 取ID后6位简化显示
} else if (item.itemType === 'raw_material') {
return `原材料(${item.itemId.slice(-6)})`;
}
return '未知物料';
},
// 格式化物料类型
formatItemType(row) {
const typeMap = {
'raw_material': '原材料',
'product': '成品'
};
return typeMap[row.itemType] || '未知';
},
// 处理图表项点击,实现数据钻取
handleChartItemClick(data) {
// 重置钻取参数
this.drillDownParams = {
itemType: null,
itemId: null,
itemName: '',
actualWarehouseId: null,
actualWarehouseName: '',
};
// 重置传给子组件的查询参数
this.drillDownQueryParams = {
actualWarehouseId: null,
itemType: null,
itemId: null
};
// 根据数据类型设置钻取参数
if (data.itemType && data.itemId) {
// 物料统计钻取
this.drillDownParams.itemType = data.itemType;
this.drillDownParams.itemId = data.itemId;
this.drillDownQueryParams.itemType = data.itemType;
this.drillDownQueryParams.itemId = data.itemId;
// 从store中获取物料名称
const item = findItemWithBom(data.itemType, data.itemId);
this.drillDownParams.itemName = item ? item.itemName : data.name;
this.dialogTitle = `${data.itemType === 'product' ? '成品' : '原材料'}库存明细 - ${this.drillDownParams.itemName}`;
} else if (data.actualWarehouseId && data.actualWarehouseName) {
// 仓库统计钻取
this.drillDownParams.actualWarehouseId = data.actualWarehouseId;
this.drillDownParams.actualWarehouseName = data.actualWarehouseName;
this.drillDownQueryParams.actualWarehouseId = data.actualWarehouseId;
this.dialogTitle = `仓库库存明细 - ${data.actualWarehouseName}`;
}
// 打开弹窗
this.dialogVisible = true;
},
// 处理表格行点击
handleTableRowClick(row) {
// 构建点击数据对象
const clickData = {};
if (this.queryParams.statType === '1') {
// 物料统计
clickData.itemType = row.itemType;
clickData.itemId = row.itemId;
clickData.name = this.getMaterialName(row);
} else {
// 仓库统计
clickData.actualWarehouseId = row.actualWarehouseId;
clickData.actualWarehouseName = row.actualWarehouseName;
}
// 调用图表点击处理函数
this.handleChartItemClick(clickData);
}
}
}
</script>
<style scoped>
.statistics-container {
padding: 20px;
background-color: #fafafa;
}
.charts-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}
.chart-item {
flex: 1;
min-width: 300px;
background: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.chart-box {
width: 100%;
height: 300px;
}
.table-container {
margin-top: 20px;
background: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
h3 {
margin: 0 0 15px 0;
font-size: 16px;
color: #1D2129;
font-weight: 500;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
/* 表格行悬停效果 */
.table-row-hover:hover {
background-color: #f5f7fa;
cursor: pointer;
}
/* 图表项悬停效果增强 */
:deep(.echarts-tooltip) {
border-radius: 6px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
}
</style>