From cad424a66fb99ef516534a562505b97c54ce2c8b Mon Sep 17 00:00:00 2001
From: zhangchen <1652267879@qq.com>
Date: 星期一, 01 九月 2025 15:53:45 +0800
Subject: [PATCH] Merge branch 'ID1766-添加推送登录功能' into test
---
src/components/QrScanner/index.vue | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 222 insertions(+), 0 deletions(-)
diff --git a/src/components/QrScanner/index.vue b/src/components/QrScanner/index.vue
new file mode 100644
index 0000000..a3f7882
--- /dev/null
+++ b/src/components/QrScanner/index.vue
@@ -0,0 +1,222 @@
+<template>
+ <div v-if="show" class="qr-scanner">
+ <div class="qr-header">
+ <i class="iconfont icon-dituweizhixinxi_chahao" @click="close"></i>
+ <div class="title">扫一扫</div>
+ </div>
+ <div class="qr-video-box">
+ <video ref="videoRef" autoplay playsinline muted class="qr-video"></video>
+ <!-- 四个角标 -->
+ <span class="corner tl"></span>
+ <span class="corner tr"></span>
+ <span class="corner bl"></span>
+ <span class="corner br"></span>
+
+ <!-- 扫描线 -->
+ <span class="scan-line"></span>
+ </div>
+ <!-- <p v-if="code">识别结果:{{ code }}</p>
+ <button @click="startScan">开始扫描</button>
+ <button @click="stopScan" v-if="isScanning">停止扫描</button> -->
+ </div>
+</template>
+
+<script lang="ts">
+import { ref, onBeforeUnmount } from "vue";
+import { BrowserQRCodeReader, IScannerControls } from "@zxing/browser";
+import { ElMessage } from "element-plus";
+
+export default {
+ name: "QrScanner",
+ emits: ["scan"],
+ setup(props, { emit }) {
+ const show = ref(false);
+ const videoRef = ref<HTMLVideoElement | null>(null);
+ const code = ref("");
+ const isScanning = ref(false);
+ let codeReader: BrowserQRCodeReader | null = null;
+ let controls: IScannerControls | null = null;
+
+ const startScan = async () => {
+ if (isScanning.value) return;
+ isScanning.value = true;
+
+ codeReader = new BrowserQRCodeReader();
+
+ try {
+ const devices = await BrowserQRCodeReader.listVideoInputDevices();
+ if (devices.length === 0) {
+ console.error("扫码失败:", "未找到摄像头");
+ ElMessage({ message: "未找到摄像头", type: "warning" });
+ return;
+ }
+
+ // 选择后置摄像头(mobile label 中常带 'back' 或 'rear')
+ let rearCamera = devices.find((d) => /back|rear|后/i.test(d.label));
+
+ const selectedDeviceId = rearCamera
+ ? rearCamera.deviceId
+ : devices[0].deviceId;
+
+ controls = await codeReader.decodeFromVideoDevice(
+ selectedDeviceId,
+ videoRef.value!,
+ (res) => {
+ if (res) {
+ code.value = res.getText();
+ console.log("code: ", code.value);
+ emit("scan", { code: code.value, success: true });
+ // stopScan(); // 识别成功后停止扫描
+ close();
+ }
+ }
+ );
+ } catch (err) {
+ console.error("扫码失败:", err);
+ ElMessage({
+ message: "无法访问摄像头,请检查权限或使用 HTTPS",
+ type: "warning",
+ });
+ }
+ };
+
+ const stopScan = () => {
+ isScanning.value = false;
+ controls?.stop();
+ controls = null;
+ };
+
+ const close = () => {
+ stopScan();
+ show.value = false;
+ emit("scan", { code: "", success: false });
+ };
+
+ const open = () => {
+ show.value = true;
+ startScan();
+ };
+
+ onBeforeUnmount(() => {
+ stopScan();
+ });
+
+ return {
+ show,
+ videoRef,
+ code,
+ isScanning,
+ startScan,
+ stopScan,
+ close,
+ open,
+ };
+ },
+};
+</script>
+
+<style lang="less" scoped>
+.qr-scanner {
+ position: fixed;
+ width: 100vw;
+ height: 100vh;
+ top: 0;
+ left: 0;
+ z-index: 9999;
+ background: rgba(0, 0, 0, 0.8);
+ .qr-header {
+ position: relative;
+ padding: 8px 5px;
+ color: #fff;
+ .title {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ font-size: 6px;
+ font-weight: bold;
+ }
+ .iconfont {
+ position: absolute;
+ left: 5px;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 9px;
+ }
+ }
+ .qr-video-box {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ width: 130px;
+ height: 130px;
+ background: rgba(0, 0, 0, 0);
+ .qr-video {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ /* 角标通用样式 */
+ .corner {
+ position: absolute;
+ width: 26px; // 角标边长
+ height: 26px;
+ border: 3px solid #409eff; // 角标颜色/粗细
+ }
+ .corner.tl {
+ top: 0;
+ left: 0;
+ border-right: none;
+ border-bottom: none;
+ }
+ .corner.tr {
+ top: 0;
+ right: 0;
+ border-left: none;
+ border-bottom: none;
+ }
+ .corner.bl {
+ bottom: 0;
+ left: 0;
+ border-right: none;
+ border-top: none;
+ }
+ .corner.br {
+ bottom: 0;
+ right: 0;
+ border-left: none;
+ border-top: none;
+ }
+
+ /* 扫描线 */
+ .scan-line {
+ position: absolute;
+ left: 0;
+ right: 0;
+ height: 2px;
+ top: 0;
+ background: linear-gradient(
+ 90deg,
+ rgba(0, 0, 0, 0) 0%,
+ rgba(64, 158, 255, 0.7) 15%,
+ rgba(64, 158, 255, 0.95) 50%,
+ rgba(64, 158, 255, 0.7) 85%,
+ rgba(0, 0, 0, 0) 100%
+ );
+ box-shadow: 0 0 8px rgba(64, 158, 255, 0.8),
+ 0 0 16px rgba(64, 158, 255, 0.5);
+ animation: scan-move 2.2s linear infinite alternate;
+ will-change: transform;
+ }
+ }
+}
+@keyframes scan-move {
+ 0% {
+ transform: translateY(0);
+ }
+ 100% {
+ transform: translateY(128px);
+ }
+}
+</style>
--
Gitblit v1.8.0