HarmonyOS ArkWeb 系列之网页图片扫码识别:长按图片用 ScanKit 解码二维码
文章目录
- 思路说明
- 流程图
- 完整代码实现
- ScanKit 支持的码类型
- 配合 enableMultiMode:识别一张图里的多个码
- Promise 风格的调用方式
- 注意:临时文件的清理
- 写在最后
你有没有遇到这种需求:网页里有张图片是二维码,用户长按想扫描它——但系统相机无法识别网页截图,只能扫真实摄像头。ArkWeb + ScanKit 的组合可以优雅解决这个问题:长按图片,把图片下载到本地,然后调用detectBarcode.decode直接识别图片里的二维码,不需要打开摄像头。
思路说明
- 用户长按网页中的图片
onContextMenuShow触发,通过getLastHitTest()拿到图片 URL- 图片如果是 rawfile 本地资源,直接复制到
filesDir - 图片如果是网络资源,用 HTTP 下载到
filesDir - 调用
detectBarcode.decode传入本地文件路径,识别二维码 - 把识别结果显示给用户
流程图
完整代码实现
import{webview}from'@kit.ArkWeb';import{common}from'@kit.AbilityKit';import{fileIoasfs}from'@kit.CoreFileKit';import{systemDateTime}from'@kit.BasicServicesKit';import{http}from'@kit.NetworkKit';import{scanCore,scanBarcode,detectBarcode}from'@kit.ScanKit';import{BusinessError}from'@kit.BasicServicesKit';@Entry@Componentstruct WebScanQRCodeDemo{// SaveButton 配置(用于其他功能,这里留一个标准配置)saveButtonOptions:SaveButtonOptions={icon:SaveIconStyle.FULL_FILLED,text:SaveDescription.SAVE_IMAGE,buttonType:ButtonType.Capsule};controller:webview.WebviewController=newwebview.WebviewController();@StateshowMenu:boolean=false;@StateimgUrl:string='';// 当前长按图片的 URL@StatedecodeResult:string='';// 扫码结果context=this.getUIContext().getHostContext()ascommon.UIAbilityContext;// ========== 复制 rawfile 图片到 filesDir ==========copyLocalPicToDir(rawfilePath:string,newFileName:string):string{constsrcFileDes=this.context.resourceManager.getRawFdSync(rawfilePath);constdstPath=this.context.filesDir+'/'+newFileName;constdest:fs.File=fs.openSync(dstPath,fs.OpenMode.CREATE|fs.OpenMode.READ_WRITE);letbufSize=4096;constbuf=newArrayBuffer(bufSize);letoffset=0;letreadLen=0;letlen=0;while((len=fs.readSync(srcFileDes.fd,buf,{offset:srcFileDes.offset+offset,length:bufSize}))!==0){readLen+=len;fs.writeSync(dest.fd,buf,{offset:offset,length:len});offset+=len;if((srcFileDes.length-readLen)<bufSize){bufSize=srcFileDes.length-readLen;}}fs.close(dest.fd);returndest.path;}// ========== 下载网络图片到 filesDir ==========asynccopyUrlPicToDir(picUrl:string,newFileName:string):Promise<string>{leturi='';consthttpRequest=http.createHttp();try{constdata:http.HttpResponse=await(httpRequest.request(picUrl)asPromise<http.HttpResponse>);if(data?.responseCode===http.ResponseCode.OK){constdstPath=this.context.filesDir+'/'+newFileName;constdest:fs.File=fs.openSync(dstPath,fs.OpenMode.CREATE|fs.OpenMode.READ_WRITE);fs.writeSync(dest.fd,data.resultasArrayBuffer);fs.close(dest.fd);uri=dstPath;}}finally{httpRequest.destroy();}returnuri;}// ========== 长按菜单 ==========@BuilderMenuBuilder(){Menu(){MenuItem({content:'扫描二维码'}).width(200).height(50).onClick(async()=>{try{letlocalFilePath='';// 处理图片来源if(this.imgUrl.includes('rawfile')){// 本地 rawfile 图片constrawFileName=this.imgUrl.substring(this.imgUrl.lastIndexOf('/')+1);localFilePath=this.copyLocalPicToDir(rawFileName,'qrcode_temp.png');}elseif(this.imgUrl.includes('http')||this.imgUrl.includes('https')){// 网络图片:先下载consttimestamp=systemDateTime.getTime();localFilePath=awaitthis.copyUrlPicToDir(this.imgUrl,`qrcode_${timestamp}.png`);}if(!localFilePath){this.decodeResult='无法获取图片文件';return;}// 配置扫码选项constscanOptions:scanBarcode.ScanOptions={scanTypes:[scanCore.ScanType.ALL],// 识别所有码类型enableMultiMode:true,// 支持多码识别enableAlbum:true};// 构造输入图片constinputImage:detectBarcode.InputImage={uri:localFilePath// 本地文件路径};// 调用识别接口(异步回调方式)detectBarcode.decode(inputImage,scanOptions,(error:BusinessError,results:Array<scanBarcode.ScanResult>)=>{if(error&&error.code){console.error(`识别失败:${error.code},${error.message}`);this.decodeResult=`识别失败(错误码:${error.code})`;return;}if(results&&results.length>0){// 把所有识别结果拼接显示this.decodeResult=results.map((r,i)=>`[${i+1}]${r.originalValue}`).join('\n');console.info('扫码结果:',JSON.stringify(results));}else{this.decodeResult='未识别到二维码';}});}catch(err){console.error(`扫码出错:${err.code},${err.message}`);this.decodeResult=`出错:${err.message}`;}})}}build(){Column(){Web({src:$rawfile('index.html'),controller:this.controller}).onContextMenuShow((event)=>{if(event){// 获取长按位置的图片信息consthitValue=this.controller.getLastHitTest();this.imgUrl=hitValue.extra;}this.showMenu=true;returntrue;}).bindContextMenu(this.MenuBuilder,ResponseType.LongPress).fileAccess(true).javaScriptAccess(true).domStorageAccess(true).height('70%')// 显示扫码结果if(this.decodeResult){Text('识别结果:').fontSize(14).fontWeight(FontWeight.Bold).margin({top:12,left:16})Text(this.decodeResult).fontSize(14).margin({top:4,left:16,right:16}).fontColor('#333').wordBreak(WordBreak.BREAK_ALL)}}.width('100%').height('100%')}}ScanKit 支持的码类型
scanCore.ScanType枚举包含:
| 类型 | 说明 |
|---|---|
ScanType.QR_CODE | 二维码(QR Code) |
ScanType.DATA_MATRIX | Data Matrix 码 |
ScanType.PDF417 | PDF417 条形码 |
ScanType.AZTEC | Aztec 码 |
ScanType.EAN_8 | EAN-8 条形码 |
ScanType.EAN_13 | EAN-13 条形码 |
ScanType.CODE_128 | Code 128 条形码 |
ScanType.ALL | 所有类型 |
配合 enableMultiMode:识别一张图里的多个码
constscanOptions:scanBarcode.ScanOptions={scanTypes:[scanCore.ScanType.QR_CODE],enableMultiMode:true,// 允许识别图片里的多个二维码enableAlbum:true};enableMultiMode开启后,一张图片里有多个二维码的话,results数组会包含所有识别出来的码。
Promise 风格的调用方式
上面用了回调方式,也可以用 Promise:
try{constresults=awaitdetectBarcode.decode(inputImage,scanOptions);if(results.length>0){this.decodeResult=results[0].originalValue??'空结果';}}catch(err){this.decodeResult=`识别失败:${err.message}`;}注意:临时文件的清理
每次扫码都会在filesDir里创建临时文件,用完最好删掉:
// 识别完成后清理临时文件try{fs.unlinkSync(localFilePath);}catch{// 删除失败不影响主流程}写在最后
网页扫码这个功能在电商、票务类 App 里很实用——商品页里有二维码,用户不需要截图再打开相机扫,直接长按就能识别。核心是把网页图片拿到本地,然后交给 ScanKit 处理,整个流程清晰不绕弯。
参考文章
- 《HarmonyOS ScanKit 扫码识别使用指南》
- 《长按图片保存到相册:SaveButton + photoAccessHelper 实战》
- 《detectBarcode.decode 图片识码 API 详解》
