html5-qrcode实现移动端浏览器扫描二维码&条形码
- 2026了,更新文档,解决一些实际问题
- 原先扫码功能确实会存在横屏无法扫码,切换至
html5-qrcode即可实现轻松扫码 - https协议还是必须的,正式环境可以生成本地ssl证书。本地开发可以用插件,本地ssl证书或者直接开发都行,正式环境必须https协议
1、https协议
1.1 方案一:使用插件,适合本地开发调试
- 移动端访问摄像头权限需要 https 协议
- 安装@vitejs/plugin-basic-ssl并配置https协议 本地开发使用
pnpm i @vitejs/plugin-basic-ssl -D
vite.config.ts
import basicSsl from '@vitejs/plugin-basic-ssl'
export default {
server: {
https: true
},
plugins: [ basicSsl() ]
}
1.2 方案二:生成ssl证书,适合本地开发或者测试环境等,正式环境替换为正式证书即可
要生成适配Nginx的本地可信 SSL 证书,推荐用 mkcert 工具,步骤简单且能避免浏览器 “不安全” 提示,全程基于 Windows 系统:
- 1.1.1.188 是测试IP地址,实际IP地址填服务器或者自己的IP地址即可
前提准备
-
下载 mkcert访问 mkcert 官网 Releases,下载 Windows 版本(如
mkcert-v1.4.4-windows-amd64.exe)。将文件重命名为mkcert.exe,放到C:\mkcert文件夹下(手动新建该文件夹)。 -
配置环境变量右键「此电脑」→ 属性 → 高级系统设置 → 环境变量 → 系统变量 → 找到
Path→ 编辑 → 新建 → 输入C:\mkcert→ 确定保存。 -
验证安装打开普通终端(无需管理员),执行:
运行
mkcert --version能显示版本号即安装成功。
生成证书(适配你的 IP 和域名)
-
进入 Nginx 的 cert 目录你的 Nginx 根目录是
D:\Desktop\nginx,先创建cert文件夹并进入:运行
cd D:\Desktop\nginx mkdir cert # 若已存在则跳过 cd cert -
生成证书执行命令,包含你需要的 IP 和本地域名(和 Nginx 配置的
server_name一致):运行
mkcert 1.1.1.188 localhost 127.0.0.1 ::1执行后,
cert文件夹会生成两个文件:- 证书文件:
1.1.1.188+3.pem(+3是自动生成的数字,以实际为准) - 私钥文件:
1.1.1.188+3-key.pem
- 证书文件:
-
信任根证书执行命令安装本地 CA 根证书,浏览器会自动信任生成的证书:
运行
mkcert -install
2、使用证书和开启https协议 ( 方案二扩展 )
2.1 本地开发
- 在 vite.config.ts 配置 https 证书路径即可
vite.config.ts
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
import compression from 'vite-plugin-compression'
import fs from 'fs';
//自动导入ui-组件 比如说ant-design-vue element-plus等
import Components from 'unplugin-vue-components/vite';
//ant-design-vue
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers"
export default defineConfig(({ command, mode }) => {
let env = loadEnv(mode, process.cwd())
return {
server: {
https: {
cert: fs.readFileSync(path.resolve(__dirname, './1.1.1.188+3.pem')), // 证书文件路径
key: fs.readFileSync(path.resolve(__dirname, './1.1.1.188+3-key.pem')) // 私钥文件路径
},
host: true,
open: false,
proxy: {
},
},
}
})
2.1 nginx 部署
-
其中 cerf 用于存放 ssl 证书
-
配置
server {
listen 9001 ssl; # 关键:9001 端口直接启用 HTTPS(而非 HTTP 重定向)
server_name localhost 1.1.1.188; # 匹配证书中的域名/IP
# 证书配置(与之前一致)
ssl_certificate ../cert/1.1.1.188+3.pem;
ssl_certificate_key ../cert/1.1.1.188+3-key.pem;
# SSL 优化配置(保留)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location = /Test {
root html;
try_files /KaBo/index.html =404;
}
# ========== ,刷新不404 ==========
location /Test/ {
root html;
index Test/index.html;
# 关键:所有 /Test/ 开头的请求,都重定向到 KaBo/index.html(交给前端路由)
try_files $uri $uri/ /Test/index.html;
}
location / {
root html;
index Test/index.html;
try_files $uri $uri/ /Test/index.html;
}
location /kaboApi/ {
proxy_pass http://1.1.1.81:18888/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
- 通过
nginx -t检查配置是否有问题,没有直接执行重载命令nginx -s reload即可 - 有问题可以交给AI修复
3、封装 html5-qrcode 组件
- 基于
html5-qrcode分装成组件,方便调用 - 处于扫码组件内部是阻止浏览器默认返回上一页的默认行为,改为销毁组件
3.1 组件完整代码
<template>
<div class="scanCode">
<div id="reader"></div>
<a-button class="btn" @click="closeScan" shape="circle" :icon="h(CloseOutlined)">
</a-button>
</div>
</template>
<script setup lang="ts">
import { ref, h, onMounted } from 'vue'
import { Html5Qrcode } from 'html5-qrcode'
import { CloseOutlined } from '@ant-design/icons-vue';
const codeContent = ref('1024')
const $emits = defineEmits(['cancel', 'scanResult'])
let html5: any = null
Html5Qrcode.getCameras()
.then((devices) => {
const html5QrCode = new Html5Qrcode('reader')
const width = window.innerWidth
const height = window.innerHeight
const aspectRatio = width / height
const reverseAspectRatio = height / width
const mobileAspectRatio =
reverseAspectRatio > 1.5
? reverseAspectRatio + (reverseAspectRatio * 12) / 100
: reverseAspectRatio
html5 = html5QrCode
if (devices && devices.length) {
html5QrCode
.start(
{ facingMode: { exact: 'environment' } },
{
fps: 10,
aspectRatio: aspectRatio + 1,
qrbox: { width: 250, height: 250 },
videoConstraints: {
facingMode: 'environment',
aspectRatio:
width < 600 ? mobileAspectRatio : aspectRatio,
},
},
(decodedText, decodedResult) => {
codeContent.value = decodedText
$emits('scanResult', decodedText)
html5QrCode.stop()
},
(errorMessage) => {
}
)
.catch((err) => {
})
}
})
.catch((err) => {
// handle err
console.log(err)
})
const closeScan = () => {
if (html5) {
html5.stop()
}
$emits('cancel')
}
onMounted(() => {
// 禁止返回上一页 返回上一页是触发关闭扫码
history.pushState('', '', document.URL)
window.addEventListener('popstate', function (e) {
if (html5) {
html5.stop()
}
$emits('cancel')
})
})
</script>
<style lang="scss" scoped>
.scanCode {
width: 100dvw;
height: 100dvh;
display: flex;
flex-direction: column;
background: rgba(0, 0, 0);
position: fixed;
top: 0;
left: 0;
z-index: 9999;
#reader {
top: 50%;
left: 0;
transform: translateY(-50%);
}
.btn {
position: absolute;
top: 20px;
right: 20px;
}
}
</style>
3.2 使用案例
同时支持PC、pad、移动端
<template>
<ScanQrCodePad v-if="scanCoding" @cancel="scanCoding = false" @scanResult="scanResult">
</ScanQrCodePad>
</template>
<script setup lang="ts">
import {ref} from 'vue'
// TODO: 全局注册或者直接导入组件
// 控制是否扫码
const scanCoding = ref(false)
// 扫描结果输出
const scanResult = (val:any)=>{
console.log(val)
}
</script>
<style scoped>
</style>