フロントエンドレシート印刷、Webページ印刷(ユニアプリ、アプレット、ESC/POSコマンド)

プロジェクト中にあらゆる種類の印刷に遭遇したため、遭遇した印刷のニーズとソリューションをまとめた記事を書きたいと思いました。一般に、私が遭遇した印刷要件は、Web ページの印刷とレシートの印刷の 2 つのカテゴリに分類できます。

目次

 1.レシート印刷

 印刷指示パッケージ

1.Bluetoothプリンター

2. イーサネットプリンター

領収書を印刷する

印刷効果(ここでは表示のみであり、上記のコードによる印刷ではありません)

3.USBプリンター

 2. Webページの印刷

1.windows.print()

1.1 メディアクエリの使用

1.2 印刷イベントのリッスン

1.3 改ページ

1.4 用紙のセット


 

 1.レシート印刷

現在、市販されている小型チケットプリンタの多くは、ESC/POS命令として設定された印刷命令を使用しており、ASCIIコード、10進数、16進数で印刷を制御することができ、フォントサイズ、活字組版、文字サイズなどの制御が可能です。太字、下線、紙送り、紙カット、キャッシュドロワー制御など。以下はプリンターの初期化を例にします。

ASCII码  ESC  @
十进制码  27  64
十六进制  1B  40

小切符の印刷用紙幅は一般的に58mmと80mmに分けられ、これは印刷用紙の幅を指しますが、実際の印刷では有効印刷領域はそれほど広くありません。

 打印机纸宽58mm,页的宽度384,字符宽度为1,每行最多盛放32个字符
 打印机纸宽80mm,页的宽度576,字符宽度为1,每行最多盛放48个字符

上記の文字とは、レシートに印刷される内容のことで、数字と文字は1文字、漢字は2文字となりますので、58mmの印画紙を使用した場合、最大16文字まで印刷できます。 1 行あたり 32 文字。数字。

もちろんフォントサイズは変えずに表示していますが、フォントサイズを変更すると行の内容も変わります。

//控制字符大小

ASCII码   GS  !   n
十进制码  29  33  n
十六进制  1D  21  n

1. ここで、n は変数、0 ≤ n ≤ 255 

2. バイナリで表現すると、n の値の範囲は 00000000 ~ 11111111 です。バイナリの最初の 4 ビットは幅の制御に使用され、最後の 4 ビットは高さの制御に使用されます。0000 は変更しないことを意味し0001 は2 倍に拡大することを意味し0002 は3 倍に拡大することを意味します。

3. 本コマンドは全ての文字(英数字、漢字)に対して有効です。

4. デフォルト値: n = 0

 文字のさまざまな倍率を見てみましょう (ここでの 1 倍は、デフォルトのサイズを使用することを意味します)。

n (バイナリ) n(10進数)
幅×1、高さ×1 0000 0000 0
幅1倍、高さ2倍 0000 0001 1
幅1倍、高さ3倍 0000 0002 2
幅2倍、高さ1倍 0001 0000 16
幅2倍、高さ2倍 00010001 17
幅2倍、高さ3倍 0001 0002 18
幅3倍、高さ1倍 0002 0000 32
幅は3倍、高さは2倍 0002 0001 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 命令セットを使用している場合 (ここでは Jiabo、Xinye、および Spirit プリンターを使用しました)、プリンターに印刷コマンドを送信する方法が見つかっていれば、プリンターはそれを認識できます。にアクセスし、印刷などの操作を実行します。では、どうやって送るのでしょうか?

1.Bluetoothプリンター

WeChat アプレットを Bluetooth プリンタに接続して写真を印刷する例については、 Nuggets zgt_ Bumengの記事 を参照してください。

  • Bluetooth モジュールの初期化 wx.openBluetoothAdapter()
  • 初期化後に近くの Bluetooth デバイスを検索する wx.startBluetoothDevicesDiscovery()
  • 新しいデバイスを見つけるイベントをリッスンする wx.onBluetoothDeviceFound()
  • 新しいデバイスをリッスンするイベント コールバックですべての Bluetooth デバイスのリストを取得します wx.getBluetoothDevices()
  • 低エネルギー Bluetooth デバイスに接続する wx.createBLEConnection()
  • 接続が成功したら、Bluetooth デバイス サービスを取得します wx.getBLEDeviceServices()
  • サービス内の (notify=true || Indicates=true) && write=true の特性値の uuid を取得します: wx.getBLEDeviceCharacteristics()
  • 完了したら狩猟を停止します wx.stopBluetoothDevicesDiscovery()
  • Bluetooth Low Energyデバイスの特性値にバイナリデータを書き込みます wx.writeBLECharacteristicValue()
  • ページを離れるときに Bluetooth 接続をキャンセルします wx.closeBLEConnection()
  • Bluetooth モジュールを閉じる wx.closeBluetoothAdapter()

個人テスト、効果あります!対応するAPIを置き換えるだけでuniappでも可能です

2. イーサネットプリンター

ここでは scoket 接続を使用しますが、USB 印刷と比較して、プリンターと Android デバイスが同じ LAN 上にあることを確認する必要があります。利点は、Android デバイスがプリンターから遠く離れていてもよいことです (キッチンで印刷する場合など)。ここでは、Sprinter プリンターを例に挙げます。Sprinter 公式 Web サイト https://www.sprinter.com.cn/ https://www.sprinter.com.cn/データ通信の前に、プリンターがどこにあるかを知る必要があります。このLAN配下の IP 下図が「ワンキー配信ネットワーク」のツールです

 このツールを使用すると、プリンターの IP を簡単かつ迅速に照会したり、空きネットワーク セグメントに応じてデフォルトで割り当てられた IP を変更したりできます。Spirit POS プリンターのポートは 9100 です

別のブランドのプリンターの場合は、arp コマンドを使用して現在の LAN 下の IP を表示することもできます。

プリンターの IP を取得した後、プリンターをテストするにはどうすればよいですか?

Telnet コマンドを使用できます (これは通常、Windows システムではデフォルトで閉じられているため、手動で開く必要があります)。

//telnet + 空格 + ip + 空格 + 端口号
telnet 192.168.5.6 9100

コマンド ライン ウィンドウを開き、telnet コマンドを入力して Enter キーを押します。

 ポートが閉じているか接続できない場合は、ホストへのリンクを開けないことが表示され、リンクが失敗します。ポートが開いている場合、リンクは成功し、Telnet ページ (すべて黒) が表示されます。これは、ポートが使用可能であることを証明します。

接続が成功した後、内容を入力して Enter キーを押すと、入力した内容がプリンターで印刷されます。

次に、scoket を使用して Android デバイスとプリンターを接続します。ここでは 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 プラグイン マーケットのプラグインを使用していますが、Android ネイティブ開発の知識がある場合は、ネイティブ プラグインを自分で作成したり、Native.js を使用して開発したりすることもできます。ネイティブ プラグインを使用してローカルでデバッグするには、まず「カスタム デバッグ ベース」をパッケージ化し、ローカルでテストした後に正式にパッケージ化する必要があります。

uni-app は、nativejs に基づいて USB-OTG 通信を実装します - 短い本1、USB 接続を監視し、許可があるかどうかを判断します 2、許可を取得した後、デバイスを開いて接続を実現します 3、データの読み取り、書き込み、送受信https://www.jianshu.com/p/7c308ffcd789

uni-app官网uni-app、uniCloud、サーバーレスhttps://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 プラグイン マーケットこのプラグインは、Android 携帯電話が USB インターフェースを介してサーマル レシート プリンターに接続して印刷するための関連機能を提供します。Bluetooth を使用するよりも USB 経由で接続する方が安定しています。https://ext.dcloud.net.cn/plugin?id=7757 USBプラグインを使用すると、USBデバイスの抜き差しを監視でき、初期化後はデータ通信やUSBデバイスの抜き差しを監視できます。上記でカプセル化された印刷コマンドをプリンターに送信します。

 2. Webページの印刷

Webページはブラウザ上で動作するため、ブラウザが提供するAPIのみを使用できます。

1.windows.print()

この API はブラウザによって異なります。その機能は、Web ページの body 要素を出力することです。body 要素全体を出力したくない場合は、body の innerHTML を置き換える必要があります。この方法を使用すると、一部のページ スタイルが印刷されたものと異なる場合があるため、他の方法を使用して最適化する必要があります。

//使用方法

document.body.innerHTML = newstr;  // 把需要打印的指定内容赋给body
window.print();

1.1 メディアクエリの使用


@media print {
  //把需要打印时才用到的样式写到这里
  p{
    font-size:16px;
  }
}

同様に、media="print" を CSS ファイルまたはスタイル タグに直接追加することもできます。

<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 指定した要素の後に改ページを挿入します

page-break-before、page-break-after 改ページのプロパティ
価値 説明
自動 デフォルト。必要に応じて要素の後に改ページを挿入します。
いつも 要素の後に改ページを挿入します。
避ける 要素の後に改ページを挿入することは避けてください。
要素の後に空白の左ページまで、十分な改ページを入れます。
要素の後に空白の右ページまで、十分な改ページを入れます。
あなたは継承します 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 指定した要素に改ページを挿入するかどうかを設定します 

page-break-inside 改ページプロパティ
価値 説明
自動 デフォルト。必要に応じて要素内に改ページを挿入します。
避ける 要素内に改ページを挿入することは避けてください。
あなたは継承します page-break-inside 属性の設定を親要素から継承することを指定します。
  1. この属性は、絶対的に配置された要素には使用できません。
  2. ページネーション属性の使用はできる限り少なくし、テーブル、フローティング要素、枠線のあるブロック要素ではページネーション属性の使用を避けてください。
  3. IE8 およびそれ以前の IE バージョンは、「inherit」属性をサポートしていません。
  4. 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のようなグレーを使用すると、印刷効果が非常に不明確になることに 注意してください。

 3.終了

良い提案やアイデアがありましたら、ぜひご相談ください

 

おすすめ

転載: blog.csdn.net/Linxi_001/article/details/130867268