<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"></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>
</view>
</template>
<script setup>
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)
// 生命周期钩子
onMounted(() => {
checkCameraPermission()
})
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 = () => {
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 = '图片选择失败'
}
})
}
// 相机初始化回调
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;
}
.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;
}
@keyframes scan {
0% {
transform: translateY(0);
}
100% {
transform: translateY(250px);
}
}
</style>这些代码要修改成上述需求,点击自定义相册,选择照片后直接扫描不跳转的完整过程
最新发布