index.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <div>
  3. <QrStream :style="scannerStyle" @decode="handleDecode" @init="handleInit" @error="handleError">
  4. <template v-if="showCloseButton">
  5. <div class="qr-scanner-container">
  6. <button class="close-view" style="padding-left: 9px !important;" @click="closeScanner">X</button>
  7. <div class="qr-scanner">
  8. <div class="box">
  9. <div class="line"></div>
  10. <div class="angle"></div>
  11. </div>
  12. </div>
  13. </div>
  14. </template>
  15. </QrStream>
  16. </div>
  17. </template>
  18. <script setup>
  19. import {
  20. ref,
  21. defineExpose
  22. } from 'vue'
  23. import {
  24. QrStream
  25. } from 'vue3-qr-reader'
  26. const emit = defineEmits(['decode', 'close'])
  27. const scannerStyle = {
  28. position: 'fixed',
  29. top: 0,
  30. left: 0,
  31. width: '100vw',
  32. height: '100vh',
  33. zIndex: 9999,
  34. }
  35. const showCloseButton = ref(false)
  36. const qrcodeData = ref(null)
  37. const handleDecode = (data) => {
  38. console.log('Decoded data:', data)
  39. if (data) {
  40. emit('decode', data)
  41. qrcodeData.value = data
  42. }
  43. }
  44. const closeScanner = () => {
  45. showCloseButton.value = false;
  46. emit('close')
  47. }
  48. const handleInit = async (promise) => {
  49. console.log("打开扫码页面", promise);
  50. try {
  51. const {
  52. capabilities
  53. } = await promise
  54. console.log('Camera capabilities:', capabilities)
  55. showCloseButton.value = true
  56. } catch (error) {
  57. handleError(error)
  58. }
  59. }
  60. const handleError = (error) => {
  61. const errorMessages = {
  62. NotAllowedError: '您需要授予相机访问权限',
  63. NotFoundError: '这个设备上没有摄像头',
  64. NotSupportedError: '所需的安全上下文(HTTPS、本地主机)',
  65. NotReadableError: '相机被占用',
  66. OverconstrainedError: '安装摄像头不合适',
  67. StreamApiNotSupportedError: '此浏览器不支持流API',
  68. InsecureContextError: '仅允许在安全上下文中访问摄像机。使用HTTPS或本地主机,而不是HTTP。',
  69. }
  70. const message = errorMessages[error.name] || 'ERROR: 摄像头错误'
  71. message.error(message)
  72. console.error(message)
  73. }
  74. defineExpose({
  75. qrcodeData
  76. })
  77. </script>
  78. <style lang="scss" scoped>
  79. .qr-scanner-container {
  80. position: relative;
  81. width: 100%;
  82. height: 100%;
  83. .close-view {
  84. width: 30px;
  85. height: 30px;
  86. position: absolute;
  87. // top: 20px;
  88. top: 54px;
  89. right: 20px;
  90. border-radius: 50%;
  91. background-color: #fff;
  92. color: #000;
  93. text-align: center;
  94. line-height: 30px;
  95. font-size: 20px;
  96. cursor: pointer;
  97. z-index: 1000000;
  98. }
  99. }
  100. .qr-scanner {
  101. background-image: linear-gradient(0deg,
  102. transparent 24%,
  103. rgba(32, 255, 77, 0.1) 25%,
  104. rgba(32, 255, 77, 0.1) 26%,
  105. transparent 27%,
  106. transparent 74%,
  107. rgba(32, 255, 77, 0.1) 75%,
  108. rgba(32, 255, 77, 0.1) 76%,
  109. transparent 77%,
  110. transparent),
  111. linear-gradient(90deg,
  112. transparent 24%,
  113. rgba(32, 255, 77, 0.1) 25%,
  114. rgba(32, 255, 77, 0.1) 26%,
  115. transparent 27%,
  116. transparent 74%,
  117. rgba(32, 255, 77, 0.1) 75%,
  118. rgba(32, 255, 77, 0.1) 76%,
  119. transparent 77%,
  120. transparent);
  121. background-size: 3rem 3rem;
  122. background-position: -1rem -1rem;
  123. width: 100%;
  124. /* height: 100%; */
  125. height: 100vh;
  126. position: relative;
  127. background-color: #1110;
  128. /* background-color: #111; */
  129. }
  130. .qr-scanner .box {
  131. width: 213px;
  132. height: 213px;
  133. position: absolute;
  134. left: 50%;
  135. top: 50%;
  136. transform: translate(-50%, -50%);
  137. overflow: hidden;
  138. border: 0.1rem solid rgba(0, 255, 51, 0.2);
  139. /* background: url('http://resource.beige.world/imgs/gongconghao.png') no-repeat center center; */
  140. }
  141. .qr-scanner .line {
  142. height: calc(100% - 2px);
  143. width: 100%;
  144. background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%);
  145. border-bottom: 3px solid #00ff33;
  146. transform: translateY(-100%);
  147. animation: radar-beam 2s infinite alternate;
  148. animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
  149. animation-delay: 1.4s;
  150. }
  151. .qr-scanner .box:after,
  152. .qr-scanner .box:before,
  153. .qr-scanner .angle:after,
  154. .qr-scanner .angle:before {
  155. content: '';
  156. display: block;
  157. position: absolute;
  158. width: 3vw;
  159. height: 3vw;
  160. border: 0.2rem solid transparent;
  161. }
  162. .qr-scanner .box:after,
  163. .qr-scanner .box:before {
  164. top: 0;
  165. border-top-color: #00ff33;
  166. }
  167. .qr-scanner .angle:after,
  168. .qr-scanner .angle:before {
  169. bottom: 0;
  170. border-bottom-color: #00ff33;
  171. }
  172. .qr-scanner .box:before,
  173. .qr-scanner .angle:before {
  174. left: 0;
  175. border-left-color: #00ff33;
  176. }
  177. .qr-scanner .box:after,
  178. .qr-scanner .angle:after {
  179. right: 0;
  180. border-right-color: #00ff33;
  181. }
  182. @keyframes radar-beam {
  183. 0% {
  184. transform: translateY(-100%);
  185. }
  186. 100% {
  187. transform: translateY(0);
  188. }
  189. }
  190. uni-button {
  191. padding-left: 9px !important;
  192. }
  193. </style>