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