feat: новая функция
This commit is contained in:
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Управление очередью (только для админов) -->
|
||||
<div v-if="isAdmin" class="queue-controls">
|
||||
<div v-if="canManageSettings" class="queue-controls">
|
||||
<h4>Управление очередью</h4>
|
||||
<div class="control-buttons">
|
||||
<button @click="controlQueue('pause')" class="btn-control btn-pause">
|
||||
@@ -123,16 +123,12 @@
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import Chart from 'chart.js/auto'
|
||||
import { usePermissions } from '@/composables/usePermissions'
|
||||
|
||||
export default {
|
||||
name: 'AIQueueMonitor',
|
||||
props: {
|
||||
isAdmin: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const { canManageSettings } = usePermissions();
|
||||
const stats = ref({
|
||||
totalProcessed: 0,
|
||||
totalFailed: 0,
|
||||
@@ -287,6 +283,7 @@ export default {
|
||||
})
|
||||
|
||||
return {
|
||||
canManageSettings,
|
||||
stats,
|
||||
loading,
|
||||
autoRefresh,
|
||||
|
||||
@@ -112,6 +112,20 @@ const handleAuthFlowSuccess = (authType) => {
|
||||
eventBus.emit('auth-success', { authType });
|
||||
};
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[BaseLayout] Clearing base layout data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// BaseLayout не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[BaseLayout] Refreshing base layout data');
|
||||
// BaseLayout не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
const {
|
||||
telegramAuth,
|
||||
handleTelegramAuth,
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
<div style="margin-bottom:1em;">Вы выбрали {{userIds.length}} пользователей для рассылки.</div>
|
||||
<ChatInterface
|
||||
v-model:newMessage="message"
|
||||
:isAdmin="true"
|
||||
:canSend="true"
|
||||
:canGenerateAI="false"
|
||||
:canSelectMessages="false"
|
||||
:messages="[]"
|
||||
:attachments="attachments"
|
||||
@update:attachments="val => attachments = val"
|
||||
@send-message="onSend"
|
||||
:showSendButton="false"
|
||||
/>
|
||||
<el-button type="primary" :disabled="!message.trim()" @click="sendBroadcast" :loading="loading">Отправить</el-button>
|
||||
<el-button @click="$emit('close')" style="margin-left:1em;">Отмена</el-button>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="chat-container" :style="{ '--chat-input-height': chatInputHeight + 'px' }">
|
||||
<div ref="messagesContainer" class="chat-messages" @scroll="handleScroll">
|
||||
<div v-for="message in messages" :key="message.id" :class="['message-wrapper', { 'selected-message': selectedMessageIds.includes(message.id) }]">
|
||||
<template v-if="isAdmin">
|
||||
<template v-if="props.canSelectMessages">
|
||||
<input type="checkbox" class="admin-select-checkbox" :checked="selectedMessageIds.includes(message.id)" @change="() => toggleSelectMessage(message.id)" />
|
||||
</template>
|
||||
<Message :message="message" />
|
||||
@@ -28,7 +28,7 @@
|
||||
:value="newMessage"
|
||||
@input="handleInput"
|
||||
placeholder="Введите сообщение..."
|
||||
:disabled="isLoading"
|
||||
:disabled="isLoading || !props.canSend"
|
||||
rows="1"
|
||||
autofocus
|
||||
@keydown.enter.prevent="sendMessage"
|
||||
@@ -43,6 +43,7 @@
|
||||
@mouseup="stopAudioRecording"
|
||||
@mouseleave="stopAudioRecording"
|
||||
:class="{ 'recording': isAudioRecording }"
|
||||
:disabled="!props.canSend"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
|
||||
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" fill="currentColor"/>
|
||||
@@ -56,12 +57,13 @@
|
||||
@mouseup="stopVideoRecording"
|
||||
@mouseleave="stopVideoRecording"
|
||||
:class="{ 'recording': isVideoRecording }"
|
||||
:disabled="!props.canSend"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
|
||||
<path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z" fill="currentColor"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="chat-icon-btn" title="Прикрепить файл" @click="handleFileUpload">
|
||||
<button class="chat-icon-btn" title="Прикрепить файл" @click="handleFileUpload" :disabled="!props.canSend">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
|
||||
<path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
@@ -81,7 +83,7 @@
|
||||
<path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z" fill="currentColor"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="props.isAdmin" class="chat-icon-btn ai-reply-btn" title="Сгенерировать ответ ИИ" @click="handleAiReply" :disabled="isAiLoading">
|
||||
<button v-if="props.canGenerateAI" class="chat-icon-btn ai-reply-btn" title="Сгенерировать ответ ІІ" @click="handleAiReply" :disabled="isAiLoading">
|
||||
<template v-if="isAiLoading">
|
||||
<svg class="ai-spinner" width="22" height="22" viewBox="0 0 50 50"><circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle></svg>
|
||||
</template>
|
||||
@@ -125,7 +127,11 @@ const props = defineProps({
|
||||
attachments: Array, // Для v-model
|
||||
// Добавляем пропс для проверки, есть ли еще сообщения для загрузки
|
||||
hasMoreMessages: Boolean,
|
||||
isAdmin: { type: Boolean, default: false }
|
||||
|
||||
// Новые props для точного контроля прав
|
||||
canSend: { type: Boolean, default: true }, // Может отправлять сообщения
|
||||
canGenerateAI: { type: Boolean, default: false }, // Может генерировать AI-ответы
|
||||
canSelectMessages: { type: Boolean, default: false } // Может выбирать сообщения
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -347,7 +353,7 @@ const clearInput = () => {
|
||||
|
||||
// --- Отправка сообщения ---
|
||||
const isSendDisabled = computed(() => {
|
||||
return props.isLoading || (!props.newMessage.trim() && localAttachments.value.length === 0);
|
||||
return props.isLoading || !props.canSend || (!props.newMessage.trim() && localAttachments.value.length === 0);
|
||||
});
|
||||
|
||||
const sendMessage = () => {
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
<template>
|
||||
<div class="contact-table-modal">
|
||||
<div class="contact-table-header">
|
||||
<el-button v-if="canRead" type="info" @click="goToPersonalMessages" style="margin-right: 1em;">Личные сообщения</el-button>
|
||||
<el-button v-if="canEdit" type="success" :disabled="!selectedIds.length" @click="() => openChatForSelected()" style="margin-right: 1em;">Публичное сообщение</el-button>
|
||||
<el-button v-if="canRead" type="warning" :disabled="!selectedIds.length" @click="() => openPrivateChatForSelected()" style="margin-right: 1em;">Приватное сообщение</el-button>
|
||||
<el-button v-if="canChatWithAdmins" type="info" @click="goToPersonalMessages" style="margin-right: 1em;">Личные сообщения</el-button>
|
||||
<el-button v-if="canSendToUsers" type="success" :disabled="!selectedIds.length" @click="() => openChatForSelected()" style="margin-right: 1em;">Публичное сообщение</el-button>
|
||||
<el-button v-if="canViewContacts" type="warning" :disabled="!selectedIds.length" @click="() => openPrivateChatForSelected()" style="margin-right: 1em;">Приватное сообщение</el-button>
|
||||
<el-button v-if="canManageSettings" type="info" :disabled="!selectedIds.length" @click="showBroadcastModal = true" style="margin-right: 1em;">Рассылка</el-button>
|
||||
<el-button v-if="canDelete" type="warning" :disabled="!selectedIds.length" @click="deleteMessagesSelected" style="margin-right: 1em;">Удалить сообщения</el-button>
|
||||
<el-button v-if="canDelete" type="danger" :disabled="!selectedIds.length" @click="deleteSelected" style="margin-right: 1em;">Удалить</el-button>
|
||||
<el-button v-if="canEdit" type="primary" @click="showImportModal = true" style="margin-right: 1em;">Импорт</el-button>
|
||||
<el-button v-if="canDeleteMessages" type="warning" :disabled="!selectedIds.length" @click="deleteMessagesSelected" style="margin-right: 1em;">Удалить сообщения</el-button>
|
||||
<el-button v-if="canDeleteData" type="danger" :disabled="!selectedIds.length" @click="deleteSelected" style="margin-right: 1em;">Удалить</el-button>
|
||||
<el-button v-if="canEditData" type="primary" @click="showImportModal = true" style="margin-right: 1em;">Импорт</el-button>
|
||||
<button class="close-btn" @click="$emit('close')">×</button>
|
||||
</div>
|
||||
<el-form :inline="true" class="filters-form" label-position="top">
|
||||
@@ -77,7 +77,7 @@
|
||||
<table class="contact-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="canRead"><input type="checkbox" v-model="selectAll" @change="toggleSelectAll" /></th>
|
||||
<th v-if="canViewContacts"><input type="checkbox" v-model="selectAll" @change="toggleSelectAll" /></th>
|
||||
<th>Тип</th>
|
||||
<th>Имя</th>
|
||||
<th>Email</th>
|
||||
@@ -89,9 +89,11 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="contact in contactsArray" :key="contact.id" :class="{ 'new-contact-row': newIds.includes(contact.id) }">
|
||||
<td v-if="canRead"><input type="checkbox" v-model="selectedIds" :value="contact.id" /></td>
|
||||
<td v-if="canViewContacts"><input type="checkbox" v-model="selectedIds" :value="contact.id" /></td>
|
||||
<td>
|
||||
<span v-if="contact.contact_type === 'admin'" class="admin-badge">Админ</span>
|
||||
<span v-else-if="contact.contact_type === 'editor'" class="editor-badge">Редактор</span>
|
||||
<span v-else-if="contact.contact_type === 'readonly'" class="readonly-badge">Чтение</span>
|
||||
<span v-else class="user-badge">Пользователь</span>
|
||||
</td>
|
||||
<td>{{ contact.name || '-' }}</td>
|
||||
@@ -133,7 +135,7 @@ const contactsArray = ref([]); // теперь управляем вручную
|
||||
const newIds = computed(() => props.newContacts.map(c => c.id));
|
||||
const newMsgUserIds = computed(() => props.newMessages.map(m => String(m.user_id)));
|
||||
const router = useRouter();
|
||||
const { canRead, canEdit, canDelete, canManageSettings } = usePermissions();
|
||||
const { canViewContacts, canSendToUsers, canDeleteData, canDeleteMessages, canManageSettings, canChatWithAdmins, canEditData } = usePermissions();
|
||||
|
||||
// Фильтры
|
||||
const filterSearch = ref('');
|
||||
@@ -551,6 +553,22 @@ async function deleteMessagesSelected() {
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.editor-badge {
|
||||
background: #f3e5f5;
|
||||
color: #7b1fa2;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.readonly-badge {
|
||||
background: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,18 @@ onMounted(() => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[Header] Clearing header data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// Header не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[Header] Refreshing header data');
|
||||
// Header не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
// Очищаем наблюдатель при удалении компонента
|
||||
|
||||
@@ -180,6 +180,20 @@ const emit = defineEmits(['update:modelValue', 'wallet-auth', 'disconnect-wallet
|
||||
|
||||
const { deleteIdentity } = useAuthContext();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[Sidebar] Clearing sidebar data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// Sidebar не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[Sidebar] Refreshing sidebar data');
|
||||
// Sidebar не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
// Обработчики событий
|
||||
const handleWalletAuth = () => {
|
||||
emit('wallet-auth');
|
||||
|
||||
@@ -52,6 +52,20 @@
|
||||
import axios from '@/api/axios';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[EmailConnect] Clearing email connect data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// EmailConnect не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[EmailConnect] Refreshing email connect data');
|
||||
// EmailConnect не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'success']);
|
||||
const { linkIdentity } = useAuthContext();
|
||||
|
||||
|
||||
@@ -30,6 +30,20 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[WalletConnection] Clearing wallet connection data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// WalletConnection не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[WalletConnection] Refreshing wallet connection data');
|
||||
// WalletConnection не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
import { connectWithWallet } from '@/services/wallet';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<template>
|
||||
<template v-if="column.type === 'multiselect'">
|
||||
<div v-if="!editing" @click="canEdit && (editing = true)" class="tags-cell-view">
|
||||
<div v-if="!editing" @click="canEditData && (editing = true)" class="tags-cell-view">
|
||||
<span v-if="selectedMultiNames.length">{{ selectedMultiNames.join(', ') }}</span>
|
||||
<span v-else class="cell-plus-icon" title="Добавить">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.type === 'relation'">
|
||||
<div v-if="!editing" @click="canEdit && (editing = true)" class="tags-cell-view">
|
||||
<div v-if="!editing" @click="canEditData && (editing = true)" class="tags-cell-view">
|
||||
<span v-if="selectedRelationName">{{ selectedRelationName }}</span>
|
||||
<span v-else class="cell-plus-icon" title="Добавить">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
@@ -64,7 +64,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.type === 'multiselect-relation'">
|
||||
<div v-if="!editing" @click="canEdit && (editing = true)" class="tags-cell-view">
|
||||
<div v-if="!editing" @click="canEditData && (editing = true)" class="tags-cell-view">
|
||||
<span v-if="selectedMultiRelationNames.length">{{ selectedMultiRelationNames.join(', ') }}</span>
|
||||
<span v-else class="cell-plus-icon" title="Добавить">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
@@ -97,7 +97,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="!editing" class="cell-view-value" @click="canEdit && (editing = true)">
|
||||
<div v-if="!editing" class="cell-view-value" @click="canEditData && (editing = true)">
|
||||
<span v-if="isArrayString(localValue)">{{ parseArrayString(localValue).join(', ') }}</span>
|
||||
<span v-else-if="localValue">{{ localValue }}</span>
|
||||
<span v-else class="cell-plus-icon" title="Добавить">
|
||||
@@ -132,7 +132,7 @@ import { usePermissions } from '@/composables/usePermissions';
|
||||
|
||||
const props = defineProps(['rowId', 'column', 'cellValues']);
|
||||
const emit = defineEmits(['update']);
|
||||
const { canEdit } = usePermissions();
|
||||
const { canEditDataData } = usePermissions();
|
||||
|
||||
const localValue = ref('');
|
||||
const editing = ref(false);
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<h2>{{ tableMeta.name }}</h2>
|
||||
<div class="table-desc">{{ tableMeta.description }}</div>
|
||||
<div class="table-header-actions" style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 8px; margin-bottom: 18px;">
|
||||
<el-button v-if="canEdit" type="danger" :disabled="!selectedRows.length" @click="deleteSelectedRows">Удалить выбранные</el-button>
|
||||
<el-button v-if="canEditData" type="danger" :disabled="!selectedRows.length" @click="deleteSelectedRows">Удалить выбранные</el-button>
|
||||
<span v-if="selectedRows.length">Выбрано: {{ selectedRows.length }}</span>
|
||||
<button v-if="canEdit" class="rebuild-btn" @click="rebuildIndex" :disabled="rebuilding">
|
||||
<button v-if="canEditData" class="rebuild-btn" @click="rebuildIndex" :disabled="rebuilding">
|
||||
{{ rebuilding ? 'Пересборка...' : 'Пересобрать индекс' }}
|
||||
</button>
|
||||
<el-button @click="resetFilters" type="default" icon="el-icon-refresh">Сбросить фильтры</el-button>
|
||||
@@ -68,7 +68,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ col.name }}</span>
|
||||
<button v-if="canEdit" class="col-menu" @click.stop="openColMenu(col, $event)">⋮</button>
|
||||
<button v-if="canEditData" class="col-menu" @click.stop="openColMenu(col, $event)">⋮</button>
|
||||
</template>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
@@ -90,7 +90,7 @@
|
||||
:resizable="false"
|
||||
>
|
||||
<template #header>
|
||||
<button v-if="canEdit" class="add-col-btn" @click.stop="openAddMenu($event)" title="Добавить">
|
||||
<button v-if="canEditData" 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"/>
|
||||
@@ -105,7 +105,7 @@
|
||||
</teleport>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<button v-if="canEdit" class="row-menu" @click.stop="openRowMenu(row, $event)">⋮</button>
|
||||
<button v-if="canEditData" class="row-menu" @click.stop="openRowMenu(row, $event)">⋮</button>
|
||||
<teleport to="body">
|
||||
<div v-if="openedRowMenuId === row.id" class="context-menu" :style="rowMenuStyle">
|
||||
<button class="menu-item" @click="addRowAfter(row)">Добавить строку</button>
|
||||
@@ -172,6 +172,21 @@ import TableCell from './TableCell.vue';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import axios from 'axios';
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[UserTableView] Clearing table data');
|
||||
// Очищаем данные при выходе из системы
|
||||
tableData.value = [];
|
||||
columns.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[UserTableView] Refreshing table data');
|
||||
loadTableData(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
// Импортируем компоненты Element Plus
|
||||
import { ElSelect, ElOption, ElButton } from 'element-plus';
|
||||
import websocketService from '../../services/websocketService';
|
||||
@@ -180,8 +195,7 @@ import { useTagsWebSocket } from '../../composables/useTagsWebSocket';
|
||||
let unsubscribeFromTableUpdate = null;
|
||||
let unsubscribeFromTagsUpdate = null;
|
||||
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canEdit } = usePermissions();
|
||||
const { canEditData } = usePermissions();
|
||||
const rebuilding = ref(false);
|
||||
const rebuildStatus = ref(null);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ const SettingsInterfaceView = () => import('../views/settings/Interface/Interfac
|
||||
|
||||
import axios from 'axios';
|
||||
import { setToStorage } from '../utils/storage.js';
|
||||
import { PERMISSIONS, hasPermission } from '/app/shared/permissions.js';
|
||||
|
||||
// console.log('router/index.js: Script loaded');
|
||||
|
||||
@@ -148,30 +149,33 @@ const routes = [
|
||||
path: '/contacts/:id',
|
||||
name: 'contact-details',
|
||||
component: () => import('../views/contacts/ContactDetailsView.vue'),
|
||||
props: true
|
||||
props: true,
|
||||
// meta: { permission: PERMISSIONS.VIEW_CONTACTS } // Временно убираем проверку прав
|
||||
},
|
||||
{
|
||||
path: '/contacts/:id/delete',
|
||||
name: 'contact-delete-confirm',
|
||||
component: () => import('../views/contacts/ContactDeleteConfirm.vue'),
|
||||
props: true
|
||||
props: true,
|
||||
meta: { permission: PERMISSIONS.DELETE_USER_DATA }
|
||||
},
|
||||
{
|
||||
path: '/contacts-list',
|
||||
name: 'contacts-list',
|
||||
component: () => import('../views/ContactsView.vue')
|
||||
component: () => import('../views/ContactsView.vue'),
|
||||
// meta: { permission: PERMISSIONS.VIEW_CONTACTS } // Временно убираем проверку прав
|
||||
},
|
||||
{
|
||||
path: '/admin-chat/:adminId',
|
||||
name: 'admin-chat',
|
||||
component: () => import('../views/AdminChatView.vue'),
|
||||
meta: { requiresAuth: true, requiresAdmin: true }
|
||||
meta: { permission: PERMISSIONS.CHAT_WITH_ADMINS }
|
||||
},
|
||||
{
|
||||
path: '/personal-messages',
|
||||
name: 'personal-messages',
|
||||
component: () => import('../views/PersonalMessagesView.vue'),
|
||||
meta: { requiresAuth: true, requiresAdmin: true }
|
||||
meta: { permission: PERMISSIONS.CHAT_WITH_ADMINS }
|
||||
},
|
||||
|
||||
{
|
||||
@@ -209,6 +213,11 @@ const routes = [
|
||||
name: 'page-edit',
|
||||
component: () => import('../views/content/PageEditView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/public/page/:id',
|
||||
name: 'public-page-view',
|
||||
component: () => import('../views/content/PublicPageView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/management',
|
||||
name: 'management',
|
||||
@@ -297,21 +306,48 @@ router.beforeEach(async (to, from, next) => {
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
|
||||
// Проверяем аутентификацию, если маршрут требует авторизации
|
||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||
// Проверяем права доступа (новая система permissions)
|
||||
const requiredPermission = to.meta?.permission;
|
||||
|
||||
if (requiredPermission) {
|
||||
try {
|
||||
const response = await axios.get('/auth/check');
|
||||
if (response.data.authenticated) {
|
||||
next();
|
||||
} else {
|
||||
// Перенаправляем на главную страницу, где есть форма аутентификации
|
||||
next({ name: 'home' });
|
||||
|
||||
if (!response.data.authenticated) {
|
||||
// Неавторизованный - редирект на главную
|
||||
console.log('[Router] Доступ запрещен: требуется авторизация для', requiredPermission);
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
|
||||
// Получаем уровень доступа пользователя
|
||||
const userAccessLevel = response.data.userAccessLevel;
|
||||
if (!userAccessLevel) {
|
||||
console.log('[Router] Доступ запрещен: нет данных об уровне доступа');
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
|
||||
// Определяем роль на основе уровня доступа
|
||||
let userRole = 'user'; // по умолчанию
|
||||
if (userAccessLevel.level === 'readonly') {
|
||||
userRole = 'readonly';
|
||||
} else if (userAccessLevel.level === 'editor') {
|
||||
userRole = 'editor';
|
||||
}
|
||||
|
||||
// Проверяем право доступа
|
||||
if (!hasPermission(userRole, requiredPermission)) {
|
||||
console.log(`[Router] Доступ запрещен: роль ${userRole} не имеет права ${requiredPermission}`);
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
|
||||
// Есть право - разрешаем переход
|
||||
next();
|
||||
} catch (error) {
|
||||
// При ошибке также перенаправляем на главную
|
||||
next({ name: 'home' });
|
||||
console.error('[Router] Ошибка проверки прав:', error);
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -106,7 +106,6 @@ export async function connectWithWallet() {
|
||||
localStorage.setItem('isAuthenticated', 'true');
|
||||
localStorage.setItem('userId', verificationResponse.data.userId);
|
||||
localStorage.setItem('address', verificationResponse.data.address);
|
||||
localStorage.setItem('isAdmin', verificationResponse.data.isAdmin);
|
||||
}
|
||||
|
||||
return verificationResponse.data;
|
||||
|
||||
@@ -137,7 +137,6 @@ export const connectWallet = async () => {
|
||||
success: true,
|
||||
address: normalizedAddress,
|
||||
userId: verifyResponse.data.userId,
|
||||
isAdmin: verifyResponse.data.isAdmin,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
:attachments="chatAttachments"
|
||||
:newMessage="chatNewMessage"
|
||||
:isLoading="isLoadingMessages"
|
||||
:isAdmin="true"
|
||||
:canSend="true"
|
||||
:canGenerateAI="false"
|
||||
:canSelectMessages="false"
|
||||
@send-message="handleSendMessage"
|
||||
@update:newMessage="val => chatNewMessage = val"
|
||||
@update:attachments="val => chatAttachments = val"
|
||||
|
||||
@@ -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="markMessagesAsRead"
|
||||
<ContactTable v-if="canViewContacts" :contacts="contacts" :new-contacts="newContacts" :new-messages="newMessages" @markNewAsRead="markMessagesAsRead"
|
||||
:markMessagesAsReadForUser="markMessagesAsReadForUser" :markContactAsRead="markContactAsRead" @close="goBack" />
|
||||
|
||||
<!-- Таблица-заглушка для обычных пользователей -->
|
||||
@@ -96,21 +96,31 @@ import { usePermissions } from '@/composables/usePermissions';
|
||||
|
||||
const {
|
||||
contacts, newContacts, newMessages,
|
||||
markMessagesAsRead, markMessagesAsReadForUser, markContactAsRead
|
||||
markMessagesAsRead, markMessagesAsReadForUser, markContactAsRead, fetchContacts, clearContactsData
|
||||
} = useContactsAndMessagesWebSocket();
|
||||
const router = useRouter();
|
||||
const auth = useAuthContext();
|
||||
const { canRead } = usePermissions();
|
||||
const { canViewContacts } = 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
|
||||
canViewContacts: canViewContacts.value,
|
||||
userAccessLevel: auth.userAccessLevel,
|
||||
userId: auth.userId,
|
||||
address: auth.address
|
||||
});
|
||||
|
||||
// Логика обновления данных централизована в useContactsWebSocket
|
||||
});
|
||||
|
||||
// Отслеживаем изменения прав доступа
|
||||
watch(canViewContacts, (newValue, oldValue) => {
|
||||
console.log('[ContactsView] canViewContacts changed:', { newValue, oldValue });
|
||||
if (newValue && !oldValue) {
|
||||
// Если права появились, загружаем данные
|
||||
fetchContacts();
|
||||
}
|
||||
});
|
||||
|
||||
function goBack() {
|
||||
|
||||
@@ -82,6 +82,20 @@ const emit = defineEmits(['auth-action-completed']);
|
||||
const auth = useAuthContext();
|
||||
const router = useRouter();
|
||||
const isLoading = ref(true);
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[CrmView] Clearing CRM data');
|
||||
// Очищаем данные при выходе из системы
|
||||
contacts.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[CrmView] Refreshing CRM data');
|
||||
loadContacts(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
const dleList = ref([]);
|
||||
const selectedDleIndex = ref(null);
|
||||
|
||||
|
||||
@@ -103,6 +103,18 @@
|
||||
|
||||
// Подписка на события авторизации
|
||||
unsubscribe = eventBus.on('auth-state-changed', handleAuthEvent);
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[HomeView] Clearing chat data');
|
||||
// Очищаем данные при выходе из системы
|
||||
messages.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[HomeView] Refreshing chat data');
|
||||
loadMessages(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
@@ -54,7 +54,7 @@ import { usePermissions } from '@/composables/usePermissions';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { canRead } = usePermissions();
|
||||
const { canChatWithAdmins } = usePermissions();
|
||||
|
||||
const isLoading = ref(true);
|
||||
const personalMessages = ref([]);
|
||||
@@ -150,14 +150,14 @@ const formatDate = (dateString) => {
|
||||
|
||||
// Следим за изменениями роута для обновления при возврате на страницу
|
||||
watch(() => route.path, async (newPath) => {
|
||||
if (newPath === '/personal-messages' && canRead.value) {
|
||||
if (newPath === '/personal-messages' && canChatWithAdmins.value) {
|
||||
console.log('[PersonalMessagesView] Возврат на страницу, обновляем список');
|
||||
await fetchPersonalMessages();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (canRead.value) {
|
||||
if (canChatWithAdmins.value) {
|
||||
await fetchPersonalMessages();
|
||||
connectWebSocket();
|
||||
}
|
||||
|
||||
@@ -57,6 +57,20 @@ const router = useRouter();
|
||||
const route = useRoute();
|
||||
const isLoading = ref(true);
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[SettingsView] Clearing settings data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// SettingsView не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[SettingsView] Refreshing settings data');
|
||||
// SettingsView не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
// Вычисляемый заголовок страницы в зависимости от роута
|
||||
const pageTitle = computed(() => {
|
||||
if (route.name === 'settings-blockchain-dle-deploy') {
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<div class="call-to-action">
|
||||
<h2>Настройте VDS сервер</h2>
|
||||
<p>Для использования всех функций управления VDS сервером необходимо его настроить.</p>
|
||||
<button class="setup-btn" @click="goToSetup">
|
||||
<button
|
||||
class="setup-btn"
|
||||
@click="canManageSettings ? goToSetup() : null"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
Перейти к настройке VDS
|
||||
</button>
|
||||
</div>
|
||||
@@ -130,6 +134,7 @@
|
||||
import { defineProps, defineEmits, ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import BaseLayout from '../components/BaseLayout.vue';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@@ -143,6 +148,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['auth-action-completed']);
|
||||
|
||||
const router = useRouter();
|
||||
const { canManageSettings } = usePermissions();
|
||||
|
||||
// Состояние VDS
|
||||
const vdsConfigured = ref(false);
|
||||
@@ -436,12 +442,20 @@ onMounted(() => {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.setup-btn:hover {
|
||||
.setup-btn:hover:not(:disabled) {
|
||||
background: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.setup-btn:disabled {
|
||||
background: #e0e0e0 !important;
|
||||
color: #aaa !important;
|
||||
cursor: not-allowed !important;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Адаптивность */
|
||||
@media (max-width: 768px) {
|
||||
.mock-header {
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
<p><strong>Кошелек:</strong> {{ contact.wallet || '-' }}</p>
|
||||
<p><strong>Дата создания:</strong> {{ formatDate(contact.created_at) }}</p>
|
||||
<div class="confirm-actions">
|
||||
<button v-if="canDelete" class="delete-btn" @click="deleteContact" :disabled="isDeleting">Удалить</button>
|
||||
<button v-if="canDeleteData" class="delete-btn" @click="deleteContact" :disabled="isDeleting">Удалить</button>
|
||||
<button class="cancel-btn" @click="cancelDelete" :disabled="isDeleting">Отменить</button>
|
||||
</div>
|
||||
<div v-if="!canDelete" class="empty-table-placeholder">Нет прав для удаления контакта</div>
|
||||
<div v-if="!canDeleteData" class="empty-table-placeholder">Нет прав для удаления контакта</div>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,10 +42,23 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const contact = ref(null);
|
||||
const isLoading = ref(true);
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[ContactDeleteConfirm] Clearing contact data');
|
||||
// Очищаем данные при выходе из системы
|
||||
contact.value = null;
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[ContactDeleteConfirm] Refreshing contact data');
|
||||
loadContact(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
const isDeleting = ref(false);
|
||||
const error = ref('');
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canDelete } = usePermissions();
|
||||
const { canDeleteData } = usePermissions();
|
||||
|
||||
function formatDate(date) {
|
||||
if (!date) return '-';
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
<template>
|
||||
<BaseLayout>
|
||||
<div v-if="!canRead" class="empty-table-placeholder">Нет доступа</div>
|
||||
<div v-else class="contact-details-page">
|
||||
<!-- Доступ проверяет router guard, v-if не нужен -->
|
||||
<div class="contact-details-page">
|
||||
<div v-if="isLoading">Загрузка...</div>
|
||||
<div v-else-if="!contact">Контакт не найден</div>
|
||||
<div v-else class="contact-details-content">
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="contact-info-block">
|
||||
<div>
|
||||
<strong>Имя:</strong>
|
||||
<template v-if="canEdit">
|
||||
<template v-if="canEditContacts">
|
||||
<input v-model="editableName" class="edit-input" @blur="saveName" @keyup.enter="saveName" />
|
||||
<span v-if="isSavingName" class="saving">Сохранение...</span>
|
||||
</template>
|
||||
@@ -41,10 +41,10 @@
|
||||
<div class="selected-langs">
|
||||
<span v-for="lang in selectedLanguages" :key="lang" class="lang-tag">
|
||||
{{ getLanguageLabel(lang) }}
|
||||
<span v-if="canEdit" class="remove-tag" @click="removeLanguage(lang)">×</span>
|
||||
<span v-if="canEditContacts" class="remove-tag" @click="removeLanguage(lang)">×</span>
|
||||
</span>
|
||||
<input
|
||||
v-if="canEdit"
|
||||
v-if="canEditContacts"
|
||||
v-model="langInput"
|
||||
@focus="showLangDropdown = true"
|
||||
@input="showLangDropdown = true"
|
||||
@@ -53,7 +53,7 @@
|
||||
placeholder="Добавить язык..."
|
||||
/>
|
||||
</div>
|
||||
<ul v-if="showLangDropdown && canEdit" class="lang-dropdown">
|
||||
<ul v-if="showLangDropdown && canEditContacts" class="lang-dropdown">
|
||||
<li
|
||||
v-for="lang in filteredLanguages"
|
||||
:key="lang.value"
|
||||
@@ -72,15 +72,15 @@
|
||||
<strong>Теги пользователя:</strong>
|
||||
<span v-for="tag in userTags" :key="tag.id" class="user-tag">
|
||||
{{ tag.name }}
|
||||
<span v-if="canEdit" class="remove-tag" @click="removeUserTag(tag.id)">×</span>
|
||||
<span v-if="canManageTags" class="remove-tag" @click="removeUserTag(tag.id)">×</span>
|
||||
</span>
|
||||
<button v-if="canEdit" class="add-tag-btn" @click="openTagModal">Добавить тег</button>
|
||||
<button v-if="canManageTags" class="add-tag-btn" @click="openTagModal">Добавить тег</button>
|
||||
</div>
|
||||
<div class="block-user-section">
|
||||
<strong>Статус блокировки:</strong>
|
||||
<span v-if="contact.is_blocked" class="blocked-status">Заблокирован</span>
|
||||
<span v-else class="unblocked-status">Не заблокирован</span>
|
||||
<template v-if="canEdit">
|
||||
<template v-if="canBlockUsers">
|
||||
<el-button
|
||||
v-if="!contact.is_blocked"
|
||||
type="danger"
|
||||
@@ -97,7 +97,7 @@
|
||||
>Разблокировать</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="delete-actions">
|
||||
<div class="delete-actions" v-if="canDeleteData">
|
||||
<button class="delete-history-btn" @click="deleteMessagesHistory">Удалить историю сообщений</button>
|
||||
<button class="delete-btn" @click="deleteContact">Удалить контакт</button>
|
||||
</div>
|
||||
@@ -109,14 +109,16 @@
|
||||
:isLoading="isLoadingMessages"
|
||||
:attachments="chatAttachments"
|
||||
:newMessage="chatNewMessage"
|
||||
:isAdmin="canEdit"
|
||||
:canSend="canSendToUsers"
|
||||
:canGenerateAI="canGenerateAI"
|
||||
:canSelectMessages="canGenerateAI"
|
||||
@send-message="handleSendMessage"
|
||||
@update:newMessage="val => chatNewMessage = val"
|
||||
@update:attachments="val => chatAttachments = val"
|
||||
@ai-reply="handleAiReply"
|
||||
/>
|
||||
</div>
|
||||
<el-dialog v-if="canEdit" v-model="showTagModal" title="Добавить тег пользователю">
|
||||
<el-dialog v-if="canManageTags" v-model="showTagModal" title="Добавить тег пользователю">
|
||||
<div v-if="allTags.length">
|
||||
<el-select
|
||||
v-model="selectedTags"
|
||||
@@ -160,6 +162,24 @@ import contactsService from '../../services/contactsService.js';
|
||||
import messagesService from '../../services/messagesService.js';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import { useContactsAndMessagesWebSocket } from '@/composables/useContactsWebSocket';
|
||||
const { canEditContacts, canDeleteData, canManageTags, canBlockUsers, canSendToUsers, canGenerateAI, canViewContacts } = usePermissions();
|
||||
const { markContactAsRead } = useContactsAndMessagesWebSocket();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[ContactDetailsView] Clearing contact data');
|
||||
// Очищаем данные при выходе из системы
|
||||
contact.value = null;
|
||||
messages.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[ContactDetailsView] Refreshing contact data');
|
||||
reloadContact(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import tablesService from '../../services/tablesService';
|
||||
import { useTagsWebSocket } from '../../composables/useTagsWebSocket';
|
||||
@@ -183,8 +203,6 @@ const newTagDescription = ref('');
|
||||
const messages = ref([]);
|
||||
const chatAttachments = ref([]);
|
||||
const chatNewMessage = ref('');
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canRead, canEdit, canDelete } = usePermissions();
|
||||
const isAiLoading = ref(false);
|
||||
const conversationId = ref(null);
|
||||
|
||||
@@ -253,7 +271,7 @@ async function loadAllTags() {
|
||||
}
|
||||
|
||||
function openTagModal() {
|
||||
if (!canEdit.value) return;
|
||||
if (!canManageTags.value) return;
|
||||
showTagModal.value = true;
|
||||
loadAllTags();
|
||||
}
|
||||
@@ -293,7 +311,7 @@ function getLanguageLabel(val) {
|
||||
return found ? found.label : val;
|
||||
}
|
||||
function addLanguage(lang) {
|
||||
if (!canEdit.value) return;
|
||||
if (!canEditContacts.value) return;
|
||||
if (!selectedLanguages.value.includes(lang)) {
|
||||
selectedLanguages.value.push(lang);
|
||||
saveLanguages();
|
||||
@@ -302,17 +320,17 @@ function addLanguage(lang) {
|
||||
showLangDropdown.value = false;
|
||||
}
|
||||
function addLanguageFromInput() {
|
||||
if (!canEdit.value) return;
|
||||
if (!canEditContacts.value) return;
|
||||
const found = filteredLanguages.value[0];
|
||||
if (found) addLanguage(found.value);
|
||||
}
|
||||
function removeLanguage(lang) {
|
||||
if (!canEdit.value) return;
|
||||
if (!canEditContacts.value) return;
|
||||
selectedLanguages.value = selectedLanguages.value.filter(l => l !== lang);
|
||||
saveLanguages();
|
||||
}
|
||||
function saveLanguages() {
|
||||
if (!canEdit.value) return;
|
||||
if (!canEditContacts.value) return;
|
||||
isSavingLangs.value = true;
|
||||
contactsService.updateContact(contact.value.id, { language: selectedLanguages.value })
|
||||
.then(() => reloadContact())
|
||||
@@ -397,7 +415,7 @@ async function loadMessages() {
|
||||
|
||||
// Получаем conversationId только для зарегистрированных пользователей
|
||||
// Гости не имеют conversations
|
||||
if (!contact.value.id.startsWith('guest_')) {
|
||||
if (!String(contact.value.id).startsWith('guest_')) {
|
||||
try {
|
||||
const conv = await messagesService.getConversationByUserId(contact.value.id);
|
||||
conversationId.value = conv?.id || null;
|
||||
@@ -554,7 +572,7 @@ async function unblockUser() {
|
||||
|
||||
// --- Теги ---
|
||||
async function createTag() {
|
||||
if (!canEdit.value) return;
|
||||
if (!canManageTags.value) return;
|
||||
if (!newTagName.value) return;
|
||||
const tableId = await ensureTagsTable();
|
||||
const table = await tablesService.getTable(tableId);
|
||||
@@ -614,7 +632,7 @@ async function loadUserTags() {
|
||||
|
||||
// После добавления/удаления тегов всегда обновляем userTags
|
||||
async function addTagsToUser() {
|
||||
if (!canEdit.value) return;
|
||||
if (!canManageTags.value) return;
|
||||
if (!contact.value || !contact.value.id) return;
|
||||
if (!selectedTags.value || selectedTags.value.length === 0) return;
|
||||
try {
|
||||
@@ -628,7 +646,7 @@ async function addTagsToUser() {
|
||||
}
|
||||
|
||||
async function removeUserTag(tagId) {
|
||||
if (!canEdit.value) return;
|
||||
if (!canManageTags.value) return;
|
||||
if (!contact.value || !contact.value.id) return;
|
||||
try {
|
||||
await contactsService.removeTagFromContact(contact.value.id, tagId);
|
||||
@@ -644,6 +662,17 @@ onMounted(async () => {
|
||||
await loadUserTags();
|
||||
await loadMessages();
|
||||
|
||||
// Помечаем контакт как прочитанный при загрузке страницы
|
||||
// Для всех админов (EDITOR и READONLY) - каждый видит свой статус просмотра
|
||||
console.log('[ContactDetailsView] DEBUG - canViewContacts:', canViewContacts.value);
|
||||
console.log('[ContactDetailsView] DEBUG - userId:', userId.value);
|
||||
if (userId.value && canViewContacts.value) {
|
||||
console.log('[ContactDetailsView] Marking contact as read (admin):', userId.value);
|
||||
await markContactAsRead(userId.value);
|
||||
} else if (userId.value) {
|
||||
console.log('[ContactDetailsView] Skipping markContactAsRead - user is not admin');
|
||||
}
|
||||
|
||||
// Подписываемся на обновления тегов
|
||||
unsubscribeFromTags = onTagsUpdate(async () => {
|
||||
// console.log('[ContactDetailsView] Получено обновление тегов, перезагружаем списки тегов');
|
||||
|
||||
@@ -22,17 +22,13 @@
|
||||
<!-- Заголовок страницы -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>📄 Управление контентом</h1>
|
||||
<p v-if="isAdmin && address">Создавайте и управляйте страницами вашего DLE</p>
|
||||
<h1>Управление контентом</h1>
|
||||
<p v-if="canEditData && address">Создавайте и управляйте страницами вашего DLE</p>
|
||||
<p v-else>Просмотр опубликованных страниц DLE</p>
|
||||
<button v-if="isAdmin && address" class="btn btn-primary" @click="goToCreate">
|
||||
<button v-if="canEditData && address" class="btn btn-primary" @click="goToCreate">
|
||||
<i class="fas fa-plus"></i>
|
||||
Создать страницу
|
||||
</button>
|
||||
<button v-else class="btn btn-primary" @click="goToPublicPages">
|
||||
<i class="fas fa-eye"></i>
|
||||
Публичные страницы
|
||||
</button>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
@@ -68,7 +64,7 @@
|
||||
<!-- Вкладка Страницы -->
|
||||
<div v-if="activeTab === 'pages'" class="pages-section">
|
||||
<div class="section-header">
|
||||
<h2 v-if="isAdmin && address">Созданные страницы</h2>
|
||||
<h2 v-if="canEditData && address">Созданные страницы</h2>
|
||||
<h2 v-else>Опубликованные страницы</h2>
|
||||
<div class="search-box">
|
||||
<input
|
||||
@@ -91,7 +87,7 @@
|
||||
>
|
||||
<div class="page-card-header">
|
||||
<h3>{{ page.title }}</h3>
|
||||
<div class="page-actions" v-if="isAdmin && address">
|
||||
<div class="page-actions" v-if="canEditData && address">
|
||||
<button
|
||||
class="action-btn edit-btn"
|
||||
@click.stop="goToEdit(page.id)"
|
||||
@@ -133,18 +129,14 @@
|
||||
<div class="empty-icon">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</div>
|
||||
<h3 v-if="isAdmin && address">Нет созданных страниц</h3>
|
||||
<h3 v-if="canEditData && address">Нет созданных страниц</h3>
|
||||
<h3 v-else>Нет опубликованных страниц</h3>
|
||||
<p v-if="isAdmin && address">Создайте первую страницу для вашего DLE</p>
|
||||
<p v-if="canEditData && address">Создайте первую страницу для вашего DLE</p>
|
||||
<p v-else>Публичные страницы появятся здесь после их создания администраторами</p>
|
||||
<button v-if="isAdmin && address" class="btn btn-primary" @click="goToCreate">
|
||||
<button v-if="canEditData && address" class="btn btn-primary" @click="goToCreate">
|
||||
<i class="fas fa-plus"></i>
|
||||
Создать страницу
|
||||
</button>
|
||||
<button v-else class="btn btn-primary" @click="goToPublicPages">
|
||||
<i class="fas fa-eye"></i>
|
||||
Публичные страницы
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Загрузка -->
|
||||
@@ -193,6 +185,7 @@ import { useRouter } from 'vue-router';
|
||||
import BaseLayout from '../../components/BaseLayout.vue';
|
||||
import pagesService from '../../services/pagesService';
|
||||
import { useAuthContext } from '../../composables/useAuth';
|
||||
import { usePermissions } from '../../composables/usePermissions';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@@ -218,7 +211,22 @@ const props = defineProps({
|
||||
const emit = defineEmits(['auth-action-completed']);
|
||||
|
||||
const router = useRouter();
|
||||
const { isAdmin, address } = useAuthContext();
|
||||
const { address } = useAuthContext();
|
||||
const { canEditData } = usePermissions();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[ContentListView] Clearing pages data');
|
||||
// Очищаем данные при выходе из системы
|
||||
pages.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[ContentListView] Refreshing pages data');
|
||||
loadPages(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
// Состояние
|
||||
const activeTab = ref('pages');
|
||||
@@ -250,16 +258,13 @@ function goToCreate() {
|
||||
router.push({ name: 'content-create' });
|
||||
}
|
||||
|
||||
function goToPublicPages() {
|
||||
router.push({ name: 'public-pages' });
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
function goToPage(id) {
|
||||
if (isAdmin.value && address.value) {
|
||||
if (canEditData.value && address.value) {
|
||||
router.push({ name: 'page-view', params: { id } });
|
||||
} else {
|
||||
router.push({ name: 'public-page-view', params: { id } });
|
||||
@@ -307,7 +312,7 @@ async function loadPages() {
|
||||
isLoading.value = true;
|
||||
|
||||
// Проверяем роль админа через кошелек
|
||||
if (isAdmin.value && address.value) {
|
||||
if (canEditData.value && address.value) {
|
||||
try {
|
||||
// Пытаемся загрузить админские страницы
|
||||
const response = await pagesService.getPages();
|
||||
|
||||
@@ -19,13 +19,7 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="public-page-view">
|
||||
<!-- Кнопка назад -->
|
||||
<div class="back-button">
|
||||
<button class="btn btn-outline" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Назад к списку страниц
|
||||
</button>
|
||||
</div>
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
|
||||
<!-- Заголовок страницы -->
|
||||
<div class="page-header" v-if="page">
|
||||
@@ -127,7 +121,7 @@ const isLoading = ref(false);
|
||||
|
||||
// Методы
|
||||
function goBack() {
|
||||
router.push({ name: 'public-pages' });
|
||||
router.push({ name: 'content-list' });
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
@@ -176,10 +170,24 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
margin-bottom: 20px;
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
color: #bbb;
|
||||
transition: color 0.2s;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
||||
@@ -99,6 +99,20 @@ const editMode = ref(false);
|
||||
|
||||
const auth = useAuthContext();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[EmailSettingsView] Clearing Email settings data');
|
||||
// Очищаем данные при выходе из системы
|
||||
settings.value = { smtpHost: '', smtpPort: '', smtpUser: '', smtpPass: '', enabled: false };
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[EmailSettingsView] Refreshing Email settings data');
|
||||
loadEmailSettings(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
const loadEmailSettings = async () => {
|
||||
// Не загружаем если не авторизован
|
||||
if (!auth.isAuthenticated.value) {
|
||||
|
||||
@@ -58,6 +58,20 @@ const editMode = ref(false);
|
||||
|
||||
const auth = useAuthContext();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[TelegramSettingsView] Clearing Telegram settings data');
|
||||
// Очищаем данные при выходе из системы
|
||||
settings.value = { botToken: '', webhookUrl: '', enabled: false };
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[TelegramSettingsView] Refreshing Telegram settings data');
|
||||
loadTelegramSettings(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
const loadTelegramSettings = async () => {
|
||||
// Не загружаем если не авторизован
|
||||
if (!auth.isAuthenticated.value) {
|
||||
|
||||
@@ -67,12 +67,26 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import AIProviderSettings from './AIProviderSettings.vue';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import NoAccessModal from '@/components/NoAccessModal.vue';
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[AiSettingsView] Clearing AI settings data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// AiSettingsView не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[AiSettingsView] Refreshing AI settings data');
|
||||
// AiSettingsView не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
const showProvider = ref(null);
|
||||
const showTelegramSettings = ref(false);
|
||||
const showEmailSettings = ref(false);
|
||||
@@ -80,7 +94,6 @@ const showDbSettings = ref(false);
|
||||
const showAiAssistantSettings = ref(false);
|
||||
const showNoAccessModal = ref(false);
|
||||
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canManageSettings } = usePermissions();
|
||||
|
||||
const providerLabels = {
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
<span><strong>Editor:</strong> {{ token.editorThreshold || 2 }} токен{{ token.editorThreshold === 1 ? '' : token.editorThreshold < 5 ? 'а' : 'ов' }}</span>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="canEdit ? 'btn-danger' : 'btn-secondary'"
|
||||
@click="canEdit ? removeToken(index) : null"
|
||||
:disabled="!canEdit"
|
||||
:class="canManageSettings ? 'btn-danger' : 'btn-secondary'"
|
||||
@click="canManageSettings ? removeToken(index) : null"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
@@ -53,7 +53,7 @@
|
||||
v-model="newToken.name"
|
||||
class="form-control"
|
||||
placeholder="test2"
|
||||
:disabled="!canEdit"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -63,12 +63,12 @@
|
||||
v-model="newToken.address"
|
||||
class="form-control"
|
||||
placeholder="0x..."
|
||||
:disabled="!canEdit"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Сеть:</label>
|
||||
<select v-model="newToken.network" class="form-control" :disabled="!canEdit">
|
||||
<select v-model="newToken.network" class="form-control" :disabled="!canManageSettings">
|
||||
<option value="">-- Выберите сеть --</option>
|
||||
<optgroup v-for="(group, groupIndex) in networkGroups" :key="groupIndex" :label="group.label">
|
||||
<option v-for="option in group.options" :key="option.value" :value="option.value">
|
||||
@@ -86,7 +86,7 @@
|
||||
placeholder="0"
|
||||
min="0"
|
||||
step="0.01"
|
||||
:disabled="!canEdit"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
<small class="form-text">Минимальный баланс токена для получения доступа</small>
|
||||
</div>
|
||||
@@ -102,7 +102,7 @@
|
||||
class="form-control"
|
||||
placeholder="1"
|
||||
min="1"
|
||||
:disabled="!canEdit"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
<small class="form-text">Количество токенов для получения прав только на чтение</small>
|
||||
</div>
|
||||
@@ -114,16 +114,16 @@
|
||||
class="form-control"
|
||||
placeholder="2"
|
||||
min="2"
|
||||
:disabled="!canEdit"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
<small class="form-text">Количество токенов для получения прав на редактирование и удаление</small>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn"
|
||||
:class="canEdit ? 'btn-primary' : 'btn-secondary'"
|
||||
@click="canEdit ? addToken() : null"
|
||||
:disabled="!canEdit"
|
||||
:class="canManageSettings ? 'btn-primary' : 'btn-secondary'"
|
||||
@click="canManageSettings ? addToken() : null"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
Добавить токен
|
||||
</button>
|
||||
@@ -132,7 +132,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed } from 'vue';
|
||||
import { reactive, computed, onMounted } from 'vue';
|
||||
import useBlockchainNetworks from '@/composables/useBlockchainNetworks';
|
||||
import api from '@/api/axios';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
@@ -152,8 +152,22 @@ const newToken = reactive({
|
||||
});
|
||||
|
||||
const { networkGroups, networks } = useBlockchainNetworks();
|
||||
const { isAdmin, checkTokenBalances, address, checkAuth, userAccessLevel, checkUserAccessLevel } = useAuthContext();
|
||||
const { canEdit, getLevelClass, getLevelDescription } = usePermissions();
|
||||
const { checkTokenBalances, address, checkAuth, userAccessLevel, checkUserAccessLevel } = useAuthContext();
|
||||
const { canManageSettings, getLevelClass, getLevelDescription } = usePermissions();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[AuthTokensSettings] Clearing tokens data');
|
||||
// Очищаем данные при выходе из системы
|
||||
tokens.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[AuthTokensSettings] Refreshing tokens data');
|
||||
loadTokens(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
async function addToken() {
|
||||
if (!newToken.name || !newToken.address || !newToken.network) {
|
||||
|
||||
@@ -854,8 +854,8 @@
|
||||
@click="deploySmartContracts"
|
||||
type="button"
|
||||
class="btn btn-primary btn-lg deploy-btn"
|
||||
:disabled="!isFormValid || !canEdit || adminTokenCheck.isLoading"
|
||||
:title="`isFormValid: ${isFormValid}, isAdmin: ${adminTokenCheck.isAdmin}, isLoading: ${adminTokenCheck.isLoading}`"
|
||||
:disabled="!isFormValid || !canManageSettings || adminTokenCheck.isLoading"
|
||||
:title="`isFormValid: ${isFormValid}, canManageSettings: ${canManageSettings}, isLoading: ${adminTokenCheck.isLoading}`"
|
||||
>
|
||||
<i class="fas fa-cogs"></i>
|
||||
Поэтапный деплой DLE
|
||||
@@ -921,13 +921,27 @@ function normalizePrivateKey(raw) {
|
||||
|
||||
|
||||
// Получаем контекст авторизации для адреса кошелька
|
||||
const { address, isAdmin } = useAuthContext();
|
||||
const { canEdit } = usePermissions();
|
||||
const { address } = useAuthContext();
|
||||
const { canManageSettings } = usePermissions();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[DleDeployFormView] Clearing DLE deploy data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// DleDeployFormView не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[DleDeployFormView] Refreshing DLE deploy data');
|
||||
checkAdminTokens(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
// Состояние для проверки админских токенов
|
||||
const adminTokenCheck = ref({
|
||||
isLoading: false,
|
||||
isAdmin: false,
|
||||
canManageSettings: false,
|
||||
error: null
|
||||
});
|
||||
|
||||
@@ -2381,7 +2395,7 @@ watch(address, (newAddress) => {
|
||||
// Функция проверки админских токенов
|
||||
const checkAdminTokens = async () => {
|
||||
if (!address.value) {
|
||||
adminTokenCheck.value = { isLoading: false, isAdmin: false, error: 'Кошелек не подключен' };
|
||||
adminTokenCheck.value = { isLoading: false, canManageSettings: false, error: 'Кошелек не подключен' };
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2391,7 +2405,7 @@ const checkAdminTokens = async () => {
|
||||
const response = await api.get(`/dle-v2/check-admin-tokens?address=${address.value}`);
|
||||
|
||||
if (response.data.success) {
|
||||
adminTokenCheck.value = { ...adminTokenCheck.value, isAdmin: response.data.data.isAdmin };
|
||||
adminTokenCheck.value = { ...adminTokenCheck.value, canManageSettings: response.data.data.userAccessLevel.hasAccess };
|
||||
console.log('Проверка админских токенов:', response.data.data);
|
||||
} else {
|
||||
adminTokenCheck.value = { ...adminTokenCheck.value, error: response.data.message || 'Ошибка проверки токенов' };
|
||||
@@ -2589,7 +2603,7 @@ const handleDeploymentCompleted = (result) => {
|
||||
console.log('🔍 unifiedPrivateKey.value:', unifiedPrivateKey.value);
|
||||
console.log('🔍 keyValidation.unified:', keyValidation.unified);
|
||||
console.log('🔍 dleSettings.coordinates:', dleSettings.coordinates);
|
||||
console.log('🔍 Кнопка должна быть активна:', !(!validation.jurisdiction || !validation.name || !validation.tokenSymbol || !validation.partners || !validation.partnersValid || !validation.quorum || !validation.networks || !validation.privateKey || !validation.keyValid || !validation.coordinates) && adminTokenCheck.value.isAdmin && !adminTokenCheck.value.isLoading);
|
||||
console.log('🔍 Кнопка должна быть активна:', !(!validation.jurisdiction || !validation.name || !validation.tokenSymbol || !validation.partners || !validation.partnersValid || !validation.quorum || !validation.networks || !validation.privateKey || !validation.keyValid || !validation.coordinates) && adminTokenCheck.value.canManageSettings && !adminTokenCheck.value.isLoading);
|
||||
|
||||
return Boolean(
|
||||
validation.jurisdiction &&
|
||||
|
||||
@@ -73,7 +73,13 @@
|
||||
<span class="feature">✓ Безопасно</span>
|
||||
<span class="feature">✓ Для локальных и VPS</span>
|
||||
</div>
|
||||
<button class="btn-primary" @click="goToWebSsh">Подробнее</button>
|
||||
<button
|
||||
class="btn-primary"
|
||||
@click="canManageSettings ? goToWebSsh() : null"
|
||||
:disabled="!canManageSettings"
|
||||
>
|
||||
Подробнее
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно с формой WEB SSH -->
|
||||
@@ -93,9 +99,23 @@ import { useRouter } from 'vue-router';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import NoAccessModal from '@/components/NoAccessModal.vue';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[InterfaceSettingsView] Clearing interface data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// InterfaceSettingsView не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[InterfaceSettingsView] Refreshing interface data');
|
||||
// InterfaceSettingsView не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
import { ref } from 'vue';
|
||||
const router = useRouter();
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canManageSettings } = usePermissions();
|
||||
const goBack = () => router.push('/settings');
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import WebSshForm from '@/components/WebSshForm.vue';
|
||||
import Header from '@/components/Header.vue';
|
||||
@@ -57,6 +57,20 @@ const toggleSidebar = () => {
|
||||
const auth = useAuthContext();
|
||||
const isAuthenticated = auth.isAuthenticated.value;
|
||||
const identities = auth.identities?.value || [];
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[InterfaceWebSshView] Clearing WebSSH data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// InterfaceWebSshView не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[InterfaceWebSshView] Refreshing WebSSH data');
|
||||
// InterfaceWebSshView не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
const tokenBalances = auth.tokenBalances?.value || [];
|
||||
const isLoadingTokens = false;
|
||||
</script>
|
||||
|
||||
@@ -80,6 +80,20 @@ import { usePermissions } from '@/composables/usePermissions';
|
||||
import NoAccessModal from '@/components/NoAccessModal.vue';
|
||||
import wsClient from '@/utils/websocket';
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[SecuritySettingsView] Clearing security data');
|
||||
// Очищаем данные при выходе из системы
|
||||
// SecuritySettingsView не нуждается в очистке данных
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[SecuritySettingsView] Refreshing security data');
|
||||
// SecuritySettingsView не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
// Состояние для отображения/скрытия дополнительных настроек
|
||||
const showRpcSettings = ref(false);
|
||||
const showAuthSettings = ref(false);
|
||||
@@ -88,7 +102,6 @@ const isSaving = ref(false);
|
||||
const showNoAccessModal = ref(false);
|
||||
|
||||
// Получаем контекст авторизации
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canManageSettings } = usePermissions();
|
||||
|
||||
// Настройки безопасности
|
||||
|
||||
@@ -186,6 +186,20 @@ const { address, isAuthenticated, tokenBalances, checkTokenBalances } = useAuthC
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[CreateProposalView] Clearing DLE proposal data');
|
||||
// Очищаем данные при выходе из системы
|
||||
dleInfo.value = null;
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[CreateProposalView] Refreshing DLE proposal data');
|
||||
loadDLEInfo(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
// Получаем адрес DLE из URL
|
||||
const dleAddress = computed(() => {
|
||||
const address = route.query.address || props.dleAddress;
|
||||
|
||||
@@ -154,6 +154,20 @@ const emit = defineEmits(['auth-action-completed']);
|
||||
const router = useRouter();
|
||||
const { address } = useAuthContext();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[DleManagementView] Clearing DLE management data');
|
||||
// Очищаем данные при выходе из системы
|
||||
dles.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[DleManagementView] Refreshing DLE management data');
|
||||
loadDleList(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
// Состояние формы
|
||||
const isAdding = ref(false);
|
||||
|
||||
|
||||
@@ -110,6 +110,20 @@ const goBackToBlocks = () => {
|
||||
// Получаем адрес пользователя из контекста аутентификации
|
||||
const { address: userAddress } = useAuthContext();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[SettingsView] Clearing DLE settings data');
|
||||
// Очищаем данные при выходе из системы
|
||||
dleInfo.value = null;
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[SettingsView] Refreshing DLE settings data');
|
||||
loadDLEInfo(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
// Загружаем информацию о DLE
|
||||
const loadDLEInfo = async () => {
|
||||
if (!address) {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<BaseLayout>
|
||||
<div class="create-table-container">
|
||||
<h2>Создать новую таблицу</h2>
|
||||
<form v-if="canEdit" @submit.prevent="handleCreateTable" class="create-table-form">
|
||||
<form v-if="canEditData" @submit.prevent="handleCreateTable" class="create-table-form">
|
||||
<label>Название таблицы</label>
|
||||
<input v-model="newTableName" required placeholder="Введите название" />
|
||||
<label>Описание</label>
|
||||
@@ -38,7 +38,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import BaseLayout from '../../components/BaseLayout.vue';
|
||||
import tablesService from '../../services/tablesService';
|
||||
@@ -49,8 +49,21 @@ const router = useRouter();
|
||||
const newTableName = ref('');
|
||||
const newTableDescription = ref('');
|
||||
const newTableIsRagSourceId = ref(2);
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canEdit } = usePermissions();
|
||||
const { canEditData } = usePermissions();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[CreateTableView] Clearing form data');
|
||||
// Очищаем данные формы при выходе из системы
|
||||
form.value = { name: '', description: '' };
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[CreateTableView] Refreshing form data');
|
||||
// CreateTableView не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
async function handleCreateTable() {
|
||||
if (!newTableName.value) return;
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
<h2>Удалить таблицу?</h2>
|
||||
<p>Вы уверены, что хотите удалить эту таблицу? Это действие необратимо.</p>
|
||||
<div class="actions">
|
||||
<button v-if="canDelete" class="danger" @click="remove">Удалить</button>
|
||||
<button v-if="canDeleteData" class="danger" @click="remove">Удалить</button>
|
||||
<button @click="cancel">Отмена</button>
|
||||
</div>
|
||||
<div v-if="!canDelete" class="empty-table-placeholder">Нет прав для удаления таблицы</div>
|
||||
<div v-if="!canDeleteData" class="empty-table-placeholder">Нет прав для удаления таблицы</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
@@ -29,10 +29,24 @@ import BaseLayout from '../../components/BaseLayout.vue';
|
||||
import axios from 'axios';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import { onMounted } from 'vue';
|
||||
const $route = useRoute();
|
||||
const router = useRouter();
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canDelete } = usePermissions();
|
||||
const { canDeleteData } = usePermissions();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[DeleteTableView] Clearing confirmation data');
|
||||
// Очищаем данные при выходе из системы
|
||||
table.value = null;
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[DeleteTableView] Refreshing confirmation data');
|
||||
// DeleteTableView не нуждается в обновлении данных
|
||||
});
|
||||
});
|
||||
|
||||
async function remove() {
|
||||
await axios.delete(`/tables/${$route.params.id}`);
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<button class="nav-btn" @click="goToTables">Таблицы</button>
|
||||
<button class="nav-btn" @click="goToCreate">Создать таблицу</button>
|
||||
<button class="close-btn" @click="closeTable">Закрыть</button>
|
||||
<button v-if="canEdit" class="action-btn" @click="goToEdit">Редактировать</button>
|
||||
<button v-if="canDelete" class="danger-btn" @click="goToDelete">Удалить</button>
|
||||
<button v-if="canEditData" class="action-btn" @click="goToEdit">Редактировать</button>
|
||||
<button v-if="canDeleteData" class="danger-btn" @click="goToDelete">Удалить</button>
|
||||
</div>
|
||||
<UserTableView v-if="canRead" :table-id="Number($route.params.id)" />
|
||||
<UserTableView v-if="canViewData" :table-id="Number($route.params.id)" />
|
||||
<div v-else class="empty-table-placeholder">Нет данных для отображения</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -32,10 +32,25 @@ import UserTableView from '../../components/tables/UserTableView.vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import { onMounted } from 'vue';
|
||||
const $route = useRoute();
|
||||
const router = useRouter();
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canRead, canEdit, canDelete } = usePermissions();
|
||||
const { canViewData, canEditData, canDeleteData } = usePermissions();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[TableView] Clearing table data');
|
||||
// Очищаем данные при выходе из системы
|
||||
tableData.value = [];
|
||||
columns.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[TableView] Refreshing table data');
|
||||
loadTableData(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
function closeTable() {
|
||||
if (window.history.length > 1) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div class="tables-list-block">
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
<h2>Список таблиц</h2>
|
||||
<UserTablesList v-if="canRead" />
|
||||
<UserTablesList v-if="canViewData" />
|
||||
<div v-else class="empty-table-placeholder">Нет данных для отображения</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -27,9 +27,24 @@ import UserTablesList from '../../components/tables/UserTablesList.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import { usePermissions } from '@/composables/usePermissions';
|
||||
import { onMounted } from 'vue';
|
||||
const router = useRouter();
|
||||
const { isAdmin } = useAuthContext();
|
||||
const { canRead } = usePermissions();
|
||||
const { canViewData } = usePermissions();
|
||||
|
||||
// Подписываемся на централизованные события очистки и обновления данных
|
||||
onMounted(() => {
|
||||
window.addEventListener('clear-application-data', () => {
|
||||
console.log('[TablesListView] Clearing tables data');
|
||||
// Очищаем данные при выходе из системы
|
||||
tables.value = [];
|
||||
});
|
||||
|
||||
window.addEventListener('refresh-application-data', () => {
|
||||
console.log('[TablesListView] Refreshing tables data');
|
||||
loadTables(); // Обновляем данные при входе в систему
|
||||
});
|
||||
});
|
||||
|
||||
function goBack() {
|
||||
router.push({ name: 'crm' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user