index.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <script setup>
  2. import {Html5Qrcode, Html5QrcodeScanner, Html5QrcodeScanType, Html5QrcodeSupportedFormats} from "html5-qrcode";
  3. const emit = defineEmits('oSuccess', 'onError')
  4. const slots = useSlots();
  5. // 判断是否有 default 插槽
  6. const hasDefaultSlot = computed(() => !!slots.default);
  7. let isShow = ref(false)
  8. let results = ref(null)
  9. let scanInstance = null
  10. const openQrcode = async () => {
  11. Html5Qrcode.getCameras()
  12. .then(devices => {
  13. if (devices && devices.length) {// 当前环境下能识别出摄像头,并且摄像头的数据可能不止一个
  14. if (!scanInstance) {
  15. // reader 放置扫码功能的元素ID
  16. scanInstance = new Html5Qrcode('qr-reader', {
  17. formatsToSupport: [
  18. Html5QrcodeSupportedFormats.QR_CODE,
  19. ],
  20. })
  21. }
  22. isShow.value = true
  23. scanInstance.start(
  24. {facingMode: "environment"},
  25. {
  26. focusMode: 'continuous',
  27. fps: 1, // 可选,每n秒帧扫描一次
  28. qrbox: { // 扫描的UI框
  29. width: 250,
  30. height: 250
  31. },
  32. videoConstraints: {
  33. // width: 375,
  34. // height: (window.visualViewport.height - 50),
  35. aspectRatio: window.visualViewport.height / window.visualViewport.width,
  36. facingMode: "environment",
  37. }
  38. },
  39. (decodedText, decodedResult) => {
  40. showToast('识别成功')
  41. // 扫描结果
  42. results.value = {
  43. decodedText,
  44. decodedResult
  45. }
  46. isShow.value = false
  47. scanInstance.stop()
  48. emit('oSuccess', {
  49. decodedText,
  50. decodedResult
  51. })
  52. },
  53. (errorMessage, error) => {
  54. emit('onError', {
  55. errorMessage,
  56. error
  57. })
  58. }
  59. )
  60. }
  61. })
  62. .catch((err) => {
  63. isShow.value = false
  64. // 错误信息处理仅供参考,具体情况看输出!!!
  65. let errorStr = ''
  66. if (typeof err === "string") {
  67. errorStr = err
  68. } else {
  69. if (err.name === "NotAllowedError")
  70. errorStr = "您需要授予相机访问权限"
  71. if (err.name === "NotFoundError") {
  72. errorStr = "未检测到摄像头"
  73. }
  74. if (err.name === "NotSupportedError")
  75. errorStr = "摄像头访问只支持在安全的上下文中,如https或localhost"
  76. if (err.name === "NotReadableError") errorStr = "摄像头被占用"
  77. if (err.name === "OverconstrainedError")
  78. errorStr = "安装摄像头不合适"
  79. if (err.name === "StreamApiNotSupportedError")
  80. errorStr = "此浏览器不支持流API"
  81. }
  82. showToast(errorStr)
  83. })
  84. }
  85. const closeQrcode = () => {
  86. isShow.value = false
  87. if (scanInstance) scanInstance.stop()
  88. }
  89. let fileList = ref([])
  90. const uploadImg = () => {
  91. try {
  92. window.qrcode.callback = (result) => {
  93. showToast(result);
  94. };
  95. let file = state.fileList[0].file;
  96. let reader = new FileReader();
  97. reader.onload = (function () {
  98. return function (e) {
  99. window.qrcode.decode(e.target.result);
  100. };
  101. })(file);
  102. reader.readAsDataURL(file);
  103. } catch (error) {
  104. console.log(error);
  105. showToast({
  106. message: error+"识别失败!",
  107. duration: 2000,
  108. });
  109. }
  110. }
  111. const selectFile = ()=> {
  112. let odeScanner = new Html5QrcodeScanner(
  113. "qr-code-file", { fps: 1 , qrbox: { // 扫描的UI框
  114. width: 250,
  115. height: 250
  116. },});
  117. odeScanner.render((e) => {
  118. console.log(e)
  119. });
  120. console.log(odeScanner, 'odeScanner')
  121. }
  122. onMounted(() => {
  123. selectFile()
  124. })
  125. onUnmounted(() => {
  126. if (scanInstance) scanInstance.stop()
  127. })
  128. </script>
  129. <template>
  130. <div>
  131. <template v-if="hasDefaultSlot">
  132. <slot></slot>
  133. </template>
  134. <template v-else>
  135. <van-button @click="openQrcode" size="mini">扫一扫</van-button>
  136. </template>
  137. <div v-if="false" class="h-200">
  138. {{ results }}
  139. </div>
  140. <div class="overlay" v-show="isShow">
  141. <div class="absolute w-full z-30 flex flex-row items-center justify-center p-12">
  142. <div class="absolute left-12 font-bold" @click="closeQrcode">
  143. <span class="iconfont icon-left text-white"></span>
  144. </div>
  145. <div class="text-base text-[#fff] font-bold">
  146. 扫描二维码
  147. </div>
  148. </div>
  149. <div class="relative qr-reader z-20" id="qr-reader"></div>
  150. <p class="absolute w-full p-12 text-center bottom-100 z-30 text-sm text-[#fff]">请将二维码对准扫码框中心</p>
  151. <div id="qr-code-file" class="absolute w-54 h-54 grid place-items-center bottom-40 z-30 rounded-full bg-[#000]/80 text-white left-1/2 -translate-x-1/2">
  152. <img src="~/assets/img/scan/pic.png" height="32" width="32" alt=""/>
  153. </div>
  154. </div>
  155. <div id="qr-code-file" class="w-54 h-54 grid place-items-center bottom-40 z-30 rounded-full bg-[#000]/80 text-white">
  156. <img src="~/assets/img/scan/pic.png" height="32" width="32" alt=""/>
  157. </div>
  158. <van-uploader
  159. v-model="fileList"
  160. :preview-image="false"
  161. :after-read="uploadImg"
  162. accept="image/*"
  163. :multiple="false"
  164. :max-count="1"
  165. >
  166. <van-icon name="photo-o"
  167. /></van-uploader>
  168. </div>
  169. </template>
  170. <style scoped lang="scss">
  171. .overlay {
  172. position: fixed;
  173. bottom: 0;
  174. left: 0;
  175. z-index: 999999;
  176. width: 100%;
  177. height: calc(100% - 50px);
  178. background: rgba(0, 0, 0, 0.7);
  179. .scan-instruction {
  180. }
  181. }
  182. </style>