一个javascript条码打印机程序给我的惊喜
发布于 4小时前 作者 tulayang 71 次浏览 来自 分享


昨天为了一个“特殊”的需要,我跟一个BTP-2200E条码打印机“鼓捣”了一下午, 竟然搞出了一个我现在最为满意的程序。

额…当我第一次知道要写那个“特殊”程序的时候,其实我是拒绝的!因为我觉得……呀……你不能叫我写马上写,第一,我要试一下,我又不想说……你接一个需求写完以后加了很多框架,那程序跑啊……很快!很短!很小!结果客户出来一定骂我,根本没有这种程序!这证明上面那个是假的……我说先要给我试一下。后来我经过也知道他们是条码的,而且没有那种三帝成分的。那……写了这下午……这下午下来之后呢……起码我写了很舒服。现在呢……每天还在写!每天还在写呢…我还给了我业务员用!来!来!来!大家试试看!那我跟老板讲︰「写的时候就写!写完之后,这个代码就是我的代码,就不要加,再加框架上去,加到没有就是这样子!我要给客户看到,我写完之后是这样子,你们写完之后,也是这样子!

说到打印机,多少让人联想到计算机的起源,比如磁带,比如移位。这个BTP-2200E是一个仓库用的条码打印机,一条长的黑色磁带,和一个长的白色底带,从盒子一端嘀嘀嘀的出来一张卡片似的条码。打出来是类似这样的:


+----------------------+
| 货号: 16C002-2       
| 颜色: 粉色  尺码: 236  
| 材质: 牛皮  等级: 一型  |
| QB/T1001-2006        |
| GB/T22969-2009       |
| ||||||||||||||||     |
|  16C002-2039136      |
+-----------------------+

源代码如下:


(function(global, mod) {
    if (typeof exports == "object" && typeof module == "object") { 
        return mod(exports); 
    } else if (typeof define == "function" && define.amd) {
        return define(["exports"], mod); 
    } else { 
        mod(global.BPLA || (global.BPLA = {})); 
    }
})(this, function(exports) {
    var BPLA_OK = 0, 
        port = -1, 
        errno = '',
        messages = {
            errorLoadBplaocx: '没有成功加载BPLAOCX插件',
            errorOpenPort: '打开端口错误,请检查连接或端口参数设置',
            errorTimeout: '超时配置未能正确设置,请检查连接或端口参数设置',
            errorPrint: '打印条形码错误'
        };

// 检测到错误,中断程序

    function error(m) {
        errno = messages[m]; 
    }

// 检查BPLAOCX插件加载

    function checkatx() {
        if (typeof BPLAOCX !== 'object' || BPLAOCX === null) {
            error('errorLoadBplaocx');
        }
    }

// 关闭端口

    function close() {
        BPLAOCX.BPLACloseUSB(port);
        port = -1;
        return "Port closed!";  
    }

// 打开端口

    function open() {
        if ((port = BPLAOCX.BPLAOpenUSB(0, 0)) === -1) {
            error('errorOpenPort');
        } else if(BPLAOCX.BPLASetTimeOut(port, iWrTime, iReTime) != BPLA_OK) {
            close();
            error('errorTimeout');
        }
    }

// 程序序列器,这个工具会顺序运行操作,直到出现一个错误,序列器会终止运行,
// 并返回错误。如果成功打印,序列器会返回一个成功信息。

    function sequence(/*fn1, fn2, ...*/) {
        var functions = Array.prototype.slice.call(arguments),
            i = 0,
            fn;
        while (typeof (fn = functions[i++]) === 'function') {
            fn();
            if (errno !== '') {
                return {msg:errno, state:0};
            }
        }
        return {msg:'成功打印条形码!', state:1};
    }

    function printer(method, args) {
        return function () {
            if (BPLAOCX[method].apply(BPLAOCX, args) !== BPLA_OK) {
                error('errorPrint');
            }
        };
    }

// 打印栈

    exports.print = function (data) {
    // data: {
    //     serial: String, 货号
    //     color: String, 颜色
    //     size: String, 尺码
    //     material: String, 材质
    //     level: String, 等级
    //     model: String, 型号
    //     qbnum: String, QB号
    //     gbnum: String, GB号
    //     hcode: String, 水平码
    //     vcode: String, 垂直码 
    // }
        return sequence(
            checkatx,
            open,
            printer('BPLASetPaperLength', [port, 550, 0]),
            printer('BPLAStartArea',      [port, 0, 1000, 0, 0, 0, 0]),
            printer('BPLAPrintBarcode',   [port, data.hcode, 150, 100, 1, 24, 80, 4, 2, "000", 0, 0]),
            printer('BPLAPrintBarcode',   [port, data.vode,  860, 50, 4, 5, 80, 4, 3, "000", 0, 0]), 
            printer('BPLAPrintTruetype',  [port, data.hcode, 210, 25, "Arial", 40, 0, 1, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, "(内部使用])", 330, 70, "Arial", 25, 0, 0, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, data.gbnum, 120, 200, "Arial", 35, 0, 0, 0, 0, 1]), 
            printer('BPLAPrintTruetype',  [port, data.qbnum, 120, 260, "Arial", 35, 0, 0, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, "型号: " + data.model, 460, 260, "黑体", 35, 0, 0, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, "材质: " + data.material, 120, 320, "黑体", 35, 0, 0, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, "等级: " + data.level, 460, 320, "黑体", 35, 0, 0, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, "颜色:", 120, 380, "黑体", 35, 0, 0, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, data.color, 250, 370, "黑体", 50, 0, 1, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, "尺码:", 460, 380, "黑体", 35, 0, 1, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, data.size, 600, 372, "黑体", 50, 0, 1, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, "货号:", 120, 440, "黑体", 35, 0, 0, 0, 0, 1]),
            printer('BPLAPrintTruetype',  [port, data.serial, 250, 430, "黑体", 50, 0, 0, 0, 0, 1]),
            printer('BPLAPrint',          [port, 1, 0, 1]),
            close
        );
    };  
});

调用的方式是这样的:


<!DOCTYPE html>
<html>
<head>
<script src="/js/bpla.js"></script>
<!--其他html内容-->
<object codebase="/bpla/BPLAOCX.cab"></object>
<script type="text/javascript">
    document.getElementById('printer').onclick = function () {
        var state = BPLA.print({
            serial   : '16C002-2',
            color    : '粉色',
            size     : '236',
            material : '牛皮',
            level    : '合格',
            model    : '一型',
            qbnum    : 'QB/T1001-2006',
            gbnum    : 'GB/T22969-2009',
            hcode    : '6961996123006',
            vcode    : '16C002-2039136'
        });
        document.getElementById('state').innerHTML = state.msg;
    };
</script>
</html>

HTML页面有一个打印按钮,当点击的时候,打印机会打开端口,打印传送的数据。

这个程序的主要处理程序是exports.print(data),输入一个配置过的数据对象, 调用sequence序列器,运行一串操作。

BTP-2200E的开发库提供了一个BPLAOCX模块,包含了打印机可调用的接口。每一个BPLAOCX调用都会返回一个状态函数,-1或者是大于0的值,用来检测打印机的可用状态,这意味着,如果你不做一些处理,你的代码将会充满了大量的没有多少价值的if else:


if (BPLAOCX.BPLASetPaperLength(port, 550, 0) < 0) {
    // 一个错误
} else if (BPLAOCX.BPLAStartArea(port, 0, 1000, 0, 0, 0, 0) < 0) {
    // 一个错误
} else if (BPLAOCX.BPLAPrintBarcode(port, data.hcode, 150, 100, 1, 24, 80, 4, 2, "000", 0, 0) < 0) {
...

if else是算法逻辑所必需的,但是如果只是单纯的状态检查,而且是一大堆的时候,你就会有些崩溃了,尤其是以后需要修改内容的时候。在长长的if else代码中查找和修改代码都是很容易产生bug,而且这些代码很难有描述性。

所以,最终,一个printer(method, args)用来作为一个语法糖,封装了错误处理。

错误处理是通过一个全局的messages对象拾取,一个errno标识来访问。如果errno是空值,就表明操作没有错误,如果errno是非空值,就表明出现错误,操作需要终止。(这个手法其实是抄袭自C的标准库)

printer()调用BPLAOCX的方法,操作打印机,当出现错误的时候,就设置errno的对应错误值。

sequence()是一个流程处理函数,输入参数都是函数。sequence()会顺序处理序列,调用每一个进入的函数,运行完的时候检查当前的errno状态,出现错误的时候,立刻终止程序,并返回一个错误信息。当所有打印成功完成后,关闭打印端口,并且返回一个成功信息。

这个打印序列的操作过程:


⇀ 检测打印模块加载
⇀ 打开端口
⇀ 设置打印纸长
⇀ 调整条码参数
⇀ 调整条码参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 开始打印
⇀ 关闭端口

而ta所对应的程序代码正是:

sequence(
      checkatx,
      open,
      printer('BPLASetPaperLength', [port, 550, 0]),
      printer('BPLAStartArea',      [port, 0, 1000, 0, 0, 0, 0]),
      printer('BPLAPrintBarcode',   [port, data.hcode, 150, 100, 1, 24, 80, 4, 2, "000", 0, 0]),
      printer('BPLAPrintBarcode',   [port, data.vode,  860, 50, 4, 5, 80, 4, 3, "000", 0, 0]), 
      printer('BPLAPrintTruetype',  [port, data.hcode, 210, 25, "Arial", 40, 0, 1, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, "(内部使用])", 330, 70, "Arial", 25, 0, 0, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, data.gbnum, 120, 200, "Arial", 35, 0, 0, 0, 0, 1]), 
      printer('BPLAPrintTruetype',  [port, data.qbnum, 120, 260, "Arial", 35, 0, 0, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, "型号: " + data.model, 460, 260, "黑体", 35, 0, 0, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, "材质: " + data.material, 120, 320, "黑体", 35, 0, 0, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, "等级: " + data.level, 460, 320, "黑体", 35, 0, 0, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, "颜色:", 120, 380, "黑体", 35, 0, 0, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, data.color, 250, 370, "黑体", 50, 0, 1, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, "尺码:", 460, 380, "黑体", 35, 0, 1, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, data.size, 600, 372, "黑体", 50, 0, 1, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, "货号:", 120, 440, "黑体", 35, 0, 0, 0, 0, 1]),
      printer('BPLAPrintTruetype',  [port, data.serial, 250, 430, "黑体", 50, 0, 0, 0, 0, 1]),
      printer('BPLAPrint',          [port, 1, 0, 1]),
      close
)

在异步代码的时候,我会常常写一些sequence方式的语法糖,而在顺序的代码中很少去这样做。然而,这次偶然的经历,让我对代码的编写有了一些新的思考。即便是顺序的代码,也许仍有一些巧妙的方法可以变得更加灵活。

回到顶部