feat: новая функция
This commit is contained in:
@@ -522,10 +522,11 @@ async function handleAiReply() {
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
@@ -533,6 +534,7 @@ async function handleAiReply() {
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
padding-bottom: 8px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
@@ -544,49 +546,13 @@ async function handleAiReply() {
|
||||
right: 0;
|
||||
border-radius: 12px 12px 0 0;
|
||||
box-shadow: 0 -2px 8px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 500px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-lg);
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--color-white);
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-top: 1px solid #e9ecef;
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition-normal);
|
||||
z-index: 10;
|
||||
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.05);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.chat-input textarea {
|
||||
width: 100%;
|
||||
border: none;
|
||||
|
||||
@@ -82,18 +82,19 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="canViewContacts"><input type="checkbox" v-model="selectAll" @change="toggleSelectAll" /></th>
|
||||
<th>ID</th>
|
||||
<th>Тип</th>
|
||||
<th>Имя</th>
|
||||
<th>Email</th>
|
||||
<th>Telegram</th>
|
||||
<th>Кошелек</th>
|
||||
<th>Дата создания</th>
|
||||
<th>Действие</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="contact in filteredContacts" :key="contact.id" :class="{ 'new-contact-row': newIds.includes(contact.id) }">
|
||||
<td v-if="canViewContacts"><input type="checkbox" v-model="selectedIds" :value="contact.id" /></td>
|
||||
<tr v-for="contact in filteredContacts" :key="contact.id" :class="{ 'new-contact-row': newIds.includes(contact.id) }" @click="goToContactDetails(contact.id)" style="cursor: pointer;">
|
||||
<td v-if="canViewContacts" @click.stop><input type="checkbox" v-model="selectedIds" :value="contact.id" /></td>
|
||||
<td>{{ contact.id }}</td>
|
||||
<td>
|
||||
<span
|
||||
v-if="getRoleDisplayName(contact.role)"
|
||||
@@ -104,14 +105,10 @@
|
||||
<span v-else class="user-badge">Неизвестно</span>
|
||||
</td>
|
||||
<td>{{ contact.name || '-' }}</td>
|
||||
<td>{{ contact.email || '-' }}</td>
|
||||
<td>{{ contact.telegram || '-' }}</td>
|
||||
<td>{{ contact.wallet || '-' }}</td>
|
||||
<td>{{ maskPersonalData(contact.email) }}</td>
|
||||
<td>{{ maskPersonalData(contact.telegram) }}</td>
|
||||
<td>{{ maskPersonalData(contact.wallet) }}</td>
|
||||
<td>{{ contact.created_at ? new Date(contact.created_at).toLocaleString() : '-' }}</td>
|
||||
<td>
|
||||
<span v-if="newMsgUserIds.includes(String(contact.id))" class="new-msg-icon" title="Новое сообщение">✉️</span>
|
||||
<button class="details-btn" @click="showDetails(contact)">Подробнее</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -132,6 +129,7 @@ import { useTagsWebSocket } from '../composables/useTagsWebSocket';
|
||||
import { useContactsAndMessagesWebSocket } from '../composables/useContactsWebSocket';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { PERMISSIONS } from '/app/shared/permissions.js';
|
||||
import api from '../api/axios';
|
||||
import { sendMessage, getPrivateUnreadCount } from '../services/messagesService';
|
||||
import { useRoles } from '@/composables/useRoles';
|
||||
@@ -147,7 +145,7 @@ const contactsArray = computed(() => props.contacts || []);
|
||||
const newIds = computed(() => props.newContacts.map(c => c.id));
|
||||
const newMsgUserIds = computed(() => props.newMessages.map(m => String(m.user_id)));
|
||||
const router = useRouter();
|
||||
const { canViewContacts, canSendToUsers, canDeleteData, canDeleteMessages, canManageSettings, canChatWithAdmins, canEditData } = usePermissions();
|
||||
const { canViewContacts, canSendToUsers, canDeleteData, canDeleteMessages, canManageSettings, canChatWithAdmins, canEditData, hasPermission } = usePermissions();
|
||||
const { userAccessLevel, userId, isAuthenticated } = useAuthContext();
|
||||
const { roles, getRoleDisplayName, getRoleClass, fetchRoles, clearRoles } = useRoles();
|
||||
|
||||
@@ -175,6 +173,19 @@ async function loadPrivateUnreadCount() {
|
||||
}
|
||||
}
|
||||
|
||||
// Функция маскировки персональных данных для читателей
|
||||
function maskPersonalData(data) {
|
||||
if (!data || data === '-') return '-';
|
||||
|
||||
// Если пользователь имеет права редактора, показываем полные данные
|
||||
if (hasPermission(PERMISSIONS.MANAGE_LEGAL_DOCS)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Для читателей маскируем данные полностью звездочками
|
||||
return '***';
|
||||
}
|
||||
|
||||
// Новый фильтр тегов через мультисвязи
|
||||
const availableTags = ref([]);
|
||||
const selectedTagIds = ref([]);
|
||||
@@ -404,14 +415,14 @@ function formatDate(date) {
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleString();
|
||||
}
|
||||
async function showDetails(contact) {
|
||||
async function goToContactDetails(contactId) {
|
||||
if (props.markContactAsRead) {
|
||||
await props.markContactAsRead(contact.id);
|
||||
await props.markContactAsRead(contactId);
|
||||
}
|
||||
if (props.markMessagesAsReadForUser) {
|
||||
props.markMessagesAsReadForUser(contact.id);
|
||||
props.markMessagesAsReadForUser(contactId);
|
||||
}
|
||||
router.push({ name: 'contact-details', params: { id: contact.id } });
|
||||
router.push({ name: 'contact-details', params: { id: contactId } });
|
||||
}
|
||||
|
||||
function onImported() {
|
||||
@@ -430,7 +441,7 @@ async function openChatForSelected() {
|
||||
if (!contact) return;
|
||||
|
||||
// Открываем чат с этим контактом (user_chat)
|
||||
await showDetails(contact);
|
||||
await goToContactDetails(contact.id);
|
||||
}
|
||||
|
||||
// Новая функция для отправки публичного сообщения
|
||||
@@ -448,7 +459,7 @@ function sendPublicMessage() {
|
||||
}
|
||||
|
||||
// Открываем страницу детали контакта с чатом для публичных сообщений
|
||||
showDetails(contact);
|
||||
goToContactDetails(contactId);
|
||||
}
|
||||
|
||||
// Функция для открытия приватного чата
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
<!-- Текстовый контент, если есть -->
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-if="message.content" class="message-content" v-html="formattedContent" />
|
||||
|
||||
<!-- Ссылка "Ответить" для публичных сообщений от других пользователей -->
|
||||
<div v-if="shouldShowReplyLink" class="message-reply-link">
|
||||
<a :href="replyLink" class="reply-link">Ответить</a>
|
||||
</div>
|
||||
|
||||
<!-- Кнопки для системного сообщения -->
|
||||
<div v-if="message.sender_type === 'system' && (message.telegramBotUrl || message.supportEmail)" class="system-actions">
|
||||
@@ -127,6 +132,11 @@ const isCurrentUserMessage = computed(() => {
|
||||
return props.message.sender_id === props.message.user_id;
|
||||
}
|
||||
|
||||
// Для публичных сообщений сравниваем sender_id с currentUserId
|
||||
if (props.message.message_type === 'public' && props.currentUserId) {
|
||||
return props.message.sender_id == props.currentUserId;
|
||||
}
|
||||
|
||||
// Для обычных сообщений используем стандартную логику
|
||||
return props.message.sender_type === 'user' || props.message.role === 'user';
|
||||
});
|
||||
@@ -145,6 +155,22 @@ const formatWalletAddress = (address) => {
|
||||
return address;
|
||||
};
|
||||
|
||||
// --- Логика ссылки "Ответить" для публичных сообщений ---
|
||||
const shouldShowReplyLink = computed(() => {
|
||||
// Показываем ссылку только для публичных сообщений от других пользователей
|
||||
return props.message.message_type === 'public' &&
|
||||
!isCurrentUserMessage.value &&
|
||||
props.message.sender_id &&
|
||||
props.currentUserId &&
|
||||
props.message.sender_id !== props.currentUserId;
|
||||
});
|
||||
|
||||
const replyLink = computed(() => {
|
||||
if (!shouldShowReplyLink.value) return '';
|
||||
// Ссылка ведет на страницу контакта отправителя
|
||||
return `/contacts/${props.message.sender_id}`;
|
||||
});
|
||||
|
||||
// --- Работа с вложениями ---
|
||||
const attachment = computed(() => {
|
||||
// Ожидаем массив attachments, даже если там только один элемент
|
||||
@@ -554,6 +580,30 @@ function copyEmail(email) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Стили для ссылки "Ответить" */
|
||||
.message-reply-link {
|
||||
margin-top: var(--spacing-xs);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.reply-link {
|
||||
color: var(--color-primary, #007bff);
|
||||
text-decoration: none;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
background-color: rgba(0, 123, 255, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.reply-link:hover {
|
||||
background-color: rgba(0, 123, 255, 0.2);
|
||||
color: var(--color-primary-dark, #0056b3);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Адаптивность для мобильных устройств */
|
||||
@media (max-width: 768px) {
|
||||
.private-current-user,
|
||||
|
||||
@@ -105,10 +105,16 @@ export function useChat(auth) {
|
||||
let totalMessages = -1;
|
||||
if (initial || messageLoading.value.offset === 0) {
|
||||
try {
|
||||
const countResponse = await api.get('/messages/public', { params: { count_only: true } });
|
||||
if (!countResponse.data.success) throw new Error('Не удалось получить количество сообщений');
|
||||
totalMessages = countResponse.data.total || countResponse.data.count || 0;
|
||||
// console.log(`[useChat] Всего сообщений в истории: ${totalMessages}`);
|
||||
// Получаем количество личных сообщений с ИИ
|
||||
const personalCountResponse = await api.get('/chat/history', { params: { count_only: true } });
|
||||
const personalCount = personalCountResponse.data.success ? (personalCountResponse.data.total || 0) : 0;
|
||||
|
||||
// Получаем количество публичных сообщений
|
||||
const publicCountResponse = await api.get('/messages/public', { params: { count_only: true } });
|
||||
const publicCount = publicCountResponse.data.success ? (publicCountResponse.data.total || 0) : 0;
|
||||
|
||||
totalMessages = personalCount + publicCount;
|
||||
// console.log(`[useChat] Всего сообщений в истории: ${totalMessages} (личные: ${personalCount}, публичные: ${publicCount})`);
|
||||
} catch(countError) {
|
||||
// console.error('[useChat] Ошибка получения количества сообщений:', countError);
|
||||
// Не прерываем выполнение, попробуем загрузить без total
|
||||
@@ -122,13 +128,41 @@ export function useChat(auth) {
|
||||
// console.log(`[useChat] Рассчитано начальное смещение: ${effectiveOffset}`);
|
||||
}
|
||||
|
||||
// Используем новый API для публичных сообщений с пагинацией
|
||||
const response = await api.get('/messages/public', {
|
||||
// Загружаем личные сообщения с ИИ
|
||||
const personalResponse = await api.get('/chat/history', {
|
||||
params: {
|
||||
offset: effectiveOffset,
|
||||
limit: messageLoading.value.limit
|
||||
}
|
||||
});
|
||||
|
||||
// Загружаем публичные сообщения от других пользователей
|
||||
const publicResponse = await api.get('/messages/public', {
|
||||
params: {
|
||||
offset: 0,
|
||||
limit: 50
|
||||
}
|
||||
});
|
||||
|
||||
// Объединяем сообщения
|
||||
let allMessages = [];
|
||||
if (personalResponse.data.success && personalResponse.data.messages) {
|
||||
allMessages = [...allMessages, ...personalResponse.data.messages];
|
||||
}
|
||||
if (publicResponse.data.success && publicResponse.data.messages) {
|
||||
allMessages = [...allMessages, ...publicResponse.data.messages];
|
||||
}
|
||||
|
||||
// Сортируем по времени создания
|
||||
allMessages.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
||||
|
||||
const response = {
|
||||
data: {
|
||||
success: true,
|
||||
messages: allMessages,
|
||||
total: allMessages.length
|
||||
}
|
||||
};
|
||||
|
||||
if (response.data.success && response.data.messages) {
|
||||
const loadedMessages = response.data.messages;
|
||||
|
||||
@@ -166,4 +166,13 @@ export async function markPrivateMessagesAsRead(conversationId) {
|
||||
conversationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
// Функция для загрузки личных сообщений с ИИ
|
||||
export async function getPersonalChatHistory(options = {}) {
|
||||
const { limit = 50, offset = 0 } = options;
|
||||
const { data } = await api.get('/chat/history', {
|
||||
params: { limit, offset }
|
||||
});
|
||||
return data;
|
||||
}
|
||||
@@ -33,6 +33,7 @@
|
||||
:messages="messages"
|
||||
:is-loading="isLoading || isConnectingWallet"
|
||||
:has-more-messages="messageLoading.hasMoreMessages"
|
||||
:currentUserId="auth.userId"
|
||||
v-model:newMessage="newMessage"
|
||||
v-model:attachments="attachments"
|
||||
@send-message="handleSendMessage"
|
||||
@@ -44,6 +45,7 @@
|
||||
:messages="messages"
|
||||
:is-loading="isLoading || isConnectingWallet"
|
||||
:has-more-messages="messageLoading.hasMoreMessages"
|
||||
:currentUserId="auth.userId"
|
||||
v-model:newMessage="newMessage"
|
||||
v-model:attachments="attachments"
|
||||
@send-message="handleSendMessage"
|
||||
@@ -161,6 +163,8 @@
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-lg);
|
||||
height: calc(100vh - 40px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -171,6 +175,7 @@
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
@@ -190,6 +195,7 @@
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Адаптивность */
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
<h2>Детали контакта</h2>
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
</div>
|
||||
<div class="contact-info-block">
|
||||
<div class="contact-info-section">
|
||||
<div class="contact-info-block">
|
||||
<div><strong>ID пользователя:</strong> {{ contact.id }}</div>
|
||||
<div>
|
||||
<strong>Имя:</strong>
|
||||
<template v-if="canEditContacts">
|
||||
@@ -32,9 +34,9 @@
|
||||
{{ contact.name }}
|
||||
</template>
|
||||
</div>
|
||||
<div><strong>Email:</strong> {{ contact.email || '-' }}</div>
|
||||
<div><strong>Telegram:</strong> {{ contact.telegram || '-' }}</div>
|
||||
<div><strong>Кошелек:</strong> {{ contact.wallet || '-' }}</div>
|
||||
<div><strong>Email:</strong> {{ maskPersonalData(contact.email) }}</div>
|
||||
<div><strong>Telegram:</strong> {{ maskPersonalData(contact.telegram) }}</div>
|
||||
<div><strong>Кошелек:</strong> {{ maskPersonalData(contact.wallet) }}</div>
|
||||
<div>
|
||||
<strong>Язык:</strong>
|
||||
<div class="multi-select">
|
||||
@@ -101,6 +103,7 @@
|
||||
<button class="delete-history-btn" @click="deleteMessagesHistory">Удалить историю сообщений</button>
|
||||
<button class="delete-btn" @click="deleteContact">Удалить контакт</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="messages-block">
|
||||
<h3>Чат с пользователем</h3>
|
||||
@@ -109,9 +112,10 @@
|
||||
:isLoading="isLoadingMessages"
|
||||
:attachments="chatAttachments"
|
||||
:newMessage="chatNewMessage"
|
||||
:canSend="canSendToUsers"
|
||||
:canSend="canSendToUsers && !!address"
|
||||
:canGenerateAI="canGenerateAI"
|
||||
:canSelectMessages="canGenerateAI"
|
||||
:currentUserId="currentUserId"
|
||||
@send-message="handleSendMessage"
|
||||
@update:newMessage="val => chatNewMessage = val"
|
||||
@update:attachments="val => chatAttachments = val"
|
||||
@@ -160,11 +164,13 @@ import Message from '../../components/Message.vue';
|
||||
import ChatInterface from '../../components/ChatInterface.vue';
|
||||
import contactsService from '../../services/contactsService.js';
|
||||
import messagesService from '../../services/messagesService.js';
|
||||
import { getPublicMessages, getConversationByUserId } from '../../services/messagesService.js';
|
||||
import { getPublicMessages, getConversationByUserId, sendMessage, getPersonalChatHistory } from '../../services/messagesService.js';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import { PERMISSIONS } from '/app/shared/permissions.js';
|
||||
import { useContactsAndMessagesWebSocket } from '@/composables/useContactsWebSocket';
|
||||
const { canEditContacts, canDeleteData, canManageTags, canBlockUsers, canSendToUsers, canGenerateAI, canViewContacts } = usePermissions();
|
||||
const { canEditContacts, canDeleteData, canManageTags, canBlockUsers, canSendToUsers, canGenerateAI, canViewContacts, hasPermission } = usePermissions();
|
||||
const { address, userId: currentUserId } = useAuthContext();
|
||||
const { markContactAsRead } = useContactsAndMessagesWebSocket();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
@@ -214,6 +220,19 @@ const tagsTableId = ref(null);
|
||||
const { onTagsUpdate } = useTagsWebSocket();
|
||||
let unsubscribeFromTags = null;
|
||||
|
||||
// Функция маскировки персональных данных для читателей
|
||||
function maskPersonalData(data) {
|
||||
if (!data || data === '-') return '-';
|
||||
|
||||
// Если пользователь имеет права редактора, показываем полные данные
|
||||
if (hasPermission(PERMISSIONS.MANAGE_LEGAL_DOCS)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Для читателей маскируем данные полностью звездочками
|
||||
return '***';
|
||||
}
|
||||
|
||||
async function ensureTagsTable() {
|
||||
// Получаем все пользовательские таблицы
|
||||
const tables = await tablesService.getTables();
|
||||
@@ -402,16 +421,42 @@ async function loadMessages() {
|
||||
console.log('[ContactDetailsView] 📥 loadMessages START for:', contact.value.id);
|
||||
isLoadingMessages.value = true;
|
||||
try {
|
||||
// Загружаем только публичные сообщения этого пользователя с пагинацией
|
||||
const response = await getPublicMessages(contact.value.id, { limit: 50, offset: 0 });
|
||||
console.log('[ContactDetailsView] 📩 Loaded messages:', response.messages?.length || 0, 'for', contact.value.id);
|
||||
// Проверяем, является ли контакт собственным ID пользователя
|
||||
const isOwnContact = currentUserId.value && contact.value.id == currentUserId.value;
|
||||
|
||||
if (response.success && response.messages) {
|
||||
messages.value = response.messages;
|
||||
let allMessages = [];
|
||||
|
||||
if (isOwnContact) {
|
||||
// Для собственного ID загружаем И личные сообщения с ИИ, И публичные сообщения от других пользователей
|
||||
console.log('[ContactDetailsView] 🔍 Loading personal chat with AI + public messages for own ID:', contact.value.id);
|
||||
|
||||
// Загружаем личные сообщения с ИИ
|
||||
const personalResponse = await getPersonalChatHistory({ limit: 50, offset: 0 });
|
||||
if (personalResponse.success && personalResponse.messages) {
|
||||
allMessages = [...allMessages, ...personalResponse.messages];
|
||||
}
|
||||
|
||||
// Загружаем публичные сообщения от других пользователей (входящие)
|
||||
const publicResponse = await getPublicMessages(contact.value.id, { limit: 50, offset: 0 });
|
||||
if (publicResponse.success && publicResponse.messages) {
|
||||
allMessages = [...allMessages, ...publicResponse.messages];
|
||||
}
|
||||
|
||||
// Сортируем по времени создания
|
||||
allMessages.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
||||
|
||||
} else {
|
||||
messages.value = [];
|
||||
// Для других пользователей загружаем публичные сообщения между текущим пользователем и выбранным контактом
|
||||
console.log('[ContactDetailsView] 🔍 Loading public messages between current user and contact:', contact.value.id);
|
||||
const response = await getPublicMessages(contact.value.id, { limit: 50, offset: 0 });
|
||||
if (response.success && response.messages) {
|
||||
allMessages = response.messages;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[ContactDetailsView] 📩 Loaded messages:', allMessages.length, 'for', contact.value.id);
|
||||
messages.value = allMessages;
|
||||
|
||||
if (messages.value.length > 0) {
|
||||
lastMessageDate.value = messages.value[messages.value.length - 1].created_at;
|
||||
} else {
|
||||
@@ -487,27 +532,23 @@ async function handleSendMessage({ message, attachments }) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await messagesService.broadcastMessage({
|
||||
userId: contact.value.id,
|
||||
message,
|
||||
attachments
|
||||
const result = await sendMessage({
|
||||
recipientId: contact.value.id,
|
||||
content: message,
|
||||
messageType: 'public'
|
||||
});
|
||||
// Формируем текст результата для отображения админу
|
||||
let resultText = '';
|
||||
if (result && Array.isArray(result.results)) {
|
||||
resultText = 'Результат рассылки по каналам:';
|
||||
for (const r of result.results) {
|
||||
resultText += `\n${r.channel}: ${(r.status === 'sent' || r.status === 'saved') ? 'Успех' : 'Ошибка'}${r.error ? ' (' + r.error + ')' : ''}`;
|
||||
|
||||
if (result && result.success) {
|
||||
// Очищаем поле ввода после успешной отправки
|
||||
chatNewMessage.value = '';
|
||||
// Обновляем список сообщений
|
||||
await loadMessages();
|
||||
if (typeof ElMessageBox === 'function') {
|
||||
ElMessageBox.alert('Сообщение отправлено успешно', 'Успех', { type: 'success' });
|
||||
}
|
||||
} else {
|
||||
resultText = 'Не удалось получить подробный ответ от сервера.';
|
||||
throw new Error(result?.message || 'Неизвестная ошибка');
|
||||
}
|
||||
if (typeof ElMessageBox === 'function') {
|
||||
ElMessageBox.alert(resultText, 'Результат рассылки', { type: 'info' });
|
||||
} else {
|
||||
console.log('Результат рассылки:', resultText);
|
||||
}
|
||||
await loadMessages();
|
||||
} catch (e) {
|
||||
if (typeof ElMessageBox === 'function') {
|
||||
ElMessageBox.alert('Ошибка отправки: ' + (e?.response?.data?.error || e?.message || e), 'Ошибка', { type: 'error' });
|
||||
@@ -701,23 +742,28 @@ watch(userId, async () => {
|
||||
|
||||
<style scoped>
|
||||
.contact-details-page {
|
||||
padding: 32px 0;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.contact-details-content {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 32px rgba(0,0,0,0.12);
|
||||
padding: 32px 24px 24px 24px;
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
.contact-details-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.close-btn {
|
||||
background: none;
|
||||
@@ -730,8 +776,14 @@ watch(userId, async () => {
|
||||
.close-btn:hover {
|
||||
color: #333;
|
||||
}
|
||||
.contact-info-section {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.contact-info-block {
|
||||
margin-bottom: 18px;
|
||||
font-size: 1.08rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
@@ -752,6 +804,7 @@ watch(userId, async () => {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.delete-history-btn {
|
||||
@@ -858,6 +911,11 @@ watch(userId, async () => {
|
||||
border-radius: 10px;
|
||||
padding: 18px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 500px;
|
||||
max-height: 70vh;
|
||||
}
|
||||
.messages-list {
|
||||
max-height: 350px;
|
||||
|
||||
Reference in New Issue
Block a user