fix: исправлена критическая ошибка - rowSelectionChanged не добавлял строки в selectedRowsData

- Изменена логика rowSelectionChanged: теперь используется параметр data вместо rows
- Параметр data содержит ВСЕ выделенные строки, а rows - только измененные
- При клике на header checkbox параметр rows пустой, поэтому selectedRowsData не обновлялся
- Добавлена правильная синхронизация выделенных строк между Tabulator и selectedRowsData
- Упрощена логика: полная пересинхронизация при каждом изменении выделения
This commit is contained in:
2026-01-21 04:40:21 +03:00
parent 91cbde5026
commit 25182132e7

View File

@@ -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) { rowSelectionChanged: function(data, rows) {
console.log('🔔 rowSelectionChanged:', { console.log('🔔 rowSelectionChanged called:', {
dataLength: data.length, 'data (all selected)': data.length,
rowsLength: rows.length, 'rows (changed)': rows.length,
selectedRowsDataSizeBefore: selectedRowsData.size 'selectedRowsData before': selectedRowsData.size
}); });
// Обрабатываем только изменившиеся строки // ВАЖНО: параметр data содержит массив данных ВСЕХ выделенных строк
rows.forEach(row => { // параметр rows содержит только Row объекты измененных строк
// Получаем все выделенные строки напрямую из Tabulator
const allSelectedData = data; // это уже массив данных всех выделенных строк
// Удаляем из selectedRowsData все строки текущей страницы
const currentPageRows = this.getRows();
currentPageRows.forEach(row => {
const rowData = row.getData(); const rowData = row.getData();
const key = getRowKey(rowData); const key = getRowKey(rowData);
const isSelected = row.isSelected(); selectedRowsData.delete(key);
console.log(` 🧹 Removed from selectedRowsData: ${key}`);
console.log(` Row key: ${key}, isSelected: ${isSelected}`);
if (isSelected) {
selectedRowsData.set(key, rowData);
} else {
selectedRowsData.delete(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(); 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) { dataLoaded: function(data) {
console.log('📄 dataLoaded, восстанавливаем выделение для', selectedRowsData.size, 'строк'); console.log('📄 dataLoaded, восстанавливаем выделение для', selectedRowsData.size, 'строк');