feat: WIP add yandex metrika goals

This commit is contained in:
2025-10-26 14:35:39 +03:00
parent fbccd50675
commit 4e59c4e788
19 changed files with 219 additions and 38 deletions

View File

@@ -547,17 +547,18 @@ HTML,
], ],
'orders' => [ 'orders' => [
'module_tgshop_order_customer_group_id' => [
'type' => 'select',
'options' => $this->getCustomerGroups(),
'help' => 'Группа покупателей, которая будет назначена для заказов, оформленных через Telegram-магазин.',
],
'module_tgshop_order_default_status_id' => [ 'module_tgshop_order_default_status_id' => [
'type' => 'select', 'type' => 'select',
'options' => $this->getOrderStatuses(), 'options' => $this->getOrderStatuses(),
'help' => 'Статус, с которым будут создаваться заказы через Telegram по умолчанию.', 'help' => 'Статус, с которым будут создаваться заказы через Telegram по умолчанию.',
], ],
'module_tgshop_order_customer_group_id' => [
'hidden' => true,
'type' => 'select',
'options' => $this->getCustomerGroups(),
'help' => 'Группа покупателей, которая будет назначена для заказов, оформленных через Telegram-магазин.',
],
], ],
]; ];
} }

View File

@@ -45,8 +45,7 @@ class ControllerExtensionTgshopHandle extends Controller
'app_debug' => $appDebug, 'app_debug' => $appDebug,
'oc_config_tax' => $this->config->get('config_tax'), 'oc_config_tax' => $this->config->get('config_tax'),
'oc_default_currency' => $this->config->get('config_currency'), 'oc_default_currency' => $this->config->get('config_currency'),
// ID группы покупателей, которая будет использоаваться в заказах через Телеграм. 'oc_customer_group_id' => $this->config->get('config_customer_group_id'),
'oc_customer_group_id' => $this->config->get('module_tgshop_order_customer_group_id'),
// ID магазина, для которого будут создаваться заказы из Телеграм // ID магазина, для которого будут создаваться заказы из Телеграм
'oc_store_id' => 0, 'oc_store_id' => 0,
// Название магазина, для которого будут создаваться заказы из Телеграм // Название магазина, для которого будут создаваться заказы из Телеграм

View File

@@ -177,6 +177,7 @@ class OrderCreateService
$this->cartService->flush(); $this->cartService->flush();
$orderData['order_id'] = $orderId; $orderData['order_id'] = $orderId;
$orderData['total_numeric'] = $orderData['total'] ?? 0;
$orderData['total'] = $cart['total_text'] ?? ''; $orderData['total'] = $cart['total_text'] ?? '';
$this->sendNotifications($orderData, $data['tgData']); $this->sendNotifications($orderData, $data['tgData']);
@@ -192,6 +193,8 @@ class OrderCreateService
'id' => $orderData['order_id'], 'id' => $orderData['order_id'],
'created_at' => $dateTimeFormatted, 'created_at' => $dateTimeFormatted,
'total' => $orderData['total'], 'total' => $orderData['total'],
'final_total_numeric' => $orderData['total_numeric'],
'currency' => $currencyCode,
]; ];
} }

View File

@@ -214,6 +214,7 @@ class ProductsService
$configTax = $this->oc->config->get('config_tax'); $configTax = $this->oc->config->get('config_tax');
$product_info = $this->oc->model_catalog_product->getProduct($productId); $product_info = $this->oc->model_catalog_product->getProduct($productId);
$currency = $this->oc->session->data['currency'];
if (! $product_info) { if (! $product_info) {
throw new EntityNotFoundException('Product with id ' . $productId . ' not found'); throw new EntityNotFoundException('Product with id ' . $productId . ' not found');
@@ -280,24 +281,24 @@ class ProductsService
$data['images'] = $images; $data['images'] = $images;
$data['price'] = $this->currency->format( $productPrice = $this->tax->calculate(
$this->tax->calculate( $product_info['price'],
$product_info['price'], $product_info['tax_class_id'],
$product_info['tax_class_id'], $configTax,
$configTax,
),
$this->oc->session->data['currency']
); );
$data['price'] = $this->currency->format($productPrice, $currency);
$data['currency'] = $currency;
$data['final_price_numeric'] = $productPrice;
if (! is_null($product_info['special']) && (float) $product_info['special'] >= 0) { if (! is_null($product_info['special']) && (float) $product_info['special'] >= 0) {
$data['special'] = $this->currency->format( $productSpecialPrice = $this->tax->calculate(
$this->tax->calculate( $product_info['special'],
$product_info['special'], $product_info['tax_class_id'],
$product_info['tax_class_id'], $configTax,
$configTax,
),
$this->oc->session->data['currency']
); );
$data['special'] = $this->currency->format($productSpecialPrice, $currency);
$data['final_price_numeric'] = $productSpecialPrice;
$tax_price = (float) $product_info['special']; $tax_price = (float) $product_info['special'];
} else { } else {
$data['special'] = false; $data['special'] = false;
@@ -305,7 +306,7 @@ class ProductsService
} }
if ($configTax) { if ($configTax) {
$data['tax'] = $this->currency->format($tax_price, $this->oc->session->data['currency']); $data['tax'] = $this->currency->format($tax_price, $currency);
} else { } else {
$data['tax'] = false; $data['tax'] = false;
} }
@@ -323,7 +324,7 @@ class ProductsService
$product_info['tax_class_id'], $product_info['tax_class_id'],
$configTax, $configTax,
), ),
$this->oc->session->data['currency'] $currency
) )
); );
} }
@@ -341,7 +342,7 @@ class ProductsService
$product_info['tax_class_id'], $product_info['tax_class_id'],
$configTax ? 'P' : false $configTax ? 'P' : false
), ),
$this->oc->session->data['currency'] $currency
); );
$product_option_value_data[] = array( $product_option_value_data[] = array(

View File

@@ -0,0 +1,14 @@
export const YA_METRIKA_GOAL = {
ADD_TO_CART: 'add_to_cart',
CREATE_ORDER: 'create_order',
ORDER_CREATED_SUCCESS: 'order_created_success',
VIEW_PRODUCT: 'view_product',
VIEW_CART: 'view_cart',
VIEW_CHECKOUT: 'view_checkout',
VIEW_HOME: 'view_home',
VIEW_FILTERS: 'view_filters',
FILTERS_APPLY: 'filters_apply',
FILTERS_RESET: 'filters_reset',
VIEW_SEARCH: 'view_search',
PERFORM_SEARCH: 'perform_search',
};

View File

@@ -35,3 +35,9 @@ export const router = createRouter({
history: createWebHashHistory('/image/catalog/tgshopspa/'), history: createWebHashHistory('/image/catalog/tgshopspa/'),
routes, routes,
}); });
router.beforeEach((to, from, next) => {
const ym = useYaMetrikaStore();
ym.prevPath = from.path;
next();
});

View File

@@ -38,5 +38,24 @@ export const useCategoriesStore = defineStore('categories', {
this.isLoading = false; this.isLoading = false;
} }
}, },
async findCategoryById(id, list = []) {
if (! id) return null;
if (list && list.length === 0) {
await this.fetchCategories();
list = this.categories;
}
for (const cat of list) {
if (parseInt(cat.id) === parseInt(id)) return cat;
if (cat.children?.length) {
const found = await this.findCategoryById(id, cat.children);
if (found) return found;
}
}
return null;
}
}, },
}); });

View File

@@ -1,5 +1,7 @@
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import ftch from "@/utils/ftch.js"; import ftch from "@/utils/ftch.js";
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
export const useSearchStore = defineStore('search', { export const useSearchStore = defineStore('search', {
state: () => ({ state: () => ({
@@ -31,6 +33,10 @@ export const useSearchStore = defineStore('search', {
return this.reset(); return this.reset();
} }
useYaMetrikaStore().reachGoal(YA_METRIKA_GOAL.PERFORM_SEARCH, {
keyword: this.search,
});
try { try {
this.isLoading = true; this.isLoading = true;
this.products = await ftch('products', { this.products = await ftch('products', {

View File

@@ -1,9 +1,12 @@
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import {useSettingsStore} from "@/stores/SettingsStore.js"; import {useSettingsStore} from "@/stores/SettingsStore.js";
import sha256 from 'crypto-js/sha256';
import {toRaw} from "vue";
export const useYaMetrikaStore = defineStore('ya_metrika', { export const useYaMetrikaStore = defineStore('ya_metrika', {
state: () => ({ state: () => ({
queue: [], queue: [],
prevPath: null,
}), }),
actions: { actions: {
@@ -16,6 +19,8 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
const fullUrl = `/#${url}`; const fullUrl = `/#${url}`;
params.referer = params.referer ?? this.prevPath;
if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) { if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
console.debug('[ym] Hit ', fullUrl); console.debug('[ym] Hit ', fullUrl);
console.debug('[ym] ID ', window.YA_METRIKA_ID); console.debug('[ym] ID ', window.YA_METRIKA_ID);
@@ -33,6 +38,49 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
} }
}, },
reachGoal(target, params = {}) {
const settings = useSettingsStore();
if (!settings.ya_metrika_enabled) {
console.debug('[ym] Yandex Metrika disabled in settings.');
return;
}
if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
console.debug('[ym] reachGoal ', target, ' params: ', params);
window.ym(window.YA_METRIKA_ID, 'reachGoal', target, params);
} else {
console.debug('[ym] Yandex Metrika is not initialized. Pushed to queue.');
this.queue.push({
event: 'reachGoal',
payload: {
target,
params
},
});
}
},
initUserParams() {
if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
let tgID = null;
if (window?.Telegram?.WebApp?.initDataUnsafe?.user?.id) {
tgID = sha256(window.Telegram.WebApp.initDataUnsafe.user.id).toString();
}
const userParams = {
tg_id: tgID,
language: window.Telegram?.WebApp?.initDataUnsafe?.user?.language_code || 'unknown',
platform: window.Telegram?.WebApp?.platform || 'unknown',
};
window.ym(window.YA_METRIKA_ID, 'userParams', userParams);
console.debug('[ym] User params initialized: ', userParams);
} else {
console.debug('[ym] Yandex Metrika is not initialized. Could not init user params.');
}
},
processQueue() { processQueue() {
if (this.queue.length === 0) { if (this.queue.length === 0) {
return; return;
@@ -43,8 +91,10 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
while (this.queue.length > 0) { while (this.queue.length > 0) {
const item = this.queue.shift(); const item = this.queue.shift();
if (item.event === 'hit') { if (item.event === 'hit') {
console.debug('[ym] Queue ', item); console.debug('[ym] Queue ', toRaw(item));
window.ym(window.YA_METRIKA_ID, item.event, item.payload.url, item.payload.params); 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 { } else {
console.error('[ym] Unsupported queue event: ', item.event); console.error('[ym] Unsupported queue event: ', item.event);
} }

View File

@@ -23,6 +23,7 @@ export function injectYaMetrika() {
window.YA_METRIKA_ID = getMetrikaId(); window.YA_METRIKA_ID = getMetrikaId();
console.debug('[Init] Detected Yandex.Metrika ID:', window.YA_METRIKA_ID); console.debug('[Init] Detected Yandex.Metrika ID:', window.YA_METRIKA_ID);
const yaMetrika = useYaMetrikaStore(); const yaMetrika = useYaMetrikaStore();
yaMetrika.initUserParams();
yaMetrika.processQueue(); yaMetrika.processQueue();
} }
} }

View File

@@ -139,9 +139,13 @@ import OptionRadio from "@/components/ProductOptions/Cart/OptionRadio.vue";
import OptionCheckbox from "@/components/ProductOptions/Cart/OptionCheckbox.vue"; import OptionCheckbox from "@/components/ProductOptions/Cart/OptionCheckbox.vue";
import OptionText from "@/components/ProductOptions/Cart/OptionText.vue"; import OptionText from "@/components/ProductOptions/Cart/OptionText.vue";
import {computed, onMounted} from "vue"; import {computed, onMounted} from "vue";
import {useRouter} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import {useSettingsStore} from "@/stores/SettingsStore.js"; import {useSettingsStore} from "@/stores/SettingsStore.js";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
const route = useRoute();
const yaMetrika = useYaMetrikaStore();
const cart = useCartStore(); const cart = useCartStore();
const router = useRouter(); const router = useRouter();
const settings = useSettingsStore(); const settings = useSettingsStore();
@@ -169,11 +173,15 @@ function goToCheckout() {
onMounted(async () => { onMounted(async () => {
window.document.title = 'Корзина покупок'; window.document.title = 'Корзина покупок';
yaMetrika.pushHit(route.path, {
title: 'Корзина покупок',
});
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_CART);
}); });
</script> </script>
<style scoped> <style scoped>
.btn-checkout { .btn-checkout {
bottom: calc(var(--spacing, 0px) * 22px + var(--tg-safe-area-inset-bottom, 0px)) bottom: calc(var(--spacing, 0px) * 22 + var(--tg-safe-area-inset-bottom, 0px))
} }
</style> </style>

View File

@@ -58,9 +58,11 @@ import {router} from "@/router.js";
import {useCategoriesStore} from "@/stores/CategoriesStore.js"; import {useCategoriesStore} from "@/stores/CategoriesStore.js";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import CategoryItem from "@/components/CategoriesList/CategoryItem.vue"; import CategoryItem from "@/components/CategoriesList/CategoryItem.vue";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
const route = useRoute(); const route = useRoute();
const categoriesStore = useCategoriesStore(); const categoriesStore = useCategoriesStore();
const yaMetrika = useYaMetrikaStore();
const parentId = computed(() => route.params.id ? Number(route.params.id) : null); const parentId = computed(() => route.params.id ? Number(route.params.id) : null);
@@ -106,6 +108,9 @@ function showProductsInParentCategory() {
onMounted(async () => { onMounted(async () => {
window.document.title = 'Каталог'; window.document.title = 'Каталог';
yaMetrika.pushHit(route.path, {
title: 'Каталог',
});
await categoriesStore.fetchCategories(); await categoriesStore.fetchCategories();
}); });
</script> </script>

View File

@@ -69,11 +69,15 @@
import {useCheckoutStore} from "@/stores/CheckoutStore.js"; import {useCheckoutStore} from "@/stores/CheckoutStore.js";
import TgInput from "@/components/Form/TgInput.vue"; import TgInput from "@/components/Form/TgInput.vue";
import TgTextarea from "@/components/Form/TgTextarea.vue"; import TgTextarea from "@/components/Form/TgTextarea.vue";
import {useRouter} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import {computed, onMounted, ref} from "vue"; import {computed, onMounted, ref} from "vue";
import {IMaskComponent} from "vue-imask"; import {IMaskComponent} from "vue-imask";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
const checkout = useCheckoutStore(); const checkout = useCheckoutStore();
const yaMetrika = useYaMetrikaStore();
const route = useRoute();
const router = useRouter(); const router = useRouter();
const error = ref(null); const error = ref(null);
@@ -84,7 +88,15 @@ const btnText = computed(() => {
async function onCreateBtnClick() { async function onCreateBtnClick() {
try { try {
error.value = null; error.value = null;
yaMetrika.reachGoal(YA_METRIKA_GOAL.CREATE_ORDER, {
price: checkout.order?.final_total_numeric,
currency: checkout.order?.currency,
});
await checkout.makeOrder(); 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'}); router.push({name: 'order_created'});
} catch { } catch {
error.value = 'Невозможно создать заказ.'; error.value = 'Невозможно создать заказ.';
@@ -93,5 +105,9 @@ async function onCreateBtnClick() {
onMounted(async () => { onMounted(async () => {
window.document.title = 'Оформление заказа'; window.document.title = 'Оформление заказа';
yaMetrika.pushHit(route.path, {
title: 'Оформление заказа',
});
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_CHECKOUT);
}); });
</script> </script>

View File

@@ -49,8 +49,10 @@ import {nextTick, onMounted} from "vue";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js"; import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import ProductPrice from "@/components/ProductFilters/Components/ProductPrice.vue"; import ProductPrice from "@/components/ProductFilters/Components/ProductPrice.vue";
import ForMainPage from "@/components/ProductFilters/Components/ForMainPage.vue"; import ForMainPage from "@/components/ProductFilters/Components/ForMainPage.vue";
import {useRouter} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import ProductCategory from "@/components/ProductFilters/Components/ProductCategory/ProductCategory.vue"; import ProductCategory from "@/components/ProductFilters/Components/ProductCategory/ProductCategory.vue";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
defineOptions({ defineOptions({
name: 'Filters' name: 'Filters'
@@ -63,6 +65,8 @@ const componentMap = {
}; };
const router = useRouter(); const router = useRouter();
const yaMetrika = useYaMetrikaStore();
const route = useRoute();
const emit = defineEmits(['close', 'apply']); const emit = defineEmits(['close', 'apply']);
const filtersStore = useProductFiltersStore(); const filtersStore = useProductFiltersStore();
@@ -72,6 +76,7 @@ const applyFilters = async () => {
filtersStore.applied = JSON.parse(JSON.stringify(filtersStore.draft)); filtersStore.applied = JSON.parse(JSON.stringify(filtersStore.draft));
console.debug('Filters: apply filters. Hash for router: ', filtersStore.paramsHashForRouter); console.debug('Filters: apply filters. Hash for router: ', filtersStore.paramsHashForRouter);
haptic.impactOccurred('soft'); haptic.impactOccurred('soft');
yaMetrika.reachGoal(YA_METRIKA_GOAL.FILTERS_APPLY);
await nextTick(); await nextTick();
router.back(); router.back();
} }
@@ -80,6 +85,7 @@ const resetFilters = async () => {
filtersStore.applied = filtersStore.default; filtersStore.applied = filtersStore.default;
console.debug('Filters: reset filters. Hash for router: ', filtersStore.paramsHashForRouter); console.debug('Filters: reset filters. Hash for router: ', filtersStore.paramsHashForRouter);
haptic.notificationOccurred('success'); haptic.notificationOccurred('success');
yaMetrika.reachGoal(YA_METRIKA_GOAL.FILTERS_RESET);
await nextTick(); await nextTick();
window.scrollTo(0, 0); window.scrollTo(0, 0);
router.back(); router.back();
@@ -89,6 +95,9 @@ onMounted(async () => {
console.debug('Filters: OnMounted'); console.debug('Filters: OnMounted');
window.document.title = 'Фильтры'; window.document.title = 'Фильтры';
yaMetrika.pushHit(route.path, {title: 'Фильтры'});
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_FILTERS);
if (filtersStore.applied?.rules) { if (filtersStore.applied?.rules) {
console.debug('Filters: Found applied filters.'); console.debug('Filters: Found applied filters.');
filtersStore.draft = JSON.parse(JSON.stringify(filtersStore.applied)); filtersStore.draft = JSON.parse(JSON.stringify(filtersStore.applied));

View File

@@ -38,6 +38,7 @@ import ftch from "@/utils/ftch.js";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js"; import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import Banner from "@/components/Banner.vue"; import Banner from "@/components/Banner.vue";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js"; import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
defineOptions({ defineOptions({
name: 'Home' name: 'Home'
@@ -99,7 +100,10 @@ async function onLoadMore() {
} }
onActivated(() => { onActivated(() => {
yaMetrika.pushHit('/'); yaMetrika.pushHit('/', {
title: 'Главная страница',
});
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_HOME);
}); });
onMounted(async () => { onMounted(async () => {

View File

@@ -39,10 +39,17 @@
<script setup> <script setup>
import {useCheckoutStore} from "@/stores/CheckoutStore.js"; import {useCheckoutStore} from "@/stores/CheckoutStore.js";
import {onMounted} from "vue"; import {onMounted} from "vue";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
import {useRoute} from "vue-router";
const checkout = useCheckoutStore(); const checkout = useCheckoutStore();
const yaMetrika = useYaMetrikaStore();
const route = useRoute();
onMounted(async () => { onMounted(() => {
window.document.title = 'Заказ оформлен'; window.document.title = 'Заказ оформлен';
yaMetrika.pushHit(route.path, {
title: 'Заказ оформлен',
});
}); });
</script> </script>

View File

@@ -106,7 +106,7 @@
class="btn btn-primary btn-lg w-full" class="btn btn-primary btn-lg w-full"
:class="isInCart ? 'btn-success' : 'btn-primary'" :class="isInCart ? 'btn-success' : 'btn-primary'"
:disabled="cart.isLoading || canAddToCart === false" :disabled="cart.isLoading || canAddToCart === false"
@click="actionBtnClick" @click="onCartBtnClick"
> >
<span v-if="cart.isLoading" class="loading loading-spinner loading-sm"></span> <span v-if="cart.isLoading" class="loading loading-spinner loading-sm"></span>
{{ btnText }} {{ btnText }}
@@ -156,7 +156,7 @@
</template> </template>
<script setup> <script setup>
import {computed, nextTick, onMounted, onUnmounted, ref} from "vue"; import {computed, 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";
@@ -168,6 +168,7 @@ import LoadingFullScreen from "@/components/LoadingFullScreen.vue";
import ProductNotFound from "@/components/ProductNotFound.vue"; import ProductNotFound from "@/components/ProductNotFound.vue";
import {useSettingsStore} from "@/stores/SettingsStore.js"; import {useSettingsStore} from "@/stores/SettingsStore.js";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js"; import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
const route = useRoute(); const route = useRoute();
const productId = computed(() => route.params.id); const productId = computed(() => route.params.id);
@@ -211,7 +212,7 @@ function closeFullScreen() {
document.body.style.overflow = ''; document.body.style.overflow = '';
} }
async function actionBtnClick() { async function onCartBtnClick() {
try { try {
error.value = ''; error.value = '';
@@ -219,6 +220,10 @@ async function actionBtnClick() {
await cart.addProduct(productId.value, product.value.name, product.value.price, quantity.value, product.value.options); await cart.addProduct(productId.value, product.value.name, product.value.price, quantity.value, product.value.options);
isInCart.value = true; isInCart.value = true;
window.Telegram.WebApp.HapticFeedback.notificationOccurred('success'); window.Telegram.WebApp.HapticFeedback.notificationOccurred('success');
yaMetrika.reachGoal(YA_METRIKA_GOAL.ADD_TO_CART, {
price: product.value.final_price_numeric,
currency: product.value.currency,
});
} else { } else {
window.Telegram.WebApp.HapticFeedback.selectionChanged(); window.Telegram.WebApp.HapticFeedback.selectionChanged();
await router.push({'name': 'cart'}); await router.push({'name': 'cart'});
@@ -275,6 +280,11 @@ onMounted(async () => {
'Цена': data.price, 'Цена': data.price,
}, },
}); });
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_PRODUCT, {
price: data.final_price_numeric,
currency: data.currency,
});
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally { } finally {

View File

@@ -5,6 +5,7 @@
:hasMore="productsStore.products.meta.hasMore" :hasMore="productsStore.products.meta.hasMore"
:isLoading="productsStore.isLoading" :isLoading="productsStore.isLoading"
:isLoadingMore="productsStore.isLoadingMore" :isLoadingMore="productsStore.isLoadingMore"
:categoryName="category?.name"
@loadMore="productsStore.loadMore" @loadMore="productsStore.loadMore"
/> />
</div> </div>
@@ -12,9 +13,11 @@
<script setup> <script setup>
import ProductsList from "@/components/ProductsList.vue"; import ProductsList from "@/components/ProductsList.vue";
import {onMounted} from "vue"; import {onMounted, ref} from "vue";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import {useProductsStore} from "@/stores/ProductsStore.js"; import {useProductsStore} from "@/stores/ProductsStore.js";
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
defineOptions({ defineOptions({
name: 'Products' name: 'Products'
@@ -22,12 +25,22 @@ defineOptions({
const route = useRoute(); const route = useRoute();
const productsStore = useProductsStore(); const productsStore = useProductsStore();
const categoriesStore = useCategoriesStore();
const yaMetrika = useYaMetrikaStore();
const categoryId = route.params.category_id ?? null; const categoryId = route.params.category_id ?? null;
const category = ref(null);
onMounted(async () => { onMounted(async () => {
console.debug("Category Products Mounted"); console.debug("[Category] Category Products Mounted");
console.debug("Load products for category: ", categoryId); console.debug("[Category] Load products for category: ", categoryId);
category.value = await categoriesStore.findCategoryById(categoryId);
console.debug("[Category] Category Name: ", category.value?.name);
window.document.title = `${category.value?.name ?? 'Неизвестная категория'}`;
yaMetrika.pushHit(route.path, {
title: `${category.value?.name ?? 'Неизвестная категория'}`,
});
if (productsStore.filtersFullUrl === route.fullPath) { if (productsStore.filtersFullUrl === route.fullPath) {
await productsStore.loadProducts(productsStore.filters ?? { await productsStore.loadProducts(productsStore.filters ?? {

View File

@@ -74,12 +74,21 @@
import {useSearchStore} from "@/stores/SearchStore.js"; import {useSearchStore} from "@/stores/SearchStore.js";
import {useDebounceFn} from "@vueuse/core"; import {useDebounceFn} from "@vueuse/core";
import {onMounted, ref} from "vue"; import {onMounted, ref} from "vue";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
import {useRoute} from "vue-router";
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
const route = useRoute();
const yaMetrika = useYaMetrikaStore();
const searchStore = useSearchStore(); const searchStore = useSearchStore();
const searchInput = ref(null); const searchInput = ref(null);
const debouncedSearch = useDebounceFn(() => searchStore.performSearch(), 500); const debouncedSearch = useDebounceFn(() => searchStore.performSearch(), 500);
onMounted(async () => { onMounted(async () => {
window.document.title = 'Поиск'; window.document.title = 'Поиск';
yaMetrika.pushHit(route.path, {
title: 'Поиск',
});
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_SEARCH);
}); });
</script> </script>