Files
UNION_NJUST/target/classes/views/mobile/question/scan.html
T
2026-09-23 09:38:39 +08:00

679 lines
19 KiB
HTML

<!--#
layout("/mobile/platform.html"){
#-->
<style>
.page-container {
background: #f7f8fa;
min-height: 100vh;
padding-bottom: 80px;
}
.van-dropdown-menu {
position: fixed;
top: 46px;
left: 0;
right: 0;
z-index: 101;
background: #fff;
}
.van-dropdown-menu__item {
flex: 1;
flex-shrink: unset;
}
.fixed-header {
position: fixed;
top: 90px;
left: 0;
right: 0;
z-index: 99;
background: #f7f8fa; /* 与页面背景色保持一致 */
}
.fixed-header::after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
right: 0;
height: 10px;
background: linear-gradient(to bottom, rgba(247, 248, 250, 1), rgba(247, 248, 250, 0));
pointer-events: none;
}
.search-bar {
background: #fff;
padding: 8px 16px;
}
.staff-card {
background: #fff;
border-radius: 8px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.card-header {
display: flex;
align-items: center;
padding: 16px;
border-bottom: 1px solid #f5f5f5;
}
.staff-name {
font-size: 16px;
font-weight: 500;
color: #323233;
margin-right: auto;
}
.card-content {
padding: 16px;
}
.card-content .info-item {
display: flex;
margin-bottom: 8px;
font-size: 14px;
color: #646566;
}
.info-label {
width: 70px;
color: #969799;
}
.card-container .receive-time {
font-size: 14px;
color: #07c160;
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed #ebedf0;
}
.stats-panel {
padding: 12px 16px;
}
.stats-content {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
padding: 16px 0;
}
.stats-item {
text-align: center;
position: relative;
}
.stats-item:not(:last-child):after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 60%;
width: 1px;
background: #ebedf0;
}
.stats-number {
font-size: 20px;
font-weight: bold;
color: #323233;
margin-bottom: 4px;
}
.stats-label {
font-size: 12px;
color: #969799;
}
.total-number {
color: #1989fa;
}
.received-number {
color: #07c160;
}
.pending-number {
color: #ff976a;
}
.card-container {
margin-top: 210px;
padding: 16px;
position: relative;
z-index: 1;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 10px 16px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
z-index: 99;
}
.scan-button {
border-radius: 24px;
height: 44px;
line-height: 44px;
font-size: 16px;
box-shadow: 0 2px 8px rgba(25, 137, 250, 0.2);
}
.scan-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: #000;
z-index: 9999;
}
/* 导航栏样式覆盖 */
.scan-wrapper .van-nav-bar {
background-color: transparent !important;
}
.scan-wrapper .van-nav-bar__title {
color: #fff !important;
}
.scan-wrapper .van-nav-bar__text {
color: #fff !important;
}
#reader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
}
/* 确保扫描区域在中间 */
#reader video {
object-fit: cover;
width: 100%;
height: 100%;
}
.confirm-dialog {
padding: 20px;
}
.dialog-title {
font-size: 18px;
font-weight: 600;
text-align: center;
color: #323233;
margin-bottom: 20px;
}
.dialog-content {
margin-bottom: 24px;
}
.confirm-dialog .info-item {
display: flex;
margin-bottom: 12px;
font-size: 14px;
line-height: 20px;
}
.confirm-dialog .info-item .label {
width: 80px;
color: #969799;
}
.confirm-dialog .info-item .value {
flex: 1;
color: #323233;
}
.dialog-footer {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.dialog-footer .van-button {
height: 40px;
font-size: 16px;
}
.confirm-dialog .receive-time {
color: #969799;
font-size: 13px;
}
.confirm-dialog .van-tag {
padding: 2px 8px;
}
.confirm-dialog .van-button--disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
<div id="app" v-cloak>
<div v-if="!scanStatus">
<van-nav-bar title="发放奖品" left-arrow placeholder fixed @click-left="history.go(-1)"></van-nav-bar>
<!-- 问卷下拉框 -->
<van-dropdown-menu>
<van-dropdown-item v-model="pageForm.questionId" :options="questionOptions"
@change="onSearch"></van-dropdown-item>
</van-dropdown-menu>
<!-- 搜索栏 -->
<div class="fixed-header">
<div class="search-bar">
<van-search
v-model="pageForm.searchKeyword"
placeholder="请输入姓名或工号"
@search="onSearch"
shape="round"
></van-search>
</div>
<!-- 统计面板 -->
<div class="stats-panel">
<div class="stats-content">
<div class="stats-grid">
<div class="stats-item">
<div class="stats-number total-number">{{totalCount}}</div>
<div class="stats-label">总人数</div>
</div>
<div class="stats-item">
<div class="stats-number received-number">{{receivedCount}}</div>
<div class="stats-label">已领取</div>
</div>
<div class="stats-item">
<div class="stats-number pending-number">{{pendingCount}}</div>
<div class="stats-label">未领取</div>
</div>
</div>
</div>
</div>
</div>
<!-- 人员 -->
<div class="card-container">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div
v-for="item in staffList"
:key="item.id"
class="staff-card"
>
<div class="card-header">
<span class="staff-name">{{item.userName}}</span>
<van-tag
:type="item.isReceive ? 'success' : 'warning'"
round
size="large"
>
{{item.isReceive ? '已领取' : '未领取'}}
</van-tag>
</div>
<div class="card-content">
<div class="info-item">
<span class="info-label">工号:</span>
<span>{{item.loginName}}</span>
</div>
<div class="info-item">
<span class="info-label">部门:</span>
<span>{{item.unitName}}</span>
</div>
<div class="receive-time" v-if="item.isReceive">
领取时间:{{item.receiveTime}}
</div>
</div>
</div>
</van-list>
</div>
<!-- 底部扫描按钮 -->
<div class="bottom-bar">
<van-button
type="primary"
block
class="scan-button"
@click="startScan"
icon="scan"
color="#1867b0"
>
扫描二维码发放奖品
</van-button>
</div>
</div>
<!-- 确认发放弹窗 -->
<van-popup
v-model="showConfirmDialog"
round
position="center"
:style="{ width: '80%' }"
>
<div class="confirm-dialog">
<!-- 标题 -->
<div class="dialog-title">
确认发放奖品
</div>
<!-- 内容区 -->
<div class="dialog-content" v-if="currentStaff">
<div class="info-item">
<span class="label">姓名:</span>
<span class="value">{{currentStaff.userName}}</span>
</div>
<div class="info-item">
<span class="label">工号:</span>
<span class="value">{{currentStaff.loginName}}</span>
</div>
<div class="info-item">
<span class="label">单位:</span>
<span class="value">{{currentStaff.unitName}}</span>
</div>
<div class="info-item">
<span class="label">工会:</span>
<span class="value">{{currentStaff.unionName}}</span>
</div>
<!-- 添加领取状态 -->
<div class="info-item">
<span class="label">状态:</span>
<span class="value">
<van-tag :type="currentStaff.isReceive ? 'success' : 'warning'" round>
{{currentStaff.isReceive ? '已领取' : '未领取'}}
</van-tag>
</span>
</div>
<!-- 如果已领取则显示领取时间 -->
<div class="info-item" v-if="currentStaff.isReceive">
<span class="label">领取时间:</span>
<span class="value">{{currentStaff.receiveTime}}</span>
</div>
</div>
<!-- 按钮区 -->
<div class="dialog-footer">
<van-button
plain
round
block
@click="showConfirmDialog = false"
>
取消
</van-button>
<van-button
type="primary"
round
block
@click="confirmReceive"
:loading="confirmLoading"
:disabled="currentStaff.isReceive"
>
确认发放
</van-button>
</div>
</div>
</van-popup>
<!-- 扫描界面 -->
<div v-if="scanStatus" class="scan-wrapper">
<div class="scan-header">
<van-nav-bar
title="扫描二维码"
left-text="返回"
left-arrow
@click-left="exitScan"
:border="false"
fixed
>
<template #left>
<van-icon name="arrow-left" size="20" color="#fff"></van-icon>
</template>
</van-nav-bar>
</div>
<div id="reader"></div>
</div>
</div>
<script>
let html5QrCode = null
const vue = new Vue({
el: '#app',
data() {
return {
questionOptions: [],
pageForm: {
questionId: null,
searchKeyword: null,
pageNumber: 1,
pageSize: 10,
totalCount: 0
},
loading: false,
finished: false,
staffList: [],
showConfirmDialog: false,
currentStaff: {},
totalCount: 0,
receivedCount: 0,
pendingCount: 0,
cameraId: null,
devices: null,
scanStatus: false,
confirmLoading: false
}
},
methods: {
onSearch() {
this.pageForm.pageNumber = 1
this.staffList = []
this.finished = false;
this.onLoad()
},
async onLoad() {
if (!this.pageForm.questionId) {
this.loading = false;
this.finished = true;
return;
}
this.finished = false
try {
const {code, data} = await $.post('/mobile/question/h5/winnerList', this.pageForm)
this.staffList = [...this.staffList, ...data.list];
if (data.list.length < this.pageForm.pageSize) {
this.finished = true;
}
this.pageForm.pageNumber++;
this.loading = false;
} catch (err) {
this.loading = false;
this.finished = true;
}
},
// 更新统计数据
updateStats() {
$.get('/mobile/question/h5/winnerCount', {questionId: this.pageForm.questionId}).then(res => {
if (res.code === 0) {
this.totalCount = res.data.totalCount;
this.receivedCount = res.data.receivedCount;
this.pendingCount = res.data.pendingCount;
}
})
},
// 开始扫描
startScan() {
Html5Qrcode.getCameras().then((devices) => {
if (devices && devices.length) {
// 如果有2个摄像头,1为前置的
if (devices.length > 1) {
this.cameraId = devices[1].id;
} else {
this.cameraId = devices[0].id;
}
let isHuawei = navigator.userAgent.toLowerCase().match(/huawei/i) === 'huawei';
if (isHuawei) {
const backCamera = devices.filter(o => o.label.includes('back'))
this.cameraId = backCamera[0].id;
}
this.devices = devices;
this.scanStatus = true
this.$nextTick(() => {
html5QrCode = new Html5Qrcode("reader");
html5QrCode.start(
this.cameraId,
{
fps: 20,
qrbox: {width: 250, height: 250},
aspectRatio: 1.7777777777777777,
},
(decodedText, decodedResult) => {
this.handleScanResult(decodedText)
},
(errorMessage) => {
console.log(errorMessage);
}
).catch(err => {
alert(err)
});
})
}
}).catch((err) => {
console.log(err)
alert(err)
})
},
// 停止扫描
exitScan() {
this.scanStatus = false;
if (html5QrCode) {
html5QrCode.stop().then(() => {
console.log('扫描已停止');
}).catch((err) => {
console.log('停止扫描出错:', err);
});
}
},
//校验扫描结果
handleScanResult(result) {
this.exitScan()
if (!result) {
this.$toast.fail('请扫描正确的二维码')
return
}
$.post('/mobile/question/h5/checkScanResult', {
questionId: this.pageForm.questionId,
id: result
}).then(res => {
if (res.code === 0) {
this.currentStaff = res.data;
this.showConfirmDialog = true;
} else {
this.$toast.fail(res.msg)
}
})
},
// 确认领取奖品
async confirmReceive() {
if (!this.currentStaff) return;
this.confirmLoading = true;
try {
const res = await $.post('/mobile/question/h5/confirmReceive', {
questionId: this.pageForm.questionId,
id: this.currentStaff.id
});
if (res.code === 0) {
// 重新搜索
this.onSearch()
// 更新统计数据
this.updateStats();
// 提示成功
this.$toast.success('发放成功');
// 关闭弹窗
this.showConfirmDialog = false;
// 重置当前
// this.currentStaff = null;
} else {
this.$toast.fail(res.msg || '发放失败');
}
} catch (err) {
this.$toast.fail('网络错误,请重试');
} finally {
this.confirmLoading = false;
}
},
},
created() {
$.get('/mobile/question/h5/listQuestion').then(res => {
if (res.code === 0) {
this.questionOptions = res.data.map(v => {
return {
text: v.qtitle,
value: v.qid
}
})
if (this.questionOptions.length > 0) {
this.pageForm.questionId = this.questionOptions[0].value
// 设置完questionId后主动加载数据
this.onLoad();
// 同时更新统计数据
this.updateStats();
//this.handleScanResult('999')
}
}
})
}
})
</script>
<!--#
}
#-->