w-笔记:uni-app的H5平台和非H5平台的拍照识别功能:

发布于:2025-07-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

uni-app的H5平台和非H5平台的拍照识别功能:

<template>
	<view class="humanVehicleBinding">
		<view v-if="warn" class="shadow">

		</view>
		<view class="header">
			<uni-nav-bar left-icon="left" shadow :border="false" height="44" title="人车绑定" @clickLeft="back">
			</uni-nav-bar>
		</view>
		<view class="body">
			<uni-forms :modelValue="formData" label-position="top">
				<uni-forms-item label="使用车辆">
					<view class="useCar">
						<template v-if="dataReversion.code">
							<text class="textValue">{{dataReversion.code}}</text>
						</template>
						<template v-else>
							<w-select v-model="vehicleListValue" :list="vehicleList" valueName="text" keyName="value"
								:filterable="true" @change="vehicleValueChange" />
						</template>
						<uni-icons type="scan" size="30" class="scan" @click="takePhoto"></uni-icons>
					</view>

				</uni-forms-item>
				<uni-forms-item label="车辆类型" class="disable">
					<text class="textValue">{{dataReversion.vehicleType ? dataReversion.vehicleType : "--:--"}}</text>
				</uni-forms-item>
				<uni-forms-item label="使用人员" class="disable">
					<text
						class="textValue">{{dataReversion.loginUserName ? dataReversion.loginUserName : "--:--"}}</text>
				</uni-forms-item>
				<uni-forms-item label="同行人">
					<view class="companions">
						<!-- <uni-data-picker :localdata="driverAccountNameList" popup-title="请选择同行人"
							v-model="dataReversion.driverAccountId" :map="{text:'text',value:'value'}"
							placeholder="请选择同行人" ref="companionsRef" @change="onchange" class="dataPicker">
						</uni-data-picker> -->
						<!-- <select  multiple  v-model="dataReversion.driverAccountId">
						    <option value="item.value" v-for="(item,index) in driverAccountNameList" :key="index">{{item.text}}</option>
						  </select> -->
						<!-- 输入框用于触发弹出层 -->
						<view class="trigger" @click="togglePopup" >
							{{ selectedLabels.length > 0 ? selectedLabels.join('、'): '请选择' }}
						</view>

						<!-- uni-popup 弹出层 -->
						<uni-popup ref="popup" type="bottom">
							<view class="popup-content">
								<view :class="isSelected(item) ? 'selected':'unselected'"  v-for="item in driverAccountNameList" :key="item.value" class="checkbox-item" @click="toggleSelection(item)">
									<text >{{ item.text }}</text>
								</view>
								<!-- <uni-list>
								
									<uni-list-item v-for="item in driverAccountNameList" :key="item.value" :show-arrow="false">
										<template #default>
											<view class="checkbox-item" @click="toggleSelection(item)">
												<text>{{ item.text }}</text>
												<uni-icons v-if="isSelected(item)" type="checkbox-filled" size="18"
													color="#4CAF50"></uni-icons>
												<uni-icons v-else type="circle" size="18" color="#999"></uni-icons>
											</view>
										</template>
									</uni-list-item>
								</uni-list> -->

								<button @click="confirmSelection">确认</button>
							</view>
						</uni-popup>

						<uni-icons type="forward" size="30" class="scan" @click="openDataPicker"></uni-icons>
					</view>
				</uni-forms-item>
				<uni-forms-item label="使用开始时间" class="disable">
					<text class="textValue">{{dataReversion.beginTime ? dataReversion.beginTime : "--:--"}}</text>
				</uni-forms-item>
				<uni-forms-item label="使用结束时间" class="disable">
					<text class="textValue">{{dataReversion.endTime ? dataReversion.endTime : "--:--"}}</text>
				</uni-forms-item>
			</uni-forms>
			<!-- <camera device-position="back" flash="auto" style="width: 100%; height: 100vh"></camera> -->

		</view>

		<view class="bottom">
			<view class="bottonBox">
				<button class="endUse" @click="usageEnd">结束使用</button>
				<button class="startUse" @click="useAtOnce">立即使用</button>
			</view>

		</view>

	</view>
</template>

<script setup>
	import {
		onMounted,
		ref,computed 
	} from "vue";
	import permision from "@/common/permission.js";
	import {
		getVehicleList,
		getDataReversion,
		getDataReversion1,
		getCompanions,
		postUseAtOnce,
		postUsageEnd,
		getRecognize
	} from '../../../api/work/index.js'
	import SpeechSynthesisUtil from 'uniapp-text-to-speech';
	const basicText = ref('您已超速,请减缓速度,注意安全');
	const tts = new SpeechSynthesisUtil({
		API_KEY: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiJjeGwiLCJVc2VyTmFtZSI6ImN4bCIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxOTMzNzc2NTc5MDI0OTE3MTI0IiwiUGhvbmUiOiIxNzM0NTM2MTYzMSIsIkdyb3VwSUQiOiIxOTMzNzc2NTc5MDE2NTI4NzczIiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDYtMTcgMTc6MjM6MzMiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.bd8aLiydYgr_m2GhQV5_Di92JkiQSCCN_TPIXmLCKC8gr64ImSWbvJuolAVzGFASzDVer5n21F-cznURRCc04s9ZwRFwVoQNcPMM-A-ChupBU2IJooYiKQgywmgki-ae0f_R7N328N6-62eaDCaVDWa23bhnHIxm7vzl5AP5CY9vwmkQGy4LPPgylhGLcT6RGRS0GYeRuezfs9qQb8L8NmrD4yYDxfJYQ6yZzPETN2FfV1OgsWQf1j0KEIpv0KdgJtUZ0ugaCdNa_pnjDpCzOZWLH1rnIpT0q-86BJGV1aCRxETfq6HZ8BBXvVOHaIJ7vitZt2-JqFu3I6dUpYdhyg",
		// Minimax API密钥
		GroupId: '1933776579016528773', // Minimax 组ID
		MAX_QUEUE_LENGTH: 3, // 可选:音频队列最大长度
		// 可选:音频生成配置
		modelConfig: {
			model: 'speech-02-hd',
			voice_setting: {
				"voice_id": "Chinese (Mandarin)_Radio_Host",
				"speed": 0.8,
				"vol": 1,
			}
		},
		// 其他配置...

	})
	const warn = ref(false)
	const vehicleList = ref([])
	const vehicleListValue = ref()
	const dataReversion = ref({
		"carId": 0,
		"code": "",
		"simCode": "",
		"vehicleType": "",
		"loginUserId": 0,
		"loginUserName": "",
		"driverAccountId": [
			0
		],
		"driverAccountName": [
			""
		],
		"bindStatus": 0,
		"beginTime": "",
		"endTime": ""
	})
	const driverAccountNameList = ref([])
	const selectedCompanion = ref('')
	onMounted((option) => {
		getVehicleListValue()
		getCompanionsValue()
		getDataReversionValue()
	})

	
	// 当前选中项
	const selectedValues = ref([])
	const selectedLabels = computed(() =>
	  selectedValues.value.map(val => {
		  console.log(driverAccountNameList.value)
		  console.log(val,"val")
		console.log(driverAccountNameList.value.find(opt => opt.value === val))
		  if(driverAccountNameList.value.find(opt => opt.value === val)){
			  return driverAccountNameList.value.find(opt => opt.value === val).text
		  }else{
			return  ""
		  }
		  })
	)
	
	// popup 控制
	const popup = ref(null)
	const togglePopup = () => {
	  popup.value.open()
	}
	
	// 切换选项选中状态
	const toggleSelection = (item) => {
	  const index = selectedValues.value.indexOf(item.value)
	  if (index === -1) {
	    selectedValues.value.push(item.value)
	  } else {
	    selectedValues.value.splice(index, 1)
	  }
	  console.log(selectedValues.value)
	}
	
	// 检查是否已选中
	const isSelected = (item) => {
	  return selectedValues.value.includes(item.value)
	}
	
	// 确认选择并关闭弹窗
	const confirmSelection = () => {
	  popup.value.close()
	}
	const getVehicleListValue = async () => {
		const res = await getVehicleList()
		if (res && res.data) {
			vehicleList.value = res.data.map(item => {
				return {
					text: item.code,
					// value: item.id,
					value: item.simCode,
				}
			})
		}
	}
	const getCompanionsValue = async () => {
		const res = await getCompanions()
		if (res && res.data) {
			driverAccountNameList.value = res.data.map(item => {
				return {
					text: item.chinaName,
					value: item.id
				}
			})
			console.log(driverAccountNameList.value, "66666666666");
		}
	}
	const getDataReversionValue = async () => {
		const res = await getDataReversion()
		if (res && res.data) {
			dataReversion.value = res.data
			// 如果有车牌号,则设置下拉框值
			if (res.data.code) {
				vehicleListValue.value = res.data.code
			}
			// 如果有同行人数据,需要设置选中状态
			if (res.data.driverAccountName && res.data.driverAccountName.length > 0) {
				const selectedName = res.data.driverAccountName[0]
				selectedValues.value = res.data.driverAccountId
				console.log("selectedValues.value",selectedLabels.value)
				// 在下拉列表中找到对应的id
				const selectedPerson = driverAccountNameList.value.find(item => item.text === selectedName)
				console.log("selectedPerson", selectedPerson);
				if (selectedPerson) {
					// 设置选中的值为找到的id,这样下拉框会显示为选中状态
					dataReversion.value.driverAccountId = [selectedPerson.value]
					dataReversion.value.driverAccountName = [selectedName]
				}
			}
		}
	}
	const vehicleValueChange = async (e) => {
		// if (!vehicleListValue.value) return
		const res = await getDataReversion1(vehicleListValue.value)
		if (res && res.data) {
			dataReversion.value.vehicleType = res.data.vehicleType
			dataReversion.value.loginUserName = red.data.loginUserName
		}
	}
	const onchange = (e) => {
		console.log('onchange:', e);
		if (e.detail.value && e.detail.value.length > 0) {
			const selectedItem = e.detail.value[0];
			const text = selectedItem.text; // 申文昊
			const value = selectedItem.value; // 41

			// 直接更新 dataReversion 中的数组
			dataReversion.value.driverAccountId = [value];
			dataReversion.value.driverAccountName = [text];

			// 同时更新 loginUserId 和 loginUserName
			dataReversion.value.loginUserId = value;
			dataReversion.value.loginUserName = text;

			console.log('更新后的 driverAccountId:', dataReversion.value.driverAccountId);
			console.log('更新后的 driverAccountName:', dataReversion.value.driverAccountName);
		}
	};
	const submitData = ref()
	const updateData = () => {
		console.log(selectedValues.value)
		 dataReversion.value.driverAccountId = selectedValues.value
		submitData.value = {
			"carId": dataReversion.value.carId,
			"code": dataReversion.value.code,
			"simCode": dataReversion.value.simCode,
			"vehicleType": dataReversion.value.vehicleType,
			"loginUserId": dataReversion.value.loginUserId,
			"loginUserName": dataReversion.value.loginUserName,
			"driverAccountId": dataReversion.value.driverAccountId,
			"bindStatus": dataReversion.value.bindStatus
		}
	}
	const getCurrentTime = () => {
		const now = new Date();
		let nowYear = now.getFullYear();
		let nowMonth = now.getMonth() + 1;
		let nowDay = now.getDate();
		let nowHours = now.getHours();
		let nowMinutes = now.getMinutes();
		let nowSeconds = now.getSeconds();
		nowMonth = nowMonth > 9 ? nowMonth : '0' + nowMonth;
		nowDay = nowDay > 9 ? nowDay : '0' + nowDay;
		nowHours = nowHours > 9 ? nowHours : '0' + nowHours;
		nowMinutes = nowMinutes > 9 ? nowMinutes : '0' + nowMinutes;
		nowSeconds = nowSeconds > 9 ? nowSeconds : '0' + nowSeconds;
		return  `${nowYear}-${nowMonth}-${nowDay} ${nowHours}:${nowMinutes}:${nowSeconds}`
	}
	const useAtOnce = async () => {
		updateData()
		await postUseAtOnce(submitData.value)
	}
	const usageEnd = async () => {
		dataReversion.value.beginTime = getCurrentTime()
		updateData()
		await postUsageEnd(submitData.value)
	}
	const back = () => {
		uni.navigateBack({
			delta: 1
		})
	}
	const openDataPicker = () => {
		companionsRef.value.show()
	}
	const endUse = async () => {
		// warn.value = !warn.value

		// try {
		// 	await tts.textToSpeech(basicText.value);
		// } catch (error) {
		// 	console.log("播放失败", error)
		// }
	}

	// #ifdef H5
	function getBase64FromFileToWeb(file) {
		console.log('调用了 H5 端 getBase64FromFile');
		return new Promise((resolve, reject) => {
			const reader = new FileReader();
			reader.onload = (e) => {
				const base64 = e.target.result.split(',')[1];
				resolve(base64);
			};
			reader.onerror = (error) => {
				reject(error);
			};
			reader.readAsDataURL(file);
		});
	}
	// #endif

	// #ifndef H5
	function getBase64FromFile(filePath) {
		return new Promise((resolve, reject) => {
			try {
				// #ifdef APP-PLUS
				plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
					entry.file((file) => {
						const fileReader = new plus.io.FileReader();
						fileReader.onload = function(e) {
							const base64 = e.target.result.split(',')[1];
							resolve(base64);
						};
						fileReader.onerror = function(error) {
							console.error('FileReader错误:', error);
							reject(new Error('读取文件失败'));
						};
						fileReader.readAsDataURL(file);
					}, (error) => {
						console.error('获取文件对象失败:', error);
						reject(new Error('获取文件对象失败'));
					});
				}, (error) => {
					console.error('解析文件路径失败:', error);
					reject(new Error('解析文件路径失败'));
				});
				// #endif

				// #ifdef MP
				const fsm = uni.getFileSystemManager();
				fsm.readFile({
					filePath: filePath,
					encoding: 'base64',
					success: (res) => {
						if (res.data) {
							console.log('Base64转换成功');
							resolve(res.data);
						} else {
							reject(new Error('Base64数据为空'));
						}
					},
					fail: (error) => {
						console.error('读取文件失败:', error);
						reject(new Error('读取文件失败: ' + (error.errMsg || '未知错误')));
					}
				});
				// #endif
			} catch (error) {
				console.error('文件处理错误:', error);
				reject(new Error('文件处理错误: ' + (error.message || '未知错误')));
			}
		});
	}
	// #endif

	const takePhoto = async () => {
		// #ifdef APP-PLUS
		// 只检测相机权限
		let status = await checkCameraPermission();
		if (status !== 1) {
			// 没有权限,直接 return
			return;
		}
		// #endif

		uni.chooseImage({
			count: 1,
			sourceType: ['camera'],
			sizeType: ['original', 'compressed'],
			success: async (res) => {
				try {
					let base64 = '';
					// #ifdef H5
					base64 = await getBase64FromFileToWeb(res.tempFiles[0]);
					// #endif
					// #ifndef H5
					if (res.tempFilePaths && res.tempFilePaths.length > 0) {
						const filePath = res.tempFilePaths[0];
						console.log('开始转换图片,文件路径:', filePath);
						base64 = await getBase64FromFile(filePath);
					} else {
						throw new Error('未能获取到图片路径');
					}
					// #endif

					if (!base64) {
						throw new Error('Base64转换失败: 结果为空');
					}

					console.log('Base64转换成功,长度:', base64.length);
					await recognizeAndBind(base64);
				} catch (error) {
					console.error('图片处理错误:', error);
					uni.showToast({
						title: error.message || '图片处理失败',
						icon: 'none',
						duration: 2000
					});
				}
			},
			fail: (err) => {
				if (err && err.errMsg && err.errMsg.indexOf('cancel') !== -1) return;
				uni.showToast({
					title: '打开相机失败,请检查权限',
					icon: 'none',
					duration: 2000
				});
				console.error('chooseImage fail:', err);
			}
		});
	};

	const recognizeAndBind = async (base64) => {
		try {
			if (!base64) {
				throw new Error('Base64数据为空');
			}

			console.log('开始识别处理,Base64长度:', base64.length);
			const options = {
				"data.format": "text",
				"thpu.parser": "single_line",
				"ocr.cls": true,
				"ocr.limit_side_len": 2880,
				"ocr.language": "models/config_chinese.txt"
			};

			const res = await getRecognize({
				base64,
				options
			});
			console.log('识别结果:', res);

			if (!res) {
				throw new Error('识别接口返回为空');
			}

			if (res && res.data) {
				if (!res.data.statusBit) {
					// 未绑定,更新表单数据并自动绑定
					console.log("此时的使用人员和id", dataReversion.value);
					dataReversion.value = {
						...dataReversion.value,
						carId: res.data.id,
						code: res.data.code,
						simCode: res.data.simCode,
						vehicleType: res.data.vehicleType,
						loginUserId: dataReversion.value.loginUserId, // 直接取当前值
						loginUserName: dataReversion.value.loginUserName, // 直接取当前值
						// driverAccountId: [0],
						// driverAccountName: [""]
						driverAccountId: dataReversion.value.driverAccountId,
						driverAccountName: dataReversion.value.driverAccountName
					}
					await useAtOnce()
					uni.showToast({
						title: '自动绑定成功',
						icon: 'success',
						duration: 2000
					});
				} else {
					// 已绑定,弹窗确认
					uni.showModal({
						title: '提示',
						content: `当前车辆已被绑定,确认是否继续绑定,绑定后默认结束上一使用人绑定记录。`,
						success: async (modalRes) => {
							if (modalRes.confirm) {
								console.log("11此时的使用人员和id", dataReversion.value);
								console.log("此时的使用人员", dataReversion.value.loginUserName);
								// 用户确认,更新表单数据并绑定
								dataReversion.value = {
									...dataReversion.value,
									carId: res.data.id,
									code: res.data.code,
									simCode: res.data.simCode,
									vehicleType: res.data.vehicleType,
									// driverAccountId: [0],
									// driverAccountName: [""],
									driverAccountId: dataReversion.value.driverAccountId,
									driverAccountName: dataReversion.value.driverAccountName,
									loginUserId: dataReversion.value.loginUserId, // 直接取当前值
									loginUserName: dataReversion.value.loginUserName, // 直接取当前值
								}
								console.log("更新后的", dataReversion.value)
								await useAtOnce()
								uni.showToast({
									title: '绑定成功',
									icon: 'success',
									duration: 2000
								});
							}
							// 用户取消,不做任何处理,保持原有数据
						}
					});
				}
			} else {
				uni.showToast({
					title: '未识别到车牌号',
					icon: 'none',
					duration: 2000
				});
			}
		} catch (error) {
			console.error('识别绑定过程出错:', error);
			uni.showToast({
				title: '识别处理失败: ' + (error.message || '未知错误'),
				icon: 'none',
				duration: 2000
			});
		}
	};

	// 检查相机权限
	const checkCameraPermission = async () => {
		// iOS
		if (permision.isIOS) {
			let status = await permision.requestIOS('camera');
			if (status !== 1) {
				uni.showModal({
					content: "没有开启相机权限",
					confirmText: "去设置",
					success: function(res) {
						if (res.confirm) {
							permision.gotoAppSetting();
						}
					}
				});
			}
			return status;
		}
		// Android
		let status = await permision.requestAndroid('android.permission.CAMERA');
		if (status !== 1) {
			uni.showModal({
				content: "没有开启相机权限",
				confirmText: "去设置",
				success: function(res) {
					if (res.confirm) {
						permision.gotoAppSetting();
					}
				}
			});
		}
		return status;
	};
</script>

<style lang="scss">
	.humanVehicleBinding {
		width: 750rpx;
		height: 100%;
		background-color: rgba(242, 242, 246, 1);
		display: flex;
		flex-direction: column;
		justify-content: space-between;

		:deep(.uni-data-tree) {
			font-size: 28rpx;
			color: #000;
		}

		.shadow {
			position: fixed;
			width: 750rpx;
			height: 100%;
			box-shadow: inset 0px 0px 20rpx red;
		}

		.header {
			width: 100%;
			height: 88rpx;

			:deep(.uni-navbar__header) {
				height: 88rpx;
				font-size: 24rpx;
			}

			:deep(.uni-nav-bar-text) {
				font-size: 28rpx;
			}
		}

		.body {
			width: 100%;
			height: 100%;
			margin: 0 0 3% 0;
			background-color: #fff;
			// overflow-y:auto

			:deep(.uni-forms-item) {
				margin: 20rpx 32rpx 0 32rpx;
				border-bottom: 1px solid rgba(17, 31, 44, 0.12);
				height: 120rpx;
			}

			:deep(.uni-forms-item__label) {
				padding: 0;
				line-height: 48rpx;
				height: 60rpx;
				width: 100% !important;
				font-size: 34rpx;
				font-weight: 400;
				letter-spacing: 0px;
				color: rgba(23, 26, 29, 1);
				// font-family:'Noto Sans SC', sans-serif;
			}

			:deep(.uni-forms-item__content) {
				font-size: 34rpx;
				color: rgba(23, 26, 29, 0.4);

			}

			:deep(.uni-select__selector-empty) {
				line-height: 70rpx;
				font-size: 34rpx;

			}

			:deep(.uni-select__selector-item) {
				line-height: 70rpx;
				font-size: 34rpx;
			}

			.disable {
				:deep(.uni-forms-item__label) {
					color: rgba(23, 26, 29, 0.24);
				}

				.textValue {
					color: rgba(23, 26, 29, 0.24);
				}
			}

			:deep(.input-value) {
				padding-left: 0;
				height: 60rpx;
				padding: 0 0 8px;
			}

			.useCar {
				position: relative;

				// bottom: 5px;
				:deep(.w-select) {
					color: rgba(23, 26, 29, 0.4);
				}

				:deep(.w-select .select-wrap) {
					width: 100%;
					border: none;
				}

				:deep(.w-select .select-wrap .select-options) {
					padding: 0;
				}

				:deep(.input-arrow) {
					// position: absolute;
					// top: -12.5px;
					// right: 0;
					border-left: 0;
					border-bottom: 0;
				}

				:deep(.placeholder) {
					font-size: 34rpx;
					font-weight: 400;
					color: rgba(23, 26, 29, 0.4);

				}



				.select {
					width: 646rpx;
					height: 44rpx;

					:deep(.uni-select) {
						border: none;
						padding-left: 0;

					}

					:deep(.uni-select__input-placeholder) {
						font-size: 34rpx;
						font-weight: 400;
						color: rgba(23, 26, 29, 0.4);
					}

					:deep(.uni-icons) {
						color: #fff !important;
					}

				}

				.scan {
					position: absolute;
					width: 48rpx;
					height: 44rpx;
					top: -40rpx;
					right: 0;
					font-size: 60rpx !important;
				}


			}

		}

		.companions {
			position: relative;
			// bottom: 10rpx;

			:deep(.input-value-border) {
				border: none;
				border-radius: none;
			}
			.popup-content{
				background-color: #fff;
				
				.selected{
					color: rgba(0, 127, 255, 1);
					line-height: 72rpx;
					padding: 20rpx;
				}
				.unselected{
					line-height: 72rpx;
					padding: 20rpx;
				}
			}
			.dataPicker {
				:deep(.uni-icons) {
					color: #fff !important;
				}
			}

			:deep(.input-arrow) {
				// position: absolute;
				// top: -12.5px;
				// right: 0;
				border-left: 0;
				border-bottom: 0;
			}

			:deep(.placeholder) {
				font-size: 34rpx;
				font-weight: 400;
				color: rgba(23, 26, 29, 0.4);

			}

			:deep(.input-value) {
				padding-left: 0;
				height: 60rpx;
			}

			.scan {
				position: absolute;
				width: 32rpx;
				height: 32rpx;
				top: -40rpx;
				right: 0;
				font-size: 60rpx !important;
			}

		}

		.bottom {
			// height: 102px;
			width: 100%;
			flex: 2.55;
			background-color: #fff;

			.bottonBox {
				display: flex;
				align-items: center;
				margin: 24rpx 0;

				:deep(uni-button) {
					font-size: 36rpx;
					display: flex;
					align-items: center;
					justify-content: center;
				}

				:deep(uni-button:after) {
					border: none;
				}

				.endUse {
					width: 330rpx;
					height: 88rpx;
					color: rgba(0, 127, 255, 1);
					border: 1px solid rgba(0, 127, 255, 1);
					background-color: rgba(255, 255, 255, 1);
					border-radius: 16rpx;
					margin-right: 14rpx;

				}

				.startUse {
					width: 330rpx;
					height: 88rpx;
					color: #fff;
					border: 1px solid rgba(0, 127, 255, 1);
					background-color: rgba(0, 127, 255, 1);
					border-radius: 16rpx;
					margin-left: 14rpx;
				}

			}
		}
	}
</style>

@/common/permission.js:

/// null = 未请求,1 = 已允许,0 = 拒绝|受限, 2 = 系统未开启

var isIOS

function album() {
    var result = 0;
    var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
    var authStatus = PHPhotoLibrary.authorizationStatus();
    if (authStatus === 0) {
        result = null;
    } else if (authStatus == 3) {
        result = 1;
    } else {
        result = 0;
    }
    plus.ios.deleteObject(PHPhotoLibrary);
    return result;
}

function camera() {
    var result = 0;
    var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
    var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
    if (authStatus === 0) {
        result = null;
    } else if (authStatus == 3) {
        result = 1;
    } else {
        result = 0;
    }
    plus.ios.deleteObject(AVCaptureDevice);
    return result;
}

function location() {
    var result = 0;
    var cllocationManger = plus.ios.import("CLLocationManager");
    var enable = cllocationManger.locationServicesEnabled();
    var status = cllocationManger.authorizationStatus();
    if (!enable) {
        result = 2;
    } else if (status === 0) {
        result = null;
    } else if (status === 3 || status === 4) {
        result = 1;
    } else {
        result = 0;
    }
    plus.ios.deleteObject(cllocationManger);
    return result;
}

function push() {
    var result = 0;
    var UIApplication = plus.ios.import("UIApplication");
    var app = UIApplication.sharedApplication();
    var enabledTypes = 0;
    if (app.currentUserNotificationSettings) {
        var settings = app.currentUserNotificationSettings();
        enabledTypes = settings.plusGetAttribute("types");
        if (enabledTypes == 0) {
            result = 0;
            console.log("推送权限没有开启");
        } else {
            result = 1;
            console.log("已经开启推送功能!")
        }
        plus.ios.deleteObject(settings);
    } else {
        enabledTypes = app.enabledRemoteNotificationTypes();
        if (enabledTypes == 0) {
            result = 3;
            console.log("推送权限没有开启!");
        } else {
            result = 4;
            console.log("已经开启推送功能!")
        }
    }
    plus.ios.deleteObject(app);
    plus.ios.deleteObject(UIApplication);
    return result;
}

function contact() {
    var result = 0;
    var CNContactStore = plus.ios.import("CNContactStore");
    var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
    if (cnAuthStatus === 0) {
        result = null;
    } else if (cnAuthStatus == 3) {
        result = 1;
    } else {
        result = 0;
    }
    plus.ios.deleteObject(CNContactStore);
    return result;
}

function record() {
    var result = null;
    var avaudiosession = plus.ios.import("AVAudioSession");
    var avaudio = avaudiosession.sharedInstance();
    var status = avaudio.recordPermission();
    console.log("permissionStatus:" + status);
    if (status === 1970168948) {
        result = null;
    } else if (status === 1735552628) {
        result = 1;
    } else {
        result = 0;
    }
    plus.ios.deleteObject(avaudiosession);
    return result;
}

function calendar() {
    var result = null;
    var EKEventStore = plus.ios.import("EKEventStore");
    var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
    if (ekAuthStatus == 3) {
        result = 1;
        console.log("日历权限已经开启");
    } else {
        console.log("日历权限没有开启");
    }
    plus.ios.deleteObject(EKEventStore);
    return result;
}

function memo() {
    var result = null;
    var EKEventStore = plus.ios.import("EKEventStore");
    var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
    if (ekAuthStatus == 3) {
        result = 1;
        console.log("备忘录权限已经开启");
    } else {
        console.log("备忘录权限没有开启");
    }
    plus.ios.deleteObject(EKEventStore);
    return result;
}


function requestIOS(permissionID) {
    return new Promise((resolve, reject) => {
        switch (permissionID) {
            case "push":
                resolve(push());
                break;
            case "location":
                resolve(location());
                break;
            case "record":
                resolve(record());
                break;
            case "camera":
                resolve(camera());
                break;
            case "album":
                resolve(album());
                break;
            case "contact":
                resolve(contact());
                break;
            case "calendar":
                resolve(calendar());
                break;
            case "memo":
                resolve(memo());
                break;
            default:
                resolve(0);
                break;
        }
    });
}

function requestAndroid(permissionID) {
    return new Promise((resolve, reject) => {
        plus.android.requestPermissions(
            [permissionID],
            function(resultObj) {
                var result = 0;
                for (var i = 0; i < resultObj.granted.length; i++) {
                    var grantedPermission = resultObj.granted[i];
                    console.log('已获取的权限:' + grantedPermission);
                    result = 1
                }
                for (var i = 0; i < resultObj.deniedPresent.length; i++) {
                    var deniedPresentPermission = resultObj.deniedPresent[i];
                    console.log('拒绝本次申请的权限:' + deniedPresentPermission);
                    result = 0
                }
                for (var i = 0; i < resultObj.deniedAlways.length; i++) {
                    var deniedAlwaysPermission = resultObj.deniedAlways[i];
                    console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
                    result = -1
                }
                resolve(result);
            },
            function(error) {
                console.log('result error: ' + error.message)
                resolve({
                    code: error.code,
                    message: error.message
                });
            }
        );
    });
}

function gotoAppPermissionSetting() {
    if (permission.isIOS) {
        var UIApplication = plus.ios.import("UIApplication");
        var application2 = UIApplication.sharedApplication();
        var NSURL2 = plus.ios.import("NSURL");
        var setting2 = NSURL2.URLWithString("app-settings:");
        application2.openURL(setting2);
        plus.ios.deleteObject(setting2);
        plus.ios.deleteObject(NSURL2);
        plus.ios.deleteObject(application2);
    } else {
        var Intent = plus.android.importClass("android.content.Intent");
        var Settings = plus.android.importClass("android.provider.Settings");
        var Uri = plus.android.importClass("android.net.Uri");
        var mainActivity = plus.android.runtimeMainActivity();
        var intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
        intent.setData(uri);
        mainActivity.startActivity(intent);
    }
}

const permission = {
    get isIOS(){
        return typeof isIOS === 'boolean' ? isIOS : (isIOS = uni.getSystemInfoSync().platform === 'ios')
    },
    requestIOS: requestIOS,
    requestAndroid: requestAndroid,
    gotoAppSetting: gotoAppPermissionSetting
}

export default permission

核心流程:

  1. 权限检查
  2. 调用相机获取图片
  3. 图片 Base64 转换
  4. 图片识别与车辆绑定

uni-app 官网上的 camera 是不支持 App 和 H5 端的

H5 端拍照识别实现:H5 端的拍照识别功能主要依赖浏览器原生 API,通过条件编译 #ifdef H5 标识 H5 端特有代码:

  • 权限处理:H5 端相机权限由浏览器管理,无需额外处理,用户首次调用相机时浏览器会自动弹出请求权限
  • 图片获取:通过 uni.chooseImage 接口调用相机,设置 sourceType: ['camera'] 指定从相机获取图片
uni.chooseImage({
  count: 1,                 // 最多选择1张图片
  sourceType: ['camera'],   // 仅从相机获取
  sizeType: ['original', 'compressed'], // 获取原图和压缩图
  success: async (res) => {
    // 处理获取到的图片
  }
});
  • 图片转换为 Base64:H5 端使用 FileReader 将图片转换为 Base64 格式
// H5端特有函数,用于将图片转换为Base64
// #ifdef H5
	function getBase64FromFileToWeb(file) {
		console.log('调用了 H5 端 getBase64FromFile');
		return new Promise((resolve, reject) => {
			const reader = new FileReader();
			reader.onload = (e) => {
				const base64 = e.target.result.split(',')[1];
				resolve(base64);
			};
			reader.onerror = (error) => {
				reject(error);
			};
			reader.readAsDataURL(file);
		});
	}
	// #endif

调用示例:

const base64 = await getBase64FromFileToWeb(res.tempFiles[0]);
  • 图片识别与车辆绑定:将 Base64 格式的图片数据传递给 OCR 识别接口,并处理识别结果
const recognizeAndBind = async (base64) => {
  try {
    // 配置识别参数
    const options = {
      "data.format": "text",
      "thpu.parser": "single_line",
      "ocr.cls": true,
      "ocr.limit_side_len": 2880,
      "ocr.language": "models/config_chinese.txt"
    };

    // 调用识别接口
    const res = await getRecognize({
      base64,
      options
    });
    
    // 处理识别结果
    if (res && res.data) {
      if (!res.data.statusBit) {
        // 未绑定,更新表单数据并自动绑定
        dataReversion.value = {
          ...dataReversion.value,
          carId: res.data.id,
          code: res.data.code,
          simCode: res.data.simCode,
          vehicleType: res.data.vehicleType,
          // 保留其他已填数据
        }
        await useAtOnce()
      } else {
        // 已绑定,弹窗确认是否覆盖
        uni.showModal({
          title: '提示',
          content: `当前车辆已被绑定,确认是否继续绑定,绑定后默认结束上一使用人绑定记录。`,
          success: async (modalRes) => {
            if (modalRes.confirm) {
              // 更新数据并绑定
              dataReversion.value = {
                ...dataReversion.value,
                carId: res.data.id,
                code: res.data.code,
                // 保留其他已填数据
              }
              await useAtOnce()
            }
          }
        });
      }
    }
  } catch (error) {
    console.error('识别绑定过程出错:', error);
    uni.showToast({
      title: '识别处理失败: ' + (error.message || '未知错误'),
      icon: 'none',
      duration: 2000
    });
  }
};

非 H5 端(App / 小程序)拍照识别功能:非 H5 端(App / 小程序)拍照识别功能需要处理原生权限,并使用不同的文件系统 API,通过条件编译 #ifndef H5 标识相关代码

  • 权限检查:区分 iOS 和 Android 平台:
// 检查相机权限
    const checkCameraPermission = async () => {
  // iOS 平台处理
      if (permision.isIOS) {
        let status = await permision.requestIOS('camera');
        if (status !== 1) {
          uni.showModal({
            content: "没有开启相机权限",
            confirmText: "去设置",
            success: function(res) {
              if (res.confirm) {
                permision.gotoAppSetting();
              }
            }
          });
        }
        return status;
      }
      // Android 平台处理
      let status = await permision.requestAndroid('android.permission.CAMERA');
      if (status !== 1) {
        uni.showModal({
          content: "没有开启相机权限",
          confirmText: "去设置",
          success: function(res) {
            if (res.confirm) {
              permision.gotoAppSetting();
            }
          }
        });
      }
      return status;
      };
      • 针对 iOS 和 Android 平台分别调用对应的权限请求 API
      • 如权限未开启,弹出模态框引导用户进入系统设置开启权限
    1. 图片获取:使用 uni.chooseImage 接口获取图片,非 H5 端返回的是本地文件路径:
    uni.chooseImage({
      count: 1,
      sourceType: ['camera'],
      success: (res) => {
        const filePath = res.tempFilePaths[0]; // 本地文件路径
        // 转换为Base64
      }
    });
    • 图片转换为 Base64:根据不同平台使用不同的文件读取方式:
        • App 端(5+App)使用 plus.io 模块
        • 小程序端使用 uni.getFileSystemManager()
    // 非H5端特有函数,用于将图片转换为Base64
    function getBase64FromFile(filePath) {
      return new Promise((resolve, reject) => {
        try {
          // 5+App平台
          #ifdef APP-PLUS
          plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
            entry.file((file) => {
              const fileReader = new plus.io.FileReader();
              fileReader.onload = (e) => {
                const base64 = e.target.result.split(',')[1];
                resolve(base64);
              };
              fileReader.readAsDataURL(file);
            }, (error) => {
              console.error('获取文件对象失败:', error);
              reject(new Error('获取文件对象失败'));
            });
          }, (error) => {
            console.error('解析文件路径失败:', error);
            reject(new Error('解析文件路径失败'));
          });
          #endif
    
          // 小程序平台
          #ifdef MP
          const fsm = uni.getFileSystemManager();
          fsm.readFile({
            filePath: filePath,
            encoding: 'base64',
            success: (res) => {
              if (res.data) {
                console.log('Base64转换成功');
                resolve(res.data);
              } else {
                reject(new Error('Base64数据为空'));
              }
            },
            fail: (error) => {
              console.error('读取文件失败:', error);
              reject(new Error('读取文件失败: ' + (error.errMsg || '未知错误')));
            }
          });
          #endif
        } catch (error) {
          console.error('文件处理错误:', error);
          reject(new Error('文件处理错误: ' + (error.message || '未知错误')));
        }
      });
    }
    • 图片识别与车辆绑定:非 H5 端使用与 H5 端相同的recognizeAndBind函数处理识别和绑定逻辑,实现了跨平台的统一处理