fix: полностью переработан механизм выделения строк

- Убрано глобальное хранилище selectedRowsData для строк текущей страницы
- Используется встроенный механизм Tabulator getSelectedData() при операциях
- Добавлено глобальное хранилище только для кросс-страничного выделения (кнопка "Выделить все")
- Упрощена логика - убраны сложные синхронизации
- Выделение через header checkbox теперь работает стабильно
This commit is contained in:
2026-01-21 04:50:52 +03:00
parent a934ed2f28
commit 418788f670

View File

@@ -3,7 +3,7 @@ let currentTable = null;
let currentMeta = null; let currentMeta = null;
let table = null; let table = null;
let enterHandler = null; let enterHandler = null;
let selectedRowsData = new Map(); let selectedRowsDataGlobal = new Map(); // Только для "Выделить все" кросс-страничного
async function api(url, method = 'GET', body) { async function api(url, method = 'GET', body) {
const opts = { method, headers: { 'Content-Type': 'application/json' } }; const opts = { method, headers: { 'Content-Type': 'application/json' } };
@@ -19,7 +19,15 @@ async function api(url, method = 'GET', body) {
function updateSelectionCounter() { function updateSelectionCounter() {
const counter = document.getElementById('selectionCounter'); const counter = document.getElementById('selectionCounter');
const count = selectedRowsData.size;
// Получаем количество выделенных строк
let count = 0;
if (table) {
const tabulatorSelected = table.getSelectedData();
count = tabulatorSelected.length + selectedRowsDataGlobal.size;
} else {
count = selectedRowsDataGlobal.size;
}
if (count > 0) { if (count > 0) {
counter.textContent = `Выбрано: ${count}`; counter.textContent = `Выбрано: ${count}`;
@@ -96,22 +104,13 @@ function escapeHtml(text) {
function getRowKey(rowData) { function getRowKey(rowData) {
if (!currentMeta || !currentMeta.primaryKey || currentMeta.primaryKey.length === 0) { if (!currentMeta || !currentMeta.primaryKey || currentMeta.primaryKey.length === 0) {
const key = JSON.stringify(rowData); return JSON.stringify(rowData);
return key;
} }
const pkValues = currentMeta.primaryKey.map(pk => { const pkValues = currentMeta.primaryKey.map(pk => rowData[pk]).join('|');
const value = rowData[pk];
if (value === undefined || value === null) {
console.warn(`⚠️ getRowKey: PK field '${pk}' is undefined/null in rowData:`, rowData);
}
return value;
}).join('|');
return pkValues; return pkValues;
} }
document.getElementById('loginBtn').addEventListener('click', async () => { document.getElementById('loginBtn').addEventListener('click', async () => {
const user = document.getElementById('loginUser').value.trim(); const user = document.getElementById('loginUser').value.trim();
const pass = document.getElementById('loginPass').value; const pass = document.getElementById('loginPass').value;
@@ -186,7 +185,7 @@ async function selectTable(schema, tableName) {
currentSchema = schema; currentSchema = schema;
currentTable = tableName; currentTable = tableName;
lastEditedRow = null; lastEditedRow = null;
selectedRowsData.clear(); selectedRowsDataGlobal.clear(); // Очищаем при смене таблицы
updateSelectionCounter(); updateSelectionCounter();
if (enterHandler) { if (enterHandler) {
@@ -198,13 +197,6 @@ async function selectTable(schema, tableName) {
`/api/table/meta?schema=${encodeURIComponent(schema)}&table=${encodeURIComponent(tableName)}` `/api/table/meta?schema=${encodeURIComponent(schema)}&table=${encodeURIComponent(tableName)}`
); );
console.log('📋 Метаданные таблицы загружены:', {
schema,
table: tableName,
primaryKey: currentMeta.primaryKey,
columnsCount: currentMeta.columns.length
});
const columns = [ const columns = [
{ {
formatter: "rowSelection", formatter: "rowSelection",
@@ -213,7 +205,6 @@ async function selectTable(schema, tableName) {
headerSort: false, headerSort: false,
width: 40, width: 40,
cellClick: function(e, cell) { cellClick: function(e, cell) {
console.log('📌 Клик на чекбокс ячейки');
cell.getRow().toggleSelect(); cell.getRow().toggleSelect();
} }
}, },
@@ -279,80 +270,24 @@ async function selectTable(schema, tableName) {
}; };
}, },
// Обновляем счетчик при изменении выделения
rowSelectionChanged: function(data, rows) { rowSelectionChanged: function(data, rows) {
console.group('🔔 rowSelectionChanged вызван');
console.log('Параметры:', {
'data.length (все выделенные)': data.length,
'rows.length (изменённые)': rows.length,
'selectedRowsData.size до': selectedRowsData.size
});
// Выводим первые 2 элемента из data для проверки
if (data.length > 0) {
console.log('Первая строка из data:', data[0]);
console.log('Проверка getRowKey для первой строки:', getRowKey(data[0]));
}
// Выводим первые 2 элемента из rows для проверки
if (rows.length > 0) {
console.log('Первая строка из rows (Row объект):', rows[0]);
console.log('getData() первой строки из rows:', rows[0].getData());
}
// Получаем все строки текущей страницы
const currentPageRows = this.getRows();
console.log('Строк на текущей странице:', currentPageRows.length);
// Удаляем все строки текущей страницы из selectedRowsData
let deletedCount = 0;
currentPageRows.forEach(row => {
const rowData = row.getData();
const key = getRowKey(rowData);
if (selectedRowsData.has(key)) {
selectedRowsData.delete(key);
deletedCount++;
}
});
console.log('Удалено из selectedRowsData:', deletedCount);
// Добавляем все выделенные строки из параметра data
let addedCount = 0;
data.forEach((rowData, index) => {
const key = getRowKey(rowData);
selectedRowsData.set(key, rowData);
addedCount++;
if (index < 3) {
console.log(` Добавлена строка ${index + 1}: key="${key}"`);
}
});
console.log('Добавлено в selectedRowsData:', addedCount);
console.log('selectedRowsData.size после:', selectedRowsData.size);
console.log('Первые 3 ключа в selectedRowsData:', Array.from(selectedRowsData.keys()).slice(0, 3));
console.groupEnd();
updateSelectionCounter(); updateSelectionCounter();
}, },
// Восстанавливаем выделение для глобально выделенных строк
dataLoaded: function(data) { dataLoaded: function(data) {
console.log('📄 dataLoaded: восстанавливаем выделение для', selectedRowsData.size, 'строк'); if (selectedRowsDataGlobal.size > 0) {
if (selectedRowsData.size > 0) {
const rows = this.getRows(); const rows = this.getRows();
let restoredCount = 0;
rows.forEach(row => { rows.forEach(row => {
const rowData = row.getData(); const rowData = row.getData();
const key = getRowKey(rowData); const key = getRowKey(rowData);
if (selectedRowsData.has(key)) { if (selectedRowsDataGlobal.has(key)) {
row.select(); row.select();
restoredCount++;
} }
}); });
console.log(' ✅ Восстановлено выделение для', restoredCount, 'строк на странице');
} }
}, },
@@ -393,7 +328,6 @@ async function selectTable(schema, tableName) {
document.addEventListener('keydown', enterHandler); document.addEventListener('keydown', enterHandler);
} }
document.getElementById('btnSelectAll').addEventListener('click', async () => { document.getElementById('btnSelectAll').addEventListener('click', async () => {
if (!currentSchema || !currentTable || !table) { if (!currentSchema || !currentTable || !table) {
alert('Сначала выберите таблицу'); alert('Сначала выберите таблицу');
@@ -428,10 +362,10 @@ document.getElementById('btnSelectAll').addEventListener('click', async () => {
if (!proceed) return; if (!proceed) return;
} }
selectedRowsData.clear(); selectedRowsDataGlobal.clear();
result.data.forEach(rowData => { result.data.forEach(rowData => {
const key = getRowKey(rowData); const key = getRowKey(rowData);
selectedRowsData.set(key, rowData); selectedRowsDataGlobal.set(key, rowData);
}); });
const rows = table.getRows(); const rows = table.getRows();
@@ -439,7 +373,7 @@ document.getElementById('btnSelectAll').addEventListener('click', async () => {
const rowData = row.getData(); const rowData = row.getData();
const key = getRowKey(rowData); const key = getRowKey(rowData);
if (selectedRowsData.has(key)) { if (selectedRowsDataGlobal.has(key)) {
row.select(); row.select();
} }
}); });
@@ -455,7 +389,7 @@ document.getElementById('btnSelectAll').addEventListener('click', async () => {
document.getElementById('btnDeselectAll').addEventListener('click', () => { document.getElementById('btnDeselectAll').addEventListener('click', () => {
if (!table) return; if (!table) return;
selectedRowsData.clear(); selectedRowsDataGlobal.clear();
table.deselectRow(); table.deselectRow();
updateSelectionCounter(); updateSelectionCounter();
}); });
@@ -662,39 +596,39 @@ async function promptForForeignKeys(fkFields) {
}); });
} }
// ✅ УДАЛИТЬ - упрощенная версия
document.getElementById('btnDelete').addEventListener('click', async () => { document.getElementById('btnDelete').addEventListener('click', async () => {
if (!table || !currentSchema || !currentTable) { if (!table || !currentSchema || !currentTable) {
alert('Сначала выберите таблицу'); alert('Сначала выберите таблицу');
return; return;
} }
const count = selectedRowsData.size; // Получаем выделенные строки напрямую из Tabulator
const tabulatorSelected = table.getSelectedData();
// Объединяем с глобально выделенными (из "Выделить все")
const allSelectedData = new Map();
// Добавляем из Tabulator
tabulatorSelected.forEach(rowData => {
const key = getRowKey(rowData);
allSelectedData.set(key, rowData);
});
// Добавляем из глобального хранилища
selectedRowsDataGlobal.forEach((rowData, key) => {
allSelectedData.set(key, rowData);
});
const count = allSelectedData.size;
console.log('🗑️ Удаление:', { console.log('🗑️ Удаление:', {
selectedRowsDataSize: count, tabulatorSelected: tabulatorSelected.length,
primaryKeys: currentMeta?.primaryKey globalSelected: selectedRowsDataGlobal.size,
total: count
}); });
if (count === 0) { if (count === 0) {
const tabulatorSelected = table.getSelectedData();
console.log('Tabulator selected rows:', tabulatorSelected.length);
if (tabulatorSelected.length > 0) {
console.error('❌ Несоответствие: Tabulator имеет выделенные строки, но selectedRowsData пуст!');
selectedRowsData.clear();
tabulatorSelected.forEach(rowData => {
const key = getRowKey(rowData);
selectedRowsData.set(key, rowData);
});
updateSelectionCounter();
if (selectedRowsData.size > 0) {
alert(`Обнаружено ${selectedRowsData.size} выделенных строк. Попробуйте удалить снова.`);
return;
}
}
alert('Выберите строки для удаления'); alert('Выберите строки для удаления');
return; return;
} }
@@ -709,7 +643,7 @@ document.getElementById('btnDelete').addEventListener('click', async () => {
document.body.appendChild(modal); document.body.appendChild(modal);
try { try {
const rowsArray = Array.from(selectedRowsData.values()); const rowsArray = Array.from(allSelectedData.values());
const result = await api('/api/table/delete-batch', 'POST', { const result = await api('/api/table/delete-batch', 'POST', {
schema: currentSchema, schema: currentSchema,
@@ -719,7 +653,8 @@ document.getElementById('btnDelete').addEventListener('click', async () => {
document.body.removeChild(modal); document.body.removeChild(modal);
selectedRowsData.clear(); selectedRowsDataGlobal.clear();
table.deselectRow();
updateSelectionCounter(); updateSelectionCounter();
await table.replaceData(); await table.replaceData();