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