diff --git a/templates/admin.html b/templates/admin.html index e49d87c..659faf7 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -584,6 +584,11 @@
+
+ + +

显示在座位号旁边的单位,如"桌"、"座位"、"区"等

+
@@ -819,6 +824,22 @@
+ +
+ +
+ + +
+
+ + + +
+ +

+ 💡 开启后,签到成功页面将显示二维码,长按可保存图片 +

@@ -863,6 +884,13 @@ }); checkAuthStatus(); + + const seatUnitInput = document.getElementById('seat_unit_name'); + if (seatUnitInput) { + seatUnitInput.addEventListener('input', function() { + updateSeatingLabels(this.value); + }); + } }); async function checkAuthStatus() { @@ -948,6 +976,18 @@ } } + /** + * 根据座位单位名称更新标签文字 + */ + function updateSeatingLabels(unitName) { + const unit = unitName || '桌'; + const labels = document.querySelectorAll('#seating_inputs .form-group label'); + if (labels.length >= 2) { + labels[1].textContent = `总${unit}数`; + labels[2].textContent = `每${unit}最大人数`; + } + } + // Load config on startup fetch('/api/admin/config') .then(res => res.json()) @@ -962,6 +1002,9 @@ document.getElementById('bg_color').value = config.bg_color; document.getElementById('image_preview').src = config.header_image; + document.getElementById('enable_qrcode').checked = config.enable_qrcode === true; + document.getElementById('qrcode_preview').src = config.qrcode_image || ''; + // Checkin Config document.getElementById('enable_ticket_validation').checked = config.enable_ticket_validation !== false; document.getElementById('enable_sms_verification').checked = config.enable_sms_verification === true; @@ -982,6 +1025,8 @@ document.getElementById('enable_seating').checked = config.enable_seating !== false; // Default true document.getElementById('total_tables').value = config.total_tables || 14; document.getElementById('max_per_table').value = config.max_per_table || 10; + document.getElementById('seat_unit_name').value = config.seat_unit_name || '桌'; + updateSeatingLabels(config.seat_unit_name); toggleSeatingInputs(); // Field Configs - 使用新版渲染函数 @@ -1467,6 +1512,9 @@ bg_color: document.getElementById('bg_color').value, header_image: document.getElementById('image_preview').src.replace(window.location.origin, ''), + enable_qrcode: document.getElementById('enable_qrcode').checked, + qrcode_image: document.getElementById('qrcode_preview').src.replace(window.location.origin, ''), + // Checkin Config enable_ticket_validation: document.getElementById('enable_ticket_validation').checked, enable_sms_verification: document.getElementById('enable_sms_verification').checked, @@ -1485,6 +1533,7 @@ // Seating Config enable_seating: document.getElementById('enable_seating').checked, + seat_unit_name: document.getElementById('seat_unit_name').value || '桌', total_tables: parseInt(document.getElementById('total_tables').value), max_per_table: parseInt(document.getElementById('max_per_table').value), @@ -1583,6 +1632,36 @@ } } + /** + * 上传入群二维码图片 + */ + async function uploadQrcode() { + const fileInput = document.getElementById('qrcode_image_file'); + if (!fileInput.files[0]) { + showMessage('请先选择图片', 'error'); + return; + } + + const formData = new FormData(); + formData.append('file', fileInput.files[0]); + + try { + const res = await fetch('/api/admin/upload', { + method: 'POST', + body: formData + }); + const data = await res.json(); + if (res.ok) { + document.getElementById('qrcode_preview').src = data.url; + showMessage('二维码上传成功,请记得点击保存设置', 'success'); + } else { + showMessage('上传失败: ' + data.message, 'error'); + } + } catch (e) { + showMessage('网络错误', 'error'); + } + } + async function initDatabase() { if (!confirm('确定要在当前配置的数据库中初始化/迁移表结构吗?\n(此操作安全,不会删除现有数据)')) return; diff --git a/templates/index.html b/templates/index.html index 3c283d0..18db249 100644 --- a/templates/index.html +++ b/templates/index.html @@ -463,6 +463,45 @@ 100% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); } } + /* 入群二维码样式 */ + .qrcode-section { + margin-top: 25px; + padding: 20px; + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + border: 1px solid rgba(0, 242, 255, 0.3); + text-align: center; + } + + .qrcode-title { + color: var(--primary-color); + font-size: 0.9rem; + margin-bottom: 15px; + font-weight: 600; + } + + .qrcode-image { + max-width: 180px; + width: 100%; + border-radius: 8px; + border: 1px solid rgba(0, 242, 255, 0.4); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + } + + .qrcode-image:hover { + transform: scale(1.02); + box-shadow: 0 6px 20px rgba(0, 242, 255, 0.3); + } + + .qrcode-hint { + color: var(--text-muted); + font-size: 0.75rem; + margin-top: 10px; + opacity: 0.8; + } + @@ -576,6 +615,12 @@ + +

请入座等待会议开始

@@ -601,6 +646,12 @@ + +

请入座等待会议开始

@@ -646,6 +697,25 @@ let isCheckinCodeVerified = false; + /** + * 获取座位单位名称 + */ + function getSeatUnitName() { + return CONFIG.seat_unit_name || '桌'; + } + + /** + * 格式化座位号显示(替换"桌"为单位名称) + * @param {string} seatText 原始座位文字 + * @returns {string} 格式化后的座位文字 + */ + function formatSeatDisplay(seatText) { + if (!seatText) return ''; + const unit = getSeatUnitName(); + if (unit === '桌') return seatText; + return seatText.replace(/桌/g, unit); + } + async function refreshConfig() { try { const res = await fetch('/api/admin/config'); @@ -844,7 +914,7 @@ document.getElementById('signed-phone').textContent = data.user.phone; if (CONFIG.enable_seating !== false) { - document.getElementById('signed-seat-display').textContent = data.seat || "自由席"; + document.getElementById('signed-seat-display').textContent = formatSeatDisplay(data.seat) || ("自由" + getSeatUnitName()); document.getElementById('signed-seat-display').style.display = 'block'; document.getElementById('signed-seat-label').textContent = "您的座位是"; document.getElementById('signed-seat-label').style.display = 'block'; @@ -860,6 +930,9 @@ document.getElementById('signed-seat-label').textContent = "签到成功,请自由入座"; document.getElementById('signed-tablemates-container').classList.add('hidden'); } + + // 显示二维码(如果配置开启) + updateQrcodeDisplay(); } else { // Single user found await refreshConfig(); @@ -968,7 +1041,7 @@ document.getElementById('success-btn').textContent = payload.name + '/点击进入活动大厅'; if (CONFIG.enable_seating !== false) { // Show assigned seat - document.getElementById('seat-display').textContent = data.seat || "自由席"; + document.getElementById('seat-display').textContent = formatSeatDisplay(data.seat) || ("自由" + getSeatUnitName()); document.getElementById('seat-display').style.display = 'block'; document.getElementById('success-seat-label').textContent = "您的座位已分配"; document.getElementById('success-seat-label').style.display = 'block'; @@ -985,6 +1058,9 @@ document.getElementById('tablemates-container').classList.add('hidden'); } + // 显示二维码(如果配置开启) + updateQrcodeDisplay(); + document.getElementById('step-form').classList.add('hidden'); document.getElementById('step-success').classList.remove('hidden'); } else { @@ -1061,6 +1137,49 @@ updateCheckinButtonState(); } + /** + * 根据配置控制二维码显示 + */ + function updateQrcodeDisplay() { + const enableQrcode = CONFIG.enable_qrcode === true; + const qrcodeImage = CONFIG.qrcode_image; + + if (enableQrcode && qrcodeImage) { + // 签到成功页面的二维码 + const successContainer = document.getElementById('qrcode-container-success'); + const successImg = document.getElementById('qrcode-img-success'); + if (successContainer) { + successImg.src = qrcodeImage; + successContainer.classList.remove('hidden'); + } + + // 已签到页面的二维码 + const signedContainer = document.getElementById('qrcode-container-signed'); + const signedImg = document.getElementById('qrcode-img-signed'); + if (signedContainer) { + signedImg.src = qrcodeImage; + signedContainer.classList.remove('hidden'); + } + } else { + document.getElementById('qrcode-container-success')?.classList.add('hidden'); + document.getElementById('qrcode-container-signed')?.classList.add('hidden'); + } + } + + /** + * 处理二维码长按保存(在 PC 端右键保存) + */ + function handleQrcodeLongPress(event) { + event.preventDefault(); + const img = event.target; + if (img.src) { + const link = document.createElement('a'); + link.href = img.src; + link.download = '入群二维码'; + link.click(); + } + } + async function sendCheckinSms() { const phone = document.getElementById('form-phone').value.trim(); const errorDiv = document.getElementById('submit-error');