Initial commit

This commit is contained in:
2026-01-31 19:51:16 +10:00
commit 836665f1ed
23 changed files with 984 additions and 0 deletions

59
src/App.vue Normal file
View File

@@ -0,0 +1,59 @@
<script setup>
import { onMounted, ref, watch } from "vue";
import My_naw from "./components/My_naw.vue";
import Section from "./components/Section.vue";
import Setings from "./components/Setings.vue";
import Authe from "./components/Authe.vue";
import axios from "axios";
// Состояния страницы и данные для входа
const currentPage = ref("admin-panel");
const items = ref([]);
// Универсальная функция для получения данных
const fetchData = async (url, targetRef) => {
try {
const { data } = await axios.get(url)
targetRef.value = data
} catch (err) {
console.error(`Ошибка при получении данных с ${url}:`, err)
}
}
// Обертки для вызова
const loadItems = () => fetchData("http://45.129.78.228:8002/records_all", items) //http://127.0.0.1:8002/records_all http://45.129.78.228:8002/records_all
onMounted(() => {
loadItems()
})
watch(items, loadItems)
function handleUpdate(newValue) {
currentPage.value = newValue; // изменение значения в родителе
}
</script>
<template>
<Authe :currentPage="currentPage" @update="handleUpdate"/>
<div v-if="currentPage === 'rezylt'">
<div class="sm:flex">
<My_naw :currentPage="currentPage" @update="handleUpdate"/>
<Section :items="items"/>
</div>
</div>
<div v-if="currentPage === 'setings'">
<div class="sm:flex">
<My_naw :currentPage="currentPage" @update="handleUpdate"/>
<Setings/>
</div>
</div>
</template>
<style scoped>
/* Добавьте стили по необходимости */
</style>

BIN
src/assets/href.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

1
src/assets/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

16
src/assets/main.css Normal file
View File

@@ -0,0 +1,16 @@
@import "tailwindcss";
/* Для темной темы */
@media (prefers-color-scheme: dark) {
body {
background: #111;
/* другие стили для темной темы */
}
}
/* Для светлой темы (по умолчанию или явно) */
@media (prefers-color-scheme: light) {
body {
background: #ccc; /* или любой другой цвет */
}
}

92
src/components/Authe.vue Normal file
View File

@@ -0,0 +1,92 @@
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import axios from "axios";
// Получение props
const props = defineProps({
currentPage: String,
});
// Объявление события для обновления страницы
const emit = defineEmits(["update"]);
const login = ref("");
const password = ref("");
const authError = ref(false);
// Функция авторизации
const auth_my = async () => {
try {
const response = await axios.post("http://45.129.78.228:8004/login", {
username: login.value,
password: password.value,
});
if (response.data.message === "Login successful") {
authError.value = false;
emit("update", "rezylt");
} else {
authError.value = true;
}
} catch (err) {
authError.value = true;
console.log(err);
}
};
// Обработка глобального нажатия Enter
const handleKeyDown = (event) => {
if (event.key === "Enter") {
auth_my();
}
};
// Добавляем глобальный слушатель при монтировании
onMounted(() => {
window.addEventListener("keydown", handleKeyDown);
});
// Удаляем при размонтировании
onUnmounted(() => {
window.removeEventListener("keydown", handleKeyDown);
});
// Переход на стартовую страницу (сброс данных)
function showStartPage() {
emit("update", "admin-panel");
login.value = "";
password.value = "";
authError.value = false;
}
</script>
<template>
<div
v-if="props.currentPage === 'admin-panel'"
class="p-4 bg-white rounded shadow max-w-md mx-auto mt-10 dark:bg-gray-800 dark:text-neutral-300"
>
<h2>Вход для администратора</h2>
<div class="mb-2">
<label>Логин:</label>
<input v-model="login" type="text" class="border p-1 w-full" />
</div>
<div class="mb-2">
<label>Пароль:</label>
<input v-model="password" type="password" class="border p-1 w-full" />
</div>
<button
@click="auth_my"
class="mr-2 bg-teal-600 hover:bg-teal-800 text-white px-4 py-2 rounded cursor-pointer"
>
Войти
</button>
<button
@click="showStartPage"
class="dark:bg-gray-600 dark:hover:bg-gray-700 bg-gray-500 hover:bg-gray-600 px-4 py-2 rounded cursor-pointer"
>
Отмена
</button>
<div v-if="authError" class="mt-2 text-red-600">
Неверный логин или пароль
</div>
</div>
</template>

View File

@@ -0,0 +1,48 @@
<template>
<div>
<button class="rounded-md bg-white/10 px-2.5 py-1.5 text-sm font-semibold text-white inset-ring inset-ring-white/5 hover:bg-white/20" @click="open = true">Open drawer</button>
<TransitionRoot as="template" :show="open">
<Dialog class="relative z-10" @close="open = false">
<TransitionChild as="template" enter="ease-in-out duration-500" enter-from="opacity-0" enter-to="" leave="ease-in-out duration-500" leave-from="" leave-to="opacity-0">
<div class="fixed inset-0 bg-gray-900/50 transition-opacity"></div>
</TransitionChild>
<div class="fixed inset-0 overflow-hidden">
<div class="absolute inset-0 overflow-hidden">
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
<TransitionChild as="template" enter="transform transition ease-in-out duration-500 sm:duration-700" enter-from="translate-x-full" enter-to="translate-x-0" leave="transform transition ease-in-out duration-500 sm:duration-700" leave-from="translate-x-0" leave-to="translate-x-full">
<DialogPanel class="pointer-events-auto relative w-screen max-w-md">
<TransitionChild as="template" enter="ease-in-out duration-500" enter-from="opacity-0" enter-to="" leave="ease-in-out duration-500" leave-from="" leave-to="opacity-0">
<div class="absolute top-0 left-0 -ml-8 flex pt-4 pr-2 sm:-ml-10 sm:pr-4">
<button type="button" class="relative rounded-md text-gray-400 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500" @click="open = false">
<span class="absolute -inset-2.5"></span>
<span class="sr-only">Close panel</span>
<XMarkIcon class="size-6" aria-hidden="true" />
</button>
</div>
</TransitionChild>
<div class="relative flex h-full flex-col overflow-y-auto bg-gray-800 py-6 shadow-xl after:absolute after:inset-y-0 after:left-0 after:w-px after:bg-white/10">
<div class="px-4 sm:px-6">
<DialogTitle class="text-base font-semibold text-white">Panel title</DialogTitle>
</div>
<div class="relative mt-6 flex-1 px-4 sm:px-6">
<!-- Your content -->
</div>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</div>
</Dialog>
</TransitionRoot>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
import { XMarkIcon } from '@heroicons/vue/24/outline'
const open = ref(true)
</script>

53
src/components/My_naw.vue Normal file
View File

@@ -0,0 +1,53 @@
<template>
<div class=" sticky top-0 w-full sm:w-1/5 sm:h-screen bg-white z-10 pt-5 md:p-5 dark:bg-gray-800">
<!-- Фиксированный левый блок -->
<div v-if="currentPage === 'rezylt'" class="flex sm:flex-col">
<button
@click="ValueRezylt"
class="bg-gray-600 w-full text-white min-w-30 px-4 py-2 rounded cursor-pointer"
>
Результат
</button>
<button
@click="ValueSeting"
class="bg-gray-300 sm:mt-3 w-full min-w-30 hover:text-white hover:bg-gray-600 px-4 py-2 rounded cursor-pointer"
>
Настройка
</button>
</div>
<div v-if="currentPage === 'setings'" class="flex sm:flex-col">
<button
@click="ValueRezylt"
class="bg-gray-300 w-full min-w-30 hover:text-white hover:bg-gray-600 px-4 py-2 rounded cursor-pointer"
>
Результат
</button>
<button
@click="ValueSeting"
class="bg-gray-600 sm:mt-3 w-full text-white min-w-30 px-4 py-2 rounded cursor-pointer"
>
Настройка
</button>
</div>
</div>
</template>
<script setup>
const props = defineProps({
currentPage: String,
});
// Макросы Vue 3.3+ не требуют явного импорта
const emit = defineEmits(["update"]);
function ValueSeting() {
emit("update", "setings");
}
function ValueRezylt() {
emit("update", "rezylt");
}
</script>

View File

@@ -0,0 +1,35 @@
<!-- AdminLogin.vue -->
<template>
<div class="w-full sm:w-4/5 dark:text-neutral-300">
<div class="bg-white flex justify-between p-5 dark:bg-gray-800 ">
<h1>Результат:</h1>
<Time />
</div>
<div class="p-4">
<Stat
v-for="item in items"
:translation_text="item.translation_text"
:original_text="item.original_text"
:url="item.url"
:title="item.title"
:article_date="item.article_date"
:short_text="item.short_text"
:category="item.category"
:parsed_at="item.parsed_at"
:status = "item.status"
:viewed = "item.viewed"
:other = "item.other"
/>
</div>
</div>
</template>
<script setup>
import Stat from "./Stat.vue";
import Time from "./Time.vue";
defineProps({
items: Array,
});
</script>

240
src/components/Setings.vue Normal file
View File

@@ -0,0 +1,240 @@
<template>
<div class="w-full sm:w-4/5 dark:text-neutral-300">
<!-- Ввод времени -->
<div class="bg-white p-4 mb-4 flex-colum sm:flex dark:bg-gray-800">
<div class="w-full sm:max-w-100 flex">
<input
v-model="time"
type="text"
placeholder="01.01.2026"
class="dark:bg-gray-900 min-w-30 border-slate-100 shadow rounded-xl w-1/2 p-3"
/>
<button
class="dark:bg-orange-500 hover:dark:bg-orange-600 ml-4 shadow text-white bg-sky-700 w-1/2 hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
@click="start_parser_1"
>
Парсить 1 источник
</button>
</div>
<button
class="dark:bg-orange-500 hover:dark:bg-orange-600 sm:ml-2 shadow mt-1 sm:mt-0 text-white w-full sm:max-w-50 bg-sky-700 hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
@click="start_parser_2"
>
Парсить 2 источник
</button>
</div>
<!-- Список источников и промт -->
<div
class="dark:bg-gray-800 sm:m-5 bg-white p-4 hover:-translate-y-2 hover:shadow-2xl border-slate-100 rounded-xl transition"
>
<div
class="flex-colum sm:flex items-center mb-4 border-slate-900 justify-between"
>
<div class="w-full sm:max-w-60 flex">
<button
v-for="(source, index) in sources"
:key="source.name"
@click="selectSource(index)"
class="dark:bg-neutral-500 hover:dark:bg-neutral-600 p-2 mr-2 rounded-xl bg-sky-700 hover:bg-sky-900 w-1/2 text-white cursor-pointer"
>
{{ source.name }}
</button>
</div>
<!-- Кнопка сохранения источников -->
<button
class="shadow mt-1 sm:mt-0 text-white bg-green-500 hover:bg-green-600 rounded-xl w-full sm:max-w-60 px-2 min-h-11 cursor-pointer"
@click="saveSources"
>
Сохранить изменения
</button>
</div>
<!-- Редактируемый промт -->
<div class=" ">
<textarea
v-model="promt"
class="w-full min-h-100 rounded-xl p-2 border-2 border-neutral-300"
></textarea>
</div>
</div>
<!-- Список логов -->
<div
class="dark:bg-gray-800 mt-5 sm:m-5 bg-white p-4 hover:-translate-y-2 hover:shadow-2xl border-slate-100 rounded-xl transition"
>
<!-- Кнопка для переключения -->
<button
@click="toggleLogs"
class="dark:bg-orange-500 hover:dark:bg-orange-600 shadow text-white bg-sky-700 w-full hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
>
{{ showLogs ? "Скрыть логи" : "Показать логи" }}
</button>
<!-- Блок логов, показывается или скрывается -->
<div
class="border-1 border-neutral-300 p-2 mt-2 rounded-xl"
v-if="showLogs"
>
<div v-for="(log, index) in logs" :key="index">
{{ log }}
</div>
</div>
</div>
<div
class="dark:bg-gray-800 mt-5 sm:m-5 bg-white p-4 hover:-translate-y-2 hover:shadow-2xl border-slate-100 rounded-xl transition"
>
<!-- Кнопка для переключения -->
<button
@click="toggletask"
class="dark:bg-orange-500 hover:dark:bg-orange-600 shadow text-white bg-sky-700 w-full hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
>
{{ showtask ? "Скрыть задачи" : "Показать задачи" }}
</button>
<div
class="border-1 border-neutral-300 p-2 mt-2 rounded-xl overflow-x-auto"
v-if="showtask"
>
<table class="w-full min-w-max">
<thead>
<tr>
<th class="px-2 py-2 text-left">Status</th>
<th class="px-2 py-2 text-left">Source URL</th>
</tr>
</thead>
<tbody>
<tr v-for="task in tasks" :key="task.id">
<td
class="px-2 py-2 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-full"
>
{{ task.status }}
</td>
<td
class="px-2 py-2 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-full"
>
{{ task.source_url }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import axios from "axios";
const sources = ref([
{ name: "source1", url: "eee", prompt: "" },
{ name: "source2", url: "https://example2.com/api", prompt: "" },
]);
const currentSource = ref(sources.value[0]);
const time = ref("");
const promt = ref("Начальный текст");
const logs = ref([]);
const tasks = ref([]);
const showLogs = ref(false); // скрыто по умолчанию
const showtask = ref(false); // скрыто по умолчанию
// функция для переключения видимости логов
function toggleLogs() {
showLogs.value = !showLogs.value;
if (showLogs.value) {
load_log();
}
}
const load_log = async () => {
try {
const response = await axios.get("http://45.129.78.228:8001/logs");
logs.value = response.data.logs;
} catch (error) {
console.error("Ошибка при загрузке настроек:", error);
}
};
// функция для переключения видимости задач
function toggletask() {
showtask.value = !showtask.value;
if (showtask.value) {
loadTasks();
}
}
const loadTasks = async () => {
try {
const response = await axios.get(
"http://45.129.78.228:8001/get_tasks_offset",
);
// Предположим, что ответ уже массив задач
tasks.value = response.data; // или response.data если это массив
} catch (error) {
console.error("Ошибка при загрузке задач:", error);
}
};
// Загрузка настроек при монтировании
onMounted(() => {
loadSettings();
// load_log();
});
// watch(logs, load_log);
// Получение настроек с сервера
const loadSettings = async () => {
try {
const response = await axios.get("http://45.129.78.228:8001/settings");
sources.value = response.data.sources;
currentSource.value = sources.value[0];
promt.value = currentSource.value.prompt;
} catch (error) {
console.error("Ошибка при загрузке настроек:", error);
}
};
// Выбор источника
const selectSource = (index) => {
currentSource.value = sources.value[index];
promt.value = currentSource.value.prompt;
};
// Сохранение источников
const saveSources = async () => {
try {
await axios.post("http://45.129.78.228:8001/settings", {
name: currentSource.value.name,
url: "",
prompt: promt.value,
});
// Здесь можно отправить на сервер, если нужно
loadSettings();
} catch (error) {
console.error("Ошибка при сохранении источников:", error);
}
};
// Запуск парсинга 1
const start_parser_1 = async () => {
try {
await axios.post("http://45.129.78.228:8001/parser_1", {
time: time.value,
});
} catch (error) {
console.error("Ошибка запроса parser_1:", error);
}
};
// Запуск парсинга 2
const start_parser_2 = async () => {
try {
await axios.post("http://45.129.78.228:8001/parser_2");
} catch (error) {
console.error("Ошибка запроса parser_2:", error);
}
};
</script>

106
src/components/Stat.vue Normal file
View File

@@ -0,0 +1,106 @@
<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import axios from "axios";
const props = defineProps({
url: String,
parsed_at: String,
title: String,
category: String,
short_text: String,
article_date: String,
original_text: String,
translation_text: String,
other: String,
viewed: Boolean,
status: Boolean,
});
//Код для раскрытия/скрытия карточки
const isOpen = ref(false);
const cardRef = ref(null);
function toggle() {
isOpen.value = true; //!isOpen.value;
}
function handleClickOutside(event) {
if (cardRef.value && !cardRef.value.contains(event.target)) {
isOpen.value = false;
}
}
const viewed_b = async () => {
try {
const response = await axios.post(
"http://45.129.78.228:8002/update_viewed_status",
null,
{
params: {
url: props.url,
viewed: true,
},
},
);
} catch (err) {
console.log(err);
}
};
onMounted(() => {
document.addEventListener("click", handleClickOutside);
});
onBeforeUnmount(() => {
document.removeEventListener("click", handleClickOutside);
});
</script>
<template>
<div
class="bg-white dark:bg-gray-800 hover:-translate-y-2 hover:shadow-2xl rounded-xl transition pb-1 mb-3"
:class="{ 'opacity-50': props.viewed }"
><!--:class="{ 'opacity-50': props.viewed }" обработчик прочитанности-->
<div ref="cardRef" class="cursor-pointer" @click="toggle">
<div class="flex justify-between p-3">
<p>{{ article_date }}</p>
<a
:href="url"
class="max-w-100 text-blue-600 dark:text-gray-400 hover:text-blue-800 underline flex"
style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis"
>
<img
src="/src/assets/href.webp"
class="w-5 h-6 mr-2"
/>
{{ url }}
</a>
</div>
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
<p>{{ title }}</p>
</div>
<!-- Вариант с Tailwind: показывать/скрывать по состоянию -->
<div v-show="isOpen" class="transition-all">
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
<p>{{ short_text }}</p>
</div>
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
<p>{{ translation_text }}</p>
</div>
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
<p>{{ category }}</p>
</div>
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
<p>{{ original_text }}</p>
</div>
</div>
</div>
<button
class="ml-4 shadow mb-5 bg-lime-500 dark:bg-orange-500 hover:dark:bg-orange-600 hover:dark:text-neutral-50 hover:bg-lime-300 hover:text-stone-900 rounded-xl w-40 h-10 text-white cursor-pointer"
@click="viewed_b"
>
Прочитано
</button>
</div>
</template>

45
src/components/Time.vue Normal file
View File

@@ -0,0 +1,45 @@
<template>
<div>
<p>Дата и время в Пекине: <br>{{ currentDateTime }}</p>
</div>
</template>
<script>
export default {
data() {
return {
currentDateTime: '', // строка с текущими датой и временем
timer: null,
};
},
methods: {
updateTime() {
const options = {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
};
const formatter = new Intl.DateTimeFormat('zh-CN', options);
const parts = formatter.formatToParts(new Date());
const dateParts = {};
parts.forEach(({ type, value }) => {
dateParts[type] = value;
});
this.currentDateTime = `${dateParts.year}-${dateParts.month}-${dateParts.day} ${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
},
},
mounted() {
this.updateTime();
this.timer = setInterval(this.updateTime, 1000);
},
beforeDestroy() {
clearInterval(this.timer);
},
};
</script>

6
src/main.js Normal file
View File

@@ -0,0 +1,6 @@
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')