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 @@
@@ -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');