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