443 lines
10 KiB
Vue
Raw Normal View History

2025-07-23 22:32:01 +08:00
<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. 二维码有效期为60秒请及时扫描</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 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;
}
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 || '';
console.log('二维码数据:', qrData);
generateQRCodeImage(qrData);
} else {
throw new Error('生成二维码失败');
}
// 启动倒计时
startTimer();
} 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();
console.log('二维码生成成功');
});
}).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();
});
console.log('二维码数据:', qrData);
} 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 = 60;
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();
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>