這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
一、小票列印
目前市面上的小票列印機大多采用的列印指令集為ESC/POS指令,它可以使用ASCII碼、十進制、十六進制來控制列印,我們可以使用它來控制字體大小、列印排版、字體加粗、下劃線、走紙、切紙、控制錢箱等,下面以初始化列印機為例:
ASCII碼 ESC @ 十進制碼 27 64 十六進制 1B 40
小票列印紙的寬度一般可分58mm和80mm,這里指的是列印紙的寬度,但是在實際列印的時候,有效列印區域并沒有這么寬,
列印機紙寬58mm,頁的寬度384,字符寬度為1,每行最多盛放32個字符 列印機紙寬80mm,頁的寬度576,字符寬度為1,每行最多盛放48個字符
上面說的字符指的是列印到小票上的內容,其中數字和字母占1個字符,中文占2個字符,也就是說,如果使用58mm的列印紙,一行最多可以列印16個漢字或者32個數字 ,
當然這是在不改變字體大小的情況下,如果我們改變了字體大小,那么一行盛放的內容也會改變,
//控制字符大小 ASCII碼 GS ! n 十進制碼 29 33 n 十六進制 1D 21 n
1.這里的n是一個變數, 0 ≤ n ≤ 255
2.用二進制表示,n的取值范圍就是00000000到11111111,其中二進制的前四位用來控制寬度,后四位用來控制高度,0000表示不變,0001表示放大2倍,0002表示放大3倍,以此類推
3.該命令對所有字符(英數字符和漢字) 有效,
4.預設值:n = 0
下面我們來看一下字符的不同放大倍數(這里的1倍,表示使用默認大小):
| 放大倍數 | n(二進制) | n(十進制) |
|---|---|---|
| 寬度1倍,高度1倍 | 00000000 | 0 |
| 寬度1倍,高度2倍 | 00000001 | 1 |
| 寬度1倍,高度3倍 | 00000002 | 2 |
| 寬度2倍,高度1倍 | 00010000 | 16 |
| 寬度2倍,高度2倍 | 00010001 | 17 |
| 寬度2倍,高度3倍 | 00010002 | 18 |
| 寬度3倍,高度1倍 | 00020000 | 32 |
| 寬度3倍,高度2倍 | 00020001 | 33 |
| 寬度3倍,高度3倍 | 00020002 | 34 |

PS:列印紙時間有些長,字跡有些模糊,見諒
列印指令封裝
// 列印機紙寬58mm,頁的寬度384,字符寬度為1,每行最多盛放32個字符
// 列印機紙寬80mm,頁的寬度576,字符寬度為1,每行最多盛放48個字符
const PAGE_WIDTH = 576;
const MAX_CHAR_COUNT_EACH_LINE = 48;
//字串轉位元組序列
function stringToByte(str) {
var bytes = new Array();
var len, c;
len = str.length;
for (var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if (c >= 0x010000 && c <= 0x10FFFF) {
bytes.push(((c >> 18) & 0x07) | 0xF0);
bytes.push(((c >> 12) & 0x3F) | 0x80);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if (c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(((c >> 12) & 0x0F) | 0xE0);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if (c >= 0x000080 && c <= 0x0007FF) {
bytes.push(((c >> 6) & 0x1F) | 0xC0);
bytes.push((c & 0x3F) | 0x80);
} else {
bytes.push(c & 0xFF);
}
}
return bytes;
}
//位元組序列轉ASCII碼
//[0x24, 0x26, 0x28, 0x2A] ==> "$&C*"
function byteToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
//居中
function Center() {
var Center = [];
Center.push(27);
Center.push(97);
Center.push(1);
var strCenter = byteToString(Center);
return strCenter;
}
//居左
function Left() {
var Left = [];
Left.push(27);
Left.push(97);
Left.push(0);
var strLeft = byteToString(Left);
return strLeft;
}
//居右
function Right() {
var right = [];
Left.push(27);
Left.push(97);
Left.push(2);
var strRight = byteToString(right);
return strRight;
}
//標準字體
function Size1() {
var Size1 = [];
Size1.push(29);
Size1.push(33);
Size1.push(0);
var strSize1 = byteToString(Size1);
return strSize1;
}
//大號字體
/* 放大1倍 n = 0
* 長寬各放大2倍 n = 17 */
function Size2(n) {
var Size2 = [];
Size2.push(29);
Size2.push(33);
Size2.push(n);
var strSize2 = byteToString(Size2);
return strSize2;
}
// 字體加粗
function boldFontOn() {
var arr = []
arr.push(27)
arr.push(69)
arr.push(1)
var cmd = byteToString(arr);
return cmd
}
// 取消字體加粗
function boldFontOff() {
var arr = []
arr.push(27)
arr.push(69)
arr.push(0)
var cmd = byteToString(arr);
return cmd
}
// 列印并走紙n行
function feedLines(n = 1) {
var feeds = []
feeds.push(27)
feeds.push(100)
feeds.push(n)
var printFeedsLines = byteToString(feeds);
return printFeedsLines
}
// 切紙
function cutPaper() {
var cut = []
cut.push(29)
cut.push(86)
cut.push(49)
var cutType = byteToString(cut);
return cutType
}
// 開錢箱
function open_money_box() {
var open = []
open.push(27)
open.push(112)
open.push(0)
open.push(60)
open.push(255)
var openType = byteToString(open)
return openType
}
// 初始化列印機
function init() {
var arr = []
arr.push(27)
arr.push(68)
arr.push(0)
var str = byteToString(arr)
return str
}
/*
設定左邊距
len:
*/
function setLeftMargin(len = 1) {
var arr = []
arr.push(29)
arr.push(76)
arr.push(len)
var str = byteToString(arr)
return str
}
// 設定列印區域寬度
function setPrintAreaWidth(width) {
var arr = []
arr.push(29)
arr.push(87)
arr.push(width)
var str = byteToString(arr)
return str
}
/**
* @param str
* @returns {boolean} str是否全是中文
*/
function isChinese(str) {
return /^[\u4e00-\u9fa5]$/.test(str);
}
// str是否全含中文或者中文標點
function isHaveChina(str) {
if (escape(str).indexOf("%u") < 0) {
return 0
} else {
return 1
}
}
/**
* 回傳字串寬度(1個中文=2個英文字符)
* @param str
* @returns {number}
*/
function getStringWidth(str) {
let width = 0;
for (let i = 0, len = str.length; i < len; i++) {
width += isHaveChina(str.charAt(i)) ? 2 : 1;
}
return width;
}
/**
* 同一行輸出str1, str2,str1居左, str2居右
* @param {string} str1 內容1
* @param {string} str2 內容2
* @param {string} fillWith str1 str2之間的填充字符
* @param {number} fontWidth 字符寬度 1/2
*
*/
function inline(str1, str2, fillWith = ' ', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
// 需要填充的字符數量
let fillCount = lineWidth - (getStringWidth(str1) + getStringWidth(str2)) % lineWidth;
let fillStr = new Array(fillCount).fill(fillWith.charAt(0)).join('');
return str1 + fillStr + str2;
}
/**
* 用字符填充一整行
* @param {string} fillWith 填充字符
* @param {number} fontWidth 字符寬度 1/2
*/
function fillLine(fillWith = '-', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
return new Array(lineWidth).fill(fillWith.charAt(0)).join('');
}
/**
* 文字內容居中,左右用字符填充
* @param {string} str 文字內容
* @param {number} fontWidth 字符寬度 1/2
* @param {string} fillWith str1 str2之間的填充字符
*/
function fillAround(str, fillWith = '-', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
let strWidth = getStringWidth(str);
// 內容已經超過一行了,沒必要填充
if (strWidth >= lineWidth) {
return str;
}
// 需要填充的字符數量
let fillCount = lineWidth - strWidth;
// 左側填充的字符數量
let leftCount = Math.round(fillCount / 2);
// 兩側的填充字符,需要考慮左邊需要填充,右邊不需要填充的情況
let fillStr = new Array(leftCount).fill(fillWith.charAt(0)).join('');
return fillStr + str + fillStr.substr(0, fillCount - leftCount);
}
也就是說,如果我們使用的列印機采用的是ESC/POS指令集(我這里使用過佳博、芯燁、斯普瑞特列印機),只要我們想辦法把列印指令發送給列印機,列印機就可以識別到并且進行列印等操作,那么我們該如何發送呢?
1.藍牙列印機
參考掘金 zgt_不夢的文章 微信小程式連接藍牙列印機列印圖片示例
- 初始化藍牙模塊 wx.openBluetoothAdapter()
- 初始化完成后搜尋附近的藍牙設備 wx.startBluetoothDevicesDiscovery()
- 監聽尋找到新設備的事件 wx.onBluetoothDeviceFound()
- 在監聽尋找到新設備的事件回呼中獲取所有藍牙設備串列 wx.getBluetoothDevices()
- 連接低功耗藍牙設備 wx.createBLEConnection()
- 連接成功后獲取藍牙設備服務 wx.getBLEDeviceServices()
- 在服務中取(notify=true || indicate=true) && write=true 的特征值的 uuid: wx.getBLEDeviceCharacteristics()
- 完成后停止搜尋 wx.stopBluetoothDevicesDiscovery()
- 向低功耗藍牙設備特征值中寫入二進制資料 wx.writeBLECharacteristicValue()
- 離開頁面時取消藍牙連接 wx.closeBLEConnection()
- 關閉藍牙模塊 wx.closeBluetoothAdapter()
親測,好使!在uniapp也可以,只需替換對應的API即可
2.網口列印機
這里我使用的scoket連接,相比于USB列印,這里需要保證列印機和安卓設備在同一局域網下,好處是安卓設備可以和列印機距離較遠(比如廚房列印),這里以斯普瑞特列印機為例:[斯普瑞特官網 www.sprinter.com.cn/在進行資料通信之前,我們需要知道列印機在此局域網下的 IP, 下圖為“一鍵配網”工具
通過這個工具我們可以方便快捷的查詢到列印機的IP,或者可以根據空閑的網段來修改默認分配的IP,斯普瑞特POS列印機的埠是9100,
如果是其他品牌的列印機,我們也可以使用arp命令來查看當前局域網下的IP
拿到列印機的IP之后我們怎么來測驗一下列印機呢?
我們可以使用telnet命令(這個在Windows系統一般默認是關閉的,需要我們手動打開)
//telnet + 空格 + ip + 空格 + 埠號 telnet 192.168.5.6 9100
打開命令列視窗輸入telnet命令,按下回車
如果埠關倍訓者無法連接,則顯示不能打開到主機的鏈接,鏈接失敗;埠打開的情況下,鏈接成功,則進入telnet頁面(全黑的),證明埠可用,
連接成功后,我們輸入任何內容后,按下回車,列印機就會列印我們剛才輸入的內容,
接下來我們要使用scoket來連接安卓設備和列印機,這里我使用的是uniapp
/**
* 呼叫tcp通信進行列印
* @param {buffer} buffer 列印資料
* @param {object} printerInfo 列印機物件{IP:'',PORT:''}
*/
function tcpWrite(buffer, printerInfo) {
var Socket = plus.android.importClass("java.net.Socket");
var PrintWriter = plus.android.importClass("java.io.PrintWriter");
var BufferedWriter = plus.android.importClass("java.io.BufferedWriter");
var OutputStreamWriter = plus.android.importClass("java.io.OutputStreamWriter");
var BufferedReader = plus.android.importClass("java.io.BufferedReader");
var InputStreamReader = plus.android.importClass("java.io.InputStreamReader");
var InetSocketAddress = plus.android.importClass("java.net.InetSocketAddress");
//連接 注意:這里的埠一定是數字型別
var sk = null
try {
sk = new Socket(printerInfo.IP, Number(printerInfo.PORT));
sk.setSoTimeout(5000);
} catch (e) {
console.log(e, 'ee')
uni.showToast({
icon: 'none',
title: '列印機連接失敗'
})
}
//發送
try {
var outputStreamWriter = new OutputStreamWriter(sk.getOutputStream(), "GBK");
var bufferWriter = new BufferedWriter(outputStreamWriter);
var out = new PrintWriter(bufferWriter, true);
out.println(buffer);
//關閉tcp連接
out.close();
} catch (e) {
console.log(e, 'ee')
uni.showToast({
icon: 'none',
title: '列印機資料傳輸失敗'
})
}
}
列印小票
目前我們已經可以開心的使用列印功能了,只需要組合一下列印指令即可,這里需要注意的是,如果我們在此之前設定了字符大小寬高均放大2倍,那么后面列印的字符都會被放大,所以如果后面我們想使用默認字符大小,我們還需要再次設定字符大小為默認來覆寫之前的指令
//這里的EscPosUtil.js就是上面封裝的列印指令
import Esc from './EscPosUtil.js';
// 列印文字格式
let strCenter = Esc.Center(); //文字居中
let strLeft = Esc.Left(); //文字靠左
let strSize1 = Esc.Size1(); //默認文字
let strSize2 = Esc.Size2(17); //文字放大兩倍(長寬均為兩倍)
let printerInfo = {
IP:'192.168.5.6',
PORT: 9100
}
let strCmd = strCenter + Esc.Size2(17) + Esc.boldFontOn() + '測驗門店'+ "\n";
strCmd += strSize1 + Esc.fillLine(' ') + "\n"
strCmd += strCenter + Esc.Size2(17) + Esc.boldFontOn() + '結賬單-堂食' + "\n";
strCmd += strSize1 + Esc.fillLine(' ') + "\n"
strCmd += strLeft + Esc.Size2(17) + "取餐號:" + '62' + "\n";
strCmd += Esc.inline('桌號:' + '牡丹廳', '人數:' + '6', ' ', 2) + "\n"
strCmd += Esc.boldFontOff() + strSize1 + Esc.fillLine(' ') + "\n"
strCmd += strLeft + strSize1 + "訂單號:" + '202305171749110001' + "\n";
// 商品資訊
strCmd += Esc.fillAround('商品') + "\n"
// 票尾
strCmd += Esc.fillLine(' ') + "\n"
strCmd += strCenter + '歡迎下次光臨!' + "\n";
strCmd += Esc.feedLines(4) + "\n"
// 切紙
strCmd += Esc.cutPaper()
tcpWrite(strCmd, printerInfo)
列印效果(這里僅為展示,非上述代碼列印)
3.USB列印機
這里我使用的是uniapp插件市場的插件,如果你了解安卓原生開發,你也可以自己制作一個原生插件,或者使用Native.js開發,使用原生插件在本地除錯需要先打包“自定義除錯基座”,在本地測驗后再打正式包,
uni-app基于nativejs實作USB-OTG通訊 - 簡書1,監聽USB拔出連接,判斷是否含有權限 2,獲取權限后,打開設備實作連接 3,讀寫發送接受資料https://www.jianshu.com/p/7c308ffcd789
uni-app官網uni-app,uniCloud,serverlesshttps://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6
Android USB介面熱敏小票列印機插件usbPrinter - DCloud 插件市場本插件提供安卓手機通過USB介面連接熱敏小票列印機進行列印的相關功能,通過USB連接相比使用藍牙連接更穩定,https://ext.dcloud.net.cn/plugin?id=7757
在使用USB插件后,我們可以監聽USB設備的插入和拔出,在初始化之后,我們可以進行資料通信,將上面封裝的列印指令傳給列印機即可
二、網頁列印
由于是網頁運行在瀏覽器中,所以我們只能使用瀏覽器給我們提供的API
1.windows.print()
這個API在不同的瀏覽器中會有差異,其作用就是可以把網頁中的body元素列印出來,如果我們不想列印整個body元素,則需要將body的innerHTML替換,使用這種方式有時有些頁面樣式會和列印出來的不一樣,那么我們就要使用其他方式來優化,
//使用方法 document.body.innerHTML = newstr; // 把需要列印的指定內容賦給body window.print();
1.1使用媒體查詢
@media print {
//把需要列印時才用到的樣式寫到這里
p{
font-size:16px;
}
}
同理,你也可以直接在CSS檔案或者style標簽中加上 media="print"<style media="print"> //CSS代碼 </style>
1.2監聽列印事件
//監聽列印之前的事件
window.onbeforeprint = function() {
//可以修改元素樣式
}
//監聽列印之后的事件
window.onafterprint = function() {
//恢復之前的樣式
}
1.3分頁符
1.3.1 page-break-before 指定元素前插入分頁符
1.3.2 page-break-after 指定元素后插入分頁符
| 值 | 描述 |
|---|---|
| auto | 默認,如果必要則在元素后插入分頁符, |
| always | 在元素后插入分頁符, |
| avoid | 避免在元素后插入分頁符, |
| left | 在元素之后足夠的分頁符,一直到一張空白的左頁為止, |
| right | 在元素之后足夠的分頁符,一直到一張空白的右頁為止, |
| inherit | 規定應該從父元素繼承 page-break-after 屬性的設定, |
1. 您不能對絕對定位的元素使用此屬性,
2. 請盡可能少地使用分頁屬性,并且避免在表格、浮動元素、帶有邊框的塊元素中使用分頁屬性,
3. 任何版本的Internet Explorer(包括IE8)支持屬性值"left","right",和"inherit",
4. Firefox,Chrome和Safari不支持屬性值"avoid","left"和"right",.
@media print {
footer {page-break-after: always;}
}
1.3.3 page-break-inside 設定是否在指定元素中插入分頁符
| 值 | 描述 |
|---|---|
| auto | 默認,如果必要則在元素內部插入分頁符, |
| avoid | 避免在元素內部插入分頁符, |
| inherit | 規定應該從父元素繼承 page-break-inside 屬性的設定, |
- 您不能對絕對定位的元素使用此屬性,
- 請盡可能少地使用分頁屬性,并且避免在表格、浮動元素、帶有邊框的塊元素中使用分頁屬性,
- IE8 及更早IE版本不支持 "inherit" 屬性,
- Firefox, Chrome, 以及 Safari 不支持屬性值 "avoid".
//避免在 <pre> 與 <blockquote> 元素中插入分頁符:
@media print {
pre, blockquote {page-break-inside: avoid;}
}
1.4設定紙張
@page: 用來設定頁面大小、邊距、方向等
//portrait:縱向; landscape: 橫向
@page {
size: A4 portrait; //設定紙張及其方向 這里表示使用A4紙張,列印方向為縱向
margin: 3.7cm 2.6cm 3.5cm; //設定紙張外邊距
}
// 去除頁眉
@page { margin-top: 0; }
// 去除頁腳
@page { margin-bottom: 0; }
值得注意的是,如果我們使用的列印機是黑白列印的,比如針式列印機,那么我們使用的顏色最好是 #000,如果使用 #999這種灰色,列印效果會很不清晰
本文轉載于:
https://juejin.cn/post/7237316724739457061
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/553546.html
標籤:其他
上一篇:深入理解 apply()方法
下一篇:返回列表

