сделан переключатель темы
This commit is contained in:
59
src/App.vue
59
src/App.vue
@@ -3,53 +3,78 @@ import { onMounted, ref, watch } from "vue";
|
|||||||
import My_naw from "./components/My_naw.vue";
|
import My_naw from "./components/My_naw.vue";
|
||||||
import Section from "./components/Section.vue";
|
import Section from "./components/Section.vue";
|
||||||
import Setings from "./components/Setings.vue";
|
import Setings from "./components/Setings.vue";
|
||||||
|
import Status from "./components/Status.vue";
|
||||||
import Authe from "./components/Authe.vue";
|
import Authe from "./components/Authe.vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
// Инициализация темы при загрузке приложения
|
||||||
|
onMounted(() => {
|
||||||
|
const savedTheme = localStorage.getItem("theme");
|
||||||
|
if (savedTheme === "dark") {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else if (savedTheme === "light") {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
} else {
|
||||||
|
// Проверяем системные настройки
|
||||||
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Состояния страницы и данные для входа
|
// Состояния страницы и данные для входа
|
||||||
const currentPage = ref("admin-panel");
|
const currentPage = ref("admin-panel");
|
||||||
const items = ref([]);
|
const items = ref([]);
|
||||||
|
const items_status = ref([]);
|
||||||
|
|
||||||
// Универсальная функция для получения данных
|
// Универсальная функция для получения данных
|
||||||
const fetchData = async (url, targetRef) => {
|
const fetchData = async (url, targetRef) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(url)
|
const { data } = await axios.get(url);
|
||||||
targetRef.value = data
|
targetRef.value = data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Ошибка при получении данных с ${url}:`, 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
|
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
|
||||||
|
const loadItems_status = () =>
|
||||||
|
fetchData("http://127.0.0.1:8002/records_all_status", items_status);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadItems()
|
(loadItems(), loadItems_status());
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(items, loadItems)
|
watch(items, loadItems);
|
||||||
|
watch(items_status, loadItems_status);
|
||||||
|
|
||||||
function handleUpdate(newValue) {
|
function handleUpdate(newValue) {
|
||||||
currentPage.value = newValue; // изменение значения в родителе
|
currentPage.value = newValue; // изменение значения в родителе
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Authe :currentPage="currentPage" @update="handleUpdate"/>
|
<Authe :currentPage="currentPage" @update="handleUpdate" />
|
||||||
<div v-if="currentPage === 'rezylt'">
|
<div v-if="currentPage === 'rezylt'">
|
||||||
<div class="sm:flex">
|
<div class="sm:flex">
|
||||||
|
<My_naw :currentPage="currentPage" @update="handleUpdate" />
|
||||||
<My_naw :currentPage="currentPage" @update="handleUpdate"/>
|
<Section :items="items" />
|
||||||
<Section :items="items"/>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="currentPage === 'setings'">
|
<div v-if="currentPage === 'setings'">
|
||||||
<div class="sm:flex">
|
<div class="sm:flex">
|
||||||
<My_naw :currentPage="currentPage" @update="handleUpdate"/>
|
<My_naw :currentPage="currentPage" @update="handleUpdate" />
|
||||||
<Setings/>
|
<Setings />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="currentPage === 'status'">
|
||||||
|
<div class="sm:flex">
|
||||||
|
<My_naw :currentPage="currentPage" @update="handleUpdate" />
|
||||||
|
<Status :items="items_status" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
/* Для темной темы */
|
/* Включение dark mode через класс в Tailwind v4 */
|
||||||
@media (prefers-color-scheme: dark) {
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
body {
|
|
||||||
background: #111;
|
/* Базовые стили для светлой темы */
|
||||||
/* другие стили для темной темы */
|
body {
|
||||||
}
|
background-color: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Для светлой темы (по умолчанию или явно) */
|
/* Базовые стили для темной темы */
|
||||||
@media (prefers-color-scheme: light) {
|
.dark body {
|
||||||
body {
|
background-color: #111827;
|
||||||
background: #ccc; /* или любой другой цвет */
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<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 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">
|
||||||
|
<!-- Переключатель темы -->
|
||||||
|
<ThemeToggle />
|
||||||
|
|
||||||
<!-- Фиксированный левый блок -->
|
<!-- Фиксированный левый блок -->
|
||||||
<div v-if="currentPage === 'rezylt'" class="flex sm:flex-col">
|
<div v-if="currentPage === 'rezylt'" class="flex sm:flex-col">
|
||||||
<button
|
<button
|
||||||
@@ -15,6 +18,13 @@
|
|||||||
>
|
>
|
||||||
Настройка
|
Настройка
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="Valuestatus"
|
||||||
|
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>
|
||||||
|
|
||||||
<div v-if="currentPage === 'setings'" class="flex sm:flex-col">
|
<div v-if="currentPage === 'setings'" class="flex sm:flex-col">
|
||||||
@@ -31,11 +41,43 @@
|
|||||||
>
|
>
|
||||||
Настройка
|
Настройка
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="Valuestatus"
|
||||||
|
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 === 'status'" 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-300 sm:mt-3 w-full min-w-30 hover:text-white hover:bg-gray-600 px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Настройка
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="Valuestatus"
|
||||||
|
class="bg-gray-600 sm:mt-3 w-full text-white min-w-30 px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Избранное
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ThemeToggle from "./ThemeToggle.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentPage: String,
|
currentPage: String,
|
||||||
});
|
});
|
||||||
@@ -50,4 +92,8 @@ function ValueSeting() {
|
|||||||
function ValueRezylt() {
|
function ValueRezylt() {
|
||||||
emit("update", "rezylt");
|
emit("update", "rezylt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Valuestatus() {
|
||||||
|
emit("update", "status");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -38,7 +38,24 @@ const viewed_b = async () => {
|
|||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
url: props.url,
|
url: props.url,
|
||||||
viewed: true,
|
viewed: !props.viewed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const status_b = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
"http://127.0.0.1:8002/update_status_status",
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
url: props.url,
|
||||||
|
status: !props.status,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -55,7 +72,11 @@ const download = async () => {
|
|||||||
},
|
},
|
||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
});
|
});
|
||||||
const blobUrl = window.URL.createObjectURL(new Blob([rez.data], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }));
|
const blobUrl = window.URL.createObjectURL(
|
||||||
|
new Blob([rez.data], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
}),
|
||||||
|
);
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
// Принудительно добавляем .docx к имени файла
|
// Принудительно добавляем .docx к имени файла
|
||||||
let filename = props.title + ".docx";
|
let filename = props.title + ".docx";
|
||||||
@@ -102,7 +123,7 @@ onBeforeUnmount(() => {
|
|||||||
<p>{{ article_date }}</p>
|
<p>{{ article_date }}</p>
|
||||||
<a
|
<a
|
||||||
:href="url"
|
:href="url"
|
||||||
class="max-w-100 text-blue-600 dark:text-gray-400 hover:text-blue-800 underline flex"
|
class="max-w-100 text-blue-600 dark:text-gray-400 hover:text-blue-800 active:text-blue-800 underline flex"
|
||||||
style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis"
|
style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis"
|
||||||
>
|
>
|
||||||
<img src="/src/assets/href.webp" class="w-5 h-6 mr-2" />
|
<img src="/src/assets/href.webp" class="w-5 h-6 mr-2" />
|
||||||
@@ -131,14 +152,35 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
|
<div class="ml-4 mb-4">
|
||||||
|
<button class="cursor-pointer" @click="viewed_b">
|
||||||
|
<img
|
||||||
|
v-if="props.viewed"
|
||||||
|
src="https://img.icons8.com/?size=100&id=IJNt9jGwqy9N&format=png&color=000000"
|
||||||
|
class="h-12 hover:opacity-75 active:opacity-75"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="https://img.icons8.com/?size=100&id=qliQ29ghmkZh&format=png&color=000000"
|
||||||
|
class="h-12 hover:opacity-75 active:opacity-75"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="cursor-pointer" @click="status_b">
|
||||||
|
<img
|
||||||
|
v-if="props.status"
|
||||||
|
src="https://img.icons8.com/?size=100&id=fiJBCEhvIMyT&format=png&color=000000"
|
||||||
|
class="h-12 hover:opacity-75 active:opacity-75"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="https://img.icons8.com/?size=100&id=BmD1kkH92ppy&format=png&color=000000"
|
||||||
|
class="h-12 hover:opacity-75 active:opacity-75"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
<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"
|
class="hover:opacity-75 active:opacity-75 mr-10 rounded-xl cursor-pointer"
|
||||||
@click="viewed_b"
|
|
||||||
>
|
|
||||||
Прочитано
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="hover:opacity-75 mr-10 rounded-xl cursor-pointer"
|
|
||||||
@click="download"
|
@click="download"
|
||||||
>
|
>
|
||||||
<img src="/src/assets/word.png" class="h-10 mb-2" />
|
<img src="/src/assets/word.png" class="h-10 mb-2" />
|
||||||
|
|||||||
34
src/components/Status.vue
Normal file
34
src/components/Status.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<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>
|
||||||
79
src/components/ThemeToggle.vue
Normal file
79
src/components/ThemeToggle.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from "vue";
|
||||||
|
|
||||||
|
const isDark = ref(false);
|
||||||
|
|
||||||
|
// Инициализация темы при загрузке
|
||||||
|
onMounted(() => {
|
||||||
|
// Проверяем localStorage
|
||||||
|
const savedTheme = localStorage.getItem("theme");
|
||||||
|
if (savedTheme) {
|
||||||
|
isDark.value = savedTheme === "dark";
|
||||||
|
} else {
|
||||||
|
// Проверяем системные настройки
|
||||||
|
isDark.value = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
|
}
|
||||||
|
applyTheme();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Применение темы
|
||||||
|
const applyTheme = () => {
|
||||||
|
if (isDark.value) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
localStorage.setItem("theme", isDark.value ? "dark" : "light");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Переключение темы
|
||||||
|
const toggleTheme = () => {
|
||||||
|
isDark.value = !isDark.value;
|
||||||
|
applyTheme();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Следим за изменениями (для реактивности)
|
||||||
|
watch(isDark, applyTheme);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center justify-center p-3">
|
||||||
|
<!-- Переключатель (toggle switch) -->
|
||||||
|
<button
|
||||||
|
@click="toggleTheme"
|
||||||
|
class="relative inline-flex h-8 w-14 items-center rounded-full transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
:class="isDark ? 'bg-gray-700' : 'bg-gray-300'"
|
||||||
|
:aria-label="isDark ? 'Переключить на светлую тему' : 'Переключить на темную тему'"
|
||||||
|
>
|
||||||
|
<!-- Ползунок -->
|
||||||
|
<span
|
||||||
|
class="inline-block h-6 w-6 transform rounded-full bg-white shadow-md transition-transform duration-300"
|
||||||
|
:class="isDark ? 'translate-x-7' : 'translate-x-1'"
|
||||||
|
>
|
||||||
|
<!-- Иконка солнца или луны -->
|
||||||
|
<svg
|
||||||
|
v-if="!isDark"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mx-0.5 text-yellow-500"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
v-else
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mx-0.5 text-gray-700"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<p>Дата и время в Пекине: <br>{{ currentDateTime }}</p>
|
<p>Дата и время по Гринвичу: <br />{{ currentDateTime }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -8,29 +8,29 @@
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentDateTime: '', // строка с текущими датой и временем
|
currentDateTime: "", // строка с текущими датой и временем
|
||||||
timer: null,
|
timer: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateTime() {
|
updateTime() {
|
||||||
const options = {
|
const options = {
|
||||||
timeZone: 'Asia/Shanghai',
|
timeZone: "UTC",
|
||||||
year: 'numeric',
|
year: "numeric",
|
||||||
month: '2-digit',
|
month: "2-digit",
|
||||||
day: '2-digit',
|
day: "2-digit",
|
||||||
hour: '2-digit',
|
hour: "2-digit",
|
||||||
minute: '2-digit',
|
minute: "2-digit",
|
||||||
second: '2-digit',
|
second: "2-digit",
|
||||||
};
|
};
|
||||||
const formatter = new Intl.DateTimeFormat('zh-CN', options);
|
const formatter = new Intl.DateTimeFormat("en-GB", options);
|
||||||
const parts = formatter.formatToParts(new Date());
|
const parts = formatter.formatToParts(new Date());
|
||||||
|
|
||||||
const dateParts = {};
|
const dateParts = {};
|
||||||
parts.forEach(({ type, value }) => {
|
parts.forEach(({ type, value }) => {
|
||||||
dateParts[type] = value;
|
dateParts[type] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.currentDateTime = `${dateParts.year}-${dateParts.month}-${dateParts.day} ${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
|
this.currentDateTime = `${dateParts.year}-${dateParts.month}-${dateParts.day} ${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -42,4 +42,4 @@ export default {
|
|||||||
clearInterval(this.timer);
|
clearInterval(this.timer);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user