You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
11 KiB
400 lines
11 KiB
|
6 months ago
|
<template>
|
||
|
|
<div class="home app_content">
|
||
|
|
<!-- 中间 -->
|
||
|
|
<div class="warp_body">
|
||
|
|
<div class="white-body">
|
||
|
|
<div class="boutique_box">
|
||
|
|
<div class="boutique_body">
|
||
|
|
<div class="box-qr-code" @click="toScanPageView">
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="scan-note">扫一扫企业二维码,即可查看企业信息</div>
|
||
|
|
<div class="nocode" @click="showSearchPopup()">手动登记,请输入关键字搜索企业 >></div>
|
||
|
|
</div>
|
||
|
6 months ago
|
|
||
|
6 months ago
|
<div class="record">
|
||
|
|
如遇网络异常、设备故障无法登录系统的情况,<a @click="toSign">补录 >></a>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
6 months ago
|
|
||
|
6 months ago
|
<!-- html5-qrcode 扫码弹层(仅 dev 环境) -->
|
||
|
|
<van-overlay :show="showScanner" @click="closeScannerOverlay" z-index="9999">
|
||
|
|
<div class="scanner-wrapper" @click.stop>
|
||
|
|
<div class="scanner-header">
|
||
|
|
<span class="scanner-title">扫描二维码</span>
|
||
|
|
<van-icon name="cross" class="scanner-close" @click="closeScanner" />
|
||
|
|
</div>
|
||
|
|
<div id="qr-reader" class="qr-reader-box"></div>
|
||
|
|
<div class="scanner-tip">请将二维码置于框内,自动识别</div>
|
||
|
|
</div>
|
||
|
|
</van-overlay>
|
||
|
|
|
||
|
|
<!-- 隐藏的文件选择(摄像头不可用时的后备方案:上传二维码图片识别) -->
|
||
|
|
<input
|
||
|
|
ref="fileInputRef"
|
||
|
|
type="file"
|
||
|
|
accept="image/*"
|
||
|
|
style="display: none;"
|
||
|
|
@change="onFileSelected"
|
||
|
|
/>
|
||
|
|
<!-- html5-qrcode scanFile 需要的隐藏容器 -->
|
||
|
|
<div id="qr-reader-file-scan" style="display: none;"></div>
|
||
|
6 months ago
|
|
||
|
6 months ago
|
<!-- 手动搜索弹窗 placeholder -->
|
||
|
|
<!-- <popup :visible="popupVisible" @close="closePopup" /> -->
|
||
|
|
</div>
|
||
|
6 months ago
|
</template>
|
||
|
|
|
||
|
6 months ago
|
<script setup name="ScanEntQRCode">
|
||
|
|
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||
|
|
import { useRouter } from 'vue-router';
|
||
|
|
import { showFailToast, showSuccessToast, showToast, showConfirmDialog } from 'vant';
|
||
|
|
import { baseUrl, appEnv } from '@/config/env.js';
|
||
|
6 months ago
|
import { getEntInfoByQRCode } from '@/api/enforcement/index';
|
||
|
6 months ago
|
import wx from 'weixin-js-sdk';
|
||
|
|
|
||
|
|
const router = useRouter();
|
||
|
|
|
||
|
|
// ========== 扫码相关 ==========
|
||
|
|
const showScanner = ref(false);
|
||
|
|
const fileInputRef = ref(null);
|
||
|
|
let html5QrCode = null;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 点击扫码入口
|
||
|
|
*/
|
||
|
|
const toScanPageView = () => {
|
||
|
|
if (appEnv === 'dev') {
|
||
|
|
openHtml5QrcodeScanner();
|
||
|
|
} else {
|
||
|
|
openWxScan();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// ---------- dev 环境:html5-qrcode ----------
|
||
|
|
|
||
|
|
const openHtml5QrcodeScanner = async () => {
|
||
|
|
showScanner.value = true;
|
||
|
|
await nextTick();
|
||
|
|
|
||
|
|
const { Html5Qrcode } = await import('html5-qrcode');
|
||
|
|
html5QrCode = new Html5Qrcode('qr-reader');
|
||
|
|
|
||
|
|
try {
|
||
|
|
await html5QrCode.start(
|
||
|
|
{ facingMode: 'environment' },
|
||
|
|
{
|
||
|
|
fps: 10,
|
||
|
|
qrbox: { width: 250, height: 250 },
|
||
|
|
},
|
||
|
|
onScanSuccess,
|
||
|
|
onScanFailure
|
||
|
|
);
|
||
|
|
} catch (err) {
|
||
|
|
console.error('摄像头启动失败', err);
|
||
|
6 months ago
|
html5QrCode = null; // 启动失败,清除实例,避免销毁时调 stop 报错
|
||
|
6 months ago
|
showScanner.value = false;
|
||
|
|
// 摄像头不可用时,提供上传二维码图片的后备方案
|
||
|
|
showConfirmDialog({
|
||
|
|
title: '摄像头不可用',
|
||
|
|
message: '无法启动摄像头,是否选择一张二维码图片进行识别?',
|
||
|
|
confirmButtonText: '选择图片',
|
||
|
|
cancelButtonText: '取消',
|
||
|
|
}).then(() => {
|
||
|
|
fileInputRef.value?.click();
|
||
|
|
}).catch(() => {
|
||
|
|
// 用户取消
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const onScanSuccess = (decodedText, decodedResult) => {
|
||
|
|
console.log('扫码结果:', decodedText);
|
||
|
|
closeScanner();
|
||
|
|
handleScanResult(decodedText);
|
||
|
|
};
|
||
|
|
|
||
|
|
const onScanFailure = (error) => {
|
||
|
|
// 扫描中,忽略连续失败的回调
|
||
|
|
};
|
||
|
|
|
||
|
|
const closeScanner = async () => {
|
||
|
|
if (html5QrCode) {
|
||
|
|
try {
|
||
|
|
await html5QrCode.stop();
|
||
|
|
} catch (e) {
|
||
|
|
console.warn('停止扫码器异常', e);
|
||
|
|
}
|
||
|
|
html5QrCode = null;
|
||
|
|
}
|
||
|
|
showScanner.value = false;
|
||
|
|
};
|
||
|
|
|
||
|
|
const closeScannerOverlay = () => {
|
||
|
|
closeScanner();
|
||
|
|
};
|
||
|
|
|
||
|
|
// ---------- 后备方案:上传二维码图片识别 ----------
|
||
|
|
|
||
|
|
const onFileSelected = async (event) => {
|
||
|
|
const file = event.target.files?.[0];
|
||
|
|
if (!file) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { Html5Qrcode } = await import('html5-qrcode');
|
||
|
|
const html5Qr = new Html5Qrcode('qr-reader-file-scan');
|
||
|
|
const decodedText = await html5Qr.scanFile(file, /* showImage= */ false);
|
||
|
|
console.log('图片识别结果:', decodedText);
|
||
|
|
handleScanResult(decodedText);
|
||
|
|
} catch (err) {
|
||
|
|
console.error('图片二维码识别失败', err);
|
||
|
|
showFailToast('无法从图片中识别二维码,请确认图片包含有效的二维码');
|
||
|
|
} finally {
|
||
|
|
// 重置 input,允许重复选择同一文件
|
||
|
|
if (fileInputRef.value) {
|
||
|
|
fileInputRef.value.value = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// ---------- prod 环境:wx.scanQRCode ----------
|
||
|
|
|
||
|
|
const openWxScan = () => {
|
||
|
|
// TODO: 对接实际微信 JSSDK token 接口后启用
|
||
|
|
// 当前仅预留调用骨架,需配合服务端 szyptGetToken 接口获取签名
|
||
|
|
const fullUrl = window.location.href;
|
||
|
|
const paramUrl = fullUrl.split('#')[0];
|
||
|
|
|
||
|
|
showToast('正在调起微信扫码...');
|
||
|
|
|
||
|
|
// 以下为 wx.config + wx.scanQRCode 的标准流程
|
||
|
|
// 实际使用时需先调用服务端接口获取 config 参数
|
||
|
|
/*
|
||
|
|
szyptGetToken({ url: paramUrl }, (res) => {
|
||
|
|
const data = res.data;
|
||
|
|
wx.config({
|
||
|
|
debug: false,
|
||
|
|
appId: data.corpid,
|
||
|
|
timestamp: data.timestamp,
|
||
|
|
nonceStr: data.nonceStr,
|
||
|
|
signature: data.signature,
|
||
|
|
jsApiList: ['scanQRCode'],
|
||
|
|
});
|
||
|
|
wx.ready(() => {
|
||
|
|
wxClickScan();
|
||
|
|
});
|
||
|
|
wx.error((err) => {
|
||
|
|
console.error('wx.config 失败', err);
|
||
|
|
showFailToast('微信配置失败,请重试');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
*/
|
||
|
|
|
||
|
|
// 开发阶段直接尝试调用(在微信浏览器内有效)
|
||
|
|
wxClickScan();
|
||
|
|
};
|
||
|
|
|
||
|
|
const wxClickScan = () => {
|
||
|
|
wx.scanQRCode({
|
||
|
|
needResult: 1,
|
||
|
|
scanType: ['qrCode', 'barCode'],
|
||
|
|
success: (res) => {
|
||
|
|
console.log('wx 扫码返回:', res);
|
||
|
|
handleScanResult(res.resultStr);
|
||
|
|
},
|
||
|
|
error: (res) => {
|
||
|
|
console.error('wx 扫码错误:', res);
|
||
|
|
if (res.errMsg && res.errMsg.indexOf('function_not_exist') > 0) {
|
||
|
|
showFailToast('版本过低请升级');
|
||
|
|
}
|
||
|
|
},
|
||
|
|
cancel: () => {
|
||
|
|
console.log('用户取消扫码');
|
||
|
|
},
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
// ========== 扫码结果处理 ==========
|
||
|
|
|
||
|
|
const handleScanResult = (resultStr) => {
|
||
|
|
console.log('========== 扫码结果处理 ==========');
|
||
|
|
console.log('原始内容:', resultStr);
|
||
|
|
|
||
|
6 months ago
|
// 不管解码结果是 HTTP 链接还是纯文本,统一交给后端解析
|
||
|
|
showToast({ message: '正在查询企业信息...', duration: 0 });
|
||
|
|
getEntInfoByQRCode(resultStr, (res) => {
|
||
|
|
showToast({ message: '', duration: 1 }); // 关闭 loading
|
||
|
|
if (res.code === 200) {
|
||
|
|
const data = res.data;
|
||
|
|
console.log('企业信息查询成功:', data);
|
||
|
|
if (!data.qrCodeValid) {
|
||
|
|
showFailToast(data.qrCodeMessage || '二维码已过期');
|
||
|
|
}
|
||
|
|
// 将企业信息存入 sessionStorage,预览页直接使用,无需再调接口
|
||
|
|
sessionStorage.setItem('enterpriseScanData', JSON.stringify(data));
|
||
|
|
// 跳转企业详情预览页
|
||
|
|
router.push({ path: '/enforcement/enterpriseScanPreview' });
|
||
|
|
} else {
|
||
|
|
showFailToast(res.msg || '查询企业信息失败');
|
||
|
6 months ago
|
}
|
||
|
6 months ago
|
}, (err) => {
|
||
|
|
showToast({ message: '', duration: 1 });
|
||
|
|
console.error('getEntInfoByQRCode 调用失败', err);
|
||
|
|
showFailToast('查询企业信息失败,请重试');
|
||
|
|
});
|
||
|
6 months ago
|
};
|
||
|
|
|
||
|
|
const isHttpLink = (url) => {
|
||
|
|
return url.indexOf('http://') === 0 || url.indexOf('https://') === 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
// ========== 页面跳转 ==========
|
||
|
|
|
||
|
|
const toSign = () => {
|
||
|
|
router.push({
|
||
|
|
path: '/sign',
|
||
|
|
query: {
|
||
|
|
reason: '补录',
|
||
|
|
checkCss: 9,
|
||
|
|
isNoScan: true,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
const showSearchPopup = () => {
|
||
|
|
// TODO: 对接手动搜索企业弹窗
|
||
|
|
showToast('手动搜索企业功能待对接');
|
||
|
|
};
|
||
|
|
|
||
|
|
// ========== 生命周期 ==========
|
||
|
|
|
||
|
|
onBeforeUnmount(() => {
|
||
|
|
// 页面销毁时确保关闭摄像头
|
||
|
|
if (html5QrCode) {
|
||
|
|
html5QrCode.stop().catch(() => {});
|
||
|
|
html5QrCode = null;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="less" scoped>
|
||
|
|
.home {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
height: 100%;
|
||
|
|
overflow: hidden;
|
||
|
|
background-color: #ebf3fb;
|
||
|
|
|
||
|
|
.warp_body {
|
||
|
|
position: relative;
|
||
|
|
background-image: linear-gradient(to bottom, #ebf3fb, transparent);
|
||
|
|
}
|
||
|
|
|
||
|
|
.boutique_box {
|
||
|
|
position: relative;
|
||
|
|
top: 25%;
|
||
|
|
z-index: 100;
|
||
|
|
width: 100%;
|
||
|
|
|
||
|
|
.boutique_body {
|
||
|
|
padding: 0 0 0.27rem 0;
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.box-qr-code {
|
||
|
|
width: 52%;
|
||
|
|
text-align: right;
|
||
|
|
height: 6rem;
|
||
|
|
background: url("../../assets/images/scan-btn.jpg") no-repeat center center;
|
||
|
|
background-size: 100%;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.white-body {
|
||
|
|
position: relative;
|
||
|
|
margin: 0 3%;
|
||
|
|
width: 94%;
|
||
|
|
height: 99%;
|
||
|
|
background-color: #fff;
|
||
|
|
border-radius: 0.27rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.scan-note {
|
||
|
|
width: 100%;
|
||
|
|
color: #d3d0d0;
|
||
|
|
font-size: 0.35rem;
|
||
|
|
text-align: center;
|
||
|
|
line-height: 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
.nocode {
|
||
|
|
margin: 15% 0 0;
|
||
|
|
width: 100%;
|
||
|
|
color: #fb7905;
|
||
|
|
font-size: 0.42rem;
|
||
|
|
text-align: center;
|
||
|
|
line-height: 5;
|
||
|
|
}
|
||
|
|
|
||
|
|
.record {
|
||
|
|
position: absolute;
|
||
|
|
bottom: 2rem;
|
||
|
|
color: #bbbaba;
|
||
|
|
font-size: 0.35rem;
|
||
|
|
width: 100%;
|
||
|
|
text-align: center;
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
.record a {
|
||
|
|
color: #287eef;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ========== html5-qrcode 扫码弹层样式 ========== */
|
||
|
|
.scanner-wrapper {
|
||
|
|
position: absolute;
|
||
|
|
top: 50%;
|
||
|
|
left: 50%;
|
||
|
|
transform: translate(-50%, -50%);
|
||
|
|
width: 90%;
|
||
|
|
max-width: 500px;
|
||
|
|
background: #fff;
|
||
|
|
border-radius: 0.27rem;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.scanner-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
padding: 0.3rem 0.4rem;
|
||
|
|
background: #1367fe;
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
|
||
|
|
.scanner-title {
|
||
|
|
font-size: 0.42rem;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.scanner-close {
|
||
|
|
font-size: 0.5rem;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.qr-reader-box {
|
||
|
|
width: 100%;
|
||
|
|
min-height: 300px;
|
||
|
|
}
|
||
|
6 months ago
|
|
||
|
6 months ago
|
.scanner-tip {
|
||
|
|
padding: 0.25rem 0;
|
||
|
|
text-align: center;
|
||
|
|
font-size: 0.32rem;
|
||
|
|
color: #999;
|
||
|
|
}
|
||
|
6 months ago
|
</style>
|