Files
klp-oa/klp-ui/src/views/wms/stockIo/panels/barcode.vue
2025-07-24 10:49:23 +08:00

355 lines
11 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="barcode-3col-layout">
<!-- 预览区 -->
<div class="barcode-preview-col">
<div class="iframe-wrapper">
<iframe ref="previewIframe" class="barcode-iframe" frameborder="0" :style="iframeStyle"></iframe>
</div>
</div>
<!-- 右侧控制+设置区 -->
<div class="barcode-right-col">
<!-- 控制区 -->
<div class="barcode-control-bar">
<el-tabs v-model="activeTab" stretch>
<el-tab-pane label="排版设置" name="layout" />
<el-tab-pane label="条码明细" name="detail" />
</el-tabs>
</div>
<!-- 设置区 -->
<div class="barcode-settings-panel">
<el-form v-if="activeTab==='layout'" label-width="80px" size="small" label-position="top">
<el-form-item label="每行数量">
<el-input-number v-model="perRow" :min="1" :max="10" />
</el-form-item>
<el-form-item label="条码宽度">
<el-input-number v-model="barcodeWidth" :min="60" :max="600" />
</el-form-item>
<el-form-item label="条码高度">
<el-input-number v-model="barcodeHeight" :min="20" :max="200" />
</el-form-item>
<el-form-item label="纸张尺寸">
<el-select v-model="paperSize" placeholder="请选择纸张尺寸" style="width: 160px">
<el-option label="A4 (210mm x 297mm)" value="A4" />
<el-option label="A5 (148mm x 210mm)" value="A5" />
<el-option label="A6 (105mm x 148mm)" value="A6" />
<el-option label="自定义" value="custom" />
</el-select>
</el-form-item>
<el-form-item v-if="paperSize==='custom'" label="自定义宽度(mm)">
<el-input-number v-model="customPaperWidth" :min="50" :max="500" />
</el-form-item>
<el-form-item v-if="paperSize==='custom'" label="自定义高度(mm)">
<el-input-number v-model="customPaperHeight" :min="50" :max="500" />
</el-form-item>
<el-form-item label="方向">
<el-radio-group v-model="paperOrientation">
<el-radio label="portrait">纵向</el-radio>
<el-radio label="landscape">横向</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handlePrint">打印</el-button>
</el-form-item>
</el-form>
<el-form v-else label-width="80px" size="small" label-position="top">
<el-form-item label="条码明细">
<div v-for="(cfg, idx) in barcodeConfigs" :key="idx" style="margin-bottom: 16px; border: 1px solid #eee; border-radius: 4px; padding: 12px 16px; background: #fafbfc;">
<div style="margin-bottom: 8px; font-weight: bold; color: #666;">条码 {{ idx + 1 }}</div>
<el-form-item label="条码内容" label-width="70px" style="margin-bottom: 8px;">
<el-input type="textarea" v-model="cfg.code" size="mini" :autosize="{ minRows: 1, maxRows: 3 }" placeholder="请输入条码内容" />
</el-form-item>
<el-form-item label="数量" label-width="70px" style="margin-bottom: 8px;">
<el-input-number v-model.number="cfg.count" :min="1" :max="100" size="mini" />
</el-form-item>
<el-form-item label="下方文字" label-width="70px" style="margin-bottom: 0;">
<el-input type="textarea" v-model="cfg.textTpl" size="mini" placeholder="如 箱号" />
</el-form-item>
</div>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
// import JsBarcode from 'jsbarcode';
import QRCode from 'qrcode';
export default {
name: 'BarcodeGenerator',
props: {
barcodes: {
type: Array,
required: true
}
},
data() {
return {
perRow: 3,
barcodeWidth: 180,
barcodeHeight: 180, // 二维码建议宽高一致
barcodeConfigs: [], // [{code, count, textTpl}]
previewScale: 1, // 预览缩放比例
activeTab: 'layout',
paperSize: 'A4',
paperOrientation: 'portrait',
customPaperWidth: 210,
customPaperHeight: 297
};
},
computed: {
expandedBarcodes() {
let arr = [];
this.barcodeConfigs.forEach(cfg => {
for (let i = 0; i < (cfg.count || 1); i++) {
arr.push(cfg.code);
}
});
return arr;
},
expandedBarcodeTexts() {
let arr = [];
this.barcodeConfigs.forEach(cfg => {
for (let i = 0; i < (cfg.count || 1); i++) {
// 模板替换 {{n}}
let text = cfg.textTpl || cfg.code;
text = text.replace(/\{\{n\}\}/g, i + 1);
arr.push(text);
}
});
return arr;
},
expandedBarcodeTableData() {
return this.expandedBarcodes.map((code, idx) => ({ code, text: this.expandedBarcodeTexts[idx] }));
},
barcodeRows() {
const rows = [];
const arr = this.expandedBarcodes;
for (let i = 0; i < arr.length; i += this.perRow) {
rows.push(arr.slice(i, i + this.perRow));
}
return rows;
},
barcodeTextRows() {
const rows = [];
const arr = this.expandedBarcodeTexts;
for (let i = 0; i < arr.length; i += this.perRow) {
rows.push(arr.slice(i, i + this.perRow));
}
return rows;
},
iframeStyle() {
const { width, height } = this.getPaperPx();
return {
width: width + 'px',
minHeight: height + 'px',
height: height + 'px',
background: '#fff',
border: 'none',
display: 'block',
margin: 0,
padding: 0
};
}
},
watch: {
barcodes: {
handler(newVal) {
// 初始化barcodeConfigs
this.barcodeConfigs = newVal.map((b, i) => {
const old = this.barcodeConfigs[i] || {};
return {
code: b,
count: old.count || 1,
textTpl: typeof old.textTpl === 'string' ? old.textTpl : b
};
});
this.$nextTick(this.renderPreviewIframe);
},
immediate: true
},
barcodeConfigs: {
handler() { this.$nextTick(this.renderPreviewIframe); },
deep: true
},
perRow() { this.$nextTick(this.renderPreviewIframe); },
barcodeWidth() { this.$nextTick(this.renderPreviewIframe); },
barcodeHeight() { this.$nextTick(this.renderPreviewIframe); },
previewScale() { this.$nextTick(this.renderPreviewIframe); },
paperSize() { this.$nextTick(this.renderPreviewIframe); },
paperOrientation() { this.$nextTick(this.renderPreviewIframe); },
customPaperWidth() { this.$nextTick(this.renderPreviewIframe); },
customPaperHeight() { this.$nextTick(this.renderPreviewIframe); }
},
methods: {
getBarcodeId(row, col) {
return `barcode-canvas-${row}-${col}`;
},
getBarcodeText(row, col, code) {
if (
this.barcodeTextRows[row] &&
typeof this.barcodeTextRows[row][col] !== 'undefined' &&
this.barcodeTextRows[row][col] !== null &&
this.barcodeTextRows[row][col] !== ''
) {
return this.barcodeTextRows[row][col];
}
return code;
},
getPaperPx() {
// mm to px, 1mm ≈ 3.78px
let width, height;
if (this.paperSize === 'A4') {
width = 210 * 3.78;
height = 297 * 3.78;
} else if (this.paperSize === 'A5') {
width = 148 * 3.78;
height = 210 * 3.78;
} else if (this.paperSize === 'A6') {
width = 105 * 3.78;
height = 148 * 3.78;
} else {
width = this.customPaperWidth * 3.78;
height = this.customPaperHeight * 3.78;
}
if (this.paperOrientation === 'landscape') {
return { width: height, height: width };
}
return { width, height };
},
getPrintHtml() {
const { width, height } = this.getPaperPx();
let html = `
<html>
<head>
<title>打印二维码</title>
<style>
body { margin: 0; padding: 0; background: #fff; }
.barcode-list { width: ${width}px; min-height: ${height}px; margin: 0 auto; background: #fff; }
.barcode-row { display: flex; margin-bottom: 24px; }
.barcode-item { flex: 1; text-align: center; }
.barcode-text { margin-top: 8px; font-size: 14px; }
html, body { overflow-x: hidden; }
</style>
</head>
<body>
<div class="barcode-list">
`;
this.barcodeRows.forEach((row, rowIndex) => {
html += '<div class="barcode-row">';
row.forEach((code, colIndex) => {
const id = this.getBarcodeId(rowIndex, colIndex);
const text = this.getBarcodeText(rowIndex, colIndex, code);
html += `<div class="barcode-item">
<canvas id="${id}" width="${this.barcodeWidth}" height="${this.barcodeHeight}"></canvas>
<div class="barcode-text">${text}</div>
</div>`;
});
html += '</div>';
});
html += '</div></body></html>';
return html;
},
async renderPreviewIframe() {
const iframe = this.$refs.previewIframe;
if (!iframe) return;
const doc = iframe.contentDocument || iframe.contentWindow.document;
doc.open();
doc.write(this.getPrintHtml());
doc.close();
// 渲染二维码
setTimeout(async () => {
for (let rowIndex = 0; rowIndex < this.barcodeRows.length; rowIndex++) {
const row = this.barcodeRows[rowIndex];
for (let colIndex = 0; colIndex < row.length; colIndex++) {
const code = row[colIndex];
const id = this.getBarcodeId(rowIndex, colIndex);
const el = doc.getElementById(id);
if (el) {
await QRCode.toCanvas(el, code, {
width: this.barcodeWidth,
height: this.barcodeHeight,
margin: 0
});
}
}
}
}, 50);
},
handlePrint() {
const iframe = this.$refs.previewIframe;
if (!iframe) return;
iframe.contentWindow.focus();
iframe.contentWindow.print();
}
},
mounted() {
this.barcodeConfigs = this.barcodes.map(b => ({ code: b, count: 1, textTpl: b }));
this.renderPreviewIframe();
}
};
</script>
<style scoped>
.barcode-3col-layout {
display: flex;
flex-direction: row;
width: 100%;
min-height: 400px;
}
.barcode-preview-col {
flex: 1 1 0;
min-width: 0;
background: #fff;
border-right: 1px solid #eee;
padding: 24px 0 24px 24px;
display: flex;
flex-direction: column;
height: 90vh;
overflow: auto;
}
.barcode-right-col {
width: 420px;
min-width: 320px;
padding: 16px;
display: flex;
flex-direction: column;
background: #fafbfc;
height: 90vh;
}
.barcode-control-bar {
border-bottom: 1px solid #eee;
background: #fafbfc;
padding: 0 16px;
}
.barcode-settings-panel {
flex: 1;
overflow: auto;
min-height: 0;
max-height: calc(90vh - 48px); /* 48px 约为tabs高度可根据实际调整 */
}
.preview-toolbar {
margin-bottom: 12px;
display: flex;
align-items: center;
}
.iframe-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
background: #f8f8f8;
overflow: auto;
}
.barcode-iframe {
/* width、height 由:style绑定动态控制 */
min-width: 0;
min-height: 0;
border: none;
background: #fff;
border-radius: 4px;
display: block;
}
</style>