input-method-select.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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. //val是扫描到的二维码内容
  84. console.log('扫描成功', val, res)
  85. uni.navigateTo({
  86. url: `/pages/index/select-input-type?studentId=${val}`
  87. })
  88. },
  89. fail: (rej) => {
  90. console.log('扫描失败', rej)
  91. }
  92. })
  93. // showScanner.value = true
  94. // await nextTick()
  95. // startCamera()
  96. }
  97. const startCamera = async () => {
  98. try {
  99. stream = await navigator.mediaDevices.getUserMedia({
  100. video: { facingMode: 'environment' }
  101. })
  102. const video = videoRef.value
  103. if (video) {
  104. video.srcObject = stream
  105. video.play()
  106. scanQRCode()
  107. }
  108. } catch (error) {
  109. console.error('摄像头启动失败', error)
  110. uni.showToast({
  111. title: '摄像头启动失败',
  112. icon: 'none'
  113. })
  114. }
  115. }
  116. const scanQRCode = () => {
  117. const video = videoRef.value
  118. const canvas = canvasRef.value
  119. if (!video || !canvas) return
  120. const ctx = canvas.getContext('2d')
  121. const scan = () => {
  122. if (video.readyState === video.HAVE_ENOUGH_DATA) {
  123. canvas.width = video.videoWidth
  124. canvas.height = video.videoHeight
  125. ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
  126. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
  127. const code = jsQR(imageData.data, imageData.width, imageData.height)
  128. if (code) {
  129. onDecode(code.data)
  130. return
  131. }
  132. }
  133. animationId = requestAnimationFrame(scan)
  134. }
  135. scan()
  136. }
  137. const onDecode = (result) => {
  138. closeScanner()
  139. console.log('扫码结果:', result)
  140. }
  141. const closeScanner = () => {
  142. if (animationId) {
  143. cancelAnimationFrame(animationId)
  144. animationId = null
  145. }
  146. if (stream) {
  147. stream.getTracks().forEach(track => track.stop())
  148. stream = null
  149. }
  150. showScanner.value = false
  151. }
  152. </script>
  153. <style lang="scss" scoped>
  154. .type-list {
  155. display: flex;
  156. flex-direction: column;
  157. gap: 20rpx;
  158. }
  159. .type-card {
  160. background: #fff;
  161. border-radius: 20rpx;
  162. display: flex;
  163. align-items: center;
  164. justify-content: center;
  165. padding: 50rpx 30rpx;
  166. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  167. position: relative;
  168. }
  169. .type-name {
  170. font-size: 32rpx;
  171. font-weight: bold;
  172. color: #333;
  173. flex: 1;
  174. text-align: center;
  175. }
  176. .type-card :deep(.van-icon) {
  177. position: absolute;
  178. right: 30rpx;
  179. }
  180. .scanner-popup {
  181. width: 100%;
  182. height: 100%;
  183. display: flex;
  184. flex-direction: column;
  185. background: #000;
  186. }
  187. .scanner-header {
  188. display: flex;
  189. justify-content: space-between;
  190. align-items: center;
  191. padding: 20rpx;
  192. background: #fff;
  193. }
  194. .scanner-body {
  195. flex: 1;
  196. display: flex;
  197. align-items: center;
  198. justify-content: center;
  199. overflow: hidden;
  200. }
  201. .scanner-video {
  202. width: 100%;
  203. height: 100%;
  204. object-fit: cover;
  205. }
  206. </style>