2025-07-27 21:56:11 +08:00

457 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="qr-code-page">
<view class="qr-container">
<!-- 页面头部 -->
<view class="page-header">
<text class="page-title">签到二维码</text>
<text class="page-subtitle">请使用微信扫描二维码进行签到</text>
</view>
<!-- 二维码显示区域 -->
<view class="qr-display">
<view class="qr-card">
<view class="qr-header">
<text class="qr-title">{{ meetingInfo?.qdmc || '签到二维码' }}</text>
<view class="qr-timer">
<text class="timer-text">{{ qrTimer }}s</text>
</view>
</view>
<view class="qr-content">
<canvas
canvas-id="qrcode"
class="qr-canvas"
:style="{ width: qrSize + 'px', height: qrSize + 'px' }"
></canvas>
</view>
<view class="qr-info">
<view class="info-item">
<text class="info-label">会议时间</text>
<text class="info-value">{{ formatTime(meetingInfo?.qdkstime) }} - {{ formatTime(meetingInfo?.qdjstime) }}</text>
</view>
<view class="info-item">
<text class="info-label">会议地点</text>
<text class="info-value">{{ meetingInfo?.qdwz || '未设置' }}</text>
</view>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-buttons">
<button @click="refreshQRCode" class="refresh-btn">
<u-icon name="reload" size="16" color="#409EFF" />
<text class="btn-text">刷新二维码</text>
</button>
<button @click="goBack" class="back-btn">
<u-icon name="arrow-left" size="16" color="#666" />
<text class="btn-text">返回</text>
</button>
</view>
<!-- 使用说明 -->
<view class="usage-tips">
<view class="tips-header">
<u-icon name="info-circle" size="16" color="#409EFF" />
<text class="tips-title">使用说明</text>
</view>
<view class="tips-content">
<text class="tip-item">1. 二维码有效期为{{ qrExpireTime }}请及时扫描</text>
<text class="tip-item">2. 使用微信扫一扫功能扫描二维码</text>
<text class="tip-item">3. 扫描后将跳转到签到确认页面</text>
<text class="tip-item">4. 请确保网络连接正常</text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { qdFindByIdApi, generateQRCodeApi } from '@/api/base/server';
// 页面参数
const qdId = ref('');
const qrExpireTime = ref(60); // 默认60秒
// 会议信息
const meetingInfo = ref<any>(null);
// 二维码相关
const qrTimer = ref(60);
const qrSize = ref(200);
let qrTimerInterval: any = null;
let qrCanvas: any = null;
onLoad((options) => {
if (options?.qdId) {
qdId.value = options.qdId;
}
// 启动默认倒计时,等待二维码数据解析后更新
startTimer();
loadMeetingInfo();
});
onMounted(() => {
initQRCode();
});
// 加载会议信息
const loadMeetingInfo = async () => {
try {
const result = await qdFindByIdApi({ id: qdId.value });
if (result && result.resultCode === 1) {
meetingInfo.value = result.result;
}
} catch (error) {
console.error('加载会议信息失败:', error);
}
};
// 初始化二维码
const initQRCode = async () => {
try {
// 生成二维码数据只传递qdId
const result = await generateQRCodeApi({
qdId: qdId.value
});
if (result && result.resultCode === 1) {
// 确保二维码数据是字符串格式
const qrData = result.result || '';
// 从二维码数据中解析rqgqtime参数
const urlParams = new URLSearchParams(qrData.split('?')[1] || '');
const rqgqtime = urlParams.get('rqgqtime');
if (rqgqtime) {
const expireTime = parseInt(rqgqtime) || 60;
qrExpireTime.value = expireTime;
qrTimer.value = expireTime;
// 重新启动倒计时
startTimer();
}
generateQRCodeImage(qrData);
} else {
throw new Error('生成二维码失败');
}
// 倒计时已在onLoad中启动这里不需要重复启动
} catch (error) {
console.error('生成二维码失败:', error);
uni.showToast({ title: '生成二维码失败', icon: 'none' });
}
};
// 生成二维码图片
const generateQRCodeImage = (qrData: string) => {
try {
// 使用qrcode库生成真正的二维码
import('qrcode').then((QRCode) => {
// 使用回调函数方式避免类型错误
(QRCode as any).toDataURL(qrData, {
width: qrSize.value,
height: qrSize.value,
margin: 2,
color: {
dark: '#000000',
light: '#FFFFFF'
},
errorCorrectionLevel: 'M'
}, (err: any, url: string) => {
if (err) {
console.error('生成二维码失败:', err);
// 如果生成失败,显示错误信息
qrCanvas = uni.createCanvasContext('qrcode');
qrCanvas.clearRect(0, 0, qrSize.value, qrSize.value);
qrCanvas.setFillStyle('#ffffff');
qrCanvas.fillRect(0, 0, qrSize.value, qrSize.value);
qrCanvas.setFillStyle('#ff0000');
qrCanvas.setFontSize(12);
qrCanvas.fillText('二维码生成失败', qrSize.value / 2 - 40, qrSize.value / 2);
qrCanvas.draw();
return;
}
// 使用uni-app的方式处理图片
// 将二维码数据直接绘制到canvas上
qrCanvas = uni.createCanvasContext('qrcode');
qrCanvas.clearRect(0, 0, qrSize.value, qrSize.value);
// 使用uni-app的drawImage方法
qrCanvas.drawImage(url, 0, 0, qrSize.value, qrSize.value);
qrCanvas.draw();
});
}).catch((error) => {
console.error('加载qrcode库失败:', error);
// 如果无法加载qrcode库显示提示信息
qrCanvas = uni.createCanvasContext('qrcode');
qrCanvas.clearRect(0, 0, qrSize.value, qrSize.value);
qrCanvas.setFillStyle('#ffffff');
qrCanvas.fillRect(0, 0, qrSize.value, qrSize.value);
qrCanvas.setFillStyle('#666666');
qrCanvas.setFontSize(12);
qrCanvas.fillText('请安装qrcode库', qrSize.value / 2 - 30, qrSize.value / 2);
qrCanvas.draw();
});
} catch (error) {
console.error('生成二维码异常:', error);
// 异常处理,显示错误信息
qrCanvas = uni.createCanvasContext('qrcode');
qrCanvas.clearRect(0, 0, qrSize.value, qrSize.value);
qrCanvas.setFillStyle('#ffffff');
qrCanvas.fillRect(0, 0, qrSize.value, qrSize.value);
qrCanvas.setFillStyle('#ff0000');
qrCanvas.setFontSize(12);
qrCanvas.fillText('二维码生成异常', qrSize.value / 2 - 40, qrSize.value / 2);
qrCanvas.draw();
}
};
// 启动倒计时
const startTimer = () => {
qrTimer.value = qrExpireTime.value;
if (qrTimerInterval) {
clearInterval(qrTimerInterval);
}
qrTimerInterval = setInterval(() => {
qrTimer.value--;
if (qrTimer.value <= 0) {
clearInterval(qrTimerInterval);
uni.showToast({ title: '二维码已过期', icon: 'none' });
// 可以在这里自动刷新二维码
refreshQRCode();
}
}, 1000);
};
// 刷新二维码
const refreshQRCode = async () => {
try {
await initQRCode();
// 重新启动倒计时
startTimer();
uni.showToast({ title: '二维码已刷新', icon: 'success' });
} catch (error) {
uni.showToast({ title: '刷新失败', icon: 'none' });
}
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 格式化时间
const formatTime = (time: string) => {
if (!time) return '未设置';
const date = new Date(time);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
// 组件销毁时清理定时器
onUnmounted(() => {
if (qrTimerInterval) {
clearInterval(qrTimerInterval);
}
});
</script>
<style lang="scss" scoped>
.qr-code-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
box-sizing: border-box;
}
.qr-container {
max-width: 400px;
margin: 0 auto;
}
// 页面头部
.page-header {
text-align: center;
margin-bottom: 30px;
.page-title {
display: block;
font-size: 28px;
font-weight: 600;
color: white;
margin-bottom: 8px;
}
.page-subtitle {
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
}
// 二维码显示区域
.qr-display {
margin-bottom: 30px;
}
.qr-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.qr-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.qr-title {
font-size: 18px;
font-weight: 600;
color: #333;
flex: 1;
}
.qr-timer {
background: linear-gradient(135deg, #ff4757 0%, #ff3742 100%);
padding: 6px 12px;
border-radius: 20px;
.timer-text {
color: white;
font-size: 14px;
font-weight: 600;
}
}
}
.qr-content {
display: flex;
justify-content: center;
margin-bottom: 20px;
.qr-canvas {
border: 2px solid #f0f0f0;
border-radius: 8px;
background: white;
}
}
.qr-info {
.info-item {
display: flex;
margin-bottom: 8px;
.info-label {
font-size: 14px;
color: #666;
min-width: 80px;
}
.info-value {
font-size: 14px;
color: #333;
flex: 1;
}
}
}
// 操作按钮
.action-buttons {
display: flex;
gap: 12px;
margin-bottom: 30px;
.refresh-btn, .back-btn {
flex: 1;
height: 44px;
border: none;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 16px;
}
.refresh-btn {
background: #409EFF;
color: white;
}
.back-btn {
background: #f5f5f5;
color: #666;
}
.btn-text {
font-size: 14px;
}
}
// 使用说明
.usage-tips {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 20px;
backdrop-filter: blur(10px);
.tips-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
.tips-title {
font-size: 16px;
font-weight: 600;
color: white;
}
}
.tips-content {
.tip-item {
display: block;
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 6px;
line-height: 1.4;
}
}
}
// 响应式优化
@media (max-width: 375px) {
.qr-code-page {
padding: 15px;
}
.qr-card {
padding: 20px;
}
.qr-size {
width: 180px;
height: 180px;
}
.page-header .page-title {
font-size: 24px;
}
}
</style>