欢迎光临
我们一直在努力

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 { fileIo as fs } 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
    @Component
    struct WebScanQRCodeDemo {
    // SaveButton 配置(用于其他功能,这里留一个标准配置)
    saveButtonOptions: SaveButtonOptions = {
    icon: SaveIconStyle.FULL_FILLED,
    text: SaveDescription.SAVE_IMAGE,
    buttonType: ButtonType.Capsule
    };

    controller: webview.WebviewController = new webview.WebviewController();
    @State showMenu: boolean = false;
    @State imgUrl: string = ''; // 当前长按图片的 URL
    @State decodeResult: string = ''; // 扫码结果

    context = this.getUIContext().getHostContext() as common.UIAbilityContext;

    // ========== 复制 rawfile 图片到 filesDir ==========
    copyLocalPicToDir(rawfilePath: string, newFileName: string): string {
    const srcFileDes = this.context.resourceManager.getRawFdSync(rawfilePath);
    const dstPath = this.context.filesDir + '/' + newFileName;
    const dest: fs.File = fs.openSync(
    dstPath,
    fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE
    );

    let bufSize = 4096;
    const buf = new ArrayBuffer(bufSize);
    let offset = 0;
    let readLen = 0;
    let len = 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);
    return dest.path;
    }

    // ========== 下载网络图片到 filesDir ==========
    async copyUrlPicToDir(picUrl: string, newFileName: string): Promise<string> {
    let uri = '';
    const httpRequest = http.createHttp();
    try {
    const data: http.HttpResponse = await (
    httpRequest.request(picUrl) as Promise<http.HttpResponse>
    );
    if (data?.responseCode === http.ResponseCode.OK) {
    const dstPath = this.context.filesDir + '/' + newFileName;
    const dest: fs.File = fs.openSync(
    dstPath,
    fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE
    );
    fs.writeSync(dest.fd, data.result as ArrayBuffer);
    fs.close(dest.fd);
    uri = dstPath;
    }
    } finally {
    httpRequest.destroy();
    }
    return uri;
    }

    // ========== 长按菜单 ==========
    @Builder
    MenuBuilder() {
    Menu() {
    MenuItem({ content: '扫描二维码' })
    .width(200)
    .height(50)
    .onClick(async () => {
    try {
    let localFilePath = '';

    // 处理图片来源
    if (this.imgUrl.includes('rawfile')) {
    // 本地 rawfile 图片
    const rawFileName = this.imgUrl.substring(
    this.imgUrl.lastIndexOf('/') + 1
    );
    localFilePath = this.copyLocalPicToDir(rawFileName, 'qrcode_temp.png');
    } else if (
    this.imgUrl.includes('http') ||
    this.imgUrl.includes('https')
    ) {
    // 网络图片:先下载
    const timestamp = systemDateTime.getTime();
    localFilePath = await this.copyUrlPicToDir(
    this.imgUrl,
    `qrcode_${timestamp}.png`
    );
    }

    if (!localFilePath) {
    this.decodeResult = '无法获取图片文件';
    return;
    }

    // 配置扫码选项
    const scanOptions: scanBarcode.ScanOptions = {
    scanTypes: [scanCore.ScanType.ALL], // 识别所有码类型
    enableMultiMode: true, // 支持多码识别
    enableAlbum: true
    };

    // 构造输入图片
    const inputImage: 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) {
    // 获取长按位置的图片信息
    const hitValue = this.controller.getLastHitTest();
    this.imgUrl = hitValue.extra;
    }
    this.showMenu = true;
    return true;
    })
    .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:识别一张图里的多个码

    const scanOptions: scanBarcode.ScanOptions = {
    scanTypes: [scanCore.ScanType.QR_CODE],
    enableMultiMode: true, // 允许识别图片里的多个二维码
    enableAlbum: true
    };

    enableMultiMode 开启后,一张图片里有多个二维码的话,results 数组会包含所有识别出来的码。

    Promise 风格的调用方式

    上面用了回调方式,也可以用 Promise:

    try {
    const results = await detectBarcode.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 详解》
    赞(0)
    未经允许不得转载:171主机测评 » HarmonyOS ArkWeb 系列之网页图片扫码识别:长按图片用 ScanKit 解码二维码
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址