703 lines
29 KiB
HTML
703 lines
29 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{{ config.event_title }} - 信息修改</title>
|
||
<style>
|
||
:root {
|
||
--primary-color: {{ config.primary_color }};
|
||
--secondary-color: {{ config.secondary_color }};
|
||
--bg-color: {{ config.bg_color }};
|
||
--card-bg: rgba(12, 24, 50, 0.5); /* Glassy blue tint */
|
||
--text-color: #ffffff;
|
||
--text-muted: #b0c4de; /* Light blue-grey */
|
||
--accent-glow: rgba(0, 242, 255, 0.6);
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
body {
|
||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||
background-color: var(--bg-color);
|
||
color: var(--text-color);
|
||
/* Deep radial gradient mimicking the poster's atmosphere */
|
||
background-image:
|
||
radial-gradient(circle at 50% 0%, #1a3a75 0%, #050814 60%),
|
||
radial-gradient(circle at 85% 30%, rgba(0, 242, 255, 0.1) 0%, transparent 40%),
|
||
radial-gradient(circle at 15% 70%, rgba(0, 102, 255, 0.15) 0%, transparent 40%);
|
||
background-attachment: fixed;
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
/* Add a subtle grid or tech-line overlay if desired, but keep it clean for now */
|
||
body::before {
|
||
content: '';
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background:
|
||
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
|
||
background-size: 50px 50px;
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
}
|
||
|
||
.container {
|
||
width: 100%;
|
||
max-width: 600px;
|
||
padding: 30px 20px;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
header {
|
||
text-align: center;
|
||
margin-bottom: 40px;
|
||
padding-bottom: 20px;
|
||
border-bottom: 1px solid rgba(0, 242, 255, 0.2);
|
||
}
|
||
|
||
.header-img {
|
||
width: 100%;
|
||
max-height: 220px;
|
||
object-fit: cover;
|
||
border-radius: 12px;
|
||
margin-bottom: 25px;
|
||
box-shadow: 0 0 20px rgba(0, 242, 255, 0.2);
|
||
border: 1px solid rgba(0, 242, 255, 0.3);
|
||
}
|
||
|
||
h1 {
|
||
font-size: 2.4rem;
|
||
margin-bottom: 15px;
|
||
/* Poster style: Bold white/cyan gradient text */
|
||
background: linear-gradient(180deg, #ffffff 0%, #d0eaff 100%);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
font-weight: 900;
|
||
letter-spacing: 1px;
|
||
text-shadow: 0 0 30px rgba(0, 242, 255, 0.4);
|
||
position: relative;
|
||
}
|
||
|
||
.card {
|
||
background: var(--card-bg);
|
||
border-radius: 16px;
|
||
padding: 30px;
|
||
margin-bottom: 25px;
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border: 1px solid rgba(0, 242, 255, 0.2);
|
||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5), inset 0 0 20px rgba(0, 242, 255, 0.05);
|
||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||
}
|
||
|
||
.card:hover {
|
||
box-shadow: 0 8px 40px 0 rgba(0, 0, 0, 0.6), inset 0 0 20px rgba(0, 242, 255, 0.1);
|
||
}
|
||
|
||
h2 {
|
||
color: #fff;
|
||
text-align: center;
|
||
margin-bottom: 25px;
|
||
font-weight: 700;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.input-group {
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
margin-bottom: 10px;
|
||
font-size: 0.95rem;
|
||
color: var(--primary-color);
|
||
font-weight: 500;
|
||
}
|
||
|
||
input, textarea, select {
|
||
width: 100%;
|
||
padding: 14px 16px;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
border: 1px solid rgba(0, 242, 255, 0.3);
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-size: 1rem;
|
||
outline: none;
|
||
transition: all 0.3s;
|
||
box-shadow: inset 0 2px 5px rgba(0,0,0,0.3);
|
||
}
|
||
|
||
input:focus, textarea:focus {
|
||
border-color: var(--primary-color);
|
||
box-shadow: 0 0 15px rgba(0, 242, 255, 0.3), inset 0 2px 5px rgba(0,0,0,0.3);
|
||
background: rgba(0, 0, 0, 0.6);
|
||
}
|
||
|
||
/* Placeholder styling */
|
||
::placeholder {
|
||
color: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
button {
|
||
width: 100%;
|
||
padding: 16px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
/* Gradient button */
|
||
background: linear-gradient(90deg, #0051ff 0%, #00f2ff 100%);
|
||
color: #fff;
|
||
font-size: 1.1rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
box-shadow: 0 4px 15px rgba(0, 242, 255, 0.3);
|
||
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
button:hover {
|
||
opacity: 1;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 6px 20px rgba(0, 242, 255, 0.5);
|
||
background: linear-gradient(90deg, #0040cc 0%, #00d0dd 100%);
|
||
}
|
||
|
||
button:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
button:disabled {
|
||
background: #2a3b55;
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
box-shadow: none;
|
||
transform: none;
|
||
}
|
||
|
||
.hidden {
|
||
display: none;
|
||
}
|
||
|
||
.error-msg {
|
||
color: #ff6b6b;
|
||
font-size: 0.9rem;
|
||
margin-top: 15px;
|
||
text-align: center;
|
||
background: rgba(255, 107, 107, 0.1);
|
||
padding: 10px;
|
||
border-radius: 6px;
|
||
border: 1px solid rgba(255, 107, 107, 0.3);
|
||
}
|
||
|
||
.success-msg {
|
||
color: #00ff88;
|
||
font-size: 1.8rem;
|
||
text-align: center;
|
||
margin: 20px 0 10px;
|
||
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
|
||
}
|
||
|
||
/* Loading spinner */
|
||
.loader {
|
||
border: 3px solid rgba(255,255,255,0.1);
|
||
border-radius: 50%;
|
||
border-top: 3px solid var(--primary-color);
|
||
width: 20px;
|
||
height: 20px;
|
||
animation: spin 0.8s linear infinite;
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.user-select-item {
|
||
padding: 15px;
|
||
background: rgba(255,255,255,0.05);
|
||
margin-bottom: 8px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
.user-select-item:hover {
|
||
background: rgba(0, 242, 255, 0.1);
|
||
border-color: var(--primary-color);
|
||
}
|
||
|
||
</style>
|
||
<style>
|
||
.tabs {
|
||
display: flex;
|
||
margin-bottom: 20px;
|
||
border-bottom: 2px solid rgba(255,255,255,0.1);
|
||
}
|
||
.tab {
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
opacity: 0.6;
|
||
transition: all 0.3s;
|
||
}
|
||
.tab.active {
|
||
opacity: 1;
|
||
border-bottom: 2px solid var(--primary-color);
|
||
margin-bottom: -2px;
|
||
color: var(--primary-color);
|
||
}
|
||
.tab-content {
|
||
display: none;
|
||
}
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Password Overlay -->
|
||
<div id="password-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--bg-color); z-index: 9999; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(10px);">
|
||
<div class="card" style="text-align: center; width: 90%; max-width: 400px;">
|
||
<h2 style="margin-bottom: 20px;">请输入访问密钥</h2>
|
||
<div class="input-group">
|
||
<input type="password" id="secret-input" placeholder="输入密钥" style="text-align: center;">
|
||
</div>
|
||
<button onclick="checkSecret()">确认</button>
|
||
<p id="secret-error" class="error-msg hidden">密钥错误</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<div style="margin-bottom: 10px;">
|
||
<a href="/admin" style="color: var(--text-muted); text-decoration: none;">← 返回管理后台</a>
|
||
</div>
|
||
<header>
|
||
<img src="{{ config.header_image }}" alt="{{ config.event_title }}" class="header-img" onerror="this.style.display='none'">
|
||
<div style="text-align: center; margin-bottom: 10px; color: var(--primary-color); font-size: 0.8rem; letter-spacing: 2px; font-weight: 600; opacity: 0.9;">
|
||
{{ config.event_sub_title }}
|
||
</div>
|
||
<h1>{{ config.event_title }}</h1>
|
||
</header>
|
||
|
||
<!-- Step 1: Search -->
|
||
<div id="step-search" class="card">
|
||
<h2 style="margin-bottom: 20px;">修改信息</h2>
|
||
<div class="input-group">
|
||
<label>请输入手机号码</label>
|
||
<input type="text" id="search-input" placeholder="例如:13800000000">
|
||
</div>
|
||
<button onclick="searchUser()" id="search-btn">查询</button>
|
||
<div id="search-error" class="error-msg hidden"></div>
|
||
<div id="user-list" class="hidden" style="margin-top: 15px;"></div>
|
||
</div>
|
||
|
||
<!-- Step 2: Edit Info -->
|
||
<div id="step-form" class="card hidden">
|
||
<h2 style="margin-bottom: 20px;">编辑信息</h2>
|
||
|
||
<div class="tabs">
|
||
<div class="tab active" onclick="switchTab('signup')">报名信息</div>
|
||
<div class="tab" onclick="switchTab('checkin')">签到信息</div>
|
||
</div>
|
||
|
||
<form id="edit-form" onsubmit="submitEdit(event)">
|
||
<input type="hidden" id="gsdh-id">
|
||
<input type="hidden" id="current-is-signed" value="FALSE">
|
||
|
||
<!-- Tab: Signup Info -->
|
||
<div id="tab-signup" class="tab-content active">
|
||
<div class="input-group">
|
||
<label>姓名</label>
|
||
<input type="text" id="form-name" required>
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>手机号码 (不可修改)</label>
|
||
<input type="text" id="form-phone" required disabled style="opacity: 0.7;">
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>单位名称</label>
|
||
<input type="text" id="form-company" placeholder="点此输入">
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>费用 (Fee)</label>
|
||
<input type="text" id="form-fee" placeholder="例如:0">
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>支付渠道</label>
|
||
<input type="text" id="form-payment" placeholder="例如:wechat">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab: Checkin Info -->
|
||
<div id="tab-checkin" class="tab-content">
|
||
<div class="input-group">
|
||
<label>职务</label>
|
||
<input type="text" id="form-position" placeholder="点此输入">
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>签到状态</label>
|
||
<select id="form-is-signed">
|
||
<option value="FALSE">未签到</option>
|
||
<option value="TRUE">已签到</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>座位号</label>
|
||
<input type="text" id="form-location" placeholder="例如:第1桌 (仅已签到有效)">
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>公司主要经营 / 业务</label>
|
||
<textarea id="form-business" rows="2" placeholder="点此输入"></textarea>
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label>2026年业务愿景</label>
|
||
<textarea id="form-vision" rows="3" placeholder="点此输入 (为了业务更好对接,最好表述清晰)"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="submit" id="submit-btn" style="margin-top: 20px;">保存修改</button>
|
||
<button type="button" onclick="resetFlow()" style="margin-top: 15px; background: transparent; border: 1px solid rgba(255,255,255,0.2);">取消</button>
|
||
</form>
|
||
<div id="submit-error" class="error-msg hidden"></div>
|
||
</div>
|
||
|
||
<!-- Step 3: Success -->
|
||
<div id="step-success" class="card hidden" style="text-align: center; padding: 40px 20px;">
|
||
<div style="font-size: 4rem; margin-bottom: 20px;">✅</div>
|
||
<h2 class="success-msg">修改成功!</h2>
|
||
<p style="color: var(--text-muted); margin-bottom: 20px;">您的信息已更新</p>
|
||
|
||
<button onclick="window.location.href='/'" style="margin-top: 30px;">返回首页</button>
|
||
<button onclick="resetFlow()" style="margin-top: 15px; background: transparent; border: 1px solid rgba(255,255,255,0.2);">继续修改</button>
|
||
<a href="/admin">
|
||
<button style="margin-top: 15px; background: transparent; border: 1px solid rgba(255,255,255,0.2);">← 返回管理后台</button>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', checkAuthStatus);
|
||
|
||
async function checkAuthStatus() {
|
||
try {
|
||
const res = await fetch('/api/admin/check-auth');
|
||
const data = await res.json();
|
||
|
||
if (data.authenticated) {
|
||
hideOverlay();
|
||
} else {
|
||
showOverlay();
|
||
}
|
||
} catch (e) {
|
||
showOverlay();
|
||
}
|
||
}
|
||
|
||
function showOverlay() {
|
||
document.getElementById('password-overlay').style.display = 'flex';
|
||
}
|
||
|
||
function hideOverlay() {
|
||
const overlay = document.getElementById('password-overlay');
|
||
overlay.style.opacity = '0';
|
||
setTimeout(() => {
|
||
overlay.style.display = 'none';
|
||
}, 500);
|
||
}
|
||
|
||
async function checkSecret() {
|
||
const input = document.getElementById('secret-input');
|
||
const error = document.getElementById('secret-error');
|
||
const password = input.value;
|
||
|
||
try {
|
||
const res = await fetch('/api/admin/login', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({password: password})
|
||
});
|
||
const data = await res.json();
|
||
|
||
if (data.success) {
|
||
error.classList.add('hidden');
|
||
hideOverlay();
|
||
} else {
|
||
error.classList.remove('hidden');
|
||
input.value = '';
|
||
input.focus();
|
||
}
|
||
} catch (e) {
|
||
error.textContent = '网络错误';
|
||
error.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
||
if (e.key === 'Enter') {
|
||
checkSecret();
|
||
}
|
||
});
|
||
|
||
async function searchUser() {
|
||
const query = document.getElementById('search-input').value.trim();
|
||
const btn = document.getElementById('search-btn');
|
||
const errorDiv = document.getElementById('search-error');
|
||
const listDiv = document.getElementById('user-list');
|
||
|
||
if (!query) {
|
||
errorDiv.textContent = "请输入查询内容";
|
||
errorDiv.classList.remove('hidden');
|
||
return;
|
||
}
|
||
|
||
// Reset UI
|
||
errorDiv.classList.add('hidden');
|
||
listDiv.classList.add('hidden');
|
||
listDiv.innerHTML = '';
|
||
btn.disabled = true;
|
||
btn.innerHTML = '<span class="loader"></span> 查询中...';
|
||
|
||
try {
|
||
// Reuse existing search API
|
||
const response = await fetch(`/api/search?query=${encodeURIComponent(query)}`);
|
||
const data = await response.json();
|
||
|
||
if (response.ok) {
|
||
if (data.multiple) {
|
||
// Handle multiple results
|
||
listDiv.classList.remove('hidden');
|
||
listDiv.innerHTML = '<p style="margin-bottom:10px; color:var(--text-muted)">查询到多位嘉宾,请选择:</p>';
|
||
data.users.forEach(user => {
|
||
const item = document.createElement('div');
|
||
item.className = 'user-select-item';
|
||
item.innerHTML = `<strong>${user.name}</strong> <span style="font-size:0.8em; opacity:0.7">(${user.phone})</span>`;
|
||
item.onclick = () => selectUser(user);
|
||
listDiv.appendChild(item);
|
||
});
|
||
} else if (data.found) {
|
||
// Single user found
|
||
// Need to handle both signed and unsigned cases
|
||
// api/search returns "user" object in both cases
|
||
// if signed, "checkin_info" might be needed.
|
||
// The /api/search response structure for signed user:
|
||
// { found: true, user: {...}, already_signed: true, seat: ..., tablemates: ... }
|
||
// Ideally we should fetch full details if already signed.
|
||
// Let's modify selectUser to handle this.
|
||
selectUser(data.user, data.already_signed);
|
||
} else {
|
||
errorDiv.textContent = "未找到该用户";
|
||
errorDiv.classList.remove('hidden');
|
||
}
|
||
} else {
|
||
errorDiv.textContent = data.message || "查询失败";
|
||
errorDiv.classList.remove('hidden');
|
||
}
|
||
} catch (err) {
|
||
errorDiv.textContent = "网络错误,请稍后重试";
|
||
errorDiv.classList.remove('hidden');
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = '查询';
|
||
}
|
||
}
|
||
|
||
async function selectUser(user, alreadySigned) {
|
||
// Populate form
|
||
document.getElementById('gsdh-id').value = user.new_id;
|
||
document.getElementById('form-name').value = user.name;
|
||
document.getElementById('form-phone').value = user.phone;
|
||
|
||
// If user is already signed, we should try to get their checkin info if possible.
|
||
// However, /api/search currently doesn't return the full checkin_info fields (like position, business_scope) in the 'user' object directly?
|
||
// Wait, looking at main.py:
|
||
// if signed: returns "user": user (from gsdh_data)
|
||
// It doesn't return checkin_info details in 'user'.
|
||
// We might need to fetch current details.
|
||
// But wait, if they are signed, /api/search returns { ... seat: ..., tablemates: ... }
|
||
// It doesn't return the fields we want to edit (position, vision_2026 etc).
|
||
// We might need to create a new endpoint to get full details OR rely on what we have.
|
||
// Actually, let's use the 'industry_company' from gsdh_data as initial value for company.
|
||
|
||
document.getElementById('form-company').value = user.industry_company || '';
|
||
|
||
// If we want to support editing existing checkin info, we need to fetch it.
|
||
// I'll add a call to get user details if already signed.
|
||
// We fetch details anyway to get signed status and location
|
||
try {
|
||
const res = await fetch(`/api/get-user-details?phone=${user.phone}`);
|
||
if (res.ok) {
|
||
const details = await res.json();
|
||
if (details.success) {
|
||
document.getElementById('form-company').value = details.data.company_name || user.industry_company || '';
|
||
document.getElementById('form-position').value = details.data.position || '';
|
||
document.getElementById('form-business').value = details.data.business_scope || '';
|
||
document.getElementById('form-vision').value = details.data.vision_2026 || '';
|
||
document.getElementById('form-is-signed').value = details.data.is_signed || 'FALSE';
|
||
document.getElementById('current-is-signed').value = details.data.is_signed || 'FALSE';
|
||
document.getElementById('form-location').value = details.data.location || '';
|
||
document.getElementById('form-fee').value = details.data.fee || '';
|
||
document.getElementById('form-payment').value = details.data.payment_channel || '';
|
||
|
||
// Update name if it's different in checkin info
|
||
if (details.data.name) document.getElementById('form-name').value = details.data.name;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error("Failed to fetch details", e);
|
||
}
|
||
|
||
// Switch steps
|
||
document.getElementById('step-search').classList.add('hidden');
|
||
document.getElementById('step-form').classList.remove('hidden');
|
||
// Default to signup tab
|
||
switchTab('signup');
|
||
}
|
||
|
||
function switchTab(tabName) {
|
||
// Update tabs
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
if (tabName === 'signup') {
|
||
document.querySelector('.tab:nth-child(1)').classList.add('active');
|
||
} else {
|
||
document.querySelector('.tab:nth-child(2)').classList.add('active');
|
||
}
|
||
|
||
// Update content
|
||
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||
document.getElementById(`tab-${tabName}`).classList.add('active');
|
||
}
|
||
|
||
async function submitEdit(e) {
|
||
e.preventDefault();
|
||
const btn = document.getElementById('submit-btn');
|
||
const errorDiv = document.getElementById('submit-error');
|
||
|
||
btn.disabled = true;
|
||
btn.innerHTML = '<span class="loader"></span> 保存中...';
|
||
errorDiv.classList.add('hidden');
|
||
|
||
const payload = {
|
||
gsdh_id: document.getElementById('gsdh-id').value,
|
||
name: document.getElementById('form-name').value,
|
||
phone: document.getElementById('form-phone').value,
|
||
company_name: document.getElementById('form-company').value,
|
||
position: document.getElementById('form-position').value,
|
||
business_scope: document.getElementById('form-business').value,
|
||
vision_2026: document.getElementById('form-vision').value,
|
||
is_signed: document.getElementById('form-is-signed').value,
|
||
location: document.getElementById('form-location').value,
|
||
fee: document.getElementById('form-fee').value,
|
||
payment_channel: document.getElementById('form-payment').value
|
||
};
|
||
const currentSigned = document.getElementById('current-is-signed').value || 'FALSE';
|
||
const desiredSigned = payload.is_signed || 'FALSE';
|
||
|
||
try {
|
||
let checkinSeat = null;
|
||
if (currentSigned !== desiredSigned) {
|
||
if (desiredSigned === 'TRUE') {
|
||
// 执行签到(创建签到记录并分配座位)
|
||
const resCheckin = await fetch('/api/checkin', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
});
|
||
const dataCheckin = await resCheckin.json();
|
||
if (!(resCheckin.ok && dataCheckin.success)) {
|
||
throw new Error(dataCheckin.message || '签到失败');
|
||
}
|
||
checkinSeat = dataCheckin.seat || null;
|
||
// 更新当前状态缓存
|
||
document.getElementById('current-is-signed').value = 'TRUE';
|
||
document.getElementById('form-is-signed').value = 'TRUE';
|
||
} else {
|
||
// 取消签到(删除签到记录并置为未签到)
|
||
const resUncheck = await fetch('/api/uncheckin', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
gsdh_id: payload.gsdh_id,
|
||
name: payload.name,
|
||
company_name: payload.company_name
|
||
})
|
||
});
|
||
const dataUncheck = await resUncheck.json();
|
||
if (!(resUncheck.ok && dataUncheck.success)) {
|
||
throw new Error(dataUncheck.message || '取消签到失败');
|
||
}
|
||
// 更新当前状态缓存
|
||
document.getElementById('current-is-signed').value = 'FALSE';
|
||
document.getElementById('form-is-signed').value = 'FALSE';
|
||
}
|
||
}
|
||
|
||
// 覆盖更新其余信息(不新增)
|
||
const payloadForUpdate = { ...payload };
|
||
if (desiredSigned === 'TRUE') {
|
||
// 避免覆盖空座位:若表单未填座位,则保留签到自动分配的座位
|
||
if (!payloadForUpdate.location && checkinSeat) {
|
||
payloadForUpdate.location = checkinSeat;
|
||
}
|
||
}
|
||
|
||
const response = await fetch('/api/update-user', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payloadForUpdate)
|
||
});
|
||
const data = await response.json();
|
||
|
||
if (response.ok && data.success) {
|
||
document.getElementById('step-form').classList.add('hidden');
|
||
document.getElementById('step-success').classList.remove('hidden');
|
||
} else {
|
||
errorDiv.textContent = data.message || "保存失败";
|
||
errorDiv.classList.remove('hidden');
|
||
}
|
||
} catch (err) {
|
||
errorDiv.textContent = "网络错误,请稍后重试";
|
||
errorDiv.classList.remove('hidden');
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = '保存修改';
|
||
}
|
||
}
|
||
|
||
function resetFlow() {
|
||
document.getElementById('step-success').classList.add('hidden');
|
||
document.getElementById('step-form').classList.add('hidden');
|
||
document.getElementById('step-search').classList.remove('hidden');
|
||
document.getElementById('search-input').value = '';
|
||
document.getElementById('user-list').innerHTML = '';
|
||
document.getElementById('edit-form').reset();
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|