Files
sh-app/components/barcodePrint.vue
T
2026-06-29 17:35:49 +08:00

673 lines
17 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>
<view>
<view class="uni-list">
<!-- 隐藏画布用于标签绘制 -->
<canvas :id="canvasId" :canvas-id="canvasId" type="2d"
:style="{ width: labelWidth + 'px', height: labelHeight + 'px' }"
style="position: fixed; left: -999999rpx; top: -999999rpx" />
<!-- 打印机列表 -->
<view class="uni-list-cell">
<view class="uni-list-cell-left">打印机</view>
<view class="uni-list-cell-db">
<picker :value="deviceIndex" :range="deviceList" @change="onDeviceChanged" range-key="name">
<view class="uni-input">{{ deviceList[deviceIndex].name }}</view>
</picker>
</view>
</view>
<!-- 出纸方向 -->
<view class="uni-list-cell">
<view class="uni-list-cell-left">出纸方向</view>
<view class="uni-list-cell-db">
<picker :value="rotationIndex" :range="rotationList" @change="onRotationChanged" range-key="name">
<view class="uni-input">{{ rotationList[rotationIndex].name }}</view>
</picker>
</view>
</view>
<!-- 标签纸类型 -->
<view class="uni-list-cell">
<view class="uni-list-cell-left">纸张类型</view>
<view class="uni-list-cell-db">
<picker :value="gapIndex" :range="gapList" @change="onGapTypeChanged" range-key="name">
<view class="uni-input">{{ gapList[gapIndex].name }}</view>
</picker>
</view>
</view>
<!-- 打印浓度 -->
<view class="uni-list-cell">
<view class="uni-list-cell-left">打印浓度</view>
<view class="uni-list-cell-db">
<picker :value="darknessIndex" :range="darknessList" @change="onDarknessChanged" range-key="name">
<view class="uni-input">{{ darknessList[darknessIndex].name }}</view>
</picker>
</view>
</view>
<!-- 打印速度 -->
<view class="uni-list-cell">
<view class="uni-list-cell-left">打印速度</view>
<view class="uni-list-cell-db">
<picker :value="speedIndex" :range="speedList" @change="onPrintSpeedChanged" range-key="name">
<view class="uni-input">{{ speedList[speedIndex].name }}</view>
</picker>
</view>
</view>
<!-- 图片处理灰度阈值 -->
<view class="uni-list-cell">
<view class="uni-list-cell-left">灰度阈值</view>
<view class="uni-list-cell-db">
<input class="uni-input" type="number" v-model="threshold" />
</view>
</view>
<!-- 打印方式两种不同的写法 -->
<view class="uni-list-cell uni-list-cell-pd">
<view class="uni-list-cell-db">JSON配置模式</view>
<switch :checked="jsonMode" @change="onPrintModeChanged" style="transform: scale(0.75)" />
</view>
</view>
<view class="button-sp-area">
<button class="mini-btn" type="warn" size="mini" plain="true" @click="handleStopDiscovery">停止搜索</button>
<button class="mini-btn" type="primary" size="mini" plain="true" @click="handleStartDiscovery">
搜索打印机
</button>
</view>
<view class="button-sp-area">
<button class="mini-btn" type="warn" size="mini" plain="true" @click="handleClosePrinter">
关闭打印机
</button>
<button class="mini-btn" type="primary" size="mini" plain="true" @click="handleOpenPrinter">
打开打印机
</button>
<button class="mini-btn" type="primary" size="mini" @click="handlePrintLabel">打印</button>
</view>
<view style="text-align: center; padding: 10px">
<view style="margin: 10px; border: solid lightgray 1px">
<image v-for="item in previewList" :src="item.value" :key="item.key" class="image" mode="widthFix"
style="margin: 10rpx; border: dashed lightgray 1px" />
</view>
</view>
</view>
</template>
<script>
import {
DrawType,
LPAPIFactory,
LPAPI
} from "../../uni_modules/dothan-lpapi-ble/js_sdk";
export default {
data() {
return {
canvasId: "lpapi-ble-uni1",
labelWidth: 960,
labelHeight: 960,
deviceList: [{
name: "未检测到打印机",
}, ],
rotationIndex: 0,
rotationList: [{
name: "横向打印",
value: 0
},
{
name: "右转90度",
value: 90
},
{
name: "转转180度",
value: 180
},
{
name: "左转90度",
value: 270
},
],
deviceIndex: 0,
gapList: [{
name: "随打印机设置",
value: 255
},
{
name: "小票纸",
value: 0
},
{
name: "不干胶",
value: 2
},
{
name: "卡纸",
value: 3
},
],
gapIndex: 0,
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
},
],
darknessIndex: 0,
speedList: [{
name: "随打印机设置",
value: 255
},
{
name: "最慢",
value: 1
},
{
name: "较慢",
value: 2
},
{
name: "正常",
value: 3
},
{
name: "较快",
value: 4
},
{
name: "最快",
value: 5
},
],
speedIndex: 0,
isAppPlus: false,
isWeiXin: false,
isAlipay: false,
/**
* 图片预览列表
* @type {{value: string; key: string;}[]}
***/
previewList: [],
threshold: 128,
jsonMode: false,
/**
* @type {LPAPI}
*/
lpapi: undefined,
};
},
onLoad(options) {
console.log("========== onLoad ==========");
//option为object类型,会序列化上个页面传递的参数
console.log(`---- type = ${options.type}`, options);
// #ifdef APP-PLUS
this.isAppPlus = true;
// #endif
// #ifdef MP-ALIPAY
this.isAlipay = true;
// #endif
// #ifdef MP-WEIXIN
this.isWeiXin = true;
// #endif
//
this.lpapi = LPAPIFactory.getInstance({
// 日志信息显示级别,值为 0 - 4,0表示不显示调试信息,4表示显示所有调试信息
showLog: 4,
// bleAdapter: bleAdapter,
// writeExtendWaits: 3,
// dataSendMode: 1,
});
// 搜索蓝牙设备
this.lpapi.startDiscovery({
timeout: 0,
deviceFound: (devices) => {
this.onDeviceFound(devices);
},
});
// 微信小程序、支付宝小程序创建绘制上下文的时候,可以不用传递 canvasId,此时会通过离屏Canvas来创建绘制上下文环境。
if (this.isWeiXin) {
this.context = this.lpapi.createDrawContext();
} else {
// 备注:支付宝小程序新版本有问题,需要去掉 canvas 中的 type="2d"才可以。
this.context = this.lpapi.createDrawContext({
// 用于进行标签绘制的画布ID
canvasId: this.canvasId,
});
}
//
if (this.context) {
this.lpapi.setDrawContext(this.context);
// 预览标签内容
this.handlePreviewLabel();
}
},
onCanvasReady() {
console.log("========== onCanvasReady ==========");
console.log(`---- isAlipay: ${this.isAlipay}`);
if (this.isAlipay) {
// 支付宝小程序中在屏 Canvas的使用的时候,1:配置 type="2d"2:设置 onReady 事件,在onReady中通过 selectorQuery来获取canvas实例。
// this.queryCanvas((canvas) => {
// this.context = this.lpapi.createDrawContext({
// canvas: canvas,
// });
// if (this.context) {
// this.lpapi.setDrawContext(this.context);
// }
// this.handlePreviewLabel();
// });
}
},
onShow() {
console.log("========== onShow ==========");
// 建议在 onShow的时候,修改下标签的绘制上下文,避免因在context没有更新导致的 canvas不显示的问题。
if (this.context) {
this.lpapi.setDrawContext(this.context);
}
},
onUnload() {
console.log("========== onUnload ==========");
this.handleClosePrinter();
},
methods: {
queryCanvas(callback) {
// 微信小程序、支付宝小程序等 最新版本初始化方式
// 注意:canvas 中必须配置 type="2d" 才能生效。
try {
uni.createSelectorQuery()
.select(`#${this.canvasId}`)
.node()
.exec((res) => {
console.log(`------- exec.result: --------`);
console.log(res);
const canvas = res[0]?.node;
// this.printCanvas = canvas;
callback(canvas);
});
} catch (e) {
console.warn(`---- 不支持通过该方式获取 Canvas 对象!`);
console.warn(e);
callback(undefined);
}
},
onDeviceChanged(e) {
console.log(`---------- onDeviceChanged${e.detail.value} ----------`);
this.deviceIndex = e ? e.detail.value : 0;
},
onRotationChanged(e) {
console.log(`---------- onRotationChanged: ${e.detail.value} ----------`);
this.rotationIndex = e ? e.detail.value : 0;
},
onGapTypeChanged(e) {
console.log(`---------- onGapTypeChanged${e.detail.value} ----------`);
this.gapIndex = e ? e.detail.value : 0;
},
onDarknessChanged(e) {
console.log(`---------- onDarknessChanged${e.detail.value} ----------`);
this.darknessIndex = e ? e.detail.value : 0;
},
onPrintSpeedChanged(e) {
console.log(`---------- onPrintSpeedChanged${e.detail.value} ----------`);
this.speedIndex = e ? e.detail.value : 0;
},
onPrintModeChanged(e) {
console.log(`---------- onPrintModeChanged: ${e.detail.value} ----------`);
this.jsonMode = e.detail.value;
this.handlePreviewLabel();
},
handleStartDiscovery() {
uni.showLoading({
title: "正在搜索打印机...",
});
this.lpapi.startDiscovery({
timeout: 5000,
deviceFound: (devices) => {
this.onDeviceFound(devices);
},
adapterStateChange: (result) => {
if (!result.discovering) {
uni.hideLoading();
}
},
});
},
handleStopDiscovery() {
this.lpapi.stopDiscovery();
},
handleOpenPrinter() {
const currDevice = this.getDevice();
const isAlipay = this.isAlipay;
if (currDevice && currDevice.deviceId) {
//
uni.showLoading({
title: "正在链接打印机...",
});
return this.lpapi
.openPrinter({
name: currDevice.name,
deviceId: currDevice.deviceId,
// 如果打印机链接失败的话,可以尝试连续进行多次链接。
tryTimes: isAlipay ? 1 : 5,
success: (resp) => {
console.log(`---- 【打印机链接成功】`);
console.log(resp.resultInfo);
uni.hideLoading();
//
uni.showToast({
title: "打印机链接成功!",
icon: "success",
});
},
fail: (resp) => {
console.warn(`---- 【打印机链接失败】:`);
console.warn(JSON.stringify(resp));
//
uni.hideLoading();
//
uni.showToast({
title: "打印机链接失败!",
icon: "fail",
});
},
})
.then((res) => {
return res.statusCode === 0;
});
} else {
console.warn("---- 未检测到打印机!");
uni.showToast({
title: "未检测到打印机",
icon: "fail",
});
if (typeof callback === "function") {
callback(false);
}
return false;
}
},
handleClosePrinter() {
console.log(`---- 关闭打印机!`);
this.lpapi.closePrinter();
},
onDeviceFound(devices) {
console.log(`---- 检测到打印机:`);
console.log(devices);
if (devices && devices.length > 0) {
this.deviceList.splice(0);
for (let item of devices) {
this.deviceList.push(item);
//
// const advertisData = item.advertisData ? new Uint8Array(item.advertisData) : undefined;
// const advDataStr = advertisData ? LPAUtils.arrayBufferToHex16(advertisData) : "-";
// console.log(`---- advertisData [${item.name}] : [${advDataStr}]`);
}
}
},
getDevice() {
return this.deviceList[this.deviceIndex];
},
getOrientation() {
return this.rotationList[this.rotationIndex].value;
},
getGapType() {
return this.gapList[this.gapIndex].value;
},
getPrintDarkness() {
return this.darknessList[this.darknessIndex].value;
},
getPrintSpeed() {
return this.speedList[this.speedIndex].value;
},
getThreshold() {
return Number(this.threshold);
},
updateCanvas(jobInfo) {
// 如果通过 createSelectorQuery 获取到了 Canvas 实例,则不需要延迟处理;
// 在微信小程序、支付宝小程序中是支持该功能的;
return new Promise((resolve) => {
// 更新canvas大小;
if (jobInfo.canvas) {
this.labelWidth = jobInfo.canvas.width;
this.labelHeight = jobInfo.canvas.height;
// 等 Canvas 大小更新后在执行后续绘制操作;
setTimeout(() => {
resolve(true);
}, 100);
} else {
resolve(true);
}
});
},
handlePreviewLabel() {
// 1. 先清空当前的图片列表
this.previewList.splice(0);
// 2. 创建并绘制标签内容
this.printLabel(true).then((res) => {
console.warn(`----- previewLabel.result: ${res.statusCode}`, res.errMsg);
// 3. 标签绘制完毕后,预览标签内容
if (res.statusCode === 0) {
console.warn(`---- 所有页面全部生成完毕!`);
res.previewData.forEach((value) => {
this.previewList.push({
value: value,
// 为了保证 v-for循环的唯一性,随便生成一个随机ID
key: `${new Date().getTime()}-${Math.random() * 1000}`,
});
});
} else {
console.warn(`---- 标签预览失败!`, res);
}
});
},
async handlePrintLabel() {
// 1. 连接打印机
const openResult = await this.handleOpenPrinter();
if (!openResult) {
uni.showToast({
title: "打印机连接失败!",
icon: "success",
});
return;
}
// 2. 创建标签后直接打印
this.printLabel(false).then((res) => {
if (res.statusCode === 0) {
uni.showToast({
title: "打印成功!",
icon: "success",
});
} else {
console.warn(`---- 打印失败!`);
if (res.errMsg) {
uni.showToast({
title: res.errMsg,
icon: "error",
});
}
}
});
},
printLabel(isPreview) {
if (this.jsonMode) {
return this.printLabelWithJsonMode(isPreview);
} else {
return this.printLabelWithDrawMode(isPreview);
}
},
/**
* 通过 draw 的方式绘制标签内容。
*/
async printLabelWithDrawMode(isPreview) {
console.log(`---- qrcodePrintWithDrawMode:`);
const api = this.lpapi;
//
const labelWidth = 40;
const labelHeight = 30;
const margin = 2;
//
const jobInfo = api.startJob({
context: this.context,
width: labelWidth,
height: labelHeight,
orientation: this.getOrientation(),
isPreview: isPreview,
});
//
await this.updateCanvas(jobInfo);
// 绘制外边框
api.drawRectangle({
width: labelWidth,
height: labelHeight,
});
api.draw1DBarcode({
text: "12345678",
x: margin,
y: margin,
width: labelWidth - margin * 2,
height: labelHeight - margin * 2,
textHeight: 5,
barcodeType: 22,
});
//
return api.commitJob({
gapType: this.getGapType(),
printDarkness: this.getPrintDarkness(),
printSpeed: this.getPrintSpeed(),
});
},
/**
* 通过 JSON 方式绘制标签内容。
*/
printLabelWithJsonMode(isPreview) {
console.log(`---- qrcodePrintWithJsonMode:`);
const device = this.getDevice();
const labelWidth = 40;
const labelHeight = 30;
const margin = 2;
//
return this.lpapi.print({
// 配置标签信息
jobInfo: {
jobWidth: labelWidth,
jobHeight: labelHeight,
orientation: this.getOrientation(),
isPreview: isPreview, // 当前任务是否是预览任务
},
// 配置打印机信息
printerInfo: {
name: device?.name,
deviceId: device?.deviceId,
},
jobPage: [{
type: DrawType.rect,
width: labelWidth,
height: labelHeight,
},
{
type: DrawType.barcode,
text: "12345678",
x: margin,
y: margin,
width: labelWidth - margin * 2,
height: labelHeight - margin * 2,
textHeight: 5,
barcodeType: 22,
},
],
onJobCreated: (jobInfo) => {
// 通过双向绑定方式更新画布大小
return this.updateCanvas(jobInfo);
},
onPageComplete(res) {
// 标签页面渲染完毕时的回调函数,预览模式下可展示标签打印效果。
},
onPagePrintComplete(res) {
// 标签页面打印完毕时的回调函数。
},
});
},
},
};
</script>
<style>
button {
margin-top: 15upx;
margin-bottom: 15upx;
height: 40px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin: 200rpx auto 50rpx auto;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
button {
margin-top: 30rpx;
margin-bottom: 30rpx;
}
.button-sp-area {
margin: 0 auto;
width: 90%;
display: flex;
}
.mini-btn {
flex: 1;
margin-right: 10rpx;
height: 40px;
line-height: 40px;
font-size: 18px;
}
</style>