在 Vue 中使用 XZing 处理二维码

在 Vue 中使用 XZing 处理二维码

XZing 是一个开源的条码处理库,我们这里简单介绍他的 js 版本,仓库地址:https://github.com/zxing-js/library

我们主要使用 XZing 的三个功能:

  1. 单张图片解码
  2. 视频动态解码
  3. 绘制二维码

安装与引入

使用 npm 安装:

npm i @zxing/browser

在 Vue 中可以直接导入对应的组件:

import { BrowserMultiFormatReader } from '@zxing/browser'

通过图片解码

官方文档中已经列举了所有的扫描方法,通过图片解码常用到以下两个:

  • decodeFromImageElement():需要传入一个 HTML 元素,可以是 id ,也可以是 Vue 的 ref 属性
  • decodeFromImageUrl():需要传入一个 URL,可以是一个从设备中选择的文件,也可以是一个网址

对于第一种方式不再赘述,从设备选择图片可以使用 FileReader,下面给出一个 Demo 代码:

<script setup>
import { ref } from 'vue'
import { BrowserMultiFormatReader } from '@zxing/browser'

const codeReader = new BrowserMultiFormatReader()
const resultFromDevice = ref('暂未获取到信息')
const resultFromElement = ref('暂未获取到信息')
const selectedImage = ref(null)
const imgElement = ref(null)

const scanningImgFromDevice = () => {
  if (!selectedImage.value) {
    resultFromDevice.value = '请先选择一张图片。'
    return
  }
  codeReader
    .decodeFromImageUrl(selectedImage.value)
    .then(decodeResult => {
      console.log(decodeResult)
      resultFromDevice.value = decodeResult.text
    })
    .catch(err => {
      console.error(err)
      resultFromDevice.value = '解码失败,请检查图片。'
    })
}

const scanningImgElement = () => {
  codeReader
    .decodeFromImageElement(imgElement.value)
    .then(decodeResult => {
      console.log(decodeResult)
      resultFromElement.value = decodeResult.text
    })
    .catch(err => {
      console.error(err)
      resultFromElement.value = '解码失败,请检查图片。'
    })
}

const resetScanning = () => {
  resultFromDevice.value = '暂未获取到信息'
  resultFromElement.value = '暂未获取到信息'
  selectedImage.value = null
  console.log('Reset.')
}

const handleFileChange = event => {
  const file = event.target.files[0]
  if (file) {
    const reader = new FileReader()
    reader.onload = e => {
      selectedImage.value = e.target.result // 获取图片的 base64 数据
    }
    reader.readAsDataURL(file) // 读取图片为 base64 格式
  }
}
</script>

<template>
  <h1>通过图片扫描 1D/2D 码</h1>
  <h2>扫描 HTML 元素:</h2>
  <div class="options-box">
    <div class="option-item" @click="resetScanning">重置</div>
    <div class="option-item" @click="scanningImgElement">扫描下方示例图片</div>
  </div>
  <h3>示例图片</h3>
  <div class="demo-box">
    <div class="left-show">
      <img
        ref="imgElement"
        src="../assets/img/test-qr-code.png"
        style="width: 150px; height: 150px"
      />
    </div>
    <div class="divider"></div>
    <div class="right-result">
      <label>结果:</label>
      <div>{{ resultFromElement }}</div>
    </div>
  </div>

  <h2>从设备获取:</h2>
  <div class="options-box">
    <div class="option-item" @click="resetScanning">重置</div>
    <div class="option-item" @click="scanningImgFromDevice">
      扫描从设备获取的图片
    </div>
  </div>
  <input type="file" accept="image/*" @change="handleFileChange" />

  <div class="demo-box">
    <div class="left-show">
      <img
        v-if="selectedImage"
        :src="selectedImage"
        alt="选择的图片"
        style="width: 150px; height: 150px"
      />
    </div>
    <div class="divider"></div>
    <div class="right-result">
      <label>结果:</label>
      <div>{{ resultFromDevice }}</div>
    </div>
  </div>
</template>

<style scoped>
h1 {
  padding: 10px 0;
  font-size: 24px;
  font-weight: bold;
  text-align: center;
}
h2 {
  padding: 10px 0 0 0;
  font-size: 20px;
  font-weight: bold;
  text-align: center;
}
.demo-box {
  border: 1px solid #000;
  border-radius: 20px;
  overflow: hidden;
  width: 90%;
  height: 150px;
  padding: 10px;
  display: flex;
  justify-content: flex-start;
}
.divider {
  border: 1px solid #000;
  margin: 5px;
}
.left-show {
  width: 150px;
  display: flex;
  justify-content: center;
  align-items: center;
}
.options-box {
  width: 90%;
  margin-top: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}
.option-item {
  height: 20px;
  padding: 10px 20px;
  margin: 5px 10px;
  background-color: #f68512;
  color: white;
  font-weight: bold;
  border-radius: 10px;
  cursor: pointer;
  text-align: center;
}
</style>

通过视频解码

通过视频解码需要指定摄像头设备,需要先通过 listVideoInputDevices() 获取所有摄像头设备,然后向 decodeFromVideoDevice 或其他 API 中传入这个 ID 即可,如果想要在页面上显示,可以再指定一个 HTML 元素。

下面给出一个 Demo 代码:

<script setup>
import { ref, onMounted } from 'vue'
import { BrowserMultiFormatReader } from '@zxing/browser'

const video = ref(null)

const codeReader = new BrowserMultiFormatReader()
const videoInputDevices = ref([])
const selectedDeviceId = ref(null)
const result = ref('暂未获取到信息')
let controls

onMounted(async () => {
  await getVideoInputDevices()
})

const getVideoInputDevices = async () => {
  try {
    // 请求摄像头权限
    const devices = await BrowserMultiFormatReader.listVideoInputDevices()

    videoInputDevices.value = devices
    if (devices.length > 0) {
      selectedDeviceId.value = devices[0].deviceId // 选择第一个设备
    }
  } catch (err) {
    console.error(err)
    result.value = '无法获取视频输入设备,请检查权限设置。'
  }
}

const startScanning = async () => {
  controls = await codeReader.decodeFromVideoDevice(
    selectedDeviceId.value,
    video.value,
    (resultData, err) => {
      if (resultData) {
        result.value = resultData.text
        console.log(resultData)
      }
      if (err) {
        // 检查错误信息是否包含 "NotFoundException"
        if (err.name === 'NotFoundException2') {
          // result.value = '未找到二维码,请检查图片或二维码质量。'
        } else {
          result.value = '解码失败: ' + err.message // 其他错误信息
          console.error(err)
        }
      }
    },
  )
  console.log(
    `Started continuous decode from camera with id ${selectedDeviceId.value}`,
  )
}

const resetScanning = () => {
  controls.stop()
  result.value = ''
  console.log('Reset.')
}
</script>

<template>
  <div style="display: flex; flex-direction: column; align-items: center">
    <h1>通过视频扫描 1D/2D 码</h1>
    <div class="options-box">
      <div class="option-item" @click="startScanning">开始</div>
      <div class="option-item" @click="resetScanning">重置</div>
    </div>
    <video
      ref="video"
      style="border: 1px solid gray; width: 300px; height: 400px"
    ></video>
    <div v-if="videoInputDevices.length > 0">
      <label for="sourceSelect">选择摄像头:</label>
      <select id="sourceSelect" v-model="selectedDeviceId">
        <option
          v-for="device in videoInputDevices"
          :key="device.deviceId"
          :value="device.deviceId"
        >
          {{ device.label }}
        </option>
      </select>
    </div>
    <label>结果:</label>
    <div>{{ result }}</div>
  </div>
</template>

<style scoped>
h1 {
  padding: 10px 0;
  font-size: 24px;
  font-weight: bold;
  text-align: center;
}
h2 {
  padding: 10px 0 0 0;
  font-size: 20px;
  font-weight: bold;
}
.demo-box {
  border: 1px solid #000;
  border-radius: 20px;
  overflow: hidden;
  width: 90%;
  height: 150px;
  padding: 10px;
  display: flex;
  justify-content: flex-start;
}
.divider {
  border: 1px solid #000;
  margin: 5px;
}
.left-show {
  width: 150px;
  display: flex;
  justify-content: center;
  align-items: center;
}
.options-box {
  width: 90%;
  margin-top: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}
.option-item {
  height: 20px;
  padding: 10px 20px;
  margin: 5px 10px;
  background-color: #f68512;
  color: white;
  font-weight: bold;
  border-radius: 10px;
  cursor: pointer;
  text-align: center;
}
</style>

绘制二维码

这里引入 file-saverhtml2canvas 用于保存生成的二维码,如果仅用于网页展示则不需要该插件。

安装命令:

npm i file-saver html2canvas
<script setup>
import { ref } from 'vue'
import { BrowserQRCodeSvgWriter } from '@zxing/browser'
import { saveAs } from 'file-saver'
import html2canvas from 'html2canvas' // 引入 html2canvas

const codeWriter = new BrowserQRCodeSvgWriter()
const input = ref('')
const result = ref(null)

function drawQRCode() {
  if (result.value) {
    while (result.value.firstChild) {
      result.value.removeChild(result.value.firstChild)
    }
  }
  codeWriter.writeToDom(result.value, input.value, 300, 300)
}

function saveQRCode(format = 'png') {
  if (result.value) {
    html2canvas(result.value).then(canvas => {
      canvas.toBlob(
        blob => {
          const fileName = `zxing-qrcode-example.${format}`
          saveAs(blob, fileName)
        },
        format === 'png' ? 'image/png' : 'image/jpeg',
      )
    })
  }
}

function resetQRCode() {
  input.value = ''
  if (result.value) {
    while (result.value.firstChild) {
      result.value.removeChild(result.value.firstChild)
    }
  }
}
</script>

<template>
  <h1>生成并保存 QR 码</h1>
  <div class="options-box">
    <div class="option-item" @click="drawQRCode">生成</div>
    <div class="option-item" @click="() => saveQRCode('png')">保存为 PNG</div>
    <div class="option-item" @click="() => saveQRCode('jpg')">保存为 JPG</div>
    <div class="option-item" @click="resetQRCode">重置</div>
  </div>
  <div class="input-box">
    <textarea v-model="input"></textarea>
  </div>
  <div ref="result" class="result"></div>
</template>

<style scoped>
textarea {
  width: 80%;
  height: 200px;
  resize: none;
  border-radius: 10px;
  margin: 10px;
  padding: 10px;
  border: 3px solid #f68512;
  font-size: 18px;
}
.input-box {
  width: 100%;
  display: flex;
  justify-content: center;
}
h1 {
  padding: 10px 0;
  font-size: 24px;
  font-weight: bold;
  text-align: center;
}
.options-box {
  width: 90%;
  margin-top: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}
.option-item {
  height: 20px;
  padding: 10px 20px;
  margin: 5px 10px;
  background-color: #f68512;
  color: white;
  font-weight: bold;
  border-radius: 10px;
  cursor: pointer;
  text-align: center;
}
</style>
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇