ваше сообщение коммита
This commit is contained in:
@@ -1 +0,0 @@
|
||||
VITE_API_URL=http://localhost:8000
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
24
frontend/src/services/pagesService.js
Normal file
24
frontend/src/services/pagesService.js
Normal 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;
|
||||
},
|
||||
};
|
||||
@@ -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)">×</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>
|
||||
@@ -204,7 +204,7 @@ function goToContactsList() {
|
||||
}
|
||||
|
||||
function goToContent() {
|
||||
router.push({ name: 'content-page' });
|
||||
router.push({ name: 'content-list' });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
82
frontend/src/views/content/ContentListView.vue
Normal file
82
frontend/src/views/content/ContentListView.vue
Normal 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>
|
||||
30
frontend/src/views/content/ContentSettingsView.vue
Normal file
30
frontend/src/views/content/ContentSettingsView.vue
Normal 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>
|
||||
46
frontend/src/views/content/PageEditView.vue
Normal file
46
frontend/src/views/content/PageEditView.vue
Normal 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>
|
||||
38
frontend/src/views/content/PageView.vue
Normal file
38
frontend/src/views/content/PageView.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user