ваше сообщение коммита
This commit is contained in:
@@ -104,13 +104,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, computed, ref, onMounted, watch } from 'vue';
|
||||
import { defineProps, computed, ref, onMounted, watch, onUnmounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElSelect, ElOption, ElForm, ElFormItem, ElInput, ElDatePicker, ElCheckbox, ElButton, ElMessageBox, ElMessage } from 'element-plus';
|
||||
import ImportContactsModal from './ImportContactsModal.vue';
|
||||
import BroadcastModal from './BroadcastModal.vue';
|
||||
import tablesService from '../services/tablesService';
|
||||
import messagesService from '../services/messagesService';
|
||||
import { useTagsWebSocket } from '../composables/useTagsWebSocket';
|
||||
const props = defineProps({
|
||||
contacts: { type: Array, default: () => [] },
|
||||
newContacts: { type: Array, default: () => [] },
|
||||
@@ -141,38 +142,81 @@ const showBroadcastModal = ref(false);
|
||||
const selectedIds = ref([]);
|
||||
const selectAll = ref(false);
|
||||
|
||||
// WebSocket для тегов - ОТКЛЮЧАЕМ из-за циклических запросов
|
||||
// const { onTagsUpdate } = useTagsWebSocket();
|
||||
// let unsubscribeFromTags = null;
|
||||
let lastTagsHash = ref(''); // Хеш последних загруженных тегов
|
||||
let tagsUpdateInterval = null; // Интервал для периодического обновления тегов
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchContacts();
|
||||
await loadAvailableTags();
|
||||
// ВРЕМЕННО ОТКЛЮЧАЕМ - await loadAvailableTags();
|
||||
|
||||
// ВРЕМЕННО ОТКЛЮЧАЕМ - Вместо WebSocket используем периодическое обновление каждые 30 секунд
|
||||
// tagsUpdateInterval = setInterval(async () => {
|
||||
// console.log('[ContactTable] Периодическое обновление тегов');
|
||||
// await loadAvailableTags();
|
||||
// }, 30000); // 30 секунд
|
||||
|
||||
// Подписываемся на обновления тегов - ОТКЛЮЧАЕМ
|
||||
// unsubscribeFromTags = onTagsUpdate(async () => {
|
||||
// console.log('[ContactTable] Получено обновление тегов, проверяем необходимость перезагрузки');
|
||||
// await loadAvailableTags();
|
||||
// });
|
||||
});
|
||||
|
||||
async function loadAvailableTags() {
|
||||
try {
|
||||
// Получаем все пользовательские таблицы и ищем "Теги клиентов"
|
||||
const tables = await tablesService.getTables();
|
||||
const tagsTable = tables.find(t => t.name === 'Теги клиентов');
|
||||
|
||||
if (tagsTable) {
|
||||
// Загружаем данные таблицы тегов
|
||||
const table = await tablesService.getTable(tagsTable.id);
|
||||
const nameColumn = table.columns.find(col => col.name === 'Название') || table.columns[0];
|
||||
|
||||
if (nameColumn) {
|
||||
// Формируем список тегов
|
||||
availableTags.value = table.rows.map(row => {
|
||||
const nameCell = table.cellValues.find(c => c.row_id === row.id && c.column_id === nameColumn.id);
|
||||
return {
|
||||
id: row.id,
|
||||
name: nameCell ? nameCell.value : `Тег ${row.id}`
|
||||
};
|
||||
}).filter(tag => tag.name.trim()); // Исключаем пустые названия
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Ошибка загрузки тегов:', e);
|
||||
availableTags.value = [];
|
||||
onUnmounted(() => {
|
||||
// Отписываемся от WebSocket при размонтировании - ОТКЛЮЧАЕМ
|
||||
// if (unsubscribeFromTags) {
|
||||
// unsubscribeFromTags();
|
||||
// }
|
||||
|
||||
// Очищаем интервал
|
||||
if (tagsUpdateInterval) {
|
||||
clearInterval(tagsUpdateInterval);
|
||||
tagsUpdateInterval = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ВРЕМЕННО ОТКЛЮЧАЕМ - async function loadAvailableTags() {
|
||||
// try {
|
||||
// // Получаем все пользовательские таблицы и ищем "Теги клиентов"
|
||||
// const tables = await tablesService.getTables();
|
||||
// const tagsTable = tables.find(t => t.name === 'Теги клиентов');
|
||||
//
|
||||
// if (tagsTable) {
|
||||
// // Загружаем данные таблицы тегов
|
||||
// const table = await tablesService.getTable(tagsTable.id);
|
||||
// const nameColumn = table.columns.find(col => col.name === 'Название') || table.columns[0];
|
||||
//
|
||||
// if (nameColumn) {
|
||||
// // Формируем список тегов
|
||||
// const newTags = table.rows.map(row => {
|
||||
// const nameCell = table.cellValues.find(c => c.row_id === row.id && c.column_id === nameColumn.id);
|
||||
// return {
|
||||
// id: row.id,
|
||||
// name: nameCell ? nameCell.value : `Тег ${row.id}`
|
||||
// };
|
||||
// }).filter(tag => tag.name.trim()); // Исключаем пустые названия
|
||||
//
|
||||
// // Создаем хеш для сравнения
|
||||
// const newTagsHash = JSON.stringify(newTags.map(t => `${t.id}:${t.name}`).sort());
|
||||
//
|
||||
// // Обновляем только если данные действительно изменились
|
||||
// if (newTagsHash !== lastTagsHash.value) {
|
||||
// console.log('[ContactTable] Теги изменились, обновляем список');
|
||||
// availableTags.value = newTags;
|
||||
// lastTagsHash.value = newTagsHash;
|
||||
// } else {
|
||||
// console.log('[ContactTable] Теги не изменились, пропускаем обновление');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error('Ошибка загрузки тегов:', e);
|
||||
// availableTags.value = [];
|
||||
// }
|
||||
// }
|
||||
|
||||
function buildQuery() {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
@@ -123,8 +123,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, computed, nextTick } from 'vue';
|
||||
import { ref, watch, onMounted, computed, nextTick, onUnmounted } from 'vue';
|
||||
import tablesService from '../../services/tablesService';
|
||||
import { useTablesWebSocket } from '../../composables/useTablesWebSocket';
|
||||
import { useTagsWebSocket } from '../../composables/useTagsWebSocket';
|
||||
import cacheService from '../../services/cacheService';
|
||||
const props = defineProps(['rowId', 'column', 'cellValues']);
|
||||
const emit = defineEmits(['update']);
|
||||
|
||||
@@ -174,12 +177,54 @@ watch(editing, (val) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем watch для отслеживания изменений в мультисвязях
|
||||
// Добавляем watch для отслеживания изменений в мультисвязях с дебаунсингом
|
||||
let debounceTimer = null;
|
||||
watch(editMultiRelationValues, (newValues, oldValues) => {
|
||||
console.log('[editMultiRelationValues] changed from:', oldValues, 'to:', newValues);
|
||||
|
||||
// Очищаем предыдущий таймер
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
|
||||
// Устанавливаем новый таймер для предотвращения множественных обновлений
|
||||
debounceTimer = setTimeout(() => {
|
||||
// Здесь можно добавить дополнительную логику, если нужно
|
||||
}, 100);
|
||||
}, { deep: true });
|
||||
|
||||
// Флаг для предотвращения циклической загрузки
|
||||
const isLoadingMultiRelations = ref(false);
|
||||
const lastLoadedValues = ref(new Map()); // Кэш последних загруженных значений
|
||||
|
||||
// WebSocket для обновлений таблиц
|
||||
const { subscribeToTableRelationsUpdates } = useTablesWebSocket();
|
||||
let unsubscribeFromWebSocket = null;
|
||||
|
||||
// Функция для очистки кэша
|
||||
function clearCache() {
|
||||
cacheService.clearAll();
|
||||
console.log('[TableCell] Кэш очищен');
|
||||
}
|
||||
|
||||
// WebSocket для тегов
|
||||
const { onTagsUpdate } = useTagsWebSocket();
|
||||
let unsubscribeFromTags = null;
|
||||
|
||||
// Удаляем локальные кэши
|
||||
// const multiRelationOptionsCache = new Map();
|
||||
// const multiRelationOptionsCacheTimeout = 30000; // 30 секунд
|
||||
// const relationsCache = new Map();
|
||||
// const relationsCacheTimeout = 10000; // 10 секунд
|
||||
|
||||
// Флаг для предотвращения повторных вызовов
|
||||
let isInitialized = false;
|
||||
let isMultiRelationValuesLoaded = false;
|
||||
|
||||
onMounted(async () => {
|
||||
const startTime = Date.now();
|
||||
console.log(`[TableCell] 🚀 Начало монтирования ячейки row:${props.rowId} col:${props.column.id} в ${startTime}`);
|
||||
|
||||
if (props.column.type === 'multiselect') {
|
||||
multiOptions.value = (props.column.options && props.column.options.options) || [];
|
||||
const cell = props.cellValues.find(
|
||||
@@ -203,8 +248,46 @@ onMounted(async () => {
|
||||
} else if (props.column.type === 'lookup') {
|
||||
await loadLookupValues();
|
||||
} else if (props.column.type === 'multiselect-relation') {
|
||||
await loadMultiRelationOptions();
|
||||
await loadMultiRelationValues();
|
||||
// Загружаем опции только один раз
|
||||
if (!isInitialized) {
|
||||
console.log(`[TableCell] 📥 Загружаем опции для row:${props.rowId} col:${props.column.id}`);
|
||||
await loadMultiRelationOptions();
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
// Загружаем relations только один раз для каждой комбинации rowId + columnId
|
||||
if (!isMultiRelationValuesLoaded) {
|
||||
console.log(`[TableCell] 📥 Загружаем relations для row:${props.rowId} col:${props.column.id}`);
|
||||
await loadMultiRelationValues();
|
||||
isMultiRelationValuesLoaded = true;
|
||||
}
|
||||
|
||||
// Подписываемся на обновления таблицы
|
||||
if (props.column.type === 'multiselect-relation') {
|
||||
unsubscribeFromWebSocket = subscribeToTableRelationsUpdates(props.column.table_id, async () => {
|
||||
console.log('[TableCell] Получено обновление таблицы, перезагружаем relations');
|
||||
// Сбрасываем флаг загрузки
|
||||
isMultiRelationValuesLoaded = false;
|
||||
// Очищаем кэш relations для текущей строки
|
||||
cacheService.clearRelationsCache(props.rowId);
|
||||
await loadMultiRelationValues();
|
||||
});
|
||||
}
|
||||
|
||||
// Подписываемся на обновления тегов, если это связанная таблица тегов
|
||||
if (props.column.options && props.column.options.relatedTableId) {
|
||||
unsubscribeFromTags = onTagsUpdate(async () => {
|
||||
console.log('[TableCell] Получено обновление тегов, перезагружаем опции');
|
||||
// Сбрасываем флаги загрузки
|
||||
isInitialized = false;
|
||||
isMultiRelationValuesLoaded = false;
|
||||
// Очищаем кэш таблицы тегов
|
||||
cacheService.clearTableCache(props.column.options.relatedTableId);
|
||||
await loadMultiRelationOptions();
|
||||
await loadMultiRelationValues();
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализация localValue для отображения массива, если нет имен
|
||||
const cell = props.cellValues.find(
|
||||
c => c.row_id === props.rowId && c.column_id === props.column.id
|
||||
@@ -216,6 +299,28 @@ onMounted(async () => {
|
||||
);
|
||||
localValue.value = cell ? cell.value : '';
|
||||
}
|
||||
const endTime = Date.now();
|
||||
console.log(`[TableCell] ✅ Завершено монтирование ячейки row:${props.rowId} col:${props.column.id} за ${endTime - startTime}ms`);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// Отписываемся от WebSocket при размонтировании компонента
|
||||
if (unsubscribeFromWebSocket) {
|
||||
unsubscribeFromWebSocket();
|
||||
unsubscribeFromWebSocket = null;
|
||||
}
|
||||
|
||||
// Отписываемся от обновлений тегов
|
||||
if (unsubscribeFromTags) {
|
||||
unsubscribeFromTags();
|
||||
unsubscribeFromTags = null;
|
||||
}
|
||||
|
||||
// Очищаем таймер дебаунсинга
|
||||
if (loadMultiRelationValuesTimer) {
|
||||
clearTimeout(loadMultiRelationValuesTimer);
|
||||
loadMultiRelationValuesTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -244,7 +349,9 @@ watch(
|
||||
} else if (props.column.type === 'lookup') {
|
||||
await loadLookupValues();
|
||||
} else if (props.column.type === 'multiselect-relation') {
|
||||
// Загружаем опции только один раз при изменении колонки
|
||||
await loadMultiRelationOptions();
|
||||
// Загружаем значения
|
||||
await loadMultiRelationValues();
|
||||
// Инициализация localValue для отображения массива, если нет имен
|
||||
const cell = props.cellValues.find(
|
||||
@@ -378,49 +485,133 @@ async function loadLookupValues() {
|
||||
}
|
||||
|
||||
async function loadMultiRelationOptions() {
|
||||
const rel = props.column.options || {};
|
||||
if (!rel.relatedTableId) return;
|
||||
const res = await fetch(`/api/tables/${rel.relatedTableId}`);
|
||||
const data = await res.json();
|
||||
// Далее используем data.columns, data.rows, data.cellValues
|
||||
const colId = rel.relatedColumnId || (data.columns[0] && data.columns[0].id);
|
||||
const opts = [];
|
||||
for (const row of data.rows) {
|
||||
const cell = data.cellValues.find(c => c.row_id === row.id && c.column_id === colId);
|
||||
opts.push({ id: row.id, display: cell ? cell.value : `ID ${row.id}` });
|
||||
// Проверяем, не загружены ли уже опции
|
||||
if (multiRelationOptions.value.length > 0) {
|
||||
console.log('[loadMultiRelationOptions] Опции уже загружены, пропускаем');
|
||||
return;
|
||||
}
|
||||
|
||||
const rel = props.column.options || {};
|
||||
if (rel.relatedTableId && rel.relatedColumnId) {
|
||||
try {
|
||||
// Проверяем кэш для данных таблицы
|
||||
const cachedTableData = cacheService.getTableData(rel.relatedTableId, 'default');
|
||||
let tableData;
|
||||
|
||||
if (cachedTableData) {
|
||||
console.log(`[loadMultiRelationOptions] ✅ Используем предварительно загруженные данные таблицы ${rel.relatedTableId}`);
|
||||
tableData = cachedTableData;
|
||||
} else {
|
||||
console.log(`[loadMultiRelationOptions] ⚠️ Данные таблицы ${rel.relatedTableId} не найдены в кэше, загружаем заново`);
|
||||
const response = await fetch(`/api/tables/${rel.relatedTableId}`);
|
||||
tableData = await response.json();
|
||||
// Сохраняем в кэш
|
||||
cacheService.setTableData(rel.relatedTableId, 'default', tableData);
|
||||
}
|
||||
|
||||
// Формируем опции из данных таблицы
|
||||
const colId = rel.relatedColumnId || (tableData.columns[0] && tableData.columns[0].id);
|
||||
const opts = [];
|
||||
for (const row of tableData.rows) {
|
||||
const cell = tableData.cellValues.find(c => c.row_id === row.id && c.column_id === colId);
|
||||
opts.push({ id: row.id, display: cell ? cell.value : `ID ${row.id}` });
|
||||
}
|
||||
multiRelationOptions.value = opts;
|
||||
console.log(`[loadMultiRelationOptions] Загружено ${opts.length} опций для таблицы ${rel.relatedTableId}`);
|
||||
} catch (e) {
|
||||
console.error('[loadMultiRelationOptions] Error:', e);
|
||||
}
|
||||
}
|
||||
multiRelationOptions.value = opts;
|
||||
}
|
||||
|
||||
// Дебаунсинг для loadMultiRelationValues
|
||||
let loadMultiRelationValuesTimer = null;
|
||||
const LOAD_DEBOUNCE_DELAY = 100; // 100ms
|
||||
|
||||
async function loadMultiRelationValues() {
|
||||
// Получаем связи для текущей строки
|
||||
console.log('[loadMultiRelationValues] called for row:', props.rowId, 'column:', props.column.id);
|
||||
editMultiRelationValues.value = [];
|
||||
selectedMultiRelationNames.value = [];
|
||||
try {
|
||||
const rel = props.column.options || {};
|
||||
if (rel.relatedTableId && rel.relatedColumnId) {
|
||||
const url = `/api/tables/${props.column.table_id}/row/${props.rowId}/relations`;
|
||||
console.log('[loadMultiRelationValues] GET request to:', url);
|
||||
const res = await fetch(url);
|
||||
const relations = await res.json();
|
||||
console.log('[loadMultiRelationValues] API response status:', res.status, 'relations:', relations);
|
||||
// Приводим все id к строке для корректного сравнения
|
||||
const relatedRowIds = relations
|
||||
.filter(r => String(r.column_id) === String(props.column.id) && String(r.to_table_id) === String(rel.relatedTableId))
|
||||
.map(r => String(r.to_row_id));
|
||||
console.log('[loadMultiRelationValues] filtered related row ids:', relatedRowIds);
|
||||
editMultiRelationValues.value = relatedRowIds;
|
||||
// Получаем display-значения
|
||||
await loadMultiRelationOptions();
|
||||
selectedMultiRelationNames.value = multiRelationOptions.value
|
||||
.filter(opt => relatedRowIds.includes(String(opt.id)))
|
||||
.map(opt => opt.display);
|
||||
console.log('[loadMultiRelationValues] selectedMultiRelationNames:', selectedMultiRelationNames.value);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[loadMultiRelationValues] Error:', e);
|
||||
// Проверяем, не загружены ли уже данные
|
||||
if (isMultiRelationValuesLoaded) {
|
||||
console.log('[loadMultiRelationValues] Данные уже загружены, пропускаем');
|
||||
return;
|
||||
}
|
||||
|
||||
// Очищаем предыдущий таймер
|
||||
if (loadMultiRelationValuesTimer) {
|
||||
clearTimeout(loadMultiRelationValuesTimer);
|
||||
}
|
||||
|
||||
// Устанавливаем новый таймер
|
||||
loadMultiRelationValuesTimer = setTimeout(async () => {
|
||||
// Получаем связи для текущей строки
|
||||
console.log('[loadMultiRelationValues] called for row:', props.rowId, 'column:', props.column.id);
|
||||
|
||||
try {
|
||||
const rel = props.column.options || {};
|
||||
if (rel.relatedTableId && rel.relatedColumnId) {
|
||||
// Проверяем кэш для relations
|
||||
let relations;
|
||||
let tableData;
|
||||
|
||||
const cachedRelations = cacheService.getRelationsData(props.rowId, props.column.id);
|
||||
if (cachedRelations) {
|
||||
console.log('[loadMultiRelationValues] ✅ Используем предварительно загруженные relations для строки', props.rowId);
|
||||
relations = cachedRelations;
|
||||
} else {
|
||||
console.log('[loadMultiRelationValues] ⚠️ Relations не найдены в кэше, загружаем заново для строки', props.rowId);
|
||||
// Выполняем запросы параллельно
|
||||
const [relationsRes, tableRes] = await Promise.all([
|
||||
fetch(`/api/tables/${props.column.table_id}/row/${props.rowId}/relations`),
|
||||
fetch(`/api/tables/${rel.relatedTableId}`)
|
||||
]);
|
||||
|
||||
[relations, tableData] = await Promise.all([
|
||||
relationsRes.json(),
|
||||
tableRes.json()
|
||||
]);
|
||||
|
||||
// Сохраняем relations в кэш
|
||||
cacheService.setRelationsData(props.rowId, props.column.id, relations);
|
||||
}
|
||||
|
||||
console.log('[loadMultiRelationValues] API response status: 200 relations:', relations);
|
||||
|
||||
// Приводим все id к строке для корректного сравнения
|
||||
const relatedRowIds = relations
|
||||
.filter(r => String(r.column_id) === String(props.column.id) && String(r.to_table_id) === String(rel.relatedTableId))
|
||||
.map(r => String(r.to_row_id));
|
||||
console.log('[loadMultiRelationValues] filtered related row ids:', relatedRowIds);
|
||||
|
||||
// Обновляем значения
|
||||
editMultiRelationValues.value = relatedRowIds;
|
||||
|
||||
// Если tableData не загружена, загружаем её отдельно
|
||||
if (!tableData) {
|
||||
const tableRes = await fetch(`/api/tables/${rel.relatedTableId}`);
|
||||
tableData = await tableRes.json();
|
||||
}
|
||||
|
||||
// Формируем опции из загруженных данных таблицы
|
||||
const colId = rel.relatedColumnId || (tableData.columns[0] && tableData.columns[0].id);
|
||||
const opts = [];
|
||||
for (const row of tableData.rows) {
|
||||
const cell = tableData.cellValues.find(c => c.row_id === row.id && c.column_id === colId);
|
||||
opts.push({ id: row.id, display: cell ? cell.value : `ID ${row.id}` });
|
||||
}
|
||||
multiRelationOptions.value = opts;
|
||||
|
||||
// Получаем display-значения
|
||||
selectedMultiRelationNames.value = multiRelationOptions.value
|
||||
.filter(opt => relatedRowIds.includes(String(opt.id)))
|
||||
.map(opt => opt.display);
|
||||
console.log('[loadMultiRelationValues] selectedMultiRelationNames:', selectedMultiRelationNames.value);
|
||||
|
||||
// Отмечаем, что данные загружены
|
||||
isMultiRelationValuesLoaded = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[loadMultiRelationValues] Error:', e);
|
||||
}
|
||||
}, LOAD_DEBOUNCE_DELAY);
|
||||
}
|
||||
|
||||
async function saveMultiRelation() {
|
||||
@@ -484,11 +675,12 @@ async function addTag() {
|
||||
newTagName.value = '';
|
||||
showAddTagInput.value = false;
|
||||
|
||||
// Обновляем список опций
|
||||
await loadMultiRelationOptions();
|
||||
// Обновляем список опций и добавляем тег в выбранные параллельно
|
||||
await Promise.all([
|
||||
loadMultiRelationOptions(),
|
||||
Promise.resolve(editMultiRelationValues.value.push(String(newRow.id)))
|
||||
]);
|
||||
|
||||
// Автоматически добавляем новый тег в выбранные
|
||||
editMultiRelationValues.value.push(String(newRow.id));
|
||||
console.log('[addTag] Тег добавлен в выбранные:', editMultiRelationValues.value);
|
||||
} catch (e) {
|
||||
console.error('[addTag] Ошибка при добавлении тега:', e);
|
||||
|
||||
@@ -160,13 +160,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { ref, onMounted, computed, watch, onUnmounted } from 'vue';
|
||||
import tablesService from '../../services/tablesService';
|
||||
import TableCell from './TableCell.vue';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import axios from 'axios';
|
||||
// Импортируем компоненты Element Plus
|
||||
import { ElSelect, ElOption, ElButton } from 'element-plus';
|
||||
import websocketService from '../../services/websocketService';
|
||||
import cacheService from '../../services/cacheService';
|
||||
let unsubscribeFromTableUpdate = null;
|
||||
|
||||
const { isAdmin } = useAuthContext();
|
||||
const rebuilding = ref(false);
|
||||
const rebuildStatus = ref(null);
|
||||
@@ -370,18 +374,131 @@ async function fetchFilteredRows() {
|
||||
|
||||
// Основная загрузка таблицы
|
||||
async function fetchTable() {
|
||||
const startTime = Date.now();
|
||||
console.log(`[UserTableView] 🚀 Начало загрузки таблицы ${props.tableId} в ${startTime}`);
|
||||
|
||||
const data = await tablesService.getTable(props.tableId);
|
||||
columns.value = data.columns;
|
||||
rows.value = data.rows;
|
||||
cellValues.value = data.cellValues;
|
||||
tableMeta.value = { name: data.name, description: data.description };
|
||||
await updateRelationFilterDefs();
|
||||
await fetchFilteredRows();
|
||||
|
||||
console.log(`[UserTableView] 📊 Загружено ${rows.value.length} строк, ${columns.value.length} столбцов`);
|
||||
|
||||
// Предварительно загружаем все relations для всех строк параллельно
|
||||
const relationColumns = columns.value.filter(col => col.type === 'multiselect-relation');
|
||||
if (relationColumns.length > 0) {
|
||||
console.log(`[UserTableView] 🔄 Предварительно загружаем relations для ${relationColumns.length} столбцов`);
|
||||
|
||||
const relationPromises = [];
|
||||
for (const row of rows.value) {
|
||||
for (const col of relationColumns) {
|
||||
const promise = fetch(`/api/tables/${col.table_id}/row/${row.id}/relations`)
|
||||
.then(res => res.json())
|
||||
.then(relations => {
|
||||
// Сохраняем в кэш
|
||||
cacheService.setRelationsData(row.id, col.id, relations);
|
||||
return { rowId: row.id, colId: col.id, relations };
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`[UserTableView] Ошибка загрузки relations для row:${row.id} col:${col.id}:`, error);
|
||||
return { rowId: row.id, colId: col.id, relations: [] };
|
||||
});
|
||||
relationPromises.push(promise);
|
||||
}
|
||||
}
|
||||
|
||||
// Ждем загрузки всех relations
|
||||
const results = await Promise.all(relationPromises);
|
||||
console.log(`[UserTableView] ✅ Предварительно загружено ${results.length} relations`);
|
||||
}
|
||||
|
||||
// Предварительно загружаем данные связанных таблиц для опций
|
||||
const relatedTableIds = new Set();
|
||||
for (const col of relationColumns) {
|
||||
if (col.options && col.options.relatedTableId) {
|
||||
relatedTableIds.add(col.options.relatedTableId);
|
||||
}
|
||||
}
|
||||
|
||||
if (relatedTableIds.size > 0) {
|
||||
console.log(`[UserTableView] 🔄 Предварительно загружаем данные ${relatedTableIds.size} связанных таблиц для опций`);
|
||||
|
||||
const tablePromises = Array.from(relatedTableIds).map(tableId =>
|
||||
fetch(`/api/tables/${tableId}`)
|
||||
.then(res => res.json())
|
||||
.then(tableData => {
|
||||
// Сохраняем в кэш с разными ключами для разных столбцов
|
||||
cacheService.setTableData(tableId, 'default', tableData);
|
||||
return { tableId, tableData };
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`[UserTableView] Ошибка загрузки таблицы ${tableId}:`, error);
|
||||
return { tableId, tableData: null };
|
||||
})
|
||||
);
|
||||
|
||||
const tableResults = await Promise.all(tablePromises);
|
||||
console.log(`[UserTableView] ✅ Предварительно загружено ${tableResults.length} связанных таблиц`);
|
||||
}
|
||||
|
||||
// Выполняем обновление фильтров и фильтрацию строк параллельно
|
||||
await Promise.all([
|
||||
updateRelationFilterDefs(),
|
||||
fetchFilteredRows()
|
||||
]);
|
||||
|
||||
// Выводим статистику кэша для отладки
|
||||
const cacheStats = cacheService.getStats();
|
||||
console.log('[UserTableView] Статистика кэша после загрузки таблицы:', {
|
||||
tableCacheSize: cacheStats.tableCacheSize,
|
||||
relationsCacheSize: cacheStats.relationsCacheSize,
|
||||
tableCacheKeys: cacheStats.tableCacheKeys,
|
||||
relationsCacheKeys: cacheStats.relationsCacheKeys.slice(0, 5) // Показываем только первые 5 ключей
|
||||
});
|
||||
|
||||
const endTime = Date.now();
|
||||
console.log(`[UserTableView] ✅ Завершена загрузка таблицы ${props.tableId} за ${endTime - startTime}ms`);
|
||||
}
|
||||
|
||||
async function updateRelationFilterDefs() {
|
||||
// Для каждого multiselect-relation-столбца формируем опции
|
||||
const defs = [];
|
||||
const relatedTableMap = new Map();
|
||||
|
||||
// Сначала собираем все уникальные relatedTableId и создаем промисы для параллельной загрузки
|
||||
for (const col of columns.value) {
|
||||
if (col.type === 'multiselect-relation' && col.options && col.options.relatedTableId && col.options.relatedColumnId) {
|
||||
const tableId = col.options.relatedTableId;
|
||||
if (!relatedTableMap.has(tableId)) {
|
||||
// Проверяем кэш
|
||||
const cached = cacheService.getTableData(tableId);
|
||||
if (cached) {
|
||||
console.log(`[updateRelationFilterDefs] Используем кэшированные данные таблицы ${tableId}`);
|
||||
relatedTableMap.set(tableId, Promise.resolve(cached));
|
||||
} else {
|
||||
relatedTableMap.set(tableId, tablesService.getTable(tableId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Загружаем все связанные таблицы параллельно
|
||||
const relatedTables = await Promise.all(Array.from(relatedTableMap.values()));
|
||||
|
||||
// Создаем Map для быстрого доступа к загруженным таблицам
|
||||
const tableMap = new Map();
|
||||
let tableIndex = 0;
|
||||
for (const tableId of relatedTableMap.keys()) {
|
||||
const tableData = relatedTables[tableIndex++];
|
||||
tableMap.set(tableId, tableData);
|
||||
|
||||
// Сохраняем в кэш, если это новые данные
|
||||
if (!cacheService.getTableData(tableId)) {
|
||||
cacheService.setTableData(tableId, 'default', tableData);
|
||||
}
|
||||
}
|
||||
|
||||
// Теперь формируем опции фильтров
|
||||
for (const col of columns.value) {
|
||||
if (col.type === 'multiselect-relation' && col.options && col.options.relatedTableId && col.options.relatedColumnId) {
|
||||
// Собираем все уникальные id из этого столбца по всем строкам
|
||||
@@ -391,8 +508,9 @@ async function updateRelationFilterDefs() {
|
||||
const arr = parseIfArray(cell ? cell.value : []);
|
||||
arr.forEach(val => idsSet.add(val));
|
||||
}
|
||||
// Получаем значения из связанной таблицы
|
||||
const relTable = await tablesService.getTable(col.options.relatedTableId);
|
||||
|
||||
// Получаем значения из связанной таблицы (уже загружена)
|
||||
const relTable = tableMap.get(col.options.relatedTableId);
|
||||
const opts = Array.from(idsSet).map(id => {
|
||||
const relRow = relTable.rows.find(r => String(r.id) === String(id));
|
||||
const cell = relTable.cellValues.find(c => c.row_id === (relRow ? relRow.id : id) && c.column_id === col.options.relatedColumnId);
|
||||
@@ -421,6 +539,19 @@ watch([relationFilters], fetchFilteredRows, { deep: true });
|
||||
|
||||
onMounted(() => {
|
||||
fetchTable();
|
||||
// Подписка на WebSocket обновления таблицы
|
||||
unsubscribeFromTableUpdate = websocketService.onTableUpdate(props.tableId, () => {
|
||||
console.log('[UserTableView] Получено событие table-updated, перезагружаем данные');
|
||||
// Очищаем кэш текущей таблицы
|
||||
cacheService.clearTableCache(props.tableId);
|
||||
fetchTable();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unsubscribeFromTableUpdate) {
|
||||
unsubscribeFromTableUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
// Для редактирования ячеек
|
||||
|
||||
Reference in New Issue
Block a user