|
@@ -17,6 +17,16 @@ import { useI18n } from 'vue-i18n'
|
|
|
import { userApi } from '@/api/user'
|
|
import { userApi } from '@/api/user'
|
|
|
import { useConfirm } from '@/hooks/useConfirm'
|
|
import { useConfirm } from '@/hooks/useConfirm'
|
|
|
import { decodeQrFromAlbumImage, CANVAS_ID } from '@/utils/decodeQrImage'
|
|
import { decodeQrFromAlbumImage, CANVAS_ID } from '@/utils/decodeQrImage'
|
|
|
|
|
+import {
|
|
|
|
|
+ ensureScanCameraPermission,
|
|
|
|
|
+ ensureScanAlbumPermission,
|
|
|
|
|
+ isPermissionError,
|
|
|
|
|
+ isIosPermissionDenied,
|
|
|
|
|
+ shouldGuideToSettings,
|
|
|
|
|
+ openAppPermissionSetting,
|
|
|
|
|
+} from '@/utils/scanPermission'
|
|
|
|
|
+
|
|
|
|
|
+type ScanPermissionType = 'camera' | 'album'
|
|
|
|
|
|
|
|
const confirm = useConfirm()
|
|
const confirm = useConfirm()
|
|
|
const { t } = useI18n()
|
|
const { t } = useI18n()
|
|
@@ -60,13 +70,112 @@ function hideScanLoading() {
|
|
|
uni.hideLoading()
|
|
uni.hideLoading()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function isUserCancelled(error: any) {
|
|
|
|
|
+ if (error?._userCancelled) return true
|
|
|
|
|
+ const errMsg = String(error?.errMsg || error?.message || error).toLowerCase()
|
|
|
|
|
+ return (
|
|
|
|
|
+ errMsg.includes('cancel')
|
|
|
|
|
+ || errMsg.includes('取消')
|
|
|
|
|
+ || errMsg.includes('no image')
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isQrDecodeError(error: any) {
|
|
|
|
|
+ if (error?._decodeFailed) return true
|
|
|
|
|
+ const errMsg = String(error?.errMsg || error?.message || error).toLowerCase()
|
|
|
|
|
+ return (
|
|
|
|
|
+ errMsg.includes('decode')
|
|
|
|
|
+ || errMsg.includes('识别')
|
|
|
|
|
+ || errMsg.includes('image load')
|
|
|
|
|
+ || error?.code === 8
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getPermissionGuideContent(type: ScanPermissionType) {
|
|
|
|
|
+ const usage = type === 'camera'
|
|
|
|
|
+ ? (t('mine.p16') || '相机的使用')
|
|
|
|
|
+ : (t('mine.p17') || '相册的使用')
|
|
|
|
|
+ const tip = t('mine.p14') || '首次使用需授权。若已拒绝,请前往系统设置开启权限。'
|
|
|
|
|
+ return `${usage},${tip}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function guideToPermissionSetting(type: ScanPermissionType) {
|
|
|
|
|
+ const content = getPermissionGuideContent(type)
|
|
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 400))
|
|
|
|
|
+ return new Promise<void>((resolve) => {
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: t('Msg.SystemPrompt') || '系统提示',
|
|
|
|
|
+ content,
|
|
|
|
|
+ confirmText: t('mine.p13') || '去设置',
|
|
|
|
|
+ cancelText: t('Btn.Cancel') || '取消',
|
|
|
|
|
+ showCancel: true,
|
|
|
|
|
+ success: (res) => {
|
|
|
|
|
+ if (res.confirm) {
|
|
|
|
|
+ openAppPermissionSetting()
|
|
|
|
|
+ }
|
|
|
|
|
+ resolve()
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: () => resolve(),
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function ensureScanPermission(type: ScanPermissionType): Promise<boolean> {
|
|
|
|
|
+ const state = type === 'camera'
|
|
|
|
|
+ ? await ensureScanCameraPermission()
|
|
|
|
|
+ : await ensureScanAlbumPermission()
|
|
|
|
|
+
|
|
|
|
|
+ if (state === 'granted') {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await guideToPermissionSetting(type)
|
|
|
|
|
+ return false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function runWithPermission<T>(
|
|
|
|
|
+ type: ScanPermissionType,
|
|
|
|
|
+ action: () => Promise<T>,
|
|
|
|
|
+): Promise<T> {
|
|
|
|
|
+ if (!await ensureScanPermission(type)) {
|
|
|
|
|
+ const err: any = new Error('permission denied')
|
|
|
|
|
+ err._permissionDenied = true
|
|
|
|
|
+ err._alreadyGuided = true
|
|
|
|
|
+ throw err
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return await action()
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ if (!error?._alreadyGuided && (
|
|
|
|
|
+ isIosPermissionDenied(type)
|
|
|
|
|
+ || shouldGuideToSettings(
|
|
|
|
|
+ type === 'camera'
|
|
|
|
|
+ ? await ensureScanCameraPermission()
|
|
|
|
|
+ : await ensureScanAlbumPermission(),
|
|
|
|
|
+ )
|
|
|
|
|
+ || isPermissionError(error)
|
|
|
|
|
+ )) {
|
|
|
|
|
+ await guideToPermissionSetting(type)
|
|
|
|
|
+ error._permissionDenied = true
|
|
|
|
|
+ error._alreadyGuided = true
|
|
|
|
|
+ }
|
|
|
|
|
+ throw error
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function scanByCamera(): Promise<string> {
|
|
function scanByCamera(): Promise<string> {
|
|
|
return new Promise((resolve, reject) => {
|
|
return new Promise((resolve, reject) => {
|
|
|
uni.scanCode({
|
|
uni.scanCode({
|
|
|
onlyFromCamera: true,
|
|
onlyFromCamera: true,
|
|
|
scanType: ['qrCode'],
|
|
scanType: ['qrCode'],
|
|
|
success: (res) => resolve(res.result),
|
|
success: (res) => resolve(res.result),
|
|
|
- fail: reject,
|
|
|
|
|
|
|
+ fail: (err) => {
|
|
|
|
|
+ if (isIosPermissionDenied('camera')) {
|
|
|
|
|
+ reject({ ...err, _permissionDenied: true })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ reject(err)
|
|
|
|
|
+ },
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
@@ -85,18 +194,25 @@ function scanByAlbum(): Promise<string> {
|
|
|
success: async (chooseRes) => {
|
|
success: async (chooseRes) => {
|
|
|
const path = chooseRes.tempFilePaths[0]
|
|
const path = chooseRes.tempFilePaths[0]
|
|
|
if (!path) {
|
|
if (!path) {
|
|
|
- reject(new Error('no image'))
|
|
|
|
|
|
|
+ reject({ message: 'no image', _userCancelled: true })
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
showScanLoading()
|
|
showScanLoading()
|
|
|
try {
|
|
try {
|
|
|
resolve(await scanByAlbumApp(path))
|
|
resolve(await scanByAlbumApp(path))
|
|
|
- } catch (err) {
|
|
|
|
|
|
|
+ } catch (err: any) {
|
|
|
hideScanLoading()
|
|
hideScanLoading()
|
|
|
|
|
+ err._decodeFailed = true
|
|
|
reject(err)
|
|
reject(err)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- fail: reject,
|
|
|
|
|
|
|
+ fail: (err) => {
|
|
|
|
|
+ if (isIosPermissionDenied('album')) {
|
|
|
|
|
+ reject({ ...err, _permissionDenied: true })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ reject({ ...err, _userCancelled: true })
|
|
|
|
|
+ },
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
// #endif
|
|
// #endif
|
|
@@ -116,18 +232,18 @@ function pickScanResult(): Promise<string> {
|
|
|
return new Promise((resolve, reject) => {
|
|
return new Promise((resolve, reject) => {
|
|
|
uni.showActionSheet({
|
|
uni.showActionSheet({
|
|
|
itemList: [t('vu.login.item13'), t('vu.login.item12')],
|
|
itemList: [t('vu.login.item13'), t('vu.login.item12')],
|
|
|
- success: async (sheetRes) => {
|
|
|
|
|
- try {
|
|
|
|
|
- const result = sheetRes.tapIndex === 0
|
|
|
|
|
- ? await scanByCamera()
|
|
|
|
|
- : await scanByAlbum()
|
|
|
|
|
- resolve(result)
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- reject(err)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ success: (sheetRes) => {
|
|
|
|
|
+ resolve(sheetRes.tapIndex)
|
|
|
},
|
|
},
|
|
|
fail: reject,
|
|
fail: reject,
|
|
|
})
|
|
})
|
|
|
|
|
+ }).then(async (tapIndex) => {
|
|
|
|
|
+ // 等 actionSheet 完全关闭后再检查权限、弹窗,避免 iOS 吞掉 showModal
|
|
|
|
|
+ await new Promise((r) => setTimeout(r, 400))
|
|
|
|
|
+ if (tapIndex === 0) {
|
|
|
|
|
+ return runWithPermission('camera', scanByCamera)
|
|
|
|
|
+ }
|
|
|
|
|
+ return runWithPermission('album', scanByAlbum)
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -169,8 +285,7 @@ async function handleScanClick() {
|
|
|
await processScanResult(rawResult)
|
|
await processScanResult(rawResult)
|
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
|
const errMsg = String(error?.errMsg || error?.message || error)
|
|
const errMsg = String(error?.errMsg || error?.message || error)
|
|
|
- const isCancel = errMsg.includes('cancel') || errMsg.includes('Cancel')
|
|
|
|
|
- if (isCancel) {
|
|
|
|
|
|
|
+ if (isUserCancelled(error)) {
|
|
|
if (isValidScanLoginScene(scene.value)) {
|
|
if (isValidScanLoginScene(scene.value)) {
|
|
|
try {
|
|
try {
|
|
|
await userApi.updateAppScanLoginStatus({
|
|
await userApi.updateAppScanLoginStatus({
|
|
@@ -185,14 +300,10 @@ async function handleScanClick() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
scene.value = ''
|
|
scene.value = ''
|
|
|
- const isDecodeError = (
|
|
|
|
|
- errMsg.includes('fail')
|
|
|
|
|
- || errMsg.includes('识别')
|
|
|
|
|
- || errMsg.includes('decode')
|
|
|
|
|
- || errMsg.includes('image load')
|
|
|
|
|
- || error?.code === 8
|
|
|
|
|
- )
|
|
|
|
|
- if (isDecodeError) {
|
|
|
|
|
|
|
+ if (isPermissionError(error) || errMsg.includes('permission denied')) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isQrDecodeError(error)) {
|
|
|
uni.$u.toast(
|
|
uni.$u.toast(
|
|
|
t('vu.login.item15')
|
|
t('vu.login.item15')
|
|
|
|| '图片二维码反光/模糊,请截图后重试或正对屏幕拍摄',
|
|
|| '图片二维码反光/模糊,请截图后重试或正对屏幕拍摄',
|