Vue 移动应用条形码扫描与拍照集成方案
下面是一个完整的 Vue 移动应用实现方案,包含条形码扫描和拍照功能,并正确处理手机相机资源。
方案一:使用 Cordova/Capacitor 插件(适合混合应用)
1. 安装必要插件
# 对于 Capacitor 项目
npm install @capacitor/camera @capacitor/barcode-scanner
npx cap sync
# 对于 Cordova 项目
cordova plugin add phonegap-plugin-barcodescanner
cordova plugin add cordova-plugin-camera
2. 实现代码
<template>
<div class="scanner-container">
<!-- 扫描界面 -->
<div v-if="mode === 'scan'" class="mode-container">
<button @click="startScan">开始扫描条形码</button>
<p v-if="scanResult">扫描结果: {{ scanResult }}</p>
</div>
<!-- 拍照界面 -->
<div v-if="mode === 'camera'" class="mode-container">
<div class="camera-preview">
<img v-if="capturedImage" :src="capturedImage" class="preview-image">
<video v-else ref="videoElement" autoplay playsinline class="camera-view"></video>
</div>
<div class="controls">
<button @click="takePicture" v-if="!capturedImage">拍照</button>
<button @click="retakePicture" v-if="capturedImage">重拍</button>
<button @click="confirmPicture" v-if="capturedImage">确认照片</button>
<button @click="closeCamera">返回</button>
</div>
</div>
</div>
</template>
<script>
import { BarcodeScanner } from '@capacitor-community/barcode-scanner';
import { Camera } from '@capacitor/camera';
export default {
data() {
return {
mode: 'scan', // 'scan' 或 'camera'
scanResult: '',
capturedImage: '',
mediaStream: null,
currentBarcode: null
};
},
methods: {
// 条形码扫描方法
async startScan() {
try {
// 请求相机权限
await BarcodeScanner.checkPermission({ force: true });
// 隐藏页面内容(全屏扫描)
document.querySelector('body').classList.add('scanner-active');
const result = await BarcodeScanner.startScan();
if (result.hasContent) {
this.scanResult = result.content;
this.currentBarcode = result.content;
this.mode = 'camera'; // 扫描成功后切换到拍照模式
}
} catch (error) {
console.error('扫描错误:', error);
alert('扫描失败: ' + error.message);
} finally {
// 恢复页面显示
BarcodeScanner.showBackground();
document.querySelector('body').classList.remove('scanner-active');
}
},
// 初始化相机
async initCamera() {
try {
this.mediaStream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment', // 使用后置摄像头
width: { ideal: 1280 },
height: { ideal: 720 }
}
});
this.$refs.videoElement.srcObject = this.mediaStream;
} catch (error) {
console.error('相机初始化失败:', error);
alert('无法访问相机: ' + error.message);
}
},
// 拍照
async takePicture() {
try {
// 使用 Capacitor Camera 插件
const image = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: 'dataUrl',
direction: 'rear'
});
this.capturedImage = image.dataUrl;
// 关闭视频流
this.closeVideoStream();
} catch (error) {
console.error('拍照失败:', error);
}
},
// 重拍
retakePicture() {
this.capturedImage = '';
this.initCamera();
},
// 确认照片
confirmPicture() {
// 这里可以处理上传逻辑
alert(`条形码: ${this.currentBarcode}\n照片已保存`);
this.resetProcess();
},
// 关闭相机
closeCamera() {
this.closeVideoStream();
this.resetProcess();
},
// 关闭视频流
closeVideoStream() {
if (this.mediaStream) {
this.mediaStream.getTracks().forEach(track => track.stop());
if (this.$refs.videoElement) {
this.$refs.videoElement.srcObject = null;
}
this.mediaStream = null;
}
},
// 重置流程
resetProcess() {
this.capturedImage = '';
this.currentBarcode = '';
this.scanResult = '';
this.mode = 'scan';
}
},
beforeDestroy() {
this.closeVideoStream();
}
};
</script>
<style>
.scanner-container {
padding: 20px;
}
.mode-container {
display: flex;
flex-direction: column;
align-items: center;
}
.camera-preview {
width: 100%;
max-width: 500px;
margin: 20px 0;
position: relative;
aspect-ratio: 4/3;
background-color: #000;
}
.camera-view, .preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
button {
padding: 10px 20px;
background-color: #42b983;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
}
/* 扫描模式下的全屏样式 */
.scanner-active {
--background: transparent;
--ion-background-color: transparent;
}
</style>
方案二:纯 Web 实现(使用开源库)
如果不使用 Cordova/Capacitor,可以使用以下纯 Web 方案:
1. 安装依赖
npm install vue-qrcode-reader quagga
2. 实现代码
<template>
<div>
<!-- 条形码扫描 -->
<div v-if="!showCamera">
<button @click="startBarcodeScan">扫描条形码</button>
<div ref="barcodeScanner" style="width: 100%; height: 300px;"></div>
<p v-if="barcodeResult">扫描结果: {{ barcodeResult }}</p>
</div>
<!-- 拍照界面 -->
<div v-if="showCamera">
<video ref="videoElement" autoplay playsinline style="width: 100%;"></video>
<button @click="captureImage">拍照</button>
<button @click="stopCamera">取消</button>
<canvas ref="canvasElement" style="display: none;"></canvas>
<div v-if="capturedImage">
<img :src="capturedImage" style="max-width: 100%;">
<button @click="savePhoto">保存照片</button>
<button @click="retakePhoto">重拍</button>
</div>
</div>
</div>
</template>
<script>
import Quagga from 'quagga';
export default {
data() {
return {
showCamera: false,
barcodeResult: '',
capturedImage: '',
mediaStream: null
};
},
methods: {
// 条形码扫描
startBarcodeScan() {
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: this.$refs.barcodeScanner,
constraints: {
width: 480,
height: 320,
facingMode: "environment"
},
},
decoder: {
readers: ["ean_reader", "ean_8_reader", "code_128_reader"]
},
}, err => {
if (err) {
console.error(err);
return;
}
Quagga.start();
});
Quagga.onDetected(result => {
this.barcodeResult = result.codeResult.code;
Quagga.stop();
this.showCamera = true;
this.initCamera();
});
},
// 初始化相机
async initCamera() {
try {
this.mediaStream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 1280 },
height: { ideal: 720 }
}
});
this.$refs.videoElement.srcObject = this.mediaStream;
} catch (error) {
console.error('相机错误:', error);
}
},
// 拍照
captureImage() {
const video = this.$refs.videoElement;
const canvas = this.$refs.canvasElement;
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
this.capturedImage = canvas.toDataURL('image/jpeg', 0.9);
this.closeVideoStream();
},
// 保存照片
savePhoto() {
// 这里可以处理上传逻辑
alert(`条形码: ${this.barcodeResult}\n照片已保存`);
this.resetProcess();
},
// 重拍
retakePhoto() {
this.capturedImage = '';
this.initCamera();
},
// 停止相机
stopCamera() {
this.closeVideoStream();
this.resetProcess();
},
// 关闭视频流
closeVideoStream() {
if (this.mediaStream) {
this.mediaStream.getTracks().forEach(track => track.stop());
this.$refs.videoElement.srcObject = null;
this.mediaStream = null;
}
},
// 重置流程
resetProcess() {
this.capturedImage = '';
this.barcodeResult = '';
this.showCamera = false;
}
},
beforeDestroy() {
Quagga.stop();
this.closeVideoStream();
}
};
</script>
关键注意事项
-
相机权限管理:
- 确保应用有相机权限
- 处理用户拒绝权限的情况
-
资源释放:
- 每次切换模式时释放之前的相机资源
- 在组件销毁时确保释放所有资源
-
移动设备优化:
- 使用
facingMode: 'environment'
强制使用后置摄像头 - 添加
playsinline
属性确保在 iOS 上正常工作
- 使用
-
性能考虑:
- 限制相机分辨率以避免性能问题
- 及时停止扫描器和相机以节省电量
-
用户体验:
- 提供清晰的界面状态指示
- 添加加载状态和错误反馈
以上两种方案都可以在 Vue 移动应用中实现先扫描条形码再拍照的功能,第一种方案更适合打包成原生应用,第二种方案则适合纯 Web 应用。