fix: fix dock layout
This commit is contained in:
@@ -1,34 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="drawer h-full">
|
<div class="app-container">
|
||||||
<input id="app-drawer" type="checkbox" class="drawer-toggle" v-model="drawerOpen"/>
|
<header class="app-header bg-base-200 w-full"></header>
|
||||||
|
|
||||||
<div class="drawer-content">
|
<section class="app-content">
|
||||||
<div class="app-container">
|
<FullscreenViewport v-if="platform === 'ios' || platform === 'android'"/>
|
||||||
<header class="app-header bg-base-200 w-full"></header>
|
|
||||||
|
|
||||||
<section class="telecart-main-section">
|
<div class="fixed inset-0 z-50 bg-white flex items-center justify-center text-center p-4
|
||||||
<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
|
|
||||||
[@supports(color:oklch(0%_0_0))]:hidden">
|
[@supports(color:oklch(0%_0_0))]:hidden">
|
||||||
<BrowserNotSupported/>
|
<BrowserNotSupported/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppDebugMessage v-if="settings.app_debug"/>
|
<AppDebugMessage v-if="settings.app_debug"/>
|
||||||
|
|
||||||
<RouterView v-slot="{ Component, route }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<KeepAlive include="Home,Products" :key="filtersStore.paramsHashForRouter">
|
<KeepAlive include="Home,Products" :key="filtersStore.paramsHashForRouter">
|
||||||
<component :is="Component" :key="route.fullPath"/>
|
<component :is="Component" :key="route.fullPath"/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</RouterView>
|
</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
|
<div
|
||||||
v-if="swiperBack.isActive.value"
|
v-if="swiperBack.isActive.value"
|
||||||
class="fixed top-1/2 left-0 z-50
|
class="fixed top-1/2 left-0 z-50
|
||||||
w-20
|
w-20
|
||||||
h-20
|
h-20
|
||||||
-translate-y-1/2
|
-translate-y-1/2
|
||||||
@@ -39,29 +36,18 @@
|
|||||||
py-2
|
py-2
|
||||||
text-primary-content
|
text-primary-content
|
||||||
"
|
"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-primary': swiperBack.deltaX.value < swiperBack.ACTIVATION_THRESHOLD,
|
'bg-primary': swiperBack.deltaX.value < swiperBack.ACTIVATION_THRESHOLD,
|
||||||
'bg-accent': 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">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -72,12 +58,11 @@ import {useRoute, useRouter} from "vue-router";
|
|||||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
||||||
import {useKeyboardStore} from "@/stores/KeyboardStore.js";
|
import {useKeyboardStore} from "@/stores/KeyboardStore.js";
|
||||||
import CartButton from "@/components/CartButton.vue";
|
|
||||||
import Dock from "@/components/Dock.vue";
|
import Dock from "@/components/Dock.vue";
|
||||||
import AppDebugMessage from "@/components/AppDebugMessage.vue";
|
import AppDebugMessage from "@/components/AppDebugMessage.vue";
|
||||||
import PrivacyPolicy from "@/components/PrivacyPolicy.vue";
|
import PrivacyPolicy from "@/components/PrivacyPolicy.vue";
|
||||||
import {useSwipeBack} from "@/composables/useSwipeBack.js";
|
|
||||||
import BrowserNotSupported from "@/BrowserNotSupported.vue";
|
import BrowserNotSupported from "@/BrowserNotSupported.vue";
|
||||||
|
import {useSwipeBack} from "@/composables/useSwipeBack.js";
|
||||||
|
|
||||||
const tg = useMiniApp();
|
const tg = useMiniApp();
|
||||||
const platform = ref();
|
const platform = ref();
|
||||||
@@ -93,9 +78,6 @@ const filtersStore = useProductFiltersStore();
|
|||||||
const keyboardStore = useKeyboardStore();
|
const keyboardStore = useKeyboardStore();
|
||||||
const backButton = window.Telegram.WebApp.BackButton;
|
const backButton = window.Telegram.WebApp.BackButton;
|
||||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
const haptic = window.Telegram.WebApp.HapticFeedback;
|
||||||
const drawerOpen = ref(false);
|
|
||||||
|
|
||||||
// Инициализация жеста Swipe Back (без визуального индикатора)
|
|
||||||
const swiperBack = useSwipeBack();
|
const swiperBack = useSwipeBack();
|
||||||
|
|
||||||
const routesToHideAppDock = [
|
const routesToHideAppDock = [
|
||||||
@@ -127,10 +109,6 @@ function navigateBack() {
|
|||||||
router.back();
|
router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDrawer() {
|
|
||||||
drawerOpen.value = !drawerOpen.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.name,
|
() => 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(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('click', handleClickOutside);
|
document.removeEventListener('click', handleClickOutside);
|
||||||
});
|
});
|
||||||
@@ -159,3 +147,19 @@ onMounted(() => {
|
|||||||
document.addEventListener('click', handleClickOutside);
|
document.addEventListener('click', handleClickOutside);
|
||||||
});
|
});
|
||||||
</script>
|
</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>
|
||||||
|
|||||||
@@ -77,3 +77,25 @@ function onDockItemClick() {
|
|||||||
haptic.selectionChanged();
|
haptic.selectionChanged();
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
<SwiperSlide
|
<SwiperSlide
|
||||||
v-for="product in block.data.products.data"
|
v-for="product in block.data.products.data"
|
||||||
:key="product.id"
|
: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
|
<RouterLink
|
||||||
:to="{name: 'product.show', params: {id: product.id}}"
|
:to="{name: 'product.show', params: {id: product.id}}"
|
||||||
@click="slideClick(product)"
|
@click="slideClick(product)"
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</BaseBlock>
|
</BaseBlock>
|
||||||
|
|||||||
10
frontend/spa/src/components/SwipeToBack.vue
Normal file
10
frontend/spa/src/components/SwipeToBack.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useSwipeBack} from "@/composables/useSwipeBack.js";
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import {createApp} from 'vue';
|
import {createApp, ref} from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
import {VueTelegramPlugin} from 'vue-tg';
|
import {VueTelegramPlugin} from 'vue-tg';
|
||||||
@@ -118,8 +118,16 @@ settings.load()
|
|||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
})
|
})
|
||||||
.then(() => window.Telegram.WebApp.ready())
|
.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 => {
|
.catch(error => {
|
||||||
const code = error.code ?? error.response._data.code;
|
console.error(error);
|
||||||
|
const code = error.code ?? error?.response?._data.code;
|
||||||
let ErrorComponent;
|
let ErrorComponent;
|
||||||
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
|
|||||||
@@ -64,3 +64,4 @@ router.beforeEach((to, from, next) => {
|
|||||||
ym.prevPath = from.path;
|
ym.prevPath = from.path;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
@plugin "daisyui" {
|
@plugin "daisyui" {
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
|
|
||||||
html, body, #app {
|
html, body, #app {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -20,24 +22,37 @@ html, body, #app {
|
|||||||
--swiper-pagination-bottom: 0px;
|
--swiper-pagination-bottom: 0px;
|
||||||
--product_list_title_max_lines: 2;
|
--product_list_title_max_lines: 2;
|
||||||
--tc-navbar-min-height: 3rem;
|
--tc-navbar-min-height: 3rem;
|
||||||
}
|
--dock-height: 72px;
|
||||||
|
|
||||||
#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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-container {
|
.app-container {
|
||||||
/*padding-top: calc(var(--tg-content-safe-area-inset-top) + var(--tg-safe-area-inset-top));*/
|
min-height: 100vh;
|
||||||
padding-bottom: calc(
|
display: flex;
|
||||||
var(--tg-safe-area-inset-bottom, 0px)
|
flex-direction: column;
|
||||||
+ 72px
|
|
||||||
|
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 {
|
.safe-top {
|
||||||
@@ -57,15 +72,6 @@ html, body, #app {
|
|||||||
color: white;
|
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 {
|
html {
|
||||||
background-color: var(--color-base-200);
|
background-color: var(--color-base-200);
|
||||||
}
|
}
|
||||||
@@ -73,3 +79,10 @@ html {
|
|||||||
.radius-box {
|
.radius-box {
|
||||||
border-radius: var(--radius-xl);
|
border-radius: var(--radius-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-fix {
|
||||||
|
bottom: calc(
|
||||||
|
var(--tg-content-safe-area-inset-bottom, 0px)
|
||||||
|
+ var(--tg-safe-area-inset-bottom, 0px)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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 class="text-error">{{ checkout.errorMessage }}</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="imagesLoaded && images.length === 0">
|
<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">
|
<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">
|
<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" />
|
<path fill-rule="evenodd"
|
||||||
</svg>
|
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"
|
||||||
<span class="text-xl">Нет изображений</span>
|
clip-rule="evenodd"/>
|
||||||
</div>
|
</svg>
|
||||||
|
<span class="text-xl">Нет изображений</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SingleProductImageSwiper v-else :images="images"/>
|
<SingleProductImageSwiper v-else :images="images"/>
|
||||||
@@ -119,8 +121,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="product.product_id"
|
<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"
|
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"
|
||||||
style="padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));">
|
>
|
||||||
<template v-if="settings.store_enabled">
|
<template v-if="settings.store_enabled">
|
||||||
<div v-if="error" class="alert alert-error alert-sm py-2">
|
<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"
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-4 w-4" fill="none"
|
||||||
@@ -306,7 +308,7 @@ function setQuantity(newQuantity) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Явно сбрасываем скролл наверх при открытии страницы товара
|
// Явно сбрасываем скролл наверх при открытии страницы товара
|
||||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
window.scrollTo({top: 0, behavior: 'instant'});
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
imagesLoaded.value = false;
|
imagesLoaded.value = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user