vue3 扫描二维码

155 阅读4分钟
<template>
  <div class="sampling-registration">
    <div class="container">
     
      <div class="content">
        <img class="sampling-icon" src="@pages/pink-ribbon/assets/imgs/sampling/sampling-icon.png" />
        <div class="input-box">
          <input placeholder="请输入采样条形码" v-model="barcode" />
        </div>
        <div class="scan" @click="startScan">
          <img src="@pages/pink-ribbon/assets/imgs/sampling/camera-icon.png" class="scan-icon" />
          扫码识别
        </div>
      </div>
      <div class="agree" :class="{ 'shake-animation': shakeAnimation }" @click="switchAgree">
        <div class="radio-btn" :class="{ 'radio-btn-active': agreeChecked, 'radio-btn-default': !agreeChecked }"></div>
        <span class="agree-text"
          >请务必确认采样信息登记时的信息与实际采样人一致,检测报告仅对本次实际采样人送检样本负责</span
        >
      </div>
    </div>
  
    <div id="interactive" v-show="isScanningFlag">
      <img @click.stop="stopScan" class="close_icon" src="@pages/pink-ribbon/assets/imgs/sampling/close_icon.png" />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { showToast } from 'vant'
import { useRouter, useRoute, LocationQueryValue } from 'vue-router'
import { save } from './service'
import { ref, onMounted, onUnmounted } from 'vue'
import Quagga from '@ericblade/quagga2'
import { isEmpty } from 'lodash'
import { useUserStore } from '@pink/store/user'
import { handleDeniedPermission } from '@/JSBridge/hooks/useDeniedPermission'
import { rnScanQrcode, rnIsShowTab, rnAndroidScanQrcode, rnGoThirdpage } from '@JSBridge/nativeOperations'
import { useService } from '@JSBridge/hooks/useService'
import { isIOS, isWechatMiniEnv, isInAppAndroid, isInApp } from '@JSBridge/hooks/useBridgeUtils'
import config from '@pink/config'
import { jsbridge } from '@hhorg/hh-sdk'

const { token, userInfo } = storeToRefs(useUserStore())
const barcode = ref(null)
const isScanning = ref(false)
const isScanningFlag = ref(false)
const agreeChecked = ref(false)
const shakeAnimation = ref(false)

const router = useRouter()
const route = useRoute()
const age = route.query.age ?? ''
const right_name = route.query.right_name ?? ''
const gender = route.query.gender ?? ''
const name = route.query.name ?? ''
const isShowKeyBoard = ref(false)
const startScan = () => {
  if (jsbridge.isWxWebEnv()) {
    isScanningFlag.value = true
    if (isScanning.value) return

    console.log('666')
    console.log(666)

    Quagga.init(
      {
        inputStream: {
          type: 'LiveStream',
          constraints: {
            width: { ideal: window.innerWidth }, // 理想宽度为窗口宽度
            height: window.innerHeight, // 理想高度为窗口高度
            facingMode: 'environment' // 使用后置摄像头
          },
          target: document.querySelector('#interactive') // 显示扫描界面的DOM节点
        },
        decoder: {
          readers: ['code_128_reader', 'ean_reader', 'ean_8_reader', 'upc_reader', 'code_39_reader']
        }
      },
      (err) => {
        if (err) {
          console.error(err)
          return
        }
        isScanning.value = true

        Quagga.start()
        attachListeners()
      }
    )
  } else if (isIOS()) {
    handleIOSScan()
  } else {
    console.log('Androif==== >', isInAppAndroid())
    handleAndroidScan()
  }
}

const switchAgree = () => {
  agreeChecked.value = !agreeChecked.value
}

const handleAndroidScan = async () => {
  console.log('点击----》》》')
  let resultObj: any = await rnAndroidScanQrcode()

  console.log('Android ====>', resultObj)
}

const setIsShowTab = async (isShow: boolean) => {
  await rnIsShowTab(isShow)
}
const handleIOSScan = async () => {
  let resultObj: any = await rnScanQrcode()
  console.log('resultObj========<><><>', resultObj)
  if (resultObj && !isEmpty(resultObj)) {
    barcode.value = resultObj.result
  } else {
    const payloadObj = {
      payload: {
        value: '',
        type: ''
      }
    }
    payloadObj.payload.value = resultObj?.value
    payloadObj.payload.type = resultObj?.type
    handleDeniedPermission(payloadObj)
  }
}
const attachListeners = () => {
  Quagga.onDetected((data) => {
    barcode.value = data.codeResult.code // 获取扫描结果
    console.log('Detected barcode:', data.codeResult.code)
    Quagga.stop() // 扫描到后停止
    isScanning.value = false // 更新状态
    isScanningFlag.value = false // 更新状态
  })
}
const stopScan = () => {
  if (isScanning.value) {
    Quagga.stop()
    isScanning.value = false
    isScanningFlag.value = false // 更新状态
  }
}

const next = async () => {
  if (!agreeChecked.value) {
    showToast('请先确认登记信息')

    shakeAnimation.value = true

    let timer = setTimeout(() => {
      shakeAnimation.value = false
      clearTimeout(timer)
    }, 500)
    return
  }
  if (!barcode.value) {
    showToast('采样码不能为空')
    return
  }

  const order_id = route.query.order_id ?? ''
  const card_id = barcode.value
  const params = {
    card_id,
    order_id
  }
  const { data } = await save(params)
  if (data.status !== 200) {
    showToast(data.message)
  } else {
    // 跳转腾讯问卷
    jumpToTencentQuestion(order_id, card_id)
  }
}

// 跳转到腾讯问券
const jumpToTencentQuestion = (order_id: string | LocationQueryValue[], card_id: string | null) => {
  console.log('isWechatMiniEnv', isWechatMiniEnv())
  // 判断当前环境是不是在小程序内
  if (isWechatMiniEnv()) {
    console.log('order_id', order_id, card_id)
    const url = `/pages/SurveyTransferPage?userId=${order_id}_${card_id}`

    jsbridge.mini.goPage({
      miniPath: url
    })
  } else if (jsbridge.isAppEnv()) {
    const url = `${config.TencentQuestion}?userid=${order_id}_${card_id}`
    window.location.replace(url)

    // rnGoThirdpage({ thirdUrl: url })
  } else {
    const url = `${config.TencentQuestion}?userid=${order_id}_${card_id}`
    window.location.replace(url)
  }
}
// 键盘弹起
const onKeyBoardUp = () => {
  isShowKeyBoard.value = true
}

// 键盘收起
const onKeyBoardDown = () => {
  isShowKeyBoard.value = false
}

onUnmounted(() => {
  Quagga.stop()
  // setIsShowTab('true')
})

onMounted(() => {
  // setIsShowTab('false')
  let originalHeight = document.documentElement.clientHeight || document.body.clientHeight
  window.onresize = function () {
    let resizeHeight = document.documentElement.clientHeight || document.body.clientHeight
    if (resizeHeight - 0 < originalHeight - 0) {
      onKeyBoardUp()
    } else {
      onKeyBoardDown()
    }
  }
})

const scanQrcodeCallBack = (data) => {
  console.log('scanQrcodeCallBack ===>', data)
  if (data.type === 'h5Scan') {
    barcode.value = data.payload
  }
}

useService('h5Scan', scanQrcodeCallBack)
useService('isDeniedPermission', handleDeniedPermission)
</script>

<style scoped lang="scss">
.sampling-registration {
  // height: 100vh;
  height: calc(100vh - 120px);
  display: flex;
  flex-direction: column;
  font-family: PingFang SC, PingFang SC;
  overflow-y: auto;
  background: #f6f8f8;
  box-sizing: border-box;

  .container {
    position: relative;
    padding: 30px;
    height: 100%;
    // height: calc(100vh - 102px);
    box-sizing: border-box;
  }

  .header {
    padding: 30px;
    min-height: 120px;
    background: #ffffff;
    border-radius: 36px 36px 36px 36px;

    .title {
      margin-bottom: 26px;
      height: 42px;
      font-weight: 600;
      font-size: 15px;
      color: #203042;
    }

    .item {
      font-size: 14px;

      .label {
        display: inline-block;
        width: 165px;
        color: #647485;
      }

      .value {
        color: #203042;
      }
    }
  }

  .content {
    margin-top: 30px;
    padding: 30px;
    background: #ffffff;
    border-radius: 36px 36px 36px 36px;

    .input-box {
      margin: 24px 0;
      display: flex;
      align-items: center;
      justify-content: center;

      input {
        width: 100%;
        height: 120px;
        text-align: center;
        font-size: 14px;
        background: #f6f8f8;
        border: none;
        border-radius: 36px 36px 36px 36px;
      }
    }

    .scan {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 90px;
      font-size: 13px;
      text-align: center;
      line-height: 45px;
      border-radius: 57px;

      img {
        width: 30px;
        height: 30px;
        margin-right: 10px;
      }
    }

    img {
      width: 100%;
      height: 251px;
    }
  }

  .footer {
    position: fixed;
    bottom: 0;
    width: 100%;
    padding-top: 12px;
    padding-bottom: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 94px;
    background: #ffffff;
    box-sizing: border-box;

    .button {
      width: 630px;
      height: 70px;
      line-height: 35px;
      text-align: center;
      font-size: 14px;
      color: #ffffff;
      background: #203042;
      border-radius: 57px 57px 57px 57px;
    }
  }

  .agree {
    margin-top: 100px;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    height: 72px;
    font-weight: 400;
    font-size: 13px;
    color: #203042;
    line-height: 15px;

    .radio-btn {
      margin-right: 14px;
      display: inline-block;
      flex-shrink: 0;
      width: 30px;
      height: 30px;
    }

    .radio-btn-default {
      border: 3px solid rgba(32, 48, 66, 0.4);
      border-radius: 100%;
      box-sizing: border-box;
    }

    .radio-btn-active {
      background-image: url('@pages/pink-ribbon/assets/imgs/sampling/checked.png');
      background-size: 100% 100%;
    }
  }
}

@keyframes shake {
  0% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-10px);
  }
  50% {
    transform: translateX(10px);
  }
  75% {
    transform: translateX(-10px);
  }
  100% {
    transform: translateX(10px);
  }
}

.shake-animation {
  animation: shake 0.3s; /* 抖动的持续时间 */
  animation-iteration-count: infinite; /* 动画重复次数 */
}

#interactive {
  height: 100%;
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  z-index: 99;
  overflow: hidden;
  :deep(video) {
    height: 100vh;
  }
  .close_icon {
    width: 46px;
    height: 46px;
    position: absolute;
    top: 130px;
    left: 50px;
    z-index: 999;
  }
}
</style>