feat: fixed width and preloader for product view page
This commit is contained in:
@@ -102,3 +102,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<Swiper
|
<div class="relative">
|
||||||
:lazy="true"
|
<div v-if="!firstImageLoaded" class="image-preloader">
|
||||||
:modules="modules"
|
<div class="preloader-spinner"></div>
|
||||||
:pagination="pagination"
|
</div>
|
||||||
@sliderMove="hapticScroll"
|
<Swiper
|
||||||
>
|
v-if="product.images && product.images.length > 0"
|
||||||
<SwiperSlide
|
:lazy="true"
|
||||||
v-for="(image, index) in product.images"
|
:modules="modules"
|
||||||
:key="image.url"
|
:pagination="pagination"
|
||||||
|
@sliderMove="hapticScroll"
|
||||||
|
:class="{ 'opacity-0': !firstImageLoaded }"
|
||||||
>
|
>
|
||||||
<img
|
<SwiperSlide
|
||||||
:src="image.thumbnailURL"
|
v-for="(image, index) in product.images"
|
||||||
:alt="image.alt"
|
:key="image.url"
|
||||||
loading="lazy"
|
>
|
||||||
@click="showFullScreen(index)"
|
<img
|
||||||
/>
|
:src="image.thumbnailURL"
|
||||||
</SwiperSlide>
|
:alt="image.alt"
|
||||||
</Swiper>
|
loading="lazy"
|
||||||
|
@load="onFirstImageLoad"
|
||||||
|
@error="onFirstImageLoad"
|
||||||
|
@click="showFullScreen(index)"
|
||||||
|
/>
|
||||||
|
</SwiperSlide>
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
|
||||||
<FullScreenImageViewer
|
<FullScreenImageViewer
|
||||||
v-if="isFullScreen"
|
v-if="isFullScreen"
|
||||||
@@ -162,7 +171,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, onMounted, onUnmounted, ref} from "vue";
|
import {computed, nextTick, onMounted, onUnmounted, ref} from "vue";
|
||||||
import {useRoute, useRouter} from 'vue-router';
|
import {useRoute, useRouter} from 'vue-router';
|
||||||
import ProductOptions from "../components/ProductOptions/ProductOptions.vue";
|
import ProductOptions from "../components/ProductOptions/ProductOptions.vue";
|
||||||
import {useCartStore} from "../stores/CartStore.js";
|
import {useCartStore} from "../stores/CartStore.js";
|
||||||
@@ -192,6 +201,7 @@ const initialFullScreenIndex = ref(0);
|
|||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const yaMetrika = useYaMetrikaStore();
|
const yaMetrika = useYaMetrikaStore();
|
||||||
|
const firstImageLoaded = ref(false);
|
||||||
|
|
||||||
const canAddToCart = computed(() => {
|
const canAddToCart = computed(() => {
|
||||||
if (!product.value || product.value.options === undefined || product.value.options?.length === 0) {
|
if (!product.value || product.value.options === undefined || product.value.options?.length === 0) {
|
||||||
@@ -280,6 +290,12 @@ function setQuantity(newQuantity) {
|
|||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onFirstImageLoad() {
|
||||||
|
if (!firstImageLoaded.value) {
|
||||||
|
firstImageLoaded.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hapticScroll = useHapticScroll();
|
const hapticScroll = useHapticScroll();
|
||||||
const pagination = {
|
const pagination = {
|
||||||
clickable: true,
|
clickable: true,
|
||||||
@@ -304,6 +320,27 @@ onMounted(async () => {
|
|||||||
try {
|
try {
|
||||||
const {data} = await apiFetch(`/index.php?route=extension/tgshop/handle&api_action=product_show&id=${productId.value}`);
|
const {data} = await apiFetch(`/index.php?route=extension/tgshop/handle&api_action=product_show&id=${productId.value}`);
|
||||||
product.value = data;
|
product.value = data;
|
||||||
|
|
||||||
|
// Сброс состояния загрузки первого изображения
|
||||||
|
firstImageLoaded.value = false;
|
||||||
|
|
||||||
|
// Если изображений нет, сразу показываем слайдер
|
||||||
|
if (!data.images || data.images.length === 0) {
|
||||||
|
firstImageLoaded.value = true;
|
||||||
|
} else {
|
||||||
|
// Проверяем загрузку после следующего тика, чтобы дать Vue время отрендерить изображения
|
||||||
|
await nextTick();
|
||||||
|
// Если первое изображение уже загружено из кэша, оно может загрузиться до того, как мы начнем отслеживать
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!firstImageLoaded.value) {
|
||||||
|
// Проверяем, загружено ли первое изображение через проверку complete
|
||||||
|
const firstImg = document.querySelector('.swiper-slide img');
|
||||||
|
if (firstImg && firstImg.complete) {
|
||||||
|
firstImageLoaded.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
window.document.title = data.name;
|
window.document.title = data.name;
|
||||||
|
|
||||||
yaMetrika.pushHit(route.path, {
|
yaMetrika.pushHit(route.path, {
|
||||||
@@ -353,4 +390,43 @@ onMounted(async () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.swiper {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-slide img {
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preloader {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--fallback-bc, oklch(var(--bc) / 0.1));
|
||||||
|
border-radius: var(--radius-box, 0.5rem);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 4px solid var(--fallback-bc, oklch(var(--bc) / 0.2));
|
||||||
|
border-top-color: var(--fallback-p, oklch(var(--p)));
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ class ProductsService
|
|||||||
$images[] = [
|
$images[] = [
|
||||||
'thumbnailURL' => $this->image
|
'thumbnailURL' => $this->image
|
||||||
->make($imagePath)
|
->make($imagePath)
|
||||||
->resize($imageThumbWidth, $imageThumbHeight)
|
->contain($imageThumbWidth, $imageThumbHeight)
|
||||||
->url(),
|
->url(),
|
||||||
'largeURL' => $this->image->make($imagePath)->resize($imageFullWidth, $imageFullHeight)->url(),
|
'largeURL' => $this->image->make($imagePath)->resize($imageFullWidth, $imageFullHeight)->url(),
|
||||||
'width' => $width,
|
'width' => $width,
|
||||||
|
|||||||
Reference in New Issue
Block a user