微信小程序 扫码+拍照

需求背景

在扫二维码的时候获取到结果并且拍照上传

wx.scanCode

微信小程序有提供扫码API,但是只能获取到结果,无法获取到图片

camera组件

camera组件提供了两种模式,相机、扫码模式。初步设想的方法是用扫码模式去扫码,在扫码获取到结果触发bindscancode回调的时候再去调用上下文对象去拍照片

js 复制代码
Page({
  // 拍照
  takePhoto() {
    const ctx = wx.createCameraContext();
    ctx.takePhoto({
      quality: "low",
      success: (res) => {
        console.log(res.tempImagePath);
      },
      fail: (err) => {
        console.log("拍照失败", err);
      },
    });
  },
  // 获取到扫码结果
  onGetCode(e) {
    console.log("获取到扫码结果", e.detail.result);
    this.takePhoto();
  },
});

实测现在无法支持扫码模式下拍照{errMsg: "Not allow to invoke takePhoto in 'scanCode' mode."},只能另做打算

接下来试了前端来获取帧数据来解码,可以实现,但是准确度不高,效率不行。

最终方案

camera扫码模式。获取到码结果之后立即获取当前帧并上传。

html 复制代码
<!--pages/scan/index.wxml-->
<camera device-position="back" flash="off" binderror="error" 
style="height: 100vh;width: 100vw;" mode="scanCode" bindscancode="onGetCode" frame-size="small"></camera>
js 复制代码
Page({
  onLoad() {
    // 帧数组
    this.frameList = [];
    this.isEnd = false;

    // 相机上下文对象
    this.cameraContext = wx.createCameraContext();
    this.cameraListener = this.cameraContext.onCameraFrame((frame) => {
      // 存储一张帧图片
      if (this.frameList.length === 0) {
        this.frameList.push({
          data: frame.data,
          width: frame.width,
          height: frame.height,
        });
        this.stopGetFrame();
      }
    });
  },
  // 获取到扫码结果
  onGetCode(e) {
    console.log("获取到扫码结果", e.detail.result);
    this.qrCodeResult = e.detail.result;
    if (this.frameList.length === 0 && !this.isEnd) this.startGetFrame();
  },
  // 开始获取帧图片
  startGetFrame() {
    this.cameraListener.start();
  },
  // 停止监听帧数据
  stopGetFrame() {
    this.cameraListener.stop();
    this.transferFrame();
  },
  // frame转base64
  frameToBase64(frameInfo) {
    const frameWidth = frameInfo.width;
    const frameHeight = frameInfo.height;
    return new Promise((resolve, reject) => {
      if (!frameInfo) {
        reject(new Error("No frame data available"));
        return;
      }
      // 创建离屏 canvas
      const offscreenCanvas = wx.createOffscreenCanvas({
        type: "2d",
        width: frameWidth,
        height: frameHeight,
      });
      const ctx = offscreenCanvas.getContext("2d");
      // 创建 ImageData 对象
      const imgData = ctx.createImageData(frameWidth, frameHeight);
      // 将 ArrayBuffer 数据复制到 ImageData
      const uint8Array = new Uint8Array(frameInfo.data);
      imgData.data.set(uint8Array);
      // 将 ImageData 绘制到 canvas
      ctx.putImageData(imgData, 0, 0);
      // 将 canvas 内容转换为 base64
      const base64 = offscreenCanvas.toDataURL("image/png");
      resolve(base64);
    });
  },
  // 去掉前缀
  removeBase64Prefix(base64Data) {
    return base64Data.replace(/^data:image\/[a-z]+;base64,/, "");
  },
  // 处理帧数据
  async transferFrame() {
    console.log("获取到帧数据", this.frameList);
    const fs = wx.getFileSystemManager();
    let filePath = `${wx.env.USER_DATA_PATH}/example.png`;
    const base64 = this.removeBase64Prefix(
      await this.frameToBase64(this.frameList[0])
    );
    fs.writeFile({
      filePath,
      encoding: "base64",
      data: base64,
      success: (res) => {
        console.log("写入成功", res);
        const eventChannel = this.getOpenerEventChannel();
        eventChannel.emit("acceptDataFromB", {
          data: this.qrCodeResult,
          filePath,
        });
        this.frameList = [];
        this.frameList.length = 0;
        this.isEnd = true;
        // 返回页面再上传文件
        wx.navigateBack();
      },
      fail(err) {
        console.error("写入失败", err);
        this.frameList = [];
        this.frameList.length = 0;
      },
    });
  },
});
html 复制代码
<!--pages/index/index.wxml-->
<button bind:tap="goScan">去扫码</button>
<text>扫码结果</text>
<image wx:if="{{filePath}}" src="{{filePath}}"></image>
js 复制代码
Page({
  data: {
    filePath: "",
  },
  goScan() {
    wx.navigateTo({
      url: "../scan/index",
      events: {
        acceptDataFromB: (data) => {
          console.log("获取到的扫码结果:", data);
          // 上传图片...
          this.setData({
            filePath:data.filePath
          })
        },
      },
    });
  },
});
相关推荐
天天扭码1 小时前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫2 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
拉不动的猪2 小时前
设计模式之------策略模式
前端·javascript·面试
旭久2 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js
独行soc2 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf
uhakadotcom3 小时前
Google Earth Engine 机器学习入门:基础知识与实用示例详解
前端·javascript·面试
麓殇⊙3 小时前
Vue--组件练习案例
前端·javascript·vue.js
outstanding木槿3 小时前
React中 点击事件写法 的注意(this、箭头函数)
前端·javascript·react.js
会点php的前端小渣渣3 小时前
vue的计算属性computed的原理和监听属性watch的原理(新)
前端·javascript·vue.js
_一条咸鱼_4 小时前
深入解析 Vue API 模块原理:从基础到源码的全方位探究(八)
前端·javascript·面试