对比新文件 |
| | |
| | | <template> |
| | | <div class="notice"> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="title == 'pro' ? '成品二维码打印' : '危化品二维码打印'" |
| | | width="800px" |
| | | :before-close="handleClose" |
| | | :close-on-press-escape="false" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div style="display: flex;justify-content: space-between"> |
| | | <el-form :inline="true" style="display: flex;align-items: center;flex-wrap: wrap;" > |
| | | <el-form-item label="条码编号:" > |
| | | <el-input v-model="state.queryParams.code" placeholder="请输入条码编号" ></el-input> |
| | | </el-form-item> |
| | | <el-form-item > |
| | | <el-button |
| | | type="primary" |
| | | @click="getList" |
| | | >查询</el-button> |
| | | <el-button |
| | | type="primary" |
| | | plain |
| | | @click="reset" |
| | | >重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div style="display: flex;flex-wrap: wrap"> |
| | | <div v-for="item in state.dataList"> |
| | | <div :id="item.code" style="width: 50mm;height: 40mm;"> |
| | | <div style="display: flex;flex-direction: column;align-items: center;margin-bottom: 5px"> |
| | | <div style="font-size:14px;margin-bottom: 2px" id="codeTitle">{{item.name}}—{{item.productSn}}</div> |
| | | <vue-qr :size="100" :correctLevel="3" colorDark="black" :margin="0" :auto-color="true" :text="item.code"></vue-qr> |
| | | <div style="font-size:10px;margin-top: 2px">{{item.code}}</div> |
| | | <div class="page-break"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 打印模板--> |
| | | <!-- <div style="display: none">--> |
| | | <!-- <div ref="printTemplate" class="print-template">--> |
| | | <!-- <div v-for="(item, index) in state.dataList" :key="'print-'+index" class="print-label">--> |
| | | <!-- <div class="label-content">--> |
| | | <!-- <div class="label-title">{{ item.name }}—{{ item.productSn }}</div>--> |
| | | <!-- <!– 确保二维码使用原始数据而不是DOM引用 –>--> |
| | | <!-- <vue-qr--> |
| | | <!-- :size="100"--> |
| | | <!-- :correctLevel="3"--> |
| | | <!-- colorDark="black"--> |
| | | <!-- :margin="2"--> |
| | | <!-- :auto-color="true"--> |
| | | <!-- :text="item.code">--> |
| | | <!-- </vue-qr>--> |
| | | <!-- <div class="label-code">{{ item.code }}</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | |
| | | |
| | | <!-- <el-table v-loading="state.loading" :data="state.dataList" :border="true" :show-header="false" height="550" @selection-change="handleSelectionChange">--> |
| | | <!-- <el-table-column type="selection" width="55" align="center" />--> |
| | | <!-- <el-table-column >--> |
| | | <!-- <template #default="scope">--> |
| | | <!-- <div :id="scope.row.code">--> |
| | | <!-- <div style="width: 50mm;height: 40mm; ">--> |
| | | <!-- <div style="font-size:14px;margin-bottom: 2px" id="codeTitle">{{scope.row.name}}—{{scope.row.productSn}}</div>--> |
| | | <!-- <vue-qr :size="100" :correctLevel="3" colorDark="black" :margin="0" :auto-color="true" :text="scope.row.code"></vue-qr>--> |
| | | <!-- <div style="font-size:10px;margin-top: 2px">{{scope.row.code}}</div>--> |
| | | <!-- <div class="page-break"></div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </template>--> |
| | | <!-- </el-table-column>--> |
| | | <!-- </el-table>--> |
| | | <pagination |
| | | v-model:pager-count="pageCount" |
| | | v-show="state.total > 0" |
| | | :total="state.total" |
| | | :page-sizes="[28,56,84,100]" |
| | | v-model:page="state.queryParams.pageNum" |
| | | v-model:limit="state.queryParams.pageSize" |
| | | @pagination="getList" |
| | | /> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="handleClose" size="default">取 消</el-button> |
| | | <el-button type="primary" @click="printLabels" size="default" v-preReClick>打印</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import {reactive, ref, toRefs} from 'vue' |
| | | import VueQr from 'vue-qr/src/packages/vue-qr.vue' |
| | | import {ElMessage} from "element-plus"; |
| | | import printJS from 'print-js'; |
| | | import QRCode from 'qrcode'; |
| | | import {addWarehouse, checkName, editWarehouse} from "@/api/hazardousChemicals/warehouse"; |
| | | import {verifyPhone} from "@/utils/validate"; |
| | | import {checkBasicName} from "@/api/hazardousChemicals/basicInfo"; |
| | | import {getProDetail, getProductRecord, getWhProDetail} from "@/api/hazardousChemicals/productRecord"; |
| | | import {getRawDetail, getWhRawDetail} from "@/api/hazardousChemicals/rawRecord"; |
| | | |
| | | const dialogVisible = ref(false); |
| | | const title = ref(""); |
| | | const busRef = ref(); |
| | | const length = ref() |
| | | const emit = defineEmits(["getList"]); |
| | | |
| | | const state = reactive({ |
| | | loading: false, |
| | | dataList: [], |
| | | total: 0, |
| | | queryParams:{ |
| | | pageNum: 1, |
| | | pageSize: 28, |
| | | // warehouseId: null, |
| | | // basicId: null, |
| | | entryId: null, |
| | | code: '' |
| | | }, |
| | | chooseList: [] |
| | | |
| | | }) |
| | | const pageCount = ref(3) |
| | | const originalList = ref([]) |
| | | const openDialog = async (type,value) => { |
| | | // state.queryParams.warehouseId =value.warehouseId |
| | | // state.queryParams.basicId =value.basicId |
| | | state.queryParams.entryId = value.id |
| | | title.value = type; |
| | | await reset() |
| | | |
| | | console.log('state.dataList',state.dataList) |
| | | dialogVisible.value = true; |
| | | } |
| | | const getRowKey = (row) => { |
| | | return row.id |
| | | } |
| | | |
| | | const onSubmit = async () => { |
| | | |
| | | } |
| | | const handleSelectionChange = (val) => { |
| | | // state.form.studentIds = val.map(item => item.id) |
| | | state.chooseList = val |
| | | console.log("选中的行", val) |
| | | } |
| | | |
| | | const handleClose = () => { |
| | | dialogVisible.value = false; |
| | | emit("getList") |
| | | } |
| | | const reset = async () => { |
| | | state.dataList = []; |
| | | state.queryParams = { |
| | | pageNum: 1, |
| | | pageSize: 28, |
| | | entryId: state.queryParams.entryId, |
| | | code: '' |
| | | } |
| | | state.total = 0 |
| | | state.chooseList = [] |
| | | await getList() |
| | | } |
| | | const getList = async () => { |
| | | if(title.value == 'pro'){ |
| | | const res = await getWhProDetail(state.queryParams) |
| | | if(res.code == 200){ |
| | | state.dataList = res.data.list.map(item => { |
| | | return{ |
| | | ...item, |
| | | name: item.productBasic.name, |
| | | productSn: item.productBasic.productSn |
| | | } |
| | | }) |
| | | state.total = res.data.total |
| | | originalList.value = state.dataList |
| | | }else{ |
| | | ElMessage.warning(res.message) |
| | | } |
| | | }else { |
| | | const res = await getWhRawDetail(state.queryParams) |
| | | if(res.code == 200){ |
| | | state.dataList = res.data.list.map(item => { |
| | | return{ |
| | | ...item, |
| | | name: item.hazmatBasic.name, |
| | | productSn: item.hazmatBasic.productSn |
| | | } |
| | | }) |
| | | state.total = res.data.total |
| | | originalList.value = state.dataList |
| | | }else{ |
| | | ElMessage.warning(res.message) |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | } |
| | | const printLabels = async () => { |
| | | const existingContainer = document.getElementById('print-container'); |
| | | if (existingContainer) { |
| | | existingContainer.remove(); |
| | | } |
| | | // 创建打印容器 |
| | | const printContainer = document.createElement('div'); |
| | | printContainer.id = 'print-container'; |
| | | // 添加内联样式 |
| | | const style = document.createElement('style'); |
| | | style.textContent = ` |
| | | .print-container { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | z-index: 9999; |
| | | background: white; |
| | | } |
| | | .print-page { |
| | | width: 210mm; |
| | | height: 297mm; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | justify-content: flex-start; |
| | | align-content: flex-start; |
| | | padding: 3mm 5mm; /* 考虑打印机物理边距 */ |
| | | box-sizing: border-box; |
| | | page-break-after: always; |
| | | } |
| | | /* 标签容器 - 保证每个标签独立且尺寸固定 */ |
| | | .print-label { |
| | | width: 50mm; /* 4列布局 (210mm / 4) */ |
| | | max-height: 40mm; |
| | | height: 40mm; /* 7行布局 (297mm / 7) */ |
| | | box-sizing: border-box; |
| | | padding: 1mm; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; /* 上方空间给标题,中间二维码,底部给编码 */ |
| | | align-items: center; |
| | | page-break-inside: avoid; /* 防止分页中断标签 */ |
| | | break-inside: avoid; |
| | | margin-bottom:7px; |
| | | } |
| | | .label-title { |
| | | font-size: 3.5mm; |
| | | font-weight: bold; |
| | | color: black; |
| | | text-align:center; |
| | | /* 强制单行 + 截断 */ |
| | | // white-space: nowrap; |
| | | // overflow: hidden; |
| | | // text-overflow: ellipsis; |
| | | /* 行高控制 */ |
| | | line-height: 1; |
| | | min-height: 2.2mm; |
| | | /* 宽度控制 */ |
| | | width: 100%; |
| | | max-width: 100%; |
| | | /* 防止 flex/grid 父级影响 */ |
| | | flex: none; |
| | | } |
| | | /* 二维码容器 - 增大二维码尺寸 */ |
| | | .qr-container { |
| | | flex-grow: 1; /* 占据可用空间 */ |
| | | width: 100%; |
| | | max-height: 35mm; /* 增大二维码区高度 */ |
| | | min-height: 35mm; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | overflow: hidden; |
| | | margin-top: -5px |
| | | } |
| | | .qrcode-img { |
| | | max-width: 25mm !important; /* 增大二维码尺寸 */ |
| | | max-height: 25mm !important; |
| | | object-fit: contain; |
| | | margin-top: 0px |
| | | } |
| | | |
| | | .label-code { |
| | | margin-top: -15px; |
| | | |
| | | font-size: 3mm; /* ≈8.5px */ |
| | | text-align: center; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | |
| | | |
| | | /* 超长编码处理 */ |
| | | -webkit-line-clamp: 0 !important; /* 解除2行限制 */ |
| | | max-height: none !important; |
| | | white-space: normal !important; |
| | | padding: 0.5mm 0; /* 增加纵向空间 */ |
| | | line-height: 0.5; /* 增大行高 */ |
| | | min-height: 1.5mm; /* 确保最小显示区域 */ |
| | | } |
| | | @media print { |
| | | body { margin: 0 !important; } |
| | | @page { margin: 0; size: auto; } |
| | | } |
| | | `; |
| | | |
| | | printContainer.appendChild(style); |
| | | |
| | | // 添加打印容器到DOM |
| | | document.body.appendChild(printContainer); |
| | | |
| | | // 分页处理并生成二维码 |
| | | const pageCount = Math.ceil(state.dataList.length / 28); |
| | | const qrPromises = []; // 存储所有二维码生成Promise |
| | | |
| | | for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) { |
| | | const pageDiv = document.createElement('div'); |
| | | pageDiv.className = 'print-page'; |
| | | |
| | | const startIndex = pageIndex * 28; |
| | | const endIndex = Math.min(startIndex + 28, state.dataList.length); |
| | | |
| | | for (let i = startIndex; i < endIndex; i++) { |
| | | const item = state.dataList[i]; |
| | | const labelDiv = document.createElement('div'); |
| | | labelDiv.className = 'print-label'; |
| | | |
| | | // 二维码容器 |
| | | const qrContainer = document.createElement('div'); |
| | | qrContainer.className = 'qr-container'; |
| | | |
| | | // 创建二维码Canvas |
| | | const canvasId = `canvas-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; |
| | | const qrCanvas = document.createElement('canvas'); |
| | | qrCanvas.id = canvasId; |
| | | qrCanvas.className = 'qrcode-img'; |
| | | qrContainer.appendChild(qrCanvas); |
| | | |
| | | // 文本内容 |
| | | const titleDiv = document.createElement('div'); |
| | | titleDiv.className = 'label-title'; |
| | | titleDiv.textContent = `${item.name}—${item.productSn}`; |
| | | |
| | | const codeDiv = document.createElement('div'); |
| | | codeDiv.className = 'label-code'; |
| | | codeDiv.textContent = item.code; |
| | | |
| | | // 组装标签 |
| | | labelDiv.appendChild(titleDiv); |
| | | labelDiv.appendChild(qrContainer); |
| | | labelDiv.appendChild(codeDiv); |
| | | pageDiv.appendChild(labelDiv); |
| | | |
| | | // 添加到打印容器 |
| | | labelDiv.style.opacity = '0'; // 初始隐藏防止闪烁 |
| | | |
| | | // 创建二维码生成Promise |
| | | qrPromises.push(new Promise(resolve => { |
| | | // 使用requestAnimationFrame处理渲染队列 |
| | | requestAnimationFrame(() => { |
| | | generateDynamicQRCode(item.code, canvasId).then(resolve); |
| | | }); |
| | | })); |
| | | } |
| | | printContainer.appendChild(pageDiv); |
| | | } |
| | | |
| | | try { |
| | | // 关键修改1:隔离打印环境 |
| | | const printSandbox = document.createElement('div'); |
| | | printSandbox.style.position = 'fixed'; |
| | | printSandbox.style.left = '-9999px'; |
| | | document.body.appendChild(printSandbox); |
| | | printSandbox.appendChild(printContainer); // 移入沙箱 |
| | | // 显示加载提示 |
| | | const loadingIndicator = document.createElement('div'); |
| | | loadingIndicator.style = ` |
| | | position: fixed; |
| | | top: 50%; |
| | | left: 50%; |
| | | transform: translate(-50%, -50%); |
| | | background: rgba(0,0,0,0.7); |
| | | color: white; |
| | | padding: 20px; |
| | | border-radius: 5px; |
| | | z-index: 10000; |
| | | `; |
| | | loadingIndicator.textContent = '正在生成二维码,请稍候...'; |
| | | document.body.appendChild(loadingIndicator); |
| | | |
| | | // 等待所有二维码生成完成 |
| | | await Promise.all(qrPromises); |
| | | |
| | | // 二维码生成完成后显示所有标签 |
| | | printContainer.querySelectorAll('.print-label').forEach(el => { |
| | | el.style.opacity = '1'; |
| | | }); |
| | | |
| | | // 添加0.5s延迟确保DOM更新 |
| | | await new Promise(resolve => setTimeout(resolve, 1000)); |
| | | |
| | | // 移除加载提示 |
| | | loadingIndicator.remove(); |
| | | |
| | | // 执行打印 |
| | | printJS({ |
| | | printable: 'print-container', |
| | | type: 'html', |
| | | scanStyles: true, |
| | | style: '@page { margin: 0; } body { margin: 0; }', |
| | | onPrintDialogClose: () => { |
| | | printSandbox.remove(); |
| | | } |
| | | }); |
| | | } catch (error) { |
| | | [loadingIndicator, printSandbox].forEach(el => { |
| | | el?.parentNode?.removeChild(el); |
| | | }); |
| | | } |
| | | }; |
| | | // 在生成二维码时根据文本长度自适应 |
| | | function generateDynamicQRCode(text, canvasId) { |
| | | return new Promise((resolve) => { |
| | | const canvas = document.getElementById(canvasId); |
| | | const container = canvas.closest('.qr-container'); |
| | | |
| | | // 根据文本长度确定最佳尺寸 |
| | | const sizeBase = text.length > 40 ? 160 : 200; /* 短文本更大尺寸 */ |
| | | const maxSize = Math.min( |
| | | container.clientWidth * 0.9, |
| | | container.clientHeight * 0.9 |
| | | ); |
| | | |
| | | // 最终尺寸 |
| | | const finalSize = Math.min(sizeBase, maxSize); |
| | | |
| | | QRCode.toCanvas(canvas, text, { |
| | | width: finalSize, |
| | | margin: 0, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#ffffff' |
| | | } |
| | | }, () => { |
| | | // 生成后重新居中 |
| | | canvas.style.margin = `${(container.clientHeight - canvas.height) / 2}px auto`; |
| | | resolve(); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog |
| | | }); |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .notice{ |
| | | :deep(.el-form .el-form-item__label) { |
| | | font-size: 15px; |
| | | } |
| | | :deep(.el-dialog__body) { |
| | | padding: 10px 20px 0 20px; |
| | | } |
| | | |
| | | .file { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | |
| | | } |
| | | </style> |