CPCL指令集微信蓝牙打印
This commit is contained in:
commit
0bdd14e0da
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# Node.js
|
||||
node_modules/
|
||||
package-lock.json
|
||||
.idea
|
||||
|
||||
# 测试
|
||||
/tests/
|
||||
|
||||
# miniprogram
|
||||
sourcemap.zip
|
||||
miniprogram_npm
|
21
LICENSE
Normal file
21
LICENSE
Normal 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
18
README.md
Normal 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
82
project.config.json
Normal 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"
|
||||
}
|
26
project.private.config.json
Normal file
26
project.private.config.json
Normal 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
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
3
src/app.js
Normal file
3
src/app.js
Normal file
@ -0,0 +1,3 @@
|
||||
App({
|
||||
onLaunch() {},
|
||||
})
|
19
src/app.json
Normal file
19
src/app.json
Normal 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
1
src/app.less
Normal file
@ -0,0 +1 @@
|
||||
/** app.less **/
|
281
src/cpcl_print/bluetooth-v2/connection.js
Normal file
281
src/cpcl_print/bluetooth-v2/connection.js
Normal 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
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
82
src/cpcl_print/bluetooth-v2/cpcl.js
Normal file
82
src/cpcl_print/bluetooth-v2/cpcl.js
Normal 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
|
||||
}
|
47
src/cpcl_print/bluetooth-v2/encoding/encoding-indexes.js
Normal file
47
src/cpcl_print/bluetooth-v2/encoding/encoding-indexes.js
Normal file
File diff suppressed because one or more lines are too long
3312
src/cpcl_print/bluetooth-v2/encoding/encoding.js
Normal file
3312
src/cpcl_print/bluetooth-v2/encoding/encoding.js
Normal file
File diff suppressed because it is too large
Load Diff
7
src/cpcl_print/bluetooth-v2/index.js
Normal file
7
src/cpcl_print/bluetooth-v2/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
let connection = require('./connection')
|
||||
const { CPCL } = require('./cpcl')
|
||||
|
||||
module.exports = {
|
||||
connection,
|
||||
CPCL
|
||||
}
|
109
src/cpcl_print/template/shunfeng.js
Normal file
109
src/cpcl_print/template/shunfeng.js
Normal 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
|
||||
}
|
29
src/cpcl_print/utils/index.js
Normal file
29
src/cpcl_print/utils/index.js
Normal 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
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
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
BIN
src/pages/.DS_Store
vendored
Normal file
Binary file not shown.
122
src/pages/print/index.js
Normal file
122
src/pages/print/index.js
Normal 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()
|
||||
})
|
||||
}
|
||||
|
||||
})
|
3
src/pages/print/index.json
Normal file
3
src/pages/print/index.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
55
src/pages/print/index.less
Normal file
55
src/pages/print/index.less
Normal 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;
|
||||
}
|
||||
}
|
30
src/pages/print/index.wxml
Normal file
30
src/pages/print/index.wxml
Normal 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
51
src/project.config.json
Normal 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
7
src/sitemap.json
Normal 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
19
src/utils/util.js
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user