fix: исправлена критическая ошибка - rowSelectionChanged не добавлял строки в selectedRowsData
- Изменена логика rowSelectionChanged: теперь используется параметр data вместо rows - Параметр data содержит ВСЕ выделенные строки, а rows - только измененные - При клике на header checkbox параметр rows пустой, поэтому selectedRowsData не обновлялся - Добавлена правильная синхронизация выделенных строк между Tabulator и selectedRowsData - Упрощена логика: полная пересинхронизация при каждом изменении выделения
This commit is contained in:
188
public/app.js
188
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) {
|
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, 'строк');
|
||||||
|
|||||||
Reference in New Issue
Block a user