add some very nb stuff and some massive bugs

This commit is contained in:
2025-10-21 14:56:09 +08:00
Unverified
parent 48a1fe1e4d
commit edac0f25e0
5 changed files with 154 additions and 315 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -1,92 +1,94 @@
// main.js
const { app, BrowserWindow, dialog, ipcMain } = require('electron'); // 1. 导入 ipcMain
const { app, BrowserWindow, globalShortcut, screen, ipcMain } = require('electron'); // 添加 ipcMain
const path = require('path');
// const cv = require('opencv4nodejs'); // 已移除视觉作弊相关
let mainWindow;
let switchCount = 0; // 在主进程中也维护一个计数器
const switchLimit = 3;
const warningLimit = 2;
// --- 已移除视觉作弊相关变量 ---
function createWindow () {
// 获取主显示器尺寸
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.size;
mainWindow = new BrowserWindow({
// --- 关键修改:设置为全屏无框 ---
width: width,
height: height,
fullscreen: true,
frame: false,
kiosk: true, // 关键kiosk 模式隐藏菜单栏、Dock
alwaysOnTop: true,
resizable: false,
movable: false,
fullscreenable: false,
titleBarStyle: 'hidden',
webPreferences: {
nodeIntegration: false,
contextIsolation: true, // 2. 必须开启
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js') // 3. 关键:加载 preload 脚本
},
// --- 可选:设置图标 ---
// icon: path.join(__dirname, 'icon.png')
preload: path.join(__dirname, 'preload.js')
}
});
mainWindow.loadFile('student_app_fullscreen.html');
// --- 关键修改:监听窗口失去焦点事件 ---
mainWindow.on('blur', () => {
switchCount++;
console.log(`窗口失去焦点,切屏次数: ${switchCount}`); // 控制台输出日志
if (switchCount <= warningLimit - 1 ) {
// 显示警告对话框
dialog.showMessageBox(mainWindow, {
type: 'warning',
title: '切屏警告',
message: `您进行了切屏操作,此行为已被记录,如您再次进行两次切屏,则会重置考试`,
buttons: ['确定']
}).then(() => {
// 警告后,将焦点重新移回主窗口
mainWindow.focus();
});
} else if (switchCount < switchLimit) {
// 第三次切屏,重置警告
dialog.showMessageBox(mainWindow, {
type: 'warning',
title: '切屏警告',
message: `您再次进行了切屏操作,此行为已被记录,如您再次进行一次切屏,则会重置考试`,
buttons: ['确定']
}).then(() => {
mainWindow.focus();
});
} else {
// 超过限制次数
dialog.showMessageBox(mainWindow, {
type: 'error',
title: '切屏过多',
message: `您因切屏超过误触缓冲次数,重置考试`,
buttons: ['确定']
}).then(() => {
// 重置考试:重新加载页面,重置计数器
switchCount = 0;
mainWindow.reload();
});
}
// --- 移除窗口关闭拦截,允许通过按钮退出 ---
// 启动时就注册全局快捷键拦截
registerGlobalShortcuts();
// 添加退出应用的IPC处理程序
ipcMain.handle('exit-app', async () => {
app.quit();
});
// --- 结束监听 ---
// 监听窗口关闭事件
mainWindow.on('closed', () => {
mainWindow = null;
});
// --- 已移除视觉作弊检测启动 ---
}
// --- 已移除视觉作弊检测相关函数 ---
// 注册全局快捷键拦截
function registerGlobalShortcuts() {
// 移除 Esc 退出功能
// 尝试注册其他快捷键(在无辅助功能权限时可能无效,但无害)
const shortcuts = [
'CommandOrControl+Tab',
'CommandOrControl+Shift+Tab',
'Command+Q',
'Command+W',
'Command+R',
'F5',
'F11',
'F12',
'Alt+Tab',
'Alt+F4',
'Control+Escape',
'CommandOrControl+W',
'CommandOrControl+R',
'Control+R',
'Alt+Space', // Windows打开菜单
'Command+Space', // Mac打开Spotlight
'Command+M', // Mac最小化
'Command+H', // Mac隐藏
'Command+Option+H', // Mac隐藏其他
'Command+Option+M', // Mac最小化所有
'Command+Option+Escape', // Mac强制退出
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', // 功能键
'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24',
'Fn' // 禁用 Fn 键
];
// --- IPC 处理程序 ---
// 处理来自渲染进程的获取切屏次数请求
ipcMain.handle('get-switch-count', async () => {
return switchCount; // 返回当前计数
});
shortcuts.forEach(key => {
try {
globalShortcut.register(key, () => {
// 吃掉事件,什么都不做
console.log(`⚠️ 阻止了快捷键:`, key);
});
} catch (e) {
console.warn('Failed to register:', key);
}
});
}
// --- 已移除未认真答题次数相关 IPC ---
// 注销全局快捷键拦截
function unregisterGlobalShortcuts() {
globalShortcut.unregisterAll();
}
app.whenReady().then(() => {
createWindow();
@@ -97,16 +99,7 @@ app.whenReady().then(() => {
});
app.on('window-all-closed', function () {
// --- 已移除应用退出时停止色码检测 ---
// 注销所有快捷键
unregisterGlobalShortcuts();
if (process.platform !== 'darwin') app.quit();
});
// --- 注释掉或移除旧的 IPC 代码 ---
// const { ipcMain } = require('electron');
// ipcMain.on('get-switch-count', (event) => {
// event.reply('switch-count-reply', switchCount);
// });
// ipcMain.on('reset-switch-count', () => {
// switchCount = 0;
// });
// --- 结束注释 ---
});

View File

@@ -1,12 +1,8 @@
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 安全地暴露 API 到渲染进程
// 安全地暴露退出应用的 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 向主进程请求获取切屏次数
getSwitchCount: () => ipcRenderer.invoke('get-switch-count'),
// 已移除未认真答题次数相关 API
// 如果需要,也可以暴露重置计数的 API
// resetSwitchCount: () => ipcRenderer.invoke('reset-switch-count'),
// resetOffTaskCount: () => ipcRenderer.invoke('reset-off-task-count'),
// 退出应用
exitApp: () => ipcRenderer.invoke('exit-app')
});

View File

@@ -965,9 +965,40 @@
margin-bottom: 10px;
}
}
/* 添加全局退出按钮样式 */
#exitButton {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
background: linear-gradient(45deg, #FF3B30, #ff6b6b);
color: white;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 20px;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
#exitButton:hover {
transform: scale(1.1);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
}
</style>
</head>
<body>
<!-- 全局退出按钮 -->
<button id="exitButton" title="退出程序">
<i class="fas fa-power-off"></i>
</button>
<!-- 鼠标跟随小圈 -->
<div class="cursor-follower"></div>
<!-- 背景粒子 -->
@@ -1155,17 +1186,6 @@
<div class="result-stat-number" id="totalCount">0</div>
<div class="result-stat-label">总计</div>
</div>
<!-- --- 添加切屏次数统计 --- -->
<div class="result-stat-card">
<div class="result-stat-number" id="switchCountFinal">0</div>
<div class="result-stat-label">切屏次数</div>
</div>
<!-- --- 添加未认真答题次数统计 --- -->
<div class="result-stat-card">
<div class="result-stat-number" id="offTaskCountFinal">0</div>
<div class="result-stat-label">未认真答题次数</div>
</div>
<!-- --- 结束添加 --- -->
</div>
<div id="errorDetails" class="error-details">
<h3><i class="fas fa-times-circle"></i> 错误详情</h3>
@@ -1181,50 +1201,17 @@
</div>
</div>
<script>
// 内置词典 - 与教师端保持一致
let toeflWords = [
{ "word": "instrument", "translation": "n. 工具、手段" },
{ "word": "therefore", "translation": "adv. 因此" },
{ "word": "tradition", "translation": "n. 传统" },
{ "word": "represent", "translation": "v. 代表、描绘" },
{ "word": "iceberg", "translation": "n. 冰山" },
{ "word": "store", "translation": "v. 储存" },
{ "word": "depend", "translation": "v. 依赖" },
{ "word": "general", "translation": "adj. 整体的、普遍的、大体的" },
{ "word": "contrast", "translation": "n. 对比、差别" },
{ "word": "predator", "translation": "n. 捕食者" },
{ "word": "against", "translation": "prep. 与……相反、违背、对比、以…为背景" },
{ "word": "survive", "translation": "v. 存活、挺过、比……活的长" },
{ "word": "nature", "translation": "n. 自然、本质" },
{ "word": "solar", "translation": "adj. 太阳的" },
{ "word": "architecture", "translation": "n. 建筑" },
{ "word": "primarily", "translation": "adv. 主要地" },
{ "word": "observe", "translation": "v. 观察(到)、遵从(法律等)" },
{ "word": "canal", "translation": "n. 运河" },
{ "word": "craft", "translation": "n. 手艺" },
{ "word": "reflect", "translation": "v. 反映、反射、反思" },
{ "word": "argue", "translation": "v. 论证" },
{ "word": "draw", "translation": "v. 画、得出、提取、轻拉" },
{ "word": "theater", "translation": "n. 剧院、戏剧表演" },
{ "word": "especially", "translation": "adv. 特别是、尤其" },
{ "word": "aggressive", "translation": "adj. 攻击性的" },
{ "word": "extreme", "translation": "adj.&n. 极度、极端、极大(的)" },
{ "word": "sense", "translation": "n. 意识" },
{ "word": "single", "translation": "adj. 单一的、单身的 v. 选中" },
{ "word": "previous", "translation": "adj. 先前的" },
{ "word": "obtain", "translation": "v. 获得" }
];
// 移除内置词库,因为我们现在只使用导入的配置
// let toeflWords = []; // 不再需要内置词库
// 全局变量
let selectedWords = []; // 存储选中的单词对象 { word, translation }
let importedWords = []; // 存储从配置文件导入的单词对象 { word, translation }
let currentWordIndex = 0;
let dictationStarted = false;
let userAnswers = []; // 存储用户答案 (字符串)
let correctAnswers = []; // 存储正确答案 (字符串) - 修复:存储英文单词字符串
let correctAnswers = []; // 存储正确答案 (字符串)
// --- 新增:用于存储用户输入的当前单词 ---
let currentInput = '';
// --- 新增:用于存储切屏次数 (仅用于重置) ---
let switchCountLocal = 0; // 仅用于本地重置,主进程中的计数由 Electron IPC 获取
// --- 结束新增 ---
// --- 新增:用于提取 .js 文件中的 Base64 字符串 ---
@@ -1244,8 +1231,6 @@
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function() {
// renderWordGrid(); // 移除教师端初始化
// updateStats(); // 移除教师端初始化
createParticles();
initCursorFollower();
// 直接显示学生端界面
@@ -1262,15 +1247,32 @@
}
// --- 新增:设置按键监听器 ---
document.addEventListener('keydown', handleKeyDown);
// --- 添加更严格的键盘事件拦截 ---
// 阻止右键菜单
document.addEventListener('contextmenu', (e) => e.preventDefault());
// 阻止拖拽
document.addEventListener('dragstart', (e) => e.preventDefault());
// 阻止文本选择
document.addEventListener('selectstart', (e) => e.preventDefault());
// --- 结束新增 ---
// 添加全局退出按钮事件监听器
document.getElementById('exitButton').addEventListener('click', function() {
// 直接退出应用
if (window.electronAPI && typeof window.electronAPI.exitApp === 'function') {
window.electronAPI.exitApp();
}
});
});
// --- 新增:处理按键输入 ---
function handleKeyDown(event) {
// 只在默写进行时处理
if (!dictationStarted) return;
// 防止默认行为(如 F5 刷新、Ctrl+R 等)
// 阻止所有默认行为,包括 Esc 键
event.preventDefault();
event.stopPropagation();
// 如果没有开始默写,不处理其他按键
if (!dictationStarted) return;
const key = event.key.toLowerCase();
@@ -1292,10 +1294,7 @@
else if (key === ' ') {
// 忽略空格
}
// 处理其他键(如方向键、功能键等,可以忽略或记录)
else {
// console.log('未处理的按键:', key); // 调试用
}
// 忽略所有其他键,包括 Esc 键
}
function updateInputDisplay() {
@@ -1406,153 +1405,54 @@
}
}
// 渲染单词网格 - 移除
function renderWordGrid(filteredWords = toeflWords) {
function renderWordGrid(filteredWords = []) {
const wordGrid = document.getElementById('wordGrid');
wordGrid.innerHTML = '';
filteredWords.forEach(wordObj => {
const wordItem = document.createElement('div');
wordItem.className = `word-item ${selectedWords.some(w => w.word === wordObj.word) ? 'selected' : ''}`;
wordItem.className = `word-item`;
// 修改:显示单词和翻译
wordItem.innerHTML = `
<div class="word-text">${wordObj.word}</div>
<div class="word-translation">${wordObj.translation}</div>
`;
wordItem.onclick = () => toggleWordSelection(wordObj); // 传递整个对象
wordGrid.appendChild(wordItem);
});
}
// 切换单词选择状态 - 移除
function toggleWordSelection(wordObj) {
const index = selectedWords.findIndex(w => w.word === wordObj.word);
if (index > -1) {
selectedWords.splice(index, 1);
} else {
selectedWords.push(wordObj);
}
renderWordGrid();
updateStats();
}
// 更新统计信息 - 移除
function updateStats() {
const total = toeflWords.length;
const selected = selectedWords.length;
const percentage = total > 0 ? Math.round((selected / total) * 100) : 0;
document.getElementById('selectedCount').textContent = selected;
document.getElementById('totalWords').textContent = total;
document.getElementById('percentage').textContent = percentage + '%';
}
// 全选单词 - 移除
function selectAllWords() {
// 确保 selectedWords 包含 toeflWords 中的所有单词对象
selectedWords = [...toeflWords];
renderWordGrid(); // 重新渲染网格以反映选择状态
updateStats(); // 更新统计信息
// 不再需要这个功能
alert('此功能已禁用');
}
// 取消全选 - 移除
function deselectAllWords() {
selectedWords = []; // 清空已选单词数组
renderWordGrid(); // 重新渲染网格
updateStats(); // 更新统计信息
// 不再需要这个功能
alert('此功能已禁用');
}
// 随机选择60个单词 - 移除
function randomSelectWords() {
selectedWords = [];
// 创建一个 toeflWords 的副本并随机打乱
const shuffled = [...toeflWords].sort(() => 0.5 - Math.random());
// 选取前60个或如果总数不足60个则全部选取
selectedWords = shuffled.slice(0, Math.min(60, shuffled.length));
renderWordGrid(); // 重新渲染网格
updateStats(); // 更新统计信息
// 不再需要这个功能
alert('此功能已禁用');
}
// 搜索单词 - 移除
function filterWords() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
if (searchTerm === '') {
renderWordGrid();
document.getElementById('addWordSuggestion').style.display = 'none';
} else {
// 修改:在单词和翻译中搜索
const filtered = toeflWords.filter(wordObj =>
wordObj.word.toLowerCase().includes(searchTerm) ||
wordObj.translation.toLowerCase().includes(searchTerm)
);
renderWordGrid(filtered);
// 如果没有找到匹配的单词,显示添加单词提示
if (filtered.length === 0) {
document.getElementById('addWordSuggestion').style.display = 'block';
// 不再直接设置 newWordText而是预填充输入框
document.getElementById('newWordInput').value = searchTerm;
document.getElementById('newTranslationInput').value = '';
} else {
document.getElementById('addWordSuggestion').style.display = 'none';
}
}
// 不再需要这个功能
alert('此功能已禁用');
}
// 添加新单词 (带翻译) - 移除
function addNewWordWithTranslation() {
const newWord = document.getElementById('newWordInput').value.trim().toLowerCase();
const newTranslation = document.getElementById('newTranslationInput').value.trim();
if (newWord && newTranslation) {
// 检查是否已存在
if (!toeflWords.some(w => w.word === newWord)) {
const newWordObj = { word: newWord, translation: newTranslation };
toeflWords.push(newWordObj);
toeflWords.sort((a, b) => a.word.localeCompare(b.word)); // 按字母排序
renderWordGrid();
updateStats();
document.getElementById('addWordSuggestion').style.display = 'none';
document.getElementById('searchInput').value = '';
document.getElementById('newWordInput').value = '';
document.getElementById('newTranslationInput').value = '';
// 显示添加成功提示
alert(`单词 "${newWord}" (${newTranslation}) 已成功添加到词库中!`);
} else {
alert(`单词 "${newWord}" 已存在于词库中!`);
}
} else {
alert('请输入英文单词和中文翻译!');
}
// 不再需要这个功能
alert('此功能已禁用');
}
// 清除搜索 - 移除
function clearSearch() {
document.getElementById('searchInput').value = '';
document.getElementById('addWordSuggestion').style.display = 'none';
renderWordGrid();
// 不再需要这个功能
alert('此功能已禁用');
}
// 导出配置文件 - 移除
function exportConfig() {
if (selectedWords.length === 0) {
alert('请先选择单词!');
return;
}
const config = {
words: selectedWords, // 导出包含 word 和 translation 的对象数组
createdTime: new Date().toISOString(),
version: '1.1' // 更新版本号
};
// 创建Blob对象
const blob = new Blob([JSON.stringify(config, null, 2)], {type: 'application/json'});
// 创建下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'toefl_words_config_with_translation.json';
document.body.appendChild(a);
a.click();
// 清理
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
// 添加成功动画
const exportBtn = event.target.closest('.action-btn');
const originalText = exportBtn.innerHTML;
exportBtn.innerHTML = '<i class="fas fa-check"></i> 已导出';
exportBtn.classList.add('btn-success');
setTimeout(() => {
exportBtn.innerHTML = originalText;
exportBtn.classList.remove('btn-success');
}, 2000);
// 不再需要这个功能
alert('此功能已禁用');
}
// 处理文件选择 (修改版,支持 .js 文件,解码 Base64 英文数组并查找翻译)
function handleFileSelect(event) {
@@ -1575,19 +1475,12 @@
if (Array.isArray(decodedWordsJson)) {
console.log("成功从 .js 文件解析并解码 Base64 英文单词数组。"); // 调试信息
// --- 核心修改:根据解码出的英文单词,在内置词典中查找翻译 ---
// --- 核心修改:根据解码出的英文单词,创建对象数组 ---
importedWords = decodedWordsJson.map(word => {
const foundWordObj = toeflWords.find(item => item.word === word);
if (foundWordObj) {
return { word: foundWordObj.word, translation: foundWordObj.translation };
} else {
// 如果在内置词典中找不到,翻译为空字符串
console.warn(`在内置词典中未找到单词: ${word}`);
return { word: word, translation: '' };
}
return { word: word, translation: '' }; // 没有内置词库,翻译为空
});
// 检查是否有成功找到翻译的单词
// 检查是否有成功解析的单词
if (importedWords.length > 0) {
// 使用第一个单词的创建时间(或当前时间)作为配置时间
const createdTime = new Date().toISOString(); // 或者可以从 .js 文件中也编码时间,但这里简化处理
@@ -1595,7 +1488,7 @@
document.getElementById('importedTime').textContent = new Date(createdTime).toLocaleString();
document.getElementById('importInfo').style.display = 'block';
} else {
throw new Error('从 .js 文件解码出的单词列表在内置词典中均未找到匹配项');
throw new Error('从 .js 文件解码出的单词列表为空');
}
} else {
throw new Error('解码出的 Base64 内容不是有效的单词数组');
@@ -1630,14 +1523,11 @@
correctAnswers = importedWords.map(w => w.word);
currentWordIndex = 0;
dictationStarted = true;
// --- 重置输入和切屏计数 (本地) ---
// --- 重置输入 ---
currentInput = '';
updateInputDisplay();
switchCountLocal = 0; // 重置本地计数,主进程计数由 Electron 管理
// --- 结束重置 ---
showCurrentWord();
// --- 移除聚焦 ---
// document.getElementById('answerInput').focus(); // 移除对输入框的聚焦
}
// 显示当前单词(横杠形式)和翻译,并显示首字母
function showCurrentWord() {
@@ -1660,18 +1550,11 @@
Math.round(((currentWordIndex + 1) / importedWords.length) * 100) : 0;
document.getElementById('progressFill').style.width = progress + '%';
// --- 回填答案到显示区域 ---
// document.getElementById('answerInput').value = userAnswers[currentWordIndex] || ''; // 移除
currentInput = userAnswers[currentWordIndex] || '';
updateInputDisplay(); // 更新显示
// --- 结束回填 ---
}
}
// 处理键盘按键 - 移除 (已在 handleKeyDown 中处理)
// function handleKeyPress(event) {
// if (event.key === 'Enter') {
// submitAnswer();
// }
// }
// 提交答案
function submitAnswer() {
if (dictationStarted) {
@@ -1735,38 +1618,6 @@
document.getElementById('wrongCount').textContent = totalCount - correctCount;
document.getElementById('totalCount').textContent = totalCount;
// --- 从主进程获取切屏次数并更新显示 ---
// 现在使用正确的 IPC 调用
let switchCountValue = 0; // 初始化变量
let offTaskCountValue = 0; // 初始化变量
const switchCountPromise = (window.electronAPI && typeof window.electronAPI.getSwitchCount === 'function')
? window.electronAPI.getSwitchCount()
: Promise.resolve(0); // 如果 API 不可用,返回 0
const offTaskCountPromise = (window.electronAPI && typeof window.electronAPI.getOffTaskCount === 'function')
? window.electronAPI.getOffTaskCount()
: Promise.resolve(0); // 如果 API 不可用,返回 0
Promise.all([switchCountPromise, offTaskCountPromise])
.then(([switchCount, offTaskCount]) => {
// 确保获取到的是数字
switchCountValue = typeof switchCount === 'number' ? switchCount : 0;
offTaskCountValue = typeof offTaskCount === 'number' ? offTaskCount : 0;
document.getElementById('switchCountFinal').textContent = switchCountValue;
document.getElementById('offTaskCountFinal').textContent = offTaskCountValue;
console.log("成功获取主进程切屏次数:", switchCountValue, "未认真答题次数:", offTaskCountValue); // 调试信息
})
.catch((error) => {
console.error("获取次数失败:", error);
// 如果 IPC 调用失败,回退到本地计数(虽然不准确)
// 注意:这里需要确保本地没有定义同名的全局变量
document.getElementById('switchCountFinal').textContent = '获取失败';
document.getElementById('offTaskCountFinal').textContent = '获取失败';
});
// --- 结束更新 ---
// 根据正确率显示等级
let gradeClass = '';
let gradeText = '';
@@ -1828,10 +1679,9 @@
userAnswers = new Array(importedWords.length).fill('');
currentWordIndex = 0;
dictationStarted = true;
// --- 重置输入和切屏计数 (本地) ---
// --- 重置输入 ---
currentInput = '';
updateInputDisplay();
switchCountLocal = 0; // 重置本地计数
// --- 结束重置 ---
showCurrentWord();
}