feat: integrate yandex metrika ecommerce
This commit is contained in:
@@ -7,11 +7,11 @@
|
||||
class="products-grid grid grid-cols-2 gap-x-5 gap-y-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8"
|
||||
>
|
||||
<RouterLink
|
||||
v-for="product in products"
|
||||
v-for="(product, index) in products"
|
||||
:key="product.id"
|
||||
class="product-grid-card group"
|
||||
:to="`/product/${product.id}`"
|
||||
@click="haptic"
|
||||
@click="productClick(product, index)"
|
||||
>
|
||||
<ProductImageSwiper :images="product.images"/>
|
||||
<h3 class="product-title mt-4 text-sm">{{ product.name }}</h3>
|
||||
@@ -54,7 +54,9 @@ import ProductImageSwiper from "@/components/ProductImageSwiper.vue";
|
||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||
import {ref} from "vue";
|
||||
import {useIntersectionObserver} from '@vueuse/core';
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
const settings = useSettingsStore();
|
||||
const bottom = ref(null);
|
||||
|
||||
@@ -87,8 +89,26 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
function haptic() {
|
||||
function productClick(product, index) {
|
||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
||||
yaMetrika.dataLayerPush({
|
||||
"ecommerce": {
|
||||
"currencyCode": settings.currency_code,
|
||||
"click": {
|
||||
"products": [
|
||||
{
|
||||
"id": product.id,
|
||||
"name": product.name,
|
||||
"price": product.final_price_numeric,
|
||||
"brand": product.manufacturer_name,
|
||||
"category": product.category_name,
|
||||
"list": "Главная страница",
|
||||
"position": index,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
useIntersectionObserver(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const YA_METRIKA_GOAL = {
|
||||
ADD_TO_CART: 'add_to_cart',
|
||||
PRODUCT_OPEN_EXTERNAL: 'product_open_external',
|
||||
CREATE_ORDER: 'create_order',
|
||||
ORDER_CREATED_SUCCESS: 'order_created_success',
|
||||
VIEW_PRODUCT: 'view_product',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {isNotEmpty} from "@/helpers.js";
|
||||
import {addToCart, cartEditItem, cartRemoveItem, getCart, setCoupon, setVoucher} from "@/utils/ftch.js";
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||
|
||||
export const useCartStore = defineStore('cart', {
|
||||
state: () => ({
|
||||
@@ -79,12 +81,27 @@ export const useCartStore = defineStore('cart', {
|
||||
}
|
||||
},
|
||||
|
||||
async removeItem(rowId) {
|
||||
async removeItem(cartItem, rowId, index = 0) {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
const formData = new FormData();
|
||||
formData.append('key', rowId);
|
||||
await cartRemoveItem(formData);
|
||||
useYaMetrikaStore().dataLayerPush({
|
||||
"ecommerce": {
|
||||
"currencyCode": useSettingsStore().currency_code,
|
||||
"remove": {
|
||||
"products": [
|
||||
{
|
||||
"id": cartItem.product_id,
|
||||
"name": cartItem.name,
|
||||
"quantity": cartItem.quantity,
|
||||
"position": index
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
await this.getProducts();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -2,6 +2,9 @@ import {defineStore} from "pinia";
|
||||
import {isNotEmpty} from "@/helpers.js";
|
||||
import {storeOrder} from "@/utils/ftch.js";
|
||||
import {useCartStore} from "@/stores/CartStore.js";
|
||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||
|
||||
export const useCheckoutStore = defineStore('checkout', {
|
||||
state: () => ({
|
||||
@@ -56,6 +59,37 @@ export const useCheckoutStore = defineStore('checkout', {
|
||||
const response = await storeOrder(this.customer);
|
||||
this.order = response.data;
|
||||
|
||||
if (! this.order.id) {
|
||||
console.debug(response.data);
|
||||
throw new Error('Ошибка создания заказа.');
|
||||
}
|
||||
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.ORDER_CREATED_SUCCESS, {
|
||||
price: this.order?.final_total_numeric,
|
||||
currency: this.order?.currency,
|
||||
});
|
||||
yaMetrika.dataLayerPush({
|
||||
"ecommerce": {
|
||||
"currencyCode": useSettingsStore().currency_code,
|
||||
"purchase": {
|
||||
"actionField": {
|
||||
"id": this.order.id,
|
||||
'revenue': this.order?.final_total_numeric,
|
||||
},
|
||||
"products": this.order.products ? this.order.products.map((product, index) => {
|
||||
return {
|
||||
id: product.product_id,
|
||||
name: product.name,
|
||||
price: product.total_numeric,
|
||||
position: index,
|
||||
quantity: product.quantif,
|
||||
};
|
||||
}) : [],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await window.Telegram.WebApp.HapticFeedback.notificationOccurred('success');
|
||||
await useCartStore().getProducts();
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ export const useSettingsStore = defineStore('settings', {
|
||||
ya_metrika_enabled: false,
|
||||
feature_coupons: false,
|
||||
feature_vouchers: false,
|
||||
currency_code: null,
|
||||
theme: {
|
||||
light: 'light', dark: 'dark', variables: {
|
||||
'--product_list_title_max_lines': 2,
|
||||
@@ -44,6 +45,7 @@ export const useSettingsStore = defineStore('settings', {
|
||||
this.store_enabled = settings.store_enabled;
|
||||
this.feature_coupons = settings.feature_coupons;
|
||||
this.feature_vouchers = settings.feature_vouchers;
|
||||
this.currency_code = settings.currency_code;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,8 +11,7 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
|
||||
|
||||
actions: {
|
||||
pushHit(url, params = {}) {
|
||||
const settings = useSettingsStore();
|
||||
if (!settings.ya_metrika_enabled) {
|
||||
if (!useSettingsStore().ya_metrika_enabled) {
|
||||
console.debug('[ym] Yandex Metrika disabled in settings.');
|
||||
return;
|
||||
}
|
||||
@@ -39,8 +38,7 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
|
||||
},
|
||||
|
||||
reachGoal(target, params = {}) {
|
||||
const settings = useSettingsStore();
|
||||
if (!settings.ya_metrika_enabled) {
|
||||
if (!useSettingsStore().ya_metrika_enabled) {
|
||||
console.debug('[ym] Yandex Metrika disabled in settings.');
|
||||
return;
|
||||
}
|
||||
@@ -61,6 +59,11 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
|
||||
},
|
||||
|
||||
initUserParams() {
|
||||
if (!useSettingsStore().ya_metrika_enabled) {
|
||||
console.debug('[ym] Yandex Metrika disabled in settings.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
|
||||
let tgID = null;
|
||||
|
||||
@@ -95,6 +98,9 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
|
||||
window.ym(window.YA_METRIKA_ID, item.event, item.payload.url, item.payload.params);
|
||||
} else if (item.event === 'reachGoal') {
|
||||
window.ym(window.YA_METRIKA_ID, item.event, item.payload.target, item.payload.params);
|
||||
} else if (item.event === 'dataLayer') {
|
||||
console.debug('[ym] queue dataLayer push: ', item.payload);
|
||||
window.dataLayer.push(item.payload);
|
||||
} else {
|
||||
console.error('[ym] Unsupported queue event: ', item.event);
|
||||
}
|
||||
@@ -102,5 +108,23 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
|
||||
|
||||
console.debug('[ym] Queue processing complete. Size: ', this.queue.length);
|
||||
},
|
||||
|
||||
dataLayerPush(object) {
|
||||
if (!useSettingsStore().ya_metrika_enabled) {
|
||||
console.debug('[ym] Yandex Metrika disabled in settings.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(window.dataLayer)) {
|
||||
console.debug('[ym] dataLayer push: ', object);
|
||||
window.dataLayer.push(object);
|
||||
} else {
|
||||
console.debug('[ym] dataLayer inaccessible. Put to queue');
|
||||
this.queue.push({
|
||||
event: 'dataLayer',
|
||||
payload: object,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ export function injectYaMetrika() {
|
||||
console.debug('[Init] Detected Yandex.Metrika ID:', window.YA_METRIKA_ID);
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
yaMetrika.initUserParams();
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
yaMetrika.processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<div v-if="cart.items.length > 0">
|
||||
<div
|
||||
v-for="item in cart.items"
|
||||
v-for="(item, index) in cart.items"
|
||||
:key="item.cart_id"
|
||||
class="card card-border bg-base-100 card-sm mb-3"
|
||||
:class="item.stock === false ? 'border-error' : ''"
|
||||
@@ -62,7 +62,7 @@
|
||||
v-model="item.quantity"
|
||||
@update:modelValue="cart.setQuantity(item.cart_id, $event)"
|
||||
/>
|
||||
<button class="btn btn-error" @click="removeItem(item.cart_id)" :disabled="cart.isLoading">
|
||||
<button class="btn btn-error" @click="removeItem(item, item.cart_id, index)" :disabled="cart.isLoading">
|
||||
<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="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
@@ -162,8 +162,8 @@ const lastTotal = computed(() => {
|
||||
return cart.totals.at(-1) ?? null;
|
||||
});
|
||||
|
||||
function removeItem(cartId) {
|
||||
cart.removeItem(cartId);
|
||||
function removeItem(cartItem, cartId, index) {
|
||||
cart.removeItem(cartItem, cartId, index);
|
||||
window.Telegram.WebApp.HapticFeedback.notificationOccurred('error');
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ import {computed, onMounted, ref} from "vue";
|
||||
import {IMaskComponent} from "vue-imask";
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||
|
||||
const checkout = useCheckoutStore();
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
@@ -92,11 +93,9 @@ async function onCreateBtnClick() {
|
||||
price: checkout.order?.final_total_numeric,
|
||||
currency: checkout.order?.currency,
|
||||
});
|
||||
|
||||
await checkout.makeOrder();
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.ORDER_CREATED_SUCCESS, {
|
||||
price: checkout.order?.final_total_numeric,
|
||||
currency: checkout.order?.currency,
|
||||
});
|
||||
|
||||
router.push({name: 'order_created'});
|
||||
} catch {
|
||||
error.value = 'Невозможно создать заказ.';
|
||||
|
||||
@@ -39,6 +39,7 @@ import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
||||
import Banner from "@/components/Banner.vue";
|
||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||
|
||||
defineOptions({
|
||||
name: 'Home'
|
||||
@@ -48,12 +49,14 @@ const router = useRouter();
|
||||
const filtersStore = useProductFiltersStore();
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
||||
const settings = useSettingsStore();
|
||||
|
||||
const products = ref([]);
|
||||
const hasMore = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const isLoadingMore = ref(false);
|
||||
const page = ref(1);
|
||||
const perPage = 20;
|
||||
|
||||
function showFilters() {
|
||||
haptic.impactOccurred('soft');
|
||||
@@ -67,11 +70,31 @@ async function fetchProducts() {
|
||||
console.debug('Home: Fetch products from server using filters: ', toRaw(filtersStore.applied));
|
||||
const response = await ftch('products', null, toRaw({
|
||||
page: page.value,
|
||||
perPage: perPage,
|
||||
filters: filtersStore.applied,
|
||||
}));
|
||||
products.value = response.data;
|
||||
hasMore.value = response.meta.hasMore;
|
||||
console.debug('Home: Products for main page loaded.');
|
||||
|
||||
yaMetrika.dataLayerPush({
|
||||
ecommerce: {
|
||||
currencyCode: settings.currency_code,
|
||||
impressions: products.value.map((product, index) => {
|
||||
return {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
price: product.final_price_numeric,
|
||||
brand: product.manufacturer_name,
|
||||
category: product.category_name,
|
||||
list: 'Главная страница',
|
||||
position: index,
|
||||
discount: product.price_numeric - product.final_price_numeric,
|
||||
quantity: product.product_quantity,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
|
||||
@@ -224,6 +224,25 @@ async function onCartBtnClick() {
|
||||
price: product.value.final_price_numeric,
|
||||
currency: product.value.currency,
|
||||
});
|
||||
yaMetrika.dataLayerPush({
|
||||
"ecommerce": {
|
||||
"currencyCode": settings.currency_code,
|
||||
"add": {
|
||||
"products": [
|
||||
{
|
||||
"id": product.value?.id,
|
||||
"name": product.value?.name,
|
||||
"price": product.value?.final_price_numeric,
|
||||
"brand": product.value?.manufacturer,
|
||||
"category": product.value?.category?.name,
|
||||
"quantity": 1,
|
||||
"list": "Выдача категории",
|
||||
"position": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
||||
await router.push({'name': 'cart'});
|
||||
@@ -240,6 +259,11 @@ function openProductInMarketplace() {
|
||||
return;
|
||||
}
|
||||
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.PRODUCT_OPEN_EXTERNAL, {
|
||||
price: product.value?.final_price_numeric,
|
||||
currency: product.value?.currency,
|
||||
});
|
||||
|
||||
window.Telegram.WebApp.openLink(product.value.share, {try_instant_view: false});
|
||||
}
|
||||
|
||||
@@ -285,6 +309,23 @@ onMounted(async () => {
|
||||
price: data.final_price_numeric,
|
||||
currency: data.currency,
|
||||
});
|
||||
|
||||
yaMetrika.dataLayerPush({
|
||||
"ecommerce": {
|
||||
"currencyCode": settings.currency_code,
|
||||
"detail": {
|
||||
"products": [
|
||||
{
|
||||
"id": data.product_id,
|
||||
"name": data.name,
|
||||
"price": data.final_price_numeric,
|
||||
"brand": data.manufacturer,
|
||||
"category": data.category?.name,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user