Browse Source

1.实现调用企业微信扫一扫

2.开发基于zxing/library的既支持扫二维码也支持扫条码的扫码组件
3.因为自研zxing组件小米手机中的企业微信只能获取到前置摄像头,实现按需加载功能,企业微信环境调用企业微信扫一扫,其它环境调用自研zxing扫码组件
lwx_v8
liwenxuan 9 months ago
parent
commit
e11ff85be8
  1. 2
      index.html
  2. 49
      package-lock.json
  3. 5
      package.json
  4. 62
      src/components/lowCode/formItem.vue
  5. 267
      src/views/home/scanQrCode.vue
  6. 368
      src/views/home/scanQrCode1.vue
  7. 497
      src/views/home/scanQrCodeInput.vue
  8. 195
      src/views/home/scanQrCodeInput1.vue

2
index.html

@ -6,11 +6,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta content="yes" name="apple-mobile-web-app-capable"> <meta content="yes" name="apple-mobile-web-app-capable">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>数通智联化工云平台</title> <title>数通智联化工云平台</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
</body> </body>
</html> </html>

49
package-lock.json

@ -9,6 +9,8 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3",
"axios": "^1.7.7", "axios": "^1.7.7",
"element-plus": "^2.8.6", "element-plus": "^2.8.6",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
@ -29,7 +31,8 @@
"vue-qrcode-reader": "^5.7.0", "vue-qrcode-reader": "^5.7.0",
"vue-router": "^4.4.5", "vue-router": "^4.4.5",
"vue3-pdf-embed": "^1.1.7", "vue3-pdf-embed": "^1.1.7",
"vue3-pdfjs": "^0.1.6" "vue3-pdfjs": "^0.1.6",
"weixin-js-sdk": "^1.6.5"
}, },
"devDependencies": { "devDependencies": {
"@types/js-beautify": "^1.14.3", "@types/js-beautify": "^1.14.3",
@ -1780,6 +1783,37 @@
} }
} }
}, },
"node_modules/@zxing/browser": {
"version": "0.1.5",
"resolved": "https://registry.npmmirror.com/@zxing/browser/-/browser-0.1.5.tgz",
"integrity": "sha512-4Lmrn/il4+UNb87Gk8h1iWnhj39TASEHpd91CwwSJtY5u+wa0iH9qS0wNLAWbNVYXR66WmT5uiMhZ7oVTrKfxw==",
"optionalDependencies": {
"@zxing/text-encoding": "^0.9.0"
},
"peerDependencies": {
"@zxing/library": "^0.21.0"
}
},
"node_modules/@zxing/library": {
"version": "0.21.3",
"resolved": "https://registry.npmmirror.com/@zxing/library/-/library-0.21.3.tgz",
"integrity": "sha512-hZHqFe2JyH/ZxviJZosZjV+2s6EDSY0O24R+FQmlWZBZXP9IqMo7S3nb3+2LBWxodJQkSurdQGnqE7KXqrYgow==",
"dependencies": {
"ts-custom-error": "^3.2.1"
},
"engines": {
"node": ">= 10.4.0"
},
"optionalDependencies": {
"@zxing/text-encoding": "~0.9.0"
}
},
"node_modules/@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmmirror.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"optional": true
},
"node_modules/abbrev": { "node_modules/abbrev": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-2.0.0.tgz",
@ -7038,6 +7072,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/ts-custom-error": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ts-md5": { "node_modules/ts-md5": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmmirror.com/ts-md5/-/ts-md5-1.3.1.tgz", "resolved": "https://registry.npmmirror.com/ts-md5/-/ts-md5-1.3.1.tgz",
@ -7823,6 +7865,11 @@
"npm": ">=3.10.0" "npm": ">=3.10.0"
} }
}, },
"node_modules/weixin-js-sdk": {
"version": "1.6.5",
"resolved": "https://registry.npmmirror.com/weixin-js-sdk/-/weixin-js-sdk-1.6.5.tgz",
"integrity": "sha512-Gph1WAWB2YN/lMOFB/ymb+hbU/wYazzJgu6PMMktCy9cSCeW5wA6Zwt0dpahJbJ+RJEwtTv2x9iIu0U4enuVSQ=="
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",

5
package.json

@ -11,6 +11,8 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3",
"axios": "^1.7.7", "axios": "^1.7.7",
"element-plus": "^2.8.6", "element-plus": "^2.8.6",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
@ -31,7 +33,8 @@
"vue-qrcode-reader": "^5.7.0", "vue-qrcode-reader": "^5.7.0",
"vue-router": "^4.4.5", "vue-router": "^4.4.5",
"vue3-pdf-embed": "^1.1.7", "vue3-pdf-embed": "^1.1.7",
"vue3-pdfjs": "^0.1.6" "vue3-pdfjs": "^0.1.6",
"weixin-js-sdk": "^1.6.5"
}, },
"devDependencies": { "devDependencies": {
"@types/js-beautify": "^1.14.3", "@types/js-beautify": "^1.14.3",

62
src/components/lowCode/formItem.vue

@ -16,6 +16,8 @@ import {
} from '@/api/lowCode/utils'; } from '@/api/lowCode/utils';
import { AnalysisCss,AnalysisInputCss } from '@/api/common/cssInfo' import { AnalysisCss,AnalysisInputCss } from '@/api/common/cssInfo'
import validate from "@/api/lowCode/form/validate" import validate from "@/api/lowCode/form/validate"
import wx from 'weixin-js-sdk'
import request from '@/utils/axios/index'
import { Md5 } from 'ts-md5' import { Md5 } from 'ts-md5'
import { debounce } from '@/utils/lowCode/item/index' import { debounce } from '@/utils/lowCode/item/index'
@ -123,6 +125,62 @@ const handleScanResult = (val: any) => {
updateModel(val) updateModel(val)
qrScaning.value = false qrScaning.value = false
} }
const isQywx = ref(false)
onMounted(()=>{
if (/wxwork\//.test(navigator.userAgent)) {
isQywx.value = true
}
})
const handelScan = ()=>{
if(isQywx.value == true){//
initWxConfig()
}else{//
qrScaning.value=true
}
}
function getQyWxSignature() {
let url = window.location.href.split("#")[0];
var req = {url:"1"};
req.url = url;
return request({
url: "/javasys/lowCode/QrCode/QyWxSignature",
method: "get",
data: req,
});
}
async function initWxConfig() {
getQyWxSignature().then(({ data }) => {
//alert(data.corpid)
wx.config({
beta: true,// wx.invokejsapi
debug: false, // ,apialertpclogpc
appId: data.corpid, // corpID
timestamp: data.timestamp, //
nonceStr: data.noncestr, //
signature: data.jsapi_ticket_enterprises,// -JS-SDK使
jsApiList: ['scanQRCode'] // 使JS
});
startScan()
});
}
//
function startScan() {
wx.ready(() => {
wx.scanQRCode({
needResult: 1, //
scanType: ['qrCode','barCode'], //
success: (res) => {
const result = res.resultStr;
handleScanResult(result);
},
fail: (err) => {
alert(err.errMsg)
}
});
});
}
//liwenxuan 20250217 end //liwenxuan 20250217 end
// //
const value = computed({ const value = computed({
@ -579,7 +637,7 @@ const currentComponent = computed(() => {
<span v-else>{{ config.append }}</span> <span v-else>{{ config.append }}</span>
</template> </template>
<template #append> <template #append>
<div style="display: flex; justify-content: center; max-width: 20px;"><SvgIcon icon-class="scanQrCode" :size="'30px'" @click="qrScaning=true" /></div> <div style="display: flex; justify-content: center; max-width: 20px;"><SvgIcon icon-class="scanQrCode" :size="'30px'" @click="handelScan" /></div>
</template> </template>
<template #append v-if="config.append"> <template #append v-if="config.append">
@ -926,7 +984,7 @@ const currentComponent = computed(() => {
:with-header="false" :with-header="false"
size="100%" size="100%"
> >
<ScanQrCodeInput v-if="qrScaning" @update-qrScaning="handleQrScaning" @update-scanResult="handleScanResult"></ScanQrCodeInput> <ScanQrCodeInput v-if="qrScaning&&!isQywx" @update-qrScaning="handleQrScaning" @update-scanResult="handleScanResult"></ScanQrCodeInput>
</el-drawer> </el-drawer>
<!-- liwenxuan 二维码扫描录入组件抽屉 20250218 end --> <!-- liwenxuan 二维码扫描录入组件抽屉 20250218 end -->
</template> </template>

267
src/views/home/scanQrCode.vue

@ -1,41 +1,43 @@
<template> <template>
<div style="height: 100vh"> <div style="height: 100vh">
<qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%"> <qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%">
<div> <div>
<div class="qr-scanner"> <div class="qr-scanner">
<!--顶部左边的返回箭头--> <!--顶部左边的返回箭头-->
<div> <div>
<!-- <van-icon class="scanImg" @click="onClickLeft" name="arrow-left" /> --> <!-- <van-icon class="scanImg" @click="onClickLeft" name="arrow-left" /> -->
<!-- <el-button type="primary" @click="onClickLeft" ></el-button> --> <!-- <el-button type="primary" @click="onClickLeft" ></el-button> -->
<el-icon @click="onClickLeft" style="margin:15px;width: 20px;height: 20px;"><Close style="width: 20px;height: 20px;" /></el-icon> <el-icon @click="onClickLeft" style="margin:15px;width: 20px;height: 20px;">
<Close style="width: 20px;height: 20px;" />
</div> </el-icon>
<!--中间的扫码框-->
<div class="box"> </div>
<div class="line"></div> <!--中间的扫码框-->
<div class="angle"></div> <div class="box">
</div> <div class="line"></div>
<div class="txt"> <div class="angle"></div>
将二维码/条码放入框内即自动扫描 </div>
</div> <div class="txt">
</div> 将二维码/条码放入框内即自动扫描
</div> </div>
</qrcode-stream> </div>
</div> </div>
</template> </qrcode-stream>
<script setup lang="ts"> </div>
import {QrcodeStream} from "vue-qrcode-reader"; </template>
import {ref} from "vue"; <script setup lang="ts">
import { QrcodeStream } from "vue-qrcode-reader";
import { ref } from "vue";
//
const dataList = ref('')
const result = ref(true) //
const error = ref('') const dataList = ref('')
const result = ref(true)
const error = ref('')
//
const onDecode = (res: any) => {
//
const onDecode = (res: any) => {
dataList.value = res dataList.value = res
result.value = false result.value = false
//console.log('',dataList.value) //console.log('',dataList.value)
@ -44,141 +46,148 @@
window.location.href = dataList.value[0].rawValue; window.location.href = dataList.value[0].rawValue;
// //
// //
} }
// //
const onInit = async (promise: any) => { const onInit = async (promise: any) => {
console.log('初始化摄像头',promise) console.log('初始化摄像头', promise)
try { try {
await promise await promise
}catch (err: any) { } catch (err: any) {
if (err.name === 'NotAllowedError') { if (err.name === 'NotAllowedError') {
error.value = 'ERROR: 您需要授予相机访问权限'; error.value = 'ERROR: 您需要授予相机访问权限';
} else if (err.name === 'NotFoundError') { } else if (err.name === 'NotFoundError') {
error.value = 'ERROR: 这个设备上没有摄像头'; error.value = 'ERROR: 这个设备上没有摄像头';
} else if (err.name === 'NotSupportedError') { } else if (err.name === 'NotSupportedError') {
error.value = 'ERROR: 所需的安全上下文(HTTPS、本地主机)'; error.value = 'ERROR: 所需的安全上下文(HTTPS、本地主机)';
} else if (err.name === 'NotReadableError') { } else if (err.name === 'NotReadableError') {
error.value = 'ERROR: 相机被占用'; error.value = 'ERROR: 相机被占用';
} else if (err.name === 'OverconstrainedError') { } else if (err.name === 'OverconstrainedError') {
error.value = 'ERROR: 安装摄像头不合适'; error.value = 'ERROR: 安装摄像头不合适';
} else if (err.name === 'StreamApiNotSupportedError') { } else if (err.name === 'StreamApiNotSupportedError') {
error.value = 'ERROR: 此浏览器不支持流API'; error.value = 'ERROR: 此浏览器不支持流API';
} }
} }
} }
// //
const onClickLeft = () => { const onClickLeft = () => {
history.back(); history.back();
} }
</script> </script>
<style scoped> <style scoped>
:deep(i.van-badge__wrapper.van-icon.van-icon-arrow-left.scanImg){ :deep(i.van-badge__wrapper.van-icon.van-icon-arrow-left.scanImg) {
font-size: 40px; font-size: 40px;
color: white; color: white;
} }
.scanImg{
margin-top: 30px; .scanImg {
margin-left: 10px; margin-top: 30px;
margin-left: 10px;
} }
.error { .error {
font-weight: bold; font-weight: bold;
color: red; color: red;
} }
.cameraMessage { .cameraMessage {
width: 100%; width: 100%;
height: 60px; height: 60px;
} }
.qr-scanner { .qr-scanner {
background-size: 3rem 3rem; background-size: 3rem 3rem;
background-position: -1rem -1rem; background-position: -1rem -1rem;
width: 100%; width: 100%;
/* height: 100%; */ /* height: 100%; */
height: 100vh; height: 100vh;
/* height: 288px; */ /* height: 288px; */
position: relative; position: relative;
background-color: #1110; background-color: #1110;
} }
.qr-scanner .box { .qr-scanner .box {
width: 213px; width: 213px;
height: 213px; height: 213px;
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 40%; top: 40%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
overflow: hidden; overflow: hidden;
border: 1px solid #3aa5ff; border: 1px solid #3aa5ff;
} }
.qr-scanner .txt { .qr-scanner .txt {
width: 100%; width: 100%;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
/* color: #f9f9f9; */ /* color: #f9f9f9; */
margin: 0 auto; margin: 0 auto;
position: absolute; position: absolute;
top: 60%; top: 60%;
left: 0; left: 0;
} }
.qr-scanner .myQrcode { .qr-scanner .myQrcode {
text-align: center; text-align: center;
color: #3aa5ff; color: #3aa5ff;
} }
.qr-scanner .line { .qr-scanner .line {
height: calc(100% - 2px); height: calc(100% - 2px);
width: 100%; width: 100%;
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3aa5ff 211%); background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3aa5ff 211%);
border-bottom: 1px solid #3aa5ff; border-bottom: 1px solid #3aa5ff;
transform: translateY(-100%); transform: translateY(-100%);
animation: radar-beam 2s infinite alternate; animation: radar-beam 2s infinite alternate;
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99); animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
animation-delay: 1.4s; animation-delay: 1.4s;
} }
.qr-scanner .box:after, .qr-scanner .box:after,
.qr-scanner .box:before, .qr-scanner .box:before,
.qr-scanner .angle:after, .qr-scanner .angle:after,
.qr-scanner .angle:before { .qr-scanner .angle:before {
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
width: 3vw; width: 3vw;
height: 3vw; height: 3vw;
} }
.qr-scanner .box:after, .qr-scanner .box:after,
.qr-scanner .box:before { .qr-scanner .box:before {
top: 0; top: 0;
border-top-color: #3aa5ff; border-top-color: #3aa5ff;
} }
.qr-scanner .angle:after, .qr-scanner .angle:after,
.qr-scanner .angle:before { .qr-scanner .angle:before {
bottom: 0; bottom: 0;
border-bottom-color: #3aa5ff; border-bottom-color: #3aa5ff;
} }
.qr-scanner .box:before, .qr-scanner .box:before,
.qr-scanner .angle:before { .qr-scanner .angle:before {
left: 0; left: 0;
border-left-color: #3aa5ff; border-left-color: #3aa5ff;
} }
.qr-scanner .box:after, .qr-scanner .box:after,
.qr-scanner .angle:after { .qr-scanner .angle:after {
right: 0; right: 0;
border-right-color: #3aa5ff; border-right-color: #3aa5ff;
} }
@keyframes radar-beam { @keyframes radar-beam {
0% { 0% {
transform: translateY(-100%); transform: translateY(-100%);
} }
100% { 100% {
transform: translateY(0); transform: translateY(0);
} }
} }
</style> </style>

368
src/views/home/scanQrCode1.vue

@ -1,212 +1,184 @@
<template> <template>
<div class="scanCode"> <div style="height: 100vh">
<div class="container"> <qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%">
<div class="qrcode"> <div>
<div class="qr-scanner">
<!--顶部左边的返回箭头-->
<div class="btn"> <div>
<div class="left-back" style="float: left;"> <!-- <van-icon class="scanImg" @click="onClickLeft" name="arrow-left" /> -->
<!-- <el-button type="primary" @click="onClickLeft" ></el-button> -->
<el-icon @click="clickBack" style="border: white solid 0px;"> <el-icon @click="onClickLeft" style="margin:15px;width: 20px;height: 20px;"><Close style="width: 20px;height: 20px;" /></el-icon>
<Back />
</el-icon> </div>
</div> <!--中间的扫码框-->
<!-- <div class="right-file"> <div class="box">
<div class="line"></div>
<van-uploader <div class="angle"></div>
v-model="fileList" </div>
:preview-image="false" <div class="txt">
:after-read="dealSelectFiles" 将二维码/条码放入框内即自动扫描
>
<el-icon><Picture /></el-icon>
</van-uploader>
</div> -->
</div> </div>
<div id="reader"></div>
</div> </div>
</div> </div>
<!--<div class="btn"> </qrcode-stream>
<div class="left-back"> </div>
<el-icon @click="clickBack">
<Back />
</el-icon>
</div>
<div class="right-file">
<van-uploader
v-model="fileList"
:preview-image="false"
:after-read="dealSelectFiles"
>
<el-icon><Picture /></el-icon>
</van-uploader>
</div>
</div> -->
</div>
</template> </template>
<script setup lang="ts">
import {QrcodeStream} from "vue-qrcode-reader";
import {ref} from "vue";
<script setup>
import { reactive } from "vue";
import { defineComponent, toRefs, onMounted, onUnmounted } from "vue";
import { Html5Qrcode } from "html5-qrcode";
import { useRouter } from 'vue-router'
const router = useRouter() //
const dataList = ref('')
const state = reactive({ const result = ref(true)
html5QrCode: null, const error = ref('')
fileList: [],
});
const start = () => {
state.html5QrCode
.start(
{ facingMode: "environment" },
{
fps: 5,
qrbox: { width: 300, height: 210 },
aspectRatio: 1.7,//
},
(decodedText, decodedResult) => {
window.location.href = decodedText;
if (state.html5QrCode&&state.html5QrCode.isScanning) {
stop();
}
console.log("decodedText", decodedText);
console.log("decodedResult", decodedResult);
}
)
.catch((err) => {
console.log("扫码错误信息", err);
let message = ""; //
if (typeof err == "string") {
message = "二维码识别失败!";
alert(message)
} else {
if (err.name == "NotAllowedError") {
message = "您需要授予相机访问权限!";
}
if (err.name == "NotFoundError") {
message = "这个设备上没有摄像头!";
}
if (err.name == "NotSupportedError") {
message =
"摄像头访问只支持在安全的上下文中,如https或localhost!";
}
if (err.name == "NotReadableError") {
message = "相机被占用!";
}
if (err.name == "OverconstrainedError") {
message = "安装摄像头不合适!";
}
if (err.name == "StreamApiNotSupportedError") {
message = "此浏览器不支持流API!";
}
alert(message)
}
});
};
const clickBack = () => {
//console.log(1)
if (state.html5QrCode&&state.html5QrCode.isScanning) {
stop();
}
router.back();
}
const getCameras = () => {
Html5Qrcode.getCameras()
.then((devices) => {
if (devices && devices.length) {
state.html5QrCode = new Html5Qrcode("reader");
start();
}
})
.catch((err) => {
console.log(err)
alert("摄像头无访问权限!");
});
};
const stop = () => {
state.html5QrCode
.stop()
.then((ignore) => {
console.log("停止扫码", ignore);
})
.catch((err) => {
console.log(err);
alert("停止扫码失败");
});
};
const dealSelectFiles = () => {
try {
window.qrcode.callback = (result) => {
alert("成功了,结果是:" + result);
}; // get select files.
let file = state.fileList[0].file;
var reader = new FileReader();
reader.onload = (function () {
return function (e) {
window.qrcode.decode(e.target.result);
};
})(file);
reader.readAsDataURL(file);
} catch (error) {
alert("图片识别失败!");
}
};
onMounted(() => {
getCameras();
});
onUnmounted(() => {
//
if (state.html5QrCode&&state.html5QrCode.isScanning) {
stop();
}
});
</script>
<style lang="scss" scoped>
.scanCode { //
height: 100vh; const onDecode = (res: any) => {
display: flex; dataList.value = res
flex-direction: column; result.value = false
background: rgba(0, 0, 0); //console.log('',dataList.value)
//alert(dataList.value)
//showSuccessToast('')---------------------------------------
window.location.href = dataList.value[0].rawValue;
//
//
} }
.container { //
height: 100vh; const onInit = async (promise: any) => {
position: relative; console.log('初始化摄像头',promise)
width: 100%; try {
/* border: red solid 7px; */ await promise
}catch (err: any) {
if (err.name === 'NotAllowedError') {
error.value = 'ERROR: 您需要授予相机访问权限';
} else if (err.name === 'NotFoundError') {
error.value = 'ERROR: 这个设备上没有摄像头';
} else if (err.name === 'NotSupportedError') {
error.value = 'ERROR: 所需的安全上下文(HTTPS、本地主机)';
} else if (err.name === 'NotReadableError') {
error.value = 'ERROR: 相机被占用';
} else if (err.name === 'OverconstrainedError') {
error.value = 'ERROR: 安装摄像头不合适';
} else if (err.name === 'StreamApiNotSupportedError') {
error.value = 'ERROR: 此浏览器不支持流API';
}
}
} }
.qrcode { //
height: 90%; const onClickLeft = () => {
/* width: 100%;*/ history.back();
/* border: white solid 7px; */
} }
#reader { </script>
display: block; <style scoped>
:deep(i.van-badge__wrapper.van-icon.van-icon-arrow-left.scanImg){
height: 90vh; font-size: 40px;
/* top: 50%; color: white;
left: 0; }
transform: translateY(-50%); */ .scanImg{
/* border: yellow solid 5px; */ margin-top: 30px;
margin-left: 10px;
}
.error {
font-weight: bold;
color: red;
}
.cameraMessage {
width: 100%;
height: 60px;
}
.qr-scanner {
background-size: 3rem 3rem;
background-position: -1rem -1rem;
width: 100%;
/* height: 100%; */
height: 100vh;
/* height: 288px; */
position: relative;
background-color: #1110;
}
.qr-scanner .box {
width: 213px;
height: 213px;
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
overflow: hidden;
border: 1px solid #3aa5ff;
}
.qr-scanner .txt {
width: 100%;
height: 35px;
line-height: 35px;
font-size: 14px;
text-align: center;
/* color: #f9f9f9; */
margin: 0 auto;
position: absolute;
top: 60%;
left: 0;
}
.qr-scanner .myQrcode {
text-align: center;
color: #3aa5ff;
}
.qr-scanner .line {
height: calc(100% - 2px);
width: 100%;
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3aa5ff 211%);
border-bottom: 1px solid #3aa5ff;
transform: translateY(-100%);
animation: radar-beam 2s infinite alternate;
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
animation-delay: 1.4s;
}
.qr-scanner .box:after,
.qr-scanner .box:before,
.qr-scanner .angle:after,
.qr-scanner .angle:before {
content: '';
display: block;
position: absolute;
width: 3vw;
height: 3vw;
}
.qr-scanner .box:after,
.qr-scanner .box:before {
top: 0;
border-top-color: #3aa5ff;
}
.qr-scanner .angle:after,
.qr-scanner .angle:before {
bottom: 0;
border-bottom-color: #3aa5ff;
}
.qr-scanner .box:before,
.qr-scanner .angle:before {
left: 0;
border-left-color: #3aa5ff;
}
.qr-scanner .box:after,
.qr-scanner .angle:after {
right: 0;
border-right-color: #3aa5ff;
}
@keyframes radar-beam {
0% {
transform: translateY(-100%);
} }
.btn { 100% {
flex: 1; transform: translateY(0);
padding-top: 2vw;
display: flex;
padding-left: 3vw;
color: #fff;
font-size: 6vw;
align-items: flex-start;
/* border: green solid 7px; */
} }
</style> }
</style>

497
src/views/home/scanQrCodeInput.vue

@ -1,186 +1,343 @@
<template> <template>
<div style="height: 100vh">
<qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%"> <div class="page-scan">
<div> <!-- 扫码区域 -->
<div class="qr-scanner"> <!-- <div style="z-index: 1000; position: relative;"> -->
<!--顶部左边的返回箭头--> <el-icon @click="onClickLeft" class= "close-icon" style="width: 30px;height: 30px;"><!-- style="margin:15px;width: 20px;height: 20px;color: white;" -->
<div> <Close style="width: 30px;height: 30px;" /><!-- style="width: 20px;height: 20px;" -->
<el-icon @click="onClickLeft" style="margin:15px;width: 20px;height: 20px;"> </el-icon>
<Close style="width: 20px;height: 20px;" /> <!-- </div> -->
</el-icon> <div class="QrCode">
</div>
<!--中间的扫码框--> <video ref="video" height="100%" width="100%" id="video" autoplay></video>
<div class="box">
<div class="line"></div> </div>
<div class="angle"></div> <!-- 扫码样式一 -->
</div> <div class="Qr_scanner">
<div class="txt"> <div class="box">
将二维码/条码放入框内即自动扫描 <div class="line_row">
</div> <div class="line"></div>
</div> </div>
<div class="angle"></div>
</div> </div>
</qrcode-stream>
</div>
<div class="txt">
将二维码/条码放入框内即自动扫描
</div>
</div> </div>
</template> </template>
<script setup lang="ts">
import { QrcodeStream } from "vue-qrcode-reader"; <script>
import { ref } from "vue"; // WebRTC ok
import 'webrtc-adapter'
const emits = defineEmits<{ import { BrowserMultiFormatReader } from '@zxing/library'
(e: 'update-qrScaning', value: any): void import request from '@/utils/axios/index'
(e: 'update-scanResult', value: any): void import wx from 'weixin-js-sdk';
}>()
// export default {
const dataList = ref('') name: 'scanCodePage',
const result = ref(true) data() {
const error = ref('') return {
codeReader: null,
isIOS: false
// }
const onDecode = (res: any) => { },
dataList.value = res mounted() {
result.value = false const ua = navigator.userAgent
//console.log('',dataList.value) //isIOS.value = /iPhone|iPad|iPod/i.test(ua)
//alert(dataList.value) this.isIOS = /iPhone|iPad|iPod/i.test(ua) && !window.MSStream;
//showSuccessToast('')--------------------------------------- this.codeReader = new BrowserMultiFormatReader()
//console.log(''+dataList.value[0].rawValue) //alert(navigator.userAgent)
emits('update-scanResult', dataList.value[0].rawValue) // if (/wxwork\//.test(navigator.userAgent)) {
// //alert('')
// this.initWxConfig();
} } else {
// // 使WebRTC
const onInit = async (promise: any) => { this.openScan()
console.log('初始化摄像头', promise) //this.initWxConfig();
try {
await promise
} catch (err: any) {
if (err.name === 'NotAllowedError') {
error.value = 'ERROR: 您需要授予相机访问权限';
} else if (err.name === 'NotFoundError') {
error.value = 'ERROR: 这个设备上没有摄像头';
} else if (err.name === 'NotSupportedError') {
error.value = 'ERROR: 所需的安全上下文(HTTPS、本地主机)';
} else if (err.name === 'NotReadableError') {
error.value = 'ERROR: 相机被占用';
} else if (err.name === 'OverconstrainedError') {
error.value = 'ERROR: 安装摄像头不合适';
} else if (err.name === 'StreamApiNotSupportedError') {
error.value = 'ERROR: 此浏览器不支持流API';
} }
}
}
//
const onClickLeft = () => {
//history.back();
emits('update-qrScaning', '1') //
}
</script>
<style scoped>
:deep(i.van-badge__wrapper.van-icon.van-icon-arrow-left.scanImg) {
font-size: 40px;
color: white;
}
.scanImg {
margin-top: 30px;
margin-left: 10px;
}
.error { },
font-weight: bold; beforeUnmount() {
color: red; this.codeReader && this.codeReader.reset()
} },
methods: {
.cameraMessage {
width: 100%; getQyWxSignature() {
height: 60px; let url = window.location.href.split("#")[0];
var req = {};
req.url = url;
return request({
url: "/javasys/lowCode/QrCode/QyWxSignature",
method: "get",
data: req,
});
},
//
async initWxConfig() {
this.getQyWxSignature().then(({ data }) => {
//alert(data.corpid)
wx.config({
beta: true,// wx.invokejsapi
debug: false, // ,apialertpclogpc
appId: data.corpid, // corpID
timestamp: data.timestamp, //
nonceStr: data.noncestr, //
signature: data.jsapi_ticket_enterprises,// -JS-SDK使
jsApiList: ['scanQRCode'] // 使JS
});
this.startScan()
});
},
//
startScan() {
wx.ready(() => {
wx.scanQRCode({
needResult: 1, //
scanType: ['qrCode','barCode'], //
success: (res) => {
const result = res.resultStr;
this.handleScanResult(result);
},
fail: (err) => {
alert(err.errMsg)
}
});
});
},
onClickLeft(){
this.codeReader && this.codeReader.reset()
this.codeReader = null
this.$emit('update-qrScaning', '1') // 触发父组件事件 emits('update-scanResult', dataList.value[0].rawValue) // 触发父组件事件
},
//
handleScanResult(code) {
/* this.$axios.post('/api/verify-code', { code })
.then(res => {
//
}); */
this.$emit('update-scanResult', code) //
},
async openScan() {
this.codeReader
.listVideoInputDevices()
.then((videoInputDevices) => {
// id
let firstDeviceId = videoInputDevices[0].deviceId
//
let noBackAndroidFlag = false
//
//console.log(videoInputDevices)
//
//alert(videoInputDevices.length)
videoInputDevices.some(element => {
if (videoInputDevices.length > 1) {//
//alert(element.label)
if (element.label.startsWith("Video device")) {//
//alert(element.label)
if (this.isIOS) {//
firstDeviceId = element.deviceId
return true;
} else {//
if (videoInputDevices.length == 2) {
noBackAndroidFlag = true
//if(element.label.startsWith("Video device 2")){
//firstDeviceId = element.deviceId
//return true
//}
} else if (videoInputDevices.length >= 3) {
if (element.label.startsWith("Video device 3")) {
firstDeviceId = element.deviceId
return true
}
}
}
} else {
if (element.label.includes("back")) {
firstDeviceId = element.deviceId
return true;
} else {
return false;
}
}
} else {//
return true
}
});
this.codeReader && this.codeReader.reset() //
this.decodeFromInputVideoFunc(firstDeviceId)
})
.catch((err) => {
console.error(err)
})
},
decodeFromInputVideoFunc(firstDeviceId) {
this.codeReader.decodeFromInputVideoDeviceContinuously(
firstDeviceId,
'video',
(result, err) => {
if (result) {
console.log('扫描结果', result)
if (result.text) {
// this.clickIndexLeft(result.text)
//alert(result.text)
this.$emit('update-scanResult', result.text)
}
}
if (err && !err) {
console.error(err)
}
}
)
},
/* // 停止扫描并返回上一页
clickIndexLeft(result) {
this.codeReader && this.codeReader.reset()
this.codeReader = null
// this.$route.params.result = result
// this.$router.back()
} */
}
} }
</script>
.qr-scanner { <style lang="scss" scope>
background-size: 3rem 3rem; .QrCode {
background-position: -1rem -1rem; width: 100vw;
width: 100%;
/* height: 100%; */
height: 100vh; height: 100vh;
/* height: 288px; */
position: relative; position: relative;
background-color: #1110;
}
.qr-scanner .box { #video {
width: 213px; width: 100%;
height: 213px; height: 100%;
position: absolute; object-fit: cover;
left: 50%; }
top: 40%;
transform: translate(-50%, -50%);
overflow: hidden;
border: 1px solid #3aa5ff;
} }
.qr-scanner .txt { .Qr_scanner {
width: 100%; position: fixed;
height: 35px; top: 0;
line-height: 35px;
font-size: 14px;
text-align: center;
/* color: #f9f9f9; */
margin: 0 auto;
position: absolute;
top: 60%;
left: 0; left: 0;
right: 0;
bottom: 0;
z-index: 9;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
} }
.qr-scanner .myQrcode { .Qr_scanner .box {
text-align: center; width: 65vw;
color: #3aa5ff; height: 65vw;
max-height: 75vh;
max-width: 75vh;
position: relative;
left: 50%;
top: 45%;
transform: translate(-50%, -50%);
border: 1px solid rgb(43, 113, 254);
.line_row {
width: 100%;
overflow: hidden;
background-image: linear-gradient(0deg,
transparent 24%,
rgba(136, 176, 255, 0.1) 25%,
rgba(136, 176, 255, 0.1) 26%,
transparent 27%,
transparent 74%,
rgba(136, 176, 255, 0.1) 75%,
rgba(136, 176, 255, 0.1) 76%,
transparent 77%,
transparent),
linear-gradient(90deg,
transparent 24%,
rgba(136, 176, 255, 0.1) 25%,
rgba(136, 176, 255, 0.1) 26%,
transparent 27%,
transparent 74%,
rgba(136, 176, 255, 0.1) 75%,
rgba(136, 176, 255, 0.1) 76%,
transparent 77%,
transparent);
background-size: 3rem 3rem;
background-position: -1rem -1rem;
animation: Heightchange 2s infinite;
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
animation-delay: 1.4s;
border-bottom: 1px solid rgba(136, 176, 255, 0.1);
display: flex;
justify-content: center;
align-items: flex-end;
}
} }
.qr-scanner .line { .Qr_scanner .line {
height: calc(100% - 2px);
width: 100%; width: 100%;
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3aa5ff 211%); height: 3px;
border-bottom: 1px solid #3aa5ff; background: #2b71fe;
transform: translateY(-100%); // opacity: 0.58;
animation: radar-beam 2s infinite alternate; filter: blur(4px);
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
animation-delay: 1.4s;
} }
.qr-scanner .box:after, .Qr_scanner .box:after,
.qr-scanner .box:before, .Qr_scanner .box:before,
.qr-scanner .angle:after, .Qr_scanner .angle:after,
.qr-scanner .angle:before { .Qr_scanner .angle:before {
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
width: 3vw; width: 78px;
height: 3vw; height: 78px;
border: 0.15rem solid transparent;
} }
.qr-scanner .box:after, .Qr_scanner .box:after,
.qr-scanner .box:before { .Qr_scanner .box:before {
top: 0; top: -7px;
border-top-color: #3aa5ff; border-top-color: #2b71fe;
} }
.qr-scanner .angle:after, .Qr_scanner .angle:after,
.qr-scanner .angle:before { .Qr_scanner .angle:before {
bottom: 0; bottom: -7px;
border-bottom-color: #3aa5ff; border-bottom-color: #2b71fe;
} }
.qr-scanner .box:before, .Qr_scanner .box:before,
.qr-scanner .angle:before { .Qr_scanner .angle:before {
left: 0; left: -7px;
border-left-color: #3aa5ff; border-left-color: #2b71fe;
} }
.qr-scanner .box:after, .Qr_scanner .box:after,
.qr-scanner .angle:after { .Qr_scanner .angle:after {
right: 0; right: -7px;
border-right-color: #3aa5ff; border-right-color: #2b71fe;
} }
@keyframes radar-beam { @keyframes radar-beam {
@ -192,4 +349,48 @@ const onClickLeft = () => {
transform: translateY(0); transform: translateY(0);
} }
} }
</style>
@keyframes Heightchange {
0% {
height: 0;
}
100% {
height: 100%;
}
}
.txt {
width: 100%;
height: 35px;
line-height: 35px;
font-size: 14px;
text-align: center;
/* color: #f9f9f9; */
margin: 0 auto;
position: absolute;
top: 63%;
left: 0;
color: white;
z-index: 1001;
}
.close-icon {
position: absolute;
top: 15px;
left: 15px;
width: 20px;
height: 20px;
color: white;
z-index: 1001;
cursor: pointer;
background: rgba(0, 0, 0, 0.3); /* 可选背景 */
border-radius: 50%; /* 圆形背景 */
padding: 5px;
/* 点击区域扩大 */
}
.Qr_scanner {
z-index: 999; /* 低于图标容器 */
/* 其他原有样式保持不变 */
}
</style>

195
src/views/home/scanQrCodeInput1.vue

@ -0,0 +1,195 @@
<template>
<div style="height: 100vh">
<qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%">
<div>
<div class="qr-scanner">
<!--顶部左边的返回箭头-->
<div>
<el-icon @click="onClickLeft" style="margin:15px;width: 20px;height: 20px;">
<Close style="width: 20px;height: 20px;" />
</el-icon>
</div>
<!--中间的扫码框-->
<div class="box">
<div class="line"></div>
<div class="angle"></div>
</div>
<div class="txt">
将二维码/条码放入框内即自动扫描
</div>
</div>
</div>
</qrcode-stream>
</div>
</template>
<script setup lang="ts">
import { QrcodeStream } from "vue-qrcode-reader";
import { ref } from "vue";
const emits = defineEmits<{
(e: 'update-qrScaning', value: any): void
(e: 'update-scanResult', value: any): void
}>()
//
const dataList = ref('')
const result = ref(true)
const error = ref('')
//
const onDecode = (res: any) => {
dataList.value = res
result.value = false
//console.log('',dataList.value)
//alert(dataList.value)
//showSuccessToast('')---------------------------------------
//console.log(''+dataList.value[0].rawValue)
emits('update-scanResult', dataList.value[0].rawValue) //
//
//
}
//
const onInit = async (promise: any) => {
console.log('初始化摄像头', promise)
try {
await promise
} catch (err: any) {
if (err.name === 'NotAllowedError') {
error.value = 'ERROR: 您需要授予相机访问权限';
} else if (err.name === 'NotFoundError') {
error.value = 'ERROR: 这个设备上没有摄像头';
} else if (err.name === 'NotSupportedError') {
error.value = 'ERROR: 所需的安全上下文(HTTPS、本地主机)';
} else if (err.name === 'NotReadableError') {
error.value = 'ERROR: 相机被占用';
} else if (err.name === 'OverconstrainedError') {
error.value = 'ERROR: 安装摄像头不合适';
} else if (err.name === 'StreamApiNotSupportedError') {
error.value = 'ERROR: 此浏览器不支持流API';
}
}
}
//
const onClickLeft = () => {
//history.back();
emits('update-qrScaning', '1') //
}
</script>
<style scoped>
:deep(i.van-badge__wrapper.van-icon.van-icon-arrow-left.scanImg) {
font-size: 40px;
color: white;
}
.scanImg {
margin-top: 30px;
margin-left: 10px;
}
.error {
font-weight: bold;
color: red;
}
.cameraMessage {
width: 100%;
height: 60px;
}
.qr-scanner {
background-size: 3rem 3rem;
background-position: -1rem -1rem;
width: 100%;
/* height: 100%; */
height: 100vh;
/* height: 288px; */
position: relative;
background-color: #1110;
}
.qr-scanner .box {
width: 213px;
height: 213px;
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
overflow: hidden;
border: 1px solid #3aa5ff;
}
.qr-scanner .txt {
width: 100%;
height: 35px;
line-height: 35px;
font-size: 14px;
text-align: center;
/* color: #f9f9f9; */
margin: 0 auto;
position: absolute;
top: 60%;
left: 0;
}
.qr-scanner .myQrcode {
text-align: center;
color: #3aa5ff;
}
.qr-scanner .line {
height: calc(100% - 2px);
width: 100%;
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3aa5ff 211%);
border-bottom: 1px solid #3aa5ff;
transform: translateY(-100%);
animation: radar-beam 2s infinite alternate;
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
animation-delay: 1.4s;
}
.qr-scanner .box:after,
.qr-scanner .box:before,
.qr-scanner .angle:after,
.qr-scanner .angle:before {
content: '';
display: block;
position: absolute;
width: 3vw;
height: 3vw;
}
.qr-scanner .box:after,
.qr-scanner .box:before {
top: 0;
border-top-color: #3aa5ff;
}
.qr-scanner .angle:after,
.qr-scanner .angle:before {
bottom: 0;
border-bottom-color: #3aa5ff;
}
.qr-scanner .box:before,
.qr-scanner .angle:before {
left: 0;
border-left-color: #3aa5ff;
}
.qr-scanner .box:after,
.qr-scanner .angle:after {
right: 0;
border-right-color: #3aa5ff;
}
@keyframes radar-beam {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
</style>
Loading…
Cancel
Save