input-method-select.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <template>
  2. <view class="wrap pr">
  3. <view class="w-full pa tw-top-0 tw-left-0 tw-z-0">
  4. <image class="w-full" :src="config.ossPathPerfixs + '/index-bg.png'" mode="widthFix"></image>
  5. </view>
  6. <view class="w-full pr tw-z-10">
  7. <view class="tw-p-[30rpx]">
  8. <view class="tw-flex tw-items-center tw-justify-start tw-mt-[60rpx]">
  9. <fs-avatar size="100rpx" src="/static/images/tool/logo.png"></fs-avatar>
  10. <text class="tw-text-[#fff] tw-text-[26rpx] tw-ml-[4rpx]"
  11. style="letter-spacing: 1rpx;">太原市中小学学生卫生保健所</text>
  12. </view>
  13. <view class="tw-mt-[20rpx] tw-text-[#fff] tw-text-[32rpx] tw-font-bold">
  14. 学校:{{ schoolName }}
  15. </view>
  16. </view>
  17. <view class="tw-px-[30rpx] tw-mt-[40rpx] tw-pb-[100rpx]">
  18. <view class="type-list">
  19. <view class="type-card" @click="handleManualInput">
  20. <view class="type-name">手工录入</view>
  21. <van-icon name="arrow" />
  22. </view>
  23. <view class="type-card" @click="handleScanInput">
  24. <view class="type-name">扫码录入</view>
  25. <van-icon name="arrow" />
  26. </view>
  27. </view>
  28. </view>
  29. </view>
  30. <van-popup v-model:show="showScanner" position="center" :style="{ width: '90%', height: '80%' }">
  31. <view class="scanner-popup">
  32. <view class="scanner-header">
  33. <text>扫描二维码</text>
  34. <van-icon name="cross" @click="closeScanner" />
  35. </view>
  36. <view class="scanner-body">
  37. <video ref="videoRef" class="scanner-video" autoplay playsinline></video>
  38. <canvas ref="canvasRef" style="display: none;"></canvas>
  39. </view>
  40. </view>
  41. </van-popup>
  42. </view>
  43. </template>
  44. <script setup>
  45. import config from '@/utils/config'
  46. import { getSchoolInfo } from '@/services/common'
  47. import jsQR from 'jsqr'
  48. const STORAGE_KEY = 'eye_examine_user_info'
  49. const schoolName = ref('')
  50. const showScanner = ref(false)
  51. const videoRef = ref(null)
  52. const canvasRef = ref(null)
  53. let stream = null
  54. let animationId = null
  55. onLoad(async () => {
  56. const savedData = uni.getStorageSync(STORAGE_KEY)
  57. if (savedData) {
  58. const userInfo = JSON.parse(savedData)
  59. const schoolId = userInfo.schoolId || ''
  60. if (schoolId) {
  61. try {
  62. const res = await getSchoolInfo(schoolId)
  63. if (res && res.code === 200) {
  64. schoolName.value = res.data.name || ''
  65. }
  66. } catch (error) {
  67. console.error('获取学校信息失败', error)
  68. }
  69. }
  70. }
  71. })
  72. const handleManualInput = () => {
  73. uni.navigateTo({
  74. url: '/pages/index/eye-examine-index'
  75. })
  76. }
  77. const handleScanInput = async () => {
  78. showScanner.value = true
  79. await nextTick()
  80. startCamera()
  81. }
  82. const startCamera = async () => {
  83. try {
  84. stream = await navigator.mediaDevices.getUserMedia({
  85. video: { facingMode: 'environment' }
  86. })
  87. const video = videoRef.value
  88. if (video) {
  89. video.srcObject = stream
  90. video.play()
  91. scanQRCode()
  92. }
  93. } catch (error) {
  94. console.error('摄像头启动失败', error)
  95. uni.showToast({
  96. title: '摄像头启动失败',
  97. icon: 'none'
  98. })
  99. }
  100. }
  101. const scanQRCode = () => {
  102. const video = videoRef.value
  103. const canvas = canvasRef.value
  104. if (!video || !canvas) return
  105. const ctx = canvas.getContext('2d')
  106. const scan = () => {
  107. if (video.readyState === video.HAVE_ENOUGH_DATA) {
  108. canvas.width = video.videoWidth
  109. canvas.height = video.videoHeight
  110. ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
  111. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
  112. const code = jsQR(imageData.data, imageData.width, imageData.height)
  113. if (code) {
  114. onDecode(code.data)
  115. return
  116. }
  117. }
  118. animationId = requestAnimationFrame(scan)
  119. }
  120. scan()
  121. }
  122. const onDecode = (result) => {
  123. closeScanner()
  124. console.log('扫码结果:', result)
  125. }
  126. const closeScanner = () => {
  127. if (animationId) {
  128. cancelAnimationFrame(animationId)
  129. animationId = null
  130. }
  131. if (stream) {
  132. stream.getTracks().forEach(track => track.stop())
  133. stream = null
  134. }
  135. showScanner.value = false
  136. }
  137. </script>
  138. <style lang="scss" scoped>
  139. .type-list {
  140. display: flex;
  141. flex-direction: column;
  142. gap: 20rpx;
  143. }
  144. .type-card {
  145. background: #fff;
  146. border-radius: 20rpx;
  147. display: flex;
  148. align-items: center;
  149. justify-content: center;
  150. padding: 50rpx 30rpx;
  151. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  152. position: relative;
  153. }
  154. .type-name {
  155. font-size: 32rpx;
  156. font-weight: bold;
  157. color: #333;
  158. flex: 1;
  159. text-align: center;
  160. }
  161. .type-card :deep(.van-icon) {
  162. position: absolute;
  163. right: 30rpx;
  164. }
  165. .scanner-popup {
  166. width: 100%;
  167. height: 100%;
  168. display: flex;
  169. flex-direction: column;
  170. background: #000;
  171. }
  172. .scanner-header {
  173. display: flex;
  174. justify-content: space-between;
  175. align-items: center;
  176. padding: 20rpx;
  177. background: #fff;
  178. }
  179. .scanner-body {
  180. flex: 1;
  181. display: flex;
  182. align-items: center;
  183. justify-content: center;
  184. overflow: hidden;
  185. }
  186. .scanner-video {
  187. width: 100%;
  188. height: 100%;
  189. object-fit: cover;
  190. }
  191. </style>