ваше сообщение коммита

This commit is contained in:
2025-12-23 12:25:12 +03:00
parent 8fe63beb8f
commit c02d0a38ac
37 changed files with 883 additions and 1131 deletions

View File

@@ -81,12 +81,31 @@ http {
return 404;
}
# Защита от доступа к чувствительным файлам
location ~* /(\.htaccess|\.htpasswd|\.env|\.git|\.svn|\.DS_Store|Thumbs\.db|web\.config|robots\.txt|sitemap\.xml)$ {
# Защита от доступа к чувствительным файлам (исключаем robots.txt и sitemap.xml для SEO)
location ~* /(\.htaccess|\.htpasswd|\.env|\.git|\.svn|\.DS_Store|Thumbs\.db|web\.config)$ {
deny all;
return 404;
}
# Разрешаем доступ к robots.txt и sitemap.xml для поисковых систем
location = /robots.txt {
proxy_pass http://${BACKEND_CONTAINER}:8000/api/pages/public/robots.txt;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Content-Type text/plain;
}
location = /sitemap.xml {
proxy_pass http://${BACKEND_CONTAINER}:8000/api/pages/public/sitemap.xml;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Content-Type application/xml;
}
# Защита от доступа к конфигурационным файлам
location ~* /\.(env|config|ini|conf|cfg|yml|yaml|json|xml|sql|db|bak|backup|old|tmp|temp|log)$ {
deny all;

View File

@@ -420,6 +420,35 @@ const isResizing = ref(false);
const resizeStartX = ref(0);
const resizeStartWidth = ref(0);
// Определяем, является ли устройство мобильным
const isMobile = ref(false);
// Функция для проверки мобильного устройства
const checkMobile = () => {
isMobile.value = window.innerWidth <= 1024;
if (isMobile.value) {
// На мобильных устройствах устанавливаем ширину в 100%
messagesWidth.value = 100;
inputWidth.value = 100;
} else {
// На десктопе используем стандартные значения
if (messagesWidth.value === 100) {
messagesWidth.value = 70;
inputWidth.value = 30;
}
}
};
// Отслеживаем изменение размера окна
onMounted(() => {
checkMobile();
window.addEventListener('resize', checkMobile);
});
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
});
const startResize = (e) => {
isResizing.value = true;
@@ -535,6 +564,10 @@ const updateChatInputHeight = () => {
};
onMounted(() => {
// Проверяем мобильное устройство и устанавливаем ширину
checkMobile();
window.addEventListener('resize', checkMobile);
// Начальная установка высоты textarea и блока ввода
adjustTextareaHeight();
updateChatInputHeight();
@@ -548,6 +581,8 @@ onMounted(() => {
});
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
if (resizeObserver && chatInputRef.value) {
resizeObserver.unobserve(chatInputRef.value);
}
@@ -709,8 +744,8 @@ async function handleAiReply() {
/* На мобильных устройствах блок ввода занимает всё пространство внизу */
@media (max-width: 1024px) {
.chat-input {
width: 100%;
max-width: 100%;
width: 100% !important;
max-width: 100% !important;
height: auto;
border-top: none;
border-right: none;
@@ -737,6 +772,7 @@ async function handleAiReply() {
color: var(--color-dark);
overflow-y: auto;
box-sizing: border-box;
max-width: 100%;
}
.chat-input textarea:focus {
@@ -748,12 +784,15 @@ async function handleAiReply() {
/* На мобильных устройствах поле ввода меньше */
@media (max-width: 1024px) {
.chat-input textarea {
width: 100% !important;
max-width: 100% !important;
border-radius: 20px;
padding: 12px 16px;
min-height: var(--chat-input-min-height, 40px);
max-height: var(--chat-input-max-height, 120px);
overflow-y: hidden;
resize: none;
box-sizing: border-box;
}
}
@@ -942,11 +981,25 @@ async function handleAiReply() {
.chat-messages {
padding: var(--spacing-md) var(--spacing-md) 8px;
width: 100% !important;
}
.chat-input {
width: 100% !important;
max-width: 100% !important;
padding: var(--spacing-xs) var(--spacing-sm);
height: auto;
box-sizing: border-box;
}
.input-area {
width: 100% !important;
box-sizing: border-box;
}
.input-area textarea {
width: 100% !important;
box-sizing: border-box;
}
.chat-icon-btn {
@@ -958,10 +1011,16 @@ async function handleAiReply() {
width: 20px;
height: 20px;
}
.resizer {
display: none;
}
}
@media (max-width: 480px) {
.chat-input {
width: 100% !important;
max-width: 100% !important;
position: sticky !important;
bottom: 0 !important;
border-radius: 0 !important;
@@ -969,14 +1028,27 @@ async function handleAiReply() {
background: #f8f8f8 !important;
border-top: 1px solid #eee !important;
}
.chat-messages {
width: 100% !important;
padding: var(--spacing-md) var(--spacing-md) 8px !important;
overflow-y: auto !important;
}
.input-area {
width: 100% !important;
}
.input-area textarea {
width: 100% !important;
box-sizing: border-box;
}
}
@media (max-width: 600px) {
.chat-input {
width: 100% !important;
max-width: 100% !important;
position: sticky !important;
bottom: 0 !important;
border-radius: 0 !important;
@@ -984,7 +1056,9 @@ async function handleAiReply() {
background: #f8f8f8 !important;
border-top: 1px solid #eee !important;
}
.chat-messages {
width: 100% !important;
padding: var(--spacing-md) var(--spacing-md) 8px !important;
overflow-y: auto !important;
}

View File

@@ -47,6 +47,9 @@
<router-link to="/" class="nav-link-btn" active-class="active">
<span>Чат</span>
</router-link>
<router-link to="/content/published" class="nav-link-btn" active-class="active">
<span>Блог</span>
</router-link>
<router-link to="/crm" class="nav-link-btn" active-class="active">
<span>CRM</span>
</router-link>

View File

@@ -30,7 +30,7 @@
<!-- Заголовок страницы -->
<header v-if="page" class="page-header">
<div class="page-header-top">
<button v-if="breadcrumbs.length > 0" class="back-btn" @click="$emit('back')" title="Вернуться к списку">
<button class="back-btn" @click="$emit('back')" title="Вернуться к списку">
<i class="fas fa-arrow-left"></i>
<span>Назад</span>
</button>
@@ -186,6 +186,68 @@ const navigation = ref(null);
const breadcrumbs = ref([]);
const isLoading = ref(false);
// Установка мета-тегов для SEO
function updateMetaTags(pageData) {
if (!pageData) return;
// Парсим seo, если это строка (может прийти из базы как JSON строка)
let seoData = pageData.seo;
if (typeof seoData === 'string') {
try {
seoData = JSON.parse(seoData);
} catch (e) {
console.warn('Ошибка парсинга SEO данных:', e);
seoData = null;
}
}
const title = seoData?.title || pageData.title || 'Документ';
const description = seoData?.description || pageData.summary || '';
const keywords = seoData?.keywords || '';
const canonicalUrl = `${window.location.origin}/content/published?page=${pageData.id}`;
// Обновляем title
document.title = title;
// Обновляем или создаем meta теги
const updateOrCreateMeta = (name, content, attribute = 'name') => {
if (!content) return;
let meta = document.querySelector(`meta[${attribute}="${name}"]`);
if (!meta) {
meta = document.createElement('meta');
meta.setAttribute(attribute, name);
document.head.appendChild(meta);
}
meta.setAttribute('content', content);
};
// Meta description
updateOrCreateMeta('description', description);
// Meta keywords
if (keywords) {
updateOrCreateMeta('keywords', keywords);
}
// Canonical URL
let canonical = document.querySelector('link[rel="canonical"]');
if (!canonical) {
canonical = document.createElement('link');
canonical.setAttribute('rel', 'canonical');
document.head.appendChild(canonical);
}
canonical.setAttribute('href', canonicalUrl);
// Open Graph теги для социальных сетей
updateOrCreateMeta('og:title', title, 'property');
updateOrCreateMeta('og:description', description, 'property');
updateOrCreateMeta('og:type', 'article', 'property');
updateOrCreateMeta('og:url', canonicalUrl, 'property');
// Robots meta
updateOrCreateMeta('robots', 'index, follow');
}
// Загрузка страницы
async function loadPage() {
if (!props.pageId) return;
@@ -194,6 +256,11 @@ async function loadPage() {
isLoading.value = true;
page.value = await pagesService.getPublicPage(props.pageId);
// Устанавливаем мета-теги для SEO
if (page.value) {
updateMetaTags(page.value);
}
// Загружаем навигацию
try {
navigation.value = await pagesService.getPublicPageNavigation(props.pageId);
@@ -959,12 +1026,44 @@ onMounted(() => {
@media (max-width: 768px) {
.docs-content {
padding: 20px;
min-height: auto;
width: 100%;
max-width: 100%;
display: block;
visibility: visible;
opacity: 1;
}
.page-header h1 {
font-size: 2rem;
}
.page-article {
width: 100%;
overflow-x: auto;
}
.content-text {
width: 100%;
overflow-x: auto;
word-wrap: break-word;
}
.file-preview {
width: 100%;
}
.pdf-embed {
width: 100%;
height: 60vh;
min-height: 400px;
}
.image-preview {
width: 100%;
max-width: 100%;
}
.page-navigation {
grid-template-columns: 1fr;
}

View File

@@ -199,4 +199,55 @@ onMounted(() => {
padding: 1rem;
background: #f9f9f9;
}
@media (max-width: 768px) {
.admin-chat-header {
padding: 0.75rem;
font-size: 1rem;
}
.close-btn {
font-size: 1.25rem;
padding: 0.2rem 0.4rem;
}
.chat-container {
height: calc(100vh - 100px);
}
:deep(.chat-messages) {
padding: 0.75rem;
}
:deep(.chat-input) {
padding: 0.75rem;
}
.loading-container {
padding: 1.5rem;
}
.loading {
font-size: 1rem;
}
}
@media (max-width: 480px) {
.admin-chat-header {
padding: 0.5rem;
font-size: 0.9rem;
}
.chat-container {
height: calc(100vh - 80px);
}
:deep(.chat-messages) {
padding: 0.5rem;
}
:deep(.chat-input) {
padding: 0.5rem;
}
}
</style>

View File

@@ -377,5 +377,98 @@ h1 {
font-size: 14px;
font-weight: 500;
}
@media (max-width: 768px) {
.connect-wallet-container {
padding: 16px;
}
.connect-wallet-card {
padding: 30px 24px;
max-width: 100%;
}
.icon {
font-size: 48px;
margin-bottom: 16px;
}
h1 {
font-size: 24px;
margin-bottom: 12px;
}
.info-block {
margin: 20px 0;
}
.provider-info {
font-size: 14px;
}
.description {
font-size: 13px;
}
.connect-button,
.go-chat-button {
padding: 12px 24px;
font-size: 15px;
}
}
@media (max-width: 480px) {
.connect-wallet-container {
padding: 12px;
}
.connect-wallet-card {
padding: 24px 16px;
border-radius: 12px;
}
.icon {
font-size: 40px;
margin-bottom: 12px;
}
h1 {
font-size: 20px;
margin-bottom: 10px;
}
.provider-info {
font-size: 13px;
}
.description {
font-size: 12px;
}
.connect-button,
.go-chat-button {
padding: 10px 20px;
font-size: 14px;
}
.error-message {
font-size: 13px;
padding: 10px;
}
.expires-info {
font-size: 12px;
}
.hint {
font-size: 13px;
padding: 10px;
}
.stats {
font-size: 13px;
padding: 10px;
}
}
</style>

View File

@@ -94,4 +94,24 @@ function goBack() {
margin-left: 7px;
}
@media (max-width: 768px) {
.contacts-header {
font-size: 1rem;
margin-bottom: 16px;
padding: 0 10px;
}
.badge {
font-size: 0.85em;
padding: 2px 6px;
}
}
@media (max-width: 480px) {
.contacts-header {
font-size: 0.9rem;
margin-bottom: 12px;
}
}
</style>

View File

@@ -19,26 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="content-create-page">
<!-- Заголовок страницы -->
<div class="page-header">
<div class="header-content">
<h1>{{ isEditMode ? 'Редактирование страницы' : 'Создание страницы' }}</h1>
<p>{{ isEditMode ? 'Редактируйте существующую страницу' : 'Создайте новую страницу для вашего DLE' }}</p>
</div>
<div class="header-actions">
<button
v-if="isEditMode && canManageLegalDocs && address"
class="btn btn-danger"
@click="deletePage"
type="button"
>
<i class="fas fa-trash"></i>
Удалить
</button>
<button class="close-btn" @click="goBack">×</button>
</div>
</div>
<!-- Основной контент с тенью -->
<div class="content-block">
<form class="content-form" @submit.prevent="handleSubmit">

View File

@@ -19,13 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="crm-management">
<!-- Заголовок -->
<div class="management-header">
<div class="header-content">
<h1>CRM Система</h1>
</div>
</div>
<!-- Блоки CRM -->
<div class="management-blocks">
<!-- Столбец 1 -->

View File

@@ -19,13 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="home-management">
<!-- Заголовок -->
<div class="management-header">
<div class="header-content">
<h1>Сообщения</h1>
</div>
</div>
<!-- Чат интерфейс -->
<div class="chat-wrapper">
<template v-if="auth.userAccessLevel.value && auth.userAccessLevel.value.hasAccess">

View File

@@ -19,12 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="management-container">
<!-- Заголовок -->
<div class="management-header">
<h1>Управление DLE</h1>
<button class="close-btn" @click="router.push('/')">×</button>
</div>
<!-- Деплоированные DLE -->
<div class="deployed-dles-section">
<div class="section-header">

View File

@@ -308,4 +308,65 @@ onUnmounted(() => {
font-size: 1rem;
text-align: center;
}
@media (max-width: 768px) {
.personal-messages-header {
padding: 0.75rem;
font-size: 1rem;
flex-wrap: wrap;
gap: 0.5rem;
}
.close-btn {
font-size: 1.25rem;
padding: 0.2rem 0.4rem;
}
.personal-messages-list {
padding: 0.5rem;
}
.message-item {
flex-direction: column;
align-items: stretch;
gap: 0.75rem;
padding: 0.75rem;
}
.message-info {
width: 100%;
}
.admin-name {
font-size: 1rem;
}
.message-preview {
font-size: 0.85rem;
}
.message-date {
font-size: 0.75rem;
}
}
@media (max-width: 480px) {
.personal-messages-header {
padding: 0.5rem;
font-size: 0.9rem;
}
.message-item {
padding: 0.5rem;
}
.admin-name {
font-size: 0.95rem;
}
.empty-state {
padding: 1.5rem 1rem;
font-size: 0.9rem;
}
}
</style>

View File

@@ -19,10 +19,8 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="settings-view-container">
<div v-if="route.name !== 'settings-index'" class="page-header">
<h1>{{ pageTitle }}</h1>
<div v-if="route.name === 'settings-blockchain-dle-deploy' || route.name === 'settings-dle-v2-deploy'" class="page-header">
<button
v-if="route.name === 'settings-blockchain-dle-deploy' || route.name === 'settings-dle-v2-deploy'"
class="close-btn"
@click="router.push('/settings')"
>×</button>

View File

@@ -19,9 +19,7 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="vds-management-container">
<!-- Заголовок -->
<div class="vds-header">
<h1>Управление VDS</h1>
<div class="status-badge" :class="{ online: isOnline }">
<div class="status-indicator" :class="{ online: isOnline }"></div>
<span>{{ isOnline ? 'Онлайн' : 'Офлайн' }}</span>

View File

@@ -19,18 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="content-management-page">
<!-- Заголовок страницы -->
<div class="page-header">
<div class="header-content">
<h1>Управление контентом</h1>
<p v-if="canEditData && address">Создавайте и управляйте страницами вашего DLE</p>
<p v-else>Просмотр опубликованных страниц DLE</p>
</div>
<div class="header-actions">
<button class="close-btn" @click="goBack">×</button>
</div>
</div>
<!-- Основной контент с тенью -->
<div class="content-block">
<!-- Быстрые разделы -->
@@ -248,8 +236,33 @@ async function deletePage(id) {
.details-btn { background: var(--color-primary); color: #fff; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem; font-weight: 600; transition: all 0.2s; min-width: 120px; margin-top: auto; }
.details-btn:hover { background: var(--color-primary-dark); transform: translateY(-1px); }
@media (max-width: 1024px) { .management-blocks { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 768px) { .management-blocks { grid-template-columns: 1fr; } }
@media (max-width: 1024px) {
.management-blocks {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
}
@media (max-width: 768px) {
.management-blocks {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.blocks-column {
gap: 1.2rem;
}
}
@media (max-width: 480px) {
.management-blocks {
gap: 1rem;
}
.blocks-column {
gap: 1rem;
}
}
.content-section {
background: #f8f9fa;
@@ -496,6 +509,12 @@ async function deletePage(id) {
color: white;
}
.header-actions {
display: flex;
align-items: center;
gap: 10px;
}
.close-btn {
background: none;
border: none;
@@ -505,6 +524,11 @@ async function deletePage(id) {
padding: 5px;
border-radius: var(--radius-sm);
transition: background 0.2s;
min-width: 32px;
min-height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
@@ -513,9 +537,37 @@ async function deletePage(id) {
}
@media (max-width: 768px) {
.content-management-page {
padding: 16px;
}
.page-header {
flex-direction: column;
gap: 15px;
margin-bottom: 20px;
padding-bottom: 15px;
}
.header-content h1 {
font-size: 1.8rem;
}
.header-content p {
font-size: 1rem;
}
.header-actions {
align-self: flex-start;
}
.close-btn {
font-size: 1.3rem;
min-width: 28px;
min-height: 28px;
}
.content-block {
padding: 20px;
}
.nav-tabs {
@@ -536,9 +588,59 @@ async function deletePage(id) {
grid-template-columns: 1fr;
}
.settings-grid {
grid-template-columns: 1fr;
}
.management-block {
padding: 1.5rem;
height: auto;
min-height: 200px;
}
.management-block h3 {
font-size: 1.3rem;
}
.management-block p {
font-size: 0.9rem;
}
.details-btn {
padding: 0.6rem 1.2rem;
font-size: 0.9rem;
}
}
@media (max-width: 480px) {
.content-management-page {
padding: 12px;
}
.page-header {
margin-bottom: 15px;
padding-bottom: 12px;
}
.header-content h1 {
font-size: 1.5rem;
}
.content-block {
padding: 16px;
}
.management-block {
padding: 1.2rem;
min-height: 180px;
}
.management-block h3 {
font-size: 1.2rem;
}
.management-block p {
font-size: 0.85rem;
}
}
</style>

View File

@@ -13,12 +13,6 @@
<template>
<BaseLayout>
<div class="list-page">
<div class="page-header">
<div class="header-content">
<h1>Настройки контента</h1>
<p>Юр. реквизиты и параметры подстановки переменных</p>
</div>
</div>
<div class="content-block">
<div class="section-header">
<h2>Юр. реквизиты (переменные)</h2>
@@ -38,13 +32,136 @@ import BaseLayout from '../../components/BaseLayout.vue';
</script>
<style scoped>
.list-page { padding: 20px; width: 100%; }
.page-header { display:flex; justify-content: space-between; align-items: flex-start; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #f0f0f0; }
.header-content h1 { color: var(--color-primary); font-size: 2.2rem; margin: 0 0 8px 0; }
.header-content p { color: var(--color-grey-dark); margin: 0; }
.content-block { background: #f8f9fa; border-radius: var(--radius-lg); padding: 25px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.section-header { display:flex; justify-content: space-between; align-items:center; margin-bottom: 20px; }
.section-header h2 { color: var(--color-primary); margin: 0; }
.empty-state { text-align:center; padding: 60px 20px; }
.empty-icon { font-size: 3rem; color: var(--color-grey-dark); margin-bottom: 10px; }
.list-page {
padding: 20px;
width: 100%;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.header-content h1 {
color: var(--color-primary);
font-size: 2.2rem;
margin: 0 0 8px 0;
}
.header-content p {
color: var(--color-grey-dark);
margin: 0;
}
.content-block {
background: #f8f9fa;
border-radius: var(--radius-lg);
padding: 25px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.section-header h2 {
color: var(--color-primary);
margin: 0;
}
.empty-state {
text-align: center;
padding: 60px 20px;
}
.empty-icon {
font-size: 3rem;
color: var(--color-grey-dark);
margin-bottom: 10px;
}
@media (max-width: 768px) {
.list-page {
padding: 16px;
}
.page-header {
flex-direction: column;
gap: 15px;
margin-bottom: 20px;
padding-bottom: 15px;
}
.header-content h1 {
font-size: 1.8rem;
}
.header-content p {
font-size: 1rem;
}
.content-block {
padding: 20px;
}
.section-header {
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
.section-header h2 {
font-size: 1.5rem;
}
.empty-state {
padding: 40px 16px;
}
.empty-icon {
font-size: 2.5rem;
}
.empty-state h3 {
font-size: 1.2rem;
}
.empty-state p {
font-size: 0.9rem;
}
}
@media (max-width: 480px) {
.list-page {
padding: 12px;
}
.page-header {
margin-bottom: 15px;
padding-bottom: 12px;
}
.header-content h1 {
font-size: 1.5rem;
}
.content-block {
padding: 16px;
}
.empty-state {
padding: 30px 12px;
}
.empty-icon {
font-size: 2rem;
}
}
</style>

View File

@@ -16,13 +16,7 @@
<template>
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
<div class="list-page">
<div class="page-header">
<div class="header-content">
<h1>Внутренние документы</h1>
<p>Документы, доступные только пользователям с ролями</p>
</div>
<button class="close-btn" @click="goBack">×</button>
</div>
<button class="close-btn" @click="goBack" style="margin-bottom: 20px;">×</button>
<div class="content-block">
<div class="section-header">
<h2>Список документов</h2>

View File

@@ -19,38 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="page-view-container">
<!-- Заголовок страницы -->
<div v-if="page" class="page-header">
<div class="header-content">
<h1>📄 {{ page.title }}</h1>
<div class="page-meta">
<span class="page-status" :class="page.status">
<i class="fas fa-circle"></i>
{{ getStatusText(page.status) }}
</span>
<span class="page-date">
<i class="fas fa-calendar"></i>
Создано: {{ formatDate(page.createdAt) }}
</span>
<span v-if="page.updatedAt" class="page-date">
<i class="fas fa-edit"></i>
Обновлено: {{ formatDate(page.updatedAt) }}
</span>
</div>
</div>
<div class="header-actions">
<button v-if="canEditPage" class="btn btn-outline" @click="goToEdit">
<i class="fas fa-edit"></i>
Редактировать
</button>
<button v-if="canManageLegalDocs && address" class="btn btn-danger" @click="deletePage" type="button">
<i class="fas fa-trash"></i>
Удалить
</button>
<button class="close-btn" @click="goBack">×</button>
</div>
</div>
<!-- Контент страницы -->
<div v-if="page" class="page-content-block">
<div class="page-content">

View File

@@ -19,13 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="public-pages-page">
<!-- Заголовок страницы -->
<div class="page-header">
<div class="header-content">
<h1>📄 Публичные страницы</h1>
<p>Просмотр опубликованных страниц DLE</p>
</div>
</div>
<!-- Основной контент -->
<div class="content-block">

View File

@@ -16,16 +16,12 @@
<template>
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
<div class="docs-page">
<!-- Заголовок страницы -->
<div class="docs-header">
<div class="header-content">
<h1>Публичные документы</h1>
</div>
<button class="close-btn" @click="goBack" title="Закрыть">×</button>
</div>
<!-- Основной контент: сайдбар + контент -->
<div class="docs-layout">
<div class="docs-layout" :class="{ 'has-content': currentPageId }">
<!-- Сайдбар навигации -->
<DocsSidebar :current-page-id="currentPageId" />
@@ -734,7 +730,8 @@ onBeforeUnmount(() => {
}
.docs-main {
overflow-y: visible;
overflow-y: auto;
min-height: 0;
}
}
@@ -954,6 +951,32 @@ onBeforeUnmount(() => {
}
@media (max-width: 768px) {
.docs-page {
height: auto;
min-height: calc(100vh - 40px);
overflow: visible;
}
.docs-layout {
flex: 1;
min-height: 0;
overflow: visible;
flex-direction: column;
}
/* В мобильной версии, когда выбран документ, скрываем сайдбар */
.docs-layout.has-content .docs-sidebar {
display: none;
}
.docs-main {
overflow-y: visible;
min-height: auto;
width: 100%;
display: block;
flex: 1;
}
.docs-header {
padding: 16px;
}

View File

@@ -5,14 +5,8 @@
<template>
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
<div class="list-page">
<div class="page-header">
<div class="header-content">
<h1>Шаблоны документов</h1>
<p>Системные шаблоны для персонализации и публикации.</p>
</div>
<div class="header-actions">
<button class="close-btn" @click="goBack">×</button>
</div>
<div class="header-actions" style="margin-bottom: 20px;">
<button class="close-btn" @click="goBack">×</button>
</div>
<div class="content-block">

View File

@@ -11,9 +11,8 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="system-messages-page">
<div class="page-header">
<div class="page-header" v-if="canManageSystemMessages">
<div class="header-content">
<h1>Системные сообщения</h1>
<p v-if="canManageSystemMessages">
Создавайте и управляйте уведомлениями, которые видят пользователи в чате и интерфейсе DLE
</p>

View File

@@ -19,14 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="groups-management">
<div class="management-header">
<div class="header-content">
<h1>Группы</h1>
<p class="groups-description">
Создание и управление группами
</p>
</div>
</div>
<!-- Блоки управления группами -->
<div class="management-blocks">

View File

@@ -14,13 +14,6 @@
<div class="security-settings">
<button class="close-btn" @click="goBack">×</button>
<!-- Заголовок в стиле основной страницы настроек -->
<div class="management-header">
<div class="header-content">
<h1>Настройки безопасности</h1>
</div>
</div>
<!-- Блоки настроек в едином стиле -->
<div class="management-blocks">
<!-- Столбец 1 -->

View File

@@ -12,13 +12,6 @@
<template>
<div class="settings-management">
<!-- Заголовок -->
<div class="management-header">
<div class="header-content">
<h1>Настройки системы</h1>
</div>
</div>
<!-- Блоки настроек -->
<div class="management-blocks">
<!-- Столбец 1 -->

View File

@@ -19,18 +19,19 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="add-module-page">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Добавление модуля в DLE</h1>
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>DLE не выбран (Адрес: {{ dleAddress }})</p>
<!-- Информация для неавторизованных пользователей -->
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ selectedDle.dleAddress }}
</div>
<div v-else-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ dleAddress }}
</div>
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
Загрузка...
</div>
<button class="close-btn" @click="goBackToProposals">×</button>
</div>
<!-- Информация для неавторизованных пользователей -->
<div v-if="!props.isAuthenticated" class="auth-notice">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>

View File

@@ -19,18 +19,16 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="analytics-container">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Аналитика DLE</h1>
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ formatAddress(dleAddress) }}</p>
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>Подробная аналитика и статистика DLE</p>
<!-- Основная информация -->
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div v-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ dleAddress }}
</div>
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
Загрузка...
</div>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Основная информация -->
<div class="info-section">
<h2>Основная информация</h2>
<div class="info-grid">

View File

@@ -19,18 +19,19 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="create-proposal-page">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Создание предложения</h1>
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>DLE не выбран</p>
<!-- Информация для неавторизованных пользователей -->
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ selectedDle.dleAddress }}
</div>
<div v-else-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ dleAddress }}
</div>
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
Загрузка...
</div>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Информация для неавторизованных пользователей -->
<div v-if="!props.isAuthenticated" class="auth-notice">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>

View File

@@ -19,18 +19,13 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="dle-blocks-management">
<!-- Заголовок -->
<div class="management-header">
<div class="header-content">
<h1>Управление DLE</h1>
<p v-if="dleAddress" class="dle-address">
<strong>DLE:</strong> {{ dleAddress }}
</p>
<!-- Блоки управления -->
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div v-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ dleAddress }}
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
<!-- Блоки управления -->
<div class="management-blocks">
<!-- Столбец 1 -->
<div class="blocks-column">

View File

@@ -19,11 +19,6 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="dle-management">
<div class="management-header">
<h3>🏢 Управление DLE</h3>
<p>Добавление DLE контрактов администраторами</p>
</div>
<!-- Форма добавления DLE -->
<div class="add-dle-form">
<div class="form-header">

View File

@@ -29,11 +29,9 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="proposals-page">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Предложения DLE</h1>
<p v-if="dleAddress">Адрес DLE: {{ dleAddress }}</p>
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div v-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ dleAddress }}
</div>
<button @click="goBack" class="close-btn">×</button>
</div>

View File

@@ -19,18 +19,16 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="history-container">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>История DLE</h1>
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>Лог операций, события и транзакции DLE</p>
<!-- Фильтры -->
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ selectedDle.dleAddress }}
</div>
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
Загрузка...
</div>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Фильтры -->
<div class="filters-section">
<h2>Фильтры</h2>
<div class="filters-form">

View File

@@ -19,24 +19,22 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="modules-management">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<div class="title-section">
<h1>Модули DLE</h1>
<div class="websocket-status" :class="{ connected: isWSConnected }" title="WebSocket соединение для обновления модулей">
<i class="fas fa-circle" :class="isWSConnected ? 'fa-solid' : 'fa-light'"></i>
<span>{{ isWSConnected ? 'Подключено' : 'Отключено' }}</span>
</div>
<!-- Модальное окно деплоя -->
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div style="display: flex; align-items: center; gap: 20px;">
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
{{ selectedDle.dleAddress }}
</div>
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
Загрузка...
</div>
<div class="websocket-status" :class="{ connected: isWSConnected }" title="WebSocket соединение для обновления модулей">
<i class="fas fa-circle" :class="isWSConnected ? 'fa-solid' : 'fa-light'"></i>
<span>{{ isWSConnected ? 'Подключено' : 'Отключено' }}</span>
</div>
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>DLE не выбран</p>
</div>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Модальное окно деплоя -->
<div v-if="showDeploymentModal" class="modal-overlay" @click="moduleDeploymentStatus === 'error' || !isDeploying ? closeDeploymentModal() : null">
<div class="modal-content" @click.stop>
<div class="modal-header">
@@ -171,11 +169,6 @@
<!-- Блоки для деплоя стандартных модулей -->
<div class="standard-modules">
<div class="modules-header">
<h3>🚀 Деплой стандартных модулей</h3>
<p>Быстрый деплой предустановленных модулей DLE</p>
</div>
<div class="modules-grid">
<!-- TreasuryModule -->
<div class="module-deploy-card">

View File

@@ -19,18 +19,19 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="settings-container">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Настройки DLE</h1>
<p v-if="dleInfo">{{ dleInfo.name }} ({{ dleInfo.symbol }}) - {{ dleInfo.address }}</p>
<p v-else-if="address">Загрузка...</p>
<p v-else>DLE не выбран</p>
<!-- Основной контент -->
<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">

View File

@@ -27,7 +27,14 @@ export default defineConfig({
build: {
rollupOptions: {
plugins: [polyfillNode()],
output: {
manualChunks: undefined,
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
},
},
chunkSizeWarningLimit: 1000,
},
optimizeDeps: {
esbuildOptions: {