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