data-table.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. // Excel风格数据表格组件
  2. document.addEventListener('DOMContentLoaded', function() {
  3. // 初始化数据表格
  4. initDataTables();
  5. });
  6. /**
  7. * 初始化所有数据表格
  8. */
  9. function initDataTables() {
  10. // 初始化拟合数据表格
  11. initFitDataTable();
  12. // 初始化回归分析表格
  13. initRegressionTable();
  14. }
  15. /**
  16. * 初始化拟合数据表格
  17. */
  18. function initFitDataTable() {
  19. const tableContainer = document.querySelector('#fit-tab .table-container');
  20. if (!tableContainer) return;
  21. // 清空现有内容
  22. tableContainer.innerHTML = '';
  23. // 创建Excel风格表格
  24. const excelTable = createExcelTable('fit-excel-table', 2); // X和Y两列
  25. tableContainer.appendChild(excelTable);
  26. // 添加示例数据
  27. const fitTable = document.getElementById('fit-excel-table');
  28. addExcelRow(fitTable, [1, 2]);
  29. addExcelRow(fitTable, [2, 4]);
  30. addExcelRow(fitTable, [3, 9]);
  31. // 添加CSV粘贴支持
  32. enableCSVPaste(fitTable);
  33. // 添加表格操作按钮
  34. updateTableActions('fit-tab', fitTable);
  35. }
  36. /**
  37. * 初始化回归分析表格
  38. */
  39. function initRegressionTable() {
  40. // 表格模式
  41. const tableContainer = document.querySelector('#table-input-mode .table-container');
  42. if (tableContainer) {
  43. // 清空现有内容
  44. tableContainer.innerHTML = '';
  45. // 创建Excel风格表格
  46. const excelTable = createExcelTable('regression-excel-table', 3); // X1, X2和Y三列
  47. tableContainer.appendChild(excelTable);
  48. // 添加示例数据
  49. const regressionTable = document.getElementById('regression-excel-table');
  50. addExcelRow(regressionTable, [1, 2, 3]);
  51. addExcelRow(regressionTable, [4, 5, 6]);
  52. // 添加CSV粘贴支持
  53. enableCSVPaste(regressionTable);
  54. // 添加表格操作按钮
  55. updateTableActions('regression-tab', regressionTable);
  56. }
  57. // CSV粘贴模式
  58. const csvTextarea = document.getElementById('regression-data');
  59. if (csvTextarea) {
  60. // 为CSV文本区域添加粘贴处理
  61. csvTextarea.addEventListener('paste', handleCSVPaste);
  62. }
  63. // 切换输入模式按钮
  64. const tableModeBtn = document.getElementById('table-mode-btn');
  65. const csvModeBtn = document.getElementById('csv-mode-btn');
  66. const tableInputMode = document.getElementById('table-input-mode');
  67. const csvInputMode = document.getElementById('csv-input-mode');
  68. if (tableModeBtn && csvModeBtn) {
  69. tableModeBtn.addEventListener('click', function() {
  70. tableModeBtn.classList.add('active');
  71. csvModeBtn.classList.remove('active');
  72. tableInputMode.classList.add('active');
  73. csvInputMode.classList.remove('active');
  74. });
  75. csvModeBtn.addEventListener('click', function() {
  76. csvModeBtn.classList.add('active');
  77. tableModeBtn.classList.remove('active');
  78. csvInputMode.classList.add('active');
  79. tableInputMode.classList.remove('active');
  80. });
  81. }
  82. }
  83. /**
  84. * 创建Excel风格表格
  85. * @param {string} id - 表格ID
  86. * @param {number} columnCount - 初始列数
  87. * @returns {HTMLElement} - 创建的表格元素
  88. */
  89. function createExcelTable(id, columnCount) {
  90. const table = document.createElement('div');
  91. table.id = id;
  92. table.className = 'excel-table';
  93. // 创建表头
  94. const header = document.createElement('div');
  95. header.className = 'excel-header';
  96. // 添加空白角落单元格
  97. const cornerCell = document.createElement('div');
  98. cornerCell.className = 'excel-corner-cell';
  99. header.appendChild(cornerCell);
  100. // 添加列标题 (A, B, C...)
  101. for (let i = 0; i < columnCount; i++) {
  102. const columnHeader = document.createElement('div');
  103. columnHeader.className = 'excel-column-header';
  104. // 创建列标题和删除按钮的容器
  105. const columnHeaderContent = document.createElement('div');
  106. columnHeaderContent.className = 'column-header-content';
  107. columnHeaderContent.textContent = String.fromCharCode(65 + i); // A, B, C...
  108. // 创建删除列按钮
  109. const deleteColBtn = document.createElement('div');
  110. deleteColBtn.className = 'delete-col-btn';
  111. deleteColBtn.innerHTML = '×';
  112. deleteColBtn.title = '删除列';
  113. deleteColBtn.addEventListener('click', function(e) {
  114. e.stopPropagation();
  115. // 获取当前列的索引,而不是使用创建时的索引
  116. const currentHeaders = table.querySelectorAll('.excel-column-header');
  117. const currentIndex = Array.from(currentHeaders).indexOf(columnHeader);
  118. deleteExcelColumn(table, currentIndex);
  119. });
  120. columnHeader.appendChild(columnHeaderContent);
  121. columnHeader.appendChild(deleteColBtn);
  122. header.appendChild(columnHeader);
  123. }
  124. // 添加添加列按钮
  125. const addColumnBtn = document.createElement('div');
  126. addColumnBtn.className = 'excel-add-column';
  127. addColumnBtn.innerHTML = '+';
  128. addColumnBtn.title = '添加列';
  129. addColumnBtn.addEventListener('click', function() {
  130. addExcelColumn(table);
  131. });
  132. header.appendChild(addColumnBtn);
  133. table.appendChild(header);
  134. // 创建表格内容区域
  135. const body = document.createElement('div');
  136. body.className = 'excel-body';
  137. table.appendChild(body);
  138. // 添加添加行按钮
  139. const footer = document.createElement('div');
  140. footer.className = 'excel-footer';
  141. const addRowBtn = document.createElement('div');
  142. addRowBtn.className = 'excel-add-row';
  143. addRowBtn.innerHTML = '+';
  144. addRowBtn.title = '添加行';
  145. addRowBtn.addEventListener('click', function() {
  146. addExcelRow(table);
  147. });
  148. footer.appendChild(addRowBtn);
  149. table.appendChild(footer);
  150. return table;
  151. }
  152. /**
  153. * 添加Excel表格行
  154. * @param {HTMLElement} table - 表格元素
  155. * @param {Array} values - 行数据值
  156. * @returns {HTMLElement} - 创建的行元素
  157. */
  158. function addExcelRow(table, values = []) {
  159. const body = table.querySelector('.excel-body');
  160. const rowIndex = body.children.length;
  161. const row = document.createElement('div');
  162. row.className = 'excel-row';
  163. row.dataset.index = rowIndex;
  164. // 添加行号和删除行按钮
  165. const rowHeader = document.createElement('div');
  166. rowHeader.className = 'excel-row-header';
  167. // 创建行号和删除按钮的容器
  168. const rowHeaderContent = document.createElement('div');
  169. rowHeaderContent.className = 'row-header-content';
  170. rowHeaderContent.textContent = rowIndex + 1;
  171. // 创建删除行按钮
  172. const deleteRowBtn = document.createElement('div');
  173. deleteRowBtn.className = 'delete-row-btn';
  174. deleteRowBtn.innerHTML = '×';
  175. deleteRowBtn.title = '删除行';
  176. deleteRowBtn.addEventListener('click', function(e) {
  177. e.stopPropagation();
  178. deleteExcelRow(table, rowIndex);
  179. });
  180. rowHeader.appendChild(rowHeaderContent);
  181. rowHeader.appendChild(deleteRowBtn);
  182. row.appendChild(rowHeader);
  183. // 获取列数
  184. const columnCount = table.querySelector('.excel-header').children.length - 2; // 减去角落单元格和添加列按钮
  185. // 添加单元格
  186. for (let i = 0; i < columnCount; i++) {
  187. const cell = document.createElement('div');
  188. cell.className = 'excel-cell';
  189. cell.dataset.row = rowIndex;
  190. cell.dataset.col = i;
  191. cell.contentEditable = true;
  192. // 设置初始值
  193. if (values && values[i] !== undefined) {
  194. cell.textContent = values[i];
  195. }
  196. // 添加单元格事件
  197. cell.addEventListener('focus', function() {
  198. this.classList.add('active');
  199. });
  200. cell.addEventListener('blur', function() {
  201. this.classList.remove('active');
  202. });
  203. cell.addEventListener('keydown', function(e) {
  204. handleCellKeydown(e, table, this);
  205. });
  206. row.appendChild(cell);
  207. }
  208. body.appendChild(row);
  209. return row;
  210. }
  211. /**
  212. * 添加Excel表格列
  213. * @param {HTMLElement} table - 表格元素
  214. */
  215. function addExcelColumn(table) {
  216. // 更新表头
  217. const header = table.querySelector('.excel-header');
  218. const columnCount = header.children.length - 2; // 减去角落单元格和添加列按钮
  219. // 在添加列按钮前插入新列标题
  220. const columnHeader = document.createElement('div');
  221. columnHeader.className = 'excel-column-header';
  222. // 创建列标题和删除按钮的容器
  223. const columnHeaderContent = document.createElement('div');
  224. columnHeaderContent.className = 'column-header-content';
  225. columnHeaderContent.textContent = String.fromCharCode(65 + columnCount); // A, B, C...
  226. // 创建删除列按钮
  227. const deleteColBtn = document.createElement('div');
  228. deleteColBtn.className = 'delete-col-btn';
  229. deleteColBtn.innerHTML = '×';
  230. deleteColBtn.title = '删除列';
  231. deleteColBtn.addEventListener('click', function(e) {
  232. e.stopPropagation();
  233. // 获取当前列的索引,而不是使用创建时的索引
  234. const currentHeaders = table.querySelectorAll('.excel-column-header');
  235. const currentIndex = Array.from(currentHeaders).indexOf(columnHeader);
  236. deleteExcelColumn(table, currentIndex);
  237. });
  238. columnHeader.appendChild(columnHeaderContent);
  239. columnHeader.appendChild(deleteColBtn);
  240. header.insertBefore(columnHeader, header.lastChild);
  241. // 为每一行添加新单元格
  242. const rows = table.querySelectorAll('.excel-row');
  243. rows.forEach((row, rowIndex) => {
  244. const cell = document.createElement('div');
  245. cell.className = 'excel-cell';
  246. cell.dataset.row = rowIndex;
  247. cell.dataset.col = columnCount;
  248. cell.contentEditable = true;
  249. // 添加单元格事件
  250. cell.addEventListener('focus', function() {
  251. this.classList.add('active');
  252. });
  253. cell.addEventListener('blur', function() {
  254. this.classList.remove('active');
  255. });
  256. cell.addEventListener('keydown', function(e) {
  257. handleCellKeydown(e, table, this);
  258. });
  259. row.appendChild(cell);
  260. });
  261. }
  262. /**
  263. * 处理单元格键盘事件
  264. * @param {Event} e - 键盘事件
  265. * @param {HTMLElement} table - 表格元素
  266. * @param {HTMLElement} cell - 当前单元格
  267. */
  268. function handleCellKeydown(e, table, cell) {
  269. const row = parseInt(cell.dataset.row);
  270. const col = parseInt(cell.dataset.col);
  271. // 按Tab键移动到下一个单元格
  272. if (e.key === 'Tab') {
  273. e.preventDefault();
  274. const nextCell = e.shiftKey ?
  275. findCell(table, row, col - 1) :
  276. findCell(table, row, col + 1);
  277. if (nextCell) {
  278. nextCell.focus();
  279. } else if (!e.shiftKey) {
  280. // 如果是最后一个单元格,添加新行并聚焦第一个单元格
  281. const newRow = addExcelRow(table);
  282. const firstCell = newRow.querySelector('.excel-cell');
  283. if (firstCell) firstCell.focus();
  284. }
  285. }
  286. // 按Enter键移动到下一行
  287. if (e.key === 'Enter') {
  288. e.preventDefault();
  289. const nextCell = e.shiftKey ?
  290. findCell(table, row - 1, col) :
  291. findCell(table, row + 1, col);
  292. if (nextCell) {
  293. nextCell.focus();
  294. } else if (!e.shiftKey) {
  295. // 如果是最后一行,添加新行并聚焦相同列的单元格
  296. const newRow = addExcelRow(table);
  297. const sameColCell = newRow.querySelector(`.excel-cell[data-col="${col}"]`);
  298. if (sameColCell) sameColCell.focus();
  299. }
  300. }
  301. // 按方向键移动
  302. if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
  303. e.preventDefault();
  304. let nextRow = row;
  305. let nextCol = col;
  306. if (e.key === 'ArrowUp') nextRow--;
  307. if (e.key === 'ArrowDown') nextRow++;
  308. if (e.key === 'ArrowLeft') nextCol--;
  309. if (e.key === 'ArrowRight') nextCol++;
  310. const nextCell = findCell(table, nextRow, nextCol);
  311. if (nextCell) nextCell.focus();
  312. }
  313. }
  314. /**
  315. * 查找表格中的单元格
  316. * @param {HTMLElement} table - 表格元素
  317. * @param {number} row - 行索引
  318. * @param {number} col - 列索引
  319. * @returns {HTMLElement|null} - 找到的单元格或null
  320. */
  321. function findCell(table, row, col) {
  322. if (row < 0 || col < 0) return null;
  323. return table.querySelector(`.excel-cell[data-row="${row}"][data-col="${col}"]`);
  324. }
  325. /**
  326. * 启用CSV粘贴功能
  327. * @param {HTMLElement} table - 表格元素
  328. */
  329. function enableCSVPaste(table) {
  330. table.addEventListener('paste', function(e) {
  331. e.preventDefault();
  332. // 获取粘贴的文本
  333. const clipboardData = e.clipboardData || window.clipboardData;
  334. const pastedData = clipboardData.getData('text');
  335. // 处理CSV数据
  336. processCSVData(table, pastedData);
  337. });
  338. }
  339. /**
  340. * 处理CSV数据
  341. * @param {HTMLElement} table - 表格元素
  342. * @param {string} csvData - CSV格式的数据
  343. */
  344. function processCSVData(table, csvData) {
  345. // 清空现有数据
  346. const body = table.querySelector('.excel-body');
  347. body.innerHTML = '';
  348. // 解析CSV数据
  349. const rows = csvData.trim().split(/\r?\n/);
  350. const maxCols = rows.reduce((max, row) => {
  351. const cols = row.split(/[,\t]/);
  352. return Math.max(max, cols.length);
  353. }, 0);
  354. // 调整列数
  355. adjustColumnCount(table, maxCols);
  356. // 添加数据行
  357. rows.forEach(row => {
  358. const values = row.split(/[,\t]/);
  359. addExcelRow(table, values);
  360. });
  361. }
  362. /**
  363. * 调整表格列数
  364. * @param {HTMLElement} table - 表格元素
  365. * @param {number} columnCount - 目标列数
  366. */
  367. function adjustColumnCount(table, columnCount) {
  368. const header = table.querySelector('.excel-header');
  369. const currentColumnCount = header.children.length - 2; // 减去角落单元格和添加列按钮
  370. // 如果需要添加列
  371. for (let i = currentColumnCount; i < columnCount; i++) {
  372. addExcelColumn(table);
  373. }
  374. }
  375. /**
  376. * 处理CSV文本区域的粘贴事件
  377. * @param {Event} e - 粘贴事件
  378. */
  379. function handleCSVPaste(e) {
  380. // 获取粘贴的文本
  381. const clipboardData = e.clipboardData || window.clipboardData;
  382. const pastedData = clipboardData.getData('text');
  383. // 如果是CSV格式,直接替换文本区域内容
  384. if (pastedData.includes(',') || pastedData.includes('\t')) {
  385. e.preventDefault();
  386. e.target.value = pastedData;
  387. }
  388. }
  389. /**
  390. * 更新表格操作按钮
  391. * @param {string} tabId - 标签页ID
  392. * @param {HTMLElement} table - 表格元素
  393. */
  394. function updateTableActions(tabId, table) {
  395. const actionsContainer = document.querySelector(`#${tabId} .table-actions`);
  396. if (!actionsContainer) return;
  397. // 清空现有按钮
  398. const buttonsContainer = actionsContainer.querySelector('div');
  399. if (buttonsContainer) {
  400. buttonsContainer.innerHTML = '';
  401. // 添加行按钮
  402. const addRowBtn = document.createElement('button');
  403. addRowBtn.id = tabId === 'fit-tab' ? 'add-row' : 'add-regression-row';
  404. addRowBtn.textContent = '添加行';
  405. addRowBtn.addEventListener('click', function() {
  406. addExcelRow(table);
  407. });
  408. buttonsContainer.appendChild(addRowBtn);
  409. // 添加列按钮(仅回归分析)
  410. if (tabId === 'regression-tab') {
  411. const addColBtn = document.createElement('button');
  412. addColBtn.id = 'add-regression-column';
  413. addColBtn.textContent = '添加变量';
  414. addColBtn.addEventListener('click', function() {
  415. addExcelColumn(table);
  416. });
  417. buttonsContainer.appendChild(addColBtn);
  418. }
  419. // 导入导出按钮(仅拟合数据)
  420. if (tabId === 'fit-tab') {
  421. const importBtn = document.createElement('button');
  422. importBtn.id = 'import-data';
  423. importBtn.textContent = '导入数据';
  424. importBtn.addEventListener('click', function() {
  425. // 创建隐藏的文件输入
  426. const fileInput = document.createElement('input');
  427. fileInput.type = 'file';
  428. fileInput.accept = '.csv,.txt';
  429. fileInput.style.display = 'none';
  430. document.body.appendChild(fileInput);
  431. fileInput.addEventListener('change', function() {
  432. const file = this.files[0];
  433. if (file) {
  434. const reader = new FileReader();
  435. reader.onload = function(e) {
  436. processCSVData(table, e.target.result);
  437. };
  438. reader.readAsText(file);
  439. }
  440. document.body.removeChild(fileInput);
  441. });
  442. fileInput.click();
  443. });
  444. buttonsContainer.appendChild(importBtn);
  445. const exportBtn = document.createElement('button');
  446. exportBtn.id = 'export-data';
  447. exportBtn.textContent = '导出数据';
  448. exportBtn.addEventListener('click', function() {
  449. exportTableData(table);
  450. });
  451. buttonsContainer.appendChild(exportBtn);
  452. }
  453. }
  454. }
  455. /**
  456. * 导出表格数据为CSV
  457. * @param {HTMLElement} table - 表格元素
  458. */
  459. function exportTableData(table) {
  460. const rows = table.querySelectorAll('.excel-row');
  461. let csvContent = '';
  462. rows.forEach(row => {
  463. const cells = row.querySelectorAll('.excel-cell');
  464. const rowData = Array.from(cells).map(cell => {
  465. // 处理CSV特殊字符
  466. let value = cell.textContent.trim();
  467. if (value.includes(',') || value.includes('"') || value.includes('\n')) {
  468. value = '"' + value.replace(/"/g, '""') + '"';
  469. }
  470. return value;
  471. }).join(',');
  472. csvContent += rowData + '\n';
  473. });
  474. // 创建下载链接
  475. const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  476. const url = URL.createObjectURL(blob);
  477. const link = document.createElement('a');
  478. link.setAttribute('href', url);
  479. link.setAttribute('download', 'data.csv');
  480. link.style.display = 'none';
  481. document.body.appendChild(link);
  482. link.click();
  483. document.body.removeChild(link);
  484. }
  485. /**
  486. * 获取表格数据
  487. * @param {HTMLElement} table - 表格元素
  488. * @returns {Array} - 表格数据数组
  489. */
  490. function getTableData(table) {
  491. const rows = table.querySelectorAll('.excel-row');
  492. const data = [];
  493. rows.forEach(row => {
  494. const cells = row.querySelectorAll('.excel-cell');
  495. const rowData = Array.from(cells).map(cell => {
  496. const value = cell.textContent.trim();
  497. return isNaN(value) ? value : parseFloat(value);
  498. });
  499. data.push(rowData);
  500. });
  501. return data;
  502. }
  503. /**
  504. * 删除Excel表格行
  505. * @param {HTMLElement} table - 表格元素
  506. * @param {number} rowIndex - 要删除的行索引
  507. */
  508. function deleteExcelRow(table, rowIndex) {
  509. const body = table.querySelector('.excel-body');
  510. const rows = body.querySelectorAll('.excel-row');
  511. // 确保行索引有效
  512. if (rowIndex < 0 || rowIndex >= rows.length) return;
  513. // 删除指定行
  514. body.removeChild(rows[rowIndex]);
  515. // 更新剩余行的索引和行号
  516. const remainingRows = body.querySelectorAll('.excel-row');
  517. remainingRows.forEach((row, idx) => {
  518. row.dataset.index = idx;
  519. const rowHeaderContent = row.querySelector('.row-header-content');
  520. if (rowHeaderContent) {
  521. rowHeaderContent.textContent = idx + 1;
  522. }
  523. // 更新单元格的行索引
  524. const cells = row.querySelectorAll('.excel-cell');
  525. cells.forEach(cell => {
  526. cell.dataset.row = idx;
  527. });
  528. });
  529. }
  530. /**
  531. * 删除Excel表格列
  532. * @param {HTMLElement} table - 表格元素
  533. * @param {number} colIndex - 要删除的列索引
  534. */
  535. function deleteExcelColumn(table, colIndex) {
  536. // 确保至少保留一列
  537. const header = table.querySelector('.excel-header');
  538. const columnCount = header.children.length - 2; // 减去角落单元格和添加列按钮
  539. if (columnCount <= 1) {
  540. alert('表格至少需要保留一列');
  541. return;
  542. }
  543. // 删除列标题
  544. const columnHeaders = header.querySelectorAll('.excel-column-header');
  545. if (colIndex < columnHeaders.length) {
  546. header.removeChild(columnHeaders[colIndex]);
  547. }
  548. // 删除每行中的对应单元格
  549. const rows = table.querySelectorAll('.excel-row');
  550. rows.forEach(row => {
  551. const cells = row.querySelectorAll('.excel-cell');
  552. if (colIndex < cells.length) {
  553. row.removeChild(cells[colIndex]);
  554. }
  555. // 更新剩余单元格的列索引
  556. const remainingCells = row.querySelectorAll('.excel-cell');
  557. remainingCells.forEach((cell, idx) => {
  558. cell.dataset.col = idx;
  559. });
  560. });
  561. // 更新列标题 - 重新分配A, B, C...标签
  562. const updatedHeaders = header.querySelectorAll('.excel-column-header');
  563. updatedHeaders.forEach((header, idx) => {
  564. const headerContent = header.querySelector('.column-header-content');
  565. if (headerContent) {
  566. headerContent.textContent = String.fromCharCode(65 + idx); // A, B, C...
  567. }
  568. });
  569. }
  570. /**
  571. * 从Excel表格获取数据点
  572. * @param {HTMLElement} table - 表格元素
  573. * @returns {Array} - 数据点数组,每个数据点包含x和y属性
  574. */
  575. function getDataPointsFromExcelTable(table) {
  576. const rows = table.querySelectorAll('.excel-row');
  577. const dataPoints = [];
  578. // 检查表格是否有足够的列
  579. const firstRow = table.querySelector('.excel-row');
  580. if (!firstRow) return dataPoints;
  581. const cellCount = firstRow.querySelectorAll('.excel-cell').length;
  582. if (cellCount < 2) {
  583. console.warn('表格列数不足,无法提取X和Y值');
  584. return dataPoints;
  585. }
  586. rows.forEach(row => {
  587. const cells = row.querySelectorAll('.excel-cell');
  588. if (cells.length >= 2) {
  589. // 始终使用前两列作为X和Y值,无论它们的列标题是什么
  590. const xValue = parseFloat(cells[0].textContent.trim());
  591. const yValue = parseFloat(cells[1].textContent.trim());
  592. // 只添加有效的数据点
  593. if (!isNaN(xValue) && !isNaN(yValue)) {
  594. dataPoints.push({ x: xValue, y: yValue });
  595. }
  596. }
  597. });
  598. return dataPoints;
  599. }
  600. // 添加CSS样式
  601. document.addEventListener('DOMContentLoaded', function() {
  602. // 添加删除按钮样式
  603. const style = document.createElement('style');
  604. style.textContent = `
  605. .excel-row-header, .excel-column-header {
  606. position: relative;
  607. }
  608. .row-header-content, .column-header-content {
  609. display: inline-block;
  610. width: 100%;
  611. text-align: center;
  612. }
  613. .delete-row-btn, .delete-col-btn {
  614. position: absolute;
  615. display: none;
  616. cursor: pointer;
  617. color: #ff4d4f;
  618. font-weight: bold;
  619. font-size: 12px;
  620. width: 16px;
  621. height: 16px;
  622. line-height: 14px;
  623. text-align: center;
  624. border-radius: 50%;
  625. background-color: #fff;
  626. border: 1px solid #ff4d4f;
  627. }
  628. .delete-row-btn {
  629. right: 2px;
  630. top: 50%;
  631. transform: translateY(-50%);
  632. }
  633. .delete-col-btn {
  634. top: 2px;
  635. right: 50%;
  636. transform: translateX(50%);
  637. }
  638. .excel-row-header:hover .delete-row-btn,
  639. .excel-column-header:hover .delete-col-btn {
  640. display: block;
  641. }
  642. `;
  643. document.head.appendChild(style);
  644. });
  645. // 导出函数供其他模块使用
  646. window.dataTable = {
  647. getTableData: getTableData,
  648. processCSVData: processCSVData,
  649. getDataPointsFromExcelTable: getDataPointsFromExcelTable
  650. };