Files
xgy-oa/klp-ui/src/views/wms/delivery/components/wayBill.vue
砂糖 522f3400fc feat(wms): 优化发货单和钢卷管理功能
- 修改重量单位从kg为T
- 发货单增加仓库位置列和禁用已发货单操作
- 优化钢卷卡片UI样式
- 改进打印功能使用PDF格式
- 增加发货单状态判断和编辑控制
2026-01-27 15:41:29 +08:00

716 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>
<div class="waybill-container" ref="waybillRef">
<!-- 头部信息 -->
<div class="waybill-header">
<div class="header-left">
<span class="label">收货单位</span>
<input type="text" class="editable-input transparent-input" v-model="localWaybill.consigneeUnit" />
</div>
<div class="header-center">
<input type="text" class="editable-input date-input transparent-input" v-model="localWaybill.deliveryYear" />
<span class="label date-label"></span>
<input type="text" class="editable-input date-input transparent-input" v-model="localWaybill.deliveryMonth" />
<span class="label date-label"></span>
<input type="text" class="editable-input date-input transparent-input" v-model="localWaybill.deliveryDay" />
<span class="label date-label"></span>
</div>
<div class="header-right">
<span class="label">发货单位</span>
<input type="text" class="editable-input transparent-input" v-model="localWaybill.senderUnit" />
</div>
</div>
<div class="waybill-header">
<div class="header-left">
<span class="label">负责人</span>
<input type="text" class="editable-input transparent-input" v-model="localWaybill.principal" />
</div>
<div class="header-right">
<span class="label">电话</span>
<input type="text" class="editable-input transparent-input" v-model="localWaybill.principalPhone" />
</div>
<div class="header-center">
<span class="label">车牌</span>
<input type="text" class="editable-input transparent-input" v-model="localWaybill.licensePlate" />
</div>
</div>
<!-- 表格 -->
<table class="waybill-table">
<thead>
<tr>
<th>品名</th>
<th>切边</th>
<th>包装</th>
<th>仓库位置</th>
<th>结算</th>
<th>原料厂家</th>
<th>卷号</th>
<th>规格</th>
<th>材质</th>
<th>数量</th>
<th>重量T</th>
<th>单价</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<!-- 无明细提示 -->
<tr v-if="localWaybillDetails.length === 0">
<td colspan="13" class="no-data">
<div class="no-data-content">
<el-empty description="暂无发货单明细" />
</div>
</td>
</tr>
<!-- 明细数据 -->
<tr v-for="(item, index) in localWaybillDetails" :key="index">
<td><input type="text" class="transparent-input" v-model="item.productName" /></td>
<td><input type="text" class="table-input transparent-input" v-model="item.edgeType" />
</td>
<td><input type="text" class="table-input transparent-input" v-model="item.packageType" />
</td>
<td><input type="text" class="table-input transparent-input" v-model="item.actualWarehouseName" />
</td>
<td><input type="text" class="table-input transparent-input" v-model="item.settlementType" />
</td>
<td><input type="text" class="table-input transparent-input" v-model="item.rawMaterialFactory" />
</td>
<td><input type="text" class="table-input transparent-input" v-model="item.coilNumber" /></td>
<td><input type="text" class="table-input transparent-input" v-model="item.specification" /></td>
<td><input type="text" class="table-input transparent-input" v-model="item.material" />
</td>
<td><input type="number" class="table-input transparent-input" v-model.number="item.quantity"
placeholder="0" /></td>
<td><input type="number" class="table-input transparent-input" v-model.number="item.weight"
placeholder="0.00" /></td>
<td><input type="number" class="table-input transparent-input" v-model.number="item.unitPrice"
placeholder="0.00" /></td>
<td>
<div class="table-cell-with-action">
<input type="text" class="table-input transparent-input" v-model="item.remark" />
</div>
</td>
</tr>
</tbody>
</table>
<!-- 备注说明 -->
<div class="waybill-remarks">
<p>
1品名冷硬钢卷酸连轧冷轧钢卷脱脂退火火拉矫镀锌卷板镀锌管料镀锌分剪料2切边净边/毛边3包装裸包周三径四简包1周三径四内外护角简包2周三径四+防锈纸普包周三径四+内外护角+防锈纸+端护板精包1周三径四+内外护角+防锈纸+薄膜+端护板+内外护板精包2周三径四+内外护角+防锈纸+薄膜+端护板+内外护板+木托
</p>
</div>
<div class="waybill-pickup-location">
<!-- <div class="pickup-location-item inline"> -->
<span class="label">取货地点</span>
<input type="text" class="editable-input full-input transparent-input" v-model="localWaybill.pickupLocation" />
<!-- </div> -->
</div>
<!-- 签名栏 -->
<div class="waybill-footer">
<div class="footer-item inline">
<span class="label">销售</span>
<input type="text" class="editable-input signature-input transparent-input" />
</div>
<div class="footer-item inline">
<span class="label">发货</span>
<input type="text" class="editable-input signature-input transparent-input" />
</div>
<div class="footer-item inline">
<span class="label">磅房</span>
<input type="text" class="editable-input signature-input transparent-input" />
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="waybill-actions">
<el-button type="primary" @click="saveAsImage">保存为图片</el-button>
<el-button type="success" @click="printWaybill">打印</el-button>
</div>
</div>
</template>
<script>
import domtoimage from 'dom-to-image';
import { PDFDocument } from 'pdf-lib';
import html2canvas from 'html2canvas';
export default {
props: {
// 组件接收完整的发货单内容, 渲染发货单,这个面板内包括一个保存为图片(已经安装了dom-to-image)和一个打印按钮(已经安装了print-js)
waybill: {
type: Object,
default: () => { }
},
waybillDetails: {
type: Array,
default: () => []
}
},
data() {
return {
// 本地可编辑的发货单数据
localWaybill: {
consigneeUnit: '',
senderUnit: '',
deliveryYear: '',
deliveryMonth: '',
deliveryDay: '',
principal: '',
principalPhone: '',
licensePlate: '',
pickupLocation: ''
},
// 本地可编辑的发货单明细
localWaybillDetails: []
};
},
watch: {
// 监听props变化更新本地数据
waybill: {
handler(newVal) {
if (newVal) {
this.localWaybill = {
consigneeUnit: newVal.consigneeUnit || '',
senderUnit: newVal.senderUnit || '',
deliveryYear: this.getYearFromDate(newVal.deliveryTime) || '',
deliveryMonth: this.getMonthFromDate(newVal.deliveryTime) || '',
deliveryDay: this.getDayFromDate(newVal.deliveryTime) || '',
principal: newVal.principal || '',
principalPhone: newVal.principalPhone || '',
licensePlate: newVal.licensePlate || '',
pickupLocation: newVal.pickupLocation || ''
};
}
},
immediate: true,
deep: true
},
waybillDetails: {
handler(newVal) {
console.log('waybillDetails', newVal);
if (newVal && Array.isArray(newVal)) {
this.localWaybillDetails = [...newVal];
} else {
this.localWaybillDetails = [];
}
},
immediate: true,
deep: true
}
},
methods: {
// 从日期字符串中提取年份
getYearFromDate(dateStr) {
if (dateStr) {
const date = new Date(dateStr);
return date.getFullYear().toString();
}
return '';
},
// 从日期字符串中提取月份
getMonthFromDate(dateStr) {
if (dateStr) {
const date = new Date(dateStr);
return (date.getMonth() + 1).toString();
}
return '';
},
// 从日期字符串中提取日
getDayFromDate(dateStr) {
if (dateStr) {
const date = new Date(dateStr);
return date.getDate().toString();
}
return '';
},
// 保存为图片
saveAsImage() {
const node = this.$refs.waybillRef;
// 确保容器在保存图片时能完整显示所有内容
const originalWidth = node.style.width;
const originalOverflow = node.style.overflow;
// 临时调整容器样式,确保所有内容可见
node.style.width = 'auto';
node.style.overflow = 'visible';
// 获取实际内容宽度
const contentWidth = node.scrollWidth;
const contentHeight = node.scrollHeight;
domtoimage.toPng(node, {
width: contentWidth,
height: contentHeight,
style: {
width: `${contentWidth}px`,
height: `${contentHeight}px`,
overflow: 'visible'
}
})
.then(dataUrl => {
const link = document.createElement('a');
link.download = `发货单_${this.waybill.waybillName || this.waybill.waybillNo || Date.now()}.png`;
link.href = dataUrl;
link.click();
})
.catch(error => {
console.error('保存图片失败:', error);
this.$message.error('保存图片失败');
})
.finally(() => {
// 恢复原始样式
node.style.width = originalWidth;
node.style.overflow = originalOverflow;
});
},
// 打印发货单
async printWaybill() {
const node = this.$refs.waybillRef;
// 确保容器在打印时能完整显示所有内容
const originalWidth = node.style.width;
const originalHeight = node.style.height;
const paperWidthMm = 297;
const paperHeightMm = 180;
const originalOverflow = node.style.overflow;
// 临时调整容器样式,确保所有内容可见
node.style.width = 'auto';
node.style.overflow = 'visible';
// 获取实际内容宽度
const contentWidth = node.scrollWidth;
const contentHeight = node.scrollHeight;
// console.log('contentWidth', contentWidth, originalWidth, originalHeight);
// 改用html2canvas + pdf-lib打印pdf
const canvas = await html2canvas(node, {
backgroundColor: '#ffffff',
scale: 3,
useCORS: true,
// 让 html2canvas 为频繁读回优化 Canvas与批量导出一致
willReadFrequently: true,
// 确保按元素尺寸截图,明确指定宽高
width: contentWidth,
height: contentHeight,
windowWidth: contentWidth,
windowHeight: contentHeight,
});
// 5. 使用 pdf-lib 生成单页 PDF占满整张纸无边距
const mmToPt = 72 / 25.4;
const pageWidthPt = paperWidthMm * mmToPt;
const pageHeightPt = paperHeightMm * mmToPt;
const pdfDoc = await PDFDocument.create();
console.log('canvas', canvas);
const png = canvas.toDataURL('image/png');
const imgPng = await pdfDoc.embedPng(png);
// 确保页面尺寸正确宽100mm高80mm横向
const page = pdfDoc.addPage([pageWidthPt, pageHeightPt]);
// 直接拉伸填充整个PDF页面不留边距确保占满整张纸
// 从(0,0)开始,直接填充整个页面尺寸
page.drawImage(imgPng, {
x: 0,
y: 0,
width: pageWidthPt,
height: pageHeightPt
});
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const win = window.open(url, '_blank');
if (!win) {
const a = document.createElement('a');
a.href = url;
a.download = `标签_${new Date().getTime()}.pdf`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// 恢复原始样式
setTimeout(() => {
node.style.width = originalWidth;
node.style.overflow = originalOverflow;
}, 100);
}
}
}
</script>
<style scoped>
.waybill-container {
width: 960px;
max-width: none;
min-width: 850px;
margin: 0 auto;
padding: 20px;
background: white;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
font-family: SimSun, serif;
overflow-x: auto;
overflow-y: visible;
}
/* 头部样式 */
.waybill-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
font-size: 16px;
}
.header-left,
.header-right {
display: flex;
align-items: center;
}
.header-center {
display: flex;
align-items: center;
gap: 5px;
}
.label {
font-weight: bold;
display: inline-block;
width: 80px;
text-align: right;
white-space: nowrap;
}
.date-label {
width: 1em;
}
/* 可编辑输入框样式 */
.editable-input {
padding: 4px 8px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
font-family: SimSun, serif;
outline: none;
transition: all 0.2s;
border-bottom: 1px dashed #dcdfe6;
}
.editable-input:focus {
border-color: #409eff;
}
.date-input {
width: 30px;
text-align: center;
margin-right: 5px;
}
/* 透明输入框样式 */
.transparent-input {
border: none;
border-radius: 0;
background-color: transparent;
}
.transparent-input:focus {
border-bottom-color: #409eff;
background-color: rgba(64, 158, 255, 0.05);
}
/* 表格样式 */
.waybill-table {
width: 100%;
max-width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
font-size: 12px;
table-layout: fixed;
}
.waybill-table th,
.waybill-table td {
border: 1px solid #000;
box-sizing: border-box;
line-height: 24px;
text-align: center;
vertical-align: middle;
word-wrap: break-word;
word-break: break-all;
}
/* 表格列宽设置 */
.waybill-table th:nth-child(1),
.waybill-table td:nth-child(1) {
width: 80px;
/* 品名 */
}
.waybill-table th:nth-child(2),
.waybill-table td:nth-child(2) {
width: 40px;
/* 切边 */
}
.waybill-table th:nth-child(3),
.waybill-table td:nth-child(3) {
width: 40px;
/* 包装 */
}
.waybill-table th:nth-child(4),
.waybill-table td:nth-child(4) {
width: 80px;
/* 仓库位置 */
}
.waybill-table th:nth-child(5),
.waybill-table td:nth-child(5) {
width: 40px;
/* 结算 */
}
.waybill-table th:nth-child(6),
.waybill-table td:nth-child(6) {
width: 80px;
/* 原料厂家 */
}
.waybill-table th:nth-child(7),
.waybill-table td:nth-child(7) {
width: 100px;
/* 卷号 */
}
.waybill-table th:nth-child(8),
.waybill-table td:nth-child(8) {
width: 80px;
/* 规格 */
}
.waybill-table th:nth-child(9),
.waybill-table td:nth-child(9) {
width: 40px;
/* 材质 */
}
.waybill-table th:nth-child(10),
.waybill-table td:nth-child(10) {
width: 60px;
/* 数量(件) */
}
.waybill-table th:nth-child(11),
.waybill-table td:nth-child(11) {
width: 80px;
/* 重量kg */
}
.waybill-table th:nth-child(12),
.waybill-table td:nth-child(12) {
width: 40px;
/* 单价 */
}
.waybill-table th:nth-child(13),
.waybill-table td:nth-child(13) {
width: 100px;
/* 备注 */
}
.waybill-table th {
background-color: #f5f7fa;
font-weight: bold;
}
.waybill-table tr:nth-child(even) {
background-color: #fafafa;
}
/* 表格输入框样式 */
.table-input {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 4px;
}
.table-input:focus {
border-color: #409eff;
}
/* 无数据样式 */
.no-data {
height: 200px;
vertical-align: middle;
}
.no-data-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
/* 表格单元格操作区 */
.table-cell-with-action {
display: flex;
align-items: center;
gap: 5px;
}
/* 备注样式 */
.waybill-remarks {
margin-bottom: 30px;
font-size: 14px;
line-height: 1.5;
text-align: justify;
}
.waybill-remarks p {
margin: 5px 0;
}
/* 底部签名样式 */
.waybill-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
font-size: 16px;
}
.waybill-pickup-location {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
font-size: 16px;
}
.waybill-pickup-location label {
font-size: 14px;
margin-right: 10px;
width: 40px;
}
.footer-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 200px;
}
.footer-item.inline {
flex-direction: row;
align-items: center;
width: auto;
}
.footer-item .label {
font-size: 14px;
margin-right: 10px;
width: 40px;
}
.signature-input {
min-width: 150px;
}
.full-input {
/* 占满本行的剩余空间父容器不是flex */
flex: 1;
}
/* 操作按钮样式 */
.waybill-actions {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
}
/* 添加明细按钮 */
.add-detail-btn {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
/* 打印样式 */
@media print {
.waybill-container {
width: 100%;
max-width: none;
box-shadow: none;
padding: 0;
}
.waybill-header {
flex-wrap: nowrap;
justify-content: space-between;
}
.waybill-footer {
flex-wrap: nowrap;
justify-content: space-between;
}
.label {
width: 80px;
text-align: right;
white-space: nowrap;
}
.waybill-actions {
display: none;
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.waybill-header {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.waybill-table {
font-size: 12px;
}
.waybill-table th,
.waybill-table td {
padding: 2px;
}
.waybill-footer {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.footer-item.inline {
width: 100%;
}
.label {
width: auto;
text-align: left;
}
}
</style>