2025-07-23 22:32:01 +08:00

630 lines
16 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="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>