// 数学虚拟键盘实现 document.addEventListener('DOMContentLoaded', function() { // 初始化虚拟键盘 initMathKeyboard(); }); /** * 初始化数学虚拟键盘 */ function initMathKeyboard() { // 创建键盘切换按钮 createKeyboardToggle(); // 创建键盘容器 createKeyboardContainer(); // 绑定输入框事件 bindInputEvents(); } /** * 创建键盘切换按钮 */ function createKeyboardToggle() { const toggleButton = document.createElement('button'); toggleButton.className = 'keyboard-toggle'; toggleButton.innerHTML = '⌨'; toggleButton.title = '显示/隐藏数学键盘'; toggleButton.addEventListener('click', function() { toggleKeyboard(); }); document.body.appendChild(toggleButton); } /** * 创建键盘容器 */ function createKeyboardContainer() { // 创建键盘主容器 const keyboard = document.createElement('div'); keyboard.className = 'math-keyboard'; keyboard.id = 'math-keyboard'; // 设置键盘固定在窗口底部 keyboard.style.position = 'fixed'; keyboard.style.bottom = '0'; keyboard.style.left = '0'; keyboard.style.width = '100%'; keyboard.style.zIndex = '1000'; // 创建关闭按钮 const closeButton = document.createElement('button'); closeButton.className = 'keyboard-close'; closeButton.innerHTML = '×'; closeButton.title = '关闭键盘'; closeButton.addEventListener('click', function() { toggleKeyboard(false); }); keyboard.appendChild(closeButton); // 创建键盘标签页 const tabsContainer = document.createElement('div'); tabsContainer.className = 'keyboard-tabs'; const tabs = [ { id: 'basic', name: '基础' }, { id: 'functions', name: '函数' }, { id: 'symbols', name: '符号' }, { id: 'greek', name: '希腊字母' } ]; tabs.forEach((tab, index) => { const tabElement = document.createElement('div'); tabElement.className = 'keyboard-tab' + (index === 0 ? ' active' : ''); tabElement.dataset.tab = tab.id; tabElement.textContent = tab.name; tabElement.addEventListener('click', function() { document.querySelectorAll('.keyboard-tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.keyboard-panel').forEach(p => p.classList.remove('active')); this.classList.add('active'); document.getElementById(`keyboard-panel-${tab.id}`).classList.add('active'); }); tabsContainer.appendChild(tabElement); }); keyboard.appendChild(tabsContainer); // 创建键盘内容容器 const keyboardContainer = document.createElement('div'); keyboardContainer.className = 'keyboard-container'; // 创建各个面板 createBasicPanel(keyboardContainer); createFunctionsPanel(keyboardContainer); createSymbolsPanel(keyboardContainer); createGreekPanel(keyboardContainer); keyboard.appendChild(keyboardContainer); // 添加到文档 document.body.appendChild(keyboard); } /** * 创建功能键行(空格、退格、清除、关闭) */ function createFunctionKeysRow() { const specialRow = createKeyboardRow(); addKey(specialRow, '空格', ' ', 'wide'); addKey(specialRow, '退格', 'backspace', 'wide'); addKey(specialRow, '清除', 'clear', 'wide'); addKey(specialRow, '关闭', 'close', 'wide'); return specialRow; } /** * 创建基础面板 */ function createBasicPanel(container) { const panel = document.createElement('div'); panel.className = 'keyboard-panel active'; panel.id = 'keyboard-panel-basic'; // 数字行 const numberRow = createKeyboardRow(); ['7', '8', '9', '+', '(', ')'].forEach(key => { addKey(numberRow, key, key, key === '+' ? 'operator' : ''); }); panel.appendChild(numberRow); const numberRow2 = createKeyboardRow(); ['4', '5', '6', '-', '[', ']'].forEach(key => { addKey(numberRow2, key, key, key === '-' ? 'operator' : ''); }); panel.appendChild(numberRow2); const numberRow3 = createKeyboardRow(); ['1', '2', '3', '*', '{', '}'].forEach(key => { addKey(numberRow3, key, key, key === '*' ? 'operator' : ''); }); panel.appendChild(numberRow3); const numberRow4 = createKeyboardRow(); ['0', '.', ',', '/', '^', '='].forEach(key => { addKey(numberRow4, key, key, ['/', '^', '='].includes(key) ? 'operator' : ''); }); panel.appendChild(numberRow4); // 添加功能键行 panel.appendChild(createFunctionKeysRow()); container.appendChild(panel); } /** * 创建函数面板 */ function createFunctionsPanel(container) { const panel = document.createElement('div'); panel.className = 'keyboard-panel'; panel.id = 'keyboard-panel-functions'; // 基础函数行 const basicFuncRow = createKeyboardRow(); ['sin', 'cos', 'tan', 'asin', 'acos', 'atan'].forEach(key => { addKey(basicFuncRow, key, key + '()', 'function'); }); panel.appendChild(basicFuncRow); // 高级函数行 const advFuncRow = createKeyboardRow(); ['log', 'ln', 'exp', 'sqrt', 'abs', 'floor'].forEach(key => { addKey(advFuncRow, key, key + '()', 'function'); }); panel.appendChild(advFuncRow); // 统计函数行 const statFuncRow = createKeyboardRow(); ['max', 'min', 'mean', 'median', 'sum', 'std'].forEach(key => { addKey(statFuncRow, key, key + '()', 'function'); }); panel.appendChild(statFuncRow); // 特殊函数行 const specialFuncRow = createKeyboardRow(); ['factorial', 'pow', 'mod', 'round', 'ceil', 'random'].forEach(key => { addKey(specialFuncRow, key, key + '()', 'function'); }); panel.appendChild(specialFuncRow); // 添加功能键行 panel.appendChild(createFunctionKeysRow()); container.appendChild(panel); } /** * 创建符号面板 */ function createSymbolsPanel(container) { const panel = document.createElement('div'); panel.className = 'keyboard-panel'; panel.id = 'keyboard-panel-symbols'; // 数学符号行1 const symbolRow1 = createKeyboardRow(); ['±', '∞', '≈', '≠', '≤', '≥'].forEach(key => { let insert = key; if (key === '±') insert = '\\pm'; if (key === '∞') insert = '\\infty'; if (key === '≈') insert = '\\approx'; if (key === '≠') insert = '!='; if (key === '≤') insert = '<='; if (key === '≥') insert = '>='; addKey(symbolRow1, key, insert); }); panel.appendChild(symbolRow1); // 数学符号行2 const symbolRow2 = createKeyboardRow(); ['×', '÷', '∂', '∫', '∑', '∏'].forEach(key => { let insert = key; if (key === '×') insert = '*'; if (key === '÷') insert = '/'; if (key === '∂') insert = '\\partial'; if (key === '∫') insert = '\\int'; if (key === '∑') insert = '\\sum'; if (key === '∏') insert = '\\prod'; addKey(symbolRow2, key, insert); }); panel.appendChild(symbolRow2); // 数学符号行3 const symbolRow3 = createKeyboardRow(); ['√', '∛', '∜', '|x|', '⌊x⌋', '⌈x⌉'].forEach(key => { let insert = key; if (key === '√') insert = 'sqrt()'; if (key === '∛') insert = 'cbrt()'; if (key === '∜') insert = 'nthRoot(,4)'; if (key === '|x|') insert = 'abs()'; if (key === '⌊x⌋') insert = 'floor()'; if (key === '⌈x⌉') insert = 'ceil()'; addKey(symbolRow3, key, insert); }); panel.appendChild(symbolRow3); // 数学符号行4 const symbolRow4 = createKeyboardRow(); ['lim', '→', '∈', '∉', '⊂', '⊃'].forEach(key => { let insert = key; if (key === 'lim') insert = '\\lim'; if (key === '→') insert = '\\to'; if (key === '∈') insert = '\\in'; if (key === '∉') insert = '\\notin'; if (key === '⊂') insert = '\\subset'; if (key === '⊃') insert = '\\supset'; addKey(symbolRow4, key, insert); }); panel.appendChild(symbolRow4); // 添加功能键行 panel.appendChild(createFunctionKeysRow()); container.appendChild(panel); } /** * 创建希腊字母面板 */ function createGreekPanel(container) { const panel = document.createElement('div'); panel.className = 'keyboard-panel'; panel.id = 'keyboard-panel-greek'; // 希腊字母行1 const greekRow1 = createKeyboardRow(); ['α', 'β', 'γ', 'δ', 'ε', 'ζ'].forEach(key => { addKey(greekRow1, key, '\\' + getGreekName(key)); }); panel.appendChild(greekRow1); // 希腊字母行2 const greekRow2 = createKeyboardRow(); ['η', 'θ', 'ι', 'κ', 'λ', 'μ'].forEach(key => { addKey(greekRow2, key, '\\' + getGreekName(key)); }); panel.appendChild(greekRow2); // 希腊字母行3 const greekRow3 = createKeyboardRow(); ['ν', 'ξ', 'π', 'ρ', 'σ', 'τ'].forEach(key => { addKey(greekRow3, key, '\\' + getGreekName(key)); }); panel.appendChild(greekRow3); // 希腊字母行4 const greekRow4 = createKeyboardRow(); ['υ', 'φ', 'χ', 'ψ', 'ω', 'Δ'].forEach(key => { addKey(greekRow4, key, '\\' + getGreekName(key)); }); panel.appendChild(greekRow4); // 添加功能键行 panel.appendChild(createFunctionKeysRow()); container.appendChild(panel); } /** * 获取希腊字母的名称 */ function getGreekName(symbol) { const greekMap = { 'α': 'alpha', 'β': 'beta', 'γ': 'gamma', 'δ': 'delta', 'ε': 'epsilon', 'ζ': 'zeta', 'η': 'eta', 'θ': 'theta', 'ι': 'iota', 'κ': 'kappa', 'λ': 'lambda', 'μ': 'mu', 'ν': 'nu', 'ξ': 'xi', 'π': 'pi', 'ρ': 'rho', 'σ': 'sigma', 'τ': 'tau', 'υ': 'upsilon', 'φ': 'phi', 'χ': 'chi', 'ψ': 'psi', 'ω': 'omega', 'Α': 'Alpha', 'Β': 'Beta', 'Γ': 'Gamma', 'Δ': 'Delta', 'Ε': 'Epsilon', 'Ζ': 'Zeta', 'Η': 'Eta', 'Θ': 'Theta', 'Ι': 'Iota', 'Κ': 'Kappa', 'Λ': 'Lambda', 'Μ': 'Mu', 'Ν': 'Nu', 'Ξ': 'Xi', 'Π': 'Pi', 'Ρ': 'Rho', 'Σ': 'Sigma', 'Τ': 'Tau', 'Υ': 'Upsilon', 'Φ': 'Phi', 'Χ': 'Chi', 'Ψ': 'Psi', 'Ω': 'Omega' }; return greekMap[symbol] || symbol; } /** * 创建键盘行 */ function createKeyboardRow() { const row = document.createElement('div'); row.className = 'keyboard-row'; return row; } /** * 添加键盘按键 */ function addKey(row, display, value, className = '') { const key = document.createElement('div'); key.className = 'keyboard-key' + (className ? ' ' + className : ''); key.textContent = display; key.dataset.value = value; key.addEventListener('click', function() { handleKeyPress(this.dataset.value); }); row.appendChild(key); return key; } /** * 处理按键点击 */ function handleKeyPress(value) { // 优先使用记录的活动输入元素,如果没有则使用当前焦点元素 const activeElement = window.activeInputElement || document.activeElement; // 检查是否有活动的输入元素 if (!activeElement || !isInputElement(activeElement)) { // 如果没有活动输入元素,尝试查找页面上的可编辑单元格 const excelCells = document.querySelectorAll('.excel-cell'); if (excelCells.length > 0) { // 使用第一个Excel单元格作为目标 window.activeInputElement = excelCells[0]; excelCells[0].focus(); return handleKeyPress(value); // 重新调用处理函数 } return; } // 处理特殊按键 if (value === 'backspace') { deleteText(activeElement); return; } if (value === 'clear') { activeElement.value = ''; return; } if (value === 'close') { toggleKeyboard(false); return; } // 插入文本 insertTextAtCursor(activeElement, value); // 如果是函数,将光标移动到括号内 if (value.endsWith('()')) { const cursorPos = activeElement.selectionStart; activeElement.setSelectionRange(cursorPos - 1, cursorPos - 1); } } /** * 检查元素是否为输入元素 */ function isInputElement(element) { if (!element) return false; return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.isContentEditable || (element.classList && element.classList.contains('excel-cell')); } /** * 在光标位置插入文本 */ function insertTextAtCursor(input, text) { if (!input) return; const isContentEditable = input.isContentEditable || (input.classList && input.classList.contains('excel-cell')); if (isContentEditable) { // 处理可编辑内容 // 保存当前选区 const selection = window.getSelection(); const range = selection.getRangeAt(0); // 插入文本 const textNode = document.createTextNode(text); range.deleteContents(); range.insertNode(textNode); // 移动光标到插入文本的末尾 range.setStartAfter(textNode); range.setEndAfter(textNode); selection.removeAllRanges(); selection.addRange(range); } else { // 处理输入框和文本区域 const startPos = input.selectionStart; const endPos = input.selectionEnd; const beforeText = input.value.substring(0, startPos); const afterText = input.value.substring(endPos); input.value = beforeText + text + afterText; // 设置光标位置 const newCursorPos = startPos + text.length; input.setSelectionRange(newCursorPos, newCursorPos); } // 触发输入事件 const event = new Event('input', { bubbles: true }); input.dispatchEvent(event); // 保持焦点 input.focus(); } /** * 删除光标前的文本 */ function deleteText(input) { if (!input) return; const isContentEditable = input.isContentEditable || (input.classList && input.classList.contains('excel-cell')); if (isContentEditable) { // 处理可编辑内容 const selection = window.getSelection(); const range = selection.getRangeAt(0); if (range.collapsed) { // 光标位置,没有选中文本 // 创建一个新的范围,包含光标前的一个字符 const newRange = range.cloneRange(); newRange.setStart(range.startContainer, Math.max(0, range.startOffset - 1)); newRange.setEnd(range.startContainer, range.startOffset); newRange.deleteContents(); } else { // 有选中的文本,直接删除 range.deleteContents(); } } else { // 处理输入框和文本区域 const startPos = input.selectionStart; const endPos = input.selectionEnd; if (startPos === endPos) { // 没有选中文本,删除前一个字符 if (startPos > 0) { const beforeText = input.value.substring(0, startPos - 1); const afterText = input.value.substring(endPos); input.value = beforeText + afterText; input.setSelectionRange(startPos - 1, startPos - 1); } } else { // 删除选中的文本 const beforeText = input.value.substring(0, startPos); const afterText = input.value.substring(endPos); input.value = beforeText + afterText; input.setSelectionRange(startPos, startPos); } } // 触发输入事件 const event = new Event('input', { bubbles: true }); input.dispatchEvent(event); // 保持焦点 input.focus(); } /** * 切换键盘显示/隐藏 * @param {boolean} forceClose - 如果为true,则强制关闭键盘 */ function toggleKeyboard(forceClose = null) { const keyboard = document.getElementById('math-keyboard'); if (!keyboard) return; if (forceClose === false) { keyboard.classList.remove('active'); } else if (forceClose === true) { keyboard.classList.add('active'); } else { keyboard.classList.toggle('active'); } // 更新键盘切换按钮状态 const toggleButton = document.querySelector('.keyboard-toggle'); if (toggleButton) { if (keyboard.classList.contains('active')) { toggleButton.classList.add('active'); } else { toggleButton.classList.remove('active'); } } // 如果键盘被激活,尝试聚焦到当前活动的输入元素 if (keyboard.classList.contains('active') && window.activeInputElement) { window.activeInputElement.focus(); } } /** * 绑定输入框事件 */ function bindInputEvents() { // 监听文档中所有输入元素的焦点事件 document.addEventListener('focusin', function(e) { // 检查获得焦点的元素是否为输入元素 if (isInputElement(e.target)) { // 记录当前活动的输入元素 window.activeInputElement = e.target; } }); // 监听Excel表格单元格的焦点事件 document.addEventListener('click', function(e) { if (e.target.classList && e.target.classList.contains('excel-cell')) { // 记录当前活动的Excel单元格 window.activeInputElement = e.target; } }); }