ваше сообщение коммита
This commit is contained in:
@@ -526,7 +526,7 @@ async function loadMultiRelationOptions() {
|
||||
|
||||
// Дебаунсинг для loadMultiRelationValues
|
||||
let loadMultiRelationValuesTimer = null;
|
||||
const LOAD_DEBOUNCE_DELAY = 100; // 100ms
|
||||
const LOAD_DEBOUNCE_DELAY = 50; // 50ms (уменьшено для ускорения)
|
||||
|
||||
async function loadMultiRelationValues() {
|
||||
// Проверяем, не загружены ли уже данные
|
||||
@@ -625,13 +625,20 @@ async function saveMultiRelation() {
|
||||
to_row_ids: editMultiRelationValues.value
|
||||
};
|
||||
console.log('[saveMultiRelation] POST payload:', payload);
|
||||
const response = await fetch(`/api/tables/${props.column.table_id}/row/${props.rowId}/multirelations`, {
|
||||
console.log('[TableCell] Отправляем запрос на обновление relations для строки:', props.rowId);
|
||||
console.log('[TableCell] Данные запроса:', payload);
|
||||
const response = await fetch(`/api/tables/${props.column.table_id}/row/${props.rowId}/relations`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
console.log('[saveMultiRelation] API response status:', response.status, 'result:', result);
|
||||
console.log('[TableCell] Ответ сервера для строки:', props.rowId, 'статус:', response.status, 'результат:', result);
|
||||
if (response.ok) {
|
||||
console.log('[TableCell] Успешно сохранены теги для строки:', props.rowId);
|
||||
} else {
|
||||
console.error('[TableCell] Ошибка сохранения тегов для строки:', props.rowId, 'статус:', response.status);
|
||||
}
|
||||
editing.value = false;
|
||||
await loadMultiRelationValues();
|
||||
console.log('[saveMultiRelation] emitting update with:', editMultiRelationValues.value);
|
||||
@@ -682,6 +689,9 @@ async function addTag() {
|
||||
]);
|
||||
|
||||
console.log('[addTag] Тег добавлен в выбранные:', editMultiRelationValues.value);
|
||||
|
||||
// Сохраняем изменения, чтобы отправить WebSocket уведомление
|
||||
await saveMultiRelation();
|
||||
} catch (e) {
|
||||
console.error('[addTag] Ошибка при добавлении тега:', e);
|
||||
}
|
||||
@@ -707,6 +717,9 @@ async function deleteTag(tagId) {
|
||||
await loadMultiRelationOptions();
|
||||
|
||||
console.log('[deleteTag] Тег удален:', tagId);
|
||||
|
||||
// Сохраняем изменения, чтобы отправить WebSocket уведомление
|
||||
await saveMultiRelation();
|
||||
} catch (e) {
|
||||
console.error('[deleteTag] Ошибка при удалении тега:', e);
|
||||
}
|
||||
|
||||
@@ -90,13 +90,19 @@
|
||||
:resizable="false"
|
||||
>
|
||||
<template #header>
|
||||
<button class="add-col-btn" @click="addColumn" title="Добавить столбец">
|
||||
<button class="add-col-btn" @click.stop="openAddMenu($event)" title="Добавить">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="11" cy="11" r="10" fill="#f3f4f6" stroke="#b6c6e6"/>
|
||||
<rect x="10" y="5.5" width="2" height="11" rx="1" fill="#4f8cff"/>
|
||||
<rect x="5.5" y="10" width="11" height="2" rx="1" fill="#4f8cff"/>
|
||||
</svg>
|
||||
</button>
|
||||
<teleport to="body">
|
||||
<div v-if="showAddMenu" class="context-menu" :style="addMenuStyle">
|
||||
<button class="menu-item" @click="addColumn">Добавить столбец</button>
|
||||
<button class="menu-item" @click="addRow">Добавить строку</button>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<button class="row-menu" @click.stop="openRowMenu(row, $event)">⋮</button>
|
||||
@@ -118,7 +124,7 @@
|
||||
<!-- <button class="menu-item" @click="addColumn">Добавить столбец</button> -->
|
||||
</div>
|
||||
</teleport>
|
||||
<div v-if="openedColMenuId || openedRowMenuId" class="menu-overlay" @click="closeMenus"></div>
|
||||
<div v-if="openedColMenuId || openedRowMenuId || showAddMenu" class="menu-overlay" @click="closeMenus"></div>
|
||||
<!-- Модалка добавления столбца -->
|
||||
<div v-if="showAddColModal" class="modal-backdrop">
|
||||
<div class="modal add-col-modal">
|
||||
@@ -169,7 +175,9 @@ import axios from 'axios';
|
||||
import { ElSelect, ElOption, ElButton } from 'element-plus';
|
||||
import websocketService from '../../services/websocketService';
|
||||
import cacheService from '../../services/cacheService';
|
||||
import { useTagsWebSocket } from '../../composables/useTagsWebSocket';
|
||||
let unsubscribeFromTableUpdate = null;
|
||||
let unsubscribeFromTagsUpdate = null;
|
||||
|
||||
const { isAdmin } = useAuthContext();
|
||||
const rebuilding = ref(false);
|
||||
@@ -269,6 +277,10 @@ const openedRowMenuId = ref(null);
|
||||
const colMenuStyle = ref('');
|
||||
const rowMenuStyle = ref('');
|
||||
|
||||
// Меню добавления
|
||||
const showAddMenu = ref(false);
|
||||
const addMenuStyle = ref('');
|
||||
|
||||
function closeAddColModal() {
|
||||
showAddColModal.value = false;
|
||||
newColName.value = '';
|
||||
@@ -546,12 +558,77 @@ onMounted(() => {
|
||||
cacheService.clearTableCache(props.tableId);
|
||||
fetchTable();
|
||||
});
|
||||
|
||||
// Подписка на WebSocket обновления тегов
|
||||
const { onTagsUpdate } = useTagsWebSocket();
|
||||
console.log('[UserTableView] Подписываемся на обновления тегов для таблицы:', props.tableId);
|
||||
console.log('[UserTableView] onTagsUpdate функция:', typeof onTagsUpdate);
|
||||
unsubscribeFromTagsUpdate = onTagsUpdate(async (data) => {
|
||||
console.log('[UserTableView] 🔔 ПОЛУЧЕНО СОБЫТИЕ TAGS-UPDATED!');
|
||||
console.log('[UserTableView] Получено событие tags-updated, обновляем данные для таблицы:', props.tableId, data);
|
||||
|
||||
// Если есть информация о конкретной строке, обновляем только её
|
||||
if (data && data.rowId) {
|
||||
console.log('[UserTableView] Точечное обновление для строки:', data.rowId);
|
||||
try {
|
||||
// Очищаем кэш relations только для конкретной строки
|
||||
const tagColumns = columns.value.filter(col =>
|
||||
col.type === 'multirelation' &&
|
||||
col.options?.relatedTableId
|
||||
);
|
||||
|
||||
for (const col of tagColumns) {
|
||||
cacheService.clearRelationsData(data.rowId, col.id);
|
||||
}
|
||||
|
||||
console.log('[UserTableView] Кэш relations очищен для строки, обновляем данные строки:', data.rowId);
|
||||
|
||||
// Обновляем только данные конкретной строки
|
||||
await updateRowData(data.rowId);
|
||||
console.log('[UserTableView] Данные строки обновлены:', data.rowId);
|
||||
} catch (error) {
|
||||
console.error('[UserTableView] Ошибка при точечном обновлении:', error);
|
||||
// Fallback: полная перезагрузка при ошибке
|
||||
await fetchTable();
|
||||
}
|
||||
} else {
|
||||
// Если нет информации о строке, используем старую логику
|
||||
console.log('[UserTableView] Общее обновление тегов');
|
||||
try {
|
||||
// Очищаем кэш relations для всех строк этой таблицы
|
||||
const tableRows = rows.value || [];
|
||||
for (const row of tableRows) {
|
||||
// Находим колонки с мульти-связями (теги)
|
||||
const tagColumns = columns.value.filter(col =>
|
||||
col.type === 'multirelation' &&
|
||||
col.options?.relatedTableId
|
||||
);
|
||||
|
||||
for (const col of tagColumns) {
|
||||
cacheService.clearRelationsData(row.id, col.id);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[UserTableView] Кэш relations очищен, перезагружаем данные таблицы:', props.tableId);
|
||||
await fetchTable();
|
||||
console.log('[UserTableView] Данные таблицы перезагружены:', props.tableId);
|
||||
} catch (error) {
|
||||
console.error('[UserTableView] Ошибка при обновлении после tags-updated:', error);
|
||||
// Fallback: полная перезагрузка при ошибке
|
||||
cacheService.clearTableCache(props.tableId);
|
||||
await fetchTable();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unsubscribeFromTableUpdate) {
|
||||
unsubscribeFromTableUpdate();
|
||||
}
|
||||
if (unsubscribeFromTagsUpdate) {
|
||||
unsubscribeFromTagsUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
// Для редактирования ячеек
|
||||
@@ -619,6 +696,14 @@ function openRowMenu(row, event) {
|
||||
function closeMenus() {
|
||||
openedColMenuId.value = null;
|
||||
openedRowMenuId.value = null;
|
||||
showAddMenu.value = false;
|
||||
}
|
||||
|
||||
function openAddMenu(event) {
|
||||
showAddMenu.value = true;
|
||||
openedColMenuId.value = null;
|
||||
openedRowMenuId.value = null;
|
||||
setMenuPosition(event, addMenuStyle);
|
||||
}
|
||||
function setMenuPosition(event, styleRef) {
|
||||
// Позиционируем меню под кнопкой
|
||||
@@ -676,6 +761,54 @@ async function rebuildIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для точечного обновления данных конкретной строки
|
||||
async function updateRowData(rowId) {
|
||||
const startTime = Date.now();
|
||||
console.log(`[UserTableView] 🔄 Начало обновления данных строки ${rowId}`);
|
||||
|
||||
try {
|
||||
// Находим строку в текущих данных
|
||||
const rowIndex = rows.value.findIndex(row => row.id === rowId);
|
||||
if (rowIndex === -1) {
|
||||
console.log(`[UserTableView] Строка ${rowId} не найдена в текущих данных`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Загружаем relations только для этой строки
|
||||
const tagColumns = columns.value.filter(col =>
|
||||
col.type === 'multirelation' &&
|
||||
col.options?.relatedTableId
|
||||
);
|
||||
|
||||
if (tagColumns.length > 0) {
|
||||
console.log(`[UserTableView] 🔄 Загружаем relations для строки ${rowId} (${tagColumns.length} столбцов)`);
|
||||
|
||||
const relationPromises = tagColumns.map(col =>
|
||||
fetch(`/api/tables/${col.table_id}/row/${rowId}/relations`)
|
||||
.then(res => res.json())
|
||||
.then(relations => {
|
||||
// Сохраняем в кэш
|
||||
cacheService.setRelationsData(rowId, col.id, relations);
|
||||
return { rowId, colId: col.id, relations };
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`[UserTableView] Ошибка загрузки relations для row:${rowId} col:${col.id}:`, error);
|
||||
return { rowId, colId: col.id, relations: [] };
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(relationPromises);
|
||||
console.log(`[UserTableView] ✅ Relations для строки ${rowId} обновлены`);
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
console.log(`[UserTableView] ✅ Завершено обновление строки ${rowId} за ${endTime - startTime}ms`);
|
||||
} catch (error) {
|
||||
console.error(`[UserTableView] ❌ Ошибка при обновлении строки ${rowId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -10,206 +10,40 @@
|
||||
* GitHub: https://github.com/HB3-ACCELERATOR
|
||||
*/
|
||||
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import websocketServiceModule from '@/services/websocketService.js';
|
||||
|
||||
const { websocketService, onTableUpdate } = websocketServiceModule;
|
||||
|
||||
export function useTablesWebSocket() {
|
||||
const ws = ref(null);
|
||||
const isConnected = ref(false);
|
||||
const isConnecting = ref(false); // Добавляем флаг для предотвращения множественных подключений
|
||||
const tableUpdateCallbacks = ref(new Map()); // tableId -> callback
|
||||
const tableRelationsUpdateCallbacks = ref(new Map()); // `${tableId}-${rowId}` -> callback
|
||||
const pingInterval = ref(null); // Интервал для ping сообщений
|
||||
|
||||
function connect() {
|
||||
if (ws.value && ws.value.readyState === WebSocket.OPEN) {
|
||||
console.log('[TablesWebSocket] Уже подключены, пропускаем');
|
||||
return; // Уже подключены
|
||||
}
|
||||
|
||||
if (isConnecting.value) {
|
||||
console.log('[TablesWebSocket] Уже пытаемся подключиться, пропускаем');
|
||||
return; // Уже пытаемся подключиться
|
||||
}
|
||||
|
||||
isConnecting.value = true;
|
||||
|
||||
// Определяем правильный URL для WebSocket
|
||||
let wsUrl;
|
||||
if (import.meta.env.DEV) {
|
||||
// В режиме разработки используем прокси через Vite
|
||||
wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/ws`;
|
||||
} else {
|
||||
// В продакшене используем тот же хост
|
||||
wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/ws`;
|
||||
}
|
||||
|
||||
console.log('[TablesWebSocket] Подключение к:', wsUrl);
|
||||
console.log('[TablesWebSocket] Текущий хост:', window.location.host);
|
||||
console.log('[TablesWebSocket] Протокол:', window.location.protocol);
|
||||
|
||||
try {
|
||||
ws.value = new WebSocket(wsUrl);
|
||||
} catch (error) {
|
||||
console.error('[TablesWebSocket] Ошибка создания WebSocket:', error);
|
||||
isConnecting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ws.value.onopen = () => {
|
||||
console.log('[TablesWebSocket] Соединение установлено');
|
||||
isConnected.value = true;
|
||||
isConnecting.value = false;
|
||||
|
||||
// Запускаем ping каждые 30 секунд
|
||||
pingInterval.value = setInterval(() => {
|
||||
if (ws.value && ws.value.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
ws.value.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
|
||||
} catch (error) {
|
||||
console.error('[TablesWebSocket] Ошибка отправки ping:', error);
|
||||
}
|
||||
}
|
||||
}, 30000);
|
||||
};
|
||||
|
||||
ws.value.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('[TablesWebSocket] Получено сообщение:', data);
|
||||
|
||||
// Обрабатываем pong ответ
|
||||
if (data.type === 'pong') {
|
||||
console.log('[TablesWebSocket] Получен pong ответ');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'table-updated') {
|
||||
const callbacks = tableUpdateCallbacks.value.get(data.tableId);
|
||||
if (callbacks) {
|
||||
callbacks.forEach(callback => callback(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === 'table-relations-updated') {
|
||||
const key = `${data.tableId}-${data.rowId}`;
|
||||
const callbacks = tableRelationsUpdateCallbacks.value.get(key);
|
||||
if (callbacks) {
|
||||
callbacks.forEach(callback => callback(data));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TablesWebSocket] Ошибка обработки сообщения:', error);
|
||||
}
|
||||
};
|
||||
|
||||
ws.value.onclose = (event) => {
|
||||
console.log('[TablesWebSocket] Соединение закрыто', {
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
wasClean: event.wasClean
|
||||
});
|
||||
isConnected.value = false;
|
||||
isConnecting.value = false;
|
||||
|
||||
// Останавливаем ping интервал
|
||||
if (pingInterval.value) {
|
||||
clearInterval(pingInterval.value);
|
||||
pingInterval.value = null;
|
||||
}
|
||||
|
||||
// Переподключение только если это не было намеренное закрытие
|
||||
if (event.code !== 1000) {
|
||||
setTimeout(() => {
|
||||
if (!isConnected.value && !isConnecting.value) {
|
||||
console.log('[TablesWebSocket] Попытка переподключения...');
|
||||
connect();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
ws.value.onerror = (error) => {
|
||||
console.error('[TablesWebSocket] Ошибка соединения:', error);
|
||||
console.error('[TablesWebSocket] WebSocket readyState:', ws.value?.readyState);
|
||||
isConnected.value = false;
|
||||
isConnecting.value = false;
|
||||
};
|
||||
}
|
||||
|
||||
// Подписка на обновления таблиц
|
||||
function subscribeToTableUpdates(tableId, callback) {
|
||||
if (!tableUpdateCallbacks.value.has(tableId)) {
|
||||
tableUpdateCallbacks.value.set(tableId, []);
|
||||
}
|
||||
tableUpdateCallbacks.value.get(tableId).push(callback);
|
||||
|
||||
// Возвращаем функцию для отписки
|
||||
return () => {
|
||||
const callbacks = tableUpdateCallbacks.value.get(tableId);
|
||||
if (callbacks) {
|
||||
const index = callbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
if (callbacks.length === 0) {
|
||||
tableUpdateCallbacks.value.delete(tableId);
|
||||
}
|
||||
}
|
||||
};
|
||||
return onTableUpdate(tableId, callback);
|
||||
}
|
||||
|
||||
// Подписка на обновления связей (relations)
|
||||
function subscribeToTableRelationsUpdates(tableId, rowId, callback) {
|
||||
const key = `${tableId}-${rowId}`;
|
||||
if (!tableRelationsUpdateCallbacks.value.has(key)) {
|
||||
tableRelationsUpdateCallbacks.value.set(key, []);
|
||||
}
|
||||
tableRelationsUpdateCallbacks.value.get(key).push(callback);
|
||||
|
||||
// Возвращаем функцию для отписки
|
||||
return () => {
|
||||
const callbacks = tableRelationsUpdateCallbacks.value.get(key);
|
||||
if (callbacks) {
|
||||
const index = callbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
if (callbacks.length === 0) {
|
||||
tableRelationsUpdateCallbacks.value.delete(key);
|
||||
}
|
||||
// Используем глобальный обработчик и фильтруем по tableId/rowId
|
||||
const handler = (data) => {
|
||||
if (data.tableId === tableId && data.rowId === rowId) {
|
||||
callback(data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (ws.value) {
|
||||
// Останавливаем ping интервал
|
||||
if (pingInterval.value) {
|
||||
clearInterval(pingInterval.value);
|
||||
pingInterval.value = null;
|
||||
}
|
||||
|
||||
// Корректно закрываем соединение
|
||||
if (ws.value.readyState === WebSocket.OPEN) {
|
||||
ws.value.close(1000, 'Manual disconnect');
|
||||
}
|
||||
ws.value = null;
|
||||
}
|
||||
isConnected.value = false;
|
||||
isConnecting.value = false;
|
||||
websocketService.on('table-relations-updated', handler);
|
||||
// Возвращаем функцию для отписки
|
||||
return () => websocketService.off('table-relations-updated', handler);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
connect();
|
||||
// Соединение управляется websocketService, ничего не делаем
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
disconnect();
|
||||
// Соединение управляется websocketService, ничего не делаем
|
||||
});
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
connect,
|
||||
disconnect,
|
||||
subscribeToTableUpdates,
|
||||
subscribeToTableRelationsUpdates
|
||||
subscribeToTableRelationsUpdates,
|
||||
};
|
||||
}
|
||||
@@ -16,44 +16,78 @@ import websocketServiceModule from '../services/websocketService';
|
||||
const { websocketService } = websocketServiceModule;
|
||||
|
||||
export function useTagsWebSocket() {
|
||||
console.log('🏷️ [useTagsWebSocket] Композабл создан');
|
||||
const tagsUpdateCallbacks = ref([]);
|
||||
let debounceTimer = null;
|
||||
const DEBOUNCE_DELAY = 1000; // 1 секунда
|
||||
const isSubscribed = ref(false);
|
||||
|
||||
function onTagsUpdate(callback) {
|
||||
console.log('🏷️ [useTagsWebSocket] Регистрация колбэка');
|
||||
|
||||
// Проверяем, не зарегистрирован ли уже этот колбэк
|
||||
if (tagsUpdateCallbacks.value.includes(callback)) {
|
||||
console.log('🏷️ [useTagsWebSocket] Колбэк уже зарегистрирован, пропускаем');
|
||||
return () => {
|
||||
const index = tagsUpdateCallbacks.value.indexOf(callback);
|
||||
if (index > -1) {
|
||||
tagsUpdateCallbacks.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
tagsUpdateCallbacks.value.push(callback);
|
||||
console.log('🏷️ [useTagsWebSocket] Количество колбэков:', tagsUpdateCallbacks.value.length);
|
||||
|
||||
// Возвращаем функцию для отписки
|
||||
return () => {
|
||||
console.log('🏷️ [useTagsWebSocket] Отписка колбэка');
|
||||
const index = tagsUpdateCallbacks.value.indexOf(callback);
|
||||
if (index > -1) {
|
||||
tagsUpdateCallbacks.value.splice(index, 1);
|
||||
console.log('🏷️ [useTagsWebSocket] Колбэк удален, осталось:', tagsUpdateCallbacks.value.length);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function handleTagsUpdate(data) {
|
||||
console.log('🏷️ [useTagsWebSocket] Получено уведомление об обновлении тегов:', data);
|
||||
console.log('🏷️ [useTagsWebSocket] Количество активных колбэков:', tagsUpdateCallbacks.value.length);
|
||||
|
||||
// Вызываем все зарегистрированные колбэки
|
||||
tagsUpdateCallbacks.value.forEach(callback => {
|
||||
tagsUpdateCallbacks.value.forEach((callback, index) => {
|
||||
try {
|
||||
console.log('🏷️ [useTagsWebSocket] Выполняем колбэк #', index);
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error('🏷️ [useTagsWebSocket] Ошибка в колбэке:', error);
|
||||
console.error('🏷️ [useTagsWebSocket] Ошибка в колбэке #', index, ':', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('🏷️ [useTagsWebSocket] onMounted вызван');
|
||||
|
||||
// Проверяем, не подписаны ли уже
|
||||
if (isSubscribed.value) {
|
||||
console.log('🏷️ [useTagsWebSocket] Уже подписаны, пропускаем');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🏷️ [useTagsWebSocket] Подписываемся на tags-updated');
|
||||
websocketService.on('tags-updated', handleTagsUpdate);
|
||||
isSubscribed.value = true;
|
||||
console.log('🏷️ [useTagsWebSocket] Подписка завершена');
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
console.log('🏷️ [useTagsWebSocket] onUnmounted вызван');
|
||||
if (isSubscribed.value) {
|
||||
websocketService.off('tags-updated', handleTagsUpdate);
|
||||
isSubscribed.value = false;
|
||||
console.log('🏷️ [useTagsWebSocket] Отписка завершена');
|
||||
}
|
||||
websocketService.off('tags-updated', handleTagsUpdate);
|
||||
// Очищаем все колбэки
|
||||
tagsUpdateCallbacks.value = [];
|
||||
console.log('🏷️ [useTagsWebSocket] Колбэки очищены');
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -57,20 +57,20 @@ export default {
|
||||
},
|
||||
// --- Работа с тегами пользователя ---
|
||||
async addTagsToContact(contactId, tagIds) {
|
||||
// PATCH /users/:id/tags { tags: [...] }
|
||||
const res = await api.patch(`/users/${contactId}/tags`, { tags: tagIds });
|
||||
return res.data;
|
||||
},
|
||||
async getContactTags(contactId) {
|
||||
// GET /users/:id/tags
|
||||
const res = await api.get(`/users/${contactId}/tags`);
|
||||
return res.data.tags || [];
|
||||
},
|
||||
async removeTagFromContact(contactId, tagId) {
|
||||
// DELETE /users/:id/tags/:tagId
|
||||
const res = await api.delete(`/users/${contactId}/tags/${tagId}`);
|
||||
return res.data;
|
||||
}
|
||||
// PATCH /api/tags/user/:id { tags: [...] }
|
||||
const res = await api.patch(`/tags/user/${contactId}`, { tags: tagIds });
|
||||
return res.data;
|
||||
},
|
||||
async getContactTags(contactId) {
|
||||
// GET /api/tags/user/:id
|
||||
const res = await api.get(`/tags/user/${contactId}`);
|
||||
return res.data.tags || [];
|
||||
},
|
||||
async removeTagFromContact(contactId, tagId) {
|
||||
// DELETE /api/tags/user/:id/tag/:tagId
|
||||
const res = await api.delete(`/tags/user/${contactId}/tag/${tagId}`);
|
||||
return res.data;
|
||||
}
|
||||
};
|
||||
|
||||
export async function getContacts() {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
class WebSocketService {
|
||||
constructor() {
|
||||
console.log('🔌 [WebSocket] Конструктор вызван');
|
||||
this.ws = null;
|
||||
this.isConnected = false;
|
||||
this.reconnectAttempts = 0;
|
||||
@@ -23,10 +24,12 @@ class WebSocketService {
|
||||
this.reconnectDelay = 1000; // 1 секунда
|
||||
this.listeners = new Map();
|
||||
this.userId = null;
|
||||
console.log('🔌 [WebSocket] Конструктор завершен');
|
||||
}
|
||||
|
||||
// Подключение к WebSocket серверу
|
||||
connect(userId = null) {
|
||||
console.log('🔌 [WebSocket] Попытка подключения, userId:', userId);
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
console.log('🔌 [WebSocket] Уже подключен');
|
||||
return;
|
||||
@@ -37,11 +40,11 @@ class WebSocketService {
|
||||
try {
|
||||
// Определяем WebSocket URL
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
// В Docker окружении backend работает на порту 8000
|
||||
const backendHost = window.location.hostname + ':8000';
|
||||
const wsUrl = `${protocol}//${backendHost}/ws`;
|
||||
// В Docker окружении используем тот же хост, что и для HTTP
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws`;
|
||||
|
||||
console.log('🔌 [WebSocket] Подключение к:', wsUrl);
|
||||
console.log('🔌 [WebSocket] Текущий хост:', window.location.host);
|
||||
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
@@ -61,10 +64,35 @@ class WebSocketService {
|
||||
this.emit('connected');
|
||||
};
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
console.log('🔌 [WebSocket] Соединение закрыто:', event.code, event.reason);
|
||||
this.isConnected = false;
|
||||
this.emit('disconnected', event);
|
||||
|
||||
// Попытка переподключения
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
console.log(`🔄 [WebSocket] Попытка переподключения ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
|
||||
|
||||
setTimeout(() => {
|
||||
this.connect(this.userId);
|
||||
}, this.reconnectDelay * this.reconnectAttempts);
|
||||
} else {
|
||||
console.error('❌ [WebSocket] Превышено максимальное количество попыток переподключения');
|
||||
this.emit('reconnect-failed');
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('❌ [WebSocket] Ошибка соединения:', error);
|
||||
this.emit('error', error);
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('📨 [WebSocket] Получено сообщение:', data);
|
||||
console.log('📨 [WebSocket] Тип сообщения:', data.type);
|
||||
this.handleMessage(data);
|
||||
} catch (error) {
|
||||
console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
|
||||
@@ -139,8 +167,9 @@ class WebSocketService {
|
||||
break;
|
||||
|
||||
case 'tags-updated':
|
||||
console.log('🔔 [websocketService] Получено сообщение tags-updated');
|
||||
this.emit('tags-updated');
|
||||
console.log('🔔 [websocketService] Получено сообщение tags-updated:', data);
|
||||
console.log('🔔 [websocketService] Количество слушателей tags-updated:', this.listeners.get('tags-updated')?.length || 0);
|
||||
this.emit('tags-updated', data);
|
||||
break;
|
||||
|
||||
case 'table-updated':
|
||||
@@ -158,10 +187,12 @@ class WebSocketService {
|
||||
|
||||
// Подписка на события
|
||||
on(event, callback) {
|
||||
console.log('🔌 [WebSocket] Подписка на событие:', event);
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, []);
|
||||
}
|
||||
this.listeners.get(event).push(callback);
|
||||
console.log('🔌 [WebSocket] Количество слушателей для', event, ':', this.listeners.get(event).length);
|
||||
}
|
||||
|
||||
// Отписка от событий
|
||||
@@ -177,14 +208,20 @@ class WebSocketService {
|
||||
|
||||
// Эмиссия событий
|
||||
emit(event, data) {
|
||||
console.log('🔌 [WebSocket] Эмиссия события:', event, 'с данными:', data);
|
||||
if (this.listeners.has(event)) {
|
||||
this.listeners.get(event).forEach(callback => {
|
||||
const callbacks = this.listeners.get(event);
|
||||
console.log('🔌 [WebSocket] Количество колбэков для', event, ':', callbacks.length);
|
||||
callbacks.forEach((callback, index) => {
|
||||
try {
|
||||
console.log('🔌 [WebSocket] Выполняем колбэк #', index, 'для события', event);
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error(`❌ [WebSocket] Ошибка в обработчике события ${event}:`, error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('🔌 [WebSocket] Нет слушателей для события:', event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +249,7 @@ class WebSocketService {
|
||||
|
||||
// Создаем единственный экземпляр
|
||||
const websocketService = new WebSocketService();
|
||||
console.log('🔌 [WebSocket] Сервис создан');
|
||||
|
||||
// Подписчики на обновления таблиц: tableId -> [callback]
|
||||
const tableUpdateSubscribers = {};
|
||||
@@ -230,4 +268,18 @@ function onTableUpdate(tableId, callback) {
|
||||
export default {
|
||||
websocketService,
|
||||
onTableUpdate,
|
||||
};
|
||||
};
|
||||
console.log('🔌 [WebSocket] Экспорт завершен');
|
||||
|
||||
// Автоматически подключаемся при загрузке модуля
|
||||
console.log('🔌 [WebSocket] Автоматическое подключение...');
|
||||
setTimeout(() => {
|
||||
console.log('🔌 [WebSocket] Подключаемся через 1 секунду...');
|
||||
websocketService.connect();
|
||||
}, 1000);
|
||||
|
||||
// Добавляем периодическую проверку состояния соединения
|
||||
setInterval(() => {
|
||||
const status = websocketService.getStatus();
|
||||
console.log('🔌 [WebSocket] Статус соединения:', status);
|
||||
}, 10000); // Проверяем каждые 10 секунд
|
||||
Reference in New Issue
Block a user