scanonweb 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,45 +1,33 @@
1
1
  /**
2
- * ScanOnWeb - 扫描控件 JavaScript SDK
3
- * https://www.brainysoft.cn 扫描控件官方网站,如需更多文档帮助请访问官网
4
- * @version 1.0.3
5
- * @author BrainySoft
6
- * @license MIT
7
- */
8
-
9
- /**
10
- * 扫描控件类 - 用于与本地扫描服务程序通信
11
- * @class ScanOnWeb
2
+ * ScanOnWeb - JavaScript SDK for the local ScanOnWeb tray service.
3
+ * https://www.brainysoft.cn
4
+ * @version 2.0.0
12
5
  */
13
6
  class ScanOnWeb {
14
7
  constructor() {
15
- // 扫描配置参数信息,后续扫描仪开始扫描前会根据这个配置信息来进行扫描设置
8
+ this.protocolVersion = 2;
9
+ this.serverProtocolVersion = null;
10
+ this.imageCount = 0;
11
+
16
12
  this.scaner_work_config = {
17
- showUI: false, // 是否显示扫描控件工作界面,如果为false则不显示扫描控件工作界面
18
- dpi_x: 300, // dpi 分辨率x,一般的图像扫描300dpi就够了
19
- dpi_y: 300, // dpi 分辨率y
20
- deviceIndex: 0, // 选中的扫描仪硬件设备id索引,如果有多个扫描仪设备,这个值就是用来选择哪个设备进行扫描的
21
- showDialog: false, // 是否显示设备内置对话框
22
- autoFeedEnable: true, // 是否使用自动进纸器(需要设备支持才能正常工作)
23
- autoFeed: false, // 是否自动装填纸张(需要设备支持才能正常工作)
24
- dupxMode: false, // 是否使用双面扫描模式(需要设备支持才能正常工作)
25
- autoDeskew: false, // 是否使用自动纠偏模式(需要设备支持才能正常工作)
26
- autoBorderDetection: false, // 是否使用自动边框检测(需要设备支持才能正常工作)
27
- colorMode: "RGB", // 色彩模式,RGB为彩色模式,BW是黑白模式 ,GRAY是灰色模式
28
- transMode: "memory", // 数据传输模式,memory,file,native 这三种配置值,默认为memory模式,大部分设备都是使用这种模式
13
+ showUI: false,
14
+ dpi_x: 300,
15
+ dpi_y: 300,
16
+ deviceIndex: 0,
17
+ showDialog: false,
18
+ scanSource: "flatbed",
19
+ autoFeed: false,
20
+ dupxMode: false,
21
+ autoDeskew: false,
22
+ autoBorderDetection: false,
23
+ colorMode: "RGB",
24
+ transMode: "memory",
29
25
  };
30
26
 
31
27
  this.h5socket = null;
32
- this.imageCount = 0; // 扫描结果图像总数
33
-
34
- // 尝试连接websocket服务器,注意连接成功哪个是通过回调实现的
35
28
  this.tryConnect();
36
29
  }
37
30
 
38
- /**
39
- * 通过连接多个websocket server端口返回一个可用的websocket连接对象,主要用于防止本地端口被占用的情况
40
- * @param {string[]} wssUrls - WebSocket服务器URL数组
41
- * @returns {Promise} WebSocket连接Promise
42
- */
43
31
  getConnectedServer(wssUrls) {
44
32
  console.log("尝试连接托盘扫描服务websocket服务器...");
45
33
  return new Promise((resolve, reject) => {
@@ -53,15 +41,12 @@ class ScanOnWeb {
53
41
  }).then(
54
42
  (server) => {
55
43
  console.log("连接websocket服务器成功!");
56
- // 找到了可用websocket服务器端口
57
44
  this.initWebsocketCallback(server);
58
45
  console.log("尝试获取扫描设备列表...");
59
- // 发送一个获取扫描设备列表的命令,询问本地计算机安装了多少个扫描设备的驱动程序
60
46
  this.loadDevices();
61
47
  return server;
62
48
  },
63
49
  (err) => {
64
- // 如果连接失败,则尝试连接其他端口
65
50
  if (wssUrls.length > 1) {
66
51
  return this.getConnectedServer(wssUrls.slice(1));
67
52
  }
@@ -70,11 +55,7 @@ class ScanOnWeb {
70
55
  );
71
56
  }
72
57
 
73
- /**
74
- * 尝试检测websocket哪个端口可以成功连接
75
- */
76
58
  tryConnect() {
77
- // 以下一共定义了5个websocket端口,如果有端口被占用,则会自动尝试连接下一个端口
78
59
  const wssUrls = [
79
60
  "ws://127.0.0.1:1001",
80
61
  "ws://127.0.0.1:2001",
@@ -85,30 +66,17 @@ class ScanOnWeb {
85
66
  this.getConnectedServer(wssUrls);
86
67
  }
87
68
 
88
- /**
89
- * 初始化websocket相关的函数绑定
90
- * @param {WebSocket} server - WebSocket服务器实例
91
- */
92
69
  initWebsocketCallback(server) {
93
70
  this.h5socket = server;
94
71
  this.h5socket.onerror = this.onSocketError.bind(this);
95
72
  this.h5socket.onmessage = this.onSocketMessage.bind(this);
96
73
  }
97
74
 
98
- /**
99
- * WebSocket错误处理
100
- * @param {Event} event - 错误事件
101
- */
102
75
  onSocketError(event) {
103
76
  alert("无法连接扫描服务程序,请检查扫描服务程序是否已经启动!");
104
77
  console.log("WebSocket error: " + event.data);
105
78
  }
106
79
 
107
- /**
108
- * 判断回调函数是否存在
109
- * @param {Function} f - 要检查的函数
110
- * @returns {boolean} 函数是否存在且为函数类型
111
- */
112
80
  isCallbackExist(f) {
113
81
  if (!f || typeof f === "undefined" || f === undefined) {
114
82
  return false;
@@ -116,154 +84,267 @@ class ScanOnWeb {
116
84
  return typeof f === "function";
117
85
  }
118
86
 
119
- /**
120
- * WebSocket消息处理
121
- * @param {MessageEvent} event - WebSocket消息事件
122
- */
87
+ dispatchCallback(name, msg) {
88
+ if (this.isCallbackExist(this[name])) {
89
+ this[name](msg);
90
+ }
91
+ }
92
+
93
+ toBoolCompat(value) {
94
+ if (typeof value === "boolean") {
95
+ return value;
96
+ }
97
+ if (typeof value === "number") {
98
+ return value !== 0;
99
+ }
100
+ if (typeof value === "string") {
101
+ const normalized = value.trim().toLowerCase();
102
+ return ["1", "true", "yes", "y", "on", "adf"].includes(normalized);
103
+ }
104
+ return !!value;
105
+ }
106
+
107
+ toIntCompat(value, fallback) {
108
+ const parsed = Number(value);
109
+ return Number.isInteger(parsed) ? parsed : fallback;
110
+ }
111
+
112
+ normalizeScanSource(scanSource, legacyAutoFeedEnable) {
113
+ if (typeof scanSource === "string") {
114
+ const normalized = scanSource.trim().toLowerCase();
115
+ if (
116
+ normalized === "adf" ||
117
+ normalized === "feeder" ||
118
+ normalized === "documentfeeder" ||
119
+ normalized === "document_feeder" ||
120
+ normalized === "automatic_document_feeder"
121
+ ) {
122
+ return "adf";
123
+ }
124
+ if (
125
+ normalized === "flatbed" ||
126
+ normalized === "flat_bed" ||
127
+ normalized === "platen"
128
+ ) {
129
+ return "flatbed";
130
+ }
131
+ }
132
+ if (this.toBoolCompat(legacyAutoFeedEnable)) {
133
+ return "adf";
134
+ }
135
+ return "flatbed";
136
+ }
137
+
138
+ getLegacyAutoFeedEnable(scanSource) {
139
+ return this.normalizeScanSource(scanSource) === "adf";
140
+ }
141
+
142
+ shouldIncludeLegacyAutoFeedEnable() {
143
+ return (
144
+ this.serverProtocolVersion === null || this.serverProtocolVersion < 2
145
+ );
146
+ }
147
+
148
+ normalizeScanConfig(config, options) {
149
+ const merged = Object.assign({}, this.scaner_work_config, config || {});
150
+ const includeLegacyAutoFeedEnable =
151
+ !options || options.includeLegacyAutoFeedEnable !== false;
152
+ const normalized = {
153
+ showUI: this.toBoolCompat(merged.showUI),
154
+ dpi_x: this.toIntCompat(merged.dpi_x, 300),
155
+ dpi_y: this.toIntCompat(merged.dpi_y, 300),
156
+ deviceIndex: this.toIntCompat(merged.deviceIndex, 0),
157
+ showDialog: this.toBoolCompat(merged.showDialog),
158
+ scanSource: this.normalizeScanSource(
159
+ merged.scanSource,
160
+ merged.autoFeedEnable
161
+ ),
162
+ autoFeed: this.toBoolCompat(merged.autoFeed),
163
+ dupxMode: this.toBoolCompat(merged.dupxMode),
164
+ autoDeskew: this.toBoolCompat(merged.autoDeskew),
165
+ autoBorderDetection: this.toBoolCompat(merged.autoBorderDetection),
166
+ colorMode: merged.colorMode || "RGB",
167
+ transMode: merged.transMode || "memory",
168
+ };
169
+
170
+ if (includeLegacyAutoFeedEnable) {
171
+ normalized.autoFeedEnable = this.getLegacyAutoFeedEnable(
172
+ normalized.scanSource
173
+ );
174
+ }
175
+
176
+ return normalized;
177
+ }
178
+
179
+ recordServerProtocolVersion(msg) {
180
+ const version = this.toIntCompat(msg && msg.protocolVersion, 1);
181
+ this.serverProtocolVersion = version;
182
+ return version;
183
+ }
184
+
185
+ applyIncomingScanConfig(msg) {
186
+ this.scaner_work_config.deviceIndex = this.toIntCompat(
187
+ msg.currentIndex,
188
+ this.scaner_work_config.deviceIndex
189
+ );
190
+ if (Object.prototype.hasOwnProperty.call(msg, "showDialog")) {
191
+ this.scaner_work_config.showDialog = this.toBoolCompat(msg.showDialog);
192
+ }
193
+ this.scaner_work_config.scanSource = this.normalizeScanSource(
194
+ msg.scanSource,
195
+ msg.autoFeedEnable
196
+ );
197
+ if (Object.prototype.hasOwnProperty.call(msg, "autoFeed")) {
198
+ this.scaner_work_config.autoFeed = this.toBoolCompat(msg.autoFeed);
199
+ }
200
+ if (Object.prototype.hasOwnProperty.call(msg, "dupxMode")) {
201
+ this.scaner_work_config.dupxMode = this.toBoolCompat(msg.dupxMode);
202
+ }
203
+ if (Object.prototype.hasOwnProperty.call(msg, "autoDeskew")) {
204
+ this.scaner_work_config.autoDeskew = this.toBoolCompat(msg.autoDeskew);
205
+ }
206
+ if (Object.prototype.hasOwnProperty.call(msg, "autoBorderDetection")) {
207
+ this.scaner_work_config.autoBorderDetection = this.toBoolCompat(
208
+ msg.autoBorderDetection
209
+ );
210
+ }
211
+ if (typeof msg.colorMode === "string" && msg.colorMode) {
212
+ this.scaner_work_config.colorMode = msg.colorMode;
213
+ }
214
+ if (typeof msg.transMode === "string" && msg.transMode) {
215
+ this.scaner_work_config.transMode = msg.transMode;
216
+ }
217
+ this.scaner_work_config.autoFeedEnable = this.getLegacyAutoFeedEnable(
218
+ this.scaner_work_config.scanSource
219
+ );
220
+ }
221
+
222
+ normalizeIncomingMessage(rawMsg) {
223
+ const msg = Object.assign({}, rawMsg || {});
224
+ const protocolVersion = this.recordServerProtocolVersion(msg);
225
+ const rawType = typeof msg.cmd_type === "string" ? msg.cmd_type : "";
226
+
227
+ if (!msg.scanSource) {
228
+ msg.scanSource = this.normalizeScanSource(
229
+ msg.scanSource,
230
+ msg.autoFeedEnable
231
+ );
232
+ }
233
+
234
+ switch (rawType) {
235
+ case "getAllImage":
236
+ msg.cmd_type = "imageListSnapshot";
237
+ break;
238
+ case "getImageById":
239
+ msg.cmd_type = "imageSnapshot";
240
+ break;
241
+ case "imageDrap":
242
+ msg.cmd_type = "imageMoved";
243
+ break;
244
+ default:
245
+ break;
246
+ }
247
+
248
+ if (!msg.protocolVersion) {
249
+ msg.protocolVersion = protocolVersion;
250
+ }
251
+ return msg;
252
+ }
253
+
123
254
  onSocketMessage(event) {
124
- const msg = JSON.parse(event.data);
125
- // console.log(msg);
255
+ const rawMsg = JSON.parse(event.data);
256
+ const msg = this.normalizeIncomingMessage(rawMsg);
257
+
258
+ if (typeof msg.imageCount === "number") {
259
+ this.imageCount = msg.imageCount;
260
+ }
261
+
126
262
  switch (msg.cmd_type) {
127
- case "getDevicesList": {
128
- // 获取设备信息列表返回结果
129
- if (this.isCallbackExist(this.onGetDevicesListEvent)) {
130
- this.onGetDevicesListEvent(msg);
131
- }
263
+ case "getDevicesList":
264
+ this.dispatchCallback("onGetDevicesListEvent", msg);
132
265
  break;
133
- }
134
- case "scanComplete": {
135
- // 扫描完成
136
- this.imageCount = msg.imageCount;
137
- if (this.isCallbackExist(this.onScanFinishedEvent)) {
138
- this.onScanFinishedEvent(msg);
139
- }
266
+ case "scanComplete":
267
+ this.dispatchCallback("onScanFinishedEvent", msg);
140
268
  break;
141
- }
142
- case "selectScanDevice": {
143
- // 选择扫描设备结果
144
- this.scaner_work_config.deviceIndex = msg.currentIndex; // 当前选中的设备索引
145
- this.scaner_work_config.showDialog = msg.showDialog; // 是否显示设备内置对话框
146
- this.scaner_work_config.autoFeedEnable = msg.autoFeedEnable; // 是否使用自动进纸器(需要设备支持才能正常工作)
147
- this.scaner_work_config.autoFeed = msg.autoFeed; // 是否自动装填纸张(需要设备支持才能正常工作)
148
- this.scaner_work_config.dupxMode = msg.dupxMode; // 是否使用双面扫描模式(需要设备支持才能正常工作)
149
- this.scaner_work_config.autoDeskew = msg.autoDeskew; // 是否使用自动纠偏模式(需要设备支持才能正常工作)
150
- this.scaner_work_config.autoBorderDetection = msg.autoBorderDetection; // 是否使用自动边框检测(需要设备支持才能正常工作)
151
-
152
- if (this.isCallbackExist(this.onSelectScanDeviceEvent)) {
153
- this.onSelectScanDeviceEvent(msg);
154
- }
269
+ case "selectScanDevice":
270
+ this.applyIncomingScanConfig(msg);
271
+ this.dispatchCallback("onSelectScanDeviceEvent", msg);
155
272
  break;
156
- }
157
- case "getImageCount": {
158
- // 获取扫描图片数量
159
- this.imageCount = msg.imageCount;
160
- if (this.isCallbackExist(this.onGetImageCountEvent)) {
161
- this.onGetImageCountEvent(msg);
162
- }
273
+ case "getImageCount":
274
+ this.dispatchCallback("onGetImageCountEvent", msg);
163
275
  break;
164
- }
165
- case "getAllImage": {
166
- // 获取所有图片
167
- this.imageCount = msg.imageCount;
168
- if (this.isCallbackExist(this.onGetAllImageEvent)) {
169
- this.onGetAllImageEvent(msg);
170
- }
276
+ case "imageListSnapshot":
277
+ this.dispatchCallback("onImageListSnapshotEvent", msg);
278
+ this.dispatchCallback("onGetAllImageEvent", msg);
171
279
  break;
172
- }
173
- case "getImageById": {
174
- // 获取某一页图片的base64编码数据
175
- this.imageCount = msg.imageCount;
176
- if (this.isCallbackExist(this.onGetImageByIdEvent)) {
177
- this.onGetImageByIdEvent(msg);
178
- }
280
+ case "imageSnapshot":
281
+ this.dispatchCallback("onImageSnapshotEvent", msg);
282
+ this.dispatchCallback("onGetImageByIdEvent", msg);
179
283
  break;
180
- }
181
- case "loadImageFromUrl": {
182
- // 从URL加载图片
183
- this.imageCount = msg.imageCount;
184
- if (this.isCallbackExist(this.onLoadImageFromUrlEvent)) {
185
- this.onLoadImageFromUrlEvent(msg);
186
- }
284
+ case "scanPageAdded":
285
+ this.dispatchCallback("onScanPageAddedEvent", msg);
187
286
  break;
188
- }
189
- case "loadImageFromBase64": {
190
- // 从Base64数据加载图片
191
- this.imageCount = msg.imageCount;
192
- if (this.isCallbackExist(this.onLoadImageFromBase64Event)) {
193
- this.onLoadImageFromBase64Event(msg);
194
- }
287
+ case "loadImageFromUrl":
288
+ this.dispatchCallback("onLoadImageFromUrlEvent", msg);
195
289
  break;
196
- }
197
- case "rotateImage": {
198
- // 旋转图片
199
- this.imageCount = msg.imageCount;
200
- if (this.isCallbackExist(this.onRotateImageEvent)) {
201
- this.onRotateImageEvent(msg);
202
- }
290
+ case "loadImageFromBase64":
291
+ this.dispatchCallback("onLoadImageFromBase64Event", msg);
203
292
  break;
204
- }
205
- case "getImageSize": {
206
- // 获取图片尺寸
207
- this.imageCount = msg.imageCount;
208
- if (this.isCallbackExist(this.onGetImageSizeEvent)) {
209
- this.onGetImageSizeEvent(msg);
210
- }
293
+ case "rotateImage":
294
+ this.dispatchCallback("onRotateImageEvent", msg);
211
295
  break;
212
- }
213
- case "uploadAllImageAsPdfToUrl": {
214
- // 上传pdf图像到指定的URL的回调
215
- if (this.isCallbackExist(this.onUploadAllImageAsPdfToUrlEvent)) {
216
- this.onUploadAllImageAsPdfToUrlEvent(msg);
217
- }
296
+ case "getImageSize":
297
+ this.dispatchCallback("onGetImageSizeEvent", msg);
218
298
  break;
219
- }
220
- case "uploadAllImageAsTiffToUrl": {
221
- // 上传tiff图像到指定的URL的回调
222
- if (this.isCallbackExist(this.onUploadAllImageAsTiffToUrlEvent)) {
223
- this.onUploadAllImageAsTiffToUrlEvent(msg);
224
- }
299
+ case "uploadAllImageAsPdfToUrl":
300
+ this.dispatchCallback("onUploadAllImageAsPdfToUrlEvent", msg);
225
301
  break;
226
- }
227
- case "uploadJpgImageByIndex": {
228
- // 上传jpg图像到指定的URL的回调
229
- if (this.isCallbackExist(this.onUploadJpgImageByIndexEvent)) {
230
- this.onUploadJpgImageByIndexEvent(msg);
231
- }
302
+ case "uploadAllImageAsTiffToUrl":
303
+ this.dispatchCallback("onUploadAllImageAsTiffToUrlEvent", msg);
232
304
  break;
233
- }
234
- case "upload": {
235
- this.imageCount = msg.imageCount;
236
- // 用户点击了界面里面的"开始上传"按钮的时间回调
237
- if (this.isCallbackExist(this.onUploadEvent)) {
238
- this.onUploadEvent(msg);
239
- }
305
+ case "uploadJpgImageByIndex":
306
+ this.dispatchCallback("onUploadJpgImageByIndexEvent", msg);
240
307
  break;
241
- }
242
- case "imageEdited": {
243
- // 用户在扫描托盘程序里面对图片进行了编辑操作的回调
244
- if (this.isCallbackExist(this.onImageEditedEvent)) {
245
- this.onImageEditedEvent(msg);
246
- }
308
+ case "upload":
309
+ this.dispatchCallback("onUploadEvent", msg);
247
310
  break;
248
- }
249
- case "imageDrap": {
250
- // 用户在扫描托盘程序里面对图片进行了拖拽操作的回调
251
- if (this.isCallbackExist(this.onImageDrapEvent)) {
252
- this.onImageDrapEvent(msg);
253
- }
311
+ case "imageEdited":
312
+ this.dispatchCallback("onImageEditedEvent", msg);
254
313
  break;
255
- }
314
+ case "imageMoved":
315
+ this.dispatchCallback("onImageMovedEvent", msg);
316
+ this.dispatchCallback("onImageDrapEvent", msg);
317
+ break;
318
+ case "imageDeleted":
319
+ this.dispatchCallback("onImageDeletedEvent", msg);
320
+ break;
321
+ case "imagesCleared":
322
+ this.dispatchCallback("onImagesClearedEvent", msg);
323
+ break;
324
+ default:
325
+ break;
326
+ }
327
+ }
328
+
329
+ normalizeOutgoingCommand(commandData) {
330
+ const cmd = Object.assign({}, commandData || {});
331
+ cmd.protocolVersion = this.protocolVersion;
332
+
333
+ if (cmd.cmd_type === "startScan") {
334
+ cmd.config = this.normalizeScanConfig(cmd.config || this.scaner_work_config, {
335
+ includeLegacyAutoFeedEnable: this.shouldIncludeLegacyAutoFeedEnable(),
336
+ });
256
337
  }
338
+
339
+ return cmd;
257
340
  }
258
341
 
259
- /**
260
- * 通过websocket发送数据给webscoket服务端
261
- * @param {Object} commandData - 要发送的命令数据
262
- */
263
342
  sendWebSocketCommand(commandData) {
264
343
  try {
265
- if (this.h5socket.readyState === 1) {
266
- this.h5socket.send(JSON.stringify(commandData));
344
+ if (this.h5socket && this.h5socket.readyState === 1) {
345
+ this.h5socket.send(
346
+ JSON.stringify(this.normalizeOutgoingCommand(commandData))
347
+ );
267
348
  } else {
268
349
  alert(
269
350
  "发送扫描指令失败!请刷新页面或者检查托盘扫描程序是否已经正常运行!"
@@ -274,292 +355,190 @@ class ScanOnWeb {
274
355
  }
275
356
  }
276
357
 
277
- /**
278
- * 设置授权信息
279
- * @param {string} licenseMode - 授权模式
280
- * @param {string} key1 - 授权密钥1
281
- * @param {string} key2 - 授权密钥2
282
- * @param {string} licenseServerUrl - 授权服务器URL
283
- */
284
358
  setLicenseKey(licenseMode, key1, key2, licenseServerUrl) {
285
- const cmdObj = {
359
+ this.sendWebSocketCommand({
286
360
  cmd_type: "setLicenseKey",
287
361
  licenseMode: licenseMode,
288
362
  key1: key1,
289
363
  key2: key2,
290
364
  url: licenseServerUrl,
291
- };
292
- this.sendWebSocketCommand(cmdObj);
365
+ });
293
366
  }
294
367
 
295
- /**
296
- * 加载所有可用的扫描设备
297
- */
298
368
  loadDevices() {
299
- const cmdObj = {
369
+ this.sendWebSocketCommand({
300
370
  cmd_type: "getDevicesList",
301
- };
302
- this.sendWebSocketCommand(cmdObj);
371
+ });
303
372
  }
304
373
 
305
- /**
306
- * 设置当前选中的扫描设备id
307
- * @param {number} deviceIndex - 设备索引
308
- */
309
374
  selectScanDevice(deviceIndex) {
310
- const cmdObj = {
375
+ this.sendWebSocketCommand({
311
376
  cmd_type: "selectScanDevice",
312
377
  deviceIndex: deviceIndex,
313
- };
314
- this.sendWebSocketCommand(cmdObj);
378
+ });
315
379
  }
316
380
 
317
- /**
318
- * 开始扫描
319
- */
320
381
  startScan() {
321
- const cmdObj = {
382
+ this.sendWebSocketCommand({
322
383
  cmd_type: "startScan",
323
384
  config: this.scaner_work_config,
324
- };
325
- this.sendWebSocketCommand(cmdObj);
385
+ });
326
386
  }
327
387
 
328
- /**
329
- * 清除全部扫描结果
330
- */
331
388
  clearAll() {
332
- const cmdObj = {
389
+ this.sendWebSocketCommand({
333
390
  cmd_type: "clearAll",
334
- };
335
- this.sendWebSocketCommand(cmdObj);
391
+ });
336
392
  }
337
393
 
338
- /**
339
- * 获取图像总数
340
- */
341
394
  getImageCount() {
342
- const cmdObj = {
395
+ this.sendWebSocketCommand({
343
396
  cmd_type: "getImageCount",
344
- };
345
- this.sendWebSocketCommand(cmdObj);
397
+ });
346
398
  }
347
399
 
348
- /**
349
- * 获取所有图像
350
- */
351
400
  getAllImage() {
352
- const cmdObj = {
401
+ this.sendWebSocketCommand({
353
402
  cmd_type: "getAllImage",
354
- };
355
- this.sendWebSocketCommand(cmdObj);
403
+ });
356
404
  }
357
405
 
358
- /**
359
- * 发送指令获取某一页图像到托盘服务
360
- * @param {number} index - 图像索引
361
- */
362
- getImageById(index) {
363
- const cmdObj = {
406
+ getImageById(indexOrImageId) {
407
+ const command = {
364
408
  cmd_type: "getImageById",
365
- index: index,
366
409
  };
367
- this.sendWebSocketCommand(cmdObj);
410
+ if (typeof indexOrImageId === "string" && indexOrImageId.trim()) {
411
+ command.imageId = indexOrImageId.trim();
412
+ } else {
413
+ command.index = indexOrImageId;
414
+ }
415
+ this.sendWebSocketCommand(command);
368
416
  }
369
417
 
370
- /**
371
- * 发送指令远程加载服务器端的多页图像到托盘服务
372
- * 支持PDF、TIFF以及常见图像格式(JPG、PNG、BMP等)
373
- * @param {string} url - 图像URL
374
- * @param {string} [type] - 可选,指定文件类型:'pdf'、'tiff'、'image',如果不指定则根据URL扩展名自动判断
375
- */
376
418
  loadImageFromUrl(url, type) {
377
- // 如果没有指定类型,则根据URL扩展名自动判断
378
419
  if (!type) {
379
- const extension = url.split('.').pop().toLowerCase();
380
- if (extension === 'pdf') {
381
- type = 'pdf';
382
- } else if (extension === 'tiff' || extension === 'tif') {
383
- type = 'tiff';
384
- } else if (['jpg', 'jpeg', 'png', 'bmp', 'gif', 'webp'].includes(extension)) {
385
- type = 'image';
420
+ const extension = url.split(".").pop().toLowerCase();
421
+ if (extension === "pdf") {
422
+ type = "pdf";
423
+ } else if (extension === "tiff" || extension === "tif") {
424
+ type = "tiff";
425
+ } else if (
426
+ ["jpg", "jpeg", "png", "bmp", "gif", "webp"].includes(extension)
427
+ ) {
428
+ type = "image";
386
429
  } else {
387
- // 默认当作普通图像处理
388
- type = 'image';
430
+ type = "image";
389
431
  }
390
432
  }
391
-
392
- const cmdObj = {
433
+
434
+ this.sendWebSocketCommand({
393
435
  cmd_type: "loadImageFromUrl",
394
436
  url: url,
395
- type: type
396
- };
397
- this.sendWebSocketCommand(cmdObj);
398
- }
399
-
400
- /**
401
- * 发送指令从Base64编码数据加载图像到托盘服务
402
- * @param {string} base64Data - Base64编码的图像数据(不包含data:image/xxx;base64,前缀)
403
- * @param {string} [format='jpg'] - 图像格式,如:'jpg'、'png'、'bmp'等,默认为'jpg'
404
- */
405
- loadImageFromBase64(base64Data, format = 'jpg') {
406
- // 如果base64数据包含data URL前缀,则去除它
407
- if (base64Data.startsWith('data:')) {
408
- const base64Index = base64Data.indexOf('base64,');
437
+ type: type,
438
+ });
439
+ }
440
+
441
+ loadImageFromBase64(base64Data, format) {
442
+ let normalizedFormat = format || "jpg";
443
+ let normalizedBase64 = base64Data;
444
+
445
+ if (normalizedBase64.startsWith("data:")) {
446
+ const base64Index = normalizedBase64.indexOf("base64,");
409
447
  if (base64Index !== -1) {
410
- // 尝试从data URL中提取格式信息
411
- const mimeMatch = base64Data.match(/data:image\/(\w+);/);
412
- if (mimeMatch && mimeMatch[1] && !format) {
413
- format = mimeMatch[1] === 'jpeg' ? 'jpg' : mimeMatch[1];
448
+ if (!format) {
449
+ const mimeMatch = normalizedBase64.match(/data:image\/(\w+);/);
450
+ if (mimeMatch && mimeMatch[1]) {
451
+ normalizedFormat = mimeMatch[1] === "jpeg" ? "jpg" : mimeMatch[1];
452
+ }
414
453
  }
415
- // 提取纯Base64数据
416
- base64Data = base64Data.substring(base64Index + 7);
454
+ normalizedBase64 = normalizedBase64.substring(base64Index + 7);
417
455
  }
418
456
  }
419
-
420
- const cmdObj = {
457
+
458
+ this.sendWebSocketCommand({
421
459
  cmd_type: "loadImageFromBase64",
422
- base64Data: base64Data,
423
- format: format
424
- };
425
- this.sendWebSocketCommand(cmdObj);
460
+ base64Data: normalizedBase64,
461
+ format: normalizedFormat,
462
+ });
426
463
  }
427
464
 
428
- /**
429
- * 发送指令旋转某一页图像到托盘服务
430
- * @param {number} index - 图像索引
431
- * @param {number} angle - 旋转角度
432
- */
433
465
  rotateImage(index, angle) {
434
- const cmdObj = {
466
+ this.sendWebSocketCommand({
435
467
  cmd_type: "rotateImage",
436
468
  index: index,
437
469
  angle: angle,
438
- };
439
- this.sendWebSocketCommand(cmdObj);
470
+ });
440
471
  }
441
472
 
442
- /**
443
- * 发送指令获取某一页图像的宽度到托盘服务
444
- * @param {number} index - 图像索引
445
- */
446
473
  getImageSize(index) {
447
- const cmdObj = {
474
+ this.sendWebSocketCommand({
448
475
  cmd_type: "getImageSize",
449
476
  index: index,
450
- };
451
- this.sendWebSocketCommand(cmdObj);
477
+ });
452
478
  }
453
479
 
454
- /**
455
- * 发送指令删除某一页图像到托盘服务
456
- * @param {number} index - 图像索引
457
- */
458
- deleteImageByIndex(index) {
459
- const cmdObj = {
480
+ deleteImageByIndex(index, imageId) {
481
+ const command = {
460
482
  cmd_type: "deleteImageByIndex",
461
483
  index: index,
462
484
  };
463
- this.sendWebSocketCommand(cmdObj);
485
+ if (typeof imageId === "string" && imageId.trim()) {
486
+ command.imageId = imageId.trim();
487
+ }
488
+ this.sendWebSocketCommand(command);
464
489
  }
465
490
 
466
- /**
467
- * 前端调整扫描结果图像顺序
468
- * @param {number} oldIndex - 原图像索引
469
- * @param {number} newIndex - 目标图像索引
470
- */
471
491
  moveImage(oldIndex, newIndex) {
472
- const cmdObj = {
492
+ this.sendWebSocketCommand({
473
493
  cmd_type: "moveImage",
474
494
  oldIndex: oldIndex,
475
495
  newIndex: newIndex,
476
- };
477
- this.sendWebSocketCommand(cmdObj);
496
+ });
478
497
  }
479
498
 
480
- /**
481
- * 以pdf格式上传全部图像到服务器端
482
- * @param {string} url - 上传URL
483
- * @param {string} id - 标识ID
484
- * @param {string} desc - 描述信息
485
- */
486
499
  uploadAllImageAsPdfToUrl(url, id, desc) {
487
- const cmdObj = {
500
+ this.sendWebSocketCommand({
488
501
  cmd_type: "uploadAllImageAsPdfToUrl",
489
502
  url: url,
490
503
  id: id,
491
504
  desc: desc,
492
- };
493
- this.sendWebSocketCommand(cmdObj);
505
+ });
494
506
  }
495
507
 
496
- /**
497
- * 以tiff格式上传全部图像到服务器端
498
- * @param {string} url - 上传URL
499
- * @param {string} id - 标识ID
500
- * @param {string} desc - 描述信息
501
- */
502
508
  uploadAllImageAsTiffToUrl(url, id, desc) {
503
- const cmdObj = {
509
+ this.sendWebSocketCommand({
504
510
  cmd_type: "uploadAllImageAsTiffToUrl",
505
511
  url: url,
506
512
  id: id,
507
513
  desc: desc,
508
- };
509
- this.sendWebSocketCommand(cmdObj);
514
+ });
510
515
  }
511
516
 
512
- /**
513
- * 以jpg格式上传某一页图像到服务器端
514
- * @param {string} url - 上传URL
515
- * @param {string} id - 标识ID
516
- * @param {string} desc - 描述信息
517
- * @param {number} index - 图像索引
518
- */
519
517
  uploadJpgImageByIndex(url, id, desc, index) {
520
- const cmdObj = {
518
+ this.sendWebSocketCommand({
521
519
  cmd_type: "uploadJpgImageByIndex",
522
520
  index: index,
523
521
  url: url,
524
522
  id: id,
525
523
  desc: desc,
526
- };
527
- this.sendWebSocketCommand(cmdObj);
524
+ });
528
525
  }
529
526
 
530
- /**
531
- * 全部图像保存到客户端本地文件
532
- * @param {string} filename - 文件名
533
- */
534
527
  saveAllImageToLocal(filename) {
535
- const cmdObj = {
528
+ this.sendWebSocketCommand({
536
529
  cmd_type: "saveAllImageToLocal",
537
530
  filename: filename,
538
- };
539
- this.sendWebSocketCommand(cmdObj);
531
+ });
540
532
  }
541
533
 
542
- /**
543
- * 从客户端本地读取图像,通过打开文件对话框选择图像文件
544
- */
545
534
  openClientLocalfile() {
546
- const cmdObj = {
535
+ this.sendWebSocketCommand({
547
536
  cmd_type: "openClientLocalfile",
548
- };
549
- this.sendWebSocketCommand(cmdObj);
550
- }
551
-
552
- /**
553
- * ftp上传全部图像文件到服务器端
554
- * @param {string} serverIp - 服务器IP
555
- * @param {number} port - 端口
556
- * @param {string} username - 用户名
557
- * @param {string} password - 密码
558
- * @param {string} serverPath - 服务器路径
559
- * @param {string} filename - 文件名
560
- */
537
+ });
538
+ }
539
+
561
540
  ftpUploadAllImage(serverIp, port, username, password, serverPath, filename) {
562
- const cmdObj = {
541
+ this.sendWebSocketCommand({
563
542
  cmd_type: "ftpUploadAllImage",
564
543
  serverIp: serverIp,
565
544
  port: port,
@@ -567,117 +546,80 @@ class ScanOnWeb {
567
546
  password: password,
568
547
  serverPath: serverPath,
569
548
  filename: filename,
570
- };
571
- this.sendWebSocketCommand(cmdObj);
549
+ });
572
550
  }
573
551
 
574
- /**
575
- * 设置上传按钮是否可见
576
- * @param {boolean} visible - 是否可见
577
- */
578
552
  setUploadButtonVisible(visible) {
579
- const cmdObj = {
553
+ this.sendWebSocketCommand({
580
554
  cmd_type: "setUploadButtonVisible",
581
555
  visible: visible,
582
- };
583
- this.sendWebSocketCommand(cmdObj);
556
+ });
584
557
  }
585
558
 
586
- /**
587
- * 设置焦点
588
- */
589
559
  setFocus() {
590
- const cmdObj = {
560
+ this.sendWebSocketCommand({
591
561
  cmd_type: "focus",
592
- };
593
- this.sendWebSocketCommand(cmdObj);
562
+ });
594
563
  }
595
564
 
596
- /**
597
- * 隐藏窗口
598
- */
599
565
  hidden() {
600
- const cmdObj = {
566
+ this.sendWebSocketCommand({
601
567
  cmd_type: "hidden",
602
- };
603
- this.sendWebSocketCommand(cmdObj);
568
+ });
604
569
  }
605
570
 
606
- /**
607
- * 关闭websocket连接
608
- */
609
571
  closeWebSocket() {
610
572
  this.h5socket.close();
611
573
  }
612
574
 
613
- /**
614
- * 批量从Base64数据加载多个图像
615
- * @param {Array} base64Images - Base64图像数组,每个元素为 {data: 'base64字符串', format: '格式'}
616
- */
617
575
  loadMultipleImagesFromBase64(base64Images) {
618
576
  if (!Array.isArray(base64Images) || base64Images.length === 0) {
619
- console.error('loadMultipleImagesFromBase64: 参数必须是非空数组');
577
+ console.error("loadMultipleImagesFromBase64: 参数必须是非空数组");
620
578
  return;
621
579
  }
622
-
623
- // 逐个加载图像
580
+
624
581
  base64Images.forEach((image, index) => {
625
582
  setTimeout(() => {
626
- this.loadImageFromBase64(image.data, image.format || 'jpg');
627
- }, index * 100); // 每个图像间隔100ms,避免同时发送过多数据
583
+ this.loadImageFromBase64(image.data, image.format || "jpg");
584
+ }, index * 100);
628
585
  });
629
586
  }
630
587
 
631
- /**
632
- * 从Canvas元素加载图像
633
- * @param {HTMLCanvasElement} canvas - Canvas元素
634
- * @param {string} [format='jpg'] - 图像格式
635
- * @param {number} [quality=0.9] - 图像质量(0-1之间,仅对jpg格式有效)
636
- */
637
- loadImageFromCanvas(canvas, format = 'jpg', quality = 0.9) {
588
+ loadImageFromCanvas(canvas, format, quality) {
589
+ const normalizedFormat = format || "jpg";
590
+ const normalizedQuality = typeof quality === "number" ? quality : 0.9;
591
+
638
592
  if (!canvas || !(canvas instanceof HTMLCanvasElement)) {
639
- console.error('loadImageFromCanvas: 参数必须是有效的Canvas元素');
593
+ console.error("loadImageFromCanvas: 参数必须是有效的Canvas元素");
640
594
  return;
641
595
  }
642
-
643
- // 将Canvas转换为Base64
644
- const mimeType = format === 'jpg' ? 'image/jpeg' : `image/${format}`;
645
- const dataUrl = canvas.toDataURL(mimeType, quality);
646
-
647
- // 提取Base64数据部分
648
- const base64Data = dataUrl.split(',')[1];
649
-
650
- // 调用Base64加载方法
651
- this.loadImageFromBase64(base64Data, format);
652
- }
653
-
654
- /**
655
- * 从文件输入元素加载图像
656
- * @param {HTMLInputElement} fileInput - 文件输入元素
657
- */
596
+
597
+ const mimeType =
598
+ normalizedFormat === "jpg" ? "image/jpeg" : "image/" + normalizedFormat;
599
+ const dataUrl = canvas.toDataURL(mimeType, normalizedQuality);
600
+ const base64Data = dataUrl.split(",")[1];
601
+ this.loadImageFromBase64(base64Data, normalizedFormat);
602
+ }
603
+
658
604
  loadImageFromFileInput(fileInput) {
659
605
  if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
660
- console.error('loadImageFromFileInput: 没有选择文件');
606
+ console.error("loadImageFromFileInput: 没有选择文件");
661
607
  return;
662
608
  }
663
-
609
+
664
610
  const files = Array.from(fileInput.files);
665
-
666
611
  files.forEach((file, index) => {
667
- // 检查是否为图像文件
668
- if (!file.type.startsWith('image/')) {
669
- console.warn(`跳过非图像文件: ${file.name}`);
612
+ if (!file.type.startsWith("image/")) {
613
+ console.warn("跳过非图像文件: " + file.name);
670
614
  return;
671
615
  }
672
-
616
+
673
617
  const reader = new FileReader();
674
618
  reader.onload = (e) => {
675
619
  const dataUrl = e.target.result;
676
- // 获取文件扩展名
677
- const extension = file.name.split('.').pop().toLowerCase();
678
- const format = extension === 'jpeg' ? 'jpg' : extension;
679
-
680
- // 延迟加载,避免同时处理过多文件
620
+ const extension = file.name.split(".").pop().toLowerCase();
621
+ const format = extension === "jpeg" ? "jpg" : extension;
622
+
681
623
  setTimeout(() => {
682
624
  this.loadImageFromBase64(dataUrl, format);
683
625
  }, index * 100);
@@ -687,15 +629,12 @@ class ScanOnWeb {
687
629
  }
688
630
  }
689
631
 
690
- // ES6 模块导出
691
632
  export default ScanOnWeb;
692
633
 
693
- // CommonJS 兼容性导出
694
634
  if (typeof module !== "undefined" && module.exports) {
695
635
  module.exports = ScanOnWeb;
696
636
  }
697
637
 
698
- // UMD 兼容性导出
699
638
  if (typeof window !== "undefined") {
700
639
  window.ScanOnWeb = ScanOnWeb;
701
640
  }