二维码生成
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": {
|
"scripts": {
|
||||||
"dev": "vue-cli-service serve",
|
"dev": "vue-cli-service serve",
|
||||||
"build:prod": "vue-cli-service build",
|
"build:prod": "vue-cli-service build",
|
||||||
|
"stage": "vue-cli-service serve --mode stage",
|
||||||
"preview": "node build/index.js --preview",
|
"preview": "node build/index.js --preview",
|
||||||
"lint": "eslint --ext .js,.vue src"
|
"lint": "eslint --ext .js,.vue src"
|
||||||
},
|
},
|
||||||
@@ -48,8 +49,10 @@
|
|||||||
"highlight.js": "10.5.0",
|
"highlight.js": "10.5.0",
|
||||||
"js-beautify": "1.13.0",
|
"js-beautify": "1.13.0",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
|
"jsbarcode": "^3.12.1",
|
||||||
"jsencrypt": "3.0.0-rc.1",
|
"jsencrypt": "3.0.0-rc.1",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"quill": "1.3.7",
|
"quill": "1.3.7",
|
||||||
"screenfull": "5.0.2",
|
"screenfull": "5.0.2",
|
||||||
"sortablejs": "1.10.2",
|
"sortablejs": "1.10.2",
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default {
|
|||||||
description: '出库入库管理',
|
description: '出库入库管理',
|
||||||
icon: 'fas fa-tools',
|
icon: 'fas fa-tools',
|
||||||
bgColor: 'bg-red-500',
|
bgColor: 'bg-red-500',
|
||||||
link: '/wms/stcokIo'
|
link: '/wms/stockIo'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '系统设置',
|
title: '系统设置',
|
||||||
|
|||||||
@@ -136,6 +136,12 @@
|
|||||||
icon="el-icon-document"
|
icon="el-icon-document"
|
||||||
@click="showDetail(scope.row)"
|
@click="showDetail(scope.row)"
|
||||||
>明细</el-button>
|
>明细</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-printer"
|
||||||
|
@click="handleShowBarcodeDrawer(scope.row)"
|
||||||
|
>打印条码</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -199,17 +205,35 @@
|
|||||||
@status-changed="onStatusChanged"
|
@status-changed="onStatusChanged"
|
||||||
/>
|
/>
|
||||||
</el-dialog>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listStockIo, getStockIo, delStockIo, addStockIo, updateStockIo } from "@/api/wms/stockIo";
|
import { listStockIo, getStockIo, delStockIo, addStockIo, updateStockIo } from "@/api/wms/stockIo";
|
||||||
|
import { listStockIoDetail } from "@/api/wms/stockIoDetail";
|
||||||
import StockIoDetailPanel from './panels/detail.vue';
|
import StockIoDetailPanel from './panels/detail.vue';
|
||||||
|
import BarcodeGenerator from './panels/barcode.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "StockIo",
|
name: "StockIo",
|
||||||
dicts: ['stock_biz_type', 'stock_io_type', 'stock_status'],
|
dicts: ['stock_biz_type', 'stock_io_type', 'stock_status'],
|
||||||
components: { StockIoDetailPanel },
|
components: { StockIoDetailPanel, BarcodeGenerator },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 按钮loading
|
// 按钮loading
|
||||||
@@ -272,6 +296,14 @@ export default {
|
|||||||
},
|
},
|
||||||
detailDialogVisible: false,
|
detailDialogVisible: false,
|
||||||
detailStockIo: null,
|
detailStockIo: null,
|
||||||
|
// 条码打印抽屉相关
|
||||||
|
drawerBarcodeVisible: false,
|
||||||
|
drawerBarcodeData: {
|
||||||
|
barcodes: [],
|
||||||
|
perRow: 3,
|
||||||
|
barcodeWidth: 180,
|
||||||
|
barcodeHeight: 60
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -406,6 +438,22 @@ export default {
|
|||||||
// 刷新列表
|
// 刷新列表
|
||||||
this.getList();
|
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) {
|
getIoTypeTagType(type) {
|
||||||
if (type === 'in') return 'success';
|
if (type === 'in') return 'success';
|
||||||
if (type === 'out') return 'primary';
|
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