fix: fix dock layout

This commit is contained in:
2025-12-21 18:06:04 +03:00
parent 28d80d0f19
commit bdbdfc3650
9 changed files with 150 additions and 91 deletions

View File

@@ -1,34 +1,31 @@
<template>
<div class="drawer h-full">
<input id="app-drawer" type="checkbox" class="drawer-toggle" v-model="drawerOpen"/>
<div class="app-container">
<header class="app-header bg-base-200 w-full"></header>
<div class="drawer-content">
<div class="app-container">
<header class="app-header bg-base-200 w-full"></header>
<section class="app-content">
<FullscreenViewport v-if="platform === 'ios' || platform === 'android'"/>
<section class="telecart-main-section">
<FullscreenViewport v-if="platform === 'ios' || platform === 'android'"/>
<div class="fixed inset-0 z-50 bg-white flex items-center justify-center text-center p-4
<div class="fixed inset-0 z-50 bg-white flex items-center justify-center text-center p-4
[@supports(color:oklch(0%_0_0))]:hidden">
<BrowserNotSupported/>
</div>
<BrowserNotSupported/>
</div>
<AppDebugMessage v-if="settings.app_debug"/>
<AppDebugMessage v-if="settings.app_debug"/>
<RouterView v-slot="{ Component, route }">
<KeepAlive include="Home,Products" :key="filtersStore.paramsHashForRouter">
<component :is="Component" :key="route.fullPath"/>
</KeepAlive>
</RouterView>
<RouterView v-slot="{ Component, route }">
<KeepAlive include="Home,Products" :key="filtersStore.paramsHashForRouter">
<component :is="Component" :key="route.fullPath"/>
</KeepAlive>
</RouterView>
<PrivacyPolicy v-if="! settings.is_privacy_consented"/>
<PrivacyPolicy v-if="! settings.is_privacy_consented"/>
<Dock v-if="isAppDockShown"/>
<Dock v-if="isAppDockShown"/>
<div class="dock-spacer bg-base-100 z-50"></div>
<div
v-if="swiperBack.isActive.value"
class="fixed top-1/2 left-0 z-50
<div
v-if="swiperBack.isActive.value"
class="fixed top-1/2 left-0 z-50
w-20
h-20
-translate-y-1/2
@@ -39,29 +36,18 @@
py-2
text-primary-content
"
:class="{
:class="{
'bg-primary': swiperBack.deltaX.value < swiperBack.ACTIVATION_THRESHOLD,
'bg-accent': swiperBack.deltaX.value >= swiperBack.ACTIVATION_THRESHOLD,
}"
:style="{ transform: `translate(${easeOut(swiperBack.deltaX.value/10, 30)}px, -50%)` }"
:style="{ transform: `translate(${easeOut(swiperBack.deltaX.value/10, 30)}px, -50%)` }"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
</div>
</section>
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
</div>
</div>
<div class="drawer-side z-50 safe-top">
<label for="app-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<ul class="menu bg-base-200 text-base-content min-h-full w-80 p-4">
<li><a href="#">🏠 Главная</a></li>
<li><a href="#">🛒 Корзина</a></li>
<li><a @click="drawerOpen = false"> Закрыть</a></li>
</ul>
</div>
</section>
</div>
</template>
@@ -72,12 +58,11 @@ import {useRoute, useRouter} from "vue-router";
import {useSettingsStore} from "@/stores/SettingsStore.js";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import {useKeyboardStore} from "@/stores/KeyboardStore.js";
import CartButton from "@/components/CartButton.vue";
import Dock from "@/components/Dock.vue";
import AppDebugMessage from "@/components/AppDebugMessage.vue";
import PrivacyPolicy from "@/components/PrivacyPolicy.vue";
import {useSwipeBack} from "@/composables/useSwipeBack.js";
import BrowserNotSupported from "@/BrowserNotSupported.vue";
import {useSwipeBack} from "@/composables/useSwipeBack.js";
const tg = useMiniApp();
const platform = ref();
@@ -93,9 +78,6 @@ const filtersStore = useProductFiltersStore();
const keyboardStore = useKeyboardStore();
const backButton = window.Telegram.WebApp.BackButton;
const haptic = window.Telegram.WebApp.HapticFeedback;
const drawerOpen = ref(false);
// Инициализация жеста Swipe Back (без визуального индикатора)
const swiperBack = useSwipeBack();
const routesToHideAppDock = [
@@ -127,10 +109,6 @@ function navigateBack() {
router.back();
}
function toggleDrawer() {
drawerOpen.value = !drawerOpen.value;
}
watch(
() => route.name,
() => {
@@ -151,6 +129,16 @@ function handleClickOutside(e) {
}
}
watch(
() => route.name,
(name) => {
let height = '72px'; // дефолт
if (name === 'product.show') height = '146px';
document.documentElement.style.setProperty('--dock-height', height);
},
{ immediate: true }
);
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
});
@@ -159,3 +147,19 @@ onMounted(() => {
document.addEventListener('click', handleClickOutside);
});
</script>
<style scoped>
.dock-spacer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: calc(
var(--tg-content-safe-area-inset-bottom, 0px)
+ var(--tg-safe-area-inset-bottom, 0px)
);
pointer-events: none;
}
</style>

View File

@@ -77,3 +77,25 @@ function onDockItemClick() {
haptic.selectionChanged();
}
</script>
<style scoped>
.dock {
position: fixed;
left: 0;
right: 0;
height: var(--dock-height);
bottom: calc(
var(--tg-content-safe-area-inset-bottom, 0px)
+ var(--tg-safe-area-inset-bottom, 0px)
);
padding-bottom: 0;
display: flex;
align-items: center;
justify-content: space-around;
background: var(--color-base-100);
z-index: 50;
}
</style>

View File

@@ -17,9 +17,9 @@
<SwiperSlide
v-for="product in block.data.products.data"
:key="product.id"
class="radius-box bg-base-100 shadow-sm p-2"
class="pb-1"
>
<div>
<div class="radius-box bg-base-100 shadow-sm p-2">
<RouterLink
:to="{name: 'product.show', params: {id: product.id}}"
@click="slideClick(product)"
@@ -30,7 +30,6 @@
</div>
</RouterLink>
</div>
</SwiperSlide>
</Swiper>
</BaseBlock>

View File

@@ -0,0 +1,10 @@
<template>
</template>
<script setup>
import {useSwipeBack} from "@/composables/useSwipeBack.js";
</script>

View File

@@ -1,4 +1,4 @@
import {createApp} from 'vue';
import {createApp, ref} from 'vue';
import App from './App.vue';
import './style.css';
import {VueTelegramPlugin} from 'vue-tg';
@@ -118,8 +118,16 @@ settings.load()
app.mount('#app');
})
.then(() => window.Telegram.WebApp.ready())
.then(() => {
const con = console;
window.Telegram.WebApp.onEvent('viewportChanged', (state) => {
con.log('[Init]: viewportChanged', state.isStateStable, this.viewportHeight);
});
})
.catch(error => {
const code = error.code ?? error.response._data.code;
console.error(error);
const code = error.code ?? error?.response?._data.code;
let ErrorComponent;
switch (code) {

View File

@@ -64,3 +64,4 @@ router.beforeEach((to, from, next) => {
ym.prevPath = from.path;
next();
});

View File

@@ -1,4 +1,5 @@
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@plugin "daisyui" {
@@ -11,6 +12,7 @@
html, body, #app {
overflow-x: hidden;
min-height: 100vh;
}
:root {
@@ -20,24 +22,37 @@ html, body, #app {
--swiper-pagination-bottom: 0px;
--product_list_title_max_lines: 2;
--tc-navbar-min-height: 3rem;
}
#app {
position: relative;
/*padding-top: var(--tg-content-safe-area-inset-top);*/
padding-bottom: var(--tg-content-safe-area-inset-bottom);
padding-left: var(--tg-content-safe-area-inset-left);
padding-right: var(--tg-content-safe-area-inset-right);
--dock-height: 72px;
}
.app-container {
/*padding-top: calc(var(--tg-content-safe-area-inset-top) + var(--tg-safe-area-inset-top));*/
padding-bottom: calc(
var(--tg-safe-area-inset-bottom, 0px)
+ 72px
min-height: 100vh;
display: flex;
flex-direction: column;
padding-left: calc(
var(--tg-content-safe-area-inset-left, 0px)
+ var(--tg-safe-area-inset-left, 0px)
);
padding-right: calc(
var(--tg-content-safe-area-inset-right, 0px)
+ var(--tg-safe-area-inset-right, 0px)
);
}
.app-content {
flex: 1 1 auto;
padding-top: calc(
var(--tg-content-safe-area-inset-top, 0px)
+ var(--tg-safe-area-inset-top, 0px)
);
padding-bottom: calc(
var(--dock-height)
+ var(--tg-content-safe-area-inset-bottom, 0px)
+ var(--tg-safe-area-inset-bottom, 0px)
);
padding-left: var(--tg-safe-area-inset-left, 0px);
padding-right: var(--tg-safe-area-inset-right, 0px);
}
.safe-top {
@@ -57,19 +72,17 @@ html, body, #app {
color: white;
}
.telecart-main-section {
padding-top: calc(
var(--tg-content-safe-area-inset-top, 0rem)
+ var(--tg-safe-area-inset-top, 0rem)
/*+ var(--tc-navbar-min-height)*/
/*+ 1rem*/
);
}
html {
background-color: var(--color-base-200);
}
.radius-box {
border-radius: var(--radius-xl);
}
}
.bottom-fix {
bottom: calc(
var(--tg-content-safe-area-inset-bottom, 0px)
+ var(--tg-safe-area-inset-bottom, 0px)
);
}

View File

@@ -30,7 +30,7 @@
</div>
<div
class="fixed px-4 pb-4 pt-4 bottom-0 left-0 w-full bg-base-200 z-50 flex flex-col justify-between items-center gap-2 border-t-1 border-t-base-300">
class="fixed bottom-fix px-4 pb-4 pt-4 bottom-0 left-0 w-full bg-base-200 z-50 flex flex-col justify-between items-center gap-2 border-t-1 border-t-base-300">
<div class="text-error">{{ checkout.errorMessage }}</div>
<div>

View File

@@ -6,12 +6,14 @@
</div>
<div v-else-if="imagesLoaded && images.length === 0">
<div class="bg-base-200 aspect-square flex items-center justify-center flex-col text-base-300">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-18">
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
</svg>
<span class="text-xl">Нет изображений</span>
</div>
<div class="bg-base-200 aspect-square flex items-center justify-center flex-col text-base-300">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-18">
<path fill-rule="evenodd"
d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z"
clip-rule="evenodd"/>
</svg>
<span class="text-xl">Нет изображений</span>
</div>
</div>
<SingleProductImageSwiper v-else :images="images"/>
@@ -119,8 +121,8 @@
</div>
<div v-if="product.product_id"
class="fixed px-4 pb-4 pt-4 bottom-0 left-0 w-full bg-base-100/95 backdrop-blur-md z-50 flex flex-col gap-3 border-t border-base-300 shadow-lg"
style="padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));">
class="fixed bottom-fix px-4 pb-4 pt-4 left-0 w-full bg-base-100/95 backdrop-blur-md z-50 flex flex-col gap-3 border-t border-base-300 shadow-lg"
>
<template v-if="settings.store_enabled">
<div v-if="error" class="alert alert-error alert-sm py-2">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-4 w-4" fill="none"
@@ -306,15 +308,15 @@ function setQuantity(newQuantity) {
onMounted(async () => {
// Явно сбрасываем скролл наверх при открытии страницы товара
window.scrollTo({ top: 0, behavior: 'instant' });
window.scrollTo({top: 0, behavior: 'instant'});
isLoading.value = true;
imagesLoaded.value = false;
// Запускаем оба запроса параллельно
const productPromise = fetchProductById(productId.value);
const imagesPromise = fetchProductImages(productId.value);
try {
// Ждем только загрузку продукта для рендеринга страницы
const response = await productPromise;