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 +++++++++++++++++++++++++++++++++++++
vite.config.ts | 4
src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue | 22 +++
package-lock.json | 33 ++++-
src/assets/font/iconfont.woff2 | 0
package.json | 1
src/assets/css/iconfont.css | 22 +++
src/assets/font/iconfont.ttf | 0
src/assets/font/iconfont.woff | 0
src/main.ts | 6
10 files changed, 300 insertions(+), 10 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 0ad23e7..f103eff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@vant/icons": "^3.0.2",
+ "@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3",
"axios": "^1.9.0",
"dayjs": "^1.11.13",
@@ -103,9 +104,9 @@
}
},
"node_modules/@element-plus/icons-vue": {
- "version": "2.3.1",
- "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
- "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+ "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"
@@ -1213,6 +1214,18 @@
"@vue/composition-api": {
"optional": true
}
+ }
+ },
+ "node_modules/@zxing/browser": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.1.5.tgz",
+ "integrity": "sha512-4Lmrn/il4+UNb87Gk8h1iWnhj39TASEHpd91CwwSJtY5u+wa0iH9qS0wNLAWbNVYXR66WmT5uiMhZ7oVTrKfxw==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "@zxing/text-encoding": "^0.9.0"
+ },
+ "peerDependencies": {
+ "@zxing/library": "^0.21.0"
}
},
"node_modules/@zxing/library": {
@@ -5375,9 +5388,9 @@
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA=="
},
"@element-plus/icons-vue": {
- "version": "2.3.1",
- "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
- "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+ "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"requires": {}
},
"@esbuild/aix-ppc64": {
@@ -6034,6 +6047,14 @@
}
}
},
+ "@zxing/browser": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.1.5.tgz",
+ "integrity": "sha512-4Lmrn/il4+UNb87Gk8h1iWnhj39TASEHpd91CwwSJtY5u+wa0iH9qS0wNLAWbNVYXR66WmT5uiMhZ7oVTrKfxw==",
+ "requires": {
+ "@zxing/text-encoding": "^0.9.0"
+ }
+ },
"@zxing/library": {
"version": "0.21.3",
"resolved": "https://registry.npmmirror.com/@zxing/library/-/library-0.21.3.tgz",
diff --git a/package.json b/package.json
index 1c21545..7bf01ac 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
},
"dependencies": {
"@vant/icons": "^3.0.2",
+ "@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3",
"axios": "^1.9.0",
"dayjs": "^1.11.13",
diff --git a/src/assets/css/iconfont.css b/src/assets/css/iconfont.css
new file mode 100644
index 0000000..ada5478
--- /dev/null
+++ b/src/assets/css/iconfont.css
@@ -0,0 +1,22 @@
+@font-face {
+ font-family: "iconfont"; /* Project id 5011061 */
+ src: url('//at.alicdn.com/t/c/font_5011061_crebeujq91a.woff2?t=1756705233110') format('woff2'),
+ url('//at.alicdn.com/t/c/font_5011061_crebeujq91a.woff?t=1756705233110') format('woff'),
+ url('//at.alicdn.com/t/c/font_5011061_crebeujq91a.ttf?t=1756705233110') format('truetype');
+}
+
+.iconfont {
+ font-family: "iconfont" !important;
+ font-size: 16px;
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-dituweizhixinxi_chahao:before {
+ content: "\e600";
+}
+
+.icon-saoma:before {
+ content: "\e749";
+}
diff --git a/src/assets/font/iconfont.ttf b/src/assets/font/iconfont.ttf
new file mode 100644
index 0000000..0ed3ab5
--- /dev/null
+++ b/src/assets/font/iconfont.ttf
Binary files differ
diff --git a/src/assets/font/iconfont.woff b/src/assets/font/iconfont.woff
new file mode 100644
index 0000000..71bb5d9
--- /dev/null
+++ b/src/assets/font/iconfont.woff
Binary files differ
diff --git a/src/assets/font/iconfont.woff2 b/src/assets/font/iconfont.woff2
new file mode 100644
index 0000000..66aa294
--- /dev/null
+++ b/src/assets/font/iconfont.woff2
Binary files differ
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>
diff --git a/src/main.ts b/src/main.ts
index 9147bbe..267b11f 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -9,11 +9,13 @@
import App from './App.vue'
import VConsole from 'vconsole'
import { createPinia } from 'pinia'
-
+import '@/assets/css/iconfont.css'
if (import.meta.env.VITE_ENV === 'development') {
// 如果需要在手机平板上打开控制台,安装一个这个
const vConsole = new VConsole()
}
const pinia = createPinia()
-createApp(App).use(router).use(pinia).use(ElementPlus).use(Vant).mount('#app')
+const app = createApp(App)
+
+app.use(router).use(pinia).use(ElementPlus).use(Vant).mount('#app')
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue
index 9394352..e2817cc 100644
--- a/src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue
@@ -23,7 +23,7 @@
</template>
<div class="setting-device-dialog-content">
<div class="content-row1">
- <div class="row1-label">设备编号</div>
+ <div class="row1-label" @click="openQrScanner">设备编号<i class="iconfont icon-saoma"></i></div>
<div class="row1-inp-box">
<input
v-model="devcieCode"
@@ -57,6 +57,9 @@
<div class="my-button refresh" @click="handleRefresh">检查更新</div>
</template>
</el-dialog>
+ <!-- 长识别二维码 -->
+ <QrScanner ref="QrScannerRef" @scan="onQrScan" />
+
</div>
</template>
@@ -72,8 +75,11 @@
import closeImg from "@/img/close.png";
import uploadImg from "@/img/upload.png";
import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+import QrScanner from "@/components/QrScanner/index.vue";
const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+const QrScannerRef = ref(null);
const isShow = ref(false);
const isUploading = ref(false);
@@ -146,6 +152,16 @@
const handleRefresh = () => {
window.location.reload();
ElMessage.success('已更新至最新版本')
+};
+
+const openQrScanner = () => {
+ QrScannerRef.value?.open();
+};
+
+const onQrScan = ({ success, code}) => {
+ if (!success) return;
+ devcieCode.value = code;
+ ElMessage.success("识别成功");
};
defineExpose({
@@ -227,6 +243,10 @@
line-height: 16px;
color: #ffffff;
font-style: normal;
+ .iconfont {
+ margin-left: 2px;
+ font-size: 9px;
+ }
}
.row1-inp-box {
flex: 1;
diff --git a/vite.config.ts b/vite.config.ts
index 97f5764..c8b97de 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,3 +1,4 @@
+// @ts-nocheck
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
@@ -10,7 +11,8 @@
server: {
port: 3034, // 指定端口号为 3000
strictPort: true, // 如果端口被占用,则抛出错误而不是尝试下一个可用端口
- host: true // 允许通过ip访问,要不然平板测试不了
+ host: true, // 允许通过ip访问,要不然平板测试不了
+ // https: true
},
resolve: {
alias: {
--
Gitblit v1.8.0