🔧 Исправление отображения данных 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

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;