Files
klp-oa/klp-ui/src/views/wms/coil/box2.vue
砂糖 77f43fa334 feat(wms): 新增钢卷状态管理和库位查询功能
refactor(warehouse): 将仓库相关术语统一修改为库位
style(warehouse): 优化库位管理界面表单布局
fix(warehouse): 修复树形组件数据展示问题

新增钢卷状态管理功能,支持在列表中直接修改状态
为钢卷管理添加库位查询条件,优化数据展示逻辑
统一将仓库相关术语修改为库位,保持系统一致性
移除部分无用代码和注释,优化界面布局
2025-10-31 15:07:03 +08:00

715 lines
23 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>
<!-- <el-form-item label="统计方式" prop="statType">
<el-radio-group v-model="queryParams.statType" @change="getList">
<el-radio label="1">物料</el-radio>
<el-radio label="2">仓库</el-radio>
</el-radio-group>
</el-form-item> -->
<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="800" :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="warehouseName" 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>
<!-- 点击图表项或者表格行后弹出弹窗, 数据详情钻取获取汇总的数据详情如果当前是物料统计钻取将itemType和itemId作为条件查询list
如果当前是仓库统计将仓库id作为筛选条件 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="80%" :close-on-click-modal="false">
<div class="drill-down-content">
<!-- 钻取条件显示 -->
<div class="drill-down-header">
<el-tag size="large" v-if="drillDownParams.itemType">
{{ drillDownParams.itemType === 'product' ? '成品' : '原材料' }}
</el-tag>
<el-tag size="large" v-if="drillDownParams.itemName" type="primary">
{{ drillDownParams.itemName }}
</el-tag>
<el-tag size="large" v-if="drillDownParams.warehouseName" type="success">
{{ drillDownParams.warehouseName }}
</el-tag>
</div>
<!-- 加载状态 -->
<el-skeleton :loading="drillDownLoading" animated>
<template #template>
<div class="demo-skeleton">
<el-skeleton-item variant="p" style="width: 80%" />
<el-skeleton-item variant="text" style="width: 30%" />
<el-skeleton-item variant="text" style="width: 60%" />
</div>
</template>
<!-- 钻取表格 -->
<el-table max-height="400" v-loading="drillDownLoading" :data="drillDownList" border stripe style="width: 100%"
v-if="!drillDownLoading">
<el-table-column prop="warehouseName" label="仓库名称" align="center" min-width="150"></el-table-column>
<el-table-column prop="currentCoilNo" label="当前卷号" align="center" min-width="120"></el-table-column>
<el-table-column prop="enterCoilNo" label="入场卷号" align="center" min-width="180"></el-table-column>
<el-table-column label="物料类型" align="center" prop="itemType">
<template slot-scope="scope">
{{ scope.row.itemType == 'product' ? '成品' : '原料' }}
</template>
</el-table-column>
<el-table-column 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 label="更新时间" align="center" prop="updateTime" />
<el-table-column prop="grossWeight" label="毛重(kg)" align="center" min-width="100"></el-table-column>
<el-table-column prop="netWeight" label="净重(kg)" align="center" min-width="100"></el-table-column>
</el-table>
</el-skeleton>
<!-- 分页 -->
<div class="pagination-container" v-if="!drillDownLoading && drillDownList.length > 0">
<el-pagination background :current-page.sync="drillDownQueryParams.pageNum"
:page-size.sync="drillDownQueryParams.pageSize" :page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper" :total="drillDownTotal"
@size-change="handleDrillDownSizeChange" @current-change="handleDrillDownCurrentChange"></el-pagination>
</div>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
import { getMaterialCoilDistributionByType, getMaterialCoilDistributionByWarehouse } from "@/api/wms/coil";
import { listMaterialCoil } 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";
export default {
components: {
MaterialSelect,
RawMaterialInfo,
ProductInfo,
},
data() {
return {
queryParams: {
statType: '1',
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: '',
drillDownLoading: false,
drillDownList: [],
drillDownTotal: 0,
drillDownParams: {
itemType: null,
itemId: null,
itemName: '',
warehouseId: null,
warehouseName: '',
dataType: 1
},
drillDownQueryParams: {
pageNum: 1,
pageSize: 20
}
}
},
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') {
// 仓库统计:物料字段无效,以仓库为核心
getMaterialCoilDistributionByWarehouse(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) },
// children: [{
// name: `卷数: ${item.coilCount}`,
// value: item.coilCount,
// itemStyle: { color: this.getTechColor(index + 1).replace('rgb', 'rgba').replace(')', ', 0.7)') }
// }]
}))
};
} else {
// 仓库统计:一级为仓库,二级为卷数信息
treeData = {
name: '仓库库存总览',
children: this.list.map((item, index) => ({
name: item.warehouseName || '未知仓库',
value: item.coilCount,
warehouseId: item.warehouseId,
warehouseName: item.warehouseName,
itemStyle: { color: this.getTechColor(index) },
// children: [{
// name: `卷数: ${item.coilCount}`,
// value: item.coilCount,
// itemStyle: { color: this.getTechColor(index + 1).replace('rgb', 'rgba').replace(')', ', 0.7)') }
// }]
}))
};
}
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.warehouseName || '未知仓库');
seriesData = this.list.map((item, index) => ({
value: item.coilCount,
warehouseId: item.warehouseId,
warehouseName: item.warehouseName
}));
}
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.warehouseName || '未知仓库',
value: item.coilCount,
warehouseId: item.warehouseId,
warehouseName: item.warehouseName,
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: '',
warehouseId: null,
warehouseName: '',
dataType: 1
};
// 根据数据类型设置钻取参数
if (data.itemType && data.itemId) {
// 物料统计钻取
this.drillDownParams.itemType = data.itemType;
this.drillDownParams.itemId = data.itemId;
// 从store中获取物料名称
const item = findItemWithBom(data.itemType, data.itemId);
console.log('item', item, data);
this.drillDownParams.itemName = item ? item.itemName : data.name;
this.dialogTitle = `${data.itemType === 'product' ? '成品' : '原材料'}库存明细 - ${this.drillDownParams.itemName}`;
} else if (data.warehouseId && data.warehouseName) {
// 仓库统计钻取
this.drillDownParams.warehouseId = data.warehouseId;
this.drillDownParams.warehouseName = data.warehouseName;
this.dialogTitle = `仓库库存明细 - ${data.warehouseName}`;
}
// 重置分页并打开弹窗
this.drillDownQueryParams.pageNum = 1;
this.dialogVisible = true;
// 获取钻取数据
this.getDrillDownList();
},
// 处理表格行点击
handleTableRowClick(row) {
// 构建点击数据对象
const clickData = {};
if (this.queryParams.statType === '1') {
// 物料统计
clickData.itemType = row.itemType;
clickData.itemId = row.itemId;
clickData.name = this.getMaterialName(row);
} else {
// 仓库统计
clickData.warehouseId = row.warehouseId;
clickData.warehouseName = row.warehouseName;
}
// 调用图表点击处理函数
this.handleChartItemClick(clickData);
},
// 获取钻取数据列表
getDrillDownList() {
this.drillDownLoading = true;
// 构建查询参数
const params = {
...this.drillDownQueryParams,
itemType: this.drillDownParams.itemType,
itemId: this.drillDownParams.itemId,
warehouseId: this.drillDownParams.warehouseId
};
// 调用API获取数据
listMaterialCoil(params).then(res => {
this.drillDownList = res.rows || [];
this.drillDownTotal = res.total || 0;
this.drillDownLoading = false;
}).catch(error => {
console.error('获取钻取数据失败:', error);
this.drillDownLoading = false;
this.$message.error('获取明细数据失败');
});
},
// 处理钻取分页大小变化
handleDrillDownSizeChange(val) {
this.drillDownQueryParams.pageSize = val;
this.getDrillDownList();
},
// 处理钻取当前页变化
handleDrillDownCurrentChange(val) {
this.drillDownQueryParams.pageNum = val;
this.getDrillDownList();
},
// 格式化卷材状态
formatCoilStatus(status) {
const statusMap = {
'IN_STOCK': '在库',
'OUT_STOCK': '出库',
'TRANSFERING': '转移中',
'MAINTAINING': '维护中'
};
return statusMap[status] || '未知';
},
// 获取卷材状态标签类型
getCoilStatusTagType(status) {
const typeMap = {
'IN_STOCK': 'success',
'OUT_STOCK': 'info',
'TRANSFERING': 'warning',
'MAINTAINING': 'danger'
};
return typeMap[status] || 'default';
}
}
}
</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;
}
/* 钻取弹窗样式 */
.drill-down-content {
padding: 10px 0;
}
.drill-down-header {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
}
.drill-down-header .el-tag {
margin-right: 10px;
margin-bottom: 10px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 骨架屏样式 */
.demo-skeleton {
padding: 20px 0;
}
/* 图表项悬停效果增强 */
:deep(.echarts-tooltip) {
border-radius: 6px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
}
</style>