Forráskód Böngészése

🐞 fix:增加扫码

jzy 3 hete
szülő
commit
1d6a6bfb20

+ 200 - 0
components/easy-scancode/easy-scancode.vue

@@ -0,0 +1,200 @@
+<template>
+  <view class="easy-scancode" v-if="show">
+    <view id="reader"></view>
+    <!-- 扫码提示区域 -->
+    <view class="scan-tip-overlay">
+      <view class="scan-tip"> 请将二维码放入框内 </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { Html5Qrcode } from "html5-qrcode";
+export default {
+  name: "easy-scancode",
+  data() {
+    return {
+      options: {
+        success: () => {},
+        fail: () => {},
+      },
+      show: false,
+      html5QrCode: null,
+    };
+  },
+  methods: {
+    start(options) {
+      this.options = options;
+      // #ifdef H5
+      // 检查摄像头权限
+      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+        navigator.mediaDevices
+          .getUserMedia({
+            video: true,
+          })
+          .then((stream) => {
+            // 获得权限后关闭流
+            stream.getTracks().forEach((track) => track.stop());
+            this.show = true;
+            // 有权限后调用扫码
+            this.getCameras();
+          })
+          .catch((err) => {
+            // 用户拒绝了权限
+            uni.showToast({
+              title: "请允许使用摄像头权限!",
+              icon: "none",
+            });
+            this.options.fail("未获得摄像头权限");
+          });
+      } else {
+        uni.showToast({
+          title: "您的浏览器不支持访问摄像头",
+          icon: "none",
+        });
+        this.options.fail("浏览器不支持访问摄像头");
+      }
+      return;
+      // #endif
+      // 其他环境
+      uni.scanCode({
+        onlyFromCamera: true,
+        success: (res) => {
+          this.options.success(res.result, res);
+        },
+        fail: (rej) => {
+          this.options.fail("扫码失败");
+        },
+      });
+    },
+    stop() {
+      this.show = false;
+      let that = this;
+      this.html5QrCode
+        .stop()
+        .then((ignore) => {
+          // 二维码扫描已停止
+          console.log("二维码扫描已停止");
+        })
+        .catch((err) => {
+          // 停止失败,处理错误
+          console.log("无法停止扫描");
+        });
+    },
+    getCameras() {
+      let that = this;
+      Html5Qrcode.getCameras()
+        .then((devices) => {
+          // console.log(devices)
+          that.html5QrCode = new Html5Qrcode("reader");
+
+          let windowInfo = uni.getWindowInfo();
+          console.log(windowInfo);
+          const width = windowInfo.windowWidth;
+          const height = windowInfo.windowHeight;
+          // 横屏
+          const aspectRatio = width / height;
+          // 竖屏
+          const reverseAspectRatio = height / width;
+
+          const mobileAspectRatio =
+            reverseAspectRatio > 1.5
+              ? reverseAspectRatio + (reverseAspectRatio * 12) / 100
+              : reverseAspectRatio;
+
+          if (devices && devices.length) {
+            let cameraId = "";
+            if (devices.length > 1) {
+              cameraId = devices[1].id;
+            } else {
+              cameraId = devices[0].id;
+            }
+            // 计算二维码扫描框大小 - 使用宽度/高度中较小的值来确保方框适合屏幕
+            // 并在扫描区域周围留出一些边距
+            const qrboxSize = Math.min(width, height) * 0.7;
+            console.log(qrboxSize);
+            // 使用这个来开始扫描
+            that.html5QrCode
+              .start(
+                {
+                  facingMode: "environment",
+                },
+                {
+                  fps: 10, // 可选,二维码扫描的每秒帧数
+                  aspectRatio: aspectRatio,
+                  qrbox: { width: qrboxSize, height: qrboxSize }, // 可选,如果你想要边界框UI
+                  videoConstraints: {
+                    facingMode: "environment",
+                    aspectRatio: width < 600 ? mobileAspectRatio : aspectRatio,
+                  },
+                },
+                (decodedText, decodedResult) => {
+                  // 当码被读取时执行操作
+                  console.log("decodedText", decodedText);
+                  that.options.success(decodedText, decodedResult);
+                  that.stop();
+                },
+                (errorMessage) => {
+                  // 解析错误,忽略它
+                  // console.log('解析错误,忽略它', errorMessage)
+                }
+              )
+              .catch((err) => {
+                // 启动失败,处理它
+                console.log("启动失败,需要处理");
+                that.options.fail(err);
+                this.show = false;
+              });
+          }
+        })
+        .catch((err) => {
+          // 处理错误
+          console.log(err);
+          that.options.fail(err);
+          this.show = false;
+        });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.easy-scancode {
+  width: 100vw;
+  height: 100vh;
+  position: fixed;
+  z-index: 1000;
+  top: 0;
+  left: 0;
+  background-color: #000;
+}
+
+#reader {
+  top: 50%;
+  left: 0;
+  transform: translateY(-50%);
+}
+
+.scan-tip-overlay {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 10000;
+	pointer-events: none; /* 不阻挡摄像头画面 */
+}
+
+.scan-tip {
+	position: absolute;
+	top: 74%;
+	left: 50%;
+	transform: translateX(-50%);
+	color: #fff;
+	font-size: 16px;
+	text-align: center;
+	z-index: 10001;
+	background-color: rgba(0, 0, 0, 0.5);
+	padding: 10px 20px;
+	border-radius: 5px;
+}
+</style>

+ 1 - 1
package.json

@@ -6,7 +6,7 @@
 	"dependencies": {
 		"async-validator": "^4.0.2",
 		"dayjs": "^1.10.6",
-		"jsqr": "^1.4.0",
+		"html5-qrcode": "^2.3.8",
 		"vant": "^4.9.22"
 	},
 	"devDependencies": {

+ 32 - 17
pages/index/input-method-select.vue

@@ -14,7 +14,7 @@
 					学校:{{ schoolName }}
 				</view>
 			</view>
-			
+
 			<view class="tw-px-[30rpx] tw-mt-[40rpx] tw-pb-[100rpx]">
 				<view class="type-list">
 					<view class="type-card" @click="handleManualInput">
@@ -28,7 +28,7 @@
 				</view>
 			</view>
 		</view>
-		
+
 		<van-popup v-model:show="showScanner" position="center" :style="{ width: '90%', height: '80%' }">
 			<view class="scanner-popup">
 				<view class="scanner-header">
@@ -42,12 +42,15 @@
 			</view>
 		</van-popup>
 	</view>
+	<scan-code ref="scan"></scan-code>
 </template>
 
 <script setup>
 import config from '@/utils/config'
 import { getSchoolInfo } from '@/services/common'
-import jsQR from 'jsqr'
+// import jsQR from 'jsqr'
+import scanCode from '@/components/easy-scancode/easy-scancode'
+
 
 const STORAGE_KEY = 'eye_examine_user_info'
 const schoolName = ref('')
@@ -57,12 +60,14 @@ const canvasRef = ref(null)
 let stream = null
 let animationId = null
 
+const scan = ref(null)
+
 onLoad(async () => {
 	const savedData = uni.getStorageSync(STORAGE_KEY)
 	if (savedData) {
 		const userInfo = JSON.parse(savedData)
 		const schoolId = userInfo.schoolId || ''
-		
+
 		if (schoolId) {
 			try {
 				const res = await getSchoolInfo(schoolId)
@@ -83,15 +88,27 @@ const handleManualInput = () => {
 }
 
 const handleScanInput = async () => {
-	showScanner.value = true
-	await nextTick()
-	startCamera()
+	scan.value.start({
+		success: (val, res) => {
+			//val是扫描到的二维码内容
+			console.log('扫描成功', val, res)
+			uni.navigateTo({
+				url: `/pages/index/select-input-type?studentId=${val}`
+			})
+		},
+		fail: (rej) => {
+			console.log('扫描失败', rej)
+		}
+	})
+	// showScanner.value = true
+	// await nextTick()
+	// startCamera()
 }
 
 const startCamera = async () => {
 	try {
-		stream = await navigator.mediaDevices.getUserMedia({ 
-			video: { facingMode: 'environment' } 
+		stream = await navigator.mediaDevices.getUserMedia({
+			video: { facingMode: 'environment' }
 		})
 		const video = videoRef.value
 		if (video) {
@@ -111,20 +128,20 @@ const startCamera = async () => {
 const scanQRCode = () => {
 	const video = videoRef.value
 	const canvas = canvasRef.value
-	
+
 	if (!video || !canvas) return
-	
+
 	const ctx = canvas.getContext('2d')
-	
+
 	const scan = () => {
 		if (video.readyState === video.HAVE_ENOUGH_DATA) {
 			canvas.width = video.videoWidth
 			canvas.height = video.videoHeight
 			ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
-			
+
 			const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
 			const code = jsQR(imageData.data, imageData.width, imageData.height)
-			
+
 			if (code) {
 				onDecode(code.data)
 				return
@@ -132,7 +149,7 @@ const scanQRCode = () => {
 		}
 		animationId = requestAnimationFrame(scan)
 	}
-	
+
 	scan()
 }
 
@@ -214,6 +231,4 @@ const closeScanner = () => {
 	height: 100%;
 	object-fit: cover;
 }
-
-
 </style>

+ 55 - 19
pages/index/select-input-type.vue

@@ -28,6 +28,9 @@
 </template>
 
 <script setup>
+import { getStudentInfo } from '@/services/common'
+
+
 const studentName = ref('')
 const studentIdCard = ref('')
 const visionStatus = ref(0)
@@ -35,25 +38,58 @@ const refractionStatus = ref(0)
 const params = ref({})
 
 onLoad((options) => {
-	studentName.value = decodeURIComponent(options.studentName || '')
-	studentIdCard.value = decodeURIComponent(options.studentIdCard || '')
-	visionStatus.value = Number(options.visionStatus || 0)
-	refractionStatus.value = Number(options.refractionStatus || 0)
-	
-	params.value = {
-		studentId: options.studentId,
-		studentName: options.studentName,
-		studentGender: options.studentGender,
-		studentIdCard: options.studentIdCard,
-		classId: options.classId,
-		className: options.className,
-		grade: options.grade,
-		visionDataId: options.visionDataId || '',
-		schoolName: options.schoolName,
-		userName: options.userName,
-		userPhone: options.userPhone,
-		userOrg: options.userOrg
+	if (options.studentId) {
+		getStudentInfo(
+			options.studentId
+		).then((res) => {
+			studentName.value = res.data.name
+			studentIdCard.value = res.data.id_card
+			visionStatus.value = 0
+			refractionStatus.value = 0
+			params.value = res.data
+
+			params.value = {
+				studentId: res.data.id,
+				studentName: res.data.name,
+				studentGender: res.data.gender,
+				studentIdCard: res.data.id_card,
+				classId: res.data.class_id,
+				className: res.data.class_name,
+				grade: res.data.grade,
+				visionDataId: res.data.vision_data_id || '',
+				schoolName: res.data.school_name || '',
+				userName: res.data.name || '',
+				userPhone:'17766664444',
+				// userOrg: options.userOrg
+			}
+		})
+	}
+
+	if (options.studentName) {
+		studentName.value = decodeURIComponent(options.studentName || '')
+		studentIdCard.value = decodeURIComponent(options.studentIdCard || '')
+		visionStatus.value = Number(options.visionStatus || 0)
+		refractionStatus.value = Number(options.refractionStatus || 0)
+
+		params.value = {
+			studentId: options.studentId,
+			studentName: options.studentName,
+			studentGender: options.studentGender,
+			studentIdCard: options.studentIdCard,
+			classId: options.classId,
+			className: options.className,
+			grade: options.grade,
+			visionDataId: options.visionDataId || '',
+			schoolName: options.schoolName,
+			userName: options.userName,
+			userPhone: options.userPhone,
+			userOrg: options.userOrg
+		}
 	}
+
+
+
+
 })
 
 const selectType = (type) => {
@@ -64,7 +100,7 @@ const selectType = (type) => {
 const onBack = () => {
 	uni.navigateBack({
 		delta: 1,
-		success: () => {},
+		success: () => { },
 		fail: () => {
 			uni.switchTab({
 				url: '/pages/index/index'

+ 25 - 7
pages/index/vision-input.vue

@@ -67,11 +67,14 @@
 			</view>
 		</van-popup>
 	</view>
+	<scan-code ref="scan"></scan-code>
 </template>
 
 <script setup>
 import { inputVisionData, inputRefractionData } from '@/services/common'
-import jsQR from 'jsqr'
+// import jsQR from 'jsqr'
+
+import scanCode from '@/components/easy-scancode/easy-scancode'
 
 const STORAGE_KEY = 'eye_examine_user_info'
 const userInfo = ref({})
@@ -119,10 +122,25 @@ const onBack = () => {
 	})
 }
 
+const scan = ref(null)
+
 const handleScan = async () => {
-	showScanner.value = true
-	await nextTick()
-	startCamera()
+	// showScanner.value = true
+	// await nextTick()
+	// startCamera()
+	scan.value.start({
+		success: (val, res) => {
+			//val是扫描到的二维码内容
+			console.log('扫描成功', val, res)
+			//关闭当前页面并跳转到选择录入类型页面
+			uni.redirectTo({
+				url: `/pages/index/select-input-type?studentId=${val}`
+			})
+		},
+		fail: (rej) => {
+			console.log('扫描失败', rej)
+		}
+	})
 }
 
 const startCamera = async () => {
@@ -226,9 +244,9 @@ const handleSave = async () => {
 					icon: 'success',
 					duration: 1000
 				})
-				setTimeout(() => {
-					uni.navigateBack({ delta: 2 })
-				}, 1000)
+				// setTimeout(() => {
+				// 	uni.navigateBack({ delta: 2 })
+				// }, 1000)
 			}
 		} catch (error) {
 			console.error('保存视力数据失败', error)

+ 8 - 0
pnpm-lock.yaml

@@ -14,6 +14,9 @@ importers:
       dayjs:
         specifier: ^1.10.6
         version: 1.11.5
+      html5-qrcode:
+        specifier: ^2.3.8
+        version: 2.3.8
       vant:
         specifier: ^4.9.22
         version: 4.9.22(vue@3.5.30)
@@ -527,6 +530,9 @@ packages:
     resolution: {integrity: sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg==}
     engines: {node: ^18.17.0 || >=20.5.0}
 
+  html5-qrcode@2.3.8:
+    resolution: {integrity: sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==}
+
   htmlparser2@9.1.0:
     resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
 
@@ -1667,6 +1673,8 @@ snapshots:
     dependencies:
       lru-cache: 10.4.3
 
+  html5-qrcode@2.3.8: {}
+
   htmlparser2@9.1.0:
     dependencies:
       domelementtype: 2.3.0

+ 7 - 0
services/common.js

@@ -86,6 +86,13 @@ export function getSchoolInfo(schoolId) {
 	return http.get(`schools/${schoolId}`, {}, { isAuth: false })
 }
 
+//获取学生详情
+export function getStudentInfo(studentId) {
+	return http.get(`students/${studentId}`, {}, { isAuth: false })
+}
+
+
+
 export function getSchoolGrades(schoolId) {
 	return http.get(`schools/${schoolId}/grades`, {}, { isAuth: false })
 }

+ 2 - 2
utils/config.js

@@ -1,5 +1,5 @@
-// const baseUrl = process.env.NODE_ENV === 'development' ? 'http://10.0.0.85:8080/' : 'http://10.0.0.85:8080/'
-const baseUrl = process.env.NODE_ENV === 'development' ? 'http://fs.towantto.com:20201/' : 'http://fs.towantto.com:20201/'
+const baseUrl = process.env.NODE_ENV === 'development' ? 'http://10.0.0.198:8080/' : 'http://10.0.0.85:8080/'
+// const baseUrl = process.env.NODE_ENV === 'development' ? 'http://fs.towantto.com:20201/' : 'http://fs.towantto.com:20201/'
 
 const ossPathPerfixs = 'https://fskj-res.oss-cn-zhangjiakou.aliyuncs.com/zxx/images'