NWJS(NodeJS)調用打印機 - 打印小票

1 背景

架構設計:VueJS + Spring Cloud微服務架構

功能要求:

  • 調用小票打印機打印小票,功能和超市收銀結算功能相同
  • 使用NWJS包裝VueJS前端代碼實現exe安裝包和可執行文件

2 調查

經過調查,主要有如下幾種思路。

2.1 思路1:使用IP+Port方式調用網絡打印機

代碼如下,只需要調用node的net模塊即可。詳情請看參考1。

var net = require('net');
var client = new net.Socket();
var buffer; // Buffer類型,放你的打印指令,具體的小票打印指令可以搜索ESC/POS指令
client.connect(port, ip, function () {
    client.write(buffer, function(){});
});

這種方式沒有去嘗試,因爲不清楚IP地址和端口。這邊的需求是小票機直連PC的,不是網絡方式。

2.2 思路2:NodeJS IPP協議

Github上有一個project名爲ipp,Internet Printing Protocol即網絡打印協議。IPP協議始於90年代,一直沿用至今。目前有數以百萬計的打印機都支持該協議。

可以使用如下方式驗證你的打印機是否支持IPP協議:

ipp檢測本機打印機方法如下:

var mdns = require('mdns'),
	browser  = mdns.createBrowser(mdns.tcp('ipp'));
	
mdns.Browser.defaultResolverSequence[1] = 'DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo() : mdns.rst.getaddrinfo({families:[4]}); 

browser.on('serviceUp', function (rec) {
	console.log(rec.name, 'http://'+rec.host+':'+rec.port+'/'+rec.txtRecord.rp);
});
browser.start();

//example output...
//HP LaserJet 400 M401dn (972E51) http://CP01.local:631/ipp/printer
//HP LaserJet Professional P1102w http://P1102W.local:631/printers/Laserjet
//Officejet Pro 8500 A910 [611B21] http://HPPRINTER.local:631/ipp/printer

官方示例:

var ipp = require('ipp');
var PDFDocument = require('pdfkit');

//make a PDF document
var doc = new PDFDocument({margin:0});
doc.text(".", 0, 780);

doc.output(function(pdf){
    // 這裏需要打印機的url,可以使用上面的方法得到
	var printer = ipp.Printer("http://NPI977E4E.local.:631/ipp/printer");
	var msg = {
		"operation-attributes-tag": {
			"requesting-user-name": "William",
			"job-name": "My Test Job",
			"document-format": "application/pdf"
		},
		data: pdf
	};
    // 執行打印任務,這裏打印的是PDF
	printer.execute("Print-Job", msg, function(err, res){
		console.log(res);
	});
});

這種方式沒有嘗試,在說明書裏面沒找到這個IPP協議。也沒有嘗試使用上面的JavaScript代碼進行檢測。

具體請看參考2和參考3。

2.3 Lodop打印控件

官方示例如下:

看樣子還是比較簡單的,但是需要exe文件安裝瀏覽器控件或者Web服務。這個會加大實施運維的難度,所以放棄了。

具體請看參考4、參考5和參考6。

2.4 使用Node Printer模塊

在參考7中使用了NWJS,且打印的效果和我們的項目預期非常相似。

其封裝了nodejs的printer模塊,並且是在NWJS中調用的小票機進行打印。

下面的解決方案將以此調查結果進行開展。

3 解決

下面將一步步介紹如何在NWJS中調用小票打印機。爲了儘量描述項目實際情況且不包含任何公司機密信息,下面的例子都是網上可見的。

注意:項目中NWJS和VueJS是分成兩個項目,二者通過NWJS中index.html中的iframe建立聯繫。當然也可以把二者合一,這個可以參照nw-seed-vue項目(https://github.com/anchengjian/vue-nw-seed)。

3.1 準備VueJS項目

爲了簡化VueJS項目,這裏直接使用vue-cli創建一個hello world項目。

創建後的項目如下:

3.2 準備NWJS項目

NWJS原本的打包工作不是自動化的,不過NWJS官方推薦了自動化打包工具:https://github.com/evshiron/nwjs-builder-phoenix

我們直接使用官方的示例作爲初始NWJS項目:https://github.com/evshiron/nwjs-builder-phoenix/tree/master/assets/project

注意:這裏用的NWJS是0.14.7版本,不要問我爲什麼,因爲它支持XP系統!

附XP下Chrome瀏覽器下載地址:https://dl.google.com/release2/h8vnfiy7pvn3lxy9ehfsaxlrnnukgff8jnodrp0y21vrlem4x71lor5zzkliyh8fv3sryayu5uk5zi20ep7dwfnwr143dzxqijv/49.0.2623.112_chrome_installer.exe

3.2.1 檢出並安裝依賴

項目檢出後,執行npm install命令安裝依賴,具體項目結構如下:

3.2.2 調整package.json中的配置

這個Sample寫的無力吐槽,我們按照https://github.com/evshiron/nwjs-builder-phoenix官方給出的Getting Started改造一下吧。

官方是在package.json中添加如下兩段代碼:

{
    // 1. 指定NWJS版本爲0.14.7
    "build": {
        "nwVersion": "0.14.7"
    },
    // 2.指定npm腳本
    // 移除不需要的構建任務:linux-x86,linux-x64,mac-x64
    // 替換掉NWJS鏡像(國內還是淘寶鏡像好使):https://dl.nwjs.io/改成https://npm.taobao.org/mirrors/nwjs/
    // 本地運行x64版本
    "scripts": {
        "dist": "build --tasks win-x86,win-x64 --mirror https://npm.taobao.org/mirrors/nwjs/ .",
        "start": "run --x64 --mirror https://npm.taobao.org/mirrors/nwjs/ ."
    }
}

修改上述配置後,需要重新npm install一下。

3.2.3 改造index.html頁面

原本的index.html如下:啓動完NWJS後就關閉了。

<html>
<head></head>
<body>
<script>
	process.exit(parseInt(nw.App.argv[0]));
</script>
</body>
</html>

index.html頁面需要嵌入iframe,地址就是VueJS項目運行地址:

<html>
<head></head>
<style>
    /* 有一丟丟滾動條,隱藏掉 */
    body {
        overflow: hidden;
    }
    /* 調整iframe爲100%大小,並去掉默認的邊框 */
    #iframe {
        width: 100%;
        height: 100%;
        border: none;
    }
</style>
<body>
<iframe id="iframe" src="http://localhost:8080/"></iframe>
</body>
</html>

上面設置的僅僅是iframe的大小,但是本身NWJS運行的窗口卻很小,需要調整下。

修改package.json配置文件,調整NWJS窗口大小:詳情參照https://nwjs.org.cn/doc/api/Manifest-Format.html#%E7%AA%97%E5%8F%A3%E5%AD%90%E5%AD%97%E6%AE%B5

運行如下命令,查看NWJS程序:

npm run start

打開的窗口如下:

3.3 安裝打印相關依賴

在參考7(https://juejin.im/post/5c6a77816fb9a049f23d50d4)中是這樣做的:使用node printer模塊,然後自己封裝esc/pos指令。

這裏也是使用node printer,但是使用的是chn-escpos來處理ESC/POS指令。

3.3.1 添加chn-escpos依賴

在package.json中的"dependencies"中添加如下依賴:

"chn-escpos": "1.1.4"

先不急執行npm install。

3.3.2 編譯node printer

安裝nw-gyp參照:https://www.npmjs.com/package/nw-gyp

安裝node printer參照:https://github.com/tojocky/node-printer

彙總下,就變成下面這樣:

# !!!不確定是否這個需要安裝
npm install -g node-gyp

# 安裝時間有點長,還會安裝Python。nw-gyp需要它!
# 參考:https://www.npmjs.com/package/windows-build-tools
# 可以指定python鏡像地址:--python_mirror=https://npm.taobao.org/mirrors/python/
npm --python_mirror=https://npm.taobao.org/mirrors/python/ --vs2015 install --global windows-build-tools 

# 全局安裝nw-gyp
npm install -g nw-gyp

# 安裝依賴:chn-escpos、printer...
npm install

# 切換到剛剛npm install後的printer目錄下
cd node_modules/printer

# 使用nw-gyp rebuild:Runs clean, configure and build all in a row
# 這樣就得到了針對NWJS0.14.7版本的64位的printer模塊
nw-gyp rebuild --target=0.14.7 --arch=x64 --dist-url=https://npm.taobao.org/mirrors/nwjs --python=C:\Users\63450\.windows-build-tools\python27\python.exe --msvs_version=2015

關於32位printer模塊的編譯:

# 一定要用32位node!
# arch改成ia32
nw-gyp rebuild --target=0.14.7 --arch=ia32 --dist-url=https://npm.taobao.org/mirrors/nwjs --python=C:\Users\63450\.windows-build-tools\python27\python.exe --msvs_version=2015

3.4 調用打印相關API

打印函數提供方:NWJS項目調用chn-escpos實現

打印函數調用方:vue-hello-world項目

二者通訊方式:window.postMessage(https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

3.4.1 NWJS項目提供函數用於打印

在index.html頁面添加如下代碼:

<script>
    // 初始化iframe的src
    var iframe = document.getElementById("iframe");
    var domain = "http://localhost:8080/";
    iframe.src = domain;

    // 檢查下node printer裝好了沒有,如果彈不出來,那麼你的node printer沒有編譯好!
    var printer = require('chn-escpos');
    alert(printer);

    // 監聽message事件,這個就是window.postMessage觸發的事件
    window.addEventListener('message', function(e) {
        var data = e.data;
        // 別的地方可能也會觸發message事件,所以需要過濾
        if (data.type === 'print') {
            print(data);
        }
    });

    function print(data) {
        new printer(data.printerName, function(err, msg) {
            // 處理回調
            if (err || msg) {
                iframe.contentWindow.postMessage({err: err, msg: msg, type: 'printCallback'}, domain);
                if (err) {
                    return;
                }
            }

            // 這裏寫實際的打印邏輯
            this.line(3);
            this.text('測試打印');
            this.line(3);

            // 執行打印並處理回調
            this.print(function(err, msg) {
                iframe.contentWindow.postMessage({err: err, msg: msg, type: 'printCallback'}, domain);
            })
        });
    }
</script>

3.4.2 vue-hello-world調用NWJS提供的打印函數

在HelloWorld.vue中添加如下代碼:原諒我還不會寫ES6語法

<script>
  // 監聽message事件,處理回調
  window.addEventListener('message', function(e) {
    var data = e.data;
    // 別的地方可能也會觸發message事件,所以需要過濾
    if (data.type === 'printCallback') {
      console.log(data);
    }
  });

  // 調用打印機進行打印
  window.parent.postMessage({printerName: 'OneNote', type: 'print'}, '*');
</script>

3.4.3 驗證打印

啓動vue-hello-world和NWJS項目,先看到彈窗,說明node printer編譯好了:

然後可以看到OneNote被調用起來,並打印出了回調函數值:

OK!!!

3.5 XP下存在的問題

XP32下,chn-escpos調用打印機打印功能會導致NWJS直接閃退:

[0422/180048:WARNING:pe_image_reader.cc(144)] CodeView debug entry of unexpected size in C:\WINDOWS\system32\OLEACC.dll

後來調試發現,只要在打印前不獲取打印機信息就不會閃退,出問題的代碼如下:

這個問題解決不掉,只能不去獲取打印機信息了。

我們可以把chn-escpos相關代碼全部提取出來,放到chn-escpose-printer.js中:

var iconv = require('iconv-lite'),
    cmds = {
        INITIAL_PRINTER: '\x1B\x40', //Initial paper
        NEW_LINE: '\x0A', //Add new line
        PAPER_CUTTING: '\x1d\x56\x41', //Cut paper
        LINE_HEIGHT: '\x1b\x32', //Normal line height
        LINE_HEIGHT_B: '\x1b\x33\x6e', //Normal line height large
        CHN_TEXT: '\x1b\x52\x0f', //CHN text
        // text style
        TXT_NORMAL: '\x1d\x21\x00', // Normal text
        TXT_SIZE: '\x1d\x21', // Double height text

        TXT_UNDERL_OFF: '\x1b\x2d\x00', // Underline font OFF
        TXT_UNDERL_ON: '\x1b\x2d\x01', // Underline font 1-dot ON
        TXT_UNDERL2_ON: '\x1b\x2d\x02', // Underline font 2-dot ON
        TXT_BOLD_OFF: '\x1b\x45\x00', // Bold font OFF
        TXT_BOLD_ON: '\x1b\x45\x01', // Bold font ON

        TXT_ALIGN_L: '\x1b\x61\x00', // Left justification
        TXT_ALIGN_C: '\x1b\x61\x01', // Centering
        TXT_ALIGN_R: '\x1b\x61\x02', // Right justification

        //字體
        TXT_FONT_A: '\x1b\x4d\x00', // Font type A
        TXT_FONT_B: '\x1b\x4d\x01', // Font type B
        TXT_FONT_C: '\x1b\x4d\x02', // Font type C
        TXT_FONT_D: '\x1b\x4d\x48', // Font type D
        TXT_FONT_E: '\x1b\x4d\x31', // Font type E

        //barcode
        BARCODE_TXT_OFF: '\x1d\x48\x00', // HRI barcode chars OFF
        BARCODE_TXT_ABV: '\x1d\x48\x01', // HRI barcode chars above
        BARCODE_TXT_BLW: '\x1d\x48\x02', // HRI barcode chars below
        BARCODE_TXT_BTH: '\x1d\x48\x03', // HRI barcode chars both above and below

        BARCODE_FONT_A: '\x1d\x66\x00', // Font type A for HRI barcode chars
        BARCODE_FONT_B: '\x1d\x66\x01', // Font type B for HRI barcode chars

        BARCODE_HEIGHT: '\x1d\x68\x64', // Barcode Height [1-255]
        BARCODE_WIDTH: '\x1d\x77\x03', // Barcode Width  [2-6]

        //一維碼
        BARCODE_UPC_A: '\x1d\x6b\x00', // Barcode type UPC-A
        BARCODE_UPC_E: '\x1d\x6b\x01', // Barcode type UPC-E
        BARCODE_EAN13: '\x1d\x6b\x02', // Barcode type EAN13
        BARCODE_EAN8: '\x1d\x6b\x03', // Barcode type EAN8
        BARCODE_CODE39: '\x1d\x6b\x04', // Barcode type CODE39
        BARCODE_ITF: '\x1d\x6b\x05', // Barcode type ITF
        BARCODE_NW7: '\x1d\x6b\x06', // Barcode type NW7

        //二維碼,from http://stackoverflow.com/questions/23577702/printing-qr-codes-through-an-esc-pos-thermal-printer
        QRCODE_SIZE_MODAL: '\x1D\x28\x6B\x03\x00\x31\x41\x32\x00', //Select the model,[49 x31, model 1] [50 x32, model 2] [51 x33, micro qr code]
        QRCODE_SIZE: '\x1D\x28\x6B\x03\x00\x31\x43',  //Set the size of module
        QRCODE_ERROR: '\x1D\x28\x6B\x03\x00\x31\x45\x31', //Set n for error correction [48 x30 -> 7%] [49 x31-> 15%] [50 x32 -> 25%] [51 x33 -> 30%]
        QRCODE_AREA_LSB: '\x1D\x28\x6B', //Store the data in the symbol storage area LSB
        QRCODE_AREA_MSB: '\x31\x50\x30', //Store the data in the symbol storage area MSB
        QRCODE_PRINT:'\x1D\x28\x6B\x03\x00\x31\x51\x30', //Print the symbol data in the symbol storage area

        //錢箱
        CASHBOX_OPEN: '\x1B\x70\x00\xFF\xFF', //Open casebox

        //蜂鳴
        BEEP:'\x1b\x42' //beep
    },
    node_printer = require("printer"),
    BufferHelper = require('bufferhelper');

/**
 * 打印任務
 * @param  {string}   printer_name 打印機名
 * @param  {function} callback     function(err,msg),當獲取打印機後執行,如果不存在指定打印機,返回err信息
 */
var printer = function(printer_name, callback) {
    if (!printer_name) {
        printer_name = node_printer.getDefaultPrinterName();
    }
    this.printer = printer_name;
    /* 解決在XP系統下獲取打印機信息崩潰的問題
    try {
        node_printer.getPrinter(this.printer);
    } catch (err) {
        if (callback) callback.call(this, err, 'Can\'t find the printer');
        return false;
    }
    */
    this._queue = new BufferHelper();
    this._writeCmd('INITIAL_PRINTER');
    this._writeCmd('CHN_TEXT');
    if (callback) callback.call(this, null, 'Get printer success');
}

printer.prototype = {
    /**
     * 打印文字
     * @param  {string} text    文字內容
     * @param  {boolen} inline  是否換行
     * @return {object}         當前對象
     */
    text: function(text, inline) {
        if (text) {
            this._queue.concat(iconv.encode(text, 'GBK'));
            if (!inline) this._writeCmd('NEW_LINE');
        }
        return this;
    },
    /**
     * 打印空行
     * @param  {number} number 行數
     * @return {object}        當前對象
     */
    line: function(number) {
        number = number || 1;
        for (var i = 0; i < number; i++) {
            this._writeCmd('NEW_LINE');
        }
        return this;
    },
    /**
     * 設置對其
     * @param  {string} align 居中類型,L/C/R
     * @return {object}       當前對象
     */
    setAlign: function(align) {
        this._writeCmd('TXT_ALIGN_' + align.toUpperCase());
        return this;
    },
    /**
     * 設置字體
     * @param  {string} family A/B/C/D/E
     * @return {object}        當前對象
     */
    setFont: function(family) {
        this._writeCmd('TXT_FONT_' + family.toUpperCase());
        return this;
    },
    /**
     * 設置行距
     * @param {number} hex 16進制數據,如'\x05'
     */
    setLineheight: function(hex) {
        this._writeCmd('LINE_HEIGHT');
        if (hex) {
            //console.log('\x1b\x33'+hex);
            this._queue.concat(new Buffer('\x1b\x33' + hex));
            //設置默認行間距
        }
        return this;
    },
    /**
     * 設置格式(加粗,下拉)
     * @param  {string} type B/U/U2/BU/BU2
     * @return {object}      當前對象
     */
    setStyle: function(type) {
        switch (type.toUpperCase()) {
            case 'B':
                this._writeCmd('TXT_UNDERL_OFF');
                this._writeCmd('TXT_BOLD_ON');
                break;
            case 'U':
                this._writeCmd('TXT_BOLD_OFF');
                this._writeCmd('TXT_UNDERL_ON');
                break;
            case 'U2':
                this._writeCmd('TXT_BOLD_OFF');
                this._writeCmd('TXT_UNDERL2_ON');
                break;
            case 'BU':
                this._writeCmd('TXT_BOLD_ON');
                this._writeCmd('TXT_UNDERL_ON');
                break;
            case 'BU2':
                this._writeCmd('TXT_BOLD_ON');
                this._writeCmd('TXT_UNDERL2_ON');
                break;
            case 'NORMAL':
            default:
                this._writeCmd('TXT_BOLD_OFF');
                this._writeCmd('TXT_UNDERL_OFF');
                break;
        }
        return this;
    },
    /**
     * 設定字體尺寸
     * @param  {string} size  2/null
     * @return {object}       當前對象
     */
    setSize: function(size) {
        this._writeCmd('TXT_NORMAL');
        this._writeCmd('LINE_HEIGHT');
        switch(parseInt(size)){
            case 2:
                this._queue.concat(new Buffer(cmds['TXT_SIZE']+'\x10'));
                this._queue.concat(new Buffer(cmds['TXT_SIZE']+'\x01'));
                break;
            case 3:
                this._queue.concat(new Buffer(cmds['TXT_SIZE']+'\x32'));
                this._queue.concat(new Buffer(cmds['TXT_SIZE']+'\x02'));
                break;
            case 4:
                this._queue.concat(new Buffer(cmds['TXT_SIZE']+'\x48'));
                this._queue.concat(new Buffer(cmds['TXT_SIZE']+'\x03'));
                break;
        }
        return this;
    },
    /**
     * 二維碼
     * @param  {string} code     打印內容
     * @param  {string} type     打印類型,UPC-A(11-12)/UPC-E(11-12,不可用)/EAN13(默認,12-13)/EAN8(7-8)/CODE39(1-255,不可用)/ITF(1-255偶數,不可用)/NW7(1-255,不可用)
     * @param  {number} width    寬度
     * @param  {number} height   高度
     * @param  {string} position OFF/ABV/BLW/BTH
     * @param  {string} font     字體A/B
     * @return {object}          當前對象
     */
    barcode: function(code, type, width, height, position, font) {
        if (width >= 1 || width <= 255) {
            this._writeCmd('BARCODE_WIDTH');
        }
        if (height >= 2 || height <= 6) {
            this._writeCmd('BARCODE_HEIGHT');
        }
        this._writeCmd('BARCODE_FONT_' + (font || 'A').toUpperCase());
        this._writeCmd('BARCODE_TXT_' + (position || 'BLW').toUpperCase());
        this._writeCmd('BARCODE_' + ((type || 'EAN13').replace('-', '_').toUpperCase()));
        this._queue.concat(new Buffer(code));
        return this;
    },
    /**
     * 打印二維碼,需要打印機支持
     * @param  {string} text    打印文字內容
     * @param  {string} size   二維碼大小,16進制字符串,如'\x01'.默認爲'\x06'
     * @param  {string} lsb    (text長度+3)%256轉16進制後的字符,如'\x01';
     * @param  {[type]} msb  (text長度+3)/256取整轉16進制後的字符,如'\x00';
     * @return {object}          當前對象
     */
    qrcode:function(text,size,lsb,msb){
        size=size?size:'\x06';
        if(!/^[\w\:\/\.\?\&\=]+$/.test(text)){
            this.text('二維碼請使用英文和數字打印');
            return this;
        }
        this._writeCmd('QRCODE_SIZE_MODAL');
        this._queue.concat(new Buffer(cmds['QRCODE_SIZE']+size));
        this._writeCmd('QRCODE_ERROR');
        this._queue.concat(new Buffer(cmds['QRCODE_AREA_LSB']+lsb+msb+cmds['QRCODE_AREA_MSB']));
        this._queue.concat(new Buffer(text));
        this._writeCmd('QRCODE_PRINT');
        return this;
    },
    /**
     * 蜂鳴警報
     * @param  {string} times    蜂鳴次數,16進制,1-9.默認'\x09'
     * @param  {string} interval 蜂鳴間隔,16進制,實際間隔時間爲interval*50ms,默認'\x01'
     * @return {object}          當前對象
     */
    beep:function(times,interval){
        times=times?times:'\x09';
        interval=interval?interval:'\x01';
        this._queue.concat(new Buffer(cmds['BEEP']+times+interval));
        return this;
    },
    /**
     * 打開錢箱
     * @return {object} 當前對象
     */
    openCashbox: function() {
        this._writeCmd('CASHBOX_OPEN');
        return this;
    },
    /**
     * 編譯指定語法字符串爲print方法
     * @param  {string} string 語法字符串
     * @return {object}        當前對象
     */
    compile: function(string) {
        if (typeof string != 'string') {
            console.log('必須爲字符串');
            return this;
        }
        var _this = this;
        //替換換行
        var tpl = string.replace(/[\n\r]+/g, '/n')
            //替換函數
            .replace(/<%([\s\S]+?)%>/g, function(match, code) {
                return '",true);\n' + _this._renderFunc(code) + '\nobj.text("';
            })
            //替換換行
            .replace(/\/n/g, '");\nobj.text("');
        tpl = 'obj.text("' + tpl + '")';
        new Function('obj', tpl).call(this, this);
        return this;
    },
    /**
     * 執行命令
     * @param  {string} cmd 命令名
     */
    _writeCmd: function(cmd) {
        if (cmds[cmd]) {
            this._queue.concat(new Buffer(cmds[cmd]));
        }
    },
    _renderFunc: function(string) {
        var _this = this,
            status = true;
        string = string.trim();
        //函數名生命
        var func = string.replace(/^([\S]+?):/, function(match, code) {
            var func_name = _this._escape(code);
            if (!_this[func_name]) {
                //無效函數
                status = false;
                console.log('解析模板出錯沒有名爲' + func_name + '的方法');
            }
            return 'obj.' + func_name + ':';
            //函數變量
        }).replace(/:([\S]+?)$/, function(match, code) {
            var func_var = _this._escape(code).split(','),
                tpl_var = '';
            var length = func_var.length;
            for (var i = 0; i < length; i++) {
                //%u hack
                var cur_func_var = func_var[i];
                if(/%u/.test(func_var[i])){
                    cur_func_var=cur_func_var.replace(/%u/g,'u');
                }
                tpl_var += '"' + cur_func_var + '",';
            }
            tpl_var = tpl_var.replace(/\,$/, '');
            return '(' + tpl_var + ');';
        });
        if (status) return func
        else return '';
    },
    /**
     * 預留跨域攻擊防禦
     * @param  {string} string 目標內容
     * @return {string}        轉換後
     */
    _escape: function(string) {
        string = unescape(string.replace(/\u/g, "%u")); //轉換unicode爲正常符號
        string = string.replace(/[\<\>\"\'\{\}]/g, '');
        return string;
    },
    /**
     * 執行打印
     * @param  {Function} callback function(err,msg),當執行打印後,回調該函數,打印錯誤返回err信息
     */
    print: function(callback) {
        this._writeCmd('PAPER_CUTTING');
        this._writeCmd('INITIAL_PRINTER');
        this.sendCmd(callback);
    },
    /**
     * 發送命令
     * @param  {Function} callback function(err,msg),當執行打印後,回調該函數,打印錯誤返回err信息
     */
    sendCmd:function(callback){
        var _this = this;
        node_printer.printDirect({
            data: _this._queue.toBuffer(),
            printer: _this.printer,
            type: "RAW",
            success: function() {
                callback.call(_this, null, 'Print successed');
                _this._queue.empty();
            },
            error: function(err) {
                callback.call(_this, null, 'Print failed');
            }
        });
    },
    /**
     * 清空打印內容
     * @return {object} 當前對象
     */
    empty: function() {
        this._queue.empty();
        return this;
    }
}

module.exports = printer;

然後調整package.json中的依賴:之前的chn-escpose刪掉

"dependencies": {
	"printer": "^0.4.0",
	"bufferhelper": "^0.2.1"
}

再調整index頁面對原有chn-escpose的引入就可以了:

var printer = require('./chn-escpos-printer');
alert(printer);
print({printerNamer:"One"});
alert(11);

詳情參見nwjs-node-printer-xp.rar。

1. NWJS項目:nwjs-project.zip

2. vue-hello-world項目:vue-hello-world.zip

3. 編譯好的printer(64位、32位)模塊:node printer.zip.001、node printer.zip.002、node printer.zip.003,三個一起解壓,然後把64位或者32位放到你的node_modules下面,這樣就不需要install這個printer模塊了

參考

1. [NodeJs 如何驅動小票打印機打印小票?](https://segmentfault.com/q/1010000008790881/a-1020000008802345)

2. [NodeJS使用ipp協議打印](https://www.iteye.com/blog/liyunpeng-2098669)

3. [NodeJS ipp](https://github.com/williamkapke/ipp)

4. [nwjs打印小票案例,包含二維碼](https://www.jianshu.com/p/003aad570c08)

5. [lodop官方打印示例](http://www.lodop.net/demolist/PrintSample1.html)

6. [VueJS中使用lodop](http://www.c-lodop.com/faq/pp35.html)

7. [如何通過Nw.js純前端實現調用熱敏打印機打印小票?](https://juejin.im/post/5c6a77816fb9a049f23d50d4)

8. [NWJS自動化打包工具:nwjs-builder-phoenix](https://nwjs.org.cn/doc/user/Package-and-Distribute.html)

9. [github: nwjs-builder-phoenix](https://github.com/evshiron/nwjs-builder-phoenix)

10. [ESC/POS指令](https://wenku.baidu.com/view/9a165cb8647d27284a735128.html)

11. [npm chn-escpos](https://www.npmjs.com/package/chn-escpos)

12. [關於在nw裏使用require('printer')和nw.require('printer')報錯的問題](https://blog.csdn.net/qq_39702364/article/details/82800935)

13. [在nw.js要如何優雅的使用node-printer](https://www.jianshu.com/p/b3c558ddb914)

14. [npm nw-gyp](https://www.npmjs.com/package/nw-gyp)

15. [window.postMessage](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage)

16. [NW.js安裝原生node模塊node-printer控制打印機](https://www.cnblogs.com/pannysp/p/9687743.html)

17. [在NW.js中安裝Node原生模塊](https://nwjs.org.cn/doc/user/Advanced/Use-Native-Node-Modules.html)

18. [A seed project with vue and nwjs ](https://github.com/anchengjian/vue-nw-seed)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章