feat(ya_metrika): WIP yandex metrika
This commit is contained in:
@@ -531,7 +531,7 @@ HTML,
|
|||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'options' => $this->getBannersList(),
|
'options' => $this->getBannersList(),
|
||||||
'help' => <<<HTML
|
'help' => <<<HTML
|
||||||
<a href="{$ocBannersLink}" target="_blank">Стандартный OpenCart баннер</a> отображаемый на главной странице магазина. Рекомендуемая максимальная высота изображения для баннера - 200 пикселей.
|
<a href="{$ocBannersLink}" target="_blank">Стандартный OpenCart баннер</a> отображаемый на главной странице магазина. Рекомендуемое соотношение сторон для изображений - 2.5:1 (например 500×200).
|
||||||
HTML,
|
HTML,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ settings.load()
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Set theme attributes');
|
console.debug('[Init] Set theme attributes');
|
||||||
document.documentElement.setAttribute('data-theme', settings.theme[window.Telegram.WebApp.colorScheme]);
|
document.documentElement.setAttribute('data-theme', settings.theme[window.Telegram.WebApp.colorScheme]);
|
||||||
if (settings.night_auto) {
|
if (settings.night_auto) {
|
||||||
window.Telegram.WebApp.onEvent('themeChanged', function () {
|
window.Telegram.WebApp.onEvent('themeChanged', function () {
|
||||||
@@ -55,7 +55,7 @@ settings.load()
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Load front page categories and products.');
|
console.debug('[Init] Load front page categories and products.');
|
||||||
const categoriesStore = useCategoriesStore();
|
const categoriesStore = useCategoriesStore();
|
||||||
categoriesStore.fetchTopCategories();
|
categoriesStore.fetchTopCategories();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Checkout from "@/views/Checkout.vue";
|
|||||||
import OrderCreated from "@/views/OrderCreated.vue";
|
import OrderCreated from "@/views/OrderCreated.vue";
|
||||||
import Search from "@/views/Search.vue";
|
import Search from "@/views/Search.vue";
|
||||||
import Filters from "@/views/Filters.vue";
|
import Filters from "@/views/Filters.vue";
|
||||||
|
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
|||||||
56
spa/src/stores/yaMetrikaStore.js
Normal file
56
spa/src/stores/yaMetrikaStore.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import {defineStore} from "pinia";
|
||||||
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
|
|
||||||
|
export const useYaMetrikaStore = defineStore('ya_metrika', {
|
||||||
|
state: () => ({
|
||||||
|
queue: [],
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
pushHit(url, params = {}) {
|
||||||
|
const settings = useSettingsStore();
|
||||||
|
if (!settings.ya_metrika_enabled) {
|
||||||
|
console.debug('[ym] Yandex Metrika disabled in settings.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullUrl = `/#${url}`;
|
||||||
|
|
||||||
|
if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
|
||||||
|
console.debug('[ym] Hit ', fullUrl);
|
||||||
|
console.debug('[ym] ID ', window.YA_METRIKA_ID);
|
||||||
|
console.debug('[ym] params ', params);
|
||||||
|
window.ym(window.YA_METRIKA_ID, 'hit', fullUrl, params);
|
||||||
|
} else {
|
||||||
|
console.debug('[ym] Yandex Metrika is not initialized. Pushed to queue.');
|
||||||
|
this.queue.push({
|
||||||
|
event: 'hit',
|
||||||
|
payload: {
|
||||||
|
fullUrl,
|
||||||
|
params,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
processQueue() {
|
||||||
|
if (this.queue.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug('[ym] Start processing queue. Size: ', this.queue.length);
|
||||||
|
|
||||||
|
while (this.queue.length > 0) {
|
||||||
|
const item = this.queue.shift();
|
||||||
|
if (item.event === 'hit') {
|
||||||
|
console.debug('[ym] Queue ', item);
|
||||||
|
window.ym(window.YA_METRIKA_ID, item.event, item.payload.url, item.payload.params);
|
||||||
|
} else {
|
||||||
|
console.error('[ym] Unsupported queue event: ', item.event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug('[ym] Queue processing complete. Size: ', this.queue.length);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,7 +1,28 @@
|
|||||||
|
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||||
|
|
||||||
|
function getMetrikaId() {
|
||||||
|
// Пробуем найти все элементы <script> с mc.yandex.ru
|
||||||
|
const scripts = Array.from(document.scripts);
|
||||||
|
for (const s of scripts) {
|
||||||
|
if (s.src.includes('mc.yandex.ru/metrika/tag.js')) {
|
||||||
|
const match = s.src.match(/id=(\d+)/);
|
||||||
|
if (match) return match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function injectYaMetrika() {
|
export function injectYaMetrika() {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = '/index.php?route=extension/tgshop/handle/ya_metrika';
|
script.src = '/index.php?route=extension/tgshop/handle/ya_metrika';
|
||||||
script.async = true;
|
// script.async = true;
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
console.log('Yandex Metrika injected to the page.');
|
console.debug('[Init] Yandex Metrika injected to the page.');
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
|
window.YA_METRIKA_ID = getMetrikaId();
|
||||||
|
console.debug('[Init] Detected Yandex.Metrika ID:', window.YA_METRIKA_ID);
|
||||||
|
const yaMetrika = useYaMetrikaStore();
|
||||||
|
yaMetrika.processQueue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ import Quantity from "@/components/Quantity.vue";
|
|||||||
import OptionRadio from "@/components/ProductOptions/Cart/OptionRadio.vue";
|
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} from "vue";
|
import {computed, onMounted} from "vue";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
|
|
||||||
@@ -166,6 +166,10 @@ function removeItem(cartId) {
|
|||||||
function goToCheckout() {
|
function goToCheckout() {
|
||||||
router.push({name: 'checkout'});
|
router.push({name: 'checkout'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
window.document.title = 'Корзина покупок';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ function showProductsInParentCategory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
window.document.title = 'Каталог';
|
||||||
await categoriesStore.fetchCategories();
|
await categoriesStore.fetchCategories();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ 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 {useRouter} from "vue-router";
|
||||||
import {computed, ref} from "vue";
|
import {computed, onMounted, ref} from "vue";
|
||||||
import {IMaskComponent} from "vue-imask";
|
import {IMaskComponent} from "vue-imask";
|
||||||
|
|
||||||
const checkout = useCheckoutStore();
|
const checkout = useCheckoutStore();
|
||||||
@@ -90,4 +90,8 @@ async function onCreateBtnClick() {
|
|||||||
error.value = 'Невозможно создать заказ.';
|
error.value = 'Невозможно создать заказ.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
window.document.title = 'Оформление заказа';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ const resetFilters = async () => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.debug('Filters: OnMounted');
|
console.debug('Filters: OnMounted');
|
||||||
|
window.document.title = 'Фильтры';
|
||||||
|
|
||||||
if (filtersStore.applied?.rules) {
|
if (filtersStore.applied?.rules) {
|
||||||
console.debug('Filters: Found applied filters.');
|
console.debug('Filters: Found applied filters.');
|
||||||
|
|||||||
@@ -31,12 +31,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import ProductsList from "@/components/ProductsList.vue";
|
import ProductsList from "@/components/ProductsList.vue";
|
||||||
import CategoriesInline from "../components/CategoriesInline.vue";
|
import CategoriesInline from "../components/CategoriesInline.vue";
|
||||||
import {onMounted, ref, toRaw} from "vue";
|
import {nextTick, onActivated, onMounted, ref, toRaw} from "vue";
|
||||||
import IconFunnel from "@/components/Icons/IconFunnel.vue";
|
import IconFunnel from "@/components/Icons/IconFunnel.vue";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import ftch from "@/utils/ftch.js";
|
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";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Home'
|
name: 'Home'
|
||||||
@@ -44,6 +45,7 @@ defineOptions({
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const filtersStore = useProductFiltersStore();
|
const filtersStore = useProductFiltersStore();
|
||||||
|
const yaMetrika = useYaMetrikaStore();
|
||||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
const haptic = window.Telegram.WebApp.HapticFeedback;
|
||||||
|
|
||||||
const products = ref([]);
|
const products = ref([]);
|
||||||
@@ -96,9 +98,14 @@ async function onLoadMore() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
yaMetrika.pushHit('/');
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.debug("Home: Home Mounted");
|
window.document.title = 'Главная страница';
|
||||||
console.debug("Home: Scroll top");
|
console.debug("[Home] Home Mounted");
|
||||||
|
console.debug("[Home] Scroll top");
|
||||||
await fetchProducts();
|
await fetchProducts();
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,11 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useCheckoutStore} from "@/stores/CheckoutStore.js";
|
import {useCheckoutStore} from "@/stores/CheckoutStore.js";
|
||||||
|
import {onMounted} from "vue";
|
||||||
|
|
||||||
const checkout = useCheckoutStore();
|
const checkout = useCheckoutStore();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
window.document.title = 'Заказ оформлен';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -130,8 +130,10 @@
|
|||||||
>
|
>
|
||||||
<template v-if="product.share">
|
<template v-if="product.share">
|
||||||
Открыть товар
|
Открыть товар
|
||||||
<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"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
stroke="currentColor" class="size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -154,7 +156,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";
|
||||||
@@ -165,6 +167,7 @@ import FullScreenImageViewer from "@/components/FullScreenImageViewer.vue";
|
|||||||
import LoadingFullScreen from "@/components/LoadingFullScreen.vue";
|
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";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const productId = computed(() => route.params.id);
|
const productId = computed(() => route.params.id);
|
||||||
@@ -179,6 +182,7 @@ const isFullScreen = ref(false);
|
|||||||
const initialFullScreenIndex = ref(0);
|
const initialFullScreenIndex = ref(0);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
|
const yaMetrika = useYaMetrikaStore();
|
||||||
|
|
||||||
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) {
|
||||||
@@ -261,6 +265,16 @@ 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;
|
||||||
|
window.document.title = data.name;
|
||||||
|
|
||||||
|
yaMetrika.pushHit(route.path, {
|
||||||
|
title: data.name,
|
||||||
|
params: {
|
||||||
|
'Название товара': data.name,
|
||||||
|
'ИД товара': data.product_id,
|
||||||
|
'Цена': data.price,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -73,9 +73,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useSearchStore} from "@/stores/SearchStore.js";
|
import {useSearchStore} from "@/stores/SearchStore.js";
|
||||||
import {useDebounceFn} from "@vueuse/core";
|
import {useDebounceFn} from "@vueuse/core";
|
||||||
import {ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
|
|
||||||
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 () => {
|
||||||
|
window.document.title = 'Поиск';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user