ваше сообщение коммита

This commit is contained in:
2025-10-06 12:15:09 +03:00
parent 6d15c5921a
commit 36d0968631
13 changed files with 1247 additions and 99 deletions

View File

@@ -0,0 +1,169 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
<template>
<BaseLayout>
<div class="admin-chat-header">
<span>Приватный чат</span>
<button class="close-btn" @click="goBack">×</button>
</div>
<div v-if="isLoadingMessages" class="loading-container">
<div class="loading">Загрузка сообщений...</div>
</div>
<div v-else class="chat-container">
<ChatInterface
:messages="messages"
:attachments="chatAttachments"
:newMessage="chatNewMessage"
:isLoading="isLoadingMessages"
:isAdmin="true"
@send-message="handleSendMessage"
@update:newMessage="val => chatNewMessage = val"
@update:attachments="val => chatAttachments = val"
@load-more="loadMessages"
/>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import BaseLayout from '../components/BaseLayout.vue';
import ChatInterface from '../components/ChatInterface.vue';
import adminChatService from '../services/adminChatService.js';
const route = useRoute();
const router = useRouter();
const adminId = computed(() => route.params.adminId);
const messages = ref([]);
const chatAttachments = ref([]);
const chatNewMessage = ref('');
const isLoadingMessages = ref(false);
async function loadMessages() {
if (!adminId.value) return;
try {
isLoadingMessages.value = true;
console.log('[AdminChatView] Загружаем сообщения для админа:', adminId.value);
const response = await adminChatService.getMessages(adminId.value);
console.log('[AdminChatView] Получен ответ:', response);
messages.value = response?.messages || [];
console.log('[AdminChatView] Загружено сообщений:', messages.value.length);
} catch (error) {
console.error('[AdminChatView] Ошибка загрузки сообщений:', error);
messages.value = [];
} finally {
isLoadingMessages.value = false;
}
}
async function handleSendMessage({ message, attachments }) {
if (!message.trim() || !adminId.value) return;
try {
console.log('[AdminChatView] Отправляем сообщение:', message, 'админу:', adminId.value);
await adminChatService.sendMessage(adminId.value, message, attachments);
// Очищаем поле ввода
chatNewMessage.value = '';
chatAttachments.value = [];
// Перезагружаем сообщения
await loadMessages();
console.log('[AdminChatView] Сообщение отправлено успешно');
} catch (error) {
console.error('[AdminChatView] Ошибка отправки сообщения:', error);
}
}
function goBack() {
if (window.history.length > 1) {
router.back();
} else {
router.push({ name: 'crm' });
}
}
onMounted(() => {
loadMessages();
});
</script>
<style scoped>
.admin-chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
font-size: 1.2rem;
font-weight: bold;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.close-btn:hover {
background-color: #e0e0e0;
}
.loading-container {
height: 200px;
position: relative;
}
.chat-container {
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
}
.loading-container {
padding: 2rem;
text-align: center;
}
.loading {
color: #888;
font-size: 1.1rem;
}
/* Стили для ChatInterface */
:deep(.chat-messages) {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
:deep(.chat-input) {
border-top: 1px solid #ddd;
padding: 1rem;
background: #f9f9f9;
}
</style>

View File

@@ -16,7 +16,7 @@
<span>Контакты</span>
<span v-if="newContacts.length" class="badge">+{{ newContacts.length }}</span>
</div>
<ContactTable v-if="canRead" :contacts="contacts" :new-contacts="newContacts" :new-messages="newMessages" @markNewAsRead="markContactsAsRead"
<ContactTable v-if="canRead" :contacts="contacts" :new-contacts="newContacts" :new-messages="newMessages" @markNewAsRead="markMessagesAsRead"
:markMessagesAsReadForUser="markMessagesAsReadForUser" :markContactAsRead="markContactAsRead" @close="goBack" />
<!-- Таблица-заглушка для обычных пользователей -->
@@ -86,7 +86,7 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../components/BaseLayout.vue';
import ContactTable from '../components/ContactTable.vue';
@@ -96,12 +96,23 @@ import { usePermissions } from '@/composables/usePermissions';
const {
contacts, newContacts, newMessages,
markContactsAsRead, markMessagesAsReadForUser, markContactAsRead
markMessagesAsRead, markMessagesAsReadForUser, markContactAsRead
} = useContactsAndMessagesWebSocket();
const router = useRouter();
const { isAdmin } = useAuthContext();
const auth = useAuthContext();
const { canRead } = usePermissions();
// Отладочная информация о правах доступа
onMounted(() => {
console.log('[ContactsView] Permissions debug:', {
canRead: canRead.value,
isAdmin: auth.isAdmin?.value,
userAccessLevel: auth.userAccessLevel?.value,
userId: auth.userId?.value,
address: auth.address?.value
});
});
function goBack() {
if (window.history.length > 1) {
router.back();

View File

@@ -0,0 +1,272 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
<template>
<BaseLayout>
<div class="personal-messages-header">
<span>Личные сообщения</span>
<span v-if="newMessagesCount > 0" class="badge">+{{ newMessagesCount }}</span>
<button class="close-btn" @click="goBack">×</button>
</div>
<div v-if="isLoading" class="loading-container">
<div class="loading">Загрузка бесед...</div>
</div>
<div v-else-if="personalMessages.length === 0" class="empty-state">
<p>У вас пока нет личных бесед с другими администраторами</p>
</div>
<div v-else class="personal-messages-list">
<div
v-for="message in personalMessages"
:key="message.id"
class="message-item"
>
<div class="message-info">
<div class="admin-name">{{ message.name }}</div>
<div class="message-preview">{{ message.last_message || 'Нет сообщений' }}</div>
<div class="message-date">{{ formatDate(message.last_message_at) }}</div>
</div>
<el-button type="primary" size="small" @click="openPersonalChat(message.id)">
Открыть
</el-button>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../components/BaseLayout.vue';
import adminChatService from '../services/adminChatService.js';
import { usePermissions } from '@/composables/usePermissions';
const router = useRouter();
const route = useRoute();
const { canRead } = usePermissions();
const isLoading = ref(true);
const personalMessages = ref([]);
const newMessagesCount = ref(0);
let ws = null;
async function fetchPersonalMessages() {
try {
isLoading.value = true;
console.log('[PersonalMessagesView] Загружаем личные сообщения...');
const response = await adminChatService.getAdminContacts();
console.log('[PersonalMessagesView] API ответ:', response);
personalMessages.value = response?.contacts || [];
console.log('[PersonalMessagesView] Загружено бесед:', personalMessages.value.length);
console.log('[PersonalMessagesView] Беседы:', personalMessages.value);
newMessagesCount.value = personalMessages.value.length; // Simplified for now
} catch (error) {
console.error('[PersonalMessagesView] Ошибка загрузки личных сообщений:', error);
personalMessages.value = [];
} finally {
isLoading.value = false;
}
}
function connectWebSocket() {
try {
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = `${protocol}://${window.location.host}/ws`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('[PersonalMessagesView] WebSocket подключен');
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'contacts-updated' ||
data.type === 'messages-updated' ||
data.type === 'contact-updated' ||
data.type === 'admin-status-changed') {
console.log('[PersonalMessagesView] Получено обновление через WebSocket:', data.type);
fetchPersonalMessages();
}
} catch (error) {
console.error('[PersonalMessagesView] Ошибка парсинга WebSocket сообщения:', error);
}
};
ws.onerror = (error) => {
console.error('[PersonalMessagesView] Ошибка WebSocket:', error);
};
ws.onclose = () => {
console.log('[PersonalMessagesView] WebSocket отключен, переподключаемся через 3 секунды');
setTimeout(() => {
if (ws?.readyState === WebSocket.CLOSED) {
connectWebSocket();
}
}, 3000);
};
} catch (error) {
console.error('[PersonalMessagesView] Ошибка подключения WebSocket:', error);
}
}
function disconnectWebSocket() {
if (ws) {
ws.close();
ws = null;
}
}
function openPersonalChat(adminId) {
console.log('[PersonalMessagesView] Открываем приватный чат с админом:', adminId);
router.push({ name: 'admin-chat', params: { adminId } });
}
function goBack() {
if (window.history.length > 1) {
router.back();
} else {
router.push({ name: 'crm' });
}
}
const formatDate = (dateString) => {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleString();
};
// Следим за изменениями роута для обновления при возврате на страницу
watch(() => route.path, async (newPath) => {
if (newPath === '/personal-messages' && canRead.value) {
console.log('[PersonalMessagesView] Возврат на страницу, обновляем список');
await fetchPersonalMessages();
}
});
onMounted(async () => {
if (canRead.value) {
await fetchPersonalMessages();
connectWebSocket();
}
});
onUnmounted(() => {
disconnectWebSocket();
});
</script>
<style scoped>
.personal-messages-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
font-size: 1.2rem;
font-weight: bold;
}
.badge {
background: #ff4757;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.close-btn:hover {
background-color: #e0e0e0;
}
.loading-container {
height: 200px;
position: relative;
}
.empty-state {
padding: 2rem;
text-align: center;
color: #666;
}
.personal-messages-list {
padding: 1rem;
}
.message-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 0.5rem;
background: white;
transition: box-shadow 0.2s;
}
.message-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.message-info {
flex: 1;
}
.admin-name {
font-weight: bold;
font-size: 1.1rem;
margin-bottom: 0.25rem;
}
.message-preview {
color: #666;
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.message-date {
color: #999;
font-size: 0.8rem;
}
/* Стили для загрузки */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
min-height: 200px;
}
.loading {
color: #666;
font-size: 1rem;
text-align: center;
}
</style>

View File

@@ -381,23 +381,22 @@ async function loadMessages() {
if (!contact.value || !contact.value.id) return;
isLoadingMessages.value = true;
try {
// Получаем conversationId для контакта
const conv = await messagesService.getConversationByUserId(contact.value.id);
conversationId.value = conv?.id || null;
if (conversationId.value) {
messages.value = await messagesService.getMessagesByConversationId(conversationId.value);
// Загружаем ВСЕ публичные сообщения этого пользователя (как на главной странице)
messages.value = await messagesService.getMessagesByUserId(contact.value.id);
if (messages.value.length > 0) {
lastMessageDate.value = messages.value[messages.value.length - 1].created_at;
} else {
lastMessageDate.value = null;
}
} else {
messages.value = [];
lastMessageDate.value = null;
}
// Также получаем conversationId для отправки новых сообщений
const conv = await messagesService.getConversationByUserId(contact.value.id);
conversationId.value = conv?.id || null;
} catch (e) {
console.error('[ContactDetailsView] Ошибка загрузки сообщений:', e);
messages.value = [];
lastMessageDate.value = null;
conversationId.value = null;
} finally {
isLoadingMessages.value = false;
}