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 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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user