| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- <template>
- <view class="cwg-language cursor-pointer" :data-tooltip="t('Downloadpage.item1')">
- <view class="pc-header-btn" @click="handleScanClick">
- <cwg-icon name="crm-scan" color="#97A1C0" :size="20" />
- <cwg-match-media :min-width="791">
- <view>{{ t('vu.login.item9') }}</view>
- </cwg-match-media>
- </view>
- <cwg-confirm-popup />
- <canvas :canvas-id="CANVAS_ID" :id="CANVAS_ID" class="scan-decode-canvas" />
- </view>
- </template>
- <script setup lang="ts">
- import { ref, getCurrentInstance } from 'vue'
- import { useI18n } from 'vue-i18n'
- import { userApi } from '@/api/user'
- import { useConfirm } from '@/hooks/useConfirm'
- import { decodeQrFromAlbumImage, CANVAS_ID } from '@/utils/decodeQrImage'
- const confirm = useConfirm()
- const { t } = useI18n()
- const instance = getCurrentInstance()
- const SCAN_LOGIN_SCENE_REG = /^[a-f0-9]{32}$/i
- function parseScene(result: string): string {
- if (!result) return ''
- const trimmed = result.trim()
- const directMatch = trimmed.match(/[a-f0-9]{32}/i)
- if (directMatch?.[0]) return directMatch[0]
- try {
- const url = new URL(trimmed)
- const sceneParam = url.searchParams.get('scene')
- if (sceneParam && SCAN_LOGIN_SCENE_REG.test(sceneParam)) return sceneParam
- const lastPath = url.pathname.split('/').pop() || ''
- if (SCAN_LOGIN_SCENE_REG.test(lastPath)) return lastPath
- return trimmed
- } catch {
- return trimmed
- }
- }
- function isValidScanLoginScene(value: string): boolean {
- return SCAN_LOGIN_SCENE_REG.test(value)
- }
- const scene = ref('')
- const scanLoadingVisible = ref(false)
- function showScanLoading() {
- if (scanLoadingVisible.value) return
- scanLoadingVisible.value = true
- uni.showLoading({ title: t('vu.login.item14'), mask: true })
- }
- function hideScanLoading() {
- if (!scanLoadingVisible.value) return
- scanLoadingVisible.value = false
- uni.hideLoading()
- }
- function scanByCamera(): Promise<string> {
- return new Promise((resolve, reject) => {
- uni.scanCode({
- onlyFromCamera: true,
- scanType: ['qrCode'],
- success: (res) => resolve(res.result),
- fail: reject,
- })
- })
- }
- function scanByAlbumApp(path: string): Promise<string> {
- return decodeQrFromAlbumImage(path, instance)
- }
- function scanByAlbum(): Promise<string> {
- // #ifdef APP-PLUS
- return new Promise((resolve, reject) => {
- uni.chooseImage({
- count: 1,
- sourceType: ['album'],
- sizeType: ['compressed'],
- success: async (chooseRes) => {
- const path = chooseRes.tempFilePaths[0]
- if (!path) {
- reject(new Error('no image'))
- return
- }
- showScanLoading()
- try {
- resolve(await scanByAlbumApp(path))
- } catch (err) {
- hideScanLoading()
- reject(err)
- }
- },
- fail: reject,
- })
- })
- // #endif
- // #ifndef APP-PLUS
- return new Promise((resolve, reject) => {
- uni.scanCode({
- onlyFromCamera: false,
- scanType: ['qrCode'],
- success: (res) => resolve(res.result),
- fail: reject,
- })
- })
- // #endif
- }
- function pickScanResult(): Promise<string> {
- return new Promise((resolve, reject) => {
- uni.showActionSheet({
- 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)
- }
- },
- fail: reject,
- })
- })
- }
- async function processScanResult(rawResult: string) {
- showScanLoading()
- try {
- scene.value = parseScene(rawResult)
- if (!isValidScanLoginScene(scene.value)) {
- hideScanLoading()
- uni.$u.toast(t('vu.login.item10') || t('login.msg0'))
- return
- }
- await userApi.updateAppScanLoginStatus({ scene: scene.value, status: 3 })
- hideScanLoading()
- await confirm({
- title: t('vu.login.item8'),
- content: t('vu.login.item7'),
- confirmText: t('newSignin.item7'),
- cancelText: t('Btn.Cancel'),
- })
- const res = await userApi.confirmAppScanLogin({ scene: scene.value })
- scene.value = ''
- if (res.code === 200) {
- uni.$u.toast(res.msg || t('login.msg0_1'))
- } else {
- uni.$u.toast(res.msg || t('login.msg0'))
- }
- } catch (error) {
- hideScanLoading()
- throw error
- }
- }
- async function handleScanClick() {
- try {
- const rawResult = await pickScanResult()
- await processScanResult(rawResult)
- } catch (error: any) {
- const errMsg = String(error?.errMsg || error?.message || error)
- const isCancel = errMsg.includes('cancel') || errMsg.includes('Cancel')
- if (isCancel) {
- if (isValidScanLoginScene(scene.value)) {
- try {
- await userApi.updateAppScanLoginStatus({
- scene: scene.value,
- status: 0,
- })
- scene.value = ''
- } catch {
- // 二维码已过期等情况,忽略回滚失败
- }
- }
- return
- }
- scene.value = ''
- const isDecodeError = (
- errMsg.includes('fail')
- || errMsg.includes('识别')
- || errMsg.includes('decode')
- || errMsg.includes('image load')
- || error?.code === 8
- )
- if (isDecodeError) {
- uni.$u.toast(
- t('vu.login.item15')
- || '图片二维码反光/模糊,请截图后重试或正对屏幕拍摄',
- )
- return
- }
- uni.$u.toast(error?.msg || error?.errMsg || t('login.msg0'))
- }
- }
- </script>
- <style scoped lang="scss">
- @import "@/uni.scss";
- .pc-header-btn {
- width: auto;
- display: flex;
- align-items: center;
- cursor: pointer;
- gap: px2rpx(6);
- padding: 0 px2rpx(5);
- }
- .cwg-language {
- @media screen and (max-width: 991px) {
- :deep(.cwg-dropdown-menu-container) {
- right: px2rpx(-20) !important;
- }
- }
- }
- :deep(.cwg-dropdown-menu-container .menu .menu-item) {
- min-height: px2rpx(36);
- }
- :deep(.cwg-dropdown) {
- overflow: visible !important;
- }
- .scan-decode-canvas {
- position: fixed;
- left: -9999px;
- top: -9999px;
- width: 1200px;
- height: 1200px;
- opacity: 0;
- pointer-events: none;
- }
- </style>
|