feat(slider): add slider feature
This commit is contained in:
@@ -1,100 +0,0 @@
|
||||
<template>
|
||||
<div v-if="slides.length > 0" class="app-banner px-4">
|
||||
<Swiper
|
||||
class="select-none"
|
||||
:slides-per-view="1"
|
||||
:space-between="50"
|
||||
pagination
|
||||
:pagination="{ clickable: true }"
|
||||
@swiper="onSwiper"
|
||||
@slideChange="onSlideChange"
|
||||
>
|
||||
<SwiperSlide v-for="slide in slides" :key="slide.id">
|
||||
<RouterLink
|
||||
v-if="slide?.link?.type === 'category'"
|
||||
:to="{name: 'product.categories.show', params: {category_id: slide.link.value.category_id}}"
|
||||
@click="sliderClick(slide)"
|
||||
>
|
||||
<img :src="slide.image" :alt="slide.title">
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink
|
||||
v-else-if="slide?.link?.type === 'product'"
|
||||
:to="{name: 'product.show', params: {id: slide.link.value.product_id}}"
|
||||
@click="sliderClick(slide)"
|
||||
>
|
||||
<img :src="slide.image" :alt="slide.title">
|
||||
</RouterLink>
|
||||
|
||||
<img
|
||||
v-else-if="slide?.link?.type === 'url'"
|
||||
:src="slide.image"
|
||||
:alt="slide.title"
|
||||
@click="openExternalLink(slide.link.value.url, slide)"
|
||||
>
|
||||
|
||||
<img v-else :src="slide.image" :alt="slide.title"/>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {Swiper, SwiperSlide} from 'swiper/vue';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import {onMounted, ref} from "vue";
|
||||
import {fetchBanner} from "@/utils/ftch.js";
|
||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
const slides = ref([]);
|
||||
|
||||
const onSwiper = (swiper) => {
|
||||
console.log(swiper);
|
||||
};
|
||||
const onSlideChange = () => {
|
||||
console.log('slide change');
|
||||
};
|
||||
|
||||
function sliderClick(slide) {
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.SLIDER_HOME_CLICK, {
|
||||
banner: slide.title,
|
||||
});
|
||||
}
|
||||
|
||||
function openExternalLink(link, slide) {
|
||||
if (! link) {
|
||||
return;
|
||||
}
|
||||
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.SLIDER_HOME_CLICK, {
|
||||
banner: slide.title,
|
||||
});
|
||||
|
||||
window.Telegram.WebApp.openLink(link, {try_instant_view: false});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetchBanner();
|
||||
slides.value = response.data;
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.app-banner .swiper-horizontal > .swiper-pagination-bullets {
|
||||
position: relative;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.app-banner .swiper-horizontal .swiper-slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-banner .swiper-horizontal .swiper-slide img {
|
||||
border-radius: var(--radius-box);
|
||||
}
|
||||
</style>
|
||||
180
frontend/spa/src/components/MainpageSlider.vue
Normal file
180
frontend/spa/src/components/MainpageSlider.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="sliders.mainpage_slider.is_enabled && sliders.mainpage_slider.slides.length > 0"
|
||||
class="app-banner"
|
||||
:class="classList"
|
||||
>
|
||||
<Swiper
|
||||
:effect="slideEffect"
|
||||
class="select-none"
|
||||
:slides-per-view="1"
|
||||
:space-between="sliders.mainpage_slider.space_between"
|
||||
:pagination="pagination"
|
||||
:lazy="true"
|
||||
:modules="modules"
|
||||
:scrollbar="scrollbar"
|
||||
:free-mode="sliders.mainpage_slider.free_mode"
|
||||
:loop="sliders.mainpage_slider.loop"
|
||||
:autoplay="autoplay"
|
||||
@swiper="onSwiper"
|
||||
@slideChange="onSlideChange"
|
||||
>
|
||||
<SwiperSlide v-for="slide in sliders.mainpage_slider.slides" :key="slide.id">
|
||||
<RouterLink
|
||||
v-if="slide?.link?.type === 'category'"
|
||||
:to="{name: 'product.categories.show', params: {category_id: slide.link.value.category_id}}"
|
||||
@click="sliderClick(slide)"
|
||||
>
|
||||
<img :src="slide.image" :alt="slide.title" loading="lazy">
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink
|
||||
v-else-if="slide?.link?.type === 'product'"
|
||||
:to="{name: 'product.show', params: {id: slide.link.value.product_id}}"
|
||||
@click="sliderClick(slide)"
|
||||
>
|
||||
<img :src="slide.image" :alt="slide.title" loading="lazy">
|
||||
</RouterLink>
|
||||
|
||||
<img
|
||||
v-else-if="slide?.link?.type === 'url'"
|
||||
:src="slide.image"
|
||||
:alt="slide.title"
|
||||
loading="lazy"
|
||||
@click="openExternalLink(slide.link.value.url, slide)"
|
||||
>
|
||||
|
||||
<img v-else :src="slide.image" :alt="slide.title" loading="lazy"/>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {Swiper, SwiperSlide} from 'swiper/vue';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
import {EffectCoverflow, EffectCards, EffectCube, EffectFlip, Scrollbar, Autoplay} from 'swiper/modules';
|
||||
import {computed, onMounted} from "vue";
|
||||
import {useSlidersStore} from "@/stores/SlidersStore.js";
|
||||
|
||||
const sliders = useSlidersStore();
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
const modules = [
|
||||
Autoplay,
|
||||
EffectCards,
|
||||
EffectFlip,
|
||||
EffectCube,
|
||||
Scrollbar,
|
||||
EffectCoverflow,
|
||||
];
|
||||
|
||||
const classList = computed(() => {
|
||||
if (sliders.mainpage_slider.effect === 'cards') {
|
||||
return ['px-8'];
|
||||
}
|
||||
|
||||
if (sliders.mainpage_slider.effect === 'flip') {
|
||||
return ['px-4', 'pb-4', 'pt-4'];
|
||||
}
|
||||
|
||||
if (sliders.mainpage_slider.effect === 'cube') {
|
||||
return ['px-4', 'pb-10'];
|
||||
}
|
||||
|
||||
return ['px-4'];
|
||||
});
|
||||
|
||||
const onSwiper = (swiper) => {
|
||||
console.log(swiper);
|
||||
};
|
||||
const onSlideChange = () => {
|
||||
console.log('slide change');
|
||||
};
|
||||
|
||||
const slideEffect = computed(() => {
|
||||
if (sliders.mainpage_slider.effect === 'slide') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sliders.mainpage_slider.effect;
|
||||
});
|
||||
|
||||
const pagination = computed(() => {
|
||||
if (sliders.mainpage_slider.pagination) {
|
||||
return {
|
||||
clickable: true, dynamicBullets: false,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const scrollbar = computed(() => {
|
||||
if (sliders.mainpage_slider.scrollbar) {
|
||||
return {
|
||||
hide: true,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const autoplay = computed(() => {
|
||||
if (sliders.mainpage_slider.autoplay) {
|
||||
return {
|
||||
delay: 3000,
|
||||
reverseDirection: false,
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
function sliderClick(slide) {
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.SLIDER_HOME_CLICK, {
|
||||
banner: slide.title,
|
||||
});
|
||||
}
|
||||
|
||||
function openExternalLink(link, slide) {
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.SLIDER_HOME_CLICK, {
|
||||
banner: slide.title,
|
||||
});
|
||||
|
||||
window.Telegram.WebApp.openLink(link, {try_instant_view: false});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.debug('[Mainpage Slider] Status: ', sliders.mainpage_slider);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.app-banner {
|
||||
aspect-ratio: 740 / 400;
|
||||
}
|
||||
|
||||
.app-banner .swiper {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.app-banner .swiper-horizontal > .swiper-pagination-bullets {
|
||||
position: relative;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.app-banner .swiper-horizontal .swiper-slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-banner .swiper-horizontal .swiper-slide img {
|
||||
border-radius: var(--radius-box);
|
||||
}
|
||||
</style>
|
||||
@@ -16,6 +16,7 @@ import 'swiper/element/bundle';
|
||||
import 'swiper/css/bundle';
|
||||
import AppLoading from "@/AppLoading.vue";
|
||||
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
||||
import {useSlidersStore} from "@/stores/SlidersStore.js";
|
||||
register();
|
||||
|
||||
const pinia = createPinia();
|
||||
@@ -26,6 +27,7 @@ app
|
||||
.use(VueTelegramPlugin);
|
||||
|
||||
const settings = useSettingsStore();
|
||||
useSlidersStore().fetchMainpageSlider();
|
||||
|
||||
const appLoading = createApp(AppLoading);
|
||||
appLoading.mount('#app');
|
||||
|
||||
26
frontend/spa/src/stores/SlidersStore.js
Normal file
26
frontend/spa/src/stores/SlidersStore.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {fetchBanner} from "@/utils/ftch.js";
|
||||
|
||||
export const useSlidersStore = defineStore('sliders', {
|
||||
state: () => ({
|
||||
mainpage_slider: {
|
||||
is_enabled: false,
|
||||
space_between: 30,
|
||||
autoplay: false,
|
||||
effect: 'cube', // null, flip, cards, cube
|
||||
pagination: false,
|
||||
scrollbar: false,
|
||||
free_mode: false,
|
||||
loop: false,
|
||||
slides: [],
|
||||
},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async fetchMainpageSlider() {
|
||||
console.debug('[Sliders Store] Fetch mainpage slider from server.');
|
||||
const response = await fetchBanner();
|
||||
this.mainpage_slider = Object.assign({}, this.mainpage_slider, response.data);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -6,6 +6,7 @@
|
||||
html, body, #app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
html {
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<div ref="goodsRef" class="pb-20">
|
||||
<CategoriesInline/>
|
||||
|
||||
<Banner/>
|
||||
<div class="overflow-hidden">
|
||||
<MainpageSlider/>
|
||||
</div>
|
||||
|
||||
<div class="px-5 fixed z-50 w-full opacity-90" style="bottom: calc(var(--tg-safe-area-inset-bottom, 0px) + 80px);">
|
||||
<div class="flex justify-center">
|
||||
@@ -31,12 +33,12 @@
|
||||
<script setup>
|
||||
import ProductsList from "@/components/ProductsList.vue";
|
||||
import CategoriesInline from "../components/CategoriesInline.vue";
|
||||
import {nextTick, onActivated, onMounted, ref, toRaw} from "vue";
|
||||
import {onActivated, onMounted, ref, toRaw} from "vue";
|
||||
import IconFunnel from "@/components/Icons/IconFunnel.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import ftch from "@/utils/ftch.js";
|
||||
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
||||
import Banner from "@/components/Banner.vue";
|
||||
import MainpageSlider from "@/components/MainpageSlider.vue";
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||
|
||||
Reference in New Issue
Block a user