feat(仓库管理): 添加库位二维码导出功能并优化查询参数

新增jsPDF依赖用于生成PDF格式的库位二维码
在仓库概览页面添加导出所有二维码按钮及实现逻辑
统一钢卷查询参数命名(itemName/itemMaterial/itemManufacturer)
修复审批状态同步问题并优化按钮点击事件处理
This commit is contained in:
砂糖
2025-12-10 14:19:37 +08:00
parent 95836e2a5a
commit 3efe81913f
5 changed files with 145 additions and 20 deletions

View File

@@ -56,6 +56,7 @@
"js-cookie": "3.0.1",
"jsbarcode": "^3.12.1",
"jsencrypt": "3.0.0-rc.1",
"jspdf": "^2.5.1",
"konva": "^10.0.2",
"mqtt": "^5.13.3",
"nprogress": "0.2.0",

View File

@@ -24,9 +24,18 @@
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="钢种">
<el-input v-model="queryParams.grade" placeholder="请输入钢种" clearable size="small"
<el-input v-model="queryParams.itemName" placeholder="请输入钢种" clearable size="small"
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质">
<el-input v-model="queryParams.itemMaterial" placeholder="请输入材质" clearable size="small"
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="厂家">
<el-input v-model="queryParams.itemManufacturer" placeholder="请输入厂家" clearable size="small"
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
@@ -115,6 +124,7 @@ export default {
pageSize: 10,
currentCoilNo: null,
grade: null,
itemType: 'raw_material',
dataType: 1 // 只查询当前数据,不查询历史数据
}
};
@@ -241,7 +251,8 @@ export default {
pageSize: 10,
currentCoilNo: null,
grade: null,
dataType: 1
dataType: 1,
itemType: 'raw_material',
};
this.getList();
},

View File

@@ -20,23 +20,18 @@
style="width: 100%; display: inline-block;" clearable />
</el-form-item>
<el-form-item label="产品名称" prop="productName">
<el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable
<el-form-item label="产品名称" prop="itemName">
<el-input v-model="queryParams.itemName" placeholder="请输入产品名称" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="规格" prop="spec">
<el-input v-model="queryParams.spec" placeholder="请输入规格" clearable
<el-form-item label="材质" prop="itemMaterial">
<el-input v-model="queryParams.itemMaterial" placeholder="请输入材质" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质" prop="material">
<el-input v-model="queryParams.material" placeholder="请输入材质" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="厂家" prop="factory">
<el-input v-model="queryParams.factory" placeholder="请输入厂家" clearable
<el-form-item label="厂家" prop="itemManufacturer">
<el-input v-model="queryParams.itemManufacturer" placeholder="请输入厂家" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
@@ -445,6 +440,10 @@ export default {
startTime: this.queryParams.updateTime?.[0],
endTime: this.queryParams.updateTime?.[1],
}
// 如果没有设置itemType则设置为raw_material
if (!query.itemType) {
query.itemType = 'raw_material';
}
listMaterialCoil(query).then(response => {
if (this.querys.warehouseId != 111) {
// 排除掉111仓库的

View File

@@ -59,9 +59,9 @@
</div>
</div>
<div class="card-actions">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(row)" v-if="row.auditStatus != 1">审批</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(row)">删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleApprove(row)" v-if="row.auditStatus != 1">审批</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(row)">删除</el-button>
</div>
</el-card>
</el-col>
@@ -92,7 +92,7 @@
<el-table-column label="厂家" align="center" prop="manufacturer" />
<el-table-column label="重量(t)" align="center" prop="netWeight" width="100" />
<el-table-column label="库区" align="center" prop="warehouseName" :show-overflow-tooltip="true" />
<el-table-column label="操作" align="center" width="100" fixed="right" v-if="currentPlan.auditStatus != 1">
<el-table-column label="操作" align="center" width="100" v-if="currentPlan.auditStatus != 1">
<template slot-scope="scope">
<el-button type="danger" size="small" @click.stop="handleDeleteCoil(scope.row)">删除</el-button>
</template>
@@ -228,6 +228,11 @@ export default {
}).then(res => {
this.loading = false;
this.buttonLoading = false;
// 如何当前选中的计划是被审批的计划,需要同步修改数据
if (row.planId == this.currentPlan.planId) {
this.currentPlan.auditStatus = 1;
}
this.$message({
message: '审批成功',
type: 'success'

View File

@@ -13,6 +13,8 @@
<!-- 右侧仓库信息区域 - 替换为 Bird 组件 -->
<div class="warehouse-container" v-if="selectedNodeId" v-loading="rightLoading" element-loading-text="加载中..."
element-loading-spinner="el-icon-loading">
<!-- 导出所有二维码 -->
<button buttonLoading type="primary" @click="exportAllQrcodes">导出二维码</button>
<WarehouseBird :warehouse-list="warehouseList" @open-init-dialog="openInitDialog" />
</div>
@@ -35,9 +37,9 @@
@mouseleave="resetGridHover">
<div v-for="row in 40" :key="`grid-row-${row}`" class="grid-selector-row">
<div v-for="col in 10" :key="`grid-col-${col}`" class="grid-selector-cell" :class="{
hovered: row <= hoverRow && col <= hoverCol,
selected: row <= initForm.rowCount && col <= initForm.columnCount
}">
hovered: row <= hoverRow && col <= hoverCol,
selected: row <= initForm.rowCount && col <= initForm.columnCount
}">
</div>
</div>
</div>
@@ -58,6 +60,8 @@
<script>
import { listActualWarehouse, treeActualWarehouseTwoLevel, getActualWarehouse, generateLocations } from "@/api/wms/actualWarehouse";
import WarehouseBird from './components/WarehouseBird.vue';
import jsPDF from 'jspdf';
import QRCode from 'qrcode';
export default {
name: "Overview",
@@ -125,6 +129,111 @@ export default {
.finally(() => { this.treeLoading = false; });
},
async exportAllQrcodes() {
this.buttonLoading = true;
if (!this.warehouseList || this.warehouseList.length === 0) {
this.$message.warning('暂无库位数据可导出');
return;
}
const loadingInstance = this.$loading({
lock: true,
text: '正在生成二维码PDF请稍候...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
try {
// 初始化PDF (A4纸尺寸: 210mm × 297mm换算成px约595 × 842)
const pdf = new jsPDF({
orientation: 'portrait', // 纵向
unit: 'mm', // 单位:毫米
format: 'a4' // A4格式
});
// 配置项
const qrCodeSize = 30; // 二维码尺寸(mm)
const margin = 10; // 页面边距(mm)
const gap = 10; // 二维码间距(mm)
const textOffset = 5; // 文字与二维码间距(mm)
const maxPerRow = Math.floor((210 - 2 * margin) / (qrCodeSize + gap)); // 每行最多二维码数
const maxPerPage = Math.floor((297 - 2 * margin) / (qrCodeSize + gap + textOffset + 5)) * maxPerRow; // 每页最多二维码数
let currentX = margin; // 当前X坐标
let currentY = margin; // 当前Y坐标
let currentIndex = 0; // 当前处理的库位索引
const totalCount = this.warehouseList.length;
// 遍历所有库位生成二维码并添加到PDF
for (const [index, item] of this.warehouseList.entries()) {
currentIndex = index;
// 跳过无ID的项
if (!item.actualWarehouseId) continue;
// 生成二维码 (返回base64格式)
const qrCodeDataUrl = await QRCode.toDataURL(item.actualWarehouseId, {
width: qrCodeSize * 3.78, // 转换mm到px (1mm ≈ 3.78px)
margin: 1,
errorCorrectionLevel: 'H' // 高容错率
});
// 检查是否需要换行
if (currentX + qrCodeSize > 210 - margin) {
currentX = margin;
currentY += qrCodeSize + gap + textOffset + 5;
}
// 检查是否需要分页
if (currentY + qrCodeSize > 297 - margin || (index > 0 && index % maxPerPage === 0)) {
pdf.addPage();
currentX = margin;
currentY = margin;
}
// 添加二维码到PDF
pdf.addImage(qrCodeDataUrl, 'PNG', currentX, currentY, qrCodeSize, qrCodeSize);
// 添加库位名称(自动换行处理)
const name = item.actualWarehouseName || '未知库位';
const fontSize = 8; // 字体大小
pdf.setFontSize(fontSize);
// 计算文字宽度,超出则截断
const textWidth = pdf.getTextWidth(name);
let displayName = name;
if (textWidth > qrCodeSize) {
const maxTextWidth = qrCodeSize;
let tempText = '';
for (let i = 0; i < name.length; i++) {
if (pdf.getTextWidth(tempText + name[i]) > maxTextWidth) {
displayName = tempText + '...';
break;
}
tempText += name[i];
}
}
// 文字居中显示在二维码下方
const textX = currentX + (qrCodeSize - pdf.getTextWidth(displayName)) / 2;
pdf.text(displayName, textX, currentY + qrCodeSize + textOffset);
// 更新X坐标
currentX += qrCodeSize + gap;
}
// 保存PDF
const fileName = `库位二维码_${this.selectedNode.actualWarehouseName}.pdf`;
pdf.save(fileName);
this.$message.success(`成功导出 ${totalCount} 个库位二维码`);
} catch (error) {
console.error('二维码导出失败:', error);
this.$message.error(`二维码导出失败:${error.message}`);
} finally {
this.buttonLoading = false;
loadingInstance.close();
}
},
// 树节点点击
handleNodeClick(node) {
if (this.isSwitching) return;