From 25182132e7019943a02d9583290b582073b5b9df Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Wed, 21 Jan 2026 04:40:21 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BA=D1=80=D0=B8=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B0=D1=8F=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B0=20-=20rowSelectionChanged=20=D0=BD=D0=B5=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=BB=20=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B2=20selectedRowsData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Изменена логика rowSelectionChanged: теперь используется параметр data вместо rows - Параметр data содержит ВСЕ выделенные строки, а rows - только измененные - При клике на header checkbox параметр rows пустой, поэтому selectedRowsData не обновлялся - Добавлена правильная синхронизация выделенных строк между Tabulator и selectedRowsData - Упрощена логика: полная пересинхронизация при каждом изменении выделения --- public/app.js | 188 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 171 insertions(+), 17 deletions(-) diff --git a/public/app.js b/public/app.js index ca67cad..2e6d45b 100644 --- a/public/app.js +++ b/public/app.js @@ -282,33 +282,187 @@ async function selectTable(schema, tableName) { }; }, - // ✅ ИСПРАВЛЕНО: Простая и правильная обработка выделения +async function selectTable(schema, tableName) { + currentSchema = schema; + currentTable = tableName; + lastEditedRow = null; + selectedRowsData.clear(); + updateSelectionCounter(); + + if (enterHandler) { + document.removeEventListener('keydown', enterHandler); + enterHandler = null; + } + + currentMeta = await api( + `/api/table/meta?schema=${encodeURIComponent(schema)}&table=${encodeURIComponent(tableName)}` + ); + + const columns = [ + { + formatter: "rowSelection", + titleFormatter: "rowSelection", + hozAlign: "center", + headerSort: false, + width: 40, + cellClick: function(e, cell) { + cell.getRow().toggleSelect(); + } + }, + ...currentMeta.columns.map(col => ({ + title: col.COLUMN_NAME, + field: col.COLUMN_NAME, + editor: "input", + headerFilter: "input" + })) + ]; + + if (table) { + table.destroy(); + table = null; + } + + table = new Tabulator("#table", { + selectableRows: true, + columns: columns, + layout: "fitColumns", + resizableColumnFit: true, + + pagination: true, + paginationMode: "remote", + paginationSize: 50, + paginationSizeSelector: [25, 50, 100, 200], + + filterMode: "remote", + sortMode: "remote", + + ajaxURL: "/api/table/data", + ajaxConfig: "POST", + ajaxContentType: "json", + + ajaxParams: function () { + const headerFilters = this.getHeaderFilters ? this.getHeaderFilters() : []; + const filters = (headerFilters || []).map(f => ({ + field: f.field, + value: f.value + })).filter(f => f.value !== null && f.value !== ''); + + const sorters = this.getSorters ? this.getSorters() : []; + const sort = (sorters && sorters.length > 0) ? { + field: sorters[0].field, + dir: sorters[0].dir + } : null; + + return { + schema: currentSchema, + table: currentTable, + filters: filters, + sort: sort, + columns: currentMeta.columns, + page: this.getPage ? this.getPage() : 1, + pageSize: this.getPageSize ? this.getPageSize() : 50 + }; + }, + + ajaxResponse: function (url, params, response) { + return { + last_page: response.last_page || 1, + data: response.data || [] + }; + }, + + // ✅ ИСПРАВЛЕНО: Используем параметр data, который содержит ВСЕ выделенные строки rowSelectionChanged: function(data, rows) { - console.log('🔔 rowSelectionChanged:', { - dataLength: data.length, - rowsLength: rows.length, - selectedRowsDataSizeBefore: selectedRowsData.size + console.log('🔔 rowSelectionChanged called:', { + 'data (all selected)': data.length, + 'rows (changed)': rows.length, + 'selectedRowsData before': selectedRowsData.size }); - // Обрабатываем только изменившиеся строки - rows.forEach(row => { + // ВАЖНО: параметр data содержит массив данных ВСЕХ выделенных строк + // параметр rows содержит только Row объекты измененных строк + + // Получаем все выделенные строки напрямую из Tabulator + const allSelectedData = data; // это уже массив данных всех выделенных строк + + // Удаляем из selectedRowsData все строки текущей страницы + const currentPageRows = this.getRows(); + currentPageRows.forEach(row => { const rowData = row.getData(); const key = getRowKey(rowData); - const isSelected = row.isSelected(); - - console.log(` Row key: ${key}, isSelected: ${isSelected}`); - - if (isSelected) { - selectedRowsData.set(key, rowData); - } else { - selectedRowsData.delete(key); - } + selectedRowsData.delete(key); + console.log(` 🧹 Removed from selectedRowsData: ${key}`); }); - console.log(' ✅ selectedRowsData size after:', selectedRowsData.size); + // Добавляем все выделенные строки обратно + allSelectedData.forEach(rowData => { + const key = getRowKey(rowData); + selectedRowsData.set(key, rowData); + console.log(` ✅ Added to selectedRowsData: ${key}`); + }); + + console.log(` 📊 selectedRowsData after: ${selectedRowsData.size}`); updateSelectionCounter(); }, + dataLoaded: function(data) { + console.log('📄 dataLoaded, восстанавливаем выделение для', selectedRowsData.size, 'строк'); + + if (selectedRowsData.size > 0) { + const rows = this.getRows(); + let restoredCount = 0; + + rows.forEach(row => { + const rowData = row.getData(); + const key = getRowKey(rowData); + + if (selectedRowsData.has(key)) { + row.select(); + restoredCount++; + } + }); + + console.log(' ✅ Восстановлено выделение для', restoredCount, 'строк на странице'); + } + }, + + cellEdited: function(cell) { + const row = cell.getRow(); + lastEditedRow = row.getData(); + row.getElement().style.backgroundColor = '#fffae6'; + }, + + headerFilterLiveFilterDelay: 800 + }); + + enterHandler = async function(e) { + if (e.key === 'Enter' && lastEditedRow && currentSchema && currentTable) { + e.preventDefault(); + + try { + const res = await api('/api/table/update', 'POST', { + schema: currentSchema, + table: currentTable, + row: lastEditedRow + }); + + lastEditedRow = null; + + table.getRows().forEach(r => { + r.getElement().style.backgroundColor = ''; + }); + + await table.replaceData(); + } catch (err) { + console.error('Ошибка сохранения:', err); + alert('Ошибка: ' + err.message); + } + } + }; + + document.addEventListener('keydown', enterHandler); +} + // После загрузки данных восстанавливаем выделение dataLoaded: function(data) { console.log('📄 dataLoaded, восстанавливаем выделение для', selectedRowsData.size, 'строк');