Files
DLE/frontend/src/views/smartcontracts/SettingsView.vue

601 lines
18 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/VC-HB3-Accelerator
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="settings-container">
<!-- Основной контент -->
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div v-if="dleInfo?.address" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ dleInfo.address }}
</div>
<div v-else-if="address" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ address }}
</div>
<div v-else-if="isLoading" style="color: var(--color-grey-dark); font-size: 0.9rem;">
Загрузка...
</div>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<div v-if="dleInfo" class="main-content">
<!-- Отображение в футере -->
<div v-if="canSetFooterDle" class="footer-card">
<div class="footer-header">
<h3>Отображение в футере</h3>
</div>
<div class="footer-content">
<p>Выберите этот DLE для отображения в футере приложения. Название будет показано в строке с кнопкой бургера.</p>
<div v-if="isSelectedForFooter" class="selected-info">
<i class="fas fa-check-circle"></i>
<span>Этот DLE отображается в футере</span>
</div>
<div v-else-if="hasFooterDle" class="other-selected-info">
<i class="fas fa-info-circle"></i>
<span>В футере отображается другой DLE: {{ footerDle.value?.name }} ({{ footerDle.value?.symbol }})</span>
</div>
<div class="footer-actions">
<button
v-if="!isSelectedForFooter"
@click="setAsFooterDle"
class="btn-primary"
:disabled="isLoading"
>
<i class="fas fa-eye"></i>
Отображать в футере
</button>
<button
v-if="isSelectedForFooter"
@click="removeFromFooter"
class="btn-danger"
:disabled="isLoading"
>
<i class="fas fa-trash"></i>
Удалить из футера
</button>
<button
v-if="hasFooterDle && !isSelectedForFooter"
@click="removeFromFooter"
class="btn-danger btn-sm"
:disabled="isLoading"
>
<i class="fas fa-trash"></i>
Удалить из футера
</button>
</div>
</div>
</div>
<!-- Удаление DLE -->
<div class="danger-card">
<div class="danger-header">
<h3>Удаление DLE</h3>
</div>
<div class="danger-content">
<p>Полное удаление DLE и всех связанных данных. Это действие необратимо.</p>
<div class="warning-info">
<h4> Важно:</h4>
<ul>
<li>Для деактивации DLE необходимо иметь токены</li>
<li>Может потребоваться голосование участников</li>
<li>DLE должен быть активен</li>
<li>Только владелец токенов может инициировать деактивацию</li>
</ul>
</div>
<button @click="deleteDLE" class="btn-danger" :disabled="isLoading">
{{ isLoading ? 'Загрузка...' : 'Удалить DLE' }}
</button>
</div>
</div>
</div>
<!-- Сообщение если DLE не выбран -->
<div v-if="!address" class="no-dle-card">
<h3>DLE не выбран</h3>
<p>Для управления настройками необходимо выбрать DLE</p>
<button @click="goBackToBlocks" class="btn-primary">
Вернуться к списку DLE
</button>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, defineProps, defineEmits, onMounted, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAuthContext } from '../../composables/useAuth';
import { useFooterDle } from '../../composables/useFooterDle';
import { usePermissions } from '../../composables/usePermissions';
import { ROLES } from '../../composables/permissions';
import BaseLayout from '../../components/BaseLayout.vue';
import { deactivateDLE } from '../../utils/dle-contract.js';
import api from '../../api/axios';
// Определяем props
const props = defineProps({
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Состояние
const isSaving = ref(false);
const dleAddress = ref('');
const dleInfo = ref(null);
const isLoading = ref(false);
// Получаем адрес DLE из URL параметров
const address = route.query.address || props.dleAddress;
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (address) {
router.push(`/management/dle-blocks?address=${address}`);
} else {
router.push('/management');
}
};
// Получаем адрес пользователя из контекста аутентификации
const { address: userAddress } = useAuthContext();
// Используем composable для проверки прав доступа
const { currentRole } = usePermissions();
// Используем composable для выбранного DLE
const { footerDle, setFooterDle, clearFooterDle } = useFooterDle();
// Проверяем, может ли пользователь устанавливать DLE для футера (только редактор)
const canSetFooterDle = computed(() => {
return currentRole.value === ROLES.EDITOR;
});
// Проверяем, выбран ли этот DLE для отображения в футере
const isSelectedForFooter = computed(() => {
if (!address || !footerDle.value) return false;
// Сравниваем адреса в нижнем регистре для надежности
return footerDle.value.address && footerDle.value.address.toLowerCase() === address.toLowerCase();
});
// Проверяем, есть ли какой-либо DLE в футере
const hasFooterDle = computed(() => {
return footerDle.value !== null && footerDle.value.address !== null;
});
// Устанавливает выбранный DLE для отображения в футере
const setAsFooterDle = async () => {
// Проверяем права доступа (только редактор может устанавливать DLE для футера)
if (!canSetFooterDle.value) {
alert('❌ Только пользователи с ролью редактор могут устанавливать DLE для отображения в футере');
return;
}
if (!dleInfo.value || !address) {
alert('Информация о DLE не загружена');
return;
}
try {
// Устанавливаем адрес, данные будут загружены из блокчейна
await setFooterDle(address, dleInfo.value?.currentChainId ?? null);
alert(`✅ DLE "${dleInfo.value.name} (${dleInfo.value.symbol})" теперь отображается в футере приложения`);
} catch (error) {
console.error('Ошибка при установке выбранного DLE:', error);
alert('❌ Не удалось установить выбранный DLE');
}
};
// Удаляет DLE из футера
const removeFromFooter = async () => {
// Проверяем права доступа (только редактор может удалять DLE из футера)
if (!canSetFooterDle.value) {
alert('❌ Только пользователи с ролью редактор могут удалять DLE из футера');
return;
}
if (!confirm('Вы уверены, что хотите удалить этот DLE из футера?')) {
return;
}
try {
await clearFooterDle();
alert('✅ DLE удален из футера приложения');
} catch (error) {
console.error('Ошибка при удалении DLE из футера:', error);
alert('❌ Не удалось удалить DLE из футера');
}
};
// Подписываемся на централизованные события очистки и обновления данных
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) {
console.error('Адрес DLE не указан');
return;
}
try {
isLoading.value = true;
console.log('Загружаем информацию о DLE:', address);
// Загружаем данные DLE из блокчейна через API
const response = await api.post('/blockchain/read-dle-info', {
dleAddress: address
});
if (response.data.success) {
const dleData = response.data.data;
console.log('Загружены данные DLE из блокчейна:', dleData);
dleInfo.value = {
name: dleData.name, // Название DLE из блокчейна
symbol: dleData.symbol, // Символ DLE из блокчейна
address: dleData.dleAddress || address, // Адрес из API или из URL
logoURI: dleData.logoURI || '', // URL логотипа
currentChainId: Number(dleData.currentChainId) || null
};
} else {
console.error('Ошибка загрузки DLE:', response.data.error);
throw new Error(response.data.error || 'Не удалось загрузить данные DLE');
}
} catch (error) {
console.error('Ошибка при загрузке информации о DLE:', error);
// В случае ошибки НЕ устанавливаем fallback данные, оставляем null
// чтобы не показывать некорректную информацию
dleInfo.value = null;
} finally {
isLoading.value = false;
}
};
// Методы
const deleteDLE = async () => {
if (!address) {
alert('Адрес DLE не найден');
return;
}
// Проверяем аутентификацию
if (!props.isAuthenticated || !userAddress.value) {
alert('❌ Для удаления DLE необходимо авторизоваться в приложении');
return;
}
if (!confirm(`ВНИМАНИЕ! Это действие необратимо. Вы уверены, что хотите деактивировать DLE ${dleInfo.value?.name || address}?`)) {
return;
}
if (!confirm('Это действие нельзя отменить. Подтвердите деактивацию еще раз.')) {
return;
}
try {
isSaving.value = true;
console.log('Деактивация DLE:', address);
// Выполняем деактивацию DLE
const result = await deactivateDLE(address, userAddress.value);
console.log('Результат деактивации:', result);
alert(`✅ DLE ${dleInfo.value?.name || address} успешно деактивирован!\n\nТранзакция: ${result.txHash}`);
// Перенаправляем на страницу блоков управления
goBackToBlocks();
} catch (error) {
console.error('Ошибка при деактивации DLE:', error);
let errorMessage = 'Ошибка при деактивации DLE';
if (error.message.includes('execution reverted')) {
errorMessage = '❌ Деактивация невозможна: не выполнены условия смарт-контракта. Возможно, требуется голосование участников или DLE уже деактивирован.';
} else if (error.message.includes('владелец')) {
errorMessage = '❌ Только владелец DLE может его деактивировать';
} else if (error.message.includes('кошелек')) {
errorMessage = '❌ Необходимо подключить кошелек';
} else if (error.message.includes('деактивирован')) {
errorMessage = '❌ DLE уже деактивирован';
} else if (error.message.includes('токены')) {
errorMessage = '❌ Для деактивации DLE необходимо иметь токены';
} else if (error.message.includes('условия смарт-контракта')) {
errorMessage = error.message; // Используем сообщение из dle-contract.js
} else {
errorMessage = `❌ Ошибка: ${error.message}`;
}
alert(errorMessage);
} finally {
isSaving.value = false;
}
};
// Загружаем данные при монтировании компонента
onMounted(() => {
if (address) {
dleAddress.value = address;
loadDLEInfo();
}
});
</script>
<style scoped>
.settings-container {
padding: 20px;
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-top: 20px;
margin-bottom: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.header-content {
flex-grow: 1;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2rem;
margin: 0 0 5px 0;
}
.page-header p {
color: var(--color-grey-dark);
font-size: 1rem;
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
flex-shrink: 0;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Основной контент */
.main-content {
display: grid;
gap: 20px;
}
/* Карточки */
.footer-card,
.danger-card {
background: white;
border: 1px solid #e9ecef;
border-radius: var(--radius-lg);
overflow: hidden;
}
.footer-header {
background: #f0f7ff;
padding: 15px 20px;
border-bottom: 1px solid #e9ecef;
}
.footer-header h3 {
color: var(--color-primary);
margin: 0;
font-size: 1.2rem;
}
.footer-content {
padding: 20px;
}
.footer-content p {
color: var(--color-grey-dark);
margin-bottom: 15px;
line-height: 1.5;
}
.footer-actions {
display: flex;
gap: 10px;
align-items: center;
}
.selected-info {
display: flex;
align-items: center;
gap: 8px;
background: #e6f7e6;
border: 1px solid #b3e5b3;
border-radius: 6px;
padding: 10px 15px;
margin-bottom: 15px;
color: #2d5a2d;
font-weight: 500;
}
.selected-info i {
color: #28a745;
font-size: 1.1rem;
}
.other-selected-info {
display: flex;
align-items: center;
gap: 8px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
padding: 10px 15px;
margin-bottom: 15px;
color: #856404;
font-weight: 500;
}
.other-selected-info i {
color: #ffc107;
font-size: 1.1rem;
}
.btn-sm {
padding: 8px 16px;
font-size: 0.875rem;
}
.danger-header {
background: #f8f9fa;
padding: 15px 20px;
border-bottom: 1px solid #e9ecef;
}
.danger-header h3 {
color: #c53030;
margin: 0;
font-size: 1.2rem;
}
.danger-content {
padding: 20px;
}
/* Кнопки */
.btn-primary,
.btn-danger {
border: none;
padding: 10px 20px;
border-radius: var(--radius-sm);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: var(--color-primary);
color: white;
}
.btn-primary:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
}
.btn-danger {
background: #e53e3e;
color: white;
}
.btn-danger:hover {
background: #c53030;
transform: translateY(-1px);
}
.btn-primary:active,
.btn-danger:active {
transform: translateY(0);
}
/* Сообщение если DLE не выбран */
.no-dle-card {
background: #fff5f5;
border: 2px solid #fed7d7;
border-radius: var(--radius-lg);
padding: 30px;
text-align: center;
}
.no-dle-card h3 {
color: #c53030;
margin-bottom: 10px;
font-size: 1.2rem;
}
.no-dle-card p {
color: #4a5568;
margin-bottom: 15px;
line-height: 1.5;
font-size: 0.9rem;
}
/* Стили для блока предупреждения */
.warning-info {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
padding: 15px;
margin: 15px 0;
}
.warning-info h4 {
color: #856404;
margin: 0 0 10px 0;
font-size: 1rem;
}
.warning-info ul {
margin: 0;
padding-left: 20px;
color: #856404;
}
.warning-info li {
margin-bottom: 5px;
font-size: 0.9rem;
line-height: 1.4;
}
</style>