сделан переключатель темы
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 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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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
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>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user