Files
qiandao/frontend/public/checkin.html
2025-10-23 00:22:43 +08:00

582 lines
20 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>会议签到</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 10px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
padding: 40px;
width: 100%;
max-width: 500px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #333;
font-size: 28px;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 16px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group.required label::after {
content: " *";
color: #e74c3c;
}
.form-control {
width: 100%;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-control:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
.btn {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.message {
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
text-align: center;
display: none;
}
.message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
display: block;
}
.message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
display: block;
}
.success-page {
text-align: center;
display: none;
}
.success-page.show {
display: block;
}
.success-icon {
font-size: 80px;
color: #28a745;
margin-bottom: 20px;
}
.success-title {
font-size: 28px;
color: #333;
margin-bottom: 15px;
font-weight: 600;
}
.success-message {
font-size: 18px;
color: #666;
margin-bottom: 30px;
line-height: 1.5;
}
.success-info {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
text-align: left;
}
.success-info h3 {
color: #333;
margin-bottom: 15px;
font-size: 18px;
}
.info-item {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
padding: 8px 0;
border-bottom: 1px solid #e9ecef;
}
.info-item:last-child {
border-bottom: none;
margin-bottom: 0;
}
.info-label {
font-weight: 500;
color: #666;
}
.info-value {
color: #333;
font-weight: 500;
}
.btn-secondary {
background: #6c757d;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
font-size: 14px;
cursor: pointer;
margin-top: 10px;
}
.btn-secondary:hover {
background: #5a6268;
}
.footer {
text-align: center;
margin-top: 20px;
color: #666;
font-size: 14px;
}
@media (max-width: 600px) {
.container {
padding: 20px;
}
.header h1 {
font-size: 24px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 id="mainTitle">会议签到</h1>
<p id="headerSubtitle">请填写以下信息完成签到</p>
</div>
<div id="message" class="message"></div>
<!-- 签到表单 -->
<div id="checkinFormContainer">
<form id="checkinForm">
<div class="form-group required">
<label for="name">姓名</label>
<input type="text" id="name" class="form-control" required>
</div>
<div class="form-group required">
<label for="phone">手机号</label>
<input type="tel" id="phone" class="form-control" required>
</div>
<div class="form-group">
<label for="company">公司</label>
<input type="text" id="company" class="form-control">
</div>
<div class="form-group">
<label for="position">职位</label>
<input type="text" id="position" class="form-control">
</div>
<div class="form-group">
<label for="email">邮箱</label>
<input type="email" id="email" class="form-control">
</div>
<button type="submit" id="submitBtn" class="btn">签到</button>
</form>
</div>
<!-- 签到成功页面 -->
<div id="successPage" class="success-page">
<div class="success-icon"></div>
<div class="success-title">签到成功!</div>
<div class="success-message">欢迎参加本次会议,祝您会议愉快!</div>
<div class="success-info">
<h3>您的签到信息</h3>
<div class="info-item">
<span class="info-label">姓名:</span>
<span class="info-value" id="successName">-</span>
</div>
<div class="info-item">
<span class="info-label">手机号:</span>
<span class="info-value" id="successPhone">-</span>
</div>
<div class="info-item">
<span class="info-label">公司:</span>
<span class="info-value" id="successCompany">-</span>
</div>
<div class="info-item">
<span class="info-label">职位:</span>
<span class="info-value" id="successPosition">-</span>
</div>
<div class="info-item">
<span class="info-label">邮箱:</span>
<span class="info-value" id="successEmail">-</span>
</div>
<div class="info-item">
<span class="info-label">签到时间:</span>
<span class="info-value" id="successTime">-</span>
</div>
</div>
<button type="button" class="btn-secondary" onclick="resetToForm()">重新签到</button>
</div>
<div class="footer">
<p>© <span id="currentYear"></span> 道友签到系统</p>
</div>
</div>
<script>
// 全局配置对象
let appConfig = {
main_title: '会议签到',
subtitle: '请填写以下信息完成签到',
success_title: '签到成功',
success_message: '欢迎参加本次会议,祝您会议愉快!',
already_checked_title: '您已签到',
already_checked_message: '您之前已经完成签到,无需重复签到。祝您会议愉快!',
success_main_title: '签到成功'
};
// 检测当前环境并设置API基础URL
const getApiBaseUrl = () => {
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
return 'http://127.0.0.1:3001';
} else {
// 在生产环境中您可以设置实际的API地址
return window.location.origin;
}
};
const API_BASE_URL = getApiBaseUrl();
// 从API加载配置
async function loadConfig() {
try {
const response = await fetch(`${API_BASE_URL}/api/config`);
if (response.ok) {
const configs = await response.json();
// 更新全局配置对象
configs.forEach(config => {
if (appConfig.hasOwnProperty(config.config_key)) {
appConfig[config.config_key] = config.config_value;
}
});
// 更新页面显示
updatePageContent();
}
} catch (error) {
console.log('加载配置失败,使用默认配置:', error);
}
}
// 更新页面内容
function updatePageContent() {
document.getElementById('mainTitle').textContent = appConfig.main_title;
document.getElementById('headerSubtitle').textContent = appConfig.subtitle;
}
// 页面加载时初始化配置
document.addEventListener('DOMContentLoaded', function() {
loadConfig();
});
document.getElementById('checkinForm').addEventListener('submit', async function(e) {
e.preventDefault();
// 获取表单数据
const formData = {
name: document.getElementById('name').value,
phone: document.getElementById('phone').value,
email: document.getElementById('email').value || null,
company: document.getElementById('company').value || null,
position: document.getElementById('position').value || null
};
// 简单验证
if (!formData.name || !formData.phone) {
showMessage('请填写姓名和手机号', 'error');
return;
}
// 简单手机号验证
if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
showMessage('请输入正确的手机号格式', 'error');
return;
}
// 简单邮箱验证
if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
showMessage('请输入正确的邮箱格式', 'error');
return;
}
// 禁用提交按钮
const submitBtn = document.getElementById('submitBtn');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = '签到中...';
try {
// 首先尝试通过邮箱或手机号查找用户
let attendeeId = null;
let isExistingUser = false;
// 获取所有参会者列表来查找匹配的用户
const listResponse = await fetch(API_BASE_URL + '/api/attendees?page_size=1000');
if (listResponse.ok) {
const response = await listResponse.json();
const attendees = response.data; // 从分页响应中获取data数组
const existingAttendee = attendees.find(a =>
formData.phone && a.phone === formData.phone && formData.name === a.name
);
if (existingAttendee) {
attendeeId = existingAttendee.id;
isExistingUser = true;
// 如果已经签到过了
if (existingAttendee.checked_in) {
showSuccessPage(formData, existingAttendee, true);
return;
}
}
}
if (isExistingUser && attendeeId) {
// 用户已存在,直接签到
const checkinResponse = await fetch(API_BASE_URL + `/api/attendees/${attendeeId}/checkin`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (checkinResponse.ok) {
const result = await checkinResponse.json();
showSuccessPage(formData, result);
} else {
const errorText = await checkinResponse.text();
showMessage('签到失败:' + (errorText || '服务器返回错误状态码: ' + checkinResponse.status), 'error');
}
} else {
// 用户不存在,先注册再签到
const registerResponse = await fetch(API_BASE_URL + '/api/attendees', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (registerResponse.ok) {
const newUser = await registerResponse.json();
// 注册成功后立即签到
const checkinResponse = await fetch(API_BASE_URL + `/api/attendees/${newUser.id}/checkin`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (checkinResponse.ok) {
const result = await checkinResponse.json();
showSuccessPage(formData, result);
} else {
showMessage('注册成功但签到失败,请联系管理员。', 'error');
}
} else {
const errorText = await registerResponse.text();
showMessage('注册失败:' + (errorText || '服务器返回错误状态码: ' + registerResponse.status), 'error');
}
}
} catch (error) {
console.error('Network error details:', error);
showMessage('网络错误,请稍后重试。错误详情: ' + error.message, 'error');
} finally {
// 恢复提交按钮
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
});
function showMessage(text, type) {
const messageEl = document.getElementById('message');
messageEl.textContent = text;
messageEl.className = 'message ' + type;
// 3秒后自动隐藏成功消息
if (type === 'success') {
setTimeout(() => {
messageEl.style.display = 'none';
}, 3000);
}
}
function showSuccessPage(formData, result, isAlreadyCheckedIn = false) {
// 隐藏表单和消息
document.getElementById('checkinFormContainer').style.display = 'none';
document.getElementById('message').style.display = 'none';
document.getElementById('headerSubtitle').style.display = 'none';
// 更新主标题为配置中的成功标题
const mainTitle = document.getElementById('mainTitle');
mainTitle.textContent = appConfig.success_main_title;
// 填充成功页面信息
document.getElementById('successName').textContent = formData.name || result.name || '-';
document.getElementById('successPhone').textContent = formData.phone || result.phone || '-';
document.getElementById('successCompany').textContent = formData.company || result.company || '-';
document.getElementById('successPosition').textContent = formData.position || result.position || '-';
document.getElementById('successEmail').textContent = formData.email || result.email || '-';
// 格式化签到时间
let checkinTime = '-';
if (result.checkin_time) {
const date = new Date(result.checkin_time);
// 后端已经返回北京时间,直接格式化显示
checkinTime = date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
} else {
// 如果没有签到时间,使用当前时间
checkinTime = new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
document.getElementById('successTime').textContent = checkinTime;
// 更新标题和消息
const titleEl = document.querySelector('.success-title');
const messageEl = document.querySelector('.success-message');
if (isAlreadyCheckedIn) {
titleEl.textContent = appConfig.already_checked_title;
messageEl.textContent = appConfig.already_checked_message;
} else {
titleEl.textContent = appConfig.success_title;
messageEl.textContent = appConfig.success_message;
}
// 显示成功页面
document.getElementById('successPage').classList.add('show');
}
function resetToForm() {
// 隐藏成功页面
document.getElementById('successPage').classList.remove('show');
// 恢复原始标题
document.getElementById('mainTitle').textContent = appConfig.main_title;
// 显示表单和副标题
document.getElementById('checkinFormContainer').style.display = 'block';
document.getElementById('headerSubtitle').style.display = 'block';
// 重置表单
document.getElementById('checkinForm').reset();
// 隐藏消息
document.getElementById('message').style.display = 'none';
}
// 设置当前年份
document.getElementById('currentYear').textContent = new Date().getFullYear();
</script>
</body>
</html>