4 changed files with 413 additions and 7 deletions
@ -1,11 +1,393 @@ |
|||||
<script setup> |
<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> |
||||
|
|
||||
</script> |
<div class="record"> |
||||
|
如遇网络异常、设备故障无法登录系统的情况,<a @click="toSign">补录 >></a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
<template> |
<!-- 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> |
||||
|
|
||||
|
<!-- 手动搜索弹窗 placeholder --> |
||||
|
<!-- <popup :visible="popupVisible" @close="closePopup" /> --> |
||||
|
</div> |
||||
</template> |
</template> |
||||
|
|
||||
<style scoped lang="less"> |
<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'; |
||||
|
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); |
||||
|
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); |
||||
|
|
||||
|
if (isHttpLink(resultStr)) { |
||||
|
// HTTP 链接类型(市监二维码) |
||||
|
// TODO: 对接服务端 getEnterpriseInfoBySj 接口 |
||||
|
console.log('识别到 HTTP 链接,待对接服务端接口:', resultStr); |
||||
|
showToast('识别到企业链接,服务端接口待对接'); |
||||
|
} else { |
||||
|
// JSON 类型(执法码二维码) |
||||
|
try { |
||||
|
const scanObj = JSON.parse(resultStr); |
||||
|
console.log('解析到 JSON 二维码:', scanObj); |
||||
|
// TODO: 对接服务端 checkIsState 接口 |
||||
|
// checkIsState(scanObj.salt, scanObj.codeId, (res) => { ... }); |
||||
|
showToast('识别到执法码二维码,服务端接口待对接'); |
||||
|
} catch (e) { |
||||
|
console.warn('非 JSON 格式,原始内容:', resultStr); |
||||
|
showFailToast('无法识别该二维码'); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
.scanner-tip { |
||||
|
padding: 0.25rem 0; |
||||
|
text-align: center; |
||||
|
font-size: 0.32rem; |
||||
|
color: #999; |
||||
|
} |
||||
</style> |
</style> |
||||
|
|||||
Loading…
Reference in new issue