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

This commit is contained in:
2025-07-16 17:25:58 +03:00
parent d952e89a26
commit 32acc60360
27 changed files with 539 additions and 138 deletions

View File

@@ -1 +0,0 @@
VITE_API_URL=http://localhost:8000

View File

@@ -1,4 +1,4 @@
import { ref, computed, watch, onMounted } from 'vue';
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
import api from '../api/axios';
import { getFromStorage, setToStorage, removeFromStorage } from '../utils/storage';
import { generateUniqueId } from '../utils/helpers';
@@ -400,9 +400,30 @@ export function useChat(auth) {
// window.addEventListener('load-chat-history', () => loadMessages({ initial: true }));
});
// onUnmounted(() => {
// window.removeEventListener('load-chat-history', () => loadMessages({ initial: true }));
// });
// --- WebSocket для real-time сообщений ---
let ws = null;
function setupChatWebSocket() {
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
ws = new WebSocket(`${wsProtocol}://${window.location.host}/ws`);
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'chat-message' && data.message) {
// Проверяем, что сообщение для текущего пользователя/диалога
// (можно доработать фильтрацию по conversation_id, user_id и т.д.)
messages.value.push(data.message);
}
} catch (e) {
console.error('[useChat] Ошибка обработки chat-message по WebSocket:', e);
}
};
}
onMounted(() => {
setupChatWebSocket();
});
onUnmounted(() => {
if (ws) ws.close();
});
return {
messages,

View File

@@ -166,9 +166,29 @@ const routes = [
},
{
path: '/content',
name: 'content-page',
name: 'content-list',
component: () => import('../views/content/ContentListView.vue'),
},
{
path: '/content/create',
name: 'content-create',
component: () => import('../views/ContentPageView.vue'),
},
{
path: '/content/settings',
name: 'content-settings',
component: () => import('../views/content/ContentSettingsView.vue'),
},
{
path: '/content/page/:id',
name: 'page-view',
component: () => import('../views/content/PageView.vue'),
},
{
path: '/content/page/:id/edit',
name: 'page-edit',
component: () => import('../views/content/PageEditView.vue'),
},
];
const router = createRouter({

View File

@@ -0,0 +1,24 @@
import api from '../api/axios';
export default {
async getPages() {
const res = await api.get('/pages');
return res.data;
},
async createPage(data) {
const res = await api.post('/pages', data);
return res.data;
},
async getPage(id) {
const res = await api.get(`/pages/${id}`);
return res.data;
},
async updatePage(id, data) {
const res = await api.patch(`/pages/${id}`, data);
return res.data;
},
async deletePage(id) {
const res = await api.delete(`/pages/${id}`);
return res.data;
},
};

View File

@@ -1,8 +1,13 @@
<template>
<BaseLayout>
<div class="content-page-block">
<h2>Контент</h2>
<form class="content-form" @submit.prevent>
<div class="content-header-nav">
<button class="nav-btn" @click="goToCreate">Создать</button>
<button class="nav-btn" @click="goToList">Список страниц</button>
<button class="nav-btn" @click="goToSettings">Настройки</button>
</div>
<router-view />
<form class="content-form" @submit.prevent="handleSubmit">
<div class="form-group">
<label for="title">Заголовок страницы *</label>
<input v-model="form.title" id="title" type="text" required />
@@ -15,51 +20,6 @@
<label for="content">Основной контент *</label>
<textarea v-model="form.content" id="content" required rows="6" />
</div>
<div class="form-group">
<label for="image">Изображение/обложка</label>
<input v-model="form.image" id="image" type="text" placeholder="URL или имя файла" />
</div>
<div class="form-group">
<label for="tags">Теги</label>
<div class="tags-input">
<input
v-model="tagInput"
@keydown.enter.prevent="addTag"
@blur="addTag"
placeholder="Введите тег и нажмите Enter"
/>
<div class="tags-list">
<span v-for="(tag, idx) in form.tags" :key="tag" class="tag">
{{ tag }}
<button type="button" @click="removeTag(idx)">&times;</button>
</span>
</div>
</div>
</div>
<div class="form-group">
<label for="category">Категория</label>
<select v-model="form.category" id="category">
<option value="">Не выбрано</option>
<option value="О компании">О компании</option>
<option value="Продукты">Продукты</option>
<option value="Блог">Блог</option>
<option value="FAQ">FAQ</option>
</select>
</div>
<div class="form-group">
<label for="addToChat">Добавить в чат</label>
<select v-model="form.addToChat" id="addToChat">
<option value="yes">Да</option>
<option value="no">Нет</option>
</select>
</div>
<div class="form-group">
<label for="rag">Интегрировать с RAG</label>
<select v-model="form.rag" id="rag">
<option value="yes">Да</option>
<option value="no">Нет</option>
</select>
</div>
<button class="submit-btn" type="submit">Сохранить</button>
</form>
</div>
@@ -68,31 +28,44 @@
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../components/BaseLayout.vue';
import pagesService from '../services/pagesService';
const router = useRouter();
function goToCreate() { router.push({ name: 'content-create' }); }
function goToList() { router.push({ name: 'content-list' }); }
function goToSettings() { router.push({ name: 'content-settings' }); }
const form = ref({
title: '',
summary: '',
content: '',
image: '',
tags: [],
category: '',
addToChat: 'yes',
rag: 'yes',
content: ''
});
const tagInput = ref('');
function addTag() {
const tag = tagInput.value.trim();
if (tag && !form.value.tags.includes(tag)) {
form.value.tags.push(tag);
async function handleSubmit() {
console.log('handleSubmit called', form.value);
try {
if (!form.value.title) {
alert('Заполните заголовок страницы!');
return;
}
// Создаём страницу через pagesService
const page = await pagesService.createPage({
title: form.value.title,
summary: form.value.summary,
content: form.value.content
});
console.log('createPage result:', page);
if (!page || !page.id) {
alert('Ошибка: страница не создана!');
return;
}
router.push({ name: 'content-list' });
} catch (e) {
alert('Ошибка при создании страницы: ' + (e?.message || e));
console.error('Ошибка при создании страницы:', e);
}
tagInput.value = '';
}
function removeTag(idx) {
form.value.tags.splice(idx, 1);
}
</script>
@@ -165,4 +138,21 @@ input[type="text"], textarea, select {
.submit-btn:hover {
background: #1a4e96;
}
.content-header-nav {
display: flex;
gap: 12px;
margin-bottom: 18px;
}
.nav-btn {
background: #f5f5f5;
border: 1px solid #d0d0d0;
border-radius: 6px;
padding: 7px 18px;
font-size: 1em;
cursor: pointer;
transition: background 0.2s;
}
.nav-btn:hover {
background: #e0e0e0;
}
</style>

View File

@@ -204,7 +204,7 @@ function goToContactsList() {
}
function goToContent() {
router.push({ name: 'content-page' });
router.push({ name: 'content-list' });
}
</script>

View File

@@ -0,0 +1,82 @@
<template>
<BaseLayout>
<div class="content-list-block">
<div class="content-header-nav">
<button class="nav-btn" @click="goToCreate">Создать</button>
<button class="nav-btn" @click="goToList">Список страниц</button>
<button class="nav-btn" @click="goToSettings">Настройки</button>
</div>
<h2>Список страниц</h2>
<ul v-if="pages.length" class="pages-list">
<li v-for="page in pages" :key="page.id">
<router-link :to="{ name: 'page-view', params: { id: page.id } }">{{ page.title }}</router-link>
</li>
</ul>
<div v-else class="empty-list-placeholder">Нет созданных страниц.</div>
</div>
</BaseLayout>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import pagesService from '../../services/pagesService';
const router = useRouter();
function goToCreate() { router.push({ name: 'content-create' }); }
function goToList() { router.push({ name: 'content-list' }); }
function goToSettings() { router.push({ name: 'content-settings' }); }
const pages = ref([]);
onMounted(async () => {
pages.value = await pagesService.getPages();
});
</script>
<style scoped>
.content-list-block {
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 32px rgba(0,0,0,0.12);
padding: 32px 24px 24px 24px;
width: 100%;
margin-top: 40px;
position: relative;
overflow-x: auto;
}
.content-header-nav {
display: flex;
gap: 12px;
margin-bottom: 18px;
}
.nav-btn {
background: #f5f5f5;
border: 1px solid #d0d0d0;
border-radius: 6px;
padding: 7px 18px;
font-size: 1em;
cursor: pointer;
transition: background 0.2s;
}
.nav-btn:hover {
background: #e0e0e0;
}
.empty-list-placeholder {
color: #888;
font-size: 1.1em;
margin-top: 2em;
}
.pages-list {
margin-top: 1.5em;
padding-left: 0;
list-style: none;
}
.pages-list li {
padding: 0.5em 0;
border-bottom: 1px solid #f0f0f0;
font-size: 1.08em;
}
.pages-list li:last-child {
border-bottom: none;
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<BaseLayout>
<div class="content-settings-block">
<h2>Настройки контента</h2>
<div class="empty-settings-placeholder">Здесь будут настройки для управления страницами.</div>
</div>
</BaseLayout>
</template>
<script setup>
import BaseLayout from '../../components/BaseLayout.vue';
</script>
<style scoped>
.content-settings-block {
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 32px rgba(0,0,0,0.12);
padding: 32px 24px 24px 24px;
width: 100%;
margin-top: 40px;
position: relative;
overflow-x: auto;
}
.empty-settings-placeholder {
color: #888;
font-size: 1.1em;
margin-top: 2em;
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<BaseLayout>
<div v-if="page" class="page-edit-block">
<h2>Редактировать страницу</h2>
<form @submit.prevent="save">
<label>Заголовок</label>
<input v-model="page.title" required />
<label>Описание</label>
<textarea v-model="page.summary" />
<label>Контент</label>
<textarea v-model="page.content" />
<button type="submit">Сохранить</button>
<button type="button" @click="goBack">Отмена</button>
</form>
</div>
<div v-else>Загрузка...</div>
</BaseLayout>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import pagesService from '../../services/pagesService';
const route = useRoute();
const router = useRouter();
const page = ref(null);
onMounted(async () => {
page.value = await pagesService.getPage(route.params.id);
});
async function save() {
await pagesService.updatePage(route.params.id, {
title: page.value.title,
summary: page.value.summary,
content: page.value.content
});
router.push({ name: 'page-view', params: { id: route.params.id } });
}
function goBack() {
router.push({ name: 'page-view', params: { id: route.params.id } });
}
</script>

View File

@@ -0,0 +1,38 @@
<template>
<BaseLayout>
<div v-if="page" class="page-view-block">
<h2>{{ page.title }}</h2>
<p><b>Описание:</b> {{ page.summary }}</p>
<div><b>Контент:</b> {{ page.content }}</div>
<button @click="goToEdit">Редактировать</button>
<button @click="deletePage" style="color:red">Удалить</button>
</div>
<div v-else>Загрузка...</div>
</BaseLayout>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import pagesService from '../../services/pagesService';
const route = useRoute();
const router = useRouter();
const page = ref(null);
onMounted(async () => {
page.value = await pagesService.getPage(route.params.id);
});
function goToEdit() {
router.push({ name: 'page-edit', params: { id: route.params.id } });
}
async function deletePage() {
if (confirm('Удалить страницу?')) {
await pagesService.deletePage(route.params.id);
router.push({ name: 'content-list' });
}
}
</script>

View File

@@ -19,8 +19,8 @@
</span>
</div>
Затем в открывшемся терминале WSL выполните:
<div class="copy-block" @click="copyToClipboard('cd ~/DApp-for-Business\nsudo bash webssh-agent/install.sh')">
<pre><code>cd ~/DApp-for-Business
<div class="copy-block" @click="copyToClipboard('cd ~/Digital_Legal_Entity(DLE)\nsudo bash webssh-agent/install.sh')">
<pre><code>cd ~/Digital_Legal_Entity(DLE)
sudo bash webssh-agent/install.sh</code></pre>
<span class="copy-icon">
<svg v-if="!copied" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="5" y="7" width="9" height="9" rx="2" stroke="#888" stroke-width="1.5"/><rect x="7" y="4" width="9" height="9" rx="2" stroke="#888" stroke-width="1.5"/></svg>
@@ -31,8 +31,8 @@ sudo bash webssh-agent/install.sh</code></pre>
<li>
<b>Linux:</b><br>
Откройте терминал и выполните:
<div class="copy-block" @click="copyToClipboard('cd ~/DApp-for-Business\nsudo bash webssh-agent/install.sh')">
<pre><code>cd ~/DApp-for-Business
<div class="copy-block" @click="copyToClipboard('cd ~/Digital_Legal_Entity(DLE)\nsudo bash webssh-agent/install.sh')">
<pre><code>cd ~/Digital_Legal_Entity(DLE)
sudo bash webssh-agent/install.sh</code></pre>
<span class="copy-icon">
<svg v-if="!copied" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="5" y="7" width="9" height="9" rx="2" stroke="#888" stroke-width="1.5"/><rect x="7" y="4" width="9" height="9" rx="2" stroke="#888" stroke-width="1.5"/></svg>