Initial commit
This commit is contained in:
59
src/App.vue
Normal file
59
src/App.vue
Normal 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
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
1
src/assets/logo.svg
Normal 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
16
src/assets/main.css
Normal 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
92
src/components/Authe.vue
Normal 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>
|
||||
48
src/components/Mob_naw.vue
Normal file
48
src/components/Mob_naw.vue
Normal 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
53
src/components/My_naw.vue
Normal 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>
|
||||
35
src/components/Section.vue
Normal file
35
src/components/Section.vue
Normal 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
240
src/components/Setings.vue
Normal 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
106
src/components/Stat.vue
Normal 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
45
src/components/Time.vue
Normal 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
6
src/main.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
Reference in New Issue
Block a user