复盘打码功能

最近工作中,需求方提出了一个打印条码的功能,需要将指定样本及其关联实验单的编号全部打印出来。
后端会把我需要的打码入参返回给我,前端需要做的是:引入厂家提供的js文件,调用提供的js方法初始化打印机,从后台获取打码参数,调用打码机打印方法,即可完成打码功能。
备注:涉及到打印的按钮共三种:打印当前样本条码,打印所有样本,打印所有样本并流转至待处理。
与后台沟通后,确定返回的打印参数是一个数组list,以便适应全部打印按钮。循环这个参数list,并逐个打印。

这其中遇到了几个值得记录的问题:

打码机厂家提供的初始化及打印方法放在一个立即执行方法中,无法直接引入调用;
由于初始化打码机、从后台获取参数均为异步方法,且为嵌套方法,那么打码机断开或打印失败时,怎样及时弹出错误提醒;
功能中包含一个全部打印并流转的按钮,如果打印失败,怎样阻止流转方法的调用。
获取打印机时,会把能识别的所有设备都拿到,有可能存在超过两个设备的情况(比如安装了多个打印设备的驱动等等),这时需要提供一个弹框,可以选择待使用的打印机。
2,3的主要难点在于打码机相关的方法均为原生xhr方法,怎样用Promise进行适当改写。

以下针对上面提到的四点一一记录。

1.模块方法改写
直接拿到的是一个min.js文件,核心方法如下:

.

..
var BrowserPrint=function(){
	...
	var e = {};
	e.Device = function(a){
		...
		this.send = function(){...}
		...
	}
	return e
}()

采用了立即执行函数来封闭内部私有方法,不会和全局函数互相污染,在原生js或JQuery中用得较多,其实这也是模块化的一种方式,但vue中主要采用import的方式引入文件,因此需要先将该方法导出。改写如下:

let BrowserPrint=function(){
	...
	let e = {};
	e.Device = function(a){
		...
		this.send = function(){...}
		...
	}
	return e
}
export default BrowserPrint

2.打印机获取失败提醒
这块出现场景主要是:断开打印机连接,点击打印按钮,无法正常打印时应弹出对应的错误提醒。
这个时候我们要调用的方法包括:获取打印机,再获取打码参数,打印条码。这三个方法均需要按序完成。
获取打印机用的是XMLHttpRequest 原生方法,使用回调函数会导致方法层层嵌套,不利于理解,考虑使用Promise进行改写。由于原方法偏复杂,在方法定义时直接改写难度较大,我选择在调用时封装进Promise中,并对应进行resolve和reject处理,避免回调地狱。

// 初始化打印机
initPrinter(){
  printerData.printer = browserPrint();
  return new Promise((resolve,reject)=>{
     _BrowserPrint.getDefaultDevice("printer", (device)=>
     {
       ...
       _BrowserPrint.getLocalDevices((device_list)=>{
         ...
         if(!Array.isArray(realDeviceList)||realDeviceList.length==0){
           // 没有获取到打印机
           typeof reject ==='function'&& reject()
         }else{
           typeof resolve ==='function'&& resolve()
         }
       }, function(error){
         typeof reject ==='function'&& reject()
       },"printer");
     }, function(error){
       typeof reject ==='function'&& reject()
     })
   })
}
this.initPrinter().then(res=>{}).catch(err=>{
  utils.msg.error('获取打印机失败')
  return 'error'
})

注意:定义了reject,调用时必须添加catch方法,否则会报错。

3.多层异步嵌套拿到最内层的结果,再进行相应处理
全部打印并流转是逻辑最复杂的方法,包括:

获取打印机(页面初始化后如果没有断开打印机可以省略这一步);
从后台获取打码参数;
逐一调用打印机进行打印;
打印成功则进行流转,打印失败则弹出错误提醒,并终止逻辑处理。
所有步骤按顺序进行。

其中,步骤1,2可以返回Promise实例,但步骤3仍然是一个xhr原生方法,所以决定在打印方法定义的地方(及下方的send方法)使用Promise进行改写。

// 原始方法
var m = this;
...
this.send = function(d, a, f) {
	var g = b("POST", l + "write");
	g && (void 0 !== m && (void 0 === a && (a = m.sendFinishedCallback), void 0 === f && (f = m.sendErrorCallback)), c(g, a, f), g.send(JSON.stringify({
		device: {
			name: this.name,
			uid: this.uid,
			connection: this.connection,
			deviceType: this.deviceType,
			version: this.version,
			provider: this.provider,
			manufacturer: this.manufacturer
		},
		data: d
	})))
};
...
function c(a, b, d) {
	a.onreadystatechange = function() {
		a.readyState === XMLHttpRequest.DONE && 200 === a.status ? "" === a.responseType ? b(a.responseText) : b(a.response) : a.readyState === XMLHttpRequest.DONE && (d ? d(a.response) : console.log("error occurred with no errorCallback set."))
	};
	return a
}
// 修改后的方法
var m = this;
...
this.send = function(d, a, f) {
	return new Promise((resolve,reject)=>{
		var g = b("POST", l + "write");
		g.onreadystatechange = function(){
			if (g.readyState !== 4) {
				return;
			}
			if (g.status === 200) {
				// 正确回调
				resolve("" === g.responseType ? g.responseText : g.response) 
			} else {
				// 错误回调
				reject(new Error(g.statusText));
			}
		}
		g && (g.send(JSON.stringify({
			device: {
				name: this.name,
				uid: this.uid,
				connection: this.connection,
				deviceType: this.deviceType,
				version: this.version,
				provider: this.provider,
				manufacturer: this.manufacturer
			},
			data: d
		})))
	})
};

这样步骤3也可以返回Promise实例了。由于步骤1~3是不同打印诉求中都要用到,因此封装成一个公共方法_printTag。接下来遇到的一个问题就是,我怎样知道什么时候打印全部完成?
Promise.all方法仅能处理无依赖关系的多个异步方法。后面发现其实很简单,把嵌套内层的异步方法return出去就可以了。
类似这种结构:

test(){
   this._test1().then(res=>{
     return this._test2().then(res=>{
       console.log('log1')
       return Promise.resolve('success')
     })
   }).then(res=>{
     console.log('log2')
     if(res=='success'){
		console.log('log3')
     }
   })
 },
_test1(){
   return new Promise((resolve,reject)=>{
     setTimeout(()=>{
       resolve('第一步')
     })
   })
 },
 _test2(){
   return new Promise((resolve,reject)=>{
     setTimeout(()=>{
       resolve('第二步')
     })
   })
 },

调用test方法时,我们希望打印出来的顺序是log1,log2,并且能打印出log3。通过测试发现,对test做以下改动即可:

在这里插入图片描述
改动1保证打印顺序是log1,log2,改动2保证可以拿到返回参数"success",可以执行打印log3。

4.多个打印设备
基本思路是:

获取打印机;
设备超过一台,出现选择设备弹框,停止接下来的打印操作;
弹框中点击“确定”按钮,继续开始打印操作。
其中,在步骤2出现过错误

// 出错代码示例
if(Array.isArray(printerData.deviceList)&&printerData.deviceList.length==1){
   	// 只有一台打印机
    ...
}else{
  	// 存在多个打印机,出现选择设备弹框
    this.printDialog.visible = true;
    return 'pause'
}

判断条件有一个漏洞,如果设备超过2个,那么一直会执行else逻辑。
正确的应该是:如果存在超过2个设备或者选择弹框已经出现,则执行打印操作。

// 调整后代码
if((Array.isArray(printerData.deviceList)&&printerData.deviceList.length==1)||this.printDialog.visible){
  // 只有一台打印机或已出现选择弹框
  ...
}else{
	// 存在多个打印机,出现选择设备弹框
  this.printDialog.visible = true;
  return 'pause'
}

总结:
这项功能的完成用了很多Promise改写,加深了对Promise使用方法的理解,比如必须使用resolve将Promise状态更改为“完成”,链式调用中才能用then;比如Promise调用时必须添加catch方法,否则可能会报错;比如then方法中return的值是下一个链式调用的返回值。这些问题以前都遇到过,但直接使用时经常不在意,出现这样或那样的问题。

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这里给出一个简单的 MFC 五子棋功能代码示例,供参考: 在 MFC 对话框应用程序中,设计一个棋盘控件(IDC_BOARD)和若干个按钮,用于选择和播放盘记录。在对话框类的头文件中定义如下变量和函数: ``` // 用于保存棋盘状态的二维数组 int board[15][15]; // 用于保存盘记录的 vector,每个元素表示一步落子信息 vector<tuple<int, int, int>> replayRecord; // 当前盘到的步数 int replayStep; // 初始化棋盘状态和盘记录 void InitBoard(); // 根据盘记录还原棋盘状态 void ReplayStep(int step); ``` 在对话框类的源文件中,实现如下函数: ``` void CMyDlg::InitBoard() { memset(board, 0, sizeof(board)); replayRecord.clear(); replayStep = 0; Invalidate(); // 重绘棋盘 } void CMyDlg::ReplayStep(int step) { if (step < 0 || step >= replayRecord.size()) return; auto [x, y, player] = replayRecord[step]; board[x][y] = player; replayStep = step; Invalidate(); // 重绘棋盘 } void CMyDlg::OnBnClickedBtnOpen() { // 使用 CFileDialog 打开棋谱文件并读取盘记录 ... InitBoard(); ReplayStep(0); } void CMyDlg::OnBnClickedBtnPrev() { ReplayStep(replayStep - 1); } void CMyDlg::OnBnClickedBtnNext() { ReplayStep(replayStep + 1); } void CMyDlg::OnPaint() { CPaintDC dc(this); // 绘制棋盘和棋子 ... } ``` 其中,OnBnClickedBtnOpen() 函数用于打开棋谱文件并读取盘记录;OnBnClickedBtnPrev() 和 OnBnClickedBtnNext() 函数用于控制盘进度;OnPaint() 函数用于绘制棋盘和棋子。 需要注意的是,上述代码仅为示例,具体实现方式会因应用场景而异。在实际开发中,还需要考虑异常处理、保存棋谱文件等功能的实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值