сделан переключатель темы

This commit is contained in:
2026-03-03 15:58:08 +10:00
parent 759e730b10
commit d83046dcf5
7 changed files with 276 additions and 52 deletions

View File

@@ -3,53 +3,78 @@ 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 Status from "./components/Status.vue";
import Authe from "./components/Authe.vue";
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 items = ref([]);
const items_status = ref([]);
// Универсальная функция для получения данных
const fetchData = async (url, targetRef) => {
try {
const { data } = await axios.get(url)
targetRef.value = data
const { data } = await axios.get(url);
targetRef.value = data;
} 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(() => {
loadItems()
})
(loadItems(), loadItems_status());
});
watch(items, loadItems)
watch(items, loadItems);
watch(items_status, loadItems_status);
function handleUpdate(newValue) {
currentPage.value = newValue; // изменение значения в родителе
}
</script>
<template>
<Authe :currentPage="currentPage" @update="handleUpdate"/>
<Authe :currentPage="currentPage" @update="handleUpdate" />
<div v-if="currentPage === 'rezylt'">
<div class="sm:flex">
<My_naw :currentPage="currentPage" @update="handleUpdate"/>
<Section :items="items"/>
<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/>
<My_naw :currentPage="currentPage" @update="handleUpdate" />
<Setings />
</div>
</div>
<div v-if="currentPage === 'status'">
<div class="sm:flex">
<My_naw :currentPage="currentPage" @update="handleUpdate" />
<Status :items="items_status" />
</div>
</div>
</template>

View File

@@ -1,16 +1,14 @@
@import "tailwindcss";
/* Для темной темы */
@media (prefers-color-scheme: dark) {
body {
background: #111;
/* другие стили для темной темы */
}
/* Включение dark mode через класс в Tailwind v4 */
@custom-variant dark (&:where(.dark, .dark *));
/* Базовые стили для светлой темы */
body {
background-color: #f3f4f6;
}
/* Для светлой темы (по умолчанию или явно) */
@media (prefers-color-scheme: light) {
body {
background: #ccc; /* или любой другой цвет */
}
}
/* Базовые стили для темной темы */
.dark body {
background-color: #111827;
}

View File

@@ -1,5 +1,8 @@
<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">
<!-- Переключатель темы -->
<ThemeToggle />
<!-- Фиксированный левый блок -->
<div v-if="currentPage === 'rezylt'" class="flex sm:flex-col">
<button
@@ -15,6 +18,13 @@
>
Настройка
</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 === 'setings'" class="flex sm:flex-col">
@@ -31,11 +41,43 @@
>
Настройка
</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>
</template>
<script setup>
import ThemeToggle from "./ThemeToggle.vue";
const props = defineProps({
currentPage: String,
});
@@ -50,4 +92,8 @@ function ValueSeting() {
function ValueRezylt() {
emit("update", "rezylt");
}
function Valuestatus() {
emit("update", "status");
}
</script>

View File

@@ -38,7 +38,24 @@ const viewed_b = async () => {
{
params: {
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",
});
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");
// Принудительно добавляем .docx к имени файла
let filename = props.title + ".docx";
@@ -102,7 +123,7 @@ onBeforeUnmount(() => {
<p>{{ article_date }}</p>
<a
: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"
>
<img src="/src/assets/href.webp" class="w-5 h-6 mr-2" />
@@ -131,14 +152,35 @@ onBeforeUnmount(() => {
</div>
</div>
<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
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>
<button
class="hover:opacity-75 mr-10 rounded-xl cursor-pointer"
class="hover:opacity-75 active:opacity-75 mr-10 rounded-xl cursor-pointer"
@click="download"
>
<img src="/src/assets/word.png" class="h-10 mb-2" />

34
src/components/Status.vue Normal file
View 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>

View 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>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<p>Дата и время в Пекине: <br>{{ currentDateTime }}</p>
<p>Дата и время по Гринвичу: <br />{{ currentDateTime }}</p>
</div>
</template>
@@ -8,29 +8,29 @@
export default {
data() {
return {
currentDateTime: '', // строка с текущими датой и временем
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',
timeZone: "UTC",
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 formatter = new Intl.DateTimeFormat("en-GB", 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}`;
},
},
@@ -42,4 +42,4 @@ export default {
clearInterval(this.timer);
},
};
</script>
</script>