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"><textclass="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 組IDMAX_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.driverAccountIdconsole.log("selectedValues.value",selectedLabels.value)// 在下拉列表中找到對應的idconst 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) returnconst res = await getDataReversion1(vehicleListValue.value)if (res && res.data) {dataReversion.value.vehicleType = res.data.vehicleTypedataReversion.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 和 loginUserNamedataReversion.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.valuesubmitData.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 H5function 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 H5function getBase64FromFile(filePath) {return new Promise((resolve, reject) => {try {// #ifdef APP-PLUSplus.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 MPconst 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 || '未知錯誤')));}});}// #endifconst takePhoto = async () => {// #ifdef APP-PLUS// 只檢測相機權限let status = await checkCameraPermission();if (status !== 1) {// 沒有權限,直接 returnreturn;}// #endifuni.chooseImage({count: 1,sourceType: ['camera'],sizeType: ['original', 'compressed'],success: async (res) => {try {let base64 = '';// #ifdef H5base64 = await getBase64FromFileToWeb(res.tempFiles[0]);// #endif// #ifndef H5if (res.tempFilePaths && res.tempFilePaths.length > 0) {const filePath = res.tempFilePaths[0];console.log('開始轉換圖片,文件路徑:', filePath);base64 = await getBase64FromFile(filePath);} else {throw new Error('未能獲取到圖片路徑');}// #endifif (!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 () => {// iOSif (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;}// Androidlet 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 isIOSfunction 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
核心流程:
- 權限檢查
- 調用相機獲取圖片
- 圖片 Base64 轉換
- 圖片識別與車輛綁定
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 H5function 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
- 如權限未開啟,彈出模態框引導用戶進入系統設置開啟權限
- 圖片獲取:使用 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-PLUSplus.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 MPconst 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函數處理識別和綁定邏輯,實現了跨平臺的統一處理