feat(wms): 新增钢卷去向标签和异常信息查看功能

- 新增钢卷去向标签(WhereTag)及相关配置
- 在base.vue中添加钢卷去向选择功能
- 新增异常信息查看弹窗组件(abnormal.vue)
- 新增多个仓库视图页面(mini.vue, scrap.vue等)
- 在发货单组件中新增Excel导出功能
- 优化标签打印功能,支持多种标签类型
- 修复报表查询时间参数问题
This commit is contained in:
砂糖
2026-02-07 14:36:56 +08:00
parent 0fae7ad434
commit 8efb46289c
14 changed files with 1331 additions and 15 deletions

View File

@@ -0,0 +1,297 @@
<template>
<div class="label-container" :style="{ '--print-scale': printScale }">
<table class="material-label-table">
<tr>
<td class="label-cell" style="width: 16.67%; padding: 4px;">卷号</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.currentCoilNo || '' }}</div>
</td>
<td class="label-cell" style="width: 16.67%; padding: 4px;">来源</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.warehouseName || '' }}</div>
</td>
</tr>
<tr>
<td class="label-cell" style="width: 16.67%; padding: 4px;">班组</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.team || '' }}</div>
</td>
<td class="label-cell" style="width: 16.67%; padding: 4px;">净重</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.netWeight || '' }}</div>
</td>
</tr>
<tr>
<td class="label-cell" style="width: 16.67%; padding: 4px;">规格</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.specification || '' }}</div>
</td>
<td class="label-cell" style="width: 16.67%; padding: 4px;">材质</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.material || '' }}</div>
</td>
</tr>
<tr>
<td class="label-cell" style="width: 16.67%; padding: 4px;">卷名</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.itemName || '' }}</div>
</td>
<td class="label-cell" style="width: 16.67%; padding: 4px;">厂家</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.manufacturer || '' }}</div>
</td>
</tr>
<tr>
<td class="label-cell" style="width: 16.67%; padding: 4px;">时间</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.updateTime || '' }}</div>
</td>
<td class="label-cell" style="width: 16.67%; padding: 4px;">去向</td>
<td class="value-cell" colspan="2" style="width: 33.33%; padding: 4px;">
<div class="nob" contenteditable>{{ content.nextWarehouseName || '' }}</div>
</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: 'WhereTag',
props: {
content: {
type: Object,
default: () => ({
coilNumber: '',
specification: '',
netWeight: '',
material: '',
nextProcess: '',
productionTeam: '',
productionDate: '',
qrcodeRecordId: '',
})
},
paperWidthMm: {
type: Number,
default: 100 // 原料码100mm x 80mm宽100高80
},
paperHeightMm: {
type: Number,
default: 80
}
},
data() {
return {
printScale: 1,
}
},
mounted() {
// 使用 matchMedia 监听打印状态(更可靠)
this.printMediaQuery = window.matchMedia('print');
this.printMediaQuery.addListener(this.handlePrintMediaChange);
// 监听打印事件作为备用
window.addEventListener('beforeprint', this.handleBeforePrint);
window.addEventListener('afterprint', this.handleAfterPrint);
// 计算初始缩放比例
this.$nextTick(() => {
this.calculatePrintScale();
});
},
beforeDestroy() {
if (this.printMediaQuery) {
this.printMediaQuery.removeListener(this.handlePrintMediaChange);
}
window.removeEventListener('beforeprint', this.handleBeforePrint);
window.removeEventListener('afterprint', this.handleAfterPrint);
},
methods: {
handlePrintMediaChange(mq) {
if (mq.matches) {
// 进入打印模式
this.calculatePrintScale();
} else {
// 退出打印模式
this.resetPrintScale();
}
},
handleBeforePrint() {
// 延迟计算确保DOM已更新
setTimeout(() => {
this.calculatePrintScale();
}, 100);
},
handleAfterPrint() {
this.resetPrintScale();
},
calculatePrintScale() {
this.$nextTick(() => {
const container = this.$el;
if (!container) return;
// 纸张尺寸180mm x 100mm
const dpi = 96; // 标准 DPI
const mmToPx = dpi / 25.4; // 1mm = 3.779527559px
const paperWidthMm = this.paperWidthMm || 100;
const paperHeightMm = this.paperHeightMm || 80;
const marginMm = 2;
const paperWidthPx = paperWidthMm * mmToPx;
const paperHeightPx = paperHeightMm * mmToPx;
const marginPx = marginMm * mmToPx;
// 获取内容实际尺寸
const rect = container.getBoundingClientRect();
let contentWidth = rect.width;
let contentHeight = rect.height;
// 如果尺寸为0或无效尝试使用 scrollWidth/scrollHeight
if (contentWidth === 0 || contentHeight === 0) {
contentWidth = container.scrollWidth || contentWidth;
contentHeight = container.scrollHeight || contentHeight;
}
// 计算可用空间(减去边距)
const availableWidth = paperWidthPx - marginPx * 2;
const availableHeight = paperHeightPx - marginPx * 2;
// 计算缩放比例
const scaleX = contentWidth > 0 ? availableWidth / contentWidth : 1;
const scaleY = contentHeight > 0 ? availableHeight / contentHeight : 1;
// 取较小的缩放比例确保内容完全适配不超过1不放大
this.printScale = Math.min(scaleX, scaleY, 1);
// 设置CSS变量和内联样式
container.style.setProperty('--print-scale', this.printScale);
container.style.setProperty('--paper-width', `${paperWidthMm}mm`);
container.style.setProperty('--paper-height', `${paperHeightMm}mm`);
});
},
resetPrintScale() {
this.printScale = 1;
if (this.$el) {
this.$el.style.setProperty('--print-scale', 1);
this.$el.style.removeProperty('--paper-width');
this.$el.style.removeProperty('--paper-height');
}
}
}
}
</script>
<style scoped>
.label-container {
width: 25em;
height: 20em;
padding: 0px;
display: flex;
/* 启用Flex布局 */
flex-direction: column;
/* 子元素垂直排列 */
font-family: "SimSun", serif;
box-sizing: border-box;
/* 确保内边距/边框不影响总尺寸 */
}
.material-label-table {
border-collapse: collapse;
width: 100%;
table-layout: fixed;
flex-grow: 1;
/* 让表格拉伸,占满剩余垂直空间 */
height: 0;
/* 配合flex-grow自动计算高度 */
border: 1px solid #333;
/* 确保表格有最外层边框 */
}
/* 四列均分宽度每列占25% */
.material-label-table td {
border: 1px solid #333;
padding: 0;
font-size: 14px;
/* width: 25%; */
box-sizing: border-box;
text-align: center;
word-break: break-all;
overflow-wrap: break-word;
white-space: normal;
overflow: hidden;
}
.label-cell {
background-color: #f5f5f5;
font-weight: bold;
}
.value-cell {
text-align: center;
}
.nob {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border: none;
outline: none;
background: transparent;
text-align: center;
font-size: inherit;
word-break: break-all;
overflow-wrap: break-word;
white-space: normal;
}
/* 打印样式 - 强制单页适配100mm x 80mm纸张保持原有样式不变 */
@media print {
@page {
size: 100mm 80mm;
margin: 0 !important;
padding: 0 !important;
}
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
html,
body {
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
}
/* 隐藏所有其他内容,只显示标签容器 */
body>*:not(.material-label-container) {
display: none !important;
}
.material-label-container {
/* 防止分页 - 多重保护 */
page-break-inside: avoid !important;
break-inside: avoid !important;
page-break-after: avoid !important;
break-after: avoid !important;
page-break-before: avoid !important;
break-before: avoid !important;
orphans: 999 !important;
widows: 999 !important;
/* 应用动态缩放,保持原有样式 */
transform: scale(var(--print-scale, 1)) !important;
transform-origin: top left !important;
/* 确保缩放后不超出纸张 */
max-width: var(--paper-width, 100mm) !important;
max-height: var(--paper-height, 80mm) !important;
overflow: hidden !important;
}
}
</style>

View File

@@ -3,23 +3,29 @@
<!-- 标签预览容器 -->
<div class="preview-container" :id="hideActions ? undefined : 'label-preview-container'" ref="labelRef">
<MaterialTag
v-if="labelType === '2'"
v-if="tagType === '2'"
:content="content"
:paperWidthMm="100"
:paperHeightMm="80"
/>
<OuterTagPreview
v-if="labelType === '3'"
v-if="tagType === '3'"
:content="content"
:paperWidthMm="180"
:paperHeightMm="100"
/>
<GalvanizedTag
v-if="labelType === '4'"
v-if="tagType === '4'"
:content="content"
:paperWidthMm="180"
:paperHeightMm="100"
/>
<WhereTag
v-if="tagType === 'where'"
:content="content"
:paperWidthMm="100"
:paperHeightMm="80"
/>
<!-- <SampleTagPreview v-if="labelType === '4'" :content="content" />
<ForgeTagPreview v-if="labelType === '5'" :content="content" />
<SaltSprayTagPreview v-if="labelType === '6'" :content="content" /> -->
@@ -40,6 +46,7 @@ import { PDFDocument } from 'pdf-lib';
import MaterialTag from './MaterialTag.vue';
import OuterTagPreview from './OuterTagPreview.vue';
import GalvanizedTag from './GalvanizedTag.vue';
import WhereTag from './WhereTag.vue';
// import SampleTagPreview from './SampleTagPreview.vue';
// import ForgeTagPreview from './ForgeTagPreview.vue';
// import SaltSprayTagPreview from './SaltSprayTagPreview.vue';
@@ -50,6 +57,7 @@ export default {
MaterialTag,
OuterTagPreview,
GalvanizedTag,
WhereTag,
// SampleTagPreview,
// ForgeTagPreview,
// SaltSprayTagPreview,
@@ -57,6 +65,32 @@ export default {
data() {
return {
labelType: '2',
tagSizeMap: {
'2': {
width: 100,
height: 80,
},
'3': {
width: 180,
height: 100,
},
'4': {
width: 180,
height: 100,
},
'where': {
width: 100,
height: 80,
},
}
}
},
computed: {
tagType() {
if (this.forceSpecialTag) {
return this.forceSpecialTag;
}
return this.labelType;
}
},
props: {
@@ -64,6 +98,10 @@ export default {
// type: String,
// required: true,
// },
forceSpecialTag: {
type: String,
required: false,
},
content: {
type: Object,
required: true,
@@ -139,8 +177,8 @@ export default {
// 2. 计算纸张尺寸(与批量导出保持一致)
// 根据 labelType 判断:'2' 是材料标签100x80宽100高80'3' 是外标180x100
const isMaterial = this.labelType === '2';
const paperWidthMm = isMaterial ? 100 : 180;
const paperHeightMm = isMaterial ? 80 : 100;
const paperWidthMm = this.tagSizeMap[this.tagType].width || 100;
const paperHeightMm = this.tagSizeMap[this.tagType].height || 80;
// 使用合适的scale值生成高清Canvas但不超过纸张尺寸
const canvasScale = 3; // 提高清晰度(单张打印)