// Excel风格数据表格组件 document.addEventListener('DOMContentLoaded', function() { // 初始化数据表格 initDataTables(); }); /** * 初始化所有数据表格 */ function initDataTables() { // 初始化拟合数据表格 initFitDataTable(); // 初始化回归分析表格 initRegressionTable(); } /** * 初始化拟合数据表格 */ function initFitDataTable() { const tableContainer = document.querySelector('#fit-tab .table-container'); if (!tableContainer) return; // 清空现有内容 tableContainer.innerHTML = ''; // 创建Excel风格表格 const excelTable = createExcelTable('fit-excel-table', 2); // X和Y两列 tableContainer.appendChild(excelTable); // 添加示例数据 const fitTable = document.getElementById('fit-excel-table'); addExcelRow(fitTable, [1, 2]); addExcelRow(fitTable, [2, 4]); addExcelRow(fitTable, [3, 9]); // 添加CSV粘贴支持 enableCSVPaste(fitTable); // 添加表格操作按钮 updateTableActions('fit-tab', fitTable); } /** * 初始化回归分析表格 */ function initRegressionTable() { // 表格模式 const tableContainer = document.querySelector('#table-input-mode .table-container'); if (tableContainer) { // 清空现有内容 tableContainer.innerHTML = ''; // 创建Excel风格表格 const excelTable = createExcelTable('regression-excel-table', 3); // X1, X2和Y三列 tableContainer.appendChild(excelTable); // 添加示例数据 const regressionTable = document.getElementById('regression-excel-table'); addExcelRow(regressionTable, [1, 2, 3]); addExcelRow(regressionTable, [4, 5, 6]); // 添加CSV粘贴支持 enableCSVPaste(regressionTable); // 添加表格操作按钮 updateTableActions('regression-tab', regressionTable); } // CSV粘贴模式 const csvTextarea = document.getElementById('regression-data'); if (csvTextarea) { // 为CSV文本区域添加粘贴处理 csvTextarea.addEventListener('paste', handleCSVPaste); } // 切换输入模式按钮 const tableModeBtn = document.getElementById('table-mode-btn'); const csvModeBtn = document.getElementById('csv-mode-btn'); const tableInputMode = document.getElementById('table-input-mode'); const csvInputMode = document.getElementById('csv-input-mode'); if (tableModeBtn && csvModeBtn) { tableModeBtn.addEventListener('click', function() { tableModeBtn.classList.add('active'); csvModeBtn.classList.remove('active'); tableInputMode.classList.add('active'); csvInputMode.classList.remove('active'); }); csvModeBtn.addEventListener('click', function() { csvModeBtn.classList.add('active'); tableModeBtn.classList.remove('active'); csvInputMode.classList.add('active'); tableInputMode.classList.remove('active'); }); } } /** * 创建Excel风格表格 * @param {string} id - 表格ID * @param {number} columnCount - 初始列数 * @returns {HTMLElement} - 创建的表格元素 */ function createExcelTable(id, columnCount) { const table = document.createElement('div'); table.id = id; table.className = 'excel-table'; // 创建表头 const header = document.createElement('div'); header.className = 'excel-header'; // 添加空白角落单元格 const cornerCell = document.createElement('div'); cornerCell.className = 'excel-corner-cell'; header.appendChild(cornerCell); // 添加列标题 (A, B, C...) for (let i = 0; i < columnCount; i++) { const columnHeader = document.createElement('div'); columnHeader.className = 'excel-column-header'; // 创建列标题和删除按钮的容器 const columnHeaderContent = document.createElement('div'); columnHeaderContent.className = 'column-header-content'; columnHeaderContent.textContent = String.fromCharCode(65 + i); // A, B, C... // 创建删除列按钮 const deleteColBtn = document.createElement('div'); deleteColBtn.className = 'delete-col-btn'; deleteColBtn.innerHTML = '×'; deleteColBtn.title = '删除列'; deleteColBtn.addEventListener('click', function(e) { e.stopPropagation(); // 获取当前列的索引,而不是使用创建时的索引 const currentHeaders = table.querySelectorAll('.excel-column-header'); const currentIndex = Array.from(currentHeaders).indexOf(columnHeader); deleteExcelColumn(table, currentIndex); }); columnHeader.appendChild(columnHeaderContent); columnHeader.appendChild(deleteColBtn); header.appendChild(columnHeader); } // 添加添加列按钮 const addColumnBtn = document.createElement('div'); addColumnBtn.className = 'excel-add-column'; addColumnBtn.innerHTML = '+'; addColumnBtn.title = '添加列'; addColumnBtn.addEventListener('click', function() { addExcelColumn(table); }); header.appendChild(addColumnBtn); table.appendChild(header); // 创建表格内容区域 const body = document.createElement('div'); body.className = 'excel-body'; table.appendChild(body); // 添加添加行按钮 const footer = document.createElement('div'); footer.className = 'excel-footer'; const addRowBtn = document.createElement('div'); addRowBtn.className = 'excel-add-row'; addRowBtn.innerHTML = '+'; addRowBtn.title = '添加行'; addRowBtn.addEventListener('click', function() { addExcelRow(table); }); footer.appendChild(addRowBtn); table.appendChild(footer); return table; } /** * 添加Excel表格行 * @param {HTMLElement} table - 表格元素 * @param {Array} values - 行数据值 * @returns {HTMLElement} - 创建的行元素 */ function addExcelRow(table, values = []) { const body = table.querySelector('.excel-body'); const rowIndex = body.children.length; const row = document.createElement('div'); row.className = 'excel-row'; row.dataset.index = rowIndex; // 添加行号和删除行按钮 const rowHeader = document.createElement('div'); rowHeader.className = 'excel-row-header'; // 创建行号和删除按钮的容器 const rowHeaderContent = document.createElement('div'); rowHeaderContent.className = 'row-header-content'; rowHeaderContent.textContent = rowIndex + 1; // 创建删除行按钮 const deleteRowBtn = document.createElement('div'); deleteRowBtn.className = 'delete-row-btn'; deleteRowBtn.innerHTML = '×'; deleteRowBtn.title = '删除行'; deleteRowBtn.addEventListener('click', function(e) { e.stopPropagation(); deleteExcelRow(table, rowIndex); }); rowHeader.appendChild(rowHeaderContent); rowHeader.appendChild(deleteRowBtn); row.appendChild(rowHeader); // 获取列数 const columnCount = table.querySelector('.excel-header').children.length - 2; // 减去角落单元格和添加列按钮 // 添加单元格 for (let i = 0; i < columnCount; i++) { const cell = document.createElement('div'); cell.className = 'excel-cell'; cell.dataset.row = rowIndex; cell.dataset.col = i; cell.contentEditable = true; // 设置初始值 if (values && values[i] !== undefined) { cell.textContent = values[i]; } // 添加单元格事件 cell.addEventListener('focus', function() { this.classList.add('active'); }); cell.addEventListener('blur', function() { this.classList.remove('active'); }); cell.addEventListener('keydown', function(e) { handleCellKeydown(e, table, this); }); row.appendChild(cell); } body.appendChild(row); return row; } /** * 添加Excel表格列 * @param {HTMLElement} table - 表格元素 */ function addExcelColumn(table) { // 更新表头 const header = table.querySelector('.excel-header'); const columnCount = header.children.length - 2; // 减去角落单元格和添加列按钮 // 在添加列按钮前插入新列标题 const columnHeader = document.createElement('div'); columnHeader.className = 'excel-column-header'; // 创建列标题和删除按钮的容器 const columnHeaderContent = document.createElement('div'); columnHeaderContent.className = 'column-header-content'; columnHeaderContent.textContent = String.fromCharCode(65 + columnCount); // A, B, C... // 创建删除列按钮 const deleteColBtn = document.createElement('div'); deleteColBtn.className = 'delete-col-btn'; deleteColBtn.innerHTML = '×'; deleteColBtn.title = '删除列'; deleteColBtn.addEventListener('click', function(e) { e.stopPropagation(); // 获取当前列的索引,而不是使用创建时的索引 const currentHeaders = table.querySelectorAll('.excel-column-header'); const currentIndex = Array.from(currentHeaders).indexOf(columnHeader); deleteExcelColumn(table, currentIndex); }); columnHeader.appendChild(columnHeaderContent); columnHeader.appendChild(deleteColBtn); header.insertBefore(columnHeader, header.lastChild); // 为每一行添加新单元格 const rows = table.querySelectorAll('.excel-row'); rows.forEach((row, rowIndex) => { const cell = document.createElement('div'); cell.className = 'excel-cell'; cell.dataset.row = rowIndex; cell.dataset.col = columnCount; cell.contentEditable = true; // 添加单元格事件 cell.addEventListener('focus', function() { this.classList.add('active'); }); cell.addEventListener('blur', function() { this.classList.remove('active'); }); cell.addEventListener('keydown', function(e) { handleCellKeydown(e, table, this); }); row.appendChild(cell); }); } /** * 处理单元格键盘事件 * @param {Event} e - 键盘事件 * @param {HTMLElement} table - 表格元素 * @param {HTMLElement} cell - 当前单元格 */ function handleCellKeydown(e, table, cell) { const row = parseInt(cell.dataset.row); const col = parseInt(cell.dataset.col); // 按Tab键移动到下一个单元格 if (e.key === 'Tab') { e.preventDefault(); const nextCell = e.shiftKey ? findCell(table, row, col - 1) : findCell(table, row, col + 1); if (nextCell) { nextCell.focus(); } else if (!e.shiftKey) { // 如果是最后一个单元格,添加新行并聚焦第一个单元格 const newRow = addExcelRow(table); const firstCell = newRow.querySelector('.excel-cell'); if (firstCell) firstCell.focus(); } } // 按Enter键移动到下一行 if (e.key === 'Enter') { e.preventDefault(); const nextCell = e.shiftKey ? findCell(table, row - 1, col) : findCell(table, row + 1, col); if (nextCell) { nextCell.focus(); } else if (!e.shiftKey) { // 如果是最后一行,添加新行并聚焦相同列的单元格 const newRow = addExcelRow(table); const sameColCell = newRow.querySelector(`.excel-cell[data-col="${col}"]`); if (sameColCell) sameColCell.focus(); } } // 按方向键移动 if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) { e.preventDefault(); let nextRow = row; let nextCol = col; if (e.key === 'ArrowUp') nextRow--; if (e.key === 'ArrowDown') nextRow++; if (e.key === 'ArrowLeft') nextCol--; if (e.key === 'ArrowRight') nextCol++; const nextCell = findCell(table, nextRow, nextCol); if (nextCell) nextCell.focus(); } } /** * 查找表格中的单元格 * @param {HTMLElement} table - 表格元素 * @param {number} row - 行索引 * @param {number} col - 列索引 * @returns {HTMLElement|null} - 找到的单元格或null */ function findCell(table, row, col) { if (row < 0 || col < 0) return null; return table.querySelector(`.excel-cell[data-row="${row}"][data-col="${col}"]`); } /** * 启用CSV粘贴功能 * @param {HTMLElement} table - 表格元素 */ function enableCSVPaste(table) { table.addEventListener('paste', function(e) { e.preventDefault(); // 获取粘贴的文本 const clipboardData = e.clipboardData || window.clipboardData; const pastedData = clipboardData.getData('text'); // 处理CSV数据 processCSVData(table, pastedData); }); } /** * 处理CSV数据 * @param {HTMLElement} table - 表格元素 * @param {string} csvData - CSV格式的数据 */ function processCSVData(table, csvData) { // 清空现有数据 const body = table.querySelector('.excel-body'); body.innerHTML = ''; // 解析CSV数据 const rows = csvData.trim().split(/\r?\n/); const maxCols = rows.reduce((max, row) => { const cols = row.split(/[,\t]/); return Math.max(max, cols.length); }, 0); // 调整列数 adjustColumnCount(table, maxCols); // 添加数据行 rows.forEach(row => { const values = row.split(/[,\t]/); addExcelRow(table, values); }); } /** * 调整表格列数 * @param {HTMLElement} table - 表格元素 * @param {number} columnCount - 目标列数 */ function adjustColumnCount(table, columnCount) { const header = table.querySelector('.excel-header'); const currentColumnCount = header.children.length - 2; // 减去角落单元格和添加列按钮 // 如果需要添加列 for (let i = currentColumnCount; i < columnCount; i++) { addExcelColumn(table); } } /** * 处理CSV文本区域的粘贴事件 * @param {Event} e - 粘贴事件 */ function handleCSVPaste(e) { // 获取粘贴的文本 const clipboardData = e.clipboardData || window.clipboardData; const pastedData = clipboardData.getData('text'); // 如果是CSV格式,直接替换文本区域内容 if (pastedData.includes(',') || pastedData.includes('\t')) { e.preventDefault(); e.target.value = pastedData; } } /** * 更新表格操作按钮 * @param {string} tabId - 标签页ID * @param {HTMLElement} table - 表格元素 */ function updateTableActions(tabId, table) { const actionsContainer = document.querySelector(`#${tabId} .table-actions`); if (!actionsContainer) return; // 清空现有按钮 const buttonsContainer = actionsContainer.querySelector('div'); if (buttonsContainer) { buttonsContainer.innerHTML = ''; // 添加行按钮 const addRowBtn = document.createElement('button'); addRowBtn.id = tabId === 'fit-tab' ? 'add-row' : 'add-regression-row'; addRowBtn.textContent = '添加行'; addRowBtn.addEventListener('click', function() { addExcelRow(table); }); buttonsContainer.appendChild(addRowBtn); // 添加列按钮(仅回归分析) if (tabId === 'regression-tab') { const addColBtn = document.createElement('button'); addColBtn.id = 'add-regression-column'; addColBtn.textContent = '添加变量'; addColBtn.addEventListener('click', function() { addExcelColumn(table); }); buttonsContainer.appendChild(addColBtn); } // 导入导出按钮(仅拟合数据) if (tabId === 'fit-tab') { const importBtn = document.createElement('button'); importBtn.id = 'import-data'; importBtn.textContent = '导入数据'; importBtn.addEventListener('click', function() { // 创建隐藏的文件输入 const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.csv,.txt'; fileInput.style.display = 'none'; document.body.appendChild(fileInput); fileInput.addEventListener('change', function() { const file = this.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { processCSVData(table, e.target.result); }; reader.readAsText(file); } document.body.removeChild(fileInput); }); fileInput.click(); }); buttonsContainer.appendChild(importBtn); const exportBtn = document.createElement('button'); exportBtn.id = 'export-data'; exportBtn.textContent = '导出数据'; exportBtn.addEventListener('click', function() { exportTableData(table); }); buttonsContainer.appendChild(exportBtn); } } } /** * 导出表格数据为CSV * @param {HTMLElement} table - 表格元素 */ function exportTableData(table) { const rows = table.querySelectorAll('.excel-row'); let csvContent = ''; rows.forEach(row => { const cells = row.querySelectorAll('.excel-cell'); const rowData = Array.from(cells).map(cell => { // 处理CSV特殊字符 let value = cell.textContent.trim(); if (value.includes(',') || value.includes('"') || value.includes('\n')) { value = '"' + value.replace(/"/g, '""') + '"'; } return value; }).join(','); csvContent += rowData + '\n'; }); // 创建下载链接 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', 'data.csv'); link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * 获取表格数据 * @param {HTMLElement} table - 表格元素 * @returns {Array} - 表格数据数组 */ function getTableData(table) { const rows = table.querySelectorAll('.excel-row'); const data = []; rows.forEach(row => { const cells = row.querySelectorAll('.excel-cell'); const rowData = Array.from(cells).map(cell => { const value = cell.textContent.trim(); return isNaN(value) ? value : parseFloat(value); }); data.push(rowData); }); return data; } /** * 删除Excel表格行 * @param {HTMLElement} table - 表格元素 * @param {number} rowIndex - 要删除的行索引 */ function deleteExcelRow(table, rowIndex) { const body = table.querySelector('.excel-body'); const rows = body.querySelectorAll('.excel-row'); // 确保行索引有效 if (rowIndex < 0 || rowIndex >= rows.length) return; // 删除指定行 body.removeChild(rows[rowIndex]); // 更新剩余行的索引和行号 const remainingRows = body.querySelectorAll('.excel-row'); remainingRows.forEach((row, idx) => { row.dataset.index = idx; const rowHeaderContent = row.querySelector('.row-header-content'); if (rowHeaderContent) { rowHeaderContent.textContent = idx + 1; } // 更新单元格的行索引 const cells = row.querySelectorAll('.excel-cell'); cells.forEach(cell => { cell.dataset.row = idx; }); }); } /** * 删除Excel表格列 * @param {HTMLElement} table - 表格元素 * @param {number} colIndex - 要删除的列索引 */ function deleteExcelColumn(table, colIndex) { // 确保至少保留一列 const header = table.querySelector('.excel-header'); const columnCount = header.children.length - 2; // 减去角落单元格和添加列按钮 if (columnCount <= 1) { alert('表格至少需要保留一列'); return; } // 删除列标题 const columnHeaders = header.querySelectorAll('.excel-column-header'); if (colIndex < columnHeaders.length) { header.removeChild(columnHeaders[colIndex]); } // 删除每行中的对应单元格 const rows = table.querySelectorAll('.excel-row'); rows.forEach(row => { const cells = row.querySelectorAll('.excel-cell'); if (colIndex < cells.length) { row.removeChild(cells[colIndex]); } // 更新剩余单元格的列索引 const remainingCells = row.querySelectorAll('.excel-cell'); remainingCells.forEach((cell, idx) => { cell.dataset.col = idx; }); }); // 更新列标题 - 重新分配A, B, C...标签 const updatedHeaders = header.querySelectorAll('.excel-column-header'); updatedHeaders.forEach((header, idx) => { const headerContent = header.querySelector('.column-header-content'); if (headerContent) { headerContent.textContent = String.fromCharCode(65 + idx); // A, B, C... } }); } /** * 从Excel表格获取数据点 * @param {HTMLElement} table - 表格元素 * @returns {Array} - 数据点数组,每个数据点包含x和y属性 */ function getDataPointsFromExcelTable(table) { const rows = table.querySelectorAll('.excel-row'); const dataPoints = []; // 检查表格是否有足够的列 const firstRow = table.querySelector('.excel-row'); if (!firstRow) return dataPoints; const cellCount = firstRow.querySelectorAll('.excel-cell').length; if (cellCount < 2) { console.warn('表格列数不足,无法提取X和Y值'); return dataPoints; } rows.forEach(row => { const cells = row.querySelectorAll('.excel-cell'); if (cells.length >= 2) { // 始终使用前两列作为X和Y值,无论它们的列标题是什么 const xValue = parseFloat(cells[0].textContent.trim()); const yValue = parseFloat(cells[1].textContent.trim()); // 只添加有效的数据点 if (!isNaN(xValue) && !isNaN(yValue)) { dataPoints.push({ x: xValue, y: yValue }); } } }); return dataPoints; } // 添加CSS样式 document.addEventListener('DOMContentLoaded', function() { // 添加删除按钮样式 const style = document.createElement('style'); style.textContent = ` .excel-row-header, .excel-column-header { position: relative; } .row-header-content, .column-header-content { display: inline-block; width: 100%; text-align: center; } .delete-row-btn, .delete-col-btn { position: absolute; display: none; cursor: pointer; color: #ff4d4f; font-weight: bold; font-size: 12px; width: 16px; height: 16px; line-height: 14px; text-align: center; border-radius: 50%; background-color: #fff; border: 1px solid #ff4d4f; } .delete-row-btn { right: 2px; top: 50%; transform: translateY(-50%); } .delete-col-btn { top: 2px; right: 50%; transform: translateX(50%); } .excel-row-header:hover .delete-row-btn, .excel-column-header:hover .delete-col-btn { display: block; } `; document.head.appendChild(style); }); // 导出函数供其他模块使用 window.dataTable = { getTableData: getTableData, processCSVData: processCSVData, getDataPointsFromExcelTable: getDataPointsFromExcelTable };