<template>
<view class="container">
<!-- 扫描区域 -->
<view class="scan-area" v-if="isScanning">
<camera v-if="hasCameraPermission" :device-position="cameraPosition" @initdone="onCameraInit"
class="camera"></camera>
<!-- 扫描框 -->
<view class="scan-box" :style="{ width: scanBoxSize + 'px', height: scanBoxSize + 'px' }">
<view class="scan-line" :style="{ animation: 'scan ' + scanSpeed + 's linear infinite' }"></view>
<view class="corner corner-top-left"></view>
<view class="corner corner-top-right"></view>
<view class="corner corner-bottom-left"></view>
<view class="corner corner-bottom-right"></view>
</view>
<!-- 提示文字 -->
<view class="scan-tip">请将二维码对准扫描框</view>
<!-- 相册选择按钮 -->
<view class="album-button" @click="selectFromAlbum">
<image src="@/static/icons/photofff.svg" mode="" class="album-img"></image>
</view>
<!-- 手电筒控制 -->
<view class="torch-control" @click="toggleTorch">
<image src="@/static/icons/light4e86e7.svg" mode="" v-if="torchOn" class="torch-control-icon"></image>
<image src="@/static/icons/ligntfff.svg" mode="" v-if="!torchOn" class="torch-control-icon"></image>
<!-- <text class="torch-text">{{ torchOn ? '关闭手电筒' : '打开手电筒' }}</text> -->
</view>
</view>
<!-- 结果显示 -->
<view class="result-container" v-else-if="scanResult">
<view class="result-title">扫描结果</view>
<view class="result-content">{{ scanResult }}</view>
<button class="result-button" @click="startScan">重新扫描</button>
</view>
<!-- 错误提示 -->
<view class="error-tip" v-if="errorMsg">{{ errorMsg }}</view>
<!-- 权限申请 -->
<!-- <view class="permission-modal" v-if="!hasCameraPermission && !permissionDenied">
<view class="permission-content">
<view class="permission-title">需要相机权限</view>
<view class="permission-desc">请授予相机权限以使用扫描功能</view>
<button class="permission-button" @click="requestCameraPermission">授予权限</button>
</view>
</view> -->
<!-- 权限被拒绝提示 -->
<view class="permission-modal" v-if="permissionDenied">
<view class="permission-content">
<view class="permission-title">权限被拒绝</view>
<view class="permission-desc">请在设置中手动开启相机权限</view>
<button class="permission-button" @click="openAppSettings">去设置</button>
</view>
</view>
<!-- 隐藏的Canvas -->
<!-- #ifdef MP-WEIXIN -->
<canvas canvas-id="qr-canvas" class="hidden-canvas" />
<!-- #endif -->
</view>
</template>
<script setup>
import jsQR from 'jsqr'; // 引入jsQR库
import {
ref,
onMounted,
onUnmounted,
nextTick
} from 'vue'
// 状态变量
const isScanning = ref(false)
const scanResult = ref('')
const errorMsg = ref('')
const hasCameraPermission = ref(false)
const permissionDenied = ref(false)
const torchOn = ref(false)
const cameraPosition = ref('back')
const scanBoxSize = ref(250)
const scanSpeed = ref(2)
const cameraContext = ref(null)
const canvasContent = ref(null)
// 生命周期钩子
onMounted(() => {
checkCameraPermission()
// 创建canvas上下文
canvasContent.value = uni.createCanvasContext('qr-canvas')
})
onUnmounted(() => {
stopScan()
})
// 检查相机权限
const checkCameraPermission = () => {
uni.getSetting({
success: (res) => {
if (res.authSetting['scope.camera']) {
hasCameraPermission.value = true
startScan()
} else {
requestCameraPermission()
}
},
fail: () => {
errorMsg.value = '获取权限信息失败'
}
})
}
// 请求相机权限
const requestCameraPermission = () => {
uni.authorize({
scope: 'scope.camera',
success: () => {
hasCameraPermission.value = true
startScan()
},
fail: () => {
permissionDenied.value = true
}
})
}
// 打开应用设置
const openAppSettings = () => {
uni.openSetting({
success: (res) => {
if (res.authSetting['scope.camera']) {
hasCameraPermission.value = true
permissionDenied.value = false
startScan()
}
}
})
}
// 开始扫描
const startScan = () => {
isScanning.value = true
scanResult.value = ''
errorMsg.value = ''
nextTick(() => {
cameraContext.value = uni.createCameraContext()
startCameraScan()
})
}
// 停止扫描
const stopScan = () => {
isScanning.value = false
if (cameraContext.value) {
cameraContext.value.stopPreview()
}
}
// 开始相机扫描
const startCameraScan = () => {
const listener = cameraContext.value.scanCode({
success: (res) => {
scanResult.value = res.result
isScanning.value = false
},
fail: (err) => {
errorMsg.value = '扫描失败,请重试'
console.error('Scan failed:', err)
// 继续扫描
setTimeout(startCameraScan, 1000)
}
})
}
// 切换手电筒
const toggleTorch = () => {
torchOn.value = !torchOn.value
if (cameraContext.value) {
cameraContext.value.switchTorch({
torch: torchOn.value ? 'on' : 'off',
success: () => {},
fail: (err) => {
console.error('Switch torch failed:', err)
torchOn.value = !torchOn.value
errorMsg.value = '手电筒切换失败'
}
})
}
}
// 从相册选择图片
const selectFromAlbum = async () => {
try {
const res = await uni.chooseImage({
count: 1,
sizeType: ['compressed'], // 压缩图片提高性能
sourceType: ["album"]
})
const imagePath = res.tempFilePaths[0]
errorMsg.value = "" //清除错误信息
// #ifdef APP-PLUS
await decodeWithNativeAPI(imagePath)
// #endif
// #ifdef H5 || MP-WEIXIN
await decodeWithJsQR(imagePath)
// #endif
} catch (err) {
console.error('Choose image failed:', err)
errorMsg.value = '图片选择失败'
isScanning.value = true
}
// uni.chooseImage({
// count: 1,
// sourceType: ['album'],
// success: (res) => {
// const imagePath = res.tempFilePaths[0]
// uni.scanCode({
// onlyFromCamera: false, //允许从相册选择
// scanType: ['qrCode'],
// path: imagePath, // 传入图片路径
// success: (scanRes) => {
// scanResult.value = scanRes.result
// isScanning.value = false
// },
// fail: (err) => {
// errorMsg.value = '未能识别图片中的二维码'
// console.error('Scan from album failed:', err)
// }
// })
// },
// fail: (err) => {
// console.error('Choose image failed:', err)
// errorMsg.value = '图片选择失败'
// }
// })
}
//使用jsQR解析二维码(微信小程序和H5)
const decodeWithJsQR = (imagePath) => {
return new Promise((resolve, reject) => {
// 创建临时Image对象
const img = new Image()
img.src = imagePath
img.onload = async () => {
try {
// 设置Canvas尺寸(缩小尺寸提高性能)
const canvasWidth = 300
const canvasHeight = 300
// 在Canvas上绘制图像
canvasContext.value.drawImage(img, 0, 0, canvasWidth, canvasHeight)
await canvasContext.value.draw(true)
// 获取图像数据
const res = await uni.canvasGetImageData({
canvasId: 'qr-canvas',
x: 0,
y: 0,
width: canvasWidth,
height: canvasHeight
})
// 使用jsQR解析二维码
const qrCode = jsQR(res.data, res.width, res.height)
if (qrCode) {
scanResult.value = qrCode.data
isScanning.value = false
resolve()
} else {
errorMsg.value = '未识别到二维码'
reject('未识别到二维码')
}
} catch (err) {
console.error('Canvas操作失败:', err)
reject(err)
}
}
img.onerror = (err) => {
console.error('图片加载失败:', err)
reject(err)
}
})
}
// 使用原生API解析二维码(APP)
const decodeWithNativeAPI = (imagePath) => {
return new Promise((resolve) => {
// APP平台使用原生扫码API
plus.barcode.decode(
imagePath,
(result) => {
scanResult.value = result
isScanning.value = false
resolve()
},
(error) => {
console.error('原生解析错误:', error)
errorMsg.value = '二维码解析失败'
reject(error)
},
'QR' // 只解析二维码
)
})
}
// 相机初始化回调
const onCameraInit = (event) => {
console.log('Camera initialized:', event)
}
</script>
<style scoped lang="scss">
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
position: relative;
}
.scan-area {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.camera {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.scan-box {
position: relative;
border: 2px solid rgba(255, 255, 255, 0.7);
box-shadow: 0 0 30px rgba(0, 255, 0, 0.5);
}
.scan-line {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(0, 255, 0, 1) 50%, rgba(255, 255, 255, 0) 100%);
}
.corner {
position: absolute;
width: 30px;
height: 30px;
border-width: 4px;
border-color: #00ff00;
}
.corner-top-left {
top: -4px;
left: -4px;
border-left-style: solid;
border-top-style: solid;
border-radius: 4px 0 0 0;
}
.corner-top-right {
top: -4px;
right: -4px;
border-right-style: solid;
border-top-style: solid;
border-radius: 0 4px 0 0;
}
.corner-bottom-left {
bottom: -4px;
left: -4px;
border-left-style: solid;
border-bottom-style: solid;
border-radius: 0 0 0 4px;
}
.corner-bottom-right {
bottom: -4px;
right: -4px;
border-right-style: solid;
border-bottom-style: solid;
border-radius: 0 0 4px 0;
}
.scan-tip {
position: absolute;
bottom: 500rpx;
left: 0;
right: 0;
text-align: center;
color: #fff;
font-size: 16px;
padding: 10px;
border-radius: 8px;
}
.torch-control {
position: absolute;
bottom: 300rpx;
right: 100rpx;
text-align: center;
background: rgba(193, 200, 210, .4);
border-radius: 50px;
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
.torch-control-icon {
width: 60rpx;
height: 60rpx;
}
}
.torch-text {
color: #fff;
font-size: 16px;
// background-color: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 25px;
cursor: pointer;
}
.result-container {
width: 100%;
text-align: center;
padding: 20px;
background-color: #fff;
border-radius: 12px;
margin-bottom: 20px;
}
.result-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 15px;
}
.result-content {
font-size: 16px;
margin-bottom: 20px;
word-break: break-all;
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #eee;
padding: 10px;
border-radius: 8px;
}
.result-button,
.permission-button {
background-color: #007aff;
color: #fff;
border: none;
padding: 12px 20px;
border-radius: 25px;
font-size: 16px;
width: 100%;
margin-top: 15px;
cursor: pointer;
}
.album-button {
position: absolute;
bottom: 300rpx;
left: 100rpx;
background: rgba(193, 200, 210, .4);
border-radius: 50px;
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
.album-img {
width: 60rpx;
height: 60rpx;
}
}
.error-tip {
color: #ff3b30;
font-size: 14px;
margin-top: 15px;
position: absolute;
bottom: 400rpx;
left: 0;
right: 0;
}
.permission-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
// background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.permission-content {
background-color: #fff;
border-radius: 12px;
padding: 30px 20px;
width: 80%;
max-width: 350px;
text-align: center;
}
.permission-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
}
.permission-desc {
font-size: 16px;
margin-bottom: 25px;
color: #666;
}
/* 隐藏的Canvas */
.hidden-canvas {
position: absolute;
top: -9999px;
width: 300px;
height: 300px;
visibility: hidden;
}
@keyframes scan {
0% {
transform: translateY(0);
}
100% {
transform: translateY(250px);
}
}
</style>这段代码按照上述需求怎么修改,不报错,vue3+uniapp开发的微信小程序,使用扫一扫,支持扫描相册中的图片