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