| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- // 数学虚拟键盘实现
- 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;
- }
- });
- }
|