Uniapp Vue2环境H5模式实现二维码扫码功能 uniapp h5实现扫码
Uniapp Vue2环境H5模式实现二维码扫码功能 uniapp h5实现扫码
生产环境开启HTTPS
移动端扫码是需要 https环境下才可以进行扫码
全局安装 mkcert
npm install -g mkcert
生成证书文件
在项目根目录下打开终端,生成证书文件(cert.crt和 cert.key)
mkcert create-ca
mkcert create-cert
配置 vue.config.js
在项目根目录创建或修改 vue.config.js文件,引入生成的证书
const path = require('path')
const fs = require('fs')
module.exports = {
devServer: {
https: {
key: fs.readFileSync(path.join(__dirname, 'cert.key')), // 你的私钥文件路径
cert: fs.readFileSync(path.join(__dirname, 'cert.crt')) // 你的证书文件路径
},
host: '0.0.0.0', // 可选,允许通过IP访问
port: 8080, // 端口号
open: true // 可选,启动后自动打开浏览器
}
}

安装扫码库依赖
npm i html5-qrcode
独立扫码页面
扫码页面文件名:index.vue,路径:pages/html5QrCode/index

扫码页面代码
<!-- 独立的h5扫码页面 -->
<template>
<view class="scan-container">
<!-- 扫码区域 -->
<view class="reader-box">
<view class="reader" id="reader"></view>
</view>
<!-- 操作按钮 -->
<view class="action-buttons">
<button class="scan-btn" @tap="handleManualInput">手动输入</button>
</view>
<!-- 手动输入模态框 -->
<uni-popup ref="inputPopup" type="dialog">
<view class="input-modal">
<text class="modal-title">手动输入编码</text>
<input
v-model="manualCode"
class="code-input"
placeholder="请输入设备编码"
focus
/>
<view class="modal-buttons">
<button class="modal-btn cancel" @tap="closeModal">取消</button>
<button class="modal-btn confirm" @tap="submitManualCode">确定</button>
</view>
</view>
</uni-popup>
<!-- 状态提示 -->
<view class="status-tip" v-if="statusMessage">
{{ statusMessage }}
</view>
</view>
</template>
<script>
import { Html5Qrcode } from 'html5-qrcode';
export default {
data() {
return {
html5QrCode: null,
cameraId: null,
scanResult: null,
statusMessage: '',
manualCode: '',
isScanning: false,
hasScanned: false, // 新增:标记是否已扫码成功
isStopping: false // 新增:标记是否正在停止扫码
};
},
onLoad() {
this.initScan();
},
onUnload() {
// 页面卸载时强制停止扫码
this.forceStopScan();
},
onHide() {
// 页面隐藏时停止扫码
this.safeStopScan();
},
methods: {
// 初始化扫码
async initScan() {
this.statusMessage = '正在初始化摄像头...';
try {
const devices = await Html5Qrcode.getCameras();
if (!devices || devices.length === 0) {
this.statusMessage = '未检测到可用摄像头';
return;
}
let cameraIndex = devices.length > 1 ? 1 : 0;
this.cameraId = devices[cameraIndex].id;
this.startScan();
} catch (err) {
console.error('获取摄像头失败:', err);
this.handleCameraError(err);
}
},
// 开始扫码
startScan() {
if (!this.cameraId || this.isScanning || this.hasScanned) return;
this.html5QrCode = new Html5Qrcode('reader');
this.isScanning = true;
this.isStopping = false;
const scanConfig = {
fps: 20,
qrbox: 300,
video: { facingMode: 'environment' }
};
this.html5QrCode.start(
this.cameraId,
scanConfig,
this.handleScanSuccess,
this.handleScanError
).catch(err => {
console.error('扫码启动失败:', err);
this.handleCameraError(err);
});
this.statusMessage = '请对准二维码';
},
// 处理扫码成功 - 关键修复
async handleScanSuccess(decodedText) {
// 防止重复处理
if (this.hasScanned) return;
this.scanResult = decodedText;
this.hasScanned = true; // 标记已扫码成功
console.log('扫码成功:', decodedText);
// 安全停止扫码
await this.safeStopScan();
// 返回结果到上一个页面
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
if (prevPage && prevPage.$vm) {
prevPage.$vm.$emit('scan-result', this.scanResult);
}
// 确保扫码完全停止后再返回
setTimeout(() => {
uni.navigateBack();
}, 100);
},
// 处理扫码错误
handleScanError(errorMessage) {
console.log('扫码错误:', errorMessage);
},
// 处理摄像头错误
handleCameraError(err) {
let message = "扫码功能不可用";
if (err.name === "NotAllowedError") {
message = "请允许浏览器访问摄像头权限";
} else if (err.name === "NotFoundError") {
message = "未找到可用摄像头";
} else if (err.name === "NotSupportedError") {
message = "当前环境不支持摄像头访问";
} else if (err.name === "NotReadableError") {
message = "摄像头被占用,请关闭其他应用";
}
this.statusMessage = message;
this.isScanning = false;
},
// 安全停止扫码
safeStopScan() {
return new Promise((resolve) => {
if (!this.html5QrCode || !this.isScanning || this.isStopping) {
resolve();
return;
}
this.isStopping = true;
this.html5QrCode.stop().then(() => {
console.log("扫码已安全停止");
this.isScanning = false;
this.isStopping = false;
resolve();
}).catch(err => {
console.log("停止扫码失败:", err);
this.isScanning = false;
this.isStopping = false;
resolve();
});
});
},
// 强制停止扫码
forceStopScan() {
if (this.html5QrCode) {
try {
// 直接停止,不等待结果
this.html5QrCode.stop().catch(() => {});
this.isScanning = false;
} catch (err) {
console.log('强制停止扫码异常:', err);
}
}
},
// 手动输入
handleManualInput() {
this.safeStopScan().then(() => {
this.$refs.inputPopup.open();
});
},
// 提交手动输入
submitManualCode() {
if (this.manualCode && !this.hasScanned) {
this.handleScanSuccess(this.manualCode);
}
this.closeModal();
},
// 关闭模态框
closeModal() {
this.$refs.inputPopup.close();
this.manualCode = '';
if (!this.hasScanned) {
this.startScan();
}
}
}
};
</script>
<style lang="scss">
.scan-container {
display: flex;
flex-direction: column;
width: 100%;
background-color: #000;
.reader-box {
flex: 1;
position: relative;
overflow: hidden;
background-color: rgba(0, 0, 0, 0.7);
.reader {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.reader video{
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
}
}
.action-buttons {
display: flex;
justify-content: space-around;
padding: 20rpx;
background-color: rgba(0, 0, 0, 0.8);
.scan-btn {
flex: 1;
margin: 0 10rpx;
background-color: #5f77fd;
color: #fff;
border-radius: 50rpx;
font-size: 28rpx;
}
}
.input-modal {
padding: 40rpx;
background-color: #fff;
border-radius: 16rpx;
width: 80vw;
.modal-title {
display: block;
text-align: center;
font-size: 36rpx;
font-weight: bold;
margin-bottom: 40rpx;
color: #333;
}
.code-input {
height: 100rpx;
border: 2rpx solid #eee;
border-radius: 10rpx;
padding: 0 20rpx;
margin-bottom: 40rpx;
font-size: 32rpx;
}
.modal-buttons {
display: flex;
justify-content: space-between;
.modal-btn {
flex: 1;
margin: 0 10rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 10rpx;
font-size: 32rpx;
&.cancel {
background-color: #f8f8f8;
color: #666;
}
&.confirm {
background-color: #5f77fd;
color: #fff;
}
}
}
}
.status-tip {
position: absolute;
bottom: 150rpx;
left: 0;
right: 0;
text-align: center;
color: #fff;
font-size: 32rpx;
padding: 20rpx;
background-color: rgba(0, 0, 0, 0.5);
}
}
</style>
配置扫码页面
在 pages.json配置当中增加这个页面
{
"path": "pages/html5QrCode/index",
"style": {
"enablePullDownRefresh": true,
"titleNView": {
"titleText": "二维码扫描",
"titleColor": "#fff",
"titleSize": "36rpx",
"backgroundColor": "#5f77fd"
},
"backgroundColor": "#F9F9F9",
"scrollIndicator": "none"
}
}
页面调用扫码功能
<template>
<view>扫码结果:{{scanVal}}</view>
</template>
<script>
export default {
data() {
return {
scanVal: ''
}
},
// 在页面加载时添加事件监听
onLoad() {
// 监听扫码结果事件
this.$on('scan-result', this.handleScanResult);
},
methods: {
// h5打开扫码页面
h5ScanCode() {
uni.navigateTo({
url: '/pages/scan-code/scan-code'
});
},
// 处理扫码结果
async handleScanResult(result) {
// 先隐藏所有的弹出提示,避免下面扫码结果处理的提示被覆盖
uni.hideLoading();
// 增加延迟,用于解决扫码返回多个逻辑一起处理导致页面异常
await new Promise(resolve => setTimeout(resolve, 500));
await new Promise(resolve => setTimeout(resolve, 100));
console.log('收到扫码结果:', result);
this.scanVal = result;
// 处理扫码结果...
}
}
}
</script>
4471

被折叠的 条评论
为什么被折叠?



