二维码生成
This commit is contained in:
20
klp-ui/.env.stage
Normal file
20
klp-ui/.env.stage
Normal file
@@ -0,0 +1,20 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = RuoYi-Flowable-Plus后台管理系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 若依管理系统/开发环境
|
||||
VUE_APP_BASE_API = 'http://140.143.206.120:8080'
|
||||
|
||||
# 应用访问路径 例如使用前缀 /admin/
|
||||
VUE_APP_CONTEXT_PATH = '/'
|
||||
|
||||
# 监控地址
|
||||
VUE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/login'
|
||||
|
||||
# xxl-job 控制台地址
|
||||
VUE_APP_XXL_JOB_ADMIN = 'http://localhost:9100/xxl-job-admin'
|
||||
|
||||
# 路由懒加载
|
||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
||||
@@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"build:prod": "vue-cli-service build",
|
||||
"stage": "vue-cli-service serve --mode stage",
|
||||
"preview": "node build/index.js --preview",
|
||||
"lint": "eslint --ext .js,.vue src"
|
||||
},
|
||||
@@ -48,8 +49,10 @@
|
||||
"highlight.js": "10.5.0",
|
||||
"js-beautify": "1.13.0",
|
||||
"js-cookie": "3.0.1",
|
||||
"jsbarcode": "^3.12.1",
|
||||
"jsencrypt": "3.0.0-rc.1",
|
||||
"nprogress": "0.2.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"quill": "1.3.7",
|
||||
"screenfull": "5.0.2",
|
||||
"sortablejs": "1.10.2",
|
||||
|
||||
@@ -147,7 +147,7 @@ export default {
|
||||
description: '出库入库管理',
|
||||
icon: 'fas fa-tools',
|
||||
bgColor: 'bg-red-500',
|
||||
link: '/wms/stcokIo'
|
||||
link: '/wms/stockIo'
|
||||
},
|
||||
{
|
||||
title: '系统设置',
|
||||
|
||||
@@ -136,6 +136,12 @@
|
||||
icon="el-icon-document"
|
||||
@click="showDetail(scope.row)"
|
||||
>明细</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-printer"
|
||||
@click="handleShowBarcodeDrawer(scope.row)"
|
||||
>打印条码</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -199,17 +205,35 @@
|
||||
@status-changed="onStatusChanged"
|
||||
/>
|
||||
</el-dialog>
|
||||
<!-- 条码打印抽屉 -->
|
||||
<el-drawer
|
||||
title="条码打印"
|
||||
:visible.sync="drawerBarcodeVisible"
|
||||
size="90%"
|
||||
direction="btt"
|
||||
:with-header="true"
|
||||
>
|
||||
<BarcodeGenerator
|
||||
v-if="drawerBarcodeVisible"
|
||||
:barcodes="drawerBarcodeData.barcodes"
|
||||
:perRow="drawerBarcodeData.perRow"
|
||||
:barcodeWidth="drawerBarcodeData.barcodeWidth"
|
||||
:barcodeHeight="drawerBarcodeData.barcodeHeight"
|
||||
/>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listStockIo, getStockIo, delStockIo, addStockIo, updateStockIo } from "@/api/wms/stockIo";
|
||||
import { listStockIoDetail } from "@/api/wms/stockIoDetail";
|
||||
import StockIoDetailPanel from './panels/detail.vue';
|
||||
import BarcodeGenerator from './panels/barcode.vue';
|
||||
|
||||
export default {
|
||||
name: "StockIo",
|
||||
dicts: ['stock_biz_type', 'stock_io_type', 'stock_status'],
|
||||
components: { StockIoDetailPanel },
|
||||
components: { StockIoDetailPanel, BarcodeGenerator },
|
||||
data() {
|
||||
return {
|
||||
// 按钮loading
|
||||
@@ -272,6 +296,14 @@ export default {
|
||||
},
|
||||
detailDialogVisible: false,
|
||||
detailStockIo: null,
|
||||
// 条码打印抽屉相关
|
||||
drawerBarcodeVisible: false,
|
||||
drawerBarcodeData: {
|
||||
barcodes: [],
|
||||
perRow: 3,
|
||||
barcodeWidth: 180,
|
||||
barcodeHeight: 60
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -406,6 +438,22 @@ export default {
|
||||
// 刷新列表
|
||||
this.getList();
|
||||
},
|
||||
async handleShowBarcodeDrawer(row) {
|
||||
// 获取明细列表
|
||||
const res = await listStockIoDetail({ stockIoId: row.stockIoId });
|
||||
const details = res.data || res.rows || [];
|
||||
// 拼接条码内容 stockIoId_warehouseId_materialId_quantity
|
||||
const barcodes = details.map(item => {
|
||||
return encodeURIComponent(`${row.stockIoId}_${item.warehouseId || ''}_${item.itemId || ''}_${item.quantity || ''}`);
|
||||
});
|
||||
this.drawerBarcodeData = {
|
||||
barcodes,
|
||||
perRow: 3,
|
||||
barcodeWidth: 180,
|
||||
barcodeHeight: 60
|
||||
};
|
||||
this.drawerBarcodeVisible = true;
|
||||
},
|
||||
getIoTypeTagType(type) {
|
||||
if (type === 'in') return 'success';
|
||||
if (type === 'out') return 'primary';
|
||||
|
||||
313
klp-ui/src/views/wms/stockIo/panels/barcode.vue
Normal file
313
klp-ui/src/views/wms/stockIo/panels/barcode.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="barcode-3col-layout">
|
||||
<!-- 预览区 -->
|
||||
<div class="barcode-preview-col">
|
||||
<div class="preview-toolbar">
|
||||
<span>缩放:</span>
|
||||
<el-slider v-model="previewScale" :min="0.3" :max="2" :step="0.01" style="width:180px;display:inline-block;" />
|
||||
<span style="margin-left:8px;">{{ Math.round(previewScale*100) }}%</span>
|
||||
</div>
|
||||
<div class="iframe-wrapper" :style="iframeWrapperStyle">
|
||||
<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>
|
||||
<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="条码明细">
|
||||
<el-table :data="barcodeConfigs" size="mini" border style="width:100%;">
|
||||
<el-table-column label="条码内容" prop="code" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="数量" width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model.number="scope.row.count" :min="1" :max="100" size="mini" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下方文字模板" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.textTpl" size="mini" placeholder="如 箱号" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</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'
|
||||
};
|
||||
},
|
||||
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() {
|
||||
return {
|
||||
transform: `scale(${this.previewScale})`,
|
||||
'transform-origin': 'top left',
|
||||
width: '794px',
|
||||
height: 'auto',
|
||||
overflow: 'hidden',
|
||||
background: '#fff',
|
||||
border: 'none',
|
||||
display: 'block'
|
||||
};
|
||||
},
|
||||
iframeWrapperStyle() {
|
||||
return {
|
||||
width: `${794 * this.previewScale}px`,
|
||||
minHeight: `${1123 * this.previewScale}px`,
|
||||
overflow: 'auto',
|
||||
background: '#f8f8f8',
|
||||
padding: '16px 0',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
}
|
||||
},
|
||||
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() {}
|
||||
},
|
||||
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;
|
||||
},
|
||||
getPrintHtml() {
|
||||
let html = `
|
||||
<html>
|
||||
<head>
|
||||
<title>打印二维码</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background: #fff; }
|
||||
.barcode-list { width: 794px; 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;
|
||||
}
|
||||
.barcode-right-col {
|
||||
width: 420px;
|
||||
min-width: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fafbfc;
|
||||
}
|
||||
.barcode-control-bar {
|
||||
border-bottom: 1px solid #eee;
|
||||
background: #fafbfc;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.barcode-settings-panel {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
.preview-toolbar {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.iframe-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
background: #f8f8f8;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.barcode-iframe {
|
||||
width: 794px;
|
||||
min-height: 1123px;
|
||||
border: none;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user