630 lines
16 KiB
Vue
Raw Normal View History

2025-07-23 22:32:01 +08:00
<template>
<view class="confirm-page">
<!-- 确认签到阶段 -->
<view v-if="currentStep === 'confirm'" class="confirm-step">
<view class="confirm-header">
<text class="confirm-title">确认签到</text>
</view>
<view class="meeting-info">
<view class="info-item">
<text class="info-label">会议名称</text>
<text class="info-value">{{ meetingInfo?.qdmc || '未设置' }}</text>
</view>
<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 class="info-item">
<text class="info-label">签到人员</text>
<text class="info-value">{{ userInfo?.jsxm || '未设置' }}</text>
</view>
</view>
<view v-if="sign_file" class="signature-preview">
<text class="preview-label">签名预览</text>
<image :src="sign_file" class="signature-preview-img" />
</view>
<button @click="submitSignIn" class="submit-btn">确认签到</button>
</view>
<!-- 签到成功阶段 -->
<view v-else-if="currentStep === 'success'" class="success-step">
<view class="success-content">
<u-icon name="checkmark-circle" size="80" color="#67C23A" />
<text class="success-title">签到成功</text>
<text class="success-time">{{ formatTime(new Date()) }}</text>
<text class="success-tip">您已成功完成签到</text>
</view>
</view>
<!-- 不在签到名单阶段 -->
<view v-else-if="currentStep === 'notInList'" class="not-in-list-step">
<view class="not-in-list-content">
<u-icon name="close-circle" size="80" color="#F56C6C" />
<text class="not-in-list-title">您不在签到名单中</text>
<text class="not-in-list-subtitle">抱歉您没有在此次会议的签到名单中</text>
<text class="not-in-list-tip">请联系会议组织者确认您的参会资格</text>
<view class="meeting-info-card">
<text class="card-title">会议信息</text>
<view class="info-item">
<text class="info-label">会议名称</text>
<text class="info-value">{{ meetingInfo?.qdmc || '未设置' }}</text>
</view>
<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>
<button @click="goBack" class="back-btn">返回</button>
</view>
</view>
<!-- 签到时间已结束阶段 -->
<view v-else-if="currentStep === 'timeExpired'" class="time-expired-step">
<view class="time-expired-content">
<u-icon name="clock" size="80" color="#E6A23C" />
<text class="time-expired-title">签到时间已结束</text>
<text class="time-expired-subtitle">抱歉本次签到的时间已经结束</text>
<text class="time-expired-tip">请关注下次签到通知</text>
<view class="meeting-info-card">
<text class="card-title">会议信息</text>
<view class="info-item">
<text class="info-label">会议名称</text>
<text class="info-value">{{ meetingInfo?.qdmc || '未设置' }}</text>
</view>
<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 class="info-item">
<text class="info-label">当前时间</text>
<text class="info-value">{{ formatTime(new Date()) }}</text>
</view>
</view>
<button @click="goBack" class="back-btn">返回</button>
</view>
</view>
<!-- 二维码过期阶段 -->
<view v-else-if="currentStep === 'qrExpired'" class="qr-expired-step">
<view class="qr-expired-content">
<u-icon name="close-circle" size="80" color="#F56C6C" />
<text class="qr-expired-title">二维码已过期</text>
<text class="qr-expired-subtitle">抱歉您扫描的二维码已经过期</text>
<text class="qr-expired-tip">请重新扫描最新的二维码</text>
<view class="qr-info-card">
<text class="card-title">二维码信息</text>
<view class="info-item">
<text class="info-label">二维码有效期</text>
<text class="info-value">60</text>
</view>
<view class="info-item">
<text class="info-label">当前时间</text>
<text class="info-value">{{ formatTime(new Date()) }}</text>
</view>
</view>
<button @click="goBack" class="back-btn">返回</button>
</view>
</view>
</view>
<!-- 签名组件 -->
<BasicSign v-if="showSignature" ref="signCompRef" :title="signTitle"></BasicSign>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { qdFindByIdApi, qdzxSignInApi, qdzxFindByQdParamsApi, qdzxFindByQdAndJsApi } from '@/api/base/server';
import { useUserStore } from '@/store/modules/user';
// 页面参数
const qdId = ref('');
const qdzxId = ref('');
// 当前步骤
const currentStep = ref<'sign' | 'confirm' | 'success' | 'notInList' | 'timeExpired' | 'qrExpired'>('confirm');
// 用户信息
const userInfo = ref<any>(null);
// 会议信息
const meetingInfo = ref<any>(null);
// 签到执行记录
const qdzxRecord = ref<any>(null);
// 签名组件相关
const signCompRef = ref<any>(null);
const signTitle = ref<string>("签到签名");
const sign_file = ref<string>("");
const showSignature = ref<boolean>(false);
// 获取用户store
const userStore = useUserStore();
onLoad(async (options) => {
if (options?.qdId) {
qdId.value = options.qdId;
}
if (options?.qdzxId) {
qdzxId.value = options.qdzxId;
}
// 第一步:获取缓存的教师信息
const jsData = userStore.getJs;
if (jsData && Object.keys(jsData).length > 0) {
userInfo.value = jsData;
console.log('获取到教师信息:', jsData);
} else {
console.error('未找到教师信息,请先登录');
uni.showToast({ title: '请先登录', icon: 'none' });
// 跳转到登录页面
setTimeout(() => {
uni.navigateTo({
url: '/pages/system/login/login'
});
}, 1500);
return;
}
// 验证二维码时间戳是否过期
if (options?.timestamp) {
const qrTimestamp = parseInt(options.timestamp);
const currentTimestamp = Date.now();
const timeDiff = currentTimestamp - qrTimestamp;
const maxValidTime = 60 * 1000; // 60秒有效期
console.log('二维码时间戳验证:', {
qrTimestamp,
currentTimestamp,
timeDiff,
maxValidTime
});
if (timeDiff > maxValidTime) {
console.error('二维码已过期');
currentStep.value = 'qrExpired';
return;
}
}
// 第二步通过qdId和教师ID查询该教师是否能签到
await checkTeacherSignInPermission();
});
// 检查教师签到权限
const checkTeacherSignInPermission = async () => {
try {
const result = await qdzxFindByQdAndJsApi({
qdId: qdId.value,
jsId: userInfo.value.id
});
if (result && result.resultCode === 1 && result.result) {
// 找到签到记录
qdzxRecord.value = result.result;
qdzxId.value = result.result.id;
console.log('找到教师签到记录:', result.result);
// 第三步:获取签到详情,验证是否需要签名
await loadMeetingInfo();
} else {
// 没有找到签到记录,显示"不在签到名单中"界面
currentStep.value = 'notInList';
await loadMeetingInfo(); // 仍然加载会议信息用于显示
}
} catch (error) {
console.error('查询教师签到权限失败:', error);
uni.showToast({ title: '查询签到权限失败', icon: 'none' });
}
};
// 加载会议信息
const loadMeetingInfo = async () => {
try {
const result = await qdFindByIdApi({ id: qdId.value });
if (result && result.resultCode === 1) {
meetingInfo.value = result.result;
// 1. 验证签到时间是否已结束
const currentTime = new Date();
const endTime = meetingInfo.value?.qdjstime ? new Date(meetingInfo.value.qdjstime) : null;
if (endTime && currentTime > endTime) {
// 签到时间已结束,显示"签到时间已结束"界面
currentStep.value = 'timeExpired';
return;
}
// 只有在找到qdzxId时才检查是否需要签名
if (qdzxId.value) {
// 检查是否需要签名
if (meetingInfo.value?.mdqz === '1') {
// 需要签名,调用签名组件
await handleSignature();
} else {
// 不需要签名,直接到确认界面
currentStep.value = 'confirm';
}
}
}
} catch (error) {
console.error('加载会议信息失败:', error);
uni.showToast({ title: '加载会议信息失败', icon: 'none' });
}
};
// 处理签名
const handleSignature = async () => {
try {
showSignature.value = true;
sign_file.value = '';
signTitle.value = "签到签名";
// 等待组件渲染完成
await nextTick();
const data = await signCompRef.value.getSyncSignature();
sign_file.value = data.base64;
showSignature.value = false;
// 签名完成后,跳转到确认界面
currentStep.value = 'confirm';
} catch (error) {
showSignature.value = false;
console.error('签名失败:', error);
uni.showToast({ title: '签名失败', icon: 'none' });
}
};
// 提交签到
const submitSignIn = async () => {
try {
const params: any = {
qdzxId: qdzxId.value,
signInTime: new Date().toISOString() // 转换为ISO字符串格式
};
// 如果有签名文件,添加到参数中
if (sign_file.value) {
params.signatureImage = sign_file.value;
}
console.log('提交签到参数:', JSON.stringify(params, null, 2));
console.log('参数类型检查:', {
qdzxId: typeof params.qdzxId,
signInTime: typeof params.signInTime,
signatureImage: typeof params.signatureImage
});
const result = await qdzxSignInApi(params);
console.log('签到结果:', result);
if (result && result.resultCode === 1) {
currentStep.value = 'success';
} else {
const errorMsg = result?.msg || result?.message || '签到失败';
uni.showToast({ title: errorMsg, icon: 'none' });
}
} catch (error) {
console.error('签到失败:', error);
uni.showToast({ title: '网络异常,请重试', icon: 'none' });
}
};
// 格式化时间
const formatTime = (time: string | Date) => {
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'
});
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
</script>
<style lang="scss" scoped>
.confirm-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
box-sizing: border-box;
}
// 确认签到阶段
.confirm-step {
background: white;
border-radius: 16px;
padding: 24px;
margin-top: 40px;
}
.confirm-header {
text-align: center;
margin-bottom: 24px;
.confirm-title {
font-size: 24px;
font-weight: 600;
color: #333;
}
}
.meeting-info {
margin-bottom: 24px;
.info-item {
display: flex;
margin-bottom: 12px;
.info-label {
font-size: 14px;
color: #666;
min-width: 80px;
}
.info-value {
font-size: 14px;
color: #333;
flex: 1;
}
}
}
.signature-preview {
margin-bottom: 24px;
.preview-label {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.signature-preview-img {
width: 100%;
height: 100px;
border: 1px solid #ddd;
border-radius: 8px;
}
}
.submit-btn {
width: 100%;
height: 44px;
background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
}
// 签到成功阶段
.success-step {
background: white;
border-radius: 16px;
padding: 40px 24px;
margin-top: 40px;
text-align: center;
}
.success-content {
.success-title {
display: block;
font-size: 24px;
font-weight: 600;
color: #333;
margin: 16px 0 8px;
}
.success-time {
display: block;
font-size: 16px;
color: #666;
margin-bottom: 16px;
}
.success-tip {
font-size: 14px;
color: #999;
}
}
// 不在签到名单阶段
.not-in-list-step {
background: white;
border-radius: 16px;
padding: 24px;
margin-top: 40px;
text-align: center;
}
.not-in-list-content {
.not-in-list-title {
display: block;
font-size: 24px;
font-weight: 600;
color: #333;
margin: 16px 0 8px;
}
.not-in-list-subtitle {
font-size: 14px;
color: #666;
margin-bottom: 16px;
}
.not-in-list-tip {
font-size: 14px;
color: #999;
margin-bottom: 24px;
}
}
.meeting-info-card {
background: #f9f9f9;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.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;
}
}
}
.back-btn {
width: 100%;
height: 44px;
background: #f5f5f5;
color: #666;
border: none;
border-radius: 8px;
font-size: 16px;
}
/* 签到时间已结束阶段 */
.time-expired-step {
background: white;
border-radius: 16px;
padding: 40px 24px;
margin-top: 40px;
text-align: center;
}
.time-expired-content {
.time-expired-title {
display: block;
font-size: 24px;
font-weight: 600;
color: #333;
margin: 16px 0 8px;
}
.time-expired-subtitle {
font-size: 14px;
color: #666;
margin-bottom: 16px;
}
.time-expired-tip {
font-size: 14px;
color: #999;
margin-bottom: 24px;
}
}
/* 二维码过期阶段 */
.qr-expired-step {
background: white;
border-radius: 16px;
padding: 40px 24px;
margin-top: 40px;
text-align: center;
}
.qr-expired-content {
.qr-expired-title {
display: block;
font-size: 24px;
font-weight: 600;
color: #333;
margin: 16px 0 8px;
}
.qr-expired-subtitle {
font-size: 14px;
color: #666;
margin-bottom: 16px;
}
.qr-expired-tip {
font-size: 14px;
color: #999;
margin-bottom: 24px;
}
}
.qr-info-card {
background: #f9f9f9;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.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;
}
}
}
</style>