ScanOnWeb API文档
完整的ScanOnWeb扫描控件JavaScript API参考文档,包含所有方法、属性和事件回调
文档目录
构造函数
new ScanOnWeb()
创建ScanOnWeb扫描控件实例,自动初始化WebSocket连接并尝试连接本地托盘服务。
示例代码
// 创建扫描控件实例 let scanonweb = new ScanOnWeb(); // 设置事件回调 scanonweb.onScanFinishedEvent = function(msg) { console.log('扫描完成,图像数量:', msg.imageAfterCount); };
注意事项
- 构造函数会自动尝试连接本地WebSocket服务(端口1001-5001)
- 需要确保本地已安装并运行ScanOnWeb托盘服务程序
- 如果连接失败,相关操作方法将无法正常工作
配置属性
scaner_work_config
扫描工作配置对象,包含所有扫描参数设置。在调用startScan()方法前需要正确配置这些参数。
配置参数
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
showUI | Boolean | false | 是否显示扫描控件工作界面 |
dpi_x | Number | 300 | 水平分辨率(DPI) |
dpi_y | Number | 300 | 垂直分辨率(DPI) |
deviceIndex | Number | 0 | 选中的扫描设备索引 |
showDialog | Boolean | false | 是否显示设备内置对话框 |
autoFeedEnable | Boolean | true | 是否启用自动进纸器 |
autoFeed | Boolean | false | 是否自动装填纸张 |
dupxMode | Boolean | false | 是否启用双面扫描模式 |
autoDeskew | Boolean | false | 是否启用自动纠偏 |
autoBorderDetection | Boolean | false | 是否启用自动边框检测 |
colorMode | String | "RGB" | 色彩模式:RGB(彩色)、GRAY(灰色)、BW(黑白) |
transMode | String | "memory" | 数据传输模式:memory、file、native |
配置示例
// 配置扫描参数 scanonweb.scaner_work_config = { showUI: false, dpi_x: 600, dpi_y: 600, deviceIndex: 0, showDialog: false, autoFeedEnable: true, autoFeed: false, dupxMode: true, // 启用双面扫描 autoDeskew: true, // 启用自动纠偏 autoBorderDetection: true, // 启用边框检测 colorMode: "RGB", transMode: "memory" };
设备管理方法
loadDevices()
获取系统中所有可用的扫描设备列表。调用后会触发onGetDevicesListEvent事件回调。
示例代码
// 设置设备列表回调 scanonweb.onGetDevicesListEvent = function(msg) { console.log('设备列表:', msg.devices); console.log('当前选中设备索引:', msg.currentIndex); // 填充设备选择下拉框 const deviceSelect = document.getElementById('deviceSelect'); deviceSelect.innerHTML = ''; msg.devices.forEach((device, index) => { const option = document.createElement('option'); option.value = index; option.textContent = device; deviceSelect.appendChild(option); }); }; // 获取设备列表 scanonweb.loadDevices();
selectScanDevice(deviceIndex)
选择指定的扫描设备作为当前工作设备。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
deviceIndex | Number | 是 | 设备索引,从0开始 |
示例代码
// 选择第一个设备 scanonweb.selectScanDevice(0); // 选择第二个设备 scanonweb.selectScanDevice(1);
setLicenseKey(licenseMode, key1, key2, licenseServerUrl)
设置软件授权信息。在使用扫描功能前需要设置有效的授权密钥。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
licenseMode | String | 是 | 授权模式 |
key1 | String | 是 | 授权密钥1 |
key2 | String | 是 | 授权密钥2 |
licenseServerUrl | String | 否 | 授权服务器URL |
示例代码
// 设置授权信息 scanonweb.setLicenseKey( "online", "your-license-key-1", "your-license-key-2", "https://license.brainysoft.cn" );
扫描操作方法
startScan()
开始扫描操作。使用当前的scaner_work_config配置进行扫描。扫描完成后会触发onScanFinishedEvent事件。
示例代码
// 配置扫描参数 scanonweb.scaner_work_config.dpi_x = 600; scanonweb.scaner_work_config.dpi_y = 600; scanonweb.scaner_work_config.colorMode = "RGB"; scanonweb.scaner_work_config.deviceIndex = 0; // 设置扫描完成回调 scanonweb.onScanFinishedEvent = function(msg) { console.log('扫描前图像数量:', msg.imageBeforeCount); console.log('扫描后图像数量:', msg.imageAfterCount); // 自动获取扫描结果 scanonweb.getAllImage(); }; // 开始扫描 scanonweb.startScan();
注意事项
- 扫描前确保已选择正确的设备(deviceIndex)
- 确保设备已连接并处于就绪状态
- 扫描参数配置会影响扫描质量和速度
clearAll()
清除所有已扫描的图像数据,释放内存空间。
示例代码
// 清除所有图像 scanonweb.clearAll(); // 同时清除页面显示 document.getElementById('imageList').innerHTML = '';
图像处理方法
getAllImage()
获取所有已扫描的图像数据。调用后会触发onGetAllImageEvent事件回调,返回Base64编码的图像数据。
示例代码
// 设置获取图像回调 scanonweb.onGetAllImageEvent = function(msg) { console.log('图像数量:', msg.imageCount); console.log('当前选中:', msg.currentSelected); // 显示图像 const imageList = document.getElementById('imageList'); imageList.innerHTML = ''; msg.images.forEach((imageBase64, index) => { const img = document.createElement('img'); img.src = 'data:image/jpg;base64,' + imageBase64; img.style.width = '200px'; img.style.height = '200px'; img.style.margin = '10px'; img.style.border = '1px solid #ccc'; imageList.appendChild(img); }); }; // 获取所有图像 scanonweb.getAllImage();
getImageById(index)
获取指定索引的单张图像数据。调用后会触发onGetImageByIdEvent事件回调。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
index | Number | 是 | 图像索引,从0开始 |
示例代码
// 设置单张图像回调 scanonweb.onGetImageByIdEvent = function(msg) { console.log('图像索引:', msg.imageIndex); console.log('图像数据:', msg.imageBase64); // 显示图像 const img = document.createElement('img'); img.src = 'data:image/jpg;base64,' + msg.imageBase64; document.body.appendChild(img); }; // 获取第一张图像 scanonweb.getImageById(0);
getImageCount()
获取当前已扫描的图像总数。调用后会触发onGetImageCountEvent事件回调。
示例代码
// 设置图像计数回调 scanonweb.onGetImageCountEvent = function(msg) { console.log('图像总数:', msg.imageCount); console.log('当前选中图像索引:', msg.currentSelected); }; // 获取图像数量 scanonweb.getImageCount();
rotateImage(index, angle)
旋转指定索引的图像。支持90度的倍数旋转。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
index | Number | 是 | 图像索引,从0开始 |
angle | Number | 是 | 旋转角度:90、180、270 |
示例代码
// 将第一张图像顺时针旋转90度 scanonweb.rotateImage(0, 90); // 将第二张图像旋转180度 scanonweb.rotateImage(1, 180);
getImageSize(index)
获取指定索引图像的尺寸信息。调用后会触发onGetImageSizeEvent事件回调。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
index | Number | 是 | 图像索引,从0开始 |
示例代码
// 设置图像尺寸回调 scanonweb.onGetImageSizeEvent = function(msg) { console.log('图像宽度:', msg.width); console.log('图像高度:', msg.height); console.log('图像索引:', msg.imageIndex); }; // 获取第一张图像尺寸 scanonweb.getImageSize(0);
loadImageFromUrl(url)
从远程URL加载图像到扫描控件中。支持多页图像文件。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
url | String | 是 | 图像文件的URL地址 |
示例代码
// 从服务器加载图像 scanonweb.loadImageFromUrl('https://example.com/document.pdf'); // 加载本地服务器图像 scanonweb.loadImageFromUrl('/uploads/scan_result.tiff');
上传保存方法
uploadAllImageAsPdfToUrl(url, id, desc)
将所有图像合并为PDF格式并上传到指定URL。调用后会触发onUploadAllImageAsPdfToUrlEvent事件回调。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
url | String | 是 | 上传目标URL地址 |
id | String | 是 | 文档标识ID |
desc | String | 否 | 文档描述信息 |
示例代码
// 设置上传回调 scanonweb.onUploadAllImageAsPdfToUrlEvent = function(msg) { const result = JSON.parse(msg.uploadResult); if (result.network) { console.log('上传成功:', result.msg); } else { console.error('上传失败:', result.msg); } }; // 上传PDF到服务器 scanonweb.uploadAllImageAsPdfToUrl( 'https://api.example.com/upload', 'DOC_001', '扫描文档' );
服务器端接收代码示例
ScanOnWeb控件通过multipart/form-data方式提交数据,包含以下4个参数:
提交参数说明
- image - 上传的图像文件二进制数据(PDF格式)
- imageCount - 本次上传的图像总数
- id - 调用方法时传入的业务ID参数
- desc - 调用方法时传入的描述信息参数
Java Spring Boot
@RestController @RequestMapping("/api") public class ScanUploadController { @PostMapping("/upload") public ResponseEntity<Map<String, Object>> uploadScanImages( @RequestParam("image") MultipartFile imageFile, @RequestParam("imageCount") Integer imageCount, @RequestParam("id") String id, @RequestParam(value = "desc", required = false) String desc) { Map<String, Object> response = new HashMap<>(); try { // 验证文件 if (imageFile.isEmpty()) { response.put("network", false); response.put("msg", "上传文件为空"); return ResponseEntity.badRequest().body(response); } // 生成文件名 String fileName = id + "_" + System.currentTimeMillis() + ".pdf"; String uploadDir = "/uploads/scan/"; Path uploadPath = Paths.get(uploadDir); // 创建目录 if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); } // 保存文件 Path filePath = uploadPath.resolve(fileName); Files.copy(imageFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING); // 记录到数据库 ScanDocument document = new ScanDocument(); document.setBusinessId(id); document.setDescription(desc); document.setImageCount(imageCount); document.setFilePath(filePath.toString()); document.setFileName(fileName); document.setFileSize(imageFile.getSize()); document.setUploadTime(new Date()); scanDocumentService.save(document); // 返回成功响应 response.put("network", true); response.put("msg", "上传成功"); response.put("fileId", document.getId()); response.put("fileName", fileName); return ResponseEntity.ok(response); } catch (Exception e) { logger.error("文件上传失败", e); response.put("network", false); response.put("msg", "上传失败: " + e.getMessage()); return ResponseEntity.status(500).body(response); } } }
ASP.NET Core
[ApiController] [Route("api/[controller]")] public class ScanUploadController : ControllerBase { private readonly ILogger<ScanUploadController> _logger; private readonly IScanDocumentService _scanDocumentService; public ScanUploadController(ILogger<ScanUploadController> logger, IScanDocumentService scanDocumentService) { _logger = logger; _scanDocumentService = scanDocumentService; } [HttpPost("upload")] public async Task<IActionResult> UploadScanImages( [FromForm] IFormFile image, [FromForm] int imageCount, [FromForm] string id, [FromForm] string desc = null) { try { // 验证文件 if (image == null || image.Length == 0) { return BadRequest(new { network = false, msg = "上传文件为空" }); } // 生成文件名 var fileName = $"{id}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}.pdf"; var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "scan"); // 创建目录 if (!Directory.Exists(uploadDir)) { Directory.CreateDirectory(uploadDir); } // 保存文件 var filePath = Path.Combine(uploadDir, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await image.CopyToAsync(stream); } // 保存到数据库 var document = new ScanDocument { BusinessId = id, Description = desc, ImageCount = imageCount, FilePath = filePath, FileName = fileName, FileSize = image.Length, UploadTime = DateTime.UtcNow }; await _scanDocumentService.SaveAsync(document); // 返回成功响应 return Ok(new { network = true, msg = "上传成功", fileId = document.Id, fileName = fileName }); } catch (Exception ex) { _logger.LogError(ex, "文件上传失败"); return StatusCode(500, new { network = false, msg = $"上传失败: {ex.Message}" }); } } }
Go (Gin Framework)
package main import ( "fmt" "io" "net/http" "os" "path/filepath" "strconv" "time" "github.com/gin-gonic/gin" ) type ScanDocument struct { ID uint `json:"id" gorm:"primaryKey"` BusinessID string `json:"business_id"` Description string `json:"description"` ImageCount int `json:"image_count"` FilePath string `json:"file_path"` FileName string `json:"file_name"` FileSize int64 `json:"file_size"` UploadTime time.Time `json:"upload_time"` } func uploadScanImages(c *gin.Context) { // 获取表单参数 imageFile, header, err := c.Request.FormFile("image") if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "network": false, "msg": "获取上传文件失败: " + err.Error(), }) return } defer imageFile.Close() imageCount, _ := strconv.Atoi(c.PostForm("imageCount")) id := c.PostForm("id") desc := c.PostForm("desc") // 验证文件 if header.Size == 0 { c.JSON(http.StatusBadRequest, gin.H{ "network": false, "msg": "上传文件为空", }) return } // 生成文件名 fileName := fmt.Sprintf("%s_%d.pdf", id, time.Now().UnixMilli()) uploadDir := "./uploads/scan" // 创建目录 if err := os.MkdirAll(uploadDir, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "network": false, "msg": "创建目录失败: " + err.Error(), }) return } // 保存文件 filePath := filepath.Join(uploadDir, fileName) dst, err := os.Create(filePath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "network": false, "msg": "创建文件失败: " + err.Error(), }) return } defer dst.Close() if _, err := io.Copy(dst, imageFile); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "network": false, "msg": "保存文件失败: " + err.Error(), }) return } // 保存到数据库 document := ScanDocument{ BusinessID: id, Description: desc, ImageCount: imageCount, FilePath: filePath, FileName: fileName, FileSize: header.Size, UploadTime: time.Now(), } // 这里应该调用数据库服务保存记录 // db.Create(&document) // 返回成功响应 c.JSON(http.StatusOK, gin.H{ "network": true, "msg": "上传成功", "fileId": document.ID, "fileName": fileName, }) } func main() { r := gin.Default() r.POST("/api/upload", uploadScanImages) r.Run(":8080") }
Rust (Actix-web)
use actix_multipart::Multipart; use actix_web::{web, App, HttpResponse, HttpServer, Result}; use futures::{StreamExt, TryStreamExt}; use serde::{Deserialize, Serialize}; use std::io::Write; use tokio::fs; use uuid::Uuid; #[derive(Serialize, Deserialize)] struct ScanDocument { id: Option<u32>, business_id: String, description: Option<String>, image_count: i32, file_path: String, file_name: String, file_size: u64, upload_time: chrono::DateTime<chrono::Utc>, } #[derive(Serialize)] struct UploadResponse { network: bool, msg: String, #[serde(skip_serializing_if = "Option::is_none")] file_id: Option<u32>, #[serde(skip_serializing_if = "Option::is_none")] file_name: Option<String>, } async fn upload_scan_images(mut payload: Multipart) -> Result<HttpResponse> { let mut image_data: Option<Vec<u8>> = None; let mut image_count: Option<i32> = None; let mut business_id: Option<String> = None; let mut description: Option<String> = None; // 解析multipart数据 while let Ok(Some(mut field)) = payload.try_next().await { let content_disposition = field.content_disposition(); if let Some(name) = content_disposition.get_name() { match name { "image" => { let mut data = Vec::new(); while let Some(chunk) = field.next().await { let chunk = chunk?; data.extend_from_slice(&chunk); } image_data = Some(data); } "imageCount" => { let mut data = Vec::new(); while let Some(chunk) = field.next().await { let chunk = chunk?; data.extend_from_slice(&chunk); } if let Ok(count_str) = String::from_utf8(data) { image_count = count_str.parse().ok(); } } "id" => { let mut data = Vec::new(); while let Some(chunk) = field.next().await { let chunk = chunk?; data.extend_from_slice(&chunk); } if let Ok(id_str) = String::from_utf8(data) { business_id = Some(id_str); } } "desc" => { let mut data = Vec::new(); while let Some(chunk) = field.next().await { let chunk = chunk?; data.extend_from_slice(&chunk); } if let Ok(desc_str) = String::from_utf8(data) { description = Some(desc_str); } } _ => {} } } } // 验证必要参数 let image_data = match image_data { Some(data) if !data.is_empty() => data, _ => { return Ok(HttpResponse::BadRequest().json(UploadResponse { network: false, msg: "上传文件为空".to_string(), file_id: None, file_name: None, })); } }; let business_id = business_id.unwrap_or_else(|| Uuid::new_v4().to_string()); let image_count = image_count.unwrap_or(1); // 生成文件名 let timestamp = chrono::Utc::now().timestamp_millis(); let file_name = format!("{}_{}.pdf", business_id, timestamp); let upload_dir = "./uploads/scan"; // 创建目录 if let Err(e) = fs::create_dir_all(upload_dir).await { return Ok(HttpResponse::InternalServerError().json(UploadResponse { network: false, msg: format!("创建目录失败: {}", e), file_id: None, file_name: None, })); } // 保存文件 let file_path = format!("{}/{}", upload_dir, file_name); if let Err(e) = fs::write(&file_path, &image_data).await { return Ok(HttpResponse::InternalServerError().json(UploadResponse { network: false, msg: format!("保存文件失败: {}", e), file_id: None, file_name: None, })); } // 保存到数据库 let document = ScanDocument { id: None, business_id: business_id.clone(), description, image_count, file_path: file_path.clone(), file_name: file_name.clone(), file_size: image_data.len() as u64, upload_time: chrono::Utc::now(), }; // 这里应该调用数据库服务保存记录 // let saved_document = db_service.save(document).await?; // 返回成功响应 Ok(HttpResponse::Ok().json(UploadResponse { network: true, msg: "上传成功".to_string(), file_id: Some(1), // document.id file_name: Some(file_name), })) } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/api/upload", web::post().to(upload_scan_images)) }) .bind("127.0.0.1:8080")? .run() .await }
前端JavaScript上传示例
第二种上传方式:前端通过WebSocket获取图像数据后,使用JavaScript进行上传
获取图像数据并上传(Base64方式)
// 获取扫描图像的Base64数据 scanonweb.onGetAllImageEvent = function(msg) { console.log('获取到图像数据:', msg.images); // 上传所有图像 uploadImagesToServer(msg.images, 'DOC_001', '扫描文档'); }; // 上传图像到服务器 async function uploadImagesToServer(images, businessId, description) { try { for (let i = 0; i < images.length; i++) { const base64Data = images[i]; // 将Base64转换为Blob const blob = base64ToBlob(base64Data, 'image/jpeg'); // 创建FormData const formData = new FormData(); formData.append('image', blob, `scan_${businessId}_${i}.jpg`); formData.append('imageCount', images.length.toString()); formData.append('id', businessId); formData.append('desc', description); formData.append('imageIndex', i.toString()); // 使用axios上传 const response = await axios.post('/api/upload-image', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (progressEvent) => { const progress = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); console.log(`图像 ${i + 1} 上传进度: ${progress}%`); } }); console.log(`图像 ${i + 1} 上传成功:`, response.data); } alert('所有图像上传完成!'); } catch (error) { console.error('上传失败:', error); alert('上传失败: ' + error.message); } } // Base64转Blob工具函数 function base64ToBlob(base64Data, contentType = '') { const byteCharacters = atob(base64Data); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += 512) { const slice = byteCharacters.slice(offset, offset + 512); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } return new Blob(byteArrays, { type: contentType }); } // 获取图像数据 scanonweb.getAllImage();
使用Fetch API上传
// 使用fetch API上传图像 async function uploadImageWithFetch(base64Data, businessId, description, index) { try { // 将Base64转换为二进制数据 const binaryData = atob(base64Data); const bytes = new Uint8Array(binaryData.length); for (let i = 0; i < binaryData.length; i++) { bytes[i] = binaryData.charCodeAt(i); } // 创建FormData const formData = new FormData(); const blob = new Blob([bytes], { type: 'image/jpeg' }); formData.append('image', blob, `scan_${businessId}_${index}.jpg`); formData.append('imageCount', '1'); formData.append('id', businessId); formData.append('desc', description); // 发送请求 const response = await fetch('/api/upload-image', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); if (result.network) { console.log('上传成功:', result.msg); return result; } else { throw new Error(result.msg); } } catch (error) { console.error('上传失败:', error); throw error; } } // 批量上传示例 async function batchUploadImages() { // 先获取图像数据 scanonweb.onGetAllImageEvent = async function(msg) { const images = msg.images; const businessId = 'BATCH_' + Date.now(); for (let i = 0; i < images.length; i++) { try { await uploadImageWithFetch( images[i], businessId, `批量扫描文档 ${i + 1}`, i ); console.log(`第 ${i + 1} 张图像上传完成`); } catch (error) { console.error(`第 ${i + 1} 张图像上传失败:`, error); } } }; // 获取所有图像 scanonweb.getAllImage(); }
saveAllImageToLocal(filename)
将所有图像保存到客户端本地文件。支持多种格式:PDF、TIFF、JPG等。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
filename | String | 是 | 保存的文件路径和名称 |
示例代码
// 保存为PDF文件 scanonweb.saveAllImageToLocal('D:/Documents/scan_result.pdf'); // 保存为TIFF文件 scanonweb.saveAllImageToLocal('D:/Documents/scan_result.tiff'); // 保存为JPG文件(仅第一张图像) scanonweb.saveAllImageToLocal('D:/Documents/scan_result.jpg');
uploadJpgImageByIndex(url, id, desc, index)
上传指定索引的单张图像(JPG格式)到服务器。
参数
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
url | String | 是 | 上传目标URL地址 |
id | String | 是 | 图像标识ID |
desc | String | 否 | 图像描述信息 |
index | Number | 是 | 图像索引,从0开始 |
示例代码
// 上传第一张图像 scanonweb.uploadJpgImageByIndex( 'https://api.example.com/upload-image', 'IMG_001', '身份证正面', 0 );
openClientLocalfile()
打开客户端文件选择对话框,允许用户选择本地图像文件加载到控件中。
示例代码
// 打开文件选择对话框 scanonweb.openClientLocalfile(); // 用户选择文件后,会自动加载到控件中 // 可以通过getAllImage()获取加载的图像
界面控制方法
setFocus()
设置扫描控件界面获得焦点,将控件窗口置于前台。
示例代码
// 将扫描控件窗口置于前台 scanonweb.setFocus();
hidden()
隐藏扫描控件界面窗口。
示例代码
// 隐藏扫描控件界面 scanonweb.hidden();