🔧 Исправление отображения данных DLE из блокчейна

 Основные изменения:
- Исправлен дублирование /api в URL запросов к бэкенду
- Добавлен новый API endpoint /api/blockchain/read-dle-info для чтения данных из блокчейна
- Исправлено отображение количества участников (participantCount вместо initialPartners.length)
- Обновлен ManagementView.vue для чтения данных из блокчейна вместо JSON файлов
- Добавлены утилиты для чтения данных DLE из блокчейна
- Исправлены координаты в форме деплоя (сохранение в localStorage)
- Добавлен индикатор прогресса деплоя с редиректом на /management

🔧 Технические детали:
- Создан backend/routes/blockchain.js с endpoint для чтения DLE данных
- Обновлен backend/app.js для регистрации нового маршрута
- Исправлен импорт axios в ManagementView.vue (используется настроенный экземпляр api)
- Добавлены скрипты utils/read-dle-info.js и utils/get-rpc-url.js
- Обновлен скрипт деплоя для сохранения всех данных в блокчейн

🎯 Результат:
- Данные DLE теперь читаются напрямую из блокчейна
- Правильное отображение координат и количества участников
- Устранены ошибки 404 при запросах к API
This commit is contained in:
2025-08-04 21:04:16 +03:00
parent df37507bbe
commit e2ebe7e8aa
14 changed files with 1760 additions and 779 deletions

View File

@@ -25,26 +25,102 @@
<button class="close-btn" @click="router.push('/')">×</button>
</div>
<!-- Блоки управления -->
<div class="management-blocks">
<!-- Деплоированные DLE -->
<div class="deployed-dles-section">
<div class="section-header">
<div class="header-actions">
<button class="add-dle-btn" @click="openDleManagement()">
<i class="fas fa-plus"></i>
Добавить DLE
</button>
<button class="refresh-btn" @click="loadDeployedDles" :disabled="isLoadingDles">
<i class="fas fa-sync-alt" :class="{ 'fa-spin': isLoadingDles }"></i>
{{ isLoadingDles ? 'Загрузка...' : 'Обновить' }}
</button>
</div>
</div>
<div v-if="isLoadingDles" class="loading-dles">
<p>Загрузка деплоированных DLE...</p>
</div>
<div v-else-if="deployedDles.length === 0" class="no-dles">
<p>Деплоированных DLE пока нет</p>
<p>Создайте новый DLE на странице <a href="/settings/dle-v2-deploy" class="link">Деплой DLE</a></p>
</div>
<div v-else class="dles-grid">
<div
v-for="dle in deployedDles"
:key="dle.dleAddress"
class="dle-card"
:class="{ 'selected': selectedDle && selectedDle.dleAddress === dle.dleAddress }"
@click="selectDle(dle)"
>
<div class="dle-header">
<h3>{{ dle.name }} ({{ dle.symbol }})</h3>
<span class="dle-version">{{ dle.version || 'v2' }}</span>
</div>
<div class="dle-details">
<div class="detail-item">
<strong>Адрес контракта:</strong>
<a
:href="`https://sepolia.etherscan.io/address/${dle.dleAddress}`"
target="_blank"
class="address-link"
@click.stop
>
{{ shortenAddress(dle.dleAddress) }}
<i class="fas fa-external-link-alt"></i>
</a>
</div>
<div class="detail-item">
<strong>Местоположение:</strong> {{ dle.location }}
</div>
<div class="detail-item">
<strong>Юрисдикция:</strong> {{ dle.jurisdiction }}
</div>
<div class="detail-item">
<strong>Коды ОКВЭД:</strong> {{ dle.okvedCodes?.join(', ') || 'Не указаны' }}
</div>
<div class="detail-item">
<strong>Партнеры:</strong> {{ dle.participantCount || 0 }} участников
</div>
<div class="detail-item">
<strong>Статус:</strong>
<span class="status active">Активен</span>
</div>
</div>
</div>
</div>
</div>
<!-- Блоки управления выбранным DLE -->
<div v-if="selectedDle" class="management-blocks">
<!-- Первый ряд -->
<div class="blocks-row">
<div class="management-block">
<h3>Предложения</h3>
<p>Создание, подписание, выполнение</p>
<button class="details-btn" @click="openProposals">Подробнее</button>
<button class="details-btn" @click="openProposalsWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Токены DLE</h3>
<p>Балансы, трансферы, распределение</p>
<button class="details-btn" @click="openTokens">Подробнее</button>
<button class="details-btn" @click="openTokensWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Кворум</h3>
<p>Настройки голосования</p>
<button class="details-btn" @click="openQuorum">Подробнее</button>
<button class="details-btn" @click="openQuorumWithDle">Подробнее</button>
</div>
</div>
@@ -53,58 +129,63 @@
<div class="management-block">
<h3>Модули DLE</h3>
<p>Установка, настройка, управление</p>
<button class="details-btn" @click="openModules">Подробнее</button>
</div>
<div class="management-block">
<h3>DLE</h3>
<p>Интеграция с другими DLE, участие в кворумах</p>
<button class="details-btn" @click="openDle">Подробнее</button>
<button class="details-btn" @click="openModulesWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Казна</h3>
<p>Управление средствами</p>
<button class="details-btn" @click="openTreasury">Подробнее</button>
<button class="details-btn" @click="openTreasuryWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Аналитика</h3>
<p>Графики, статистика, отчеты</p>
<button class="details-btn" @click="openAnalyticsWithDle">Подробнее</button>
</div>
</div>
<!-- Третий ряд -->
<div class="blocks-row">
<div class="management-block">
<h3>Аналитика</h3>
<p>Графики, статистика, отчеты</p>
<button class="details-btn" @click="openAnalytics">Подробнее</button>
</div>
<div class="management-block">
<h3>История</h3>
<p>Лог операций, события, транзакции</p>
<button class="details-btn" @click="openHistory">Подробнее</button>
<button class="details-btn" @click="openHistoryWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Настройки</h3>
<p>Параметры DLE, конфигурация</p>
<button class="details-btn" @click="openSettings">Подробнее</button>
<button class="details-btn" @click="openSettingsWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Мультиподпись</h3>
<p>Управление мультиподписью</p>
<button class="details-btn" @click="openMultisigWithDle">Подробнее</button>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import { defineProps, defineEmits, ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../components/BaseLayout.vue';
import api from '@/api/axios';
// Определяем props
const props = defineProps({
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false }
});
// Определяем emits
@@ -112,6 +193,13 @@ const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
// Состояние для DLE
const deployedDles = ref([]);
const isLoadingDles = ref(false);
const selectedDle = ref(null);
// Функции для открытия страниц управления
const openProposals = () => {
router.push('/management/proposals');
@@ -148,6 +236,156 @@ const openHistory = () => {
const openSettings = () => {
router.push('/management/settings');
};
// Загрузка деплоированных DLE из блокчейна
async function loadDeployedDles() {
try {
isLoadingDles.value = true;
// Сначала получаем список DLE из API
const response = await api.get('/dle-v2');
if (response.data.success) {
const dlesFromApi = response.data.data || [];
// Для каждого DLE читаем актуальные данные из блокчейна
const dlesWithBlockchainData = await Promise.all(
dlesFromApi.map(async (dle) => {
try {
// Читаем данные из блокчейна
const blockchainResponse = await api.post('/blockchain/read-dle-info', {
dleAddress: dle.dleAddress
});
if (blockchainResponse.data.success) {
const blockchainData = blockchainResponse.data.data;
// Объединяем данные из API с данными из блокчейна
return {
...dle,
// Данные из блокчейна (приоритет)
name: blockchainData.name || dle.name,
symbol: blockchainData.symbol || dle.symbol,
location: blockchainData.location || dle.location,
coordinates: blockchainData.coordinates || dle.coordinates,
jurisdiction: blockchainData.jurisdiction || dle.jurisdiction,
oktmo: blockchainData.oktmo || dle.oktmo,
okvedCodes: blockchainData.okvedCodes || dle.okvedCodes,
kpp: blockchainData.kpp || dle.kpp,
// Информация о токенах из блокчейна
totalSupply: blockchainData.totalSupply,
deployerBalance: blockchainData.deployerBalance,
// Количество участников (держателей токенов)
participantCount: blockchainData.participantCount || 0
};
} else {
console.warn(`Не удалось прочитать данные из блокчейна для ${dle.dleAddress}`);
return dle;
}
} catch (error) {
console.warn(`Ошибка при чтении данных из блокчейна для ${dle.dleAddress}:`, error);
return dle;
}
})
);
deployedDles.value = dlesWithBlockchainData;
console.log('Загружены деплоированные DLE с данными из блокчейна:', deployedDles.value);
} else {
console.error('Ошибка при загрузке DLE:', response.data.message);
deployedDles.value = [];
}
} catch (error) {
console.error('Ошибка при загрузке DLE:', error);
deployedDles.value = [];
} finally {
isLoadingDles.value = false;
}
}
// Функции для работы с DLE
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function openDleOnEtherscan(address) {
window.open(`https://sepolia.etherscan.io/address/${address}`, '_blank');
}
function openDleManagement(dleAddress) {
// Переход к детальному управлению DLE (если нужно)
router.push(`/management/dle-management?address=${dleAddress}`);
}
function selectDle(dle) {
selectedDle.value = dle;
console.log('Выбран DLE:', dle);
}
function openMultisig() {
router.push('/management/multisig');
}
// Функции с передачей адреса DLE
function openProposalsWithDle() {
if (selectedDle.value) {
router.push(`/management/proposals?address=${selectedDle.value.dleAddress}`);
}
}
function openTokensWithDle() {
if (selectedDle.value) {
router.push(`/management/tokens?address=${selectedDle.value.dleAddress}`);
}
}
function openQuorumWithDle() {
if (selectedDle.value) {
router.push(`/management/quorum?address=${selectedDle.value.dleAddress}`);
}
}
function openModulesWithDle() {
if (selectedDle.value) {
router.push(`/management/modules?address=${selectedDle.value.dleAddress}`);
}
}
function openTreasuryWithDle() {
if (selectedDle.value) {
router.push(`/management/treasury?address=${selectedDle.value.dleAddress}`);
}
}
function openAnalyticsWithDle() {
if (selectedDle.value) {
router.push(`/management/analytics?address=${selectedDle.value.dleAddress}`);
}
}
function openHistoryWithDle() {
if (selectedDle.value) {
router.push(`/management/history?address=${selectedDle.value.dleAddress}`);
}
}
function openSettingsWithDle() {
if (selectedDle.value) {
router.push(`/management/settings?address=${selectedDle.value.dleAddress}`);
}
}
function openMultisigWithDle() {
if (selectedDle.value) {
router.push(`/management/multisig?address=${selectedDle.value.dleAddress}`);
}
}
onMounted(() => {
loadDeployedDles();
});
</script>
<style scoped>
@@ -201,6 +439,7 @@ const openSettings = () => {
display: flex;
flex-direction: column;
gap: 2rem;
margin-top: 2rem;
}
.blocks-row {
@@ -227,6 +466,226 @@ const openSettings = () => {
transform: translateY(-2px);
}
/* Секция деплоированных DLE */
.deployed-dles-section {
margin-top: 3rem;
}
.section-header {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 2rem;
}
.header-actions {
display: flex;
gap: 1rem;
align-items: center;
}
.add-dle-btn {
background: var(--color-primary);
color: white;
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 600;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.add-dle-btn:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
}
.add-dle-btn i {
font-size: 0.875rem;
}
.section-header h2 {
color: var(--color-primary);
margin: 0;
}
.refresh-btn {
background: var(--color-primary);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
transition: background-color 0.2s;
}
.refresh-btn:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
}
.refresh-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading-dles,
.no-dles {
text-align: center;
padding: 2rem;
color: #666;
}
.no-dles .link {
color: var(--color-primary);
text-decoration: none;
}
.no-dles .link:hover {
text-decoration: underline;
}
.dles-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 1.5rem;
}
.dle-card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #e9ecef;
transition: all 0.3s ease;
}
.dle-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
cursor: pointer;
}
.dle-card.selected {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
background: #f8f9ff;
}
.dle-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.dle-header h3 {
margin: 0;
color: var(--color-primary);
font-size: 1.25rem;
}
.dle-version {
background: #e9ecef;
color: #495057;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
}
.dle-details {
margin-bottom: 1.5rem;
}
.detail-item {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.detail-item strong {
color: #333;
}
.address {
font-family: monospace;
background: #f8f9fa;
padding: 0.125rem 0.25rem;
border-radius: 3px;
font-size: 0.875rem;
}
.address-link {
font-family: monospace;
background: #f8f9fa;
padding: 0.125rem 0.25rem;
border-radius: 3px;
font-size: 0.875rem;
color: var(--color-primary);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.25rem;
transition: all 0.2s;
}
.address-link:hover {
background: #e3f2fd;
color: var(--color-primary-dark);
text-decoration: none;
}
.address-link i {
font-size: 0.75rem;
opacity: 0.7;
}
.status {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
}
.status.active {
background: #d4edda;
color: #155724;
}
.dle-actions {
display: flex;
gap: 0.5rem;
}
.btn-sm {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.btn-info {
background: #17a2b8;
color: white;
}
.btn-info:hover {
background: #138496;
}
.management-block h3 {
color: var(--color-primary);
margin: 0 0 1rem 0;
@@ -274,4 +733,8 @@ const openSettings = () => {
font-size: 1.3rem;
}
}
</style>

View File

@@ -325,19 +325,7 @@
<small class="form-help">3-10 символов для токена управления (Governance Token)</small>
</div>
<!-- Координаты -->
<div class="form-group">
<label class="form-label" for="coordinates">Координаты (широта, долгота):</label>
<input
type="text"
id="coordinates"
v-model="dleSettings.coordinates"
class="form-control"
placeholder="Например: 55.7558,37.6176"
pattern="^-?\d+\.\d+,-?\d+\.\d+$"
>
<small class="form-help">Координаты в формате "широта,долгота" (например: 55.7558,37.6176)</small>
</div>
<!-- Партнеры и распределение токенов -->
<div class="partners-section">
@@ -488,19 +476,7 @@
</div>
</div> -->
<!-- Общая стоимость -->
<div v-if="selectedNetworks.length > 0" class="total-cost-section">
<div class="cost-breakdown">
<h5>💰 Стоимость деплоя:</h5>
<div v-for="network in selectedNetworkDetails" :key="network.chainId" class="cost-line">
<span>{{ network.name }}:</span>
<span class="cost">~${{ network.estimatedCost }}</span>
</div>
<div class="total-line">
<strong>Общая стоимость: ~${{ totalDeployCost.toFixed(2) }}</strong>
</div>
</div>
</div>
<!-- Кнопки управления RPC -->
<div class="rpc-settings-actions">
@@ -546,40 +522,27 @@
</div>
</div>
<!-- Информация о ключе -->
<div v-if="selectedNetworks.length > 0" class="key-info">
<div class="info-card">
<div class="info-icon">
<i class="fas fa-key"></i>
</div>
<div class="info-content">
<h5>Как это работает?</h5>
<p>Один приватный ключ создаст одинаковый адрес во всех EVM-совместимых сетях. Это упрощает управление и позволяет использовать один кошелек для всех операций.</p>
</div>
</div>
</div>
<!-- Ввод приватного ключа -->
<div v-if="selectedNetworks.length > 0" class="key-input-section">
<div class="form-group">
<label class="form-label">Приватный ключ:</label>
<div class="input-icon-wrapper">
<input
:type="showUnifiedKey ? 'text' : 'password'"
v-model="unifiedPrivateKey"
class="form-control"
placeholder="Введите приватный ключ (0x... или без префикса)"
@input="() => { console.log('Input event triggered'); validatePrivateKey('unified'); }"
@focus="() => console.log('Input field focused')"
@blur="() => console.log('Input field blurred')"
@input="validatePrivateKey('unified')"
@keyup="validatePrivateKey('unified')"
@change="validatePrivateKey('unified')"
>
<span class="input-icon" @click="showUnifiedKey = !showUnifiedKey">
<i :class="showUnifiedKey ? 'fas fa-eye-slash' : 'fas fa-eye'"></i>
</span>
</div>
<small class="form-help">
Этот ключ будет использован для деплоя во всех выбранных сетях
</small>
</div>
<!-- Валидация ключа -->
@@ -790,7 +753,7 @@
@click="deploySmartContracts"
type="button"
class="btn btn-primary btn-lg deploy-btn"
:disabled="!isFormValid"
:disabled="!isFormValid || !adminTokenCheck.isAdmin || adminTokenCheck.isLoading || showDeployProgress"
>
<i class="fas fa-rocket"></i> Деплой смарт контрактов
</button>
@@ -799,13 +762,49 @@
@click="clearAllData"
class="btn btn-danger btn-lg clear-btn"
title="Очистить все данные"
:disabled="showDeployProgress"
>
Удалить все
</button>
</div>
<small class="deploy-help">
<!-- Индикатор процесса деплоя -->
<div v-if="showDeployProgress" class="deploy-progress">
<div class="progress-header">
<h4>🚀 Деплой DLE в блокчейне</h4>
<p>{{ deployStatus }}</p>
</div>
</small>
<div class="progress-bar-container">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: deployProgress + '%' }"
></div>
</div>
<span class="progress-text">{{ deployProgress }}%</span>
</div>
<div class="progress-steps">
<div class="step" :class="{ active: deployProgress >= 10 }">
<i class="fas fa-check-circle"></i>
<span>Подготовка данных</span>
</div>
<div class="step" :class="{ active: deployProgress >= 30 }">
<i class="fas fa-check-circle"></i>
<span>Отправка на сервер</span>
</div>
<div class="step" :class="{ active: deployProgress >= 70 }">
<i class="fas fa-check-circle"></i>
<span>Деплой в блокчейне</span>
</div>
<div class="step" :class="{ active: deployProgress >= 100 }">
<i class="fas fa-check-circle"></i>
<span>Завершение</span>
</div>
</div>
</div>
</div>
</div>
@@ -829,6 +828,13 @@ const router = useRouter();
// Получаем контекст авторизации для адреса кошелька
const { address, isAdmin } = useAuthContext();
// Состояние для проверки админских токенов
const adminTokenCheck = ref({
isLoading: false,
isAdmin: false,
error: null
});
// Основные настройки DLE
const dleSettings = reactive({
// Юрисдикция
@@ -996,6 +1002,11 @@ const selectedOkvedLevel4 = ref('');
const currentSelectedOkvedCode = ref('');
const currentSelectedOkvedText = ref('');
// Состояние процесса деплоя
const showDeployProgress = ref(false);
const deployProgress = ref(0);
const deployStatus = ref('');
// Функция определения уровня ОКВЭД кода
const getOkvedLevel = (code) => {
if (!code) return 0;
@@ -1300,7 +1311,8 @@ const saveFormData = () => {
showUnifiedKey: showUnifiedKey.value
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(dataToSave));
// console.log('[DleDeployForm] Данные формы сохранены в localStorage');
console.log('[DleDeployForm] Данные формы сохранены в localStorage');
console.log('[DleDeployForm] Coordinates saved:', dataToSave.coordinates);
} catch (error) {
// console.error('[DleDeployForm] Ошибка сохранения данных:', error);
}
@@ -1335,6 +1347,8 @@ const loadFormData = () => {
tokenSymbol: parsedData.tokenSymbol || '',
partners: parsedData.partners || [{ address: '', amount: 1 }],
governanceQuorum: parsedData.governanceQuorum || 51,
// Координаты
coordinates: parsedData.coordinates || '',
// Мульти-чейн настройки
selectedNetworks: parsedData.selectedNetworks || [],
tokenStandard: parsedData.tokenStandard || 'ERC20',
@@ -1365,7 +1379,8 @@ const loadFormData = () => {
Object.assign(keyValidation, parsedData.keyValidation || {});
showUnifiedKey.value = parsedData.showUnifiedKey || false;
// console.log('[DleDeployForm] Данные формы восстановлены из localStorage');
console.log('[DleDeployForm] Данные формы восстановлены из localStorage');
console.log('[DleDeployForm] Coordinates loaded:', dleSettings.coordinates);
return true;
}
} catch (error) {
@@ -1413,6 +1428,9 @@ const clearAllData = () => {
dleSettings.tokenStandard = 'ERC20'; // Сбрасываем к стандартному ERC-20
dleSettings.predictedAddress = '';
// Очищаем координаты
dleSettings.coordinates = '';
// Устаревшие поля
dleSettings.deployNetwork = '';
dleSettings.privateKey = '';
@@ -1536,6 +1554,8 @@ const findOktmoByAddress = (result) => {
// Заполнение полей из результата поиска
const fillFromSearchResult = (result) => {
console.log('[FillFromSearchResult] Called with result:', result);
dleSettings.addressData.postalCode = result.postcode;
dleSettings.addressData.region = result.region;
dleSettings.addressData.city = result.city;
@@ -1544,6 +1564,22 @@ const fillFromSearchResult = (result) => {
dleSettings.addressData.apartment = ''; // Квартиру пользователь введет сам
dleSettings.addressData.isVerified = false; // Требует проверки после дозаполнения
// Сохраняем координаты в dleSettings
if (result.coordinates && result.coordinates.lat && result.coordinates.lon) {
dleSettings.coordinates = `${result.coordinates.lat},${result.coordinates.lon}`;
console.log(`[FillFromSearchResult] Saved coordinates from coordinates object: ${dleSettings.coordinates}`);
// Сохраняем в localStorage
saveFormData();
} else if (result.lat && result.lon) {
// Альтернативный формат координат
dleSettings.coordinates = `${result.lat},${result.lon}`;
console.log(`[FillFromSearchResult] Saved coordinates from lat/lon: ${dleSettings.coordinates}`);
// Сохраняем в localStorage
saveFormData();
} else {
console.log('[FillFromSearchResult] No coordinates found in result');
}
// Сохраняем результат API для отображения в превью
lastApiResult.value = result;
@@ -1597,6 +1633,14 @@ const verifyAddress = async () => {
addr.fullAddress = verificationResult.display_name;
addr.isVerified = true;
// Сохраняем координаты из результата проверки
if (verificationResult.lat && verificationResult.lon) {
dleSettings.coordinates = `${verificationResult.lat},${verificationResult.lon}`;
console.log(`[VerifyAddress] Saved coordinates: ${dleSettings.coordinates}`);
// Сохраняем в localStorage
saveFormData();
}
console.log('[VerifyAddress] Address verified successfully:', addr.fullAddress);
} else {
// Если не найден - все равно считаем валидным (пользователь может знать лучше)
@@ -1633,6 +1677,8 @@ const clearAddress = () => {
fullAddress: '',
isVerified: false
};
// Очищаем координаты
dleSettings.coordinates = '';
postalCodeInput.value = '';
searchResults.value = [];
autoSelectedOktmo.value = false;
@@ -1955,8 +2001,6 @@ const toggleKeyVisibility = (chainId) => {
// Валидация приватного ключа с дебаунсом
const validatePrivateKey = async (chainId) => {
console.log('Функция validatePrivateKey вызвана для chainId:', chainId);
// Очищаем предыдущий таймер
if (validatePrivateKey.timeout) {
clearTimeout(validatePrivateKey.timeout);
@@ -1965,7 +2009,6 @@ const validatePrivateKey = async (chainId) => {
// Устанавливаем новый таймер для дебаунса
validatePrivateKey.timeout = setTimeout(async () => {
const key = chainId === 'unified' ? unifiedPrivateKey.value : privateKeys[chainId];
console.log('Ключ для валидации:', key);
if (!key) {
keyValidation[chainId] = null;
@@ -1973,18 +2016,11 @@ const validatePrivateKey = async (chainId) => {
}
try {
// Логируем отправляемый ключ (только для отладки)
console.log('Отправляем приватный ключ для валидации:', key);
console.log('Длина ключа:', key.length);
console.log('Полный ключ:', key);
// Отправляем запрос на бэкенд для валидации
const response = await axios.post('/api/dle-v2/validate-private-key', {
const response = await axios.post('/dle-v2/validate-private-key', {
privateKey: key
});
console.log('Ответ от сервера:', response.data);
if (response.data.success) {
keyValidation[chainId] = response.data.data;
} else {
@@ -2065,6 +2101,15 @@ watch([selectedOkvedLevel1, selectedOkvedLevel2, postalCodeInput], () => {
}, 100);
});
// Watcher для координат
watch(() => dleSettings.coordinates, (newCoordinates) => {
console.log('[Coordinates Watcher] Coordinates changed:', newCoordinates);
// Добавляем небольшую задержку для предотвращения рекурсии
setTimeout(() => {
saveFormData();
}, 100);
});
// ==================== МУЛЬТИ-ЧЕЙН WATCHERS ====================
// Watcher для selectedNetworks - синхронизация с dleSettings
@@ -2120,8 +2165,6 @@ watch([() => dleSettings.name, () => dleSettings.tokenSymbol, selectedNetworks],
// Инициализация
onMounted(() => {
console.log('🚀 DleDeployFormView компонент загружен - ТЕСТ ОБНОВЛЕНИЯ');
alert('Компонент загружен - проверьте консоль');
// Загружаем список стран
loadCountries();
@@ -2149,6 +2192,9 @@ onMounted(() => {
// Добавляем слушатель события видимости страницы для обновления списка сетей
document.addEventListener('visibilitychange', handleVisibilityChange);
// Проверяем админские токены при загрузке
checkAdminTokens();
});
// Удаляем слушатель при размонтировании компонента
@@ -2163,6 +2209,33 @@ watch(address, (newAddress) => {
}
});
// Функция проверки админских токенов
const checkAdminTokens = async () => {
if (!address.value) {
adminTokenCheck.value = { isLoading: false, isAdmin: false, error: 'Кошелек не подключен' };
return;
}
adminTokenCheck.value.isLoading = true;
adminTokenCheck.value.error = null;
try {
const response = await axios.get(`/dle-v2/check-admin-tokens?address=${address.value}`);
if (response.data.success) {
adminTokenCheck.value.isAdmin = response.data.data.isAdmin;
console.log('Проверка админских токенов:', response.data.data);
} else {
adminTokenCheck.value.error = response.data.message || 'Ошибка проверки токенов';
}
} catch (error) {
console.error('Ошибка проверки админских токенов:', error);
adminTokenCheck.value.error = error.response?.data?.message || 'Ошибка проверки токенов';
} finally {
adminTokenCheck.value.isLoading = false;
}
};
// Функции для работы с партнерами
const addPartner = () => {
dleSettings.partners.push({ address: '', amount: 1 });
@@ -2202,6 +2275,11 @@ const deploySmartContracts = async () => {
return;
}
// Показываем индикатор процесса
showDeployProgress.value = true;
deployProgress.value = 10;
deployStatus.value = 'Подготовка данных для деплоя...';
// Подготовка данных для деплоя
const deployData = {
// Основная информация DLE
@@ -2232,24 +2310,38 @@ const deploySmartContracts = async () => {
};
console.log('Данные для деплоя DLE:', deployData);
deployProgress.value = 30;
deployStatus.value = 'Отправка данных на сервер...';
// Вызов API для деплоя
const response = await axios.post('/api/dle-v2', deployData);
const response = await axios.post('/dle-v2', deployData);
deployProgress.value = 70;
deployStatus.value = 'Деплой смарт-контракта в блокчейне...';
if (response.data.success) {
alert('✅ DLE успешно развернут!');
deployProgress.value = 100;
deployStatus.value = '✅ DLE успешно развернут!';
// Сохраняем адрес контракта
dleSettings.predictedAddress = response.data.data?.contractAddress || 'Адрес будет доступен после деплоя';
// Перенаправляем на страницу управления
router.push('/management/dle-management');
// Небольшая задержка для показа успешного завершения
setTimeout(() => {
showDeployProgress.value = false;
// Перенаправляем на главную страницу управления
router.push('/management');
}, 2000);
} else {
showDeployProgress.value = false;
alert('❌ Ошибка при деплое: ' + response.data.error);
}
} catch (error) {
console.error('Ошибка деплоя DLE:', error);
showDeployProgress.value = false;
alert('❌ Ошибка при деплое смарт-контракта: ' + error.message);
}
};
@@ -3920,4 +4012,128 @@ const validateCoordinates = (coordinates) => {
.clear-btn {
min-width: 150px;
}
/* Стили для индикатора статуса админских токенов */
.admin-status {
padding: 8px 12px;
border-radius: 4px;
margin-top: 8px;
font-size: 0.9rem;
}
.admin-status.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.admin-status.warning {
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.admin-status.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Стили для индикатора процесса деплоя */
.deploy-progress {
margin-top: 2rem;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
color: white;
animation: fadeIn 0.5s ease;
}
.progress-header {
text-align: center;
margin-bottom: 2rem;
}
.progress-header h4 {
margin: 0 0 0.5rem 0;
font-size: 1.5rem;
font-weight: 600;
}
.progress-header p {
margin: 0;
opacity: 0.9;
font-size: 1.1rem;
}
.progress-bar-container {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.progress-bar {
flex: 1;
height: 12px;
background: rgba(255, 255, 255, 0.2);
border-radius: 6px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4ade80 0%, #22c55e 100%);
border-radius: 6px;
transition: width 0.5s ease;
}
.progress-text {
font-weight: 600;
font-size: 1.1rem;
min-width: 50px;
}
.progress-steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.step {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
opacity: 0.5;
transition: all 0.3s ease;
}
.step.active {
opacity: 1;
background: rgba(255, 255, 255, 0.2);
}
.step i {
font-size: 1.2rem;
color: #4ade80;
}
.step span {
font-size: 0.9rem;
font-weight: 500;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,14 @@
-->
<template>
<div class="dle-multisig-management">
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="dle-multisig-management">
<div class="multisig-header">
<h3>🔐 Управление мультиподписью</h3>
<button class="btn btn-primary" @click="showCreateForm = true">
@@ -272,17 +279,25 @@
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, defineProps, defineEmits } from 'vue';
import { useAuthContext } from '@/composables/useAuth';
import BaseLayout from '../../components/BaseLayout.vue';
const props = defineProps({
dleAddress: { type: String, required: true },
dleContract: { type: Object, required: true }
dleAddress: { type: String, required: false, default: null },
dleContract: { type: Object, required: false, default: null },
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
const emit = defineEmits(['auth-action-completed']);
const { address } = useAuthContext();
// Состояние формы

View File

@@ -11,10 +11,29 @@
-->
<template>
<div class="dle-proposals-management">
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="dle-proposals-management">
<div class="proposals-header">
<h3>🗳 Управление предложениями</h3>
<button class="btn btn-primary" @click="showCreateForm = true">
<div class="header-info">
<h3>🗳 Управление предложениями</h3>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ shortenAddress(selectedDle.dleAddress) }}</span>
</div>
<div v-else-if="isLoadingDle" class="loading-info">
<span>Загрузка данных DLE...</span>
</div>
<div v-else class="no-dle-info">
<span>DLE не выбран</span>
</div>
</div>
<button class="btn btn-primary" @click="showCreateForm = true" :disabled="!selectedDle">
<i class="fas fa-plus"></i> Создать предложение
</button>
</div>
@@ -322,18 +341,41 @@
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, watch, defineProps, defineEmits } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAuthContext } from '@/composables/useAuth';
import BaseLayout from '../../components/BaseLayout.vue';
import axios from 'axios';
const props = defineProps({
dleAddress: { type: String, required: true },
dleContract: { type: Object, required: true }
dleAddress: { type: String, required: false, default: null },
dleContract: { type: Object, required: false, default: null },
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
const emit = defineEmits(['auth-action-completed']);
const { address } = useAuthContext();
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из URL
const dleAddress = computed(() => {
const address = route.query.address || props.dleAddress;
console.log('DLE Address from URL:', address);
return address;
});
// Состояние DLE
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Состояние формы
const showCreateForm = ref(false);
@@ -382,6 +424,38 @@ const filteredProposals = computed(() => {
});
// Функции
async function loadDleData() {
console.log('loadDleData вызвана с адресом:', dleAddress.value);
if (!dleAddress.value) {
console.warn('Адрес DLE не указан');
return;
}
isLoadingDle.value = true;
try {
// Загружаем данные DLE из backend
const response = await axios.get(`/dle-v2`);
const dles = response.data.data; // Используем response.data.data
console.log('Получены DLE из API:', dles);
// Находим нужный DLE по адресу
const dle = dles.find(d => d.dleAddress === dleAddress.value);
console.log('Найденный DLE:', dle);
if (dle) {
selectedDle.value = dle;
console.log('Загружен DLE:', dle);
} else {
console.warn('DLE не найден:', dleAddress.value);
}
} catch (error) {
console.error('Ошибка загрузки DLE:', error);
} finally {
isLoadingDle.value = false;
}
}
function validateOperationParams() {
const params = newProposal.value.operationParams;
@@ -606,7 +680,15 @@ function viewProposalDetails(proposalId) {
// console.log('Просмотр деталей предложения:', proposalId);
}
// Отслеживаем изменения в адресе DLE
watch(dleAddress, (newAddress) => {
if (newAddress) {
loadDleData();
}
}, { immediate: true });
onMounted(() => {
// Загрузка предложений
loadProposals();
});
</script>
@@ -623,6 +705,47 @@ onMounted(() => {
margin-bottom: 2rem;
}
.header-info {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.header-info h3 {
margin: 0;
color: var(--color-primary);
}
.dle-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.dle-name {
font-weight: 600;
color: #333;
font-size: 1rem;
}
.dle-address {
font-family: monospace;
font-size: 0.875rem;
color: #666;
background: #f8f9fa;
padding: 0.25rem 0.5rem;
border-radius: 4px;
display: inline-block;
width: fit-content;
}
.loading-info,
.no-dle-info {
font-size: 0.875rem;
color: #666;
font-style: italic;
}
.create-proposal-form {
background: #f8f9fa;
border-radius: 8px;

View File

@@ -23,7 +23,16 @@
<div class="page-header">
<div class="header-content">
<h1>Токены DLE</h1>
<p>Балансы, трансферы и распределение токенов</p>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ shortenAddress(selectedDle.dleAddress) }}</span>
</div>
<div v-else-if="isLoadingDle" class="loading-info">
<span>Загрузка данных DLE...</span>
</div>
<div v-else class="no-dle-info">
<span>DLE не выбран</span>
</div>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
@@ -185,9 +194,10 @@
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
import { useRouter } from 'vue-router';
import { ref, computed, onMounted, watch, defineProps, defineEmits } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import axios from 'axios';
// Определяем props
const props = defineProps({
@@ -201,16 +211,28 @@ const props = defineProps({
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из URL
const dleAddress = computed(() => {
const address = route.query.address;
console.log('DLE Address from URL (Tokens):', address);
return address;
});
// Состояние DLE
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Состояние
const isTransferring = ref(false);
const isDistributing = ref(false);
// Данные токенов
const tokenSymbol = ref('MDLE');
const totalSupply = ref(10000);
const userBalance = ref(1000);
const quorumPercentage = ref(51);
// Данные токенов (реактивные)
const tokenSymbol = computed(() => selectedDle.value?.symbol || 'MDLE');
const totalSupply = computed(() => selectedDle.value?.initialAmounts?.[0] || 10000);
const userBalance = computed(() => Math.floor(totalSupply.value * 0.1)); // 10% для демо
const quorumPercentage = computed(() => selectedDle.value?.governanceSettings?.quorumPercentage || 51);
const tokenPrice = ref(1.25);
// Данные трансфера
@@ -237,6 +259,41 @@ const tokenHolders = ref([
{ address: '0x5678901234567890123456789012345678901234', balance: 600 }
]);
// Функции
async function loadDleData() {
if (!dleAddress.value) {
console.warn('Адрес DLE не указан');
return;
}
isLoadingDle.value = true;
try {
// Загружаем данные DLE из backend
const response = await axios.get(`/dle-v2`);
const dles = response.data.data; // Используем response.data.data
// Находим нужный DLE по адресу
const dle = dles.find(d => d.dleAddress === dleAddress.value);
if (dle) {
selectedDle.value = dle;
console.log('Загружен DLE:', dle);
console.log('Данные токенов будут обновлены автоматически');
} else {
console.warn('DLE не найден:', dleAddress.value);
}
} catch (error) {
console.error('Ошибка загрузки DLE:', error);
} finally {
isLoadingDle.value = false;
}
}
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
// Методы
const transferTokens = async () => {
if (isTransferring.value) return;
@@ -324,6 +381,13 @@ const formatAddress = (address) => {
if (!address) return '';
return address.substring(0, 6) + '...' + address.substring(address.length - 4);
};
// Отслеживаем изменения в адресе DLE
watch(dleAddress, (newAddress) => {
if (newAddress) {
loadDleData();
}
}, { immediate: true });
</script>
<style scoped>
@@ -361,6 +425,38 @@ const formatAddress = (address) => {
margin: 0;
}
.dle-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: 0.5rem;
}
.dle-name {
font-weight: 600;
color: #333;
font-size: 1rem;
}
.dle-address {
font-family: monospace;
font-size: 0.875rem;
color: #666;
background: #f8f9fa;
padding: 0.25rem 0.5rem;
border-radius: 4px;
display: inline-block;
width: fit-content;
}
.loading-info,
.no-dle-info {
font-size: 0.875rem;
color: #666;
font-style: italic;
margin-top: 0.5rem;
}
.close-btn {
background: none;
border: none;