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