5 changed files with 609 additions and 157 deletions
@ -0,0 +1,548 @@ |
|||
<!-- 跳转企业二维码load页面 --> |
|||
<template> |
|||
<div class="home app_content"> |
|||
<!-- 中间 --> |
|||
<div class="warp_body"> |
|||
<div class="white-body"> |
|||
<div class="boutique_box"> |
|||
<div class="box-qr-title">执法监督码</div> |
|||
<div class="boutique_body"> |
|||
<div class="box-qr-code"> |
|||
<img class="box-qr-code-img" :src="data.qrCodeImg" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="Home"> |
|||
import Vconsole from "vconsole"; |
|||
import {showFailToast,showToast} from 'vant'; |
|||
import {checkIsState,getEnterpriseInfoBySj} from '@/api/enforce'; |
|||
import {szyptGetToken} from "@/api/Home"; |
|||
import { baseUrl } from "../../config/env"; |
|||
import wx from "weixin-js-sdk"; |
|||
|
|||
const {useCommon} = $globalStore; |
|||
const router = useRouter(); |
|||
//路由参数 |
|||
const route = useRoute(); |
|||
const intervalId = ref(null); |
|||
const data = reactive({ |
|||
showNoScanPicker: ref(false), |
|||
qrCodeImg: baseUrl + "/profile/upload/2024/11/29/03043b8531b8e1aad5a0f72d2fc605f0.jpg", |
|||
}); |
|||
const indexSens = ref(3); |
|||
const butText = ref("点击跳转企业画像....,"+indexSens.value+"秒后自动跳转"); |
|||
|
|||
|
|||
const openScan = () => { |
|||
let fullUrl = window.location.href; |
|||
let code = fullUrl.substring(fullUrl.indexOf('code=') + 5, fullUrl.indexOf('&state=')); |
|||
let paramUrl = window.location.href.split("#")[0]; |
|||
let param = {code: code, url: paramUrl}; |
|||
console.log(paramUrl); |
|||
szyptGetToken(param, success => { |
|||
let resMsg = success.data; |
|||
console.log(resMsg); |
|||
wx.config({ |
|||
// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 |
|||
debug: false, |
|||
// 必填,公众号的唯一标识 |
|||
appId: resMsg.corpid, |
|||
// 必填,生成签名的时间戳 |
|||
timestamp: resMsg.timestamp, |
|||
// 必填,生成签名的随机串 |
|||
nonceStr: resMsg.nonceStr, |
|||
// 必填,签名,见附录1 |
|||
signature: resMsg.signature, |
|||
// 必填,需要使用的JS接口列表,所有JS接口列表见附录2 |
|||
jsApiList: ["scanQRCode"], |
|||
}); |
|||
wx.ready(() => { |
|||
//微信扫一扫请求操作 |
|||
ClickScan() |
|||
}); |
|||
wx.error((res) => { |
|||
console.log("获取失败"); |
|||
}); |
|||
}, error => { |
|||
}); |
|||
}; |
|||
const ClickScan = () => { |
|||
wx.scanQRCode({ |
|||
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果, |
|||
scanType: ['qrCode', 'barCode'], // 可以指定扫二维码还是一维码,默认二者都有 |
|||
success: (res) => { |
|||
// 页面跳转 |
|||
console.log("=========== 扫码返回 ===========") |
|||
console.log(res) |
|||
//TODO 判断是否是市监 |
|||
if(isHttpLink(res.resultStr)){ |
|||
//判断地址是否存在cid统一信用代码 |
|||
getEnterpriseInfoBySj({"url":res.resultStr},success =>{ |
|||
if (success.success) { |
|||
var element = success.data; |
|||
//TODO 添加延时弹出监督码 |
|||
showToast({ |
|||
duration: 3000, |
|||
message:"企业监督码", |
|||
icon: baseUrl + element.qrCodeGenerateHistory.qrCodeUrl, |
|||
iconSize:"100px", |
|||
onClose: openEnterpriseView(element.enterpriseNumber) |
|||
}) |
|||
|
|||
} else { |
|||
showFailToast(success.message); |
|||
router.push({ |
|||
path: "/enforce/scanQrCode", |
|||
}) |
|||
} |
|||
},error =>{}) |
|||
}else{ |
|||
const scanObj = JSON.parse(res.resultStr); |
|||
// 判断二维码是否过期, 如果已过期则跳转提示页 |
|||
checkIsState(scanObj.salt, scanObj.codeId, success => { |
|||
console.log(success) |
|||
if (success.data) { |
|||
router.push({ |
|||
path: "/enterpriseScanPreview", |
|||
query: { |
|||
enterpriseNumber: scanObj.salt, |
|||
isNoScan: false |
|||
} |
|||
}) |
|||
} else { |
|||
showFailToast(success.message); |
|||
router.push({ |
|||
path: "/enforce/scanQrCode", |
|||
}) |
|||
} |
|||
}, error => { |
|||
showFailToast("该二维码不正确"); |
|||
}) |
|||
} |
|||
}, |
|||
error: function(res) { |
|||
console.log("错误回调",res) |
|||
if (res.errMsg.indexOf('function_not_exist') > 0) { |
|||
showFailToast('版本过低请升级') |
|||
} |
|||
}, |
|||
cancel: (res) => { |
|||
console.log('err', res); |
|||
}, |
|||
|
|||
}); |
|||
}; |
|||
|
|||
const toScanPageView = async () => { |
|||
openScan(); |
|||
}; |
|||
onMounted(() => { |
|||
// new Vconsole(); |
|||
startTimer(route?.query?.enterpriseNumber); |
|||
console.log("企业统一信用代码:",route?.query?.enterpriseNumber) |
|||
console.log("企业二维码地址:",route?.query?.qrCodeUrl) |
|||
}) |
|||
//定时器 |
|||
const startTimer = (enterpriseNumber) => { |
|||
// 假设我们每隔1秒钟执行一些操作 |
|||
intervalId.value = setInterval(() => { |
|||
console.log('定时器执行中...'); |
|||
// 执行一些定时器相关的操作 |
|||
indexSens.value--; |
|||
if(indexSens.value >= 0){ |
|||
console.log("加减",indexSens.value) |
|||
butText.value = "点击跳转企业画像....,"+indexSens.value+"秒后自动跳转"; |
|||
}else{ |
|||
console.log("清除定时器") |
|||
console.log("清除定时器",enterpriseNumber) |
|||
clearInterval(intervalId.value); |
|||
router.push({ |
|||
path: "/enterpriseScanPreview", |
|||
query: { |
|||
enterpriseNumber: enterpriseNumber, |
|||
isNoScan: false |
|||
} |
|||
}) |
|||
} |
|||
}, 1000); |
|||
}; |
|||
|
|||
// 返回 |
|||
const goBack = () => { |
|||
router.go(-1); |
|||
}; |
|||
|
|||
const isHttpLink = (url) =>{ |
|||
return url.indexOf("http://") === 0 || url.indexOf("https://") === 0; |
|||
} |
|||
const openEnterpriseView = (enterpriseNumber) =>{ |
|||
console.log("外层获取企业统一信用代码",enterpriseNumber); |
|||
setTimeout(() =>{ |
|||
console.log("定时内获取企业统一信用代码",enterpriseNumber); |
|||
router.push({ |
|||
path: "/enterpriseScanPreview", |
|||
query: { |
|||
enterpriseNumber: enterpriseNumber, |
|||
isNoScan: false |
|||
} |
|||
}) |
|||
}, 3000); |
|||
} |
|||
onUnmounted(() => { |
|||
// 清除定时器 |
|||
if (intervalId.value) { |
|||
clearInterval(intervalId.value); |
|||
} |
|||
}); |
|||
</script> |
|||
<script> |
|||
//不扫码企业筛选弹窗 |
|||
import Popup from '@/views/enforce/noScanFilter.vue'; |
|||
import { baseUrl } from "../../config/env"; |
|||
|
|||
export default { |
|||
components: { |
|||
Popup |
|||
}, |
|||
data() { |
|||
return { |
|||
popupVisible: false, |
|||
popupContent: '' |
|||
} |
|||
}, |
|||
methods: { |
|||
showPopup() { |
|||
this.popupVisible = true; |
|||
}, |
|||
closePopup() { |
|||
this.popupVisible = false; |
|||
} |
|||
} |
|||
} |
|||
</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); |
|||
|
|||
.carousel_box { |
|||
position: relative; |
|||
top: 2%; |
|||
height: 4rem; |
|||
width: 100%; |
|||
background: url("/src/assets/images/scan-mean.jpg") no-repeat center center; |
|||
background-size: 60%; |
|||
opacity: 0.75; |
|||
|
|||
.box-scan-one, .box-scan-two { |
|||
position: absolute; |
|||
top: 30%; |
|||
left: 15%; |
|||
height: 2.67rem; |
|||
width: 30%; |
|||
font-size: 0.4rem; |
|||
color: #999; |
|||
|
|||
.box-scan-text { |
|||
margin-top: 70%; |
|||
display: block; |
|||
width: 100%; |
|||
text-align: center |
|||
} |
|||
} |
|||
|
|||
.box-scan-two { |
|||
left: auto; |
|||
right: 12.5%; |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
.boutique_box { |
|||
position: relative; |
|||
top: 5%; |
|||
z-index: 100; |
|||
width: 100%; |
|||
|
|||
.boutique_body { |
|||
padding: 0 0 0.27rem 0; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
; |
|||
|
|||
.box-qr-code { |
|||
width: 80%; |
|||
text-align: right; |
|||
height: 7rem; |
|||
//background-color: var(--van-primary-color); |
|||
// background: url("../../assets/images/qrCode.jpg") no-repeat center center; |
|||
background-size: 100%; |
|||
// border-radius: 0.27rem; |
|||
// background-size: cover; |
|||
// border-radius: 50%; |
|||
// background-image: url("@/assets/images/scan.png"); |
|||
.box-qr-code-text { |
|||
margin-right: 25%; |
|||
margin-top: 70%; |
|||
display: block; |
|||
font-size: 0.45rem; |
|||
} |
|||
.box-qr-code-img{ |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
// img { |
|||
// position: absolute; |
|||
// top: 0%; |
|||
// left: 34%; |
|||
// } |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
.white-body { |
|||
position: relative; |
|||
margin: 0 3%; |
|||
width: 94%; |
|||
height: 99%; |
|||
background-color: #fff; |
|||
border-radius: 0.27rem; |
|||
} |
|||
.box-qr-title{ |
|||
margin: 3% 0 0; |
|||
width: 90%; |
|||
font-size: 0.42rem; |
|||
text-align: center; |
|||
transform: translate(5%, 5%); |
|||
line-height: 3; |
|||
} |
|||
|
|||
.nocode { |
|||
margin: 3% 0 0; |
|||
width: 90%; |
|||
font-size: 0.42rem; |
|||
text-align: center; |
|||
line-height: 3; |
|||
background-color: #619cd9; |
|||
transform: translate(5%, 5%); |
|||
border-radius: 6px; |
|||
} |
|||
|
|||
.scan-note { |
|||
width: 100%; |
|||
font-size: 0.45rem; |
|||
text-align: center; |
|||
line-height: 2; |
|||
margin-top: 2%; |
|||
} |
|||
|
|||
.popup { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background: rgba(0, 0, 0, 0.35); |
|||
} |
|||
|
|||
.popup-content { |
|||
position: fixed; |
|||
z-index: 999; |
|||
bottom: 0; |
|||
left: 0; |
|||
padding: 0 3%; |
|||
width: 94%; |
|||
height: 35vh; |
|||
background: #fff; |
|||
overflow-y: auto; |
|||
font-size: 0.37rem; |
|||
} |
|||
|
|||
.subtitle { |
|||
margin: 5% 2% 2%; |
|||
width: 96%; |
|||
/*font-size: 0.4rem; |
|||
font-weight: 600;*/ |
|||
border-bottom: 1px solid #f4f4f4; |
|||
line-height: 2; |
|||
} |
|||
|
|||
.item-btn { |
|||
float: left; |
|||
margin: 0.3rem 0.2rem; |
|||
padding: 0.2rem 0.3rem; |
|||
background-color: #f2f3f4; |
|||
color: #999; |
|||
border-radius: 1rem; |
|||
font-size: 0.35rem; |
|||
} |
|||
|
|||
.item-btn.active { |
|||
background-color: #e9f2fb; |
|||
color: #1367fe; |
|||
} |
|||
|
|||
.btnbox { |
|||
display: flex; |
|||
justify-content: center; |
|||
position: absolute; |
|||
bottom: 1rem; |
|||
left: 5%; |
|||
width: 90%; |
|||
height: 1rem; |
|||
} |
|||
|
|||
.btn-reset { |
|||
display: inline-block; |
|||
margin: 0 2%; |
|||
padding: 0; |
|||
width: 60%; |
|||
color: #fff; |
|||
font-size: 0.35rem; |
|||
border-radius: 1rem; |
|||
background: #1367fe; |
|||
border: 1px solid #1367fe; |
|||
text-align: center; |
|||
} |
|||
|
|||
.btn-search { |
|||
display: inline-block; |
|||
margin: 3% 7%; |
|||
padding: 0; |
|||
width: 70%; |
|||
color: #fff; |
|||
font-size: 0.35rem; |
|||
border-radius: 1rem; |
|||
background: #1367fe; |
|||
border: 1px solid #1367fe; |
|||
text-align: center; |
|||
height: 90%; |
|||
} |
|||
|
|||
.btn-reset { |
|||
width: 35%; |
|||
background-color: #fff; |
|||
border-color: rgba(19, 103, 254, .7); |
|||
color: #1367fe; |
|||
} |
|||
|
|||
.close-button { |
|||
position: absolute; |
|||
top: 0.15rem; |
|||
right: 0.1rem; |
|||
width: 0.8rem; |
|||
height: 0.8rem; |
|||
background-color: rgba(255, 255, 255, .5); |
|||
border: 0.05rem solid rgba(255, 255, 255, .7); |
|||
border-radius: 50%; |
|||
transform: rotate(45deg); |
|||
} |
|||
|
|||
.close-button:before, |
|||
.close-button:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
background: #333; |
|||
border-radius: 50%; |
|||
transform: translate(-50%, -50%); |
|||
height: 0.4rem; |
|||
width: 0.02rem; |
|||
} |
|||
|
|||
.close-button:after { |
|||
height: 0.02rem; |
|||
width: 0.4rem; |
|||
} |
|||
|
|||
.nav-div { |
|||
display: flex; |
|||
margin: 15% 2% 0; |
|||
width: 96%; |
|||
color: #287eef; |
|||
font-size: 0.4rem; |
|||
line-height: 2; |
|||
text-align: center; |
|||
} |
|||
|
|||
.nav-div > div { |
|||
margin: 2% 3%; |
|||
width: 45%; |
|||
} |
|||
|
|||
.nav-div div span{ |
|||
display: block; |
|||
} |
|||
|
|||
.nav-div div i { |
|||
margin: 0.35rem; |
|||
width: 1.5rem; |
|||
height: 1.5rem; |
|||
font-size: 0.8rem; |
|||
background: #e6eff7; |
|||
border-radius: 50%; |
|||
text-align: center; |
|||
line-height: 1.85; |
|||
} |
|||
|
|||
.nav-div .act-note { |
|||
margin: 10% 0 0; |
|||
width: 100%; |
|||
font-size: 0.3rem; |
|||
color: #d3d0d0; |
|||
line-height: 1.5; |
|||
text-align: left; |
|||
} |
|||
|
|||
.addinfo { |
|||
position: absolute; |
|||
bottom: 2rem; |
|||
right: 2%; |
|||
z-index: 999; |
|||
width: 1.5rem; |
|||
height: 1.5rem; |
|||
background-color: #e0e9f9; |
|||
border-radius: 50%; |
|||
box-shadow: 0 0 0.27rem #90b6f9; |
|||
color: #0240f7; |
|||
font-size: 0.4rem; |
|||
text-align: center; |
|||
vertical-align: middle; |
|||
padding-top: 0.45rem; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.record { |
|||
position: absolute; |
|||
bottom: 2rem; |
|||
color: #bbbaba; |
|||
font-size: 0.35rem; |
|||
width: 100%; |
|||
text-align: center; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.record a { |
|||
color: #287eef; |
|||
} |
|||
|
|||
</style> |
|||
|
|||
@ -1,135 +0,0 @@ |
|||
<style scoped> |
|||
.scan-area { |
|||
width: 100%; |
|||
height: 18.15rem; |
|||
} |
|||
</style> |
|||
|
|||
<template> |
|||
<div class="scan-area"> |
|||
<qrcode-stream @click="handleClick" :paused="scanPause" :track="paintOutLineAndCenterText" @error="handleError" @camera-on="onCameraOn"> |
|||
<div v-if="!cameraReady"> |
|||
loading... |
|||
</div> |
|||
</qrcode-stream> |
|||
<!-- <audio ref="audioRef" src="/src/assets/Di.mp3"></audio> --> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// import Vconsole from "vconsole"; |
|||
import { QrcodeStream } from "vue-qrcode-reader"; |
|||
import {ref} from 'vue' |
|||
const audioRef = ref(null); |
|||
const errorInfo = ref(''); |
|||
const cameraReady = ref(false); |
|||
const scanPause = ref(false); |
|||
const reactAngles = ref<any>([]); |
|||
const onCameraOn = function () { |
|||
cameraReady.value = true; |
|||
// new Vconsole(); |
|||
} |
|||
const handleError = function (err) { |
|||
errorInfo.value = `[${err.name}]: ` |
|||
if (err.name === 'NotAllowedError') { |
|||
errorInfo.value += '您需要授予相机访问权限' |
|||
} else if (err.name === 'NotFoundError') { |
|||
errorInfo.value = '此设备上没有检测到摄像头'; |
|||
} else if (err.name === 'NotReadableError') { |
|||
errorInfo.value = '相机被占用' |
|||
} else if (err.name === 'NotSupportedError') { |
|||
errorInfo.value = 'secure context required (HTTPS, localhost)' |
|||
} else if (err.name === 'OverconstrainedError') { |
|||
errorInfo.value += '' |
|||
} else if (err.name === 'StreamApiNotSupportedError') { |
|||
errorInfo.value += 'Stream API is not supported in this browser' |
|||
} else if (err.name === 'InsecureContextError') { |
|||
errorInfo.value += 'Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.' |
|||
} else { |
|||
errorInfo.value += err.message |
|||
} |
|||
console.log(errorInfo.value); |
|||
errorInfo.value = ''; |
|||
} |
|||
|
|||
const paintOutLineAndCenterText = (detectedCodes, ctx) => { |
|||
// 暂停页面 |
|||
scanPause.value = true; |
|||
// 播放语音 |
|||
// audioRef.value.play(); |
|||
// 画边框和文本 |
|||
reactAngles.value.splice(); |
|||
for (const detectedCode of detectedCodes) { |
|||
// 画边框 |
|||
ctx.lineWidth = 1; |
|||
const [firstPoint, ...otherPoints] = detectedCode.cornerPoints |
|||
ctx.strokeStyle = 'red' |
|||
ctx.beginPath() |
|||
ctx.moveTo(firstPoint.x, firstPoint.y) |
|||
for (const { x, y } of otherPoints) { |
|||
ctx.lineTo(x, y) |
|||
} |
|||
ctx.lineTo(firstPoint.x, firstPoint.y) |
|||
ctx.closePath(); |
|||
ctx.stroke(); |
|||
// 画文字 |
|||
const { boundingBox, rawValue } = detectedCode |
|||
const centerX = boundingBox.x + boundingBox.width / 2 |
|||
const centerY = boundingBox.y + boundingBox.height / 2 |
|||
const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width) |
|||
console.log(boundingBox.width, ctx.canvas.width) |
|||
ctx.lineWidth = 2; |
|||
ctx.font = `${fontSize}px sans-serif` |
|||
ctx.textAlign = 'center' |
|||
ctx.strokeStyle = '#35e5a7' |
|||
ctx.strokeText('>', centerX, centerY) |
|||
// 配置矩形内容 |
|||
if (detectedCode.cornerPoints.length==4) { |
|||
reactAngles.value.push({ |
|||
leftTop: {x: firstPoint.x, y:firstPoint.y}, |
|||
rightTop: {x: otherPoints[0].x, y:otherPoints[0].y}, |
|||
rightBottom: {x: otherPoints[1].x, y:otherPoints[1].y}, |
|||
leftBottom: {x: otherPoints[2].x, y:otherPoints[2].y}, |
|||
text: detectedCode.rawValue |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
// 获取事件的发生位置 |
|||
const getEventPosition = (ev) => { |
|||
var x, y; |
|||
if (ev.layerX || ev.layerX == 0) { |
|||
x = ev.layerX; |
|||
y = ev.layerY; |
|||
} else if (ev.offsetX || ev.offsetX == 0) { // Opera |
|||
x = ev.offsetX; |
|||
y = ev.offsetY; |
|||
} |
|||
return {x: x, y: y}; |
|||
}; |
|||
|
|||
const handleClick = function (event) { |
|||
// 1. 获取点击点的位置 |
|||
var eventPosition = getEventPosition(event); |
|||
// 2. 判断在不在已有的矩形内部,在的话获取二维码解析的结果,并进行业务处理 |
|||
var resolveText = handleClickRectAngle(eventPosition); |
|||
if (resolveText != undefined) { |
|||
alert(resolveText); |
|||
} |
|||
// 3. 业务处理完,如果决定跳转就跳转,不决定跳转的话就关闭暂停 |
|||
scanPause.value = false; |
|||
} |
|||
|
|||
// 判断一个点在不在某个矩形内部的算法,后面可以采取凸包算法,优化判断的实现,不过这里已经能实现了,没必要深究 |
|||
const handleClickRectAngle = function (eventPosition) { |
|||
for (let i = 0; i < reactAngles.value.length; i++) { |
|||
let reactAngle = reactAngles.value[i]; |
|||
if ( ((eventPosition.x >= Math.min(reactAngle.leftTop.x, reactAngle.leftBottom.x)) && (eventPosition.x <= Math.max(reactAngle.rightTop.x, reactAngle.rightBottom.x))) |
|||
&& ((eventPosition.y >= Math.min(reactAngle.leftTop.y, reactAngle.rightTop.y)) && (eventPosition.y <= Math.max(reactAngle.leftBottom.y, reactAngle.rightBottom.y))) |
|||
) { |
|||
return reactAngle.text; |
|||
} |
|||
} |
|||
return undefined; |
|||
} |
|||
</script> |
|||
Loading…
Reference in new issue