扫码功能文档(含源码+常见权限问题)

效果

在这里插入图片描述

常见问题

在这里插入图片描述

概述

本文档介绍了uni-app应用中的扫码功能实现,包括相机扫码和图片识别两种方式,支持二维码和条形码的识别。

功能特性

1. 多种扫码方式

  • 相机实时扫码:使用设备相机进行实时二维码/条形码扫描
  • 图片识别:从相册选择图片进行二维码/条形码识别

2. 智能状态管理

  • 扫描状态指示:动态显示当前扫描状态(等待、扫描中、权限被拒绝等)
  • 动画效果控制:扫描线动画仅在扫描过程中显示
  • 权限状态管理:智能处理相机权限授权状态

3. 跨平台支持

  • APP-PLUS:完整功能支持
  • H5:部分功能支持(相册选择暂不支持)
  • 鸿蒙系统:特殊权限处理适配

技术实现

核心文件

  • 文件路径pages/scan/index.vue
  • 主要组件:Vue单文件组件

关键数据字段

data() {
    return {
        scanResult: '',           // 扫描结果
        isScanning: false,        // 是否正在扫描
        scanStatus: 'waiting',    // 扫描状态:waiting/scanning/permission_denied
        isInitialized: false,     // 页面是否已初始化
        permissionDenied: false   // 权限是否被拒绝
    }
}

扫描状态说明

状态 描述 显示文本
waiting 等待扫描 “将二维码放入框内,即可自动扫描”
scanning 扫描中 “准备扫描中…”
permission_denied 权限被拒绝 “相机权限被拒绝,请授权后重试”

主要功能方法

1. 相机扫码 (startScan)

startScan() {
    // 设置扫描状态
    this.isScanning = true;
    this.scanStatus = 'scanning';
    
    // 调用uni.scanCode进行扫码
    uni.scanCode({
        scanType: ['qrCode', 'barCode'],
        autoDecodeCharSet: true,
        success: (res) => {
            // 处理扫码成功
            this.scanResult = res.result;
            this.handleScanResult(res.result);
        },
        fail: (err) => {
            // 处理扫码失败
            this.scanStatus = 'waiting';
        },
        complete: () => {
            this.isScanning = false;
        }
    });
}

2. 图片识别 (openAlbum)

openAlbum() {
    // 选择图片
    uni.chooseImage({
        count: 1,
        sourceType: ['album'],
        success: (res) => {
            const imagePath = res.tempFilePaths[0];
            
            // 双重识别机制
            try {
                // 优先使用plus.barcode.scan
                plus.barcode.scan(imagePath, (type, result) => {
                    if (result) {
                        this.handleScanResult(result);
                    }
                });
            } catch (e) {
                // 降级使用uni.scanCode
                uni.scanCode({
                    filePath: imagePath,
                    success: (scanRes) => {
                        this.handleScanResult(scanRes.result);
                    }
                });
            }
        }
    });
}

3. 权限检查 (checkCameraPermission)

支持Android和鸿蒙系统的相机权限检查:

checkCameraPermission() {
    return new Promise((resolve, reject) => {
        if (this.isHarmonyOS()) {
            // 鸿蒙系统权限处理
            plus.android.requestPermissions(['ohos.permission.CAMERA'], 
                (result) => {
                    resolve(result.granted.length > 0);
                },
                (error) => {
                    reject(error);
                }
            );
        } else {
            // Android系统权限处理
            plus.android.requestPermissions(['android.permission.CAMERA'],
                (result) => {
                    resolve(result.granted.length > 0);
                },
                (error) => {
                    reject(error);
                }
            );
        }
    });
}

4. 结果处理 (handleScanResult)

handleScanResult(result) {
    if (result.startsWith('http')) {
        // URL类型结果处理
        const idMatch = result.match(/[\?&]id=([^&]*)/i);
        const id = idMatch ? idMatch[1] : null;
        
        if (id) {
            // 跳转到指定页面
            uni.navigateTo({
                url: `/pages/workbench/inspection/list?AS_ID=${id}`
            });
        }
    } else {
        // 其他类型结果处理
        uni.showToast({
            title: `扫码结果:${result}`,
            icon: 'none'
        });
    }
}

UI组件说明

扫描框架构

<template>
    <view class="scan-container">
        <!-- 扫描框 -->
        <view class="scan-box">
            <!-- 扫描线动画 -->
            <view class="scan-line" :class="{ 'scan-line-active': scanStatus === 'scanning' }"></view>
            
            <!-- 四个角的装饰 -->
            <view class="scan-angle top-left"></view>
            <view class="scan-angle top-right"></view>
            <view class="scan-angle bottom-left"></view>
            <view class="scan-angle bottom-right"></view>
        </view>
        
        <!-- 状态提示文本 -->
        <text class="tips-text tips-waiting" v-if="scanStatus === 'waiting'">
            将二维码放入框内,即可自动扫描
        </text>
        <text class="tips-text tips-error" v-else-if="scanStatus === 'permission_denied'">
            相机权限被拒绝,请授权后重试
        </text>
        <text class="tips-text" v-else>
            准备扫描中...
        </text>
        
        <!-- 操作按钮 -->
        <view class="scan-buttons">
            <button @click="startScan">重新扫描</button>
            <button @click="openAlbum">从相册选择</button>
        </view>
    </view>
</template>

样式特性

  • 扫描线动画:仅在 scan-line-active 状态下执行
  • 状态指示颜色
    • tips-waiting:正常状态(蓝色)
    • tips-error:错误状态(红色)
  • 响应式设计:适配不同屏幕尺寸

使用方法

1. 相机扫码

  1. 进入扫码页面
  2. 授权相机权限(首次使用)
  3. 将二维码/条形码对准扫描框
  4. 系统自动识别并处理结果

2. 图片识别

  1. 点击"从相册选择"按钮
  2. 选择包含二维码/条形码的图片
  3. 系统自动识别图片中的码
  4. 显示识别结果

常见问题

1、打包时未添加barcode模块,请参考https://ask.dcloud.net.cn/article/283
解决方案:在manifest.json中添加配置

    "app-plus" : {
        "usingComponents" : true,
        "compilerVersion" : 3,
        "modules" : {
            "Push" : {},
            "Geolocation" : {},
            "Maps" : {},
            "Camera" : {},
            "Barcode" : {}
        },

全部代码

<template>
	<view class="scan-container">
		<!-- <view class="scan-header">
			<text class="scan-title">扫一扫</text>
		</view> -->
		
		<view class="scan-content">
			<view class="scan-area">
				<view class="scan-frame">
					<view class="scan-corner scan-corner-tl"></view>
					<view class="scan-corner scan-corner-tr"></view>
					<view class="scan-corner scan-corner-bl"></view>
					<view class="scan-corner scan-corner-br"></view>
					<view class="scan-line" :class="{ 'scan-line-active': scanStatus === 'scanning' }"></view>
				</view>
			</view>
			
			<view class="scan-tips">
				<text class="tips-text" v-if="scanStatus === 'scanning'">将二维码放入框内,即可自动扫描</text>
				<text class="tips-text tips-waiting" v-else-if="scanStatus === 'waiting'">点击"重新扫描"开始扫描</text>
				<text class="tips-text tips-error" v-else-if="scanStatus === 'permission_denied'">相机权限被拒绝,请授权后重试</text>
				<text class="tips-text tips-waiting" v-else>准备扫描中...</text>
			</view>
			<view class="scan-actions">
				<button class="scan-btn scan-btn-half" @click="retryPermissionAndScan">重新扫描</button>
				<button class="scan-btn scan-btn-half scan-btn-secondary" @click="openAlbum">从相册选择</button>
			</view>
		</view>
		
		<!-- <view class="scan-result" v-if="scanResult">
			<text class="result-title">扫描结果:</text>
			<text class="result-content">{{ scanResult }}</text>
		</view> -->
	</view>
</template>

<script>
export default {
	data() {
		return {
			scanResult: '',
			isShowCheckPermissionDialog: false,
			permissionChecked: false, // 权限是否已检查过
			permissionGranted: false, // 权限是否已授予
			pageInitialized: false, // 页面是否已初始化
			isScanning: false, // 是否正在扫描
			scanStatus: 'waiting', // 扫描状态:waiting(等待), scanning(扫描中), permission_denied(权限被拒绝)
		}
	},
	onLoad() {
		// 页面加载时自动开始扫描
		// this.startScan();
	},
	onHide() {
		console.log('页面隐藏了');
		// 页面隐藏时重置初始化状态,确保下次显示时能重新初始化
		this.pageInitialized = false;
	},
	onShow() {
		console.log('页面显示了,pageInitialized:', this.pageInitialized);
		
		// 只在页面首次显示时执行权限检查
		if (!this.pageInitialized) {
			this.pageInitialized = true;
			console.log('页面首次初始化,开始权限检查');
			this.scanStatus = 'waiting'; // 设置初始状态
			this.checkPermissionsAndScan();
		} else {
			console.log('页面已初始化,跳过自动权限检查');
			// 如果权限已授予,可以直接开始扫描
			if (this.permissionChecked && this.permissionGranted) {
				console.log('权限已授予,直接开始扫描');
				this.startScan();
			} else if (this.permissionChecked && !this.permissionGranted) {
				// 权限被拒绝,显示相应状态
				this.scanStatus = 'permission_denied';
			}
		}
	},
	methods: {
		// 重置权限状态
		resetPermissionStatus() {
			this.permissionChecked = false;
			this.permissionGranted = false;
			this.pageInitialized = false;
			this.isScanning = false;
			this.scanStatus = 'waiting';
			console.log('权限状态已重置');
		},
		
		// 重试权限检查并扫描
		retryPermissionAndScan() {
			console.log('用户点击重新扫描');
			this.resetPermissionStatus();
			this.checkPermissionsAndScan();
		},
		
		// 检查权限并开始扫描
		checkPermissionsAndScan() {
			// 防止重复检查
			if (this.isShowCheckPermissionDialog) {
				return;
			}
			
			// 如果权限已经检查过且已授予,直接开始扫描
			if (this.permissionChecked && this.permissionGranted) {
				this.startScan();
				return;
			}
			
			// 如果权限已经检查过但被拒绝,不再重复弹窗
			if (this.permissionChecked && !this.permissionGranted) {
				console.log('权限已被拒绝,不再重复检查');
				return;
			}
			
			this.isShowCheckPermissionDialog = true;
			// #ifdef APP-PLUS
			this.checkCameraPermission().then(() => {
				this.permissionChecked = true;
				this.permissionGranted = true;
				this.startScan();
			}).catch(() => {
				this.permissionChecked = true;
				this.permissionGranted = false;
				this.scanStatus = 'permission_denied';
				this.showPermissionDeniedAlert();
			}).finally(() => {
				this.isShowCheckPermissionDialog = false;
			});
			// #endif
			
			// #ifdef H5
			this.permissionChecked = true;
			this.permissionGranted = true;
			this.startScan();
			this.isShowCheckPermissionDialog = false;
			// #endif
		},
		
		// 检测是否为鸿蒙系统
		isHarmonyOS() {
			try {
				// #ifdef APP-PLUS-ANDROID
				const main = plus.android.runtimeMainActivity();
				const Build = plus.android.importClass('android.os.Build');
				
				// 检查系统属性
				const brand = Build.BRAND;
				const manufacturer = Build.MANUFACTURER;
				const model = Build.MODEL;
				
				console.log('设备信息 - Brand:', brand, 'Manufacturer:', manufacturer, 'Model:', model);
				
				// 检查是否包含华为/荣耀相关标识
				const isHuawei = brand && (brand.toLowerCase().includes('huawei') || brand.toLowerCase().includes('honor'));
				const isHuaweiManufacturer = manufacturer && (manufacturer.toLowerCase().includes('huawei') || manufacturer.toLowerCase().includes('honor'));
				
				// 尝试检测鸿蒙系统特有的API或属性
				try {
					const SystemProperties = plus.android.importClass('android.os.SystemProperties');
					const harmonyVersion = SystemProperties.get('hw_sc.build.platform.version', '');
					const isHarmony = harmonyVersion && harmonyVersion.length > 0;
					console.log('鸿蒙版本信息:', harmonyVersion, '是否为鸿蒙:', isHarmony);
					return isHarmony || isHuawei || isHuaweiManufacturer;
				} catch (e) {
					console.log('无法获取鸿蒙版本信息,使用品牌判断:', isHuawei || isHuaweiManufacturer);
					return isHuawei || isHuaweiManufacturer;
				}
				// #endif
				
				return false;
			} catch (error) {
				console.error('检测鸿蒙系统失败:', error);
				return false;
			}
		},
		
		// 检查相机权限
		checkCameraPermission() {
			return new Promise((resolve, reject) => {
				// #ifdef APP-PLUS
				// 运行时判断平台类型
				const platform = uni.getSystemInfoSync().platform;
				console.log('当前平台:', platform);
				
				if (platform === 'android') {
					// Android 和 鸿蒙系统处理
					try {
						const main = plus.android.runtimeMainActivity();
						const Context = plus.android.importClass('android.content.Context');
						const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
						const permission = 'android.permission.CAMERA';
						
						// 检测是否为鸿蒙系统
						const isHarmonyOS = this.isHarmonyOS();
						console.log('是否为鸿蒙系统:', isHarmonyOS);
						
						const result = main.checkSelfPermission(permission);
						console.log('Android/鸿蒙权限检查结果:', result);
						if (result === PackageManager.PERMISSION_GRANTED) {
							resolve();
						} else {
							// 请求权限
							plus.android.requestPermissions([permission], (resultObj) => {
								const granted = resultObj.granted && resultObj.granted.length > 0;
								if (granted) {
									resolve();
								} else {
									reject();
								}
							}, (error) => {
								console.error('Android/鸿蒙权限请求失败:', error);
								reject();
							});
						}
					} catch (error) {
						console.error('Android/鸿蒙权限检查失败:', error);
						reject();
					}
				} else if (platform === 'ios') {
					// iOS 系统处理
					const AVCaptureDevice = plus.ios.importClass('AVCaptureDevice');
					const AVMediaTypeVideo = 'vide';
					
					try {
						const authStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo);
						if (authStatus === 3) { // AVAuthorizationStatusAuthorized
							resolve();
						} else if (authStatus === 0) { // AVAuthorizationStatusNotDetermined
							// 请求权限
							AVCaptureDevice.requestAccessForMediaTypeCompletionHandler(AVMediaTypeVideo, (granted) => {
								if (granted) {
									resolve();
								} else {
									reject();
								}
							});
						} else {
							// 权限被拒绝或受限
							reject();
						}
					} catch (error) {
						console.error('iOS权限检查失败:', error);
						reject();
					}
				} else {
					console.log('未知平台:', platform);
					reject();
				}
				// #endif
				// #ifdef H5
				resolve();
				// #endif
			});
		},
		
		// 显示权限被拒绝的提示
		showPermissionDeniedAlert() {
			// #ifdef APP-PLUS
			const platform = uni.getSystemInfoSync().platform;
			
			if (platform === 'android') {
				// Android 和 鸿蒙系统处理
				const isHarmonyOS = this.isHarmonyOS();
				const systemName = isHarmonyOS ? '鸿蒙' : 'Android';
				uni.showModal({
					title: '相机权限',
					content: `扫码功能需要相机权限,请在${systemName}系统设置中开启相机权限`,
					confirmText: '去设置',
					cancelText: '稍后再说',
					success: (res) => {
						if (res.confirm) {
							this.openAppSettings();
						}
					}
				});
			} else if (platform === 'ios') {
				// iOS 系统处理
				uni.showModal({
					title: '相机权限',
					content: '扫码功能需要相机权限,请在"设置 > 隐私与安全性 > 相机"中开启权限',
					confirmText: '去设置',
					cancelText: '取消',
					success: (res) => {
						if (res.confirm) {
							this.openAppSettings();
						}
					}
				});
			}
			// #endif
		},
		
		// 打开应用设置
		openAppSettings() {
			// #ifdef APP-PLUS
			const platform = uni.getSystemInfoSync().platform;
			
			if (platform === 'android') {
				// Android 和 鸿蒙系统处理
				const Intent = plus.android.importClass('android.content.Intent');
				const Settings = plus.android.importClass('android.provider.Settings');
				const Uri = plus.android.importClass('android.net.Uri');
				const main = plus.android.runtimeMainActivity();
				
				const intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
				const uri = Uri.fromParts('package', main.getPackageName(), null);
				intent.setData(uri);
				main.startActivity(intent);
			} else if (platform === 'ios') {
				// iOS 系统处理
				const UIApplication = plus.ios.importClass('UIApplication');
				const NSURL = plus.ios.importClass('NSURL');
				const settingsUrl = NSURL.URLWithString('app-settings:');
				const application = UIApplication.sharedApplication();
				application.openURL(settingsUrl);
			}
			// #endif
		},
		
		startScan() {
			console.log('开始扫描');
			this.isScanning = true;
			this.scanStatus = 'scanning';
			
			// #ifdef APP-PLUS
			uni.scanCode({
				success: (res) => {
					console.log('扫码成功:', res.result);
					this.isScanning = false;
                    	uni.showToast({
						title: `扫码成功`,
						icon: 'success'
					});
					this.scanResult = res.result;
					this.handleScanResult(res.result);
				},
				fail: (err) => {
					console.log('扫码失败:', err);
					this.isScanning = false;
					this.scanStatus = 'waiting';
					uni.showToast({
						title: '扫码失败',
						icon: 'none'
					});
				}
			});
			// #endif
			
			// #ifdef H5
			this.isScanning = false;
			this.scanStatus = 'waiting';
			uni.showToast({
				title: 'H5环境暂不支持扫码功能',
				icon: 'none'
			});
			// #endif
		},
		
		openAlbum() {
			console.log('从相册选择图片进行扫码');
			// #ifdef APP-PLUS
			uni.chooseImage({
				count: 1,
				sourceType: ['album'],
				success: (res) => {
					const imagePath = res.tempFilePaths[0];
					console.log('选择的图片路径:', imagePath);
					
					// 使用plus.barcode识别图片中的二维码
					// #ifdef APP-PLUS
					try {
						plus.barcode.scan(imagePath, (type, result, file) => {
							console.log('图片扫码成功 - 类型:', type, '结果:', result);
							if (result) {
								uni.showToast({
									title: `扫码成功`,
									icon: 'success'
								});
								this.scanResult = result;
								this.handleScanResult(result);
							} else {
								uni.showToast({
									title: '未识别到二维码',
									icon: 'none'
								});
							}
						}, (error) => {
							console.log('图片扫码失败:', error);
							uni.showToast({
								title: '未识别到二维码',
								icon: 'none'
							});
						});
					} catch (e) {
						console.log('扫码API调用失败:', e);
						// 降级使用uni.scanCode
						uni.scanCode({
							scanType: ['qrCode', 'barCode'],
							autoDecodeCharSet: true,
							filePath: imagePath,
							success: (scanRes) => {
								console.log('图片扫码成功:', scanRes.result);
								uni.showToast({
									title: `扫码成功`,
									icon: 'success'
								});
								this.scanResult = scanRes.result;
								this.handleScanResult(scanRes.result);
							},
							fail: (scanErr) => {
								console.log('图片扫码失败:', scanErr);
								uni.showToast({
									title: '未识别到二维码',
									icon: 'none'
								});
							}
						});
					}
					// #endif
				},
				fail: (err) => {
					console.log('选择图片失败:', err);
					uni.showToast({
						title: '选择图片失败',
						icon: 'none'
					});
				}
			});
			// #endif
			
			// #ifdef H5
			uni.showToast({
				title: 'H5环境暂不支持此功能',
				icon: 'none'
			});
			// #endif
		},
		
		
		handleScanResult(result) {
			// 处理扫描结果
			if (result.startsWith('http')) {
				console.log('开始处理URL:', result);
				try {
					// 使用正则表达式提取URL参数
					const idMatch = result.match(/[\?&]id=([^&]*)/i);
					const id = idMatch ? idMatch[1] : null;
					console.log('解析到的ID:', id);
					
					// 检查是否成功提取到ID
					if (!id) {
						throw new Error('未找到ID参数');
					}
					
					console.log('准备跳转到:', `/pages/workbench/inspection/list?AS_ID=${id}`);
					
					// 添加延迟确保页面准备就绪
					setTimeout(() => {
						uni.navigateTo({
							url: `/pages/workbench/inspection/list?AS_ID=${id}`,
							success: (res) => {
								console.log('跳转成功:', res);
							},
							fail: (err) => {
								console.error('跳转失败:', err);
								uni.showToast({
									title: '页面跳转失败',
									icon: 'none'
								});
								// 尝试使用reLaunch作为备选方案
								uni.reLaunch({
									url: `/pages/workbench/inspection/list?AS_ID=${id}`,
									success: (res) => {
										console.log('reLaunch成功:', res);
									},
									fail: (err) => {
										console.error('reLaunch也失败:', err);
									}
								});
							}
						});
					}, 100);
				} catch (error) {
					console.error('URL解析错误:', error);
					uni.showToast({
						title: '无法识别',
						icon: 'none'
					});
				}
			} else {
				uni.showModal({
					title: '扫描结果',
					content: result,
					showCancel: false
				});
			}
		}
	}
}
</script>

<style lang="scss" scoped>
.scan-container {
	min-height: 100vh;
	background-color: #121212;
	display: flex;
	flex-direction: column;
}

.scan-header {
	padding: 30rpx 20rpx;
	text-align: center;
	background-color: rgba(18, 18, 18, 0.95);
	border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
}

.scan-title {
	color: #fff;
	font-size: 36rpx;
	font-weight: bold;
	letter-spacing: 2rpx;
}

.scan-content {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: 40rpx;
}

.scan-area {
	position: relative;
	width: 550rpx;
	height: 550rpx;
	margin-bottom: 30rpx;
	margin-top: 20rpx;
}

.scan-frame {
	position: relative;
	width: 100%;
	height: 100%;
	border: 2rpx solid rgba(255, 255, 255, 0.2);
	box-shadow: 0 0 0 4000rpx rgba(0, 0, 0, 0.5);
	backdrop-filter: blur(5px);
}

.scan-corner {
	position: absolute;
	width: 60rpx;
	height: 60rpx;
	border: 4rpx solid #1677FF;
	box-shadow: 0 0 10rpx rgba(22, 119, 255, 0.5);
}

.scan-corner-tl {
	top: -2rpx;
	left: -2rpx;
	border-right: none;
	border-bottom: none;
}

.scan-corner-tr {
	top: -2rpx;
	right: -2rpx;
	border-left: none;
	border-bottom: none;
}

.scan-corner-bl {
	bottom: -2rpx;
	left: -2rpx;
	border-right: none;
	border-top: none;
}

.scan-corner-br {
	bottom: -2rpx;
	right: -2rpx;
	border-left: none;
	border-top: none;
}

.scan-line {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 4rpx;
	background: linear-gradient(90deg, transparent, #1677FF, transparent);
	box-shadow: 0 0 8rpx rgba(22, 119, 255, 0.8);
	opacity: 0.3;
}

.scan-line-active {
	animation: scanMove 2.5s ease-in-out infinite;
	opacity: 1;
}

@keyframes scanMove {
	0% {
		top: 0;
		opacity: 0.6;
	}
	50% {
		opacity: 1;
	}
	100% {
		top: calc(100% - 4rpx);
		opacity: 0.6;
	}
}

.scan-tips {
	margin-bottom: 80rpx;
	margin-top: 40rpx;
	background-color: rgba(0, 0, 0, 0.3);
	padding: 16rpx 30rpx;
	border-radius: 30rpx;
}

.tips-text {
	color: #fff;
	font-size: 28rpx;
	text-align: center;
	letter-spacing: 1rpx;
}

.tips-waiting {
	color: #ffa500;
}

.tips-error {
	color: #ff6b6b;
}

.scan-actions {
	display: flex;
	flex-direction: row;
	justify-content: space-between;
	gap: 20rpx;
	width: 100%;
	padding: 0 30rpx;
}

.scan-btn {
	height: 90rpx;
	background-color: #1677FF;
	color: #fff;
	border: none;
	border-radius: 45rpx;
	font-size: 32rpx;
	font-weight: bold;
	line-height: 90rpx;
	padding: 0;
	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
	transition: all 0.3s ease;
}

.scan-btn:active {
	transform: scale(0.98);
	box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.2);
}

.scan-btn-half {
	width: 45%;
	display: flex;
	align-items: center;
	justify-content: center;
}

.scan-btn-secondary {
	background-color: rgba(255, 255, 255, 0.15);
	border: 1rpx solid rgba(255, 255, 255, 0.3);
	color: #fff;
}

.scan-result {
	position: fixed;
	bottom: 0;
	left: 0;
	right: 0;
	background-color: rgba(0, 0, 0, 0.85);
	padding: 40rpx;
	border-top-left-radius: 30rpx;
	border-top-right-radius: 30rpx;
	box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.3);
	animation: slideUp 0.3s ease-out;
}

@keyframes slideUp {
	from {
		transform: translateY(100%);
	}
	to {
		transform: translateY(0);
	}
}

.result-title {
	color: #fff;
	font-size: 30rpx;
	font-weight: bold;
	margin-bottom: 20rpx;
	display: block;
}

.result-content {
	color: #e0e0e0;
	font-size: 26rpx;
	word-break: break-all;
	display: block;
	line-height: 1.5;
}
</style>

错误处理

权限相关

  • 权限被拒绝:显示授权提示,提供设置跳转
  • 权限检查失败:提供重试机制

扫码相关

  • 识别失败:显示"未识别到二维码"提示
  • API调用失败:自动降级到备用方案

平台兼容

  • H5环境:部分功能不支持时显示相应提示
  • 不同Android版本:适配不同的权限API

注意事项

  1. 权限管理:首次使用需要用户授权相机权限
  2. 平台差异:H5环境功能受限,建议在APP中使用
  3. 图片格式:支持常见图片格式的二维码识别
  4. 网络要求:扫码结果处理可能需要网络连接
  5. 性能优化:大图片识别可能耗时较长

更新日志

v1.0.0

  • 基础扫码功能实现
  • 支持相机扫码和图片识别
  • 权限管理和状态控制
  • 跨平台兼容性处理

本文档基于uni-app框架编写,适用于移动端应用开发。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐