CPCL指令集微信蓝牙打印

This commit is contained in:
QunSheng Lin 2025-02-06 14:40:04 +08:00
commit 0bdd14e0da
27 changed files with 4335 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Node.js
node_modules/
package-lock.json
.idea
# 测试
/tests/
# miniprogram
sourcemap.zip
miniprogram_npm

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 smoixan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# cpcl-bluetooth-print
适用于微信小程序的蓝牙打印采用CPCL指令
# 基于CPCL指令集的微信小程序连接蓝牙打印机示例
小程序连接蓝牙打印机打印文本与二维码等示例在 github 上都能找到一些,唯独打印图片这个案例几乎没有。希望能帮助到有打印图片需求的小伙伴。
- 测试打印机:[汉印HM-A300L](https://cn.hprt.com/ChanPin/)
- 打印机指令类型:`CPCL 指令集`
- 注:图片越大打印越慢,由于小程序只能使用低功率蓝牙,一次最多发送 20 个字节的数据,而图片数据可以达到几万、几十万个字节。
## 主要参考
- [便携式蓝牙打印](https://ask.dcloud.net.cn/article/37984)
- [微信小程序连接蓝牙打印机示例](https://developers.weixin.qq.com/community/develop/doc/0008acd004ccd86b37d649ee55b009?highLine=%25E8%2593%259D%25E7%2589%2599)

82
project.config.json Normal file
View File

@ -0,0 +1,82 @@
{
"description": "项目配置文件详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"miniprogramRoot": "src/",
"setting": {
"urlCheck": false,
"es6": true,
"enhance": true,
"postcss": true,
"preloadBackgroundData": false,
"minified": true,
"newFeature": false,
"coverView": true,
"nodeModules": true,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"lazyloadPlaceholderEnable": false,
"useMultiFrameRuntime": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"useIsolateContext": false,
"userConfirmedBundleSwitch": false,
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./src/"
}
],
"minifyWXSS": true,
"disableUseStrict": false,
"minifyWXML": true,
"showES6CompileOption": false,
"useCompilerPlugins": [
"less"
],
"useStaticServer": true,
"condition": false,
"ignoreUploadUnusedFiles": true
},
"compileType": "miniprogram",
"srcMiniprogramRoot": "src/",
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
},
"condition": {
"search": {
"list": []
},
"conversation": {
"list": []
},
"game": {
"list": []
},
"plugin": {
"list": []
},
"gamePlugin": {
"list": []
},
"miniprogram": {
"list": []
}
},
"libVersion": "2.15.0",
"packOptions": {
"ignore": [],
"include": []
},
"appid": "wx463c21c58bdf2d7f"
}

View File

@ -0,0 +1,26 @@
{
"condition": {
"miniprogram": {
"list": [
{
"name": "pages/print/index",
"pathName": "pages/print/index",
"query": "",
"scene": null
},
{
"name": "",
"pathName": "pages/print/index",
"query": "",
"launchMode": "default",
"scene": null
}
]
}
},
"projectname": "cpcl-bluetooth-print",
"setting": {
"compileHotReLoad": true
},
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
}

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

3
src/app.js Normal file
View File

@ -0,0 +1,3 @@
App({
onLaunch() {},
})

19
src/app.json Normal file
View File

@ -0,0 +1,19 @@
{
"resolveAlias": {
"@/*": "/*"
},
"useExtendedLib": {
"weui": true
},
"pages": [
"pages/print/index"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle": "black"
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}

1
src/app.less Normal file
View File

@ -0,0 +1 @@
/** app.less **/

View File

@ -0,0 +1,281 @@
module.exports = Behavior({
data: {
printList: [],
},
methods: {
// 1.查找蓝牙设备 S
start: function () {
if (!wx.openBluetoothAdapter) {
this.showTip("当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。")
return;
}
var _this = this;
wx.openBluetoothAdapter({
success(res) {
_this.getBluetoothAdapterState();
},
fail(err) {
console.log(err);
_this.showTip('蓝牙初始化失败,请确保已开启手机蓝牙,且已开启小程序使用权限')
},
})
},
getBluetoothAdapterState: function () {
var _this = this;
wx.getBluetoothAdapterState({
complete: function (res) {
if (!!res && res.available) {
_this.startSearch();
} else {
_this.showTip("蓝牙初始化失败,请确保已开启手机蓝牙,且已开启小程序使用权限")
}
}
})
},
research: function () {
var _this = this;
wx.removeStorageSync('bluetoothPrintList')
wx.closeBluetoothAdapter({
complete: function (res) {
_this.start();
}
})
},
startSearch: function () {
var _this = this;
let printList = wx.getStorageSync('bluetoothPrintList')
if (printList && printList.length > 0) {
_this.setData({
printList
});
return
}
wx.showLoading({
title: '搜索中',
mask: true
})
wx.startBluetoothDevicesDiscovery({
services: [],
complete: function (res) {
setTimeout(function () {
wx.getBluetoothDevices({
complete: function (res) {
wx.hideLoading();
var list = _this.filterPrint(res.devices);
_this.setData({
printList: list
});
wx.setStorageSync('bluetoothPrintList', list)
if (list.length == 0) {
_this.showTip('没有发现新的设备哦');
}
}
});
wx.stopBluetoothDevicesDiscovery({
success: function (res) { },
})
}, 3000)
}
})
},
filterPrint: function (list) {
var _this = this;
let printList = [];
let storageList = _this.data.storageList || [];
for (let i = 0; i < list.length; i++) {
let base64 = wx.arrayBufferToBase64(list[i].advertisData);
let str = Array.prototype.map.call(new Uint8Array(list[i].advertisData), x => ('00' + x.toString(16)).slice(-2)).join('');
if (str.length == 16) {
let has = false;
for (let j = 0; j < storageList.length; j++) {
if (storageList[j].deviceId == list[i].deviceId) {
has = true; break;
}
}
if (!has) {
list[i].address = str.toUpperCase()
printList.push(list[i]);
}
}
}
return printList;
}, // 1.查找蓝牙设备 E
// 2.连接蓝牙设备并打印 S
connectDevice: function (deviceId) {
var _this = this;
// var item = event.mark.item;
// if (!this.data.sendData64.length) {
// _this.showTip('没有打印数据');
// return
// }
wx.showLoading({
title: '连接中...',
mask: true
})
wx.createBLEConnection({
deviceId,
success(res) {
wx.showLoading({
title: '正在传输...',
mask: true
});
_this.getDeviceService(deviceId);
},
fail(res) {
wx.hideLoading();
console.log('createBLEConnection', res);
_this.showTip('连接失败');
}
})
},
closeBLEConnection: function (deviceId) {
wx.closeBLEConnection({
deviceId: deviceId,
success: function (res) {}
})
},
filterService: function (services) {
const services_uuid1 = "0000EEE0-0000-1000-8000-00805F9B34FB";
const services_uuid2 = "0000FF00-0000-1000-8000-00805F9B34FB";
const services_uuid3 = "49535343-FE7D-4AE5-8FA9-9FAFD205E455";
let serviceId = "";
for (let i = 0; i < services.length; i++) {
let serverid = services[i].uuid.toUpperCase();
if (serverid.indexOf(services_uuid1) != -1 ||
serverid.indexOf(services_uuid2) != -1 ||
serverid.indexOf(services_uuid3) != -1
) {
serviceId = services[i].uuid;
break;
}
}
return serviceId;
},
getDeviceService: function (deviceId) {
var _this = this;
wx.getBLEDeviceServices({
deviceId: deviceId,
success: function (res) {
var serviceId = _this.filterService(res.services);
if (serviceId != "") {
_this.getDeviceCharacter(deviceId, serviceId);
} else {
_this.showTip('没有找到主服务');
_this.closeBLEConnection(deviceId);
}
},
fail: function (res) {
_this.showTip('搜索设备服务失败');
_this.closeBLEConnection(deviceId);
}
})
},
getDeviceCharacter: function (deviceId, serviceId) {
var _this = this;
wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: function (res) {
let writeId = _this.filterCharacter(res.characteristics);
if (writeId != '') {
_this.writeBLECharacteristicValue(deviceId, serviceId, writeId, 1);
} else {
_this.showTip('获取特征值失败');
_this.closeBLEConnection(deviceId);
}
},
fail: function (res) {
_this.showTip('获取特征值失败');
_this.closeBLEConnection(deviceId);
}
})
},
writeBLECharacteristicValue: function (deviceId, serviceId, writeId, times) {
var _this = this;
var sendData64 = _this.data.sendData64;
if (sendData64.length >= times) {
wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: writeId,
value: sendData64[times - 1],
// value: wx.base64ToArrayBuffer(sendData64[times - 1]),
success: function (res) {
_this.writeBLECharacteristicValue(deviceId, serviceId, writeId, ++times);
},
fail: function (res) {
console.log('打印失败', res);
_this.showTip('打印失败');
_this.closeBLEConnection(deviceId);
}
})
} else {
wx.hideLoading();
_this.showTip('传输完成');
_this.triggerEvent('printSuccess')
_this.closeBLEConnection(deviceId);
}
},
filterCharacter: function (characteristics) {
let writeId = '';
for (let i = 0; i < characteristics.length; i++) {
let charc = characteristics[i];
if (charc.properties.write) {
writeId = charc.uuid;
break;
}
}
return writeId;
},
// 打印命令
printCommand: function ({ deviceId, data }) {
var uint8Buf = Array.from(data);
let sendData64 = [];
function splitArray(data, size) {
var result = {};
var j = 0
for (var i = 0; i < data.length; i += size) {
result[j] = data.slice(i, i + size)
let resultMap = [...result[j]]
let buffer = new ArrayBuffer(result[j].length)
let dataView = new DataView(buffer)
for (let k = 0; k < resultMap.length; k++) {
dataView.setUint8(k, resultMap[k]);
}
sendData64[j] = buffer
j++
}
return result
}
splitArray(uint8Buf, 20)
this.setData({
sendData64: sendData64
})
this.connectDevice(deviceId)
}, // 2.连接蓝牙设备 E
showTip: function (data) {
wx.hideLoading();
wx.showModal({
content: data,
showCancel: false
})
},
}
})

View File

@ -0,0 +1,82 @@
const encode = require("./encoding/encoding");
const { splitTextByWidth } = require("../utils/index");
class CPCL {
constructor({ mode = 'CPCL', maxWidth = 540, size = 24 } = {}) {
this.commandMode = mode
this.position = 0
this.maxWidth = maxWidth
this.size = size
}
init() {
this.data = ''
this.command = []
return this
}
addCommand(content) {
const code = new encode.TextEncoder('gb18030', { NONSTANDARD_allowLegacyEncoding: true }).encode(content + '\r\n')
for (let i = 0; i < code.length; ++i) {
this.command.push(code[i])
}
return this
}
setBitmap (x, y, res) {
const w = res.width
const width = parseInt((res.width + 7) / 8 * 8 / 8)
const height = res.height;
const data = `CG ${width} ${height} ${x} ${y}\r\n`
this.addCommand(data)
const r = []
const bits = new Uint8Array(height * width);
for (y = 0; y < height; y++) {
for (x = 0; x < w; x++) {
const color = res.data[(y * w + x) * 4 + 1];
if (color > 128) {
bits[parseInt(y * width + x / 8)] |= (0x80 >> (x % 8));
}
}
}
for (let i = 0; i < bits.length; i++) {
this.command.push((~bits[i]) & 0xFF);
r.push((~bits[i]) & 0xFF);
}
}
pushCode(content) {
content.forEach((el)=>{
this.command.push(el)
})
}
setPagePrint() {
this.addCommand("PRINT")
return this
}
getData() {
return this.command
}
batchAddCommand(content, position, maxWidth) {
const maxW = maxWidth || this.maxWidth
const maps = splitTextByWidth(content, maxW, this.size)
this.position = position
maps.forEach((item, index) => {
this.position = position + index * 30
this.addCommand(`T 6 0 20 ${this.position} ${item}`)
})
return this
}
}
module.exports = {
CPCL
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
let connection = require('./connection')
const { CPCL } = require('./cpcl')
module.exports = {
connection,
CPCL
}

View File

@ -0,0 +1,109 @@
const { CPCL } = require('../bluetooth-v2/index');
/**
* @description 顺丰快递单模版
* @param {Object} config - 模版信息
* @param {string} config.barcode - 条码信息
* @param {string} config.expressNo - 顺丰单号
* @param {string} config.time - 下单时间
* @param {string} config.senderAddress - 发件人地址
* @param {string} config.senderContact - 寄件人联系方式
* @param {string} config.recipientAddress - 收件人地址
* @param {string} config.recipientContact - 收件人联系方式
* @param {string} config.thing - 发货物品
* @param {string} config.qrCode - 二维码信息
* @param {string | number} config.qty - 数量
* @param {string} config.payment - 支付方式
* @param {string} config.expressType - 快递类型
* @param {string} config.orderNo - 订单号
* */
function SF({
barcode = 'SF6399695948',
expressNo = 'SF6399695948',
time = '2023-05-06 14:26:00',
senderAddress = '这是一段很长的文本需要自动换行所以我们使用了TEXT指令的WIDTH参数来指定每行的最大宽度。',
senderContact = '苏先生: 13398410573',
recipientAddress = '北京市海淀区长春桥路',
recipientContact = '刘先生: 15528476654',
thing = '运动营养香橙黑加仑味耐力固体饮料',
qrCode = 'https://www.baidu.com',
qty = 1,
payment = '微信支付',
expressType = '标准快递',
orderNo = '000004999'
} = {}) {
const command = new CPCL()
command.init()
.addCommand(`! 5 200 200 990 1`)
.addCommand(`PAGE - WIDTH 630`)
.addCommand(`BOX 10 10 560 990 2`)
.addCommand(`CENTER`)
// 条纹码打印 非数字无法打印
.addCommand(`BARCODE-TEXT OFF`) // 打印条码,不打印条码文本
.addCommand(`BARCODE 128 2 0 60 0 30 ${barcode}`)
.addCommand(`LEFT`)
.addCommand(`T 6 0 20 100 ${expressNo}`)
.addCommand(`RIGHT`)
.addCommand(`T 6 0 20 100 ${time}`)
.addCommand(`LEFT`)
.addCommand(`LINE 10 130 560 130 2`)
// 寄件方信息
.addCommand(`T 6 0 20 145 寄件方信息: `)
.batchAddCommand(senderAddress, 175)
.addCommand(`T 6 0 20 295 ${senderContact}`)
.addCommand(`LINE 10 330 560 330 2`)
// 收件方信息
.addCommand(`T 6 0 20 345 收件方信息: `)
.batchAddCommand(recipientAddress, 375)
.addCommand(`T 6 0 20 495 ${recipientContact}`)
.addCommand(`LINE 10 530 560 530 2`)
// 产品名称
.batchAddCommand(thing, 545, 305)
.addCommand(`LINE 10 640 310 640 2`)
// 二维码
.addCommand(`LEFT`)
.addCommand(`B QR 335 560 M 2 U 8`)
.addCommand(`MA,${qrCode}`)
.addCommand(`ENDQR`)
.addCommand(`LINE 310 530 310 940 2`)
.addCommand(`T 6 0 20 655 `)
.addCommand(`LINE 10 700 310 700 2`)
.addCommand(`LINE 150 700 150 880 2`)
// 件数
.addCommand(`T 6 0 20 715 件数`)
.addCommand(`T 6 0 165 715 ${qty}`)
.addCommand(`LINE 10 760 310 760 2`)
// 付款方式
.addCommand(`T 6 0 20 775 付款方式`)
.addCommand(`T 6 0 165 775 ${payment}`)
.addCommand(`LINE 10 820 310 820 2`)
// 快递类型
.addCommand(`T 6 0 20 835 类型`)
.addCommand(`T 6 0 165 835 ${expressType}`)
.addCommand(`LINE 10 880 310 880 2`)
// 订单号
.addCommand(`T 6 0 20 895 订单号:`)
.addCommand(`T 6 0 130 895 ${orderNo}`)
.addCommand(`LINE 10 940 560 940 2`)
.addCommand(`FORM`)
.setPagePrint()
return command
}
module.exports = {
SF
}

View File

@ -0,0 +1,29 @@
function splitTextByWidth(text, maxWidth, charWidth) {
let result = [];
let start = 0;
let lineWidth = 0;
while (start < text.length) {
let end = start + 1;
while (end <= text.length) {
let char = text.charAt(end - 1);
let charWidthPx = charWidth * (char === ' ' ? 1.5 : 1); // 空格需要加上额外宽度
if (lineWidth + charWidthPx > maxWidth) {
result.push(text.slice(start, end - 1)); // 将上一个子字符串保存到结果中
start = end - 1; // 更新起始位置
lineWidth = 0;
break;
}
lineWidth += charWidthPx;
end++;
}
if (end > text.length) {
result.push(text.slice(start)); // 将最后一个子字符串保存到结果中
break;
}
}
return result;
}
module.exports = {
splitTextByWidth,
}

BIN
src/images/icon-YTO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
src/images/yt-logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
src/pages/.DS_Store vendored Normal file

Binary file not shown.

122
src/pages/print/index.js Normal file
View File

@ -0,0 +1,122 @@
// pages/print/index.js
const { connection, CPCL } = require('../../cpcl_print/bluetooth-v2/index')
const { SF } = require('../../cpcl_print/template/shunfeng')
Page({
behaviors: [connection],
data: {
printList: [
// {
// RSSI: -42,
// address: "0000C47F0E301D33",
// connectable: true,
// deviceId: "828B78AB-57B8-72EE-F428-B74BBD7A11B2",
// localName: "HM-A300-1d33",
// name: "HM-A300-1d33",
// },
],
canvasWidth: 0,
canvasHeight: 0,
},
onLoad() {
this.start()
this.loadCanvasImage()
},
onClickConnectDevice(event) {
console.log(event.mark.deviceId);
this.printTemplate(event.mark.deviceId)
},
printTemplate(deviceId) {
const command = SF()
this.printCommand({
deviceId: deviceId,
data: command.getData()
})
},
loadCanvasImage() {
const ctx = wx.createCanvasContext('secondCanvas');
const that = this
let tempFilePath = '/images/shunfeng.png'
wx.getImageInfo({
src: tempFilePath,
success (res) {
console.log(res);
let paperWidth = 240;
// 打印宽度须是8的整数倍这里处理掉多余的使得宽度合适不然有可能乱码
const mw = paperWidth % 8;
const w = mw === 0 ? paperWidth : paperWidth - mw;
// 等比算出图片的高度∂
const h = Math.floor((res.height * w) / res.width);
// 设置canvas宽高
that.setData({
img: tempFilePath,
canvasHeight: h,
canvasWidth: w,
});
// 在canvas 画一张图片
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.clearRect(0, 0, w, h);
ctx.fillRect(0, 0, w, h);
ctx.drawImage(tempFilePath, 0, 0, w, h);
ctx.draw(false, () => {
wx.hideLoading();
});
},
fail: (res) => {
console.log('get info fail', res);
},
})
},
getCanvasImage(event) {
const { canvasWidth, canvasHeight } = this.data
wx.canvasGetImageData({
canvasId: 'secondCanvas',
x: 0,
y: 0,
width: canvasWidth,
height: canvasHeight,
success: (res) => {
const command = new CPCL()
command.init()
command.addCommand(`! 5 200 200 500 1`)
command.addCommand(`PAGE - WIDTH 630`)
command.addCommand(`T 6 4 3 100 =======图片打印示例=======`)
command.setBitmap(15, 205, res)
command.setPagePrint()
this.printCommand({
deviceId: event.mark.deviceId,
data: command.getData()
})
},
fail(err) {
console.log(err);
}
});
},
printCacheImage(event) {
// yzdImage的数据可参考setBitmap的console.log
const yzdImage = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,240,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,252,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,15,248,15,255,255,254,0,1,128,0,0,0,60,97,143,224,252,3,193,248,63,128,31,128,31,7,255,255,0,6,0,0,0,0,252,115,143,241,255,15,199,252,255,128,62,0,60,1,255,255,128,12,0,0,0,1,128,119,28,49,135,24,14,0,192,0,248,0,120,0,127,255,192,56,0,0,0,3,0,62,24,51,135,48,12,1,192,1,224,0,32,0,31,255,224,112,0,0,0,7,248,60,24,115,142,127,143,225,254,3,128,0,0,0,15,255,225,192,0,0,0,7,252,56,31,227,252,127,199,248,255,7,0,0,0,0,3,255,195,128,0,0,0,7,0,124,63,195,28,112,0,56,7,14,0,0,0,0,1,255,143,0,0,0,0,7,0,236,48,7,12,112,0,56,7,24,0,0,0,0,0,254,30,0,0,0,0,7,249,206,48,6,12,127,31,241,254,16,0,0,0,0,0,124,124,0,0,0,0,3,251,134,48,6,12,63,31,227,252,32,0,0,0,0,0,48,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,35,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,0,0,0,0,0,0,0,0,0,0,48,16,0,15,128,15,128,0,0,0,127,255,0,0,0,0,15,255,255,255,128,0,60,120,0,15,128,15,128,0,0,0,255,255,0,0,0,0,15,255,255,255,128,192,120,120,0,15,128,31,128,0,0,3,255,255,128,0,0,0,15,255,255,255,129,224,120,124,0,15,128,31,128,0,0,7,231,255,128,0,0,0,31,0,0,15,131,224,248,124,0,31,128,31,128,0,0,31,199,255,128,0,0,0,30,31,255,15,3,224,255,255,224,31,128,31,0,0,0,63,131,255,128,0,0,0,30,63,255,143,3,193,255,255,224,0,31,255,255,0,0,254,3,255,128,0,0,0,30,63,255,143,0,3,255,255,224,60,63,255,255,128,1,252,3,255,128,0,0,0,30,60,7,143,0,3,255,255,224,126,63,255,255,0,7,248,3,255,128,0,0,0,30,60,7,143,0,7,224,240,0,127,0,63,0,0,31,240,3,255,128,0,0,0,62,127,255,159,0,15,224,240,0,63,0,63,0,0,63,192,3,255,128,0,0,0,62,127,255,30,3,223,224,240,0,63,0,31,128,0,255,128,3,255,128,0,0,0,60,0,0,30,3,255,255,255,192,62,0,15,192,1,255,0,3,255,128,0,0,0,60,127,255,30,7,223,255,255,192,62,0,239,192,7,252,24,3,255,128,0,0,0,60,255,255,158,7,223,255,255,192,62,0,231,224,15,248,30,3,255,128,0,0,0,60,255,255,190,7,139,255,255,128,62,1,243,240,63,240,31,135,255,0,0,0,0,61,224,7,188,7,131,193,224,0,62,3,241,248,127,224,7,199,255,0,0,0,0,121,227,199,60,7,131,193,224,0,126,7,225,253,255,128,3,247,255,0,0,0,0,121,227,143,60,7,131,193,224,0,126,15,192,255,255,0,0,255,254,0,0,0,0,121,231,143,60,7,131,255,255,128,124,31,192,127,254,0,0,127,254,0,0,0,0,120,15,240,60,15,7,255,255,128,124,63,128,63,252,0,0,31,254,0,0,0,0,120,63,252,60,15,7,255,255,128,124,63,0,63,240,0,0,31,252,0,0,0,0,251,255,255,124,15,7,129,224,0,252,126,0,31,224,0,0,63,252,0,0,0,0,251,248,63,120,15,7,129,224,0,252,252,0,15,192,0,0,63,248,0,0,0,0,243,224,14,120,15,7,131,192,1,253,252,0,7,128,0,0,127,240,0,0,0,0,240,0,0,120,15,7,255,255,129,255,248,0,6,0,0,0,255,240,0,0,0,0,255,255,255,248,30,15,255,255,131,254,0,0,0,0,0,0,255,224,0,0,0,0,255,255,255,248,30,15,255,255,131,207,255,255,248,0,0,1,255,192,0,0,0,0,255,255,255,240,30,15,255,255,131,135,255,255,252,0,0,3,255,128,0,0,0,1,240,0,0,240,30,15,0,0,3,7,255,255,248,0,0,7,255,128,0,0,0,1,224,0,0,240,0,0,0,0,0,0,0,0,0,0,0,15,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,112,0,3,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,112,0,15,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,112,0,63,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,224,112,0,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,48,15,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
const command = new CPCL()
command.init()
command.addCommand(`! 5 200 200 500 1`)
command.addCommand('CG 22 69 5 35')
command.pushCode(yzdImage)
// command.addCommand('FORM')
command.setPagePrint()
this.printCommand({
deviceId: event.mark.deviceId,
data: command.getData()
})
}
})

View File

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@ -0,0 +1,55 @@
.print-device {
padding: 40rpx 32rpx;
.print-device__hd {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
font-family: PingFang SC;
font-weight: 400;
line-height: 44rpx;
color: #333;
margin-bottom: 20rpx;
}
.print-device__hd__desc {
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 700;
line-height: 40rpx;
color: #1A83F9;
}
}
.device-row {
display: flex;
align-items: center;
border-radius: 8rpx;
border: 1px solid #C7C6C6;
padding: 20rpx;
margin-bottom: 20rpx;
.device-row__bd {
flex: 1;
font-size: 36rpx;
font-family: PingFang SC;
font-weight: 700;
line-height: 50rpx;
color: #333;
word-wrap: break-word;
word-break: break-all;
}
.device-row__ft {
flex-shrink: 0;
}
}
.print-button {
background: #F8C82D;
line-height: 80rpx;
border-radius: 8rpx;
font-size: 32rpx;
text-align: center;
padding: 0 20rpx;
& + & {
margin-top: 10rpx;
}
}

View File

@ -0,0 +1,30 @@
<!--pages/print/index.wxml-->
<view class="print-device">
<view class="print-device__hd">
<view class="print-device__hd__title">选择打印设备</view>
<view bindtap="research" class="print-device__hd__desc">重新搜索</view>
</view>
<view wx:for="{{ printList }}" wx:key="deviceId" class="device-row">
<view class="device-row__bd">{{ item.name || '未知设备' }}</view>
<view class="device-row__ft">
<view style="font-size: 28rpx;">
<view bindtap="onClickConnectDevice" mark:deviceId="{{ item.deviceId }}" class="print-button" >打印示例</view>
<view bindtap="getCanvasImage" mark:deviceId="{{ item.deviceId }}" class="print-button" >打印画布图片</view>
<view bindtap="printCacheImage" mark:deviceId="{{ item.deviceId }}" class="print-button" >打印图片</view>
</view>
</view>
</view>
<view wx:if="{{ printList.length == 0 }}" style="font-size: 28rpx;color:#333;" class="">
未找到打印机
</view>
</view>
<view class="print-device">
<view class="print-device__hd">
<view class="print-device__hd__title">画布图片容器</view>
</view>
<view class="print-device__bd">
<canvas style="width: {{ canvasWidth }}px; height: {{ canvasHeight }}px" canvas-id="secondCanvas"></canvas>
</view>
</view>

51
src/project.config.json Normal file
View File

@ -0,0 +1,51 @@
{
"description": "项目配置文件",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"bundle": false,
"userConfirmedBundleSwitch": false,
"urlCheck": true,
"scopeDataCheck": false,
"coverView": true,
"es6": true,
"postcss": true,
"compileHotReLoad": false,
"preloadBackgroundData": false,
"minified": true,
"autoAudits": false,
"newFeature": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"useIsolateContext": true,
"nodeModules": false,
"enhance": false,
"useCompilerModule": true,
"userConfirmedUseCompilerModuleSwitch": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"showShadowRootInWxmlPanel": true,
"packNpmManually": false,
"enableEngineNative": false,
"packNpmRelationList": [],
"minifyWXSS": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"condition": false
},
"compileType": "miniprogram",
"libVersion": "2.31.0",
"appid": "wx463c21c58bdf2d7f",
"projectname": "cpcl-bluetooth-print",
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
}
}

7
src/sitemap.json Normal file
View File

@ -0,0 +1,7 @@
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}

19
src/utils/util.js Normal file
View File

@ -0,0 +1,19 @@
const formatTime = date => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
}
const formatNumber = n => {
n = n.toString()
return n[1] ? n : `0${n}`
}
module.exports = {
formatTime
}