diff --git a/backend/routes/blockchain.js b/backend/routes/blockchain.js index 6b2bf22..aa21ce7 100644 --- a/backend/routes/blockchain.js +++ b/backend/routes/blockchain.js @@ -54,7 +54,7 @@ router.post('/read-dle-info', async (req, res) => { if (!rpcUrl) { return res.status(500).json({ success: false, error: `RPC URL для сети ${targetChainId} не найден` }); } - provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); + provider = new ethers.JsonRpcProvider(rpcUrl); const code = await provider.getCode(dleAddress); if (!code || code === '0x') { return res.status(400).json({ success: false, error: `По адресу ${dleAddress} нет контракта в сети ${targetChainId}` }); diff --git a/backend/routes/settings.js b/backend/routes/settings.js index 4a99ce7..7679e07 100644 --- a/backend/routes/settings.js +++ b/backend/routes/settings.js @@ -52,11 +52,70 @@ const aiAssistantSettingsService = require('../services/aiAssistantSettingsServi const aiAssistantRulesService = require('../services/aiAssistantRulesService'); const botsSettings = require('../services/botsSettings'); const dbSettingsService = require('../services/dbSettingsService'); +const footerDleService = require('../services/footerDleService'); const { broadcastAuthTokenAdded, broadcastAuthTokenDeleted, broadcastAuthTokenUpdated } = require('../wsHub'); // Логируем версию ethers для отладки logger.info(`Ethers version: ${ethers.version || 'unknown'}`); +// === FOOTER DLE SELECTION =================================================== + +router.get('/footer-dle', async (req, res) => { + try { + const selection = await footerDleService.getFooterSelection(); + res.json({ success: true, data: selection }); + } catch (error) { + logger.error('[Settings] Ошибка при получении footer DLE:', error); + res.status(500).json({ success: false, error: 'Не удалось получить выбранный DLE для футера' }); + } +}); + +router.post('/footer-dle', requireAdmin, async (req, res) => { + try { + const { dleAddress, chainId } = req.body || {}; + + if (!dleAddress) { + return res.status(400).json({ success: false, error: 'Необходимо указать адрес DLE' }); + } + + if (!ethers.isAddress(dleAddress)) { + return res.status(400).json({ success: false, error: 'Указан некорректный адрес DLE' }); + } + + let normalizedChainId = null; + if (chainId !== undefined && chainId !== null && chainId !== '') { + const parsed = Number(chainId); + if (!Number.isFinite(parsed)) { + return res.status(400).json({ success: false, error: 'Некорректный chainId' }); + } + normalizedChainId = parsed; + } + + const updatedBy = req.session?.address || req.session?.userId || null; + const selection = await footerDleService.setFooterSelection({ + address: ethers.getAddress(dleAddress), + chainId: normalizedChainId, + updatedBy, + }); + + res.json({ success: true, data: selection }); + } catch (error) { + logger.error('[Settings] Ошибка при сохранении footer DLE:', error); + res.status(500).json({ success: false, error: 'Не удалось сохранить выбранный DLE для футера' }); + } +}); + +router.delete('/footer-dle', requireAdmin, async (req, res) => { + try { + const updatedBy = req.session?.address || req.session?.userId || null; + const selection = await footerDleService.clearFooterSelection(updatedBy); + res.json({ success: true, data: selection }); + } catch (error) { + logger.error('[Settings] Ошибка при очистке footer DLE:', error); + res.status(500).json({ success: false, error: 'Не удалось очистить выбранный DLE для футера' }); + } +}); + // Получение RPC настроек router.get('/rpc', async (req, res, next) => { try { diff --git a/backend/services/footerDleService.js b/backend/services/footerDleService.js new file mode 100644 index 0000000..de5dc2d --- /dev/null +++ b/backend/services/footerDleService.js @@ -0,0 +1,83 @@ +/** + * 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 + */ + +const logger = require('../utils/logger'); +const { getSecret, setSecret } = require('./secretStore'); + +const FOOTER_DLE_KEY = 'FOOTER_DLE_SELECTION'; + +function parseSelection(rawValue) { + if (!rawValue) { + return null; + } + + if (typeof rawValue === 'object') { + return rawValue; + } + + if (typeof rawValue === 'string') { + try { + return JSON.parse(rawValue); + } catch (error) { + logger.warn('[FooterDleService] Не удалось распарсить сохраненное значение footer DLE:', error.message); + } + } + + return null; +} + +async function getFooterSelection() { + const storedValue = await getSecret(FOOTER_DLE_KEY); + const parsed = parseSelection(storedValue); + + if (!parsed) { + return null; + } + + return { + address: parsed.address || null, + chainId: parsed.chainId ?? null, + updatedAt: parsed.updatedAt || null, + updatedBy: parsed.updatedBy || null, + }; +} + +async function setFooterSelection({ address, chainId = null, updatedBy = null }) { + const payload = { + address, + chainId: chainId !== undefined && chainId !== null ? Number(chainId) : null, + updatedAt: new Date().toISOString(), + updatedBy: updatedBy || null, + }; + + await setSecret(FOOTER_DLE_KEY, JSON.stringify(payload)); + return payload; +} + +async function clearFooterSelection(updatedBy = null) { + const payload = { + address: null, + chainId: null, + updatedAt: new Date().toISOString(), + updatedBy: updatedBy || null, + }; + + await setSecret(FOOTER_DLE_KEY, JSON.stringify(payload)); + return payload; +} + +module.exports = { + getFooterSelection, + setFooterSelection, + clearFooterSelection, +}; + diff --git a/docs/system-messages-management.md b/docs/system-messages-management.md new file mode 100644 index 0000000..8b3e386 --- /dev/null +++ b/docs/system-messages-management.md @@ -0,0 +1,132 @@ +# Техническое задание: управление системными сообщениями + +## 1. Цель и контекст +- Обеспечить управляемое отображение системных сообщений на главной странице (`/`, компонент `HomeView.vue`) и добавить административный интерфейс для их создания и модерации в разделе контента (`/content`, компонент `ContentListView.vue`). +- Системные сообщения должны поддерживать статусы «черновик» и «опубликовано», храниться в базе данных и быть доступны через REST API. + +## 2. Актуальное состояние +- Главная страница строится компонентом `HomeView.vue` и отображает чат ассистента (`ChatInterface.vue`), в котором выделены системные сообщения (`Message.vue`) по признаку `message.role === 'system'`. +- Раздел контента (`ContentListView.vue`) содержит карточки переходов: «Создать страницу», «Шаблоны», «Публичные», «Настройка», «Внутренние». Карточки ведут на существующие маршруты `content-create`, `content-templates`, `content-published`, `content-settings`, `content-internal`. +- В проекте отсутствуют сущности и API для системных сообщений; текущий `pagesService.js` работает только со страницами (`/pages`). + +## 3. Новые пользовательские сценарии +- **Просмотр системных сообщений (главная, `/`):** + - Опубликованные системные сообщения подгружаются в чат ассистента и отображаются в виде свернутых карточек с кликабельным заголовком. + - При клике на заголовок сообщение раскрывается: в ленте чата отображается полный текст сообщения **или** отправляется предзаготовленный ответ от ИИ ассистента (контент «ответа» хранится вместе с сообщением и выбирается по флагу `reply_type`). + - Сообщения должны явно маркироваться как системные (цвет, иконка). При повторном открытии пользователь видит последнее состояние раскрытия; возможно локальное запоминание «прочитано». +- **Раздел «Системные сообщения» (`/content`):** + - На странице `/content` появляется новая карточка «Системные сообщения» с кнопкой «Подробнее». Переход ведёт на страницу с пользовательской таблицей (`/content/system-messages/table`), построенной на уже существующих компонентах таблиц (см. `UserTablesList.vue`), без отдельного дэшборда карточек. + - Таблица отображает системные сообщения построчно, с возможностью множественного выбора через чекбоксы; доступные массовые действия: публикация, снятие с публикации, перевод в черновики, удаление. + - Для каждого сообщения по клику «Подробнее» (внутри строки) открывается просмотр/редактирование с формой (см. ниже). +- **Создание/редактирование (`/content/system-messages/create`, `/content/system-messages/:id/edit`):** + - Форма с полями: заголовок, краткое описание, основной текст (Markdown/HTML), тип ответа (`inline` — показывать контент, `assistant_reply` — отправлять подготовленный ответ от ассистента), поле «Ответ ассистента» (активно при `assistant_reply`), тег важности (info/warning/danger), дата начала публикации (опционально), дата окончания (опционально), флаг отображения гостям. + - Кнопки: «Сохранить как черновик», «Опубликовать». При редактировании — «Обновить», «Снять с публикации», «Удалить». + - Проверки: обязательность заголовка и основного текста (или ответа ассистента в соответствующем режиме); валидация дат (окончание ≥ начало). +- **Работа с таблицей системных сообщений:** + - Колонки: чекбокс выбора, заголовок (кликабелен), статус, тип ответа, период действия, целевая аудитория (гости/авторизованные/все), дата создания, автор. + - Массовые действия выполняются для выбранных строк; одиночные действия доступны через контекстное меню/кнопки в строке (редактировать, опубликовать, снять с публикации, удалить). + +## 4. Требования к интерфейсу +- В `ContentListView.vue` в сетку `management-blocks` добавить карточку «Системные сообщения» с кнопкой `Подробнее`. По дизайну карточка должна соответствовать существующим блокам (заголовок, описание, кнопка). +- Страница с таблицей системных сообщений: + - Использовать `BaseLayout` и локальные стили (`scoped`). + - Таблица поддерживает сортировку, фильтрацию по статусам и поиск по заголовку. + - Чекбоксы в шапке и строках для массового выбора; панель действий появляется при наличии выбора. + - Кнопка «Создать сообщение» открывает форму создания. +- Форма создания/редактирования: + - Rich-text (минимум Markdown) с предпросмотром и счётчиками символов/слов. + - Переключатель режима показа (`inline`/`assistant_reply`) с условным отображением поля «Ответ ассистента» (можно использовать ``). + - Поле для выбора иконки/цвета по `severity` (статические пресеты). +- Главная страница: + - Системные сообщения отображаются в блоке чата как свернутые карточки (`system-message-collapsed`). При клике заголовок разворачивает карточку (`system-message-expanded`) или инициирует отправку ассистента (UI показывает «сообщение от ассистента»). + - Для развёрнутых сообщений предусмотреть кнопку «Свернуть» и (опционально) «Отметить как прочитанное». Состояние хранить в `localStorage`. + +## 5. Маршрутизация и компоненты +- Добавить маршруты в `router/index.js`: +- - `/content/system-messages/table` → `SystemMessagesTableView.vue` +- - `/content/system-messages/create` → `SystemMessageCreateView.vue` +- - `/content/system-messages/:id` → `SystemMessageDetailsView.vue` (просмотр) +- - `/content/system-messages/:id/edit` → `SystemMessageEditView.vue` +- При необходимости для модальных/вложенных маршрутов можно использовать дочерние маршруты или именованные вью. +- Создать соответствующие Vue-компоненты в `src/views/content/system-messages/` и общий набор переиспользуемых элементов (таблица, форма, фильтры, массовые действия) в `src/components/system-messages/`. +- Создать сервис `src/services/systemMessagesService.js` с методами для нового API. + +## 6. Требования к API и данным +- **Новая таблица** `system_messages` (PostgreSQL): + - `id` (uuid, pk) + - `title` (text, not null) + - `summary` (text, nullable) + - `content` (text, not null) + - `reply_type` (enum: inline, assistant_reply; default inline) + - `assistant_reply_content` (text, nullable; требуется при `reply_type = assistant_reply`) + - `severity` (enum: info, warning, danger; default info) + - `status` (enum: draft, published; not null) + - `visible_for` (enum: all, authenticated, guests; default all) + - `publish_at` (timestamp, nullable) + - `expire_at` (timestamp, nullable) + - `created_at`, `updated_at` + - `created_by`, `updated_by` (references users/identities, nullable) + - `slug` (text, уникальный, для адресации по ссылке при необходимости) +- **REST API (Express):** + - `GET /system-messages` (пагинация, фильтры по статусу, поиску) + - `GET /system-messages/published` (фильтрация по дате/аудитории; публичная) + - `GET /system-messages/:id` (доступ только авторизованным редакторам) + - `POST /system-messages` (создание; права `MANAGE_LEGAL_DOCS`) + - `PATCH /system-messages/:id` (редактирование; проверка статусов) + - `DELETE /system-messages/:id` (мягкое удаление или физическое) + - `POST /system-messages/:id/publish` и `POST /system-messages/:id/unpublish` (опционально, если не использовать PATCH) +- Все защищённые эндпоинты должны требовать авторизацию и права (см. `permissions.js`, `usePermissions`). +- Добавить новую миграцию (`backend/scripts/run-migrations.js`) и ORM/SQL-файлы в существующем формате проекта. +- Обновить логирование и обработку ошибок `winston`, добавить валидацию входных данных (например, `Joi` или кастомную). + +## 7. Логика отображения на фронтенде +- `HomeView.vue`: + - При инициализации запрашивать опубликованные системные сообщения (учитывая текущую аудиторию) через `systemMessagesService.getPublished({ includeExpired: false })`. + - Кэшировать ответ в сторе или локальном состоянии; при подписке на WebSocket можно предусмотреть `system_message_updated` событие. + - Добавить обработчик раскрытия: по клику на заголовок либо подставлять полный текст сообщения (`inline`), либо инициировать цепочку отправки `assistant_reply_content` в чат (без участия пользователя). + - Добавить обработчик скрытия сообщения, сохраняющий идентификатор в `localStorage` и фильтрующий локально. +- `ContentListView.vue`: + - Добавить новую карточку «Системные сообщения» в сетку `management-blocks`, не нарушая адаптивную сетку (обновить `grid-template-columns` при необходимости). +- Страницы списков: + - Реализовать пагинацию (lazy loading или обычная), сортировку по дате. + - Для статусов использовать цветовые бейджи (info/warning/danger). +- Форма создания: + - Поддерживать сабмит через `yarn lint`-friendly код; валидация на клиенте (например, с использованием `computed`/`watch`). + - При успешной публикации перенаправлять на список опубликованных; при сохранении черновика — оставаться на странице с уведомлением. + +## 8. Требования к безопасности и доступу +- Сценарии создания/изменения доступны только ролям с `PERMISSIONS.MANAGE_LEGAL_DOCS`. +- Публичный список (`GET /system-messages/published`) фильтрует по: + - `status === 'published'`. + - `publish_at <= now()` (или null). + - `expire_at > now()` (или null). + - `visible_for` проверяется на основе контекста (гость/авторизованный). +- При выдаче через чат скрывать поля `created_by`, `updated_by`, внутренние метки. +- Учитывать CSRF, CORS, rate-limit (перенять конфиг из существующих роутов). + +## 9. Тестирование +- **Backend:** + - Юнит-тесты для CRUD в `tests/system-messages/*.test.js` (Mocha). + - Проверка фильтров publish/expire и доступа по ролям. + - Тест миграции (откат/применение). +- **Frontend:** + - Юнит-тесты Vue (если настроены) для основных компонентов (форма, список). + - E2E (при наличии) — сценарий: создание черновика → публикация → отображение на главной. +- **Регрессионные проверки:** + - Убедиться, что существующий список контента и чат ассистента продолжают работать без ошибок (`yarn lint`, `yarn test`). + +## 10. Интеграция и DevOps +- Обновить `docker-compose.yml` при необходимости (например, добавить миграции в стартовый процесс). +- Убедиться, что новые переменные окружения (если будут, например, лимиты количества сообщений) документированы в `README.md` и `setup-instruction.md`. +- Добавить скрипт seeding (опционально) для тестовых системных сообщений. + +## 11. Открытые вопросы +- Нужно ли хранить историю публикаций (auditing)? Если да — предусмотреть таблицу `system_messages_history`. +- Требуется ли поддержка многоязычности? (При отсутствии — ограничение на один язык, RU). +- Нужно ли уведомление по WebSocket при появлении новых сообщений? (Если да — добавить событие в `wsHub.js`). + +## 12. Итоговые артефакты +- Backend: новые маршруты, контроллеры, сервис, миграция. +- Frontend: новые страницы и сервис, обновлённые маршруты и компоненты `HomeView`, `ContentListView`. +- Документация: обновление `README.md` (раздел запуск), `application-description.md` или `tables-system.md` при изменении схем, настоящая спецификация. + diff --git a/frontend/src/composables/useFooterDle.js b/frontend/src/composables/useFooterDle.js index c621206..4ad5dc0 100644 --- a/frontend/src/composables/useFooterDle.js +++ b/frontend/src/composables/useFooterDle.js @@ -12,45 +12,23 @@ import { ref, provide, inject } from 'vue'; import api from '../api/axios'; -import { getFromStorage, setToStorage } from '../utils/storage'; // === SINGLETON STATE === const footerDle = ref(null); const isLoading = ref(false); const selectedDleAddress = ref(null); -// Загрузка адреса выбранного DLE из localStorage при инициализации -function loadSavedDleAddress() { - try { - const savedAddress = getFromStorage('footerDleAddress', null); - if (savedAddress) { - selectedDleAddress.value = savedAddress; - // Загружаем актуальные данные из блокчейна - loadDleFromBlockchain(savedAddress).then((loadedDle) => { - if (loadedDle) { - footerDle.value = loadedDle; - } - }); - } - } catch (error) { - console.warn('[useFooterDle] Ошибка при загрузке адреса из localStorage:', error); - } -} - -// Инициализация при загрузке модуля -loadSavedDleAddress(); - -// === API === - /** * Загружает данные DLE из блокчейна по адресу */ -async function loadDleFromBlockchain(dleAddress) { +async function loadDleFromBlockchain(dleAddress, chainId = null) { try { - isLoading.value = true; - const response = await api.post('/blockchain/read-dle-info', { - dleAddress: dleAddress - }); + const payload = { dleAddress }; + if (chainId !== undefined && chainId !== null) { + payload.chainId = chainId; + } + + const response = await api.post('/blockchain/read-dle-info', payload); if (response.data.success) { const blockchainData = response.data.data; @@ -58,79 +36,102 @@ async function loadDleFromBlockchain(dleAddress) { address: dleAddress, name: blockchainData.name || '', symbol: blockchainData.symbol || '', - logoURI: blockchainData.logoURI || '' + logoURI: blockchainData.logoURI || '', + chainId: blockchainData.currentChainId ?? chainId ?? null }; } return null; } catch (error) { console.error('[useFooterDle] Ошибка при загрузке DLE из блокчейна:', error); return null; + } +} + +/** + * Загружает текущее значение футера из backend + */ +async function fetchFooterSelection() { + try { + isLoading.value = true; + const response = await api.get('/settings/footer-dle'); + const selection = response.data?.data || null; + + if (selection && selection.address) { + selectedDleAddress.value = selection.address; + const loadedDle = await loadDleFromBlockchain(selection.address, selection.chainId ?? null); + + if (loadedDle) { + footerDle.value = loadedDle; + } else { + footerDle.value = { + address: selection.address, + name: '', + symbol: '', + logoURI: '', + chainId: selection.chainId ?? null + }; + } + } else { + selectedDleAddress.value = null; + footerDle.value = null; + } + } catch (error) { + console.error('[useFooterDle] Ошибка при получении footer DLE с backend:', error); } finally { isLoading.value = false; } } /** - * Устанавливает выбранный DLE для отображения в футере - * Сохраняет только адрес, данные всегда загружаются из блокчейна + * Устанавливает выбранный DLE для отображения в футере через backend * @param {string} dleAddress - Адрес DLE */ -async function setFooterDle(dleAddress) { +async function setFooterDle(dleAddress, chainId = null) { if (!dleAddress) { - footerDle.value = null; - selectedDleAddress.value = null; - // Удаляем из localStorage - try { - setToStorage('footerDleAddress', null); - } catch (error) { - console.warn('[useFooterDle] Ошибка при удалении из localStorage:', error); - } - return; + return clearFooterDle(); } - // Сохраняем только адрес - selectedDleAddress.value = dleAddress; - setToStorage('footerDleAddress', dleAddress); - - // Всегда загружаем актуальные данные из блокчейна - const loadedDle = await loadDleFromBlockchain(dleAddress); - if (loadedDle) { - footerDle.value = loadedDle; - } else { - // Если не удалось загрузить, очищаем состояние - footerDle.value = null; + try { + isLoading.value = true; + await api.post('/settings/footer-dle', { + dleAddress, + chainId: chainId ?? null + }); + await fetchFooterSelection(); + } catch (error) { + console.error('[useFooterDle] Ошибка при сохранении footer DLE:', error); + throw error; + } finally { + isLoading.value = false; } } /** - * Обновляет данные выбранного DLE из блокчейна - * Используется для периодического обновления или при необходимости + * Обновляет данные выбранного DLE, синхронизируя их с backend и блокчейном */ async function refreshFooterDle() { - if (!selectedDleAddress.value) { - return; - } - - const loadedDle = await loadDleFromBlockchain(selectedDleAddress.value); - if (loadedDle) { - footerDle.value = loadedDle; - } + await fetchFooterSelection(); } /** * Очищает выбранный DLE */ -function clearFooterDle() { - footerDle.value = null; - selectedDleAddress.value = null; - // Удаляем адрес из localStorage +async function clearFooterDle() { try { - setToStorage('footerDleAddress', null); + isLoading.value = true; + await api.delete('/settings/footer-dle'); + await fetchFooterSelection(); } catch (error) { - console.warn('[useFooterDle] Ошибка при очистке localStorage:', error); + console.error('[useFooterDle] Ошибка при очистке footer DLE:', error); + throw error; + } finally { + isLoading.value = false; } } +// Первичная загрузка при инициализации +fetchFooterSelection(); + // === SINGLETON API === const footerDleApi = { footerDle, diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 69e00f9..ea7f4a0 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -218,6 +218,11 @@ const routes = [ name: 'content-settings', component: () => import('../views/content/ContentSettingsView.vue'), }, + { + path: '/content/system-messages/table', + name: 'content-system-messages-table', + component: () => import('../views/content/system-messages/SystemMessagesTableView.vue'), + }, { path: '/content/page/:id', name: 'page-view', diff --git a/frontend/src/services/systemMessagesService.js b/frontend/src/services/systemMessagesService.js new file mode 100644 index 0000000..5830f92 --- /dev/null +++ b/frontend/src/services/systemMessagesService.js @@ -0,0 +1,55 @@ +/** + * Сервис для работы с системными сообщениями + */ + +import api from '../api/axios'; + +const BASE_URL = '/system-messages'; + +export default { + async getSystemMessages(params = {}) { + const response = await api.get(BASE_URL, { params }); + return response.data; + }, + + async getPublishedSystemMessages(params = {}) { + const response = await api.get(`${BASE_URL}/published`, { params }); + return response.data; + }, + + async getSystemMessage(id) { + const response = await api.get(`${BASE_URL}/${id}`); + return response.data; + }, + + async createSystemMessage(payload) { + const response = await api.post(BASE_URL, payload); + return response.data; + }, + + async updateSystemMessage(id, payload) { + const response = await api.patch(`${BASE_URL}/${id}`, payload); + return response.data; + }, + + async deleteSystemMessage(id) { + const response = await api.delete(`${BASE_URL}/${id}`); + return response.data; + }, + + async bulkPublish(ids) { + const response = await api.post(`${BASE_URL}/bulk/publish`, { ids }); + return response.data; + }, + + async bulkUnpublish(ids) { + const response = await api.post(`${BASE_URL}/bulk/unpublish`, { ids }); + return response.data; + }, + + async bulkDelete(ids) { + const response = await api.post(`${BASE_URL}/bulk/delete`, { ids }); + return response.data; + } +}; + diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index a41f585..ccb236a 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -22,7 +22,7 @@
-

ИИ Ассистент

+

Сообщения

diff --git a/frontend/src/views/content/ContentListView.vue b/frontend/src/views/content/ContentListView.vue index 009653a..616c884 100644 --- a/frontend/src/views/content/ContentListView.vue +++ b/frontend/src/views/content/ContentListView.vue @@ -70,6 +70,12 @@

Внутренние документы, видимые только по ролям

+ +
+

Системные сообщения

+

Управляйте уведомлениями, отображаемыми в чате и интерфейсе DLE

+ +
@@ -140,6 +146,10 @@ function goToContentSettings() { router.push({ name: 'content-settings' }); } +function goToSystemMessages() { + router.push({ name: 'content-system-messages-table' }); +} + async function deletePage(id) { if (confirm('Вы уверены, что хотите удалить эту страницу?')) { try { diff --git a/frontend/src/views/content/system-messages/SystemMessagesTableView.vue b/frontend/src/views/content/system-messages/SystemMessagesTableView.vue new file mode 100644 index 0000000..492c9d6 --- /dev/null +++ b/frontend/src/views/content/system-messages/SystemMessagesTableView.vue @@ -0,0 +1,803 @@ + + + + + + + + diff --git a/frontend/src/views/smartcontracts/SettingsView.vue b/frontend/src/views/smartcontracts/SettingsView.vue index 6ae7c6d..5301a3f 100644 --- a/frontend/src/views/smartcontracts/SettingsView.vue +++ b/frontend/src/views/smartcontracts/SettingsView.vue @@ -198,7 +198,7 @@ const setAsFooterDle = async () => { try { // Устанавливаем адрес, данные будут загружены из блокчейна - await setFooterDle(address); + await setFooterDle(address, dleInfo.value?.currentChainId ?? null); alert(`✅ DLE "${dleInfo.value.name} (${dleInfo.value.symbol})" теперь отображается в футере приложения`); } catch (error) { @@ -220,7 +220,7 @@ const removeFromFooter = async () => { } try { - clearFooterDle(); + await clearFooterDle(); alert('✅ DLE удален из футера приложения'); } catch (error) { console.error('Ошибка при удалении DLE из футера:', error); @@ -266,7 +266,8 @@ const loadDLEInfo = async () => { name: dleData.name, // Название DLE из блокчейна symbol: dleData.symbol, // Символ DLE из блокчейна address: dleData.dleAddress || address, // Адрес из API или из URL - logoURI: dleData.logoURI || '' // URL логотипа + logoURI: dleData.logoURI || '', // URL логотипа + currentChainId: Number(dleData.currentChainId) || null }; } else { console.error('Ошибка загрузки DLE:', response.data.error);