scanonweb 1.0.2 → 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,65 +1,40 @@
1
1
  /*!
2
- * scanonweb v1.0.2
3
- * ScanOnWeb - 扫描控件 JavaScript SDK,用于与本地扫描服务程序通信
2
+ * scanonweb v2.0.0
3
+ * ScanOnWeb - 扫描控件 JavaScript SDK,支持 v2 协议、scanSource 和增量图像事件
4
4
  * https://www.brainysoft.cn
5
5
  *
6
- * Copyright (c) 2025 BrainySoft
6
+ * Copyright (c) 2026 BrainySoft
7
7
  * Licensed under the MIT license
8
8
  */
9
9
  'use strict';
10
10
 
11
11
  /**
12
- * ScanOnWeb - 扫描控件 JavaScript SDK
13
- * https://www.brainysoft.cn 扫描控件官方网站,如需更多文档帮助请访问官网
14
- * @version 1.0.1
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,277 +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);
404
+ }
405
+ moveImage(oldIndex, newIndex) {
406
+ this.sendWebSocketCommand({
407
+ cmd_type: "moveImage",
408
+ oldIndex: oldIndex,
409
+ newIndex: newIndex
410
+ });
487
411
  }
488
-
489
- /**
490
- * 以pdf格式上传全部图像到服务器端
491
- * @param {string} url - 上传URL
492
- * @param {string} id - 标识ID
493
- * @param {string} desc - 描述信息
494
- */
495
412
  uploadAllImageAsPdfToUrl(url, id, desc) {
496
- const cmdObj = {
413
+ this.sendWebSocketCommand({
497
414
  cmd_type: "uploadAllImageAsPdfToUrl",
498
415
  url: url,
499
416
  id: id,
500
417
  desc: desc
501
- };
502
- this.sendWebSocketCommand(cmdObj);
418
+ });
503
419
  }
504
-
505
- /**
506
- * 以tiff格式上传全部图像到服务器端
507
- * @param {string} url - 上传URL
508
- * @param {string} id - 标识ID
509
- * @param {string} desc - 描述信息
510
- */
511
420
  uploadAllImageAsTiffToUrl(url, id, desc) {
512
- const cmdObj = {
421
+ this.sendWebSocketCommand({
513
422
  cmd_type: "uploadAllImageAsTiffToUrl",
514
423
  url: url,
515
424
  id: id,
516
425
  desc: desc
517
- };
518
- this.sendWebSocketCommand(cmdObj);
426
+ });
519
427
  }
520
-
521
- /**
522
- * 以jpg格式上传某一页图像到服务器端
523
- * @param {string} url - 上传URL
524
- * @param {string} id - 标识ID
525
- * @param {string} desc - 描述信息
526
- * @param {number} index - 图像索引
527
- */
528
428
  uploadJpgImageByIndex(url, id, desc, index) {
529
- const cmdObj = {
429
+ this.sendWebSocketCommand({
530
430
  cmd_type: "uploadJpgImageByIndex",
531
431
  index: index,
532
432
  url: url,
533
433
  id: id,
534
434
  desc: desc
535
- };
536
- this.sendWebSocketCommand(cmdObj);
435
+ });
537
436
  }
538
-
539
- /**
540
- * 全部图像保存到客户端本地文件
541
- * @param {string} filename - 文件名
542
- */
543
437
  saveAllImageToLocal(filename) {
544
- const cmdObj = {
438
+ this.sendWebSocketCommand({
545
439
  cmd_type: "saveAllImageToLocal",
546
440
  filename: filename
547
- };
548
- this.sendWebSocketCommand(cmdObj);
441
+ });
549
442
  }
550
-
551
- /**
552
- * 从客户端本地读取图像,通过打开文件对话框选择图像文件
553
- */
554
443
  openClientLocalfile() {
555
- const cmdObj = {
444
+ this.sendWebSocketCommand({
556
445
  cmd_type: "openClientLocalfile"
557
- };
558
- this.sendWebSocketCommand(cmdObj);
446
+ });
559
447
  }
560
-
561
- /**
562
- * ftp上传全部图像文件到服务器端
563
- * @param {string} serverIp - 服务器IP
564
- * @param {number} port - 端口
565
- * @param {string} username - 用户名
566
- * @param {string} password - 密码
567
- * @param {string} serverPath - 服务器路径
568
- * @param {string} filename - 文件名
569
- */
570
448
  ftpUploadAllImage(serverIp, port, username, password, serverPath, filename) {
571
- const cmdObj = {
449
+ this.sendWebSocketCommand({
572
450
  cmd_type: "ftpUploadAllImage",
573
451
  serverIp: serverIp,
574
452
  port: port,
@@ -576,114 +454,66 @@ class ScanOnWeb {
576
454
  password: password,
577
455
  serverPath: serverPath,
578
456
  filename: filename
579
- };
580
- this.sendWebSocketCommand(cmdObj);
457
+ });
581
458
  }
582
-
583
- /**
584
- * 设置上传按钮是否可见
585
- * @param {boolean} visible - 是否可见
586
- */
587
459
  setUploadButtonVisible(visible) {
588
- const cmdObj = {
460
+ this.sendWebSocketCommand({
589
461
  cmd_type: "setUploadButtonVisible",
590
462
  visible: visible
591
- };
592
- this.sendWebSocketCommand(cmdObj);
463
+ });
593
464
  }
594
-
595
- /**
596
- * 设置焦点
597
- */
598
465
  setFocus() {
599
- const cmdObj = {
466
+ this.sendWebSocketCommand({
600
467
  cmd_type: "focus"
601
- };
602
- this.sendWebSocketCommand(cmdObj);
468
+ });
603
469
  }
604
-
605
- /**
606
- * 隐藏窗口
607
- */
608
470
  hidden() {
609
- const cmdObj = {
471
+ this.sendWebSocketCommand({
610
472
  cmd_type: "hidden"
611
- };
612
- this.sendWebSocketCommand(cmdObj);
473
+ });
613
474
  }
614
-
615
- /**
616
- * 关闭websocket连接
617
- */
618
475
  closeWebSocket() {
619
476
  this.h5socket.close();
620
477
  }
621
-
622
- /**
623
- * 批量从Base64数据加载多个图像
624
- * @param {Array} base64Images - Base64图像数组,每个元素为 {data: 'base64字符串', format: '格式'}
625
- */
626
478
  loadMultipleImagesFromBase64(base64Images) {
627
479
  if (!Array.isArray(base64Images) || base64Images.length === 0) {
628
- console.error('loadMultipleImagesFromBase64: 参数必须是非空数组');
480
+ console.error("loadMultipleImagesFromBase64: 参数必须是非空数组");
629
481
  return;
630
482
  }
631
-
632
- // 逐个加载图像
633
483
  base64Images.forEach((image, index) => {
634
484
  setTimeout(() => {
635
- this.loadImageFromBase64(image.data, image.format || 'jpg');
636
- }, index * 100); // 每个图像间隔100ms,避免同时发送过多数据
485
+ this.loadImageFromBase64(image.data, image.format || "jpg");
486
+ }, index * 100);
637
487
  });
638
488
  }
639
-
640
- /**
641
- * 从Canvas元素加载图像
642
- * @param {HTMLCanvasElement} canvas - Canvas元素
643
- * @param {string} [format='jpg'] - 图像格式
644
- * @param {number} [quality=0.9] - 图像质量(0-1之间,仅对jpg格式有效)
645
- */
646
- 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;
647
492
  if (!canvas || !(canvas instanceof HTMLCanvasElement)) {
648
- console.error('loadImageFromCanvas: 参数必须是有效的Canvas元素');
493
+ console.error("loadImageFromCanvas: 参数必须是有效的Canvas元素");
649
494
  return;
650
495
  }
651
-
652
- // 将Canvas转换为Base64
653
- const mimeType = format === 'jpg' ? 'image/jpeg' : `image/${format}`;
654
- const dataUrl = canvas.toDataURL(mimeType, quality);
655
-
656
- // 提取Base64数据部分
657
- const base64Data = dataUrl.split(',')[1];
658
-
659
- // 调用Base64加载方法
660
- 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);
661
500
  }
662
-
663
- /**
664
- * 从文件输入元素加载图像
665
- * @param {HTMLInputElement} fileInput - 文件输入元素
666
- */
667
501
  loadImageFromFileInput(fileInput) {
668
502
  if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
669
- console.error('loadImageFromFileInput: 没有选择文件');
503
+ console.error("loadImageFromFileInput: 没有选择文件");
670
504
  return;
671
505
  }
672
506
  const files = Array.from(fileInput.files);
673
507
  files.forEach((file, index) => {
674
- // 检查是否为图像文件
675
- if (!file.type.startsWith('image/')) {
676
- console.warn(`跳过非图像文件: ${file.name}`);
508
+ if (!file.type.startsWith("image/")) {
509
+ console.warn("跳过非图像文件: " + file.name);
677
510
  return;
678
511
  }
679
512
  const reader = new FileReader();
680
513
  reader.onload = e => {
681
514
  const dataUrl = e.target.result;
682
- // 获取文件扩展名
683
- const extension = file.name.split('.').pop().toLowerCase();
684
- const format = extension === 'jpeg' ? 'jpg' : extension;
685
-
686
- // 延迟加载,避免同时处理过多文件
515
+ const extension = file.name.split(".").pop().toLowerCase();
516
+ const format = extension === "jpeg" ? "jpg" : extension;
687
517
  setTimeout(() => {
688
518
  this.loadImageFromBase64(dataUrl, format);
689
519
  }, index * 100);
@@ -692,13 +522,9 @@ class ScanOnWeb {
692
522
  });
693
523
  }
694
524
  }
695
-
696
- // CommonJS 兼容性导出
697
525
  if (typeof module !== "undefined" && module.exports) {
698
526
  module.exports = ScanOnWeb;
699
527
  }
700
-
701
- // UMD 兼容性导出
702
528
  if (typeof window !== "undefined") {
703
529
  window.ScanOnWeb = ScanOnWeb;
704
530
  }