229 lines
7.4 KiB
JavaScript
229 lines
7.4 KiB
JavaScript
// 用户点名JavaScript - 支持服务器和本地双模式
|
|
|
|
// 状态管理
|
|
let currentList = null;
|
|
let stream = null;
|
|
|
|
// DOM元素
|
|
const inputSection = document.getElementById('inputSection');
|
|
const rollCallSection = document.getElementById('rollCallSection');
|
|
const enterForm = document.getElementById('enterForm');
|
|
const codeInput = document.getElementById('codeInput');
|
|
const scanQRBtn = document.getElementById('scanQRBtn');
|
|
const completedCount = document.getElementById('completedCount');
|
|
const totalCount = document.getElementById('totalCount');
|
|
const progressFill = document.getElementById('progressFill');
|
|
const memberListDisplay = document.getElementById('memberListDisplay');
|
|
const video = document.getElementById('video');
|
|
const canvas = document.getElementById('canvas');
|
|
const startCameraBtn = document.getElementById('startCameraBtn');
|
|
const captureBtn = document.getElementById('captureBtn');
|
|
const stopCameraBtn = document.getElementById('stopCameraBtn');
|
|
const capturedPhoto = document.getElementById('capturedPhoto');
|
|
const photoPreview = document.getElementById('photoPreview');
|
|
const confirmPhotoBtn = document.getElementById('confirmPhotoBtn');
|
|
const retakeBtn = document.getElementById('retakeBtn');
|
|
const manualSelect = document.getElementById('manualSelect');
|
|
const manualRollCallBtn = document.getElementById('manualRollCallBtn');
|
|
|
|
// 初始化
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// 显示运行模式
|
|
const modeText = dataStorage.isServerMode ? '服务器模式' : '本地模式';
|
|
const modeIndicator = document.getElementById('modeIndicator');
|
|
|
|
if (modeIndicator) {
|
|
modeIndicator.textContent = `当前运行模式: ${modeText}`;
|
|
if (!dataStorage.isServerMode) {
|
|
modeIndicator.textContent += ' (数据保存在浏览器本地)';
|
|
}
|
|
}
|
|
|
|
console.log(`点名系统运行在: ${modeText}`);
|
|
|
|
// 检查URL参数
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const codeFromUrl = urlParams.get('code');
|
|
if (codeFromUrl) {
|
|
codeInput.value = codeFromUrl;
|
|
enterRollCall(codeFromUrl);
|
|
}
|
|
});
|
|
|
|
// 进入点名
|
|
enterForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
const code = codeInput.value.trim();
|
|
enterRollCall(code);
|
|
});
|
|
|
|
async function enterRollCall(code) {
|
|
try {
|
|
currentList = await dataStorage.getList(code);
|
|
|
|
inputSection.style.display = 'none';
|
|
rollCallSection.style.display = 'block';
|
|
|
|
updateProgress();
|
|
renderMemberList();
|
|
populateManualSelect();
|
|
} catch (error) {
|
|
alert('进入点名失败:' + error.message);
|
|
}
|
|
}
|
|
|
|
// 扫描二维码(简化版)
|
|
scanQRBtn.addEventListener('click', () => {
|
|
alert('扫码功能需要集成摄像头扫码库,当前版本请手动输入编号');
|
|
});
|
|
|
|
// 更新进度
|
|
function updateProgress() {
|
|
if (!currentList) return;
|
|
|
|
const completed = currentList.members.filter(m => m.completed).length;
|
|
const total = currentList.members.length;
|
|
const percentage = total > 0 ? (completed / total * 100) : 0;
|
|
|
|
completedCount.textContent = completed;
|
|
totalCount.textContent = total;
|
|
progressFill.style.width = percentage + '%';
|
|
progressFill.textContent = Math.round(percentage) + '%';
|
|
}
|
|
|
|
// 渲染成员列表
|
|
function renderMemberList() {
|
|
if (!currentList) return;
|
|
|
|
memberListDisplay.innerHTML = currentList.members.map((member, index) => `
|
|
<div class="member-item ${member.completed ? 'completed' : ''}">
|
|
<span class="member-name">${member.name}</span>
|
|
<span class="member-status ${member.completed ? 'status-completed' : 'status-pending'}">
|
|
${member.completed ? '已点名' : '未点名'}
|
|
</span>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// 填充手动选择下拉框
|
|
function populateManualSelect() {
|
|
if (!currentList) return;
|
|
|
|
const uncompletedMembers = currentList.members.filter(m => !m.completed);
|
|
manualSelect.innerHTML = '<option value="">请选择姓名</option>' +
|
|
uncompletedMembers.map(member => `<option value="${member.name}">${member.name}</option>`).join('');
|
|
}
|
|
|
|
// 开启摄像头
|
|
startCameraBtn.addEventListener('click', async () => {
|
|
try {
|
|
stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
video.srcObject = stream;
|
|
video.style.display = 'block';
|
|
startCameraBtn.style.display = 'none';
|
|
captureBtn.style.display = 'inline-block';
|
|
stopCameraBtn.style.display = 'inline-block';
|
|
} catch (err) {
|
|
alert('无法访问摄像头,请检查权限设置');
|
|
console.error(err);
|
|
}
|
|
});
|
|
|
|
// 拍照
|
|
captureBtn.addEventListener('click', () => {
|
|
const context = canvas.getContext('2d');
|
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
|
|
const dataURL = canvas.toDataURL('image/jpeg');
|
|
photoPreview.src = dataURL;
|
|
|
|
capturedPhoto.style.display = 'block';
|
|
video.style.display = 'none';
|
|
captureBtn.style.display = 'none';
|
|
});
|
|
|
|
// 关闭摄像头
|
|
stopCameraBtn.addEventListener('click', () => {
|
|
if (stream) {
|
|
stream.getTracks().forEach(track => track.stop());
|
|
stream = null;
|
|
}
|
|
video.style.display = 'block';
|
|
capturedPhoto.style.display = 'none';
|
|
startCameraBtn.style.display = 'inline-block';
|
|
captureBtn.style.display = 'none';
|
|
stopCameraBtn.style.display = 'none';
|
|
});
|
|
|
|
// 确认照片
|
|
confirmPhotoBtn.addEventListener('click', () => {
|
|
// 模拟人脸识别
|
|
const recognizedName = simulateFaceRecognition();
|
|
|
|
if (recognizedName) {
|
|
markAsCompleted(recognizedName);
|
|
alert(`识别成功:${recognizedName}`);
|
|
} else {
|
|
alert('未能识别,请手动选择姓名');
|
|
}
|
|
|
|
// 重置拍照区域
|
|
capturedPhoto.style.display = 'none';
|
|
video.style.display = 'block';
|
|
captureBtn.style.display = 'inline-block';
|
|
});
|
|
|
|
// 重新拍摄
|
|
retakeBtn.addEventListener('click', () => {
|
|
capturedPhoto.style.display = 'none';
|
|
video.style.display = 'block';
|
|
captureBtn.style.display = 'inline-block';
|
|
});
|
|
|
|
// 模拟人脸识别
|
|
function simulateFaceRecognition() {
|
|
// 随机返回一个未点名的成员(模拟识别)
|
|
const uncompletedMembers = currentList.members.filter(m => !m.completed);
|
|
if (uncompletedMembers.length === 0) return null;
|
|
|
|
const randomIndex = Math.floor(Math.random() * uncompletedMembers.length);
|
|
return uncompletedMembers[randomIndex].name;
|
|
}
|
|
|
|
// 手动点名
|
|
manualRollCallBtn.addEventListener('click', () => {
|
|
const selectedName = manualSelect.value;
|
|
if (!selectedName) {
|
|
alert('请选择姓名');
|
|
return;
|
|
}
|
|
markAsCompleted(selectedName);
|
|
});
|
|
|
|
// 标记为已完成
|
|
async function markAsCompleted(name) {
|
|
try {
|
|
await dataStorage.rollCall(currentList.code, name);
|
|
|
|
// 更新本地数据
|
|
const member = currentList.members.find(m => m.name === name);
|
|
if (member) {
|
|
member.completed = true;
|
|
member.timestamp = new Date().toLocaleString();
|
|
}
|
|
|
|
// 更新UI
|
|
updateProgress();
|
|
renderMemberList();
|
|
populateManualSelect();
|
|
|
|
// 检查是否全部完成
|
|
const allCompleted = currentList.members.every(m => m.completed);
|
|
if (allCompleted) {
|
|
alert('点名完成!所有成员已点名');
|
|
}
|
|
} catch (error) {
|
|
alert('点名失败:' + error.message);
|
|
}
|
|
}
|