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

This commit is contained in:
2025-11-06 16:24:50 +03:00
parent b3620b264b
commit 714a3f55c7
34 changed files with 5436 additions and 2433 deletions

View File

@@ -169,9 +169,11 @@ import { useAuthContext } from '@/composables/useAuth';
import { usePermissions } from '@/composables/usePermissions';
import { PERMISSIONS } from './permissions.js';
import { useContactsAndMessagesWebSocket } from '@/composables/useContactsWebSocket';
import websocketServiceModule from '@/services/websocketService';
const { canEditContacts, canDeleteData, canManageTags, canBlockUsers, canSendToUsers, canGenerateAI, canViewContacts, hasPermission } = usePermissions();
const { address, userId: currentUserId } = useAuthContext();
const { markContactAsRead } = useContactsAndMessagesWebSocket();
const { websocketService } = websocketServiceModule;
// Подписываемся на централизованные события очистки и обновления данных
onMounted(() => {
@@ -220,6 +222,13 @@ const tagsTableId = ref(null);
const { onTagsUpdate } = useTagsWebSocket();
let unsubscribeFromTags = null;
// Обработчик обновления контактов через WebSocket
const handleContactsUpdate = async () => {
console.log('[ContactDetailsView] Получено обновление контакта, перезагружаем данные');
await reloadContact();
await loadUserTags();
};
// Функция маскировки персональных данных для читателей
function maskPersonalData(data) {
if (!data || data === '-') return '-';
@@ -725,6 +734,9 @@ onMounted(async () => {
await loadAllTags();
await loadUserTags();
});
// Подписываемся на обновления контактов (для обновления имени)
websocketService.on('contacts-updated', handleContactsUpdate);
});
onUnmounted(() => {
@@ -732,6 +744,7 @@ onUnmounted(() => {
if (unsubscribeFromTags) {
unsubscribeFromTags();
}
websocketService.off('contacts-updated', handleContactsUpdate);
});
watch(userId, async () => {
await reloadContact();

View File

@@ -43,10 +43,27 @@
<span class="page-status"><i class="fas fa-file"></i>{{ p.format || 'html' }}</span>
</div>
<div v-if="canManageLegalDocs && address" class="page-actions">
<button class="action-btn primary" title="Индексировать" @click.stop="reindex(p.id)"><i class="fas fa-sync"></i><span>Индекс</span></button>
<button
class="action-btn primary"
title="Индексировать"
:disabled="reindexStatus[p.id]?.state === 'loading'"
@click.stop="reindex(p.id)"
>
<i :class="['fas', reindexStatus[p.id]?.state === 'loading' ? 'fa-spinner fa-spin' : 'fa-sync']"></i>
<span>Индекс</span>
</button>
<button class="action-btn primary" title="Редактировать" @click.stop="goEdit(p.id)"><i class="fas fa-edit"></i><span>Ред.</span></button>
<button class="action-btn danger" title="Удалить" @click.stop="doDelete(p.id)"><i class="fas fa-trash"></i><span>Удалить</span></button>
</div>
<transition name="fade">
<div
v-if="reindexStatus[p.id]"
class="reindex-status"
:class="reindexStatus[p.id].state"
>
{{ reindexStatus[p.id].message }}
</div>
</transition>
</div>
</div>
</div>
@@ -60,7 +77,7 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import pagesService from '../../services/pagesService';
@@ -79,19 +96,48 @@ const props = defineProps({
const router = useRouter();
const search = ref('');
const pages = ref([]);
const reindexStatus = ref({});
const { address } = useAuthContext();
const { hasPermission } = usePermissions();
const canManageLegalDocs = computed(() => hasPermission(SHARED_PERMISSIONS.MANAGE_LEGAL_DOCS));
const reindexTimers = new Map();
function goBack() { router.push({ name: 'content-list' }); }
function openPublic(id) { router.push({ name: 'public-page-view', params: { id } }); }
function goEdit(id) { router.push({ name: 'content-create', query: { edit: id } }); }
function updateReindexStatus(id, state, message) {
reindexStatus.value = {
...reindexStatus.value,
[id]: { state, message }
};
}
function scheduleReindexCleanup(id, delay = 4000) {
if (reindexTimers.has(id)) {
clearTimeout(reindexTimers.get(id));
}
const timer = setTimeout(() => {
const next = { ...reindexStatus.value };
delete next[id];
reindexStatus.value = next;
reindexTimers.delete(id);
}, delay);
reindexTimers.set(id, timer);
}
async function reindex(id) {
try {
if (reindexStatus.value[id]?.state === 'loading') {
return;
}
updateReindexStatus(id, 'loading', 'Индексация запущена...');
await api.post(`/pages/${id}/reindex`);
alert('Индексация выполнена');
updateReindexStatus(id, 'success', 'Индексация выполняется. Проверьте логи.');
scheduleReindexCleanup(id);
} catch (e) {
alert('Ошибка индексации: ' + (e?.response?.data?.error || e.message));
const errorMessage = e?.response?.data?.error || e.message;
updateReindexStatus(id, 'error', `Ошибка индексации: ${errorMessage}`);
scheduleReindexCleanup(id, 6000);
}
}
async function doDelete(id) {
@@ -119,6 +165,11 @@ onMounted(async () => {
pages.value = [];
}
});
onBeforeUnmount(() => {
reindexTimers.forEach(timer => clearTimeout(timer));
reindexTimers.clear();
});
</script>
<style scoped>
@@ -151,6 +202,13 @@ onMounted(async () => {
.action-btn.primary:hover { background: var(--color-primary-dark); }
.action-btn.danger { background: #fef2f2; color: #b91c1c; border-color: #fecaca; }
.action-btn.danger:hover { background: #fee2e2; }
.action-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.reindex-status { margin-top: 10px; font-size: 0.9rem; font-weight: 500; }
.reindex-status.loading { color: #2563eb; }
.reindex-status.success { color: #16a34a; }
.reindex-status.error { color: #dc2626; }
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
.empty-state { text-align:center; padding: 60px 20px; }
.empty-icon { font-size: 3rem; color: var(--color-grey-dark); margin-bottom: 10px; }
</style>

File diff suppressed because it is too large Load Diff