This commit is contained in:
@jyuhsin
2026-01-04 14:28:56 +08:00
parent d2cdc72f3c
commit 1f5f221850
8 changed files with 534 additions and 2 deletions
@@ -67,6 +67,9 @@
<!--g2plot-->
<script src="${base!}/assets/platform/plugins/g2plot/g2plot.min.js"></script>
<!--二维码-->
<script src="${base!}/assets/platform/plugins/vue-qrcode/index.js"></script>
<script>
window._AMapSecurityConfig = {
securityJsCode: "4fa1e1aeabba7eb9518129cf57ab17c1"
@@ -262,6 +265,7 @@
</script>
<script>
Vue.component(VueQrcode.name, VueQrcode)
Vue.component("rich-text", httpVueLoader("/components/plugins/sysRichTextView/index.vue?v=" + new Date().getTime()))
Vue.component("h5-file-upload", httpVueLoader("/components/plugins/sysUpload/h5Index.vue?v=" + new Date().getTime()))
Vue.component("h5-signature", httpVueLoader("/components/plugins/sysSignature/h5Index.vue?v=" + new Date().getTime()))
@@ -0,0 +1,272 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<link rel="stylesheet" href="https://res.wx.qq.com/open/libs/weui/2.2.0/weui.min.css">
<style>
#app {
min-height: 100vh;
background: #f7f8fa;
}
.scan-container {
width: 100%;
height: 65vh;
position: relative;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
/* 扫描框 - 更大、圆角、柔和边框 */
.scan-box {
position: absolute;
width: 260px;
height: 260px;
border: 2px solid #1989fa;
border-radius: 16px;
z-index: 2;
}
/* 扫描线 - 渐变色 + 柔和动画 */
.scan-line {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(to right, transparent, #1989fa, transparent);
animation: scan 2.5s ease-in-out infinite;
z-index: 3;
}
@keyframes scan {
0% { top: 0; opacity: 0.8; }
50% { opacity: 1; }
100% { top: 257px; opacity: 0.8; }
}
.footer-container {
position: fixed;
bottom: 16px;
width: 100%;
}
.footer-container .van-button--block {
width: 90%;
margin: 0 auto 6px;
height: 50px;
border-radius: 12px;
}
#reader {
width: 90%;
height: 60vh;
margin: 10px auto 40px;
text-align: center;
position: relative;
overflow: hidden;
}
#reader video {
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
}
.overlay {
position: absolute;
top: 0; left: 0;
width: 100%; height: 65vh;
pointer-events: none;
z-index: 10;
}
.weui-msg__title {
font-weight: normal;
}
.weui-msg__msg {
font-weight: bolder;
}
.weui-msg {
padding-top: 20px;
}
.weui-msg__icon-area {
margin-bottom: 20px;
}
</style>
<div id="app" v-cloak>
<van-sticky>
<van-nav-bar title="扫一扫" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
</van-sticky>
<div v-if="!scanStatus" class="scan-container overlay">
<!-- 扫描框 -->
<div class="scan-box">
<div class="scan-line"></div>
</div>
<!-- 初始图标(仅在未启动时显示) -->
<van-icon v-if="!scanStatus" name="scan" size="56" color="#1989fa" style="z-index: 2;"></van-icon>
</div>
<div id="reader"></div>
<div class="footer-container">
<van-button v-if="scanStatus" @click="closeScan" block color="#006DB9">关闭扫描</van-button>
<van-button v-else @click="initCamera" block color="#006DB9">点击扫描</van-button>
</div>
<van-dialog v-model="resultVisible"
@confirm="resultConfirm"
@cancel="isProcessing = false; initCamera()"
:confirm-button-text="result?.code === 0 ? '确认入场' : '确认'"
:show-cancel-button="result?.code === 0"
cancel-button-text="取消入场"
>
<div class="weui-msg">
<div class="weui-msg__icon-area">
<i v-if="result?.code === 0" class="weui-icon-success weui-icon_msg"></i>
<i v-else class="weui-icon-warn weui-icon_msg"></i>
</div>
<div class="weui-msg__text-area">
<div class="weui-msg__title">查询结果</div>
<div class="weui-msg__msg" v-html="result?.msg"></div>
</div>
</div>
</van-dialog>
</div>
<script src="${base!}/assets/platform/plugins/html5-qrcode/html5-qrcode.min.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
scanStatus: false,
html5QrCode: null,
isProcessing: false, // 防止重复处理
resultVisible: false,
result: null,
decodedText: null,
}
},
methods: {
async initCamera() {
try {
const devices = await Html5Qrcode.getCameras()
if (!devices || devices.length === 0) {
vant.Dialog.alert({
title: '提示',
message: '未检测到摄像头,请检查设备权限或尝试其他浏览器'
}).then(() => {})
return;
}
let cameraId
if (devices.length > 1) {
cameraId = devices[1].id;
} else {
cameraId = devices[0].id;
}
let isHuawei = navigator.userAgent.toLowerCase().match(/huawei/i) == 'huawei';
const backCamera = devices.find(d => /back|rear/i.test(d.label));
if (isHuawei && backCamera) cameraId = backCamera.id;
await this.startScanning(cameraId);
} catch (err) {
console.error('摄像头初始化失败:', err);
let msg = '请允许访问摄像头权限';
if (err.name !== 'NotAllowedError') {
msg = '摄像头启动失败,请重试或更换设备';
}
vant.Dialog.alert({
title: '权限提示',
message: msg
}).then(() => {})
}
},
async startScanning(cameraId) {
this.html5QrCode = new Html5Qrcode("reader");
this.scanStatus = true;
try {
await this.html5QrCode.start(
cameraId,
{
fps: 300,
qrbox: { width: 300, height: 300 },
aspectRatio: 1.0
},
(decodedText) => {
this.decodedText = decodedText
this.onHandle(decodedText)
},
(errorMessage) => {}
);
} catch (err) {
console.error('扫描启动失败:', err);
vant.Dialog.alert({
title: '扫描失败',
message: '无法启动扫码功能,请刷新页面重试'
}).then(() => {})
}
},
async closeScan() {
if (!this.html5QrCode || !this.scanStatus) return;
try {
await this.html5QrCode.stop();
this.scanStatus = false;
this.html5QrCode = null;
} catch (err) {
console.error('关闭失败:', err);
}
},
async onHandle(decodedText) {
// 防重:如果正在处理,直接忽略
if (this.isProcessing) return;
// 立即设置为处理中,并停止扫描
this.isProcessing = true;
await this.closeScan()
const { bizType, userId } = JSON.parse(decodedText)
this.result = await this.$axios.post('/platform/scan/handle', {
bizType: bizType,
params: JSON.stringify({ userId: userId, handle: false }),
})
this.resultVisible = true
},
resultConfirm() {
const { bizType, userId } = JSON.parse(this.decodedText)
this.$axios.post('/platform/scan/handle', {
bizType: bizType,
params: JSON.stringify({ userId: userId, handle: true }),
})
this.isProcessing = false;
this.initCamera()
}
},
async created() {
},
beforeDestroy() {
if (this.scanStatus) {
this.closeScan();
}
}
});
</script>
<!--#
}
#-->
@@ -79,6 +79,13 @@ const mine = {
</span>
</van-cell>
<van-cell is-link @click="qrCode" class="action-cell" v-if="codeValid()">
<span class="cell-title" slot="title">
<van-icon name="apps-o" class="cell-icon"></van-icon>
电子身份码
</span>
</van-cell>
<!-- <van-cell :value="$store.state.user.totalIntegral" is-link @click="$pjaxReplace('/platform/integral/records/h5')" class="action-cell">-->
<!-- <span class="cell-title" slot="title">-->
<!-- <van-icon name="points" class="cell-icon"></van-icon>-->
@@ -91,7 +98,12 @@ const mine = {
<div class="logout-wrapper">
<van-button round block color="#ee0a24" @click="logout" class="logout-btn">退出登录</van-button>
</div>
<van-action-sheet v-model="codeVisible" cancel-text="取消" title="电子身份码">
<qrcode :options="{ width: 300 }" :value="codeAddress" class="qrcode"></qrcode>
</van-action-sheet>
</div>
`,
computed: {
avatar() {
@@ -103,6 +115,13 @@ const mine = {
}
}
},
store,
data() {
return {
codeVisible: false,
codeAddress: '',
}
},
methods: {
logout() {
this.$dialog
@@ -113,10 +132,27 @@ const mine = {
.then(() => {
this.$store.dispatch("logout")
})
}
},
codeValid() {
if(this.$store.state.user.member === true || ['年薪制', '境外聘用', '劳务派遣一类', '劳务派遣二类'].includes(this.$store.state.user.preparedBy)) {
return true
}
return false
},
qrCode() {
this.codeVisible = true
this.codeAddress = JSON.stringify({userId: this.$store.state.user.id, bizType: 'culture'})
},
},
style() {
return /*language=CSS*/ `
.qrcode {
transition: opacity 0.5s ease;
display: block;
margin: 0 auto;
}
.home-mine {
background: #f7f8fa;
}