Rework frontend

This commit is contained in:
F4ilji
2025-03-23 00:38:16 +05:00
parent 17518ceed2
commit 49072e50c7
438 changed files with 14375 additions and 20015 deletions
@@ -0,0 +1,185 @@
<template>
<div class="flex justify-between pb-4 items-center">
<div class="flex w-full sm:items-center gap-x-5 sm:gap-x-3">
<div class="grow">
<div class="grid sm:flex sm:justify-between sm:items-center gap-2">
<BreadcrumbsWrapper v-if="breadcrumbs">
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
</Link>
</li>
<li v-if="breadcrumbs.mainSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSectionClick(breadcrumbs.mainSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.mainSection.data.title, 25) }}
</span>
</li>
<li v-if="breadcrumbs.subSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSubSectionClick(breadcrumbs.mainSection, breadcrumbs.subSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.subSection.data.title, 25) }}
</span>
</li>
<li class="text-sm">
<Link :href="route('page.view', breadcrumbs.page.data.path)" class="flex items-center text-gray-500 hover:text-primaryBlue">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.page.data.title, 25) }}
</Link>
</li>
</BreadcrumbsWrapper>
<BreadcrumbsWrapper v-else>
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
</Link>
</li>
<li class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(pageTitle, 25) }}
</span>
</li>
</BreadcrumbsWrapper>
</div>
</div>
</div>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
import BreadcrumbsWrapper from "@/componentss/ui/wrappers/BreadcrumbsWrapper.vue";
export default {
name: "PageBreadcrumbs",
components: {BreadcrumbsWrapper, BasicIcon, Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
isMobileDevice() {
if (typeof window !== 'undefined') {
return window.innerWidth < 1024; // Проверка на мобильные устройства
}
return false; // По умолчанию возвращаем false, когда не в браузере
},
handleSectionClick(breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSection(breadcrumb)
} else {
this.toggleDesktopNavSection(breadcrumb)
}
},
handleSubSectionClick(mainSectionBreadcrumb, breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb)
} else {
this.toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb)
}
},
highlightNavItem(item) {
item.classList.add('animate-pulse');
setTimeout(() => {
item.classList.remove('animate-pulse');
}, 4000);
},
openMobileNavMenu() {
const openMobileNavBtn = document.getElementById('open-mobile-btn');
openMobileNavBtn.click();
},
toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb) {
this.openMobileNavMenu()
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + mainSectionBreadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + mainSectionBreadcrumb.data.slug);
if (!sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
const subSectionNavBlock = mobileNavElement.querySelector('#nav-sub-section-accordion-' + breadcrumb.data.slug);
const subSectionNavBlockBtn = mobileNavElement.querySelector('#nav-sub-section-accordion-btn-' + breadcrumb.data.slug);
if (subSectionNavBlock.classList.contains('active')) {
subSectionNavBlockBtn.click()
}
this.highlightNavItem(subSectionNavBlock)
},
toggleMobileNavSection(breadcrumb) {
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + breadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + breadcrumb.data.slug);
if (sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
this.openMobileNavMenu()
this.highlightNavItem(sectionNavBlock)
},
toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavTitle = desktopNavElement.querySelector('#nav-sub-section-title-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + mainSectionBreadcrumb.data.slug);
sectionNavBlockBtn.click()
this.highlightNavItem(sectionNavTitle)
},
toggleDesktopNavSection(breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavBlock = desktopNavElement.querySelector('#nav-section-menu-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + breadcrumb.data.slug);
sectionNavBlockBtn.click()
// this.highlightNavItem(sectionNavBlock)
},
},
props: {
breadcrumbs: {
type: Object,
},
pageTitle: {
type: String,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,114 @@
<template>
<div class="w-full h-[67px] fixed pointer-events-none bvi-no-styles" id="visor"></div>
<nav class="order-last hidden w-56 shrink-0 lg:block">
<div v-if="headerNavs.length > 0" class="sticky top-[100px] h-[calc(100vh-121px)]">
<div class="text-gray-1000 mb-2 text-md font-medium">На этой странице</div>
<ul class="max-h-[70vh] space-y-1.5 overflow-hidden py-2 text-sm">
<li class="anchor-li" v-for="pageNav in headerNavs" :key="pageNav.id">
<a :class="{ 'translate-x-2 text-primaryBlue' : currentNavSection === generateSlug(pageNav.text), 'bg-transperant text-gray-600 hover:text-gray-900' : currentNavSection !== generateSlug(pageNav.text) }"
class="duration-150 block py-1 px-2 leading-[1.6] rounded-md"
:href="'#' + generateSlug(pageNav.text)">{{ pageNav.text }}</a>
</li>
<transition name="fade">
<li class="anchor-li flex items-center py-2 border-t" v-if="scrollTop" @click.prevent="scrollToTop">
<button class="bg-transperant text-gray-600 cursor-pointer hover:text-gray-900 duration-300 block px-2 leading-[1.6] rounded-md">К началу</button>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-[17px] text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 11.25l-3-3m0 0l-3 3m3-3v7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</li>
</transition>
</ul>
</div>
</nav>
</template>
<script>
import { Link } from "@inertiajs/vue3";
import slugify from "slugify";
export default {
name: "PageNavigateLinks",
components: { Link },
data() {
return {
currentNavSection: null,
scrollTop: false,
};
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText;
LimitedText = text.substring(0, symbols);
return LimitedText + "...";
}
return text;
},
generateSlug(text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
onScroll(e) {
const windowTop = window.scrollY;
this.scrollTop = windowTop > 100;
const headings = document.querySelectorAll("h2");
const visor = document.querySelector("#visor");
let lastVisibleHeading = null;
const visorRect = visor.getBoundingClientRect();
// Проверяем, находится ли визор в пределах видимости
if (visorRect.top > window.scrollY) {
this.currentNavSection = null;
lastVisibleHeading = null; // Сбрасываем заголовок, если визор не виден
return; // Выходим из функции, если визор не виден
}
for (let i = 0; i < headings.length; i++) {
const heading = headings[i];
const rect = heading.getBoundingClientRect();
// Проверяем, находится ли заголовок в видимой области и касается ли он элемента visor
if (
rect.top >= 0 &&
rect.bottom <= window.innerHeight &&
rect.bottom >= visorRect.top &&
rect.top <= visorRect.bottom
) {
// Проверяем, изменился ли заголовок
if (heading !== lastVisibleHeading) {
this.currentNavSection = heading.id;
lastVisibleHeading = heading;
}
break; // Выходим из цикла, если нашли видимый заголовок
}
}
},
scrollToTop() {
window.scrollTo(0, 0);
},
},
mounted() {
window.addEventListener("scroll", this.onScroll);
},
unmounted() {
window.removeEventListener("scroll", this.onScroll);
},
props: {
headerNavs: {
type: Array,
},
},
};
</script>
<style scoped>
</style>
@@ -0,0 +1,80 @@
<template>
<div class="sticky top-[100px] hidden h-[calc(100vh-121px)] max-w-[20%] min-w-[20%] md:flex md:shrink-0 md:flex-col md:justify-between">
<nav v-if="subSectionPages"
class="flex h-[calc(100vh-200px)] flex-col overflow-hidden pr-2 pb-4">
<div class="text-gray-1000 mb-2 text-md font-medium">{{ currentSection }}</div>
<div>
<ul class="px-0.5 last-of-type:mb-0 mb-8">
<li v-for="page in subSectionPages.data" :key="page.id" class="my-1.5 flex">
<a :class="{'text-white font-normal bg-primaryBlue border': isSameRoute(page.path), 'text-gray-600 hover:text-[#2C6288]': !isSameRoute(page.path) }"
:href="(page.is_url) ? page.path : route('page.view', page.path) + '/'"
class="duration-300 flex gap-x-2 w-full rounded-md cursor-pointer items-center px-2 py-1.5 text-left text-sm">
<BasicIcon class="w-4" :name="page.icon" />
{{
page.title
}}
</a>
</li>
</ul>
</div>
</nav>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
export default {
name: "PageSubSectionLinks",
components: {Link, BasicIcon},
data() {
return {
currentNavSection: null,
scrollTop: false,
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
isSameRoute(route) {
if (this.$page.props.ziggy.location === this.$page.props.ziggy.url + '/' + route) {
return true
}
},
},
mounted() {
// console.log(IconsMap)
},
props: {
subSectionPages: {
type: Object,
},
currentSection: {
type: String,
},
},
}
</script>
<style scoped>
</style>