feat: новая функция

This commit is contained in:
2025-10-13 22:41:49 +03:00
parent 34666b44d8
commit 0e028bc722
83 changed files with 1595 additions and 6093 deletions

View File

@@ -19,12 +19,11 @@ const authType = ref(null);
const userId = ref(null);
const address = ref(null);
const telegramId = ref(null);
const isAdmin = ref(false);
const email = ref(null);
const processedGuestIds = ref([]);
const identities = ref([]);
const tokenBalances = ref([]);
const userAccessLevel = ref({ level: 'user', tokenCount: 0, hasAccess: false });
const userAccessLevel = ref({ level: 'guest', tokenCount: 0, hasAccess: false });
// Функция для обновления списка идентификаторов
const updateIdentities = async () => {
@@ -134,8 +133,8 @@ const updateAuth = async ({
userId: newUserId,
address: newAddress,
telegramId: newTelegramId,
isAdmin: newIsAdmin,
email: newEmail,
userAccessLevel: newUserAccessLevel,
}) => {
const wasAuthenticated = isAuthenticated.value;
const previousUserId = userId.value;
@@ -146,8 +145,8 @@ const updateAuth = async ({
newUserId,
newAddress,
newTelegramId,
newIsAdmin,
newEmail,
newUserAccessLevel,
});
// Убедимся, что переменные являются реактивными
@@ -156,8 +155,31 @@ const updateAuth = async ({
userId.value = newUserId || null;
address.value = newAddress || null;
telegramId.value = newTelegramId || null;
isAdmin.value = newIsAdmin === true;
email.value = newEmail || null;
// Обновляем userAccessLevel только если он изменился
if (newUserAccessLevel) {
// Используем userAccessLevel из ответа сервера
console.log('[updateAuth] Setting userAccessLevel from server:', JSON.stringify(newUserAccessLevel, null, 2));
userAccessLevel.value = newUserAccessLevel;
} else if (authenticated && newAddress) {
// Если userAccessLevel не передан, но пользователь аутентифицирован, запрашиваем его
try {
const accessLevel = await checkUserAccessLevel(newAddress);
if (accessLevel && accessLevel.level !== userAccessLevel.value.level) {
console.log('[updateAuth] Updating userAccessLevel from API:', accessLevel);
userAccessLevel.value = accessLevel;
}
} catch (error) {
console.error('Error updating userAccessLevel in updateAuth:', error);
}
} else if (!authenticated) {
// Сбрасываем userAccessLevel для неавторизованных пользователей
if (userAccessLevel.value.level !== 'guest') {
console.log('[updateAuth] Resetting userAccessLevel to guest');
userAccessLevel.value = { level: 'guest', tokenCount: 0, hasAccess: false };
}
}
// Кэшируем данные аутентификации
localStorage.setItem(
@@ -168,7 +190,6 @@ const updateAuth = async ({
userId: newUserId,
address: newAddress,
telegramId: newTelegramId,
isAdmin: newIsAdmin,
email: newEmail,
})
);
@@ -204,8 +225,34 @@ const updateAuth = async ({
address: address.value,
telegramId: telegramId.value,
email: email.value,
isAdmin: isAdmin.value,
});
// Уведомляем все компоненты об изменении состояния аутентификации
// Только если состояние действительно изменилось
if (wasAuthenticated !== isAuthenticated.value || previousUserId !== newUserId) {
// Централизованная очистка данных при отключении
if (!isAuthenticated.value && wasAuthenticated) {
console.log('[useAuth] User logged out, clearing application data');
// Очищаем глобальные данные приложения
window.dispatchEvent(new CustomEvent('clear-application-data'));
}
// Централизованное обновление данных при подключении
if (isAuthenticated.value && !wasAuthenticated) {
console.log('[useAuth] User logged in, refreshing application data');
window.dispatchEvent(new CustomEvent('refresh-application-data'));
}
window.dispatchEvent(new CustomEvent('auth-state-changed', {
detail: {
authenticated: isAuthenticated.value,
authType: authType.value,
userId: userId.value,
address: address.value,
userAccessLevel: userAccessLevel.value
}
}));
}
// Если пользователь только что аутентифицировался или сменил аккаунт,
// пробуем связать сообщения
@@ -314,22 +361,34 @@ const linkMessages = async () => {
const checkAuth = async () => {
try {
const response = await axios.get('/auth/check');
console.log('Auth check response:', response.data);
console.log('Auth check response:', JSON.stringify(response.data, null, 2));
const wasAuthenticated = isAuthenticated.value;
const previousUserId = userId.value;
const previousAuthType = authType.value;
// Обновляем данные авторизации через updateAuth вместо прямого изменения
await updateAuth({
authenticated: response.data.authenticated,
authType: response.data.authType,
userId: response.data.userId,
address: response.data.address,
telegramId: response.data.telegramId,
email: response.data.email,
isAdmin: response.data.isAdmin,
});
// Проверяем, изменилось ли состояние аутентификации
const authChanged = (
wasAuthenticated !== response.data.authenticated ||
previousUserId !== response.data.userId ||
previousAuthType !== response.data.authType
);
if (authChanged) {
console.log('[checkAuth] Authentication state changed, updating...');
// Обновляем данные авторизации через updateAuth вместо прямого изменения
await updateAuth({
authenticated: response.data.authenticated,
authType: response.data.authType,
userId: response.data.userId,
address: response.data.address,
telegramId: response.data.telegramId,
email: response.data.email,
userAccessLevel: response.data.userAccessLevel, // Добавляем userAccessLevel из ответа сервера
});
} else {
console.log('[checkAuth] No authentication changes, skipping update');
}
// Если пользователь аутентифицирован, обновляем список идентификаторов и связываем сообщения
if (response.data.authenticated) {
@@ -385,7 +444,6 @@ const disconnect = async () => {
address: null,
telegramId: null,
email: null,
isAdmin: false,
});
// Обновляем отображение отключенного состояния
@@ -399,7 +457,6 @@ const disconnect = async () => {
localStorage.removeItem('isAuthenticated');
localStorage.removeItem('userId');
localStorage.removeItem('address');
localStorage.removeItem('isAdmin');
localStorage.removeItem('guestId');
localStorage.removeItem('guestMessages');
localStorage.removeItem('telegramId');
@@ -507,7 +564,6 @@ const authApi = {
authType,
userId,
address,
isAdmin,
telegramId,
email,
identities,

View File

@@ -512,12 +512,29 @@ export function useChat(auth) {
// Подключаем WebSocket если пользователь уже аутентифицирован
setupChatWebSocket();
// Логика обновления данных централизована в useAuth.js
});
onUnmounted(() => {
cleanupWebSocket();
});
// Подписываемся на централизованные события очистки и обновления данных
window.addEventListener('clear-application-data', () => {
console.log('[useChat] Clearing chat data');
// Очищаем данные при выходе из системы
messages.value = [];
newMessages.value = [];
readUserIds.value = [];
lastReadMessageDate.value = {};
});
window.addEventListener('refresh-application-data', () => {
console.log('[useChat] Refreshing chat data');
loadMessages({ initial: true }); // Обновляем данные при входе в систему
});
return {
messages,
newMessage, // v-model

View File

@@ -63,21 +63,42 @@ export function useContactsAndMessagesWebSocket() {
}
function updateNewContacts() {
console.log('[useContactsWebSocket] updateNewContacts called');
console.log('[useContactsWebSocket] contacts:', contacts.value.length);
console.log('[useContactsWebSocket] readContacts:', readContacts.value);
if (!contacts.value.length) {
newContacts.value = [];
console.log('[useContactsWebSocket] No contacts, newContacts cleared');
return;
}
newContacts.value = contacts.value.filter(c => !readContacts.value.includes(c.id));
const beforeCount = newContacts.value.length;
newContacts.value = contacts.value.filter(c => !readContacts.value.includes(String(c.id)));
console.log('[useContactsWebSocket] newContacts updated:', beforeCount, '->', newContacts.value.length);
}
async function markContactAsRead(contactId) {
try {
await axios.post('/users/mark-contact-read', { contactId });
if (!readContacts.value.includes(contactId)) {
readContacts.value.push(contactId);
console.log('[useContactsWebSocket] Marking contact as read:', contactId);
const response = await axios.post('/users/mark-contact-read', { contactId });
console.log('[useContactsWebSocket] Mark contact response:', response.data);
// Приводим contactId к строке для совместимости с readContacts
const contactIdStr = String(contactId);
console.log('[useContactsWebSocket] Converting contactId to string:', contactId, '->', contactIdStr);
if (!readContacts.value.includes(contactIdStr)) {
readContacts.value.push(contactIdStr);
updateNewContacts();
console.log('[useContactsWebSocket] Contact marked as read, updated newContacts');
} else {
console.log('[useContactsWebSocket] Contact already marked as read:', contactIdStr);
}
} catch (e) {}
} catch (e) {
console.error('[useContactsWebSocket] Error marking contact as read:', e);
console.error('[useContactsWebSocket] Error response:', e.response?.data);
}
}
async function fetchReadStatus() {
@@ -139,17 +160,41 @@ export function useContactsAndMessagesWebSocket() {
};
}
function clearContactsData() {
contacts.value = [];
messages.value = [];
readContacts.value = [];
newContacts.value = [];
newMessages.value = [];
readUserIds.value = [];
lastReadMessageDate.value = {};
}
// Централизованная подписка на изменения аутентификации
onMounted(async () => {
await fetchContactsReadStatus();
await fetchContacts();
await fetchReadStatus();
await fetchMessages();
setupWebSocket();
// Подписываемся на централизованные события очистки и обновления данных
window.addEventListener('clear-application-data', () => {
console.log('[useContactsWebSocket] Clearing contacts data');
clearContactsData(); // Очищаем данные при выходе из системы
});
window.addEventListener('refresh-application-data', () => {
console.log('[useContactsWebSocket] Refreshing contacts data');
fetchContacts(); // Обновляем данные при входе в систему
});
});
onUnmounted(() => {
if (ws) ws.close();
});
// Логика обновления данных централизована в useAuth.js через события
return {
contacts,
newContacts,
@@ -158,6 +203,8 @@ export function useContactsAndMessagesWebSocket() {
markContactAsRead,
markMessagesAsRead,
markMessagesAsReadForUser,
readUserIds
readUserIds,
fetchContacts,
clearContactsData
};
}

View File

@@ -12,69 +12,77 @@
import { computed } from 'vue';
import { useAuthContext } from './useAuth';
import { PERMISSIONS, ROLES, hasPermission as checkPermission, getRoleDescription } from '/app/shared/permissions';
/**
* Composable для работы с правами доступа
* Использует единую матрицу прав из shared/permissions.js
* @returns {Object} - Объект с функциями для проверки прав доступа
*/
export function usePermissions() {
const { userAccessLevel, isAdmin } = useAuthContext();
const { userAccessLevel, isAuthenticated } = useAuthContext();
/**
* Проверяет, может ли пользователь только читать данные
* Текущая роль пользователя
*/
const canRead = computed(() => {
return (userAccessLevel.value && userAccessLevel.value.hasAccess) || isAdmin.value;
const currentRole = computed(() => {
if (!isAuthenticated.value) {
return ROLES.GUEST; // Неавторизованный
}
// Если userAccessLevel не определен, возвращаем USER (авторизованный пользователь)
return userAccessLevel.value?.level || ROLES.USER;
});
/**
* Проверяет, может ли пользователь редактировать данные
*/
const canEdit = computed(() => {
return userAccessLevel.value && userAccessLevel.value.level === 'editor';
});
/**
* Проверяет, может ли пользователь удалять данные
*/
const canDelete = computed(() => {
return userAccessLevel.value && userAccessLevel.value.level === 'editor';
});
/**
* Проверяет, может ли пользователь управлять настройками системы
*/
const canManageSettings = computed(() => {
return userAccessLevel.value && userAccessLevel.value.level === 'editor';
});
/**
* Получает текущий уровень доступа
*/
const currentLevel = computed(() => {
return userAccessLevel.value ? userAccessLevel.value.level : 'user';
});
/**
* Получает количество токенов пользователя
* Количество токенов пользователя
*/
const tokenCount = computed(() => {
return userAccessLevel.value ? userAccessLevel.value.tokenCount : 0;
return userAccessLevel.value?.tokenCount || 0;
});
/**
* Универсальная проверка любого права
* @param {string} permission - Право для проверки
* @returns {boolean}
*/
const hasPermission = (permission) => {
return checkPermission(currentRole.value, permission);
};
// ========================================================================
// Computed проверки для частого использования
// ========================================================================
// Просмотр данных
const canViewData = computed(() => hasPermission(PERMISSIONS.VIEW_DATA));
const canViewContacts = computed(() => hasPermission(PERMISSIONS.VIEW_CONTACTS));
const canViewCrm = computed(() => hasPermission(PERMISSIONS.VIEW_CRM));
// Редактирование и удаление
const canEditData = computed(() => hasPermission(PERMISSIONS.EDIT_USER_DATA));
const canEditContacts = computed(() => hasPermission(PERMISSIONS.EDIT_CONTACTS));
const canDeleteData = computed(() => hasPermission(PERMISSIONS.DELETE_USER_DATA));
const canDeleteMessages = computed(() => hasPermission(PERMISSIONS.DELETE_MESSAGES));
// Коммуникация
const canSendToUsers = computed(() => hasPermission(PERMISSIONS.SEND_TO_USERS));
const canChatWithAdmins = computed(() => hasPermission(PERMISSIONS.CHAT_WITH_ADMINS));
const canGenerateAI = computed(() => hasPermission(PERMISSIONS.GENERATE_AI_REPLIES));
const canBroadcast = computed(() => hasPermission(PERMISSIONS.BROADCAST));
// Управление
const canManageTags = computed(() => hasPermission(PERMISSIONS.MANAGE_TAGS));
const canBlockUsers = computed(() => hasPermission(PERMISSIONS.BLOCK_USERS));
const canManageSettings = computed(() => hasPermission(PERMISSIONS.MANAGE_SETTINGS));
const currentLevel = computed(() => currentRole.value);
/**
* Получает описание текущего уровня доступа
*/
const getLevelDescription = (level) => {
switch (level) {
case 'readonly':
return 'Только чтение';
case 'editor':
return 'Редактор';
case 'user':
default:
return 'Пользователь';
}
return getRoleDescription(level);
};
/**
@@ -82,24 +90,56 @@ export function usePermissions() {
*/
const getLevelClass = (level) => {
switch (level) {
case 'readonly':
case ROLES.READONLY:
return 'access-readonly';
case 'editor':
case ROLES.EDITOR:
return 'access-editor';
case 'user':
case ROLES.USER:
return 'access-user';
case ROLES.GUEST:
return 'access-guest';
default:
return 'access-user';
}
};
return {
canRead,
canEdit,
canDelete,
canManageSettings,
currentLevel,
// Главная функция
hasPermission,
// Информация о роли
currentRole,
currentLevel, // alias для совместимости
tokenCount,
// Просмотр
canViewData,
canViewContacts,
canViewCrm,
// Редактирование
canEditData,
canEditContacts,
canDeleteData,
canDeleteMessages,
// Коммуникация
canSendToUsers,
canChatWithAdmins,
canGenerateAI,
canBroadcast,
// Управление
canManageTags,
canBlockUsers,
canManageSettings,
// Утилиты
getLevelDescription,
getLevelClass
getLevelClass,
// Константы
ROLES,
PERMISSIONS
};
}