input-method-select.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. <scan-code ref="scan"></scan-code>
  44. </template>
  45. <script setup>
  46. import config from '@/utils/config'
  47. import { getSchoolInfo } from '@/services/common'
  48. // import jsQR from 'jsqr'
  49. import scanCode from '@/components/easy-scancode/easy-scancode'
  50. const STORAGE_KEY = 'eye_examine_user_info'
  51. const schoolName = ref('')
  52. const showScanner = ref(false)
  53. const videoRef = ref(null)
  54. const canvasRef = ref(null)
  55. let stream = null
  56. let animationId = null
  57. const scan = ref(null)
  58. onLoad(async () => {
  59. const savedData = uni.getStorageSync(STORAGE_KEY)
  60. if (savedData) {
  61. const userInfo = JSON.parse(savedData)
  62. const schoolId = userInfo.schoolId || ''
  63. if (schoolId) {
  64. try {
  65. const res = await getSchoolInfo(schoolId)
  66. if (res && res.code === 200) {
  67. schoolName.value = res.data.name || ''
  68. }
  69. } catch (error) {
  70. console.error('获取学校信息失败', error)
  71. }
  72. }
  73. }
  74. })
  75. const handleManualInput = () => {
  76. uni.navigateTo({
  77. url: '/pages/index/eye-examine-index'
  78. })
  79. }
  80. const handleScanInput = async () => {
  81. scan.value.start({
  82. success: (val, res) => {
  83. const savedData = uni.getStorageSync(STORAGE_KEY)
  84. let userPhone = ''
  85. if (savedData) {
  86. const info = JSON.parse(savedData)
  87. userPhone = info.phone || ''
  88. }
  89. uni.navigateTo({
  90. url: `/pages/index/select-input-type?studentId=${val}&userPhone=${encodeURIComponent(userPhone)}`
  91. })
  92. },
  93. fail: (rej) => {
  94. console.log('扫描失败', rej)
  95. }
  96. })
  97. // showScanner.value = true
  98. // await nextTick()
  99. // startCamera()
  100. }
  101. const startCamera = async () => {
  102. try {
  103. stream = await navigator.mediaDevices.getUserMedia({
  104. video: { facingMode: 'environment' }
  105. })
  106. const video = videoRef.value
  107. if (video) {
  108. video.srcObject = stream
  109. video.play()
  110. scanQRCode()
  111. }
  112. } catch (error) {
  113. console.error('摄像头启动失败', error)
  114. uni.showToast({
  115. title: '摄像头启动失败',
  116. icon: 'none'
  117. })
  118. }
  119. }
  120. const scanQRCode = () => {
  121. const video = videoRef.value
  122. const canvas = canvasRef.value
  123. if (!video || !canvas) return
  124. const ctx = canvas.getContext('2d')
  125. const scan = () => {
  126. if (video.readyState === video.HAVE_ENOUGH_DATA) {
  127. canvas.width = video.videoWidth
  128. canvas.height = video.videoHeight
  129. ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
  130. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
  131. const code = jsQR(imageData.data, imageData.width, imageData.height)
  132. if (code) {
  133. onDecode(code.data)
  134. return
  135. }
  136. }
  137. animationId = requestAnimationFrame(scan)
  138. }
  139. scan()
  140. }
  141. const onDecode = (result) => {
  142. closeScanner()
  143. console.log('扫码结果:', result)
  144. }
  145. const closeScanner = () => {
  146. if (animationId) {
  147. cancelAnimationFrame(animationId)
  148. animationId = null
  149. }
  150. if (stream) {
  151. stream.getTracks().forEach(track => track.stop())
  152. stream = null
  153. }
  154. showScanner.value = false
  155. }
  156. </script>
  157. <style lang="scss" scoped>
  158. .type-list {
  159. display: flex;
  160. flex-direction: column;
  161. gap: 20rpx;
  162. }
  163. .type-card {
  164. background: #fff;
  165. border-radius: 20rpx;
  166. display: flex;
  167. align-items: center;
  168. justify-content: center;
  169. padding: 50rpx 30rpx;
  170. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  171. position: relative;
  172. }
  173. .type-name {
  174. font-size: 32rpx;
  175. font-weight: bold;
  176. color: #333;
  177. flex: 1;
  178. text-align: center;
  179. }
  180. .type-card :deep(.van-icon) {
  181. position: absolute;
  182. right: 30rpx;
  183. }
  184. .scanner-popup {
  185. width: 100%;
  186. height: 100%;
  187. display: flex;
  188. flex-direction: column;
  189. background: #000;
  190. }
  191. .scanner-header {
  192. display: flex;
  193. justify-content: space-between;
  194. align-items: center;
  195. padding: 20rpx;
  196. background: #fff;
  197. }
  198. .scanner-body {
  199. flex: 1;
  200. display: flex;
  201. align-items: center;
  202. justify-content: center;
  203. overflow: hidden;
  204. }
  205. .scanner-video {
  206. width: 100%;
  207. height: 100%;
  208. object-fit: cover;
  209. }
  210. </style>