条码打印
This commit is contained in:
@@ -1882,6 +1882,11 @@ $shadow-md: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
|||||||
&--clickable {
|
&--clickable {
|
||||||
min-height: 48rpx;
|
min-height: 48rpx;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.card__value {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
"autoscan": true,
|
"autoscan": true,
|
||||||
"custom": {
|
"custom": {
|
||||||
"^nbd-print-prop$": "@/scanpages/components/nbd-print-prop.vue",
|
"^nbd-print-prop$": "@/scanpages/components/nbd-print-prop.vue",
|
||||||
"^nbd-print-prop-qr$": "@/scanpages/components/nbd-print-prop-qr.vue",
|
|
||||||
"^u-(.*)": "uview-pro/components/u-$1/u-$1.vue",
|
"^u-(.*)": "uview-pro/components/u-$1/u-$1.vue",
|
||||||
"^nbd-(.*)": "@/components/nbd-$1.vue"
|
"^nbd-(.*)": "@/components/nbd-$1.vue"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,748 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view>
|
|
||||||
<u-popup v-model="localVisible" mode="bottom" :height="height" :closeable="true" @close="handleClose">
|
|
||||||
<view class="print-popup">
|
|
||||||
<!-- 标题栏 -->
|
|
||||||
<view class="print-popup__header">
|
|
||||||
<text class="print-popup__title">标签打印</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 隐藏画布:标签绘制 -->
|
|
||||||
<canvas
|
|
||||||
:id="canvasId"
|
|
||||||
:canvas-id="canvasId"
|
|
||||||
type="2d"
|
|
||||||
:style="canvasStyles"
|
|
||||||
/>
|
|
||||||
<!-- 隐藏画布:二维码生成(独立,避免占用标签画布导致只导出左上角) -->
|
|
||||||
<canvas
|
|
||||||
:id="barcodeCanvasId"
|
|
||||||
:canvas-id="barcodeCanvasId"
|
|
||||||
type="2d"
|
|
||||||
:style="barcodeCanvasStyles"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<scroll-view scroll-y="true" class="print-popup__body">
|
|
||||||
<!-- 预览区域 -->
|
|
||||||
<view class="preview-section" v-if="previewList.length > 0">
|
|
||||||
<text class="preview-section__title">打印预览</text>
|
|
||||||
<view class="preview-section__list">
|
|
||||||
<image
|
|
||||||
v-for="item in previewList"
|
|
||||||
:src="item.value"
|
|
||||||
:key="item.key"
|
|
||||||
class="preview-img"
|
|
||||||
mode="widthFix"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 打印机选择 -->
|
|
||||||
<view class="setting-row">
|
|
||||||
<text class="setting-row__label">打印机</text>
|
|
||||||
<view class="setting-row__right">
|
|
||||||
<picker :value="deviceIndex" :range="deviceList" @change="onDeviceChanged" range-key="name">
|
|
||||||
<view class="setting-row__picker">
|
|
||||||
<text
|
|
||||||
class="setting-row__value"
|
|
||||||
:class="{ 'setting-row__value--empty': !deviceList[deviceIndex]?.deviceId }"
|
|
||||||
>
|
|
||||||
{{ deviceList[deviceIndex]?.name || '未检测到打印机' }}
|
|
||||||
</text>
|
|
||||||
<u-icon name="arrow-down" :size="24" color="#999" />
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 出纸方向 -->
|
|
||||||
<view class="setting-row">
|
|
||||||
<text class="setting-row__label">出纸方向</text>
|
|
||||||
<view class="setting-row__right">
|
|
||||||
<picker :value="rotationIndex" :range="rotationList" @change="onRotationChanged" range-key="name">
|
|
||||||
<view class="setting-row__picker">
|
|
||||||
<text class="setting-row__value">{{ rotationList[rotationIndex]?.name || '' }}</text>
|
|
||||||
<u-icon name="arrow-down" :size="24" color="#999" />
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 纸张类型 -->
|
|
||||||
<view class="setting-row">
|
|
||||||
<text class="setting-row__label">纸张类型</text>
|
|
||||||
<view class="setting-row__right">
|
|
||||||
<picker :value="gapIndex" :range="gapList" @change="onGapTypeChanged" range-key="name">
|
|
||||||
<view class="setting-row__picker">
|
|
||||||
<text class="setting-row__value">{{ gapList[gapIndex]?.name || '' }}</text>
|
|
||||||
<u-icon name="arrow-down" :size="24" color="#999" />
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 打印浓度 -->
|
|
||||||
<view class="setting-row">
|
|
||||||
<text class="setting-row__label">打印浓度</text>
|
|
||||||
<view class="setting-row__right">
|
|
||||||
<picker :value="darknessIndex" :range="darknessList" @change="onDarknessChanged" range-key="name">
|
|
||||||
<view class="setting-row__picker">
|
|
||||||
<text class="setting-row__value">{{ darknessList[darknessIndex]?.name || '' }}</text>
|
|
||||||
<u-icon name="arrow-down" :size="24" color="#999" />
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 打印速度 -->
|
|
||||||
<view class="setting-row">
|
|
||||||
<text class="setting-row__label">打印速度</text>
|
|
||||||
<view class="setting-row__right">
|
|
||||||
<picker :value="speedIndex" :range="speedList" @change="onPrintSpeedChanged" range-key="name">
|
|
||||||
<view class="setting-row__picker">
|
|
||||||
<text class="setting-row__value">{{ speedList[speedIndex]?.name || '' }}</text>
|
|
||||||
<u-icon name="arrow-down" :size="24" color="#999" />
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 灰度阈值 -->
|
|
||||||
<view class="setting-row">
|
|
||||||
<text class="setting-row__label">灰度阈值</text>
|
|
||||||
<view class="setting-row__right">
|
|
||||||
<input class="setting-row__input" type="number" v-model="threshold" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- JSON配置模式 -->
|
|
||||||
<view class="setting-row">
|
|
||||||
<text class="setting-row__label">JSON配置</text>
|
|
||||||
<view class="setting-row__right">
|
|
||||||
<switch :checked="jsonMode" @change="onPrintModeChanged" style="transform: scale(0.75)" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<!-- 底部操作按钮 -->
|
|
||||||
<view class="print-popup__footer">
|
|
||||||
<view class="footer-grid">
|
|
||||||
<view class="footer-btn" @click="handleStartDiscovery">
|
|
||||||
<u-icon name="search" :size="30" color="#2979ff" />
|
|
||||||
<text>搜索打印机</text>
|
|
||||||
</view>
|
|
||||||
<view class="footer-btn" @click="handleStopDiscovery">
|
|
||||||
<u-icon name="close-circle" :size="30" color="#f56c6c" />
|
|
||||||
<text>停止搜索</text>
|
|
||||||
</view>
|
|
||||||
<view class="footer-btn" @click="handleOpenPrinter">
|
|
||||||
<u-icon name="link" :size="30" color="#2979ff" />
|
|
||||||
<text>连接打印机</text>
|
|
||||||
</view>
|
|
||||||
<view class="footer-btn" @click="handleClosePrinter">
|
|
||||||
<u-icon name="minus-circle" :size="30" color="#f56c6c" />
|
|
||||||
<text>断开连接</text>
|
|
||||||
</view>
|
|
||||||
<view class="footer-btn footer-btn--print" @click="handlePrintLabel">
|
|
||||||
<u-icon v-if="!printing" name="printer" :size="30" color="#fff" />
|
|
||||||
<u-icon v-else name="reload" :size="30" color="#fff" class="spinning" />
|
|
||||||
<text>{{ printing ? '打印中...' : '打印' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</u-popup>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, watch, onBeforeUnmount, nextTick, getCurrentInstance } from 'vue'
|
|
||||||
import {
|
|
||||||
DrawType,
|
|
||||||
LPAPIFactory
|
|
||||||
} from '@/assets/js/js_sdk'
|
|
||||||
import '@/utils/bwipjs-polyfill.js'
|
|
||||||
import bwipjs from '@/scanpages/assets/bwip-js/bwip-js.mjs'
|
|
||||||
|
|
||||||
// ===== Props =====
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: { type: Boolean, default: false },
|
|
||||||
height: { type: String, default: '85%' },
|
|
||||||
labelWidth: { type: Number, default: 50 },
|
|
||||||
labelHeight: { type: Number, default: 30 },
|
|
||||||
margin: { type: Number, default: 2 },
|
|
||||||
barcodeText: { type: String, default: '12345678' },
|
|
||||||
barcodeTopText: { type: String, default: '' },
|
|
||||||
barcodeBottomText: { type: String, default: '' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'close', 'printed', 'error'])
|
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
|
||||||
|
|
||||||
// ===== 响应式数据 =====
|
|
||||||
const canvasId = 'lpapi-ble-popup-qr'
|
|
||||||
const barcodeCanvasId = 'lpapi-ble-barcode-qr'
|
|
||||||
const labelW = ref(960)
|
|
||||||
const labelH = ref(960)
|
|
||||||
const barcodeCanvasPx = ref(320)
|
|
||||||
const canvasStyles = ref({})
|
|
||||||
const barcodeCanvasStyles = ref({})
|
|
||||||
const localVisible = ref(false)
|
|
||||||
const deviceList = ref([{ name: '未检测到打印机' }])
|
|
||||||
const deviceIndex = ref(0)
|
|
||||||
const rotationIndex = ref(1)
|
|
||||||
const rotationList = [
|
|
||||||
{ name: '横向打印', value: 0 },
|
|
||||||
{ name: '右转90度', value: 90 },
|
|
||||||
{ name: '转转180度', value: 180 },
|
|
||||||
{ name: '左转90度', value: 270 }
|
|
||||||
]
|
|
||||||
const gapList = [
|
|
||||||
{ name: '随打印机设置', value: 255 },
|
|
||||||
{ name: '小票纸', value: 0 },
|
|
||||||
{ name: '不干胶', value: 2 },
|
|
||||||
{ name: '卡纸', value: 3 }
|
|
||||||
]
|
|
||||||
const gapIndex = ref(0)
|
|
||||||
const darknessList = [
|
|
||||||
{ name: '随打印机设置', value: 255 },
|
|
||||||
{ name: '6 (正常)', value: 6 },
|
|
||||||
{ name: '7', value: 7 },
|
|
||||||
{ name: '8', value: 8 },
|
|
||||||
{ name: '9', value: 9 },
|
|
||||||
{ name: '10 (较浓)', value: 10 },
|
|
||||||
{ name: '11', value: 11 },
|
|
||||||
{ name: '12', value: 12 },
|
|
||||||
{ name: '13', value: 13 },
|
|
||||||
{ name: '14', value: 14 },
|
|
||||||
{ name: '15 (最浓)', value: 15 }
|
|
||||||
]
|
|
||||||
const darknessIndex = ref(0)
|
|
||||||
const speedList = [
|
|
||||||
{ name: '随打印机设置', value: 255 },
|
|
||||||
{ name: '最慢', value: 1 },
|
|
||||||
{ name: '较慢', value: 2 },
|
|
||||||
{ name: '正常', value: 3 },
|
|
||||||
{ name: '较快', value: 4 },
|
|
||||||
{ name: '最快', value: 5 }
|
|
||||||
]
|
|
||||||
const speedIndex = ref(0)
|
|
||||||
const threshold = ref(128)
|
|
||||||
const jsonMode = ref(false)
|
|
||||||
const printing = ref(false)
|
|
||||||
const isWeiXin = ref(false)
|
|
||||||
const isAlipay = ref(false)
|
|
||||||
const lpapi = ref(undefined)
|
|
||||||
const context = ref(undefined)
|
|
||||||
const previewList = ref([])
|
|
||||||
|
|
||||||
// ===== 取值辅助 =====
|
|
||||||
const getDevice = () => {
|
|
||||||
return deviceList.value[deviceIndex.value]
|
|
||||||
}
|
|
||||||
const getOrientation = () => {
|
|
||||||
return rotationList[rotationIndex.value]?.value || 0
|
|
||||||
}
|
|
||||||
const getGapType = () => {
|
|
||||||
return gapList[gapIndex.value]?.value || 255
|
|
||||||
}
|
|
||||||
const getPrintDarkness = () => {
|
|
||||||
return darknessList[darknessIndex.value]?.value || 255
|
|
||||||
}
|
|
||||||
const getPrintSpeed = () => {
|
|
||||||
return speedList[speedIndex.value]?.value || 255
|
|
||||||
}
|
|
||||||
const getThreshold = () => {
|
|
||||||
return Number(threshold.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== Canvas 样式 =====
|
|
||||||
const updateCanvasStyle = () => {
|
|
||||||
var pos = { position: 'absolute' }
|
|
||||||
if (isWeiXin.value) {
|
|
||||||
pos.left = '-999999rpx'
|
|
||||||
pos.top = '-999999rpx'
|
|
||||||
pos.opacity = '0'
|
|
||||||
pos.pointerEvents = 'none'
|
|
||||||
pos.zIndex = '-1'
|
|
||||||
} else {
|
|
||||||
pos.left = '-999999rpx'
|
|
||||||
pos.top = '-999999rpx'
|
|
||||||
}
|
|
||||||
canvasStyles.value = Object.assign(
|
|
||||||
{ width: labelW.value + 'px', height: labelH.value + 'px' },
|
|
||||||
pos
|
|
||||||
)
|
|
||||||
barcodeCanvasStyles.value = Object.assign(
|
|
||||||
{
|
|
||||||
width: barcodeCanvasPx.value + 'px',
|
|
||||||
height: barcodeCanvasPx.value + 'px',
|
|
||||||
},
|
|
||||||
pos
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== Canvas 更新 =====
|
|
||||||
const updateCanvas = (jobInfo) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
if (jobInfo && jobInfo.canvas) {
|
|
||||||
labelW.value = jobInfo.canvas.width
|
|
||||||
labelH.value = jobInfo.canvas.height
|
|
||||||
updateCanvasStyle()
|
|
||||||
}
|
|
||||||
setTimeout(() => resolve(true), 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 二维码生成(bwip-js qrcode,保持正方形不拉伸)=====
|
|
||||||
const _renderBwipjs = (canvas, opts) => {
|
|
||||||
try {
|
|
||||||
bwipjs.toCanvas(canvas, opts)
|
|
||||||
} catch (e) {
|
|
||||||
if (typeof canvas.getContext === 'function') {
|
|
||||||
var proto = Object.getPrototypeOf(canvas)
|
|
||||||
var ctorName = proto && proto.constructor && proto.constructor.name
|
|
||||||
if (ctorName && !(/HTMLCanvasElement|OffscreenCanvas/).test(ctorName)) {
|
|
||||||
try {
|
|
||||||
Object.defineProperty(proto.constructor, 'name', { configurable: true, value: 'HTMLCanvasElement' })
|
|
||||||
bwipjs.toCanvas(canvas, opts)
|
|
||||||
Object.defineProperty(proto.constructor, 'name', { configurable: true, value: ctorName })
|
|
||||||
return
|
|
||||||
} catch (e2) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成正方形二维码图片(使用独立画布,避免与标签画布尺寸冲突)
|
|
||||||
* @param {string} text
|
|
||||||
* @param {number} sizeMM 边长(毫米)
|
|
||||||
*/
|
|
||||||
const generateBarcodeImage = (text, sizeMM) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var dpi = 203
|
|
||||||
var pxPerMM = dpi / 25.4
|
|
||||||
var tw = Math.max(160, Math.round(sizeMM * pxPerMM))
|
|
||||||
barcodeCanvasPx.value = tw
|
|
||||||
updateCanvasStyle()
|
|
||||||
|
|
||||||
var baseScale = Math.max(4, Math.floor(tw / 33))
|
|
||||||
var bwipOpts = {
|
|
||||||
bcid: 'qrcode',
|
|
||||||
text: text || '',
|
|
||||||
scale: baseScale,
|
|
||||||
includetext: false,
|
|
||||||
padding: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
var query = uni.createSelectorQuery().in(instance)
|
|
||||||
query.select('#' + barcodeCanvasId).node().exec((res) => {
|
|
||||||
try {
|
|
||||||
if (!res || !res[0] || !res[0].node) {
|
|
||||||
return reject(new Error('二维码 canvas 未找到'))
|
|
||||||
}
|
|
||||||
var canvas = res[0].node
|
|
||||||
// 先固定位图尺寸,再交给 bwip 绘制(微信 2d canvas 不独立设置会沿用错误尺寸)
|
|
||||||
canvas.width = tw
|
|
||||||
canvas.height = tw
|
|
||||||
_renderBwipjs(canvas, bwipOpts)
|
|
||||||
|
|
||||||
var outW = canvas.width || tw
|
|
||||||
var outH = canvas.height || tw
|
|
||||||
uni.canvasToTempFilePath({
|
|
||||||
canvas: canvas,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: outW,
|
|
||||||
height: outH,
|
|
||||||
destWidth: outW,
|
|
||||||
destHeight: outH,
|
|
||||||
success: (t) => {
|
|
||||||
if (isWeiXin.value) {
|
|
||||||
var img = canvas.createImage()
|
|
||||||
img.src = t.tempFilePath
|
|
||||||
img.onload = () => { resolve(img) }
|
|
||||||
img.onerror = (e) => { reject(e) }
|
|
||||||
} else {
|
|
||||||
resolve(t.tempFilePath)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (e) => { reject(e) }
|
|
||||||
})
|
|
||||||
} catch (e) { reject(e) }
|
|
||||||
})
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在可用区域内居中放置正方形二维码
|
|
||||||
* @param {number} areaX
|
|
||||||
* @param {number} areaY
|
|
||||||
* @param {number} areaW
|
|
||||||
* @param {number} areaH
|
|
||||||
*/
|
|
||||||
const getQrDrawRect = (areaX, areaY, areaW, areaH) => {
|
|
||||||
var size = Math.min(areaW, areaH)
|
|
||||||
return {
|
|
||||||
x: areaX + (areaW - size) / 2,
|
|
||||||
y: areaY + (areaH - size) / 2,
|
|
||||||
size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 标签绘制(总入口) =====
|
|
||||||
const printLabel = (isPreview) => {
|
|
||||||
if (jsonMode.value) {
|
|
||||||
return printLabelWithJsonMode(isPreview)
|
|
||||||
} else {
|
|
||||||
return printLabelWithDrawMode(isPreview)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const printLabelWithDrawMode = async (isPreview) => {
|
|
||||||
var api = lpapi.value
|
|
||||||
var lW = props.labelWidth
|
|
||||||
var lH = props.labelHeight
|
|
||||||
var m = props.margin
|
|
||||||
var uW = lW - m * 2
|
|
||||||
|
|
||||||
var jobInfo = api.startJob({
|
|
||||||
context: context.value,
|
|
||||||
width: lW,
|
|
||||||
height: lH,
|
|
||||||
orientation: getOrientation(),
|
|
||||||
isPreview: isPreview
|
|
||||||
})
|
|
||||||
await updateCanvas(jobInfo)
|
|
||||||
|
|
||||||
if (props.barcodeTopText) {
|
|
||||||
api.drawText({
|
|
||||||
text: props.barcodeTopText,
|
|
||||||
x: m,
|
|
||||||
y: m,
|
|
||||||
width: uW,
|
|
||||||
height: 5,
|
|
||||||
fontHeight: 4,
|
|
||||||
horizontalAlignment: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var barcodeY = m + (props.barcodeTopText ? 7 : 0)
|
|
||||||
var barcodeH = lH - m * 2 - (props.barcodeTopText ? 7 : 0) - (props.barcodeBottomText ? 7 : 0)
|
|
||||||
try {
|
|
||||||
var rect = getQrDrawRect(m, barcodeY, uW, barcodeH)
|
|
||||||
var img = await generateBarcodeImage(props.barcodeText, rect.size)
|
|
||||||
api.drawImage({ img: img, x: rect.x, y: rect.y, width: rect.size, height: rect.size })
|
|
||||||
} catch (e) {
|
|
||||||
console.error('二维码绘制失败:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.barcodeBottomText) {
|
|
||||||
var by = lH - m - 5
|
|
||||||
api.drawText({
|
|
||||||
text: props.barcodeBottomText,
|
|
||||||
x: m,
|
|
||||||
y: by,
|
|
||||||
width: uW,
|
|
||||||
height: 5,
|
|
||||||
fontHeight: 3.5,
|
|
||||||
horizontalAlignment: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.commitJob({
|
|
||||||
gapType: getGapType(),
|
|
||||||
printDarkness: getPrintDarkness(),
|
|
||||||
printSpeed: getPrintSpeed()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const printLabelWithJsonMode = (isPreview) => {
|
|
||||||
var device = getDevice()
|
|
||||||
var lW = props.labelWidth
|
|
||||||
var lH = props.labelHeight
|
|
||||||
var m = props.margin
|
|
||||||
var uW = lW - m * 2
|
|
||||||
|
|
||||||
var jobPage = []
|
|
||||||
|
|
||||||
if (props.barcodeTopText) {
|
|
||||||
jobPage.push({
|
|
||||||
type: DrawType.text,
|
|
||||||
text: props.barcodeTopText,
|
|
||||||
x: m,
|
|
||||||
y: m,
|
|
||||||
width: uW,
|
|
||||||
height: 5,
|
|
||||||
fontHeight: 4,
|
|
||||||
horizontalAlignment: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var barcodeY = m + (props.barcodeTopText ? 7 : 0)
|
|
||||||
var barcodeH = lH - m * 2 - (props.barcodeTopText ? 7 : 0) - (props.barcodeBottomText ? 7 : 0)
|
|
||||||
var rect = getQrDrawRect(m, barcodeY, uW, barcodeH)
|
|
||||||
return generateBarcodeImage(props.barcodeText, rect.size).then((img) => {
|
|
||||||
jobPage.push({
|
|
||||||
type: DrawType.image,
|
|
||||||
img: img,
|
|
||||||
x: rect.x,
|
|
||||||
y: rect.y,
|
|
||||||
width: rect.size,
|
|
||||||
height: rect.size
|
|
||||||
})
|
|
||||||
|
|
||||||
if (props.barcodeBottomText) {
|
|
||||||
jobPage.push({
|
|
||||||
type: DrawType.text,
|
|
||||||
text: props.barcodeBottomText,
|
|
||||||
x: m,
|
|
||||||
y: lH - m - 5,
|
|
||||||
width: uW,
|
|
||||||
height: 5,
|
|
||||||
fontHeight: 3.5,
|
|
||||||
horizontalAlignment: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return lpapi.value.print({
|
|
||||||
jobInfo: {
|
|
||||||
jobWidth: lW,
|
|
||||||
jobHeight: lH,
|
|
||||||
orientation: getOrientation(),
|
|
||||||
isPreview: isPreview
|
|
||||||
},
|
|
||||||
printerInfo: {
|
|
||||||
name: device ? device.name : undefined,
|
|
||||||
deviceId: device ? device.deviceId : undefined
|
|
||||||
},
|
|
||||||
jobPage: jobPage,
|
|
||||||
onJobCreated: (jobInfo) => {
|
|
||||||
return updateCanvas(jobInfo)
|
|
||||||
},
|
|
||||||
onPageComplete: (res) => {
|
|
||||||
// 标签页面渲染完毕时的回调
|
|
||||||
},
|
|
||||||
onPagePrintComplete: (res) => {
|
|
||||||
// 标签页面打印完毕时的回调
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 初始化 =====
|
|
||||||
const initPrinter = () => {
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
isWeiXin.value = true
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
isAlipay.value = true
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
updateCanvasStyle()
|
|
||||||
|
|
||||||
lpapi.value = LPAPIFactory.getInstance({ showLog: 4 })
|
|
||||||
|
|
||||||
lpapi.value.startDiscovery({
|
|
||||||
timeout: 0,
|
|
||||||
deviceFound: (devices) => {
|
|
||||||
if (devices && devices.length > 0) {
|
|
||||||
deviceList.value.splice(0)
|
|
||||||
deviceList.value.push(...devices)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isWeiXin.value) {
|
|
||||||
context.value = lpapi.value.createDrawContext()
|
|
||||||
} else {
|
|
||||||
context.value = lpapi.value.createDrawContext({ canvasId: canvasId })
|
|
||||||
}
|
|
||||||
if (context.value) {
|
|
||||||
lpapi.value.setDrawContext(context.value)
|
|
||||||
handlePreviewLabel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 选择器变更 =====
|
|
||||||
const onDeviceChanged = (e) => {
|
|
||||||
deviceIndex.value = e ? e.detail.value : 0
|
|
||||||
}
|
|
||||||
const onRotationChanged = (e) => {
|
|
||||||
rotationIndex.value = e ? e.detail.value : 0
|
|
||||||
}
|
|
||||||
const onGapTypeChanged = (e) => {
|
|
||||||
gapIndex.value = e ? e.detail.value : 0
|
|
||||||
}
|
|
||||||
const onDarknessChanged = (e) => {
|
|
||||||
darknessIndex.value = e ? e.detail.value : 0
|
|
||||||
}
|
|
||||||
const onPrintSpeedChanged = (e) => {
|
|
||||||
speedIndex.value = e ? e.detail.value : 0
|
|
||||||
}
|
|
||||||
const onPrintModeChanged = (e) => {
|
|
||||||
jsonMode.value = e.detail.value
|
|
||||||
handlePreviewLabel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 打印机操作 =====
|
|
||||||
const handleStartDiscovery = () => {
|
|
||||||
uni.showLoading({ title: '正在搜索打印机...' })
|
|
||||||
lpapi.value.startDiscovery({
|
|
||||||
timeout: 5000,
|
|
||||||
deviceFound: (devices) => {
|
|
||||||
if (devices && devices.length > 0) {
|
|
||||||
deviceList.value.splice(0)
|
|
||||||
deviceList.value.push(...devices)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
adapterStateChange: (result) => {
|
|
||||||
if (!result.discovering) {
|
|
||||||
uni.hideLoading()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const handleStopDiscovery = () => {
|
|
||||||
lpapi.value.stopDiscovery()
|
|
||||||
uni.hideLoading()
|
|
||||||
}
|
|
||||||
const handleOpenPrinter = () => {
|
|
||||||
const currDevice = getDevice()
|
|
||||||
if (!currDevice || !currDevice.deviceId) {
|
|
||||||
uni.showToast({ title: '未检测到打印机', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uni.showLoading({ title: '正在连接打印机...' })
|
|
||||||
lpapi.value.openPrinter({
|
|
||||||
name: currDevice.name,
|
|
||||||
deviceId: currDevice.deviceId,
|
|
||||||
tryTimes: isAlipay.value ? 1 : 5,
|
|
||||||
success: (resp) => {
|
|
||||||
console.log('---- 【打印机连接成功】')
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({ title: '打印机连接成功!', icon: 'success' })
|
|
||||||
},
|
|
||||||
fail: (resp) => {
|
|
||||||
console.warn('---- 【打印机连接失败】:', JSON.stringify(resp))
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({ title: '打印机连接失败!', icon: 'error' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const handleClosePrinter = () => {
|
|
||||||
console.log('---- 关闭打印机!')
|
|
||||||
lpapi.value.closePrinter()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 预览 =====
|
|
||||||
const handlePreviewLabel = () => {
|
|
||||||
previewList.value.splice(0)
|
|
||||||
printLabel(true).then((res) => {
|
|
||||||
if (res.statusCode === 0) {
|
|
||||||
res.previewData.forEach((v) => {
|
|
||||||
previewList.value.push({
|
|
||||||
value: v,
|
|
||||||
key: Date.now() + '-' + Math.random() * 1000
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.warn('预览失败:', res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 打印 =====
|
|
||||||
const handlePrintLabel = async () => {
|
|
||||||
printing.value = true
|
|
||||||
try {
|
|
||||||
var d = getDevice()
|
|
||||||
if (!d || !d.deviceId) {
|
|
||||||
printing.value = false
|
|
||||||
return uni.showToast({ title: '未检测到打印机', icon: 'none' })
|
|
||||||
}
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
lpapi.value.openPrinter({
|
|
||||||
name: d.name,
|
|
||||||
deviceId: d.deviceId,
|
|
||||||
tryTimes: 5,
|
|
||||||
success: resolve,
|
|
||||||
fail: reject
|
|
||||||
})
|
|
||||||
})
|
|
||||||
var res = await printLabel(false)
|
|
||||||
if (res.statusCode === 0) {
|
|
||||||
uni.showToast({ title: '打印成功!', icon: 'success' })
|
|
||||||
emit('printed', { statusCode: 0 })
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: res.errMsg || '打印失败!', icon: 'error' })
|
|
||||||
emit('error', res)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('打印异常:', e)
|
|
||||||
uni.showToast({ title: '打印机连接失败!', icon: 'error' })
|
|
||||||
emit('error', e)
|
|
||||||
} finally {
|
|
||||||
printing.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 关闭弹窗 =====
|
|
||||||
const handleClose = () => {
|
|
||||||
handleClosePrinter()
|
|
||||||
emit('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 监听器 =====
|
|
||||||
watch(() => props.modelValue, (val) => {
|
|
||||||
localVisible.value = val
|
|
||||||
if (val) nextTick(() => initPrinter())
|
|
||||||
})
|
|
||||||
watch(localVisible, (val) => {
|
|
||||||
emit('update:modelValue', val)
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (lpapi.value) {
|
|
||||||
lpapi.value.closePrinter()
|
|
||||||
lpapi.value.stopDiscovery()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.print-popup { display: flex; flex-direction: column; height: 100%; background: #f5f6f8;
|
|
||||||
&__header { flex-shrink: 0; padding: 28rpx 32rpx 20rpx; text-align: center; border-bottom: 1rpx solid #e8ecf1; }
|
|
||||||
&__title { font-size: 32rpx; font-weight: 700; color: #1a1a2e; }
|
|
||||||
&__body { flex: 1; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; padding: 20rpx 24rpx; }
|
|
||||||
&__footer { flex-shrink: 0; padding: 12rpx 24rpx; padding-bottom: calc(12rpx + env(safe-area-inset-bottom)); background: #fff; border-top: 1rpx solid #e8ecf1; }
|
|
||||||
}
|
|
||||||
.footer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; }
|
|
||||||
.footer-btn { display: flex; align-items: center; justify-content: center; gap: 8rpx; height: 76rpx; font-size: 26rpx; font-weight: 500; border-radius: 12rpx; background: #f5f6f8; color: #1a1a2e; transition: transform .15s, box-shadow .15s;
|
|
||||||
&:active { transform: scale(.95); opacity: .85; }
|
|
||||||
&--print { background: linear-gradient(135deg, #2979ff, #5a9aff); color: #fff; box-shadow: 0 4rpx 16rpx rgba(41,121,255,.35); grid-column: 1 / -1;}
|
|
||||||
}
|
|
||||||
.spinning { animation: spin .8s linear infinite; }
|
|
||||||
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
||||||
.setting-row { display: flex; align-items: center; justify-content: space-between; box-sizing: border-box; padding: 22rpx 24rpx; margin-bottom: 12rpx; background: #fff; border-radius: 12rpx;
|
|
||||||
&__label { font-size: 26rpx; color: #6b7c93; flex-shrink: 0; width: 160rpx; }
|
|
||||||
&__right { flex: 1; display: flex; justify-content: flex-end; }
|
|
||||||
&__picker { display: flex; align-items: center; gap: 8rpx; }
|
|
||||||
&__value { font-size: 26rpx; color: #1a1a2e; font-weight: 500;
|
|
||||||
&--empty { color: #b0bec5; }
|
|
||||||
}
|
|
||||||
&__input { flex: 1; font-size: 26rpx; color: #1a1a2e; text-align: right; padding: 4rpx 0; }
|
|
||||||
}
|
|
||||||
.preview-section { margin-bottom: 24rpx; padding: 20rpx; background: #fff; border-radius: 12rpx; overflow: hidden;
|
|
||||||
&__title { display: block; font-size: 26rpx; font-weight: 600; color: #1a1a2e; margin-bottom: 16rpx; }
|
|
||||||
&__list { display: flex; flex-wrap: wrap; justify-content: center; gap: 16rpx; }
|
|
||||||
}
|
|
||||||
.preview-img { width: 100%; border: 1rpx dashed #ccc; border-radius: 4rpx; }
|
|
||||||
</style>
|
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 标签预览与打印 -->
|
<!-- 标签预览与打印 -->
|
||||||
<nbd-print-prop-qr
|
<nbd-print-prop
|
||||||
v-model="showPrint"
|
v-model="showPrint"
|
||||||
:labelWidth="50"
|
:labelWidth="50"
|
||||||
:labelHeight="30"
|
:labelHeight="30"
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
<picker class="filter-picker" mode="date" :value="weldingDate" @change="onDateChange">
|
<picker class="filter-picker" style="width: 100%" mode="date" :value="weldingDate"
|
||||||
|
@change="onDateChange">
|
||||||
<view class="filter-btn">
|
<view class="filter-btn">
|
||||||
<text class="filter-btn__text" :class="{ 'filter-btn__text--placeholder': !weldingDate }">
|
<text class="filter-btn__text" :class="{ 'filter-btn__text--placeholder': !weldingDate }">
|
||||||
{{ weldingDate || '焊接日期' }}
|
{{ weldingDate || '焊接日期' }}
|
||||||
@@ -406,18 +407,19 @@
|
|||||||
|
|
||||||
.filter-item {
|
.filter-item {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
height: 70rpx;
|
height: 70rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-picker {
|
.filter-picker {
|
||||||
flex: 1;
|
flex: 1 1 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
height: 70rpx;
|
height: 70rpx;
|
||||||
display: flex;
|
display: block;
|
||||||
align-items: center;
|
box-sizing: border-box;
|
||||||
line-height: 70rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-btn {
|
.filter-btn {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 专项审核办理人:材料编码批次或材料数量不准确时必填 -->
|
<!-- 专项审核办理人:材料编码批次或材料数量不准确时必填 -->
|
||||||
<view v-if="needSpecialReviewer" class="card__row" @click="handleShowReviewerSelect">
|
<view v-if="needSpecialReviewer" class="card__row" @click="handleShowReviewerSelect">
|
||||||
<text class="card__label required">专项审核办理人</text>
|
<text class="card__label required">质检员</text>
|
||||||
<view class="card__value-wrap card__value-wrap--clickable">
|
<view class="card__value-wrap card__value-wrap--clickable">
|
||||||
<text class="card__value" :class="{ 'card__value--placeholder': !specialReviewerName }">
|
<text class="card__value" :class="{ 'card__value--placeholder': !specialReviewerName }">
|
||||||
{{ specialReviewerName || '请选择' }}
|
{{ specialReviewerName || '请选择' }}
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 两侧主编码 -->
|
<!-- 两侧主编码 -->
|
||||||
<view class="card__row card__row--stack">
|
<!-- <view class="card__row card__row--stack">
|
||||||
<text class="card__label">两侧主编码</text>
|
<text class="card__label">两侧主编码</text>
|
||||||
<view v-if="sideMaterials.length" class="side-codes">
|
<view v-if="sideMaterials.length" class="side-codes">
|
||||||
<view v-for="(m, idx) in sideMaterials" :key="m.side || idx" class="side-codes__item">
|
<view v-for="(m, idx) in sideMaterials" :key="m.side || idx" class="side-codes__item">
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text v-else class="card__value">暂无主编码信息</text>
|
<text v-else class="card__value">暂无主编码信息</text>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 专项审核办理人选择 -->
|
<!-- 专项审核办理人选择 -->
|
||||||
<nbd-select v-model="showReviewerPicker" :show-search="true" title="选择专项审核办理人" :list="reviewerList"
|
<nbd-select v-model="showReviewerPicker" :show-search="true" title="选择质检员" :list="reviewerList"
|
||||||
label-key="BaseInfoName" value-key="BaseInfoId" @confirm="handleConfirmReviewer" />
|
label-key="BaseInfoName" value-key="BaseInfoId" @confirm="handleConfirmReviewer" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -233,7 +233,7 @@ const cuttingFields = [
|
|||||||
{ label: '侧2原主编码', key: 'Side2OriginalMainCode' },
|
{ label: '侧2原主编码', key: 'Side2OriginalMainCode' },
|
||||||
{ label: '材料编码批次', key: 'IsMaterialCodeBatchNoAccurate', type: 'tag', tagMatch: true, tagPassText: '准确', tagFailText: '不准确' },
|
{ label: '材料编码批次', key: 'IsMaterialCodeBatchNoAccurate', type: 'tag', tagMatch: true, tagPassText: '准确', tagFailText: '不准确' },
|
||||||
{ label: '材料数量', key: 'IsMaterialQuantityAccurate', type: 'tag', tagMatch: true, tagPassText: '准确', tagFailText: '不准确' },
|
{ label: '材料数量', key: 'IsMaterialQuantityAccurate', type: 'tag', tagMatch: true, tagPassText: '准确', tagFailText: '不准确' },
|
||||||
{ label: '专项审核办理人', key: 'SpecialReviewerName' },
|
{ label: '质检员', key: 'SpecialReviewerName' },
|
||||||
{ label: '检查人', key: 'CheckPersonName' },
|
{ label: '检查人', key: 'CheckPersonName' },
|
||||||
{ label: '检查时间', key: 'CheckTime' },
|
{ label: '检查时间', key: 'CheckTime' },
|
||||||
{ label: '检查结果', key: 'CheckResult', type: 'tag', tagMatch: '合格' },
|
{ label: '检查结果', key: 'CheckResult', type: 'tag', tagMatch: '合格' },
|
||||||
@@ -376,7 +376,7 @@ const handleConfirmReviewer = (item) => {
|
|||||||
const validateSpecialReviewer = () => {
|
const validateSpecialReviewer = () => {
|
||||||
if (!needSpecialReviewer.value) return true
|
if (!needSpecialReviewer.value) return true
|
||||||
if (!layingOffForm.value.SpecialReviewerId) {
|
if (!layingOffForm.value.SpecialReviewerId) {
|
||||||
uni.showToast({ title: '请选择专项审核办理人', icon: 'none' })
|
uni.showToast({ title: '请选择质检员', icon: 'none' })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
+2
-2
@@ -89,11 +89,11 @@ export const defaultApps = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '焊缝检测',
|
name: '焊缝检测',
|
||||||
icon: 'daiban',
|
icon: 'search',
|
||||||
menuId: "",
|
menuId: "",
|
||||||
path: "/scanpages/hj/weld_appearance",
|
path: "/scanpages/hj/weld_appearance",
|
||||||
isScan: true,
|
isScan: true,
|
||||||
prefix: 'nbd-icon',
|
prefix: 'uicon',
|
||||||
selected: false
|
selected: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user