feat: add haptic feedback toggle setting
- Add haptic_enabled field to AppDTO with default value true - Update SettingsSerializerService to deserialize haptic_enabled - Add haptic_enabled setting in admin panel (GeneralView) with toggle - Update admin settings store to include haptic_enabled default value - Update SPA SettingsStore to load haptic_enabled from API - Refactor useHapticFeedback composable to return safe wrapper object - Replace all direct window.Telegram.WebApp.HapticFeedback usage with composable - Update useHapticScroll to use useHapticFeedback composable - Add getHapticFeedback helper function in CheckoutStore for store usage - Add haptic_enabled default value to app.php configuration - All haptic feedback methods now check settings internally - Remove redundant if checks from components (handled in composable)
This commit is contained in:
@@ -20,6 +20,7 @@ export const useSettingsStore = defineStore('settings', {
|
|||||||
privacy_policy_link: null,
|
privacy_policy_link: null,
|
||||||
image_aspect_ratio: '1:1',
|
image_aspect_ratio: '1:1',
|
||||||
image_crop_algorithm: 'cover',
|
image_crop_algorithm: 'cover',
|
||||||
|
haptic_enabled: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
telegram: {
|
telegram: {
|
||||||
|
|||||||
@@ -62,6 +62,10 @@
|
|||||||
<li><strong>Resize</strong> - изменяет размер изображения с сохранением пропорций (без обрезки)</li>
|
<li><strong>Resize</strong> - изменяет размер изображения с сохранением пропорций (без обрезки)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</ItemSelect>
|
</ItemSelect>
|
||||||
|
|
||||||
|
<ItemBool label="Тактильная обратная связь (Haptic Feedback)" v-model="settings.items.app.haptic_enabled">
|
||||||
|
Включить виброотклик при взаимодействии с элементами интерфейса. Если выключено, тактильная обратная связь не будет использоваться.
|
||||||
|
</ItemBool>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import AppDebugMessage from "@/components/AppDebugMessage.vue";
|
|||||||
import PrivacyPolicy from "@/components/PrivacyPolicy.vue";
|
import PrivacyPolicy from "@/components/PrivacyPolicy.vue";
|
||||||
import BrowserNotSupported from "@/BrowserNotSupported.vue";
|
import BrowserNotSupported from "@/BrowserNotSupported.vue";
|
||||||
import {useSwipeBack} from "@/composables/useSwipeBack.js";
|
import {useSwipeBack} from "@/composables/useSwipeBack.js";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -69,7 +70,7 @@ const settings = useSettingsStore();
|
|||||||
const filtersStore = useProductFiltersStore();
|
const filtersStore = useProductFiltersStore();
|
||||||
const keyboardStore = useKeyboardStore();
|
const keyboardStore = useKeyboardStore();
|
||||||
const backButton = window.Telegram.WebApp.BackButton;
|
const backButton = window.Telegram.WebApp.BackButton;
|
||||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
const haptic = useHapticFeedback();
|
||||||
const swiperBack = useSwipeBack();
|
const swiperBack = useSwipeBack();
|
||||||
|
|
||||||
const routesToHideAppDock = [
|
const routesToHideAppDock = [
|
||||||
|
|||||||
@@ -18,10 +18,12 @@
|
|||||||
import {computed, onMounted} from "vue";
|
import {computed, onMounted} from "vue";
|
||||||
import {useCartStore} from "@/stores/CartStore.js";
|
import {useCartStore} from "@/stores/CartStore.js";
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const cart = useCartStore();
|
const cart = useCartStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
const isCartBtnShow = computed(() => {
|
const isCartBtnShow = computed(() => {
|
||||||
return route.name === 'product.show';
|
return route.name === 'product.show';
|
||||||
@@ -29,7 +31,7 @@ const isCartBtnShow = computed(() => {
|
|||||||
|
|
||||||
|
|
||||||
function openCart() {
|
function openCart() {
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
router.push({name: 'cart'});
|
router.push({name: 'cart'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,12 +84,13 @@ import {useRoute} from "vue-router";
|
|||||||
import {useCartStore} from "@/stores/CartStore.js";
|
import {useCartStore} from "@/stores/CartStore.js";
|
||||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
import {useTgData} from "@/composables/useTgData.js";
|
import {useTgData} from "@/composables/useTgData.js";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const cart = useCartStore();
|
const cart = useCartStore();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const tgData = useTgData();
|
const tgData = useTgData();
|
||||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
function onDockItemClick(event) {
|
function onDockItemClick(event) {
|
||||||
haptic.selectionChanged();
|
haptic.selectionChanged();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
import {Navigation, Pagination, Zoom} from "swiper/modules";
|
import {Navigation, Pagination, Zoom} from "swiper/modules";
|
||||||
import {Swiper, SwiperSlide} from "swiper/vue";
|
import {Swiper, SwiperSlide} from "swiper/vue";
|
||||||
import {onMounted} from "vue";
|
import {onMounted} from "vue";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
images: {
|
images: {
|
||||||
@@ -49,11 +50,12 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const emits = defineEmits(['close']);
|
const emits = defineEmits(['close']);
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
let canVibrate = true;
|
let canVibrate = true;
|
||||||
|
|
||||||
function vibrate() {
|
function vibrate() {
|
||||||
if (!canVibrate) return;
|
if (!canVibrate) return;
|
||||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('soft');
|
haptic.impactOccurred('soft');
|
||||||
canVibrate = false;
|
canVibrate = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
canVibrate = true;
|
canVibrate = true;
|
||||||
@@ -61,7 +63,7 @@ function vibrate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('medium');
|
haptic.impactOccurred('medium');
|
||||||
emits('close');
|
emits('close');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -30,8 +30,10 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
block: {
|
block: {
|
||||||
@@ -41,6 +43,6 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function onCategoryClick() {
|
function onCategoryClick() {
|
||||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('soft');
|
haptic.impactOccurred('soft');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -66,9 +66,10 @@ import IconFunnel from "@/components/Icons/IconFunnel.vue";
|
|||||||
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import PriceTitle from "@/components/ProductItem/PriceTitle.vue";
|
import PriceTitle from "@/components/ProductItem/PriceTitle.vue";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
const haptic = useHapticFeedback();
|
||||||
const yaMetrika = useYaMetrikaStore();
|
const yaMetrika = useYaMetrikaStore();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const filtersStore = useProductFiltersStore();
|
const filtersStore = useProductFiltersStore();
|
||||||
@@ -104,7 +105,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function productClick(product, index) {
|
function productClick(product, index) {
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
yaMetrika.dataLayerPush({
|
yaMetrika.dataLayerPush({
|
||||||
"ecommerce": {
|
"ecommerce": {
|
||||||
"currencyCode": settings.currency_code,
|
"currencyCode": settings.currency_code,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const model = defineModel();
|
const model = defineModel();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -22,6 +23,8 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
const btnClassList = computed(() => {
|
const btnClassList = computed(() => {
|
||||||
let classList = ['btn'];
|
let classList = ['btn'];
|
||||||
if (props.size) {
|
if (props.size) {
|
||||||
@@ -33,7 +36,7 @@ const btnClassList = computed(() => {
|
|||||||
function inc() {
|
function inc() {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
|
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
|
|
||||||
if (props.max && model.value + 1 > props.max) {
|
if (props.max && model.value + 1 > props.max) {
|
||||||
model.value = props.max;
|
model.value = props.max;
|
||||||
@@ -46,7 +49,7 @@ function inc() {
|
|||||||
function dec() {
|
function dec() {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
|
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
|
|
||||||
if (model.value - 1 >= 1) {
|
if (model.value - 1 >= 1) {
|
||||||
model.value--;
|
model.value--;
|
||||||
|
|||||||
@@ -26,13 +26,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {useSearchStore} from "@/stores/SearchStore.js";
|
import {useSearchStore} from "@/stores/SearchStore.js";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
function showSearchPage() {
|
function showSearchPage() {
|
||||||
router.push({name: 'search'});
|
router.push({name: 'search'});
|
||||||
useSearchStore().reset();
|
useSearchStore().reset();
|
||||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('medium');
|
haptic.impactOccurred('medium');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,39 @@
|
|||||||
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable для безопасной работы с HapticFeedback
|
||||||
|
* Проверяет настройку haptic_enabled перед использованием
|
||||||
|
* @returns {object} Объект с методами haptic feedback, которые безопасно вызываются
|
||||||
|
*/
|
||||||
export function useHapticFeedback() {
|
export function useHapticFeedback() {
|
||||||
return window.Telegram?.WebApp?.HapticFeedback;
|
const settings = useSettingsStore();
|
||||||
|
const haptic = window.Telegram?.WebApp?.HapticFeedback;
|
||||||
|
|
||||||
|
// Возвращаем обёртку с методами, которые проверяют настройку при каждом вызове
|
||||||
|
return {
|
||||||
|
impactOccurred: (style) => {
|
||||||
|
// Проверяем настройку при каждом вызове (реактивно)
|
||||||
|
const isHapticEnabled = settings.haptic_enabled !== false;
|
||||||
|
if (!haptic || !isHapticEnabled || !haptic.impactOccurred) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
haptic.impactOccurred(style);
|
||||||
|
},
|
||||||
|
selectionChanged: () => {
|
||||||
|
// Проверяем настройку при каждом вызове (реактивно)
|
||||||
|
const isHapticEnabled = settings.haptic_enabled !== false;
|
||||||
|
if (!haptic || !isHapticEnabled || !haptic.selectionChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
haptic.selectionChanged();
|
||||||
|
},
|
||||||
|
notificationOccurred: (type) => {
|
||||||
|
// Проверяем настройку при каждом вызове (реактивно)
|
||||||
|
const isHapticEnabled = settings.haptic_enabled !== false;
|
||||||
|
if (!haptic || !isHapticEnabled || !haptic.notificationOccurred) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
haptic.notificationOccurred(type);
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {ref} from 'vue';
|
import {ref} from 'vue';
|
||||||
|
import {useHapticFeedback} from './useHapticFeedback.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable для Haptic Feedback по свайпу
|
* Composable для Haptic Feedback по свайпу
|
||||||
@@ -13,6 +14,7 @@ export function useHapticScroll(
|
|||||||
feedback = 'soft'
|
feedback = 'soft'
|
||||||
) {
|
) {
|
||||||
const lastTranslate = ref(0);
|
const lastTranslate = ref(0);
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
return function (
|
return function (
|
||||||
swiper
|
swiper
|
||||||
@@ -20,14 +22,11 @@ export function useHapticScroll(
|
|||||||
const current = Math.abs(swiper.translate);
|
const current = Math.abs(swiper.translate);
|
||||||
const delta = Math.abs(current - lastTranslate.value);
|
const delta = Math.abs(current - lastTranslate.value);
|
||||||
|
|
||||||
if (delta > threshold) {
|
if (delta > threshold && haptic) {
|
||||||
const haptic = window.Telegram?.WebApp?.HapticFeedback;
|
if (type === 'impactOccurred' && haptic.impactOccurred) {
|
||||||
if (haptic) {
|
haptic.impactOccurred(feedback);
|
||||||
if (type === 'impactOccurred' && haptic.impactOccurred) {
|
} else if (type === 'selectionChanged' && haptic.selectionChanged) {
|
||||||
haptic.impactOccurred(feedback);
|
haptic.selectionChanged();
|
||||||
} else if (type === 'selectionChanged' && haptic.selectionChanged) {
|
|
||||||
haptic.selectionChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lastTranslate.value = current;
|
lastTranslate.value = current;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,40 @@ import {usePulseStore} from "@/stores/Pulse.js";
|
|||||||
import {TC_PULSE_EVENTS} from "@/constants/tPulseEvents.js";
|
import {TC_PULSE_EVENTS} from "@/constants/tPulseEvents.js";
|
||||||
import {nextTick} from "vue";
|
import {nextTick} from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper функция для получения haptic feedback (можно использовать в store)
|
||||||
|
* @returns {object} Объект с методами haptic feedback
|
||||||
|
*/
|
||||||
|
function getHapticFeedback() {
|
||||||
|
const settings = useSettingsStore();
|
||||||
|
const haptic = window.Telegram?.WebApp?.HapticFeedback;
|
||||||
|
|
||||||
|
// Возвращаем обёртку с методами, которые проверяют настройку при каждом вызове
|
||||||
|
return {
|
||||||
|
impactOccurred: (style) => {
|
||||||
|
const isHapticEnabled = settings.haptic_enabled !== false;
|
||||||
|
if (!haptic || !isHapticEnabled || !haptic.impactOccurred) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
haptic.impactOccurred(style);
|
||||||
|
},
|
||||||
|
selectionChanged: () => {
|
||||||
|
const isHapticEnabled = settings.haptic_enabled !== false;
|
||||||
|
if (!haptic || !isHapticEnabled || !haptic.selectionChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
haptic.selectionChanged();
|
||||||
|
},
|
||||||
|
notificationOccurred: (type) => {
|
||||||
|
const isHapticEnabled = settings.haptic_enabled !== false;
|
||||||
|
if (!haptic || !isHapticEnabled || !haptic.notificationOccurred) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
haptic.notificationOccurred(type);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const useCheckoutStore = defineStore('checkout', {
|
export const useCheckoutStore = defineStore('checkout', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
form: {},
|
form: {},
|
||||||
@@ -95,7 +129,8 @@ export const useCheckoutStore = defineStore('checkout', {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await window.Telegram.WebApp.HapticFeedback.notificationOccurred('success');
|
const haptic = getHapticFeedback();
|
||||||
|
await haptic.notificationOccurred('success');
|
||||||
await useCartStore().getProducts();
|
await useCartStore().getProducts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response?.status === 422) {
|
if (error.response?.status === 422) {
|
||||||
@@ -104,7 +139,8 @@ export const useCheckoutStore = defineStore('checkout', {
|
|||||||
console.error('Server error', error);
|
console.error('Server error', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Telegram.WebApp.HapticFeedback.notificationOccurred('error');
|
const haptic = getHapticFeedback();
|
||||||
|
haptic.notificationOccurred('error');
|
||||||
|
|
||||||
this.errorMessage = 'Возникла ошибка при создании заказа.';
|
this.errorMessage = 'Возникла ошибка при создании заказа.';
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const useSettingsStore = defineStore('settings', {
|
|||||||
feature_vouchers: false,
|
feature_vouchers: false,
|
||||||
show_category_products_button: true,
|
show_category_products_button: true,
|
||||||
currency_code: null,
|
currency_code: null,
|
||||||
|
haptic_enabled: true,
|
||||||
theme: {
|
theme: {
|
||||||
light: 'light', dark: 'dark', variables: {
|
light: 'light', dark: 'dark', variables: {
|
||||||
'--product_list_title_max_lines': 2,
|
'--product_list_title_max_lines': 2,
|
||||||
@@ -53,6 +54,7 @@ export const useSettingsStore = defineStore('settings', {
|
|||||||
this.mainpage_blocks = settings.mainpage_blocks;
|
this.mainpage_blocks = settings.mainpage_blocks;
|
||||||
this.privacy_policy_link = settings.privacy_policy_link;
|
this.privacy_policy_link = settings.privacy_policy_link;
|
||||||
this.image_aspect_ratio = settings.image_aspect_ratio;
|
this.image_aspect_ratio = settings.image_aspect_ratio;
|
||||||
|
this.haptic_enabled = settings.haptic_enabled ?? true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import {onMounted, onUnmounted} from "vue";
|
|||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||||
import {usePulseStore} from "@/stores/Pulse.js";
|
import {usePulseStore} from "@/stores/Pulse.js";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Account'
|
name: 'Account'
|
||||||
@@ -92,6 +93,7 @@ const settings = useSettingsStore();
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const yaMetrika = useYaMetrikaStore();
|
const yaMetrika = useYaMetrikaStore();
|
||||||
const pulse = usePulseStore();
|
const pulse = usePulseStore();
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
const username = computed(() => {
|
const username = computed(() => {
|
||||||
if (tgData?.user?.first_name || tgData?.user?.last_name) {
|
if (tgData?.user?.first_name || tgData?.user?.last_name) {
|
||||||
@@ -136,7 +138,7 @@ function openManagerChat() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
|
|
||||||
// Формируем ссылку для открытия чата с менеджером
|
// Формируем ссылку для открытия чата с менеджером
|
||||||
const managerUsername = String(settings.manager_username).trim();
|
const managerUsername = String(settings.manager_username).trim();
|
||||||
@@ -149,7 +151,7 @@ function openManagerChat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleHomeScreenAdded() {
|
function handleHomeScreenAdded() {
|
||||||
window.Telegram.WebApp.HapticFeedback.notificationOccurred('success');
|
haptic.notificationOccurred('success');
|
||||||
window.Telegram.WebApp.showAlert('Приложение успешно добавлено на главный экран!');
|
window.Telegram.WebApp.showAlert('Приложение успешно добавлено на главный экран!');
|
||||||
isHomeScreenAdded.value = true;
|
isHomeScreenAdded.value = true;
|
||||||
}
|
}
|
||||||
@@ -169,7 +171,7 @@ function checkHomeScreenSupport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addToHomeScreen() {
|
function addToHomeScreen() {
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
|
|
||||||
// Используем Telegram Web App API для добавления на главный экран
|
// Используем Telegram Web App API для добавления на главный экран
|
||||||
if (window.Telegram?.WebApp?.addToHomeScreen) {
|
if (window.Telegram?.WebApp?.addToHomeScreen) {
|
||||||
|
|||||||
@@ -153,12 +153,14 @@ 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";
|
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||||
import BaseViewWrapper from "@/views/BaseViewWrapper.vue";
|
import BaseViewWrapper from "@/views/BaseViewWrapper.vue";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const yaMetrika = useYaMetrikaStore();
|
const yaMetrika = useYaMetrikaStore();
|
||||||
const cart = useCartStore();
|
const cart = useCartStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
// const componentMap = {
|
// const componentMap = {
|
||||||
// radio: OptionRadio,
|
// radio: OptionRadio,
|
||||||
@@ -174,7 +176,7 @@ const lastTotal = computed(() => {
|
|||||||
|
|
||||||
function removeItem(cartItem, cartId, index) {
|
function removeItem(cartItem, cartId, index) {
|
||||||
cart.removeItem(cartItem, cartId, index);
|
cart.removeItem(cartItem, cartId, index);
|
||||||
window.Telegram.WebApp.HapticFeedback.notificationOccurred('error');
|
haptic.notificationOccurred('error');
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToCheckout() {
|
function goToCheckout() {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ 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 {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Filters'
|
name: 'Filters'
|
||||||
@@ -70,7 +71,7 @@ const route = useRoute();
|
|||||||
const emit = defineEmits(['close', 'apply']);
|
const emit = defineEmits(['close', 'apply']);
|
||||||
|
|
||||||
const filtersStore = useProductFiltersStore();
|
const filtersStore = useProductFiltersStore();
|
||||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
const applyFilters = async () => {
|
const applyFilters = async () => {
|
||||||
filtersStore.applied = JSON.parse(JSON.stringify(filtersStore.draft));
|
filtersStore.applied = JSON.parse(JSON.stringify(filtersStore.draft));
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {useSettingsStore} from "@/stores/SettingsStore.js";
|
|||||||
import MainPage from "@/components/MainPage/MainPage.vue";
|
import MainPage from "@/components/MainPage/MainPage.vue";
|
||||||
import {useBlocksStore} from "@/stores/BlocksStore.js";
|
import {useBlocksStore} from "@/stores/BlocksStore.js";
|
||||||
import Navbar from "@/components/Navbar.vue";
|
import Navbar from "@/components/Navbar.vue";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Home'
|
name: 'Home'
|
||||||
@@ -25,7 +26,7 @@ defineOptions({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const filtersStore = useProductFiltersStore();
|
const filtersStore = useProductFiltersStore();
|
||||||
const yaMetrika = useYaMetrikaStore();
|
const yaMetrika = useYaMetrikaStore();
|
||||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
const haptic = useHapticFeedback();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const blocks = useBlocksStore();
|
const blocks = useBlocksStore();
|
||||||
|
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
|||||||
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
|
||||||
import Loader from "@/components/Loader.vue";
|
import Loader from "@/components/Loader.vue";
|
||||||
import SingleProductImageSwiper from "@/components/SingleProductImageSwiper.vue";
|
import SingleProductImageSwiper from "@/components/SingleProductImageSwiper.vue";
|
||||||
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const productId = computed(() => route.params.id);
|
const productId = computed(() => route.params.id);
|
||||||
@@ -247,6 +248,7 @@ const settings = useSettingsStore();
|
|||||||
const yaMetrika = useYaMetrikaStore();
|
const yaMetrika = useYaMetrikaStore();
|
||||||
const imagesLoaded = ref(false);
|
const imagesLoaded = ref(false);
|
||||||
const images = ref([]);
|
const images = ref([]);
|
||||||
|
const haptic = useHapticFeedback();
|
||||||
|
|
||||||
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) {
|
||||||
@@ -269,7 +271,7 @@ async function onCartBtnClick() {
|
|||||||
if (isInCart.value === false) {
|
if (isInCart.value === false) {
|
||||||
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');
|
haptic.notificationOccurred('success');
|
||||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.ADD_TO_CART, {
|
yaMetrika.reachGoal(YA_METRIKA_GOAL.ADD_TO_CART, {
|
||||||
price: product.value.final_price_numeric,
|
price: product.value.final_price_numeric,
|
||||||
currency: product.value.currency,
|
currency: product.value.currency,
|
||||||
@@ -294,12 +296,12 @@ async function onCartBtnClick() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
await router.push({'name': 'cart'});
|
await router.push({'name': 'cart'});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await window.Telegram.WebApp.HapticFeedback.notificationOccurred('error');
|
await haptic.notificationOccurred('error');
|
||||||
error.value = e.message;
|
error.value = e.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,7 +324,7 @@ function openManagerChat() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
|
|
||||||
// Формируем ссылку для открытия чата с менеджером
|
// Формируем ссылку для открытия чата с менеджером
|
||||||
// manager_username должен быть username (например, @username)
|
// manager_username должен быть username (например, @username)
|
||||||
@@ -340,7 +342,7 @@ function openManagerChat() {
|
|||||||
|
|
||||||
function setQuantity(newQuantity) {
|
function setQuantity(newQuantity) {
|
||||||
quantity.value = newQuantity;
|
quantity.value = newQuantity;
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ return [
|
|||||||
"app_debug" => false,
|
"app_debug" => false,
|
||||||
'image_aspect_ratio' => '1:1',
|
'image_aspect_ratio' => '1:1',
|
||||||
'image_crop_algorithm' => 'cover',
|
'image_crop_algorithm' => 'cover',
|
||||||
|
'haptic_enabled' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'telegram' => [
|
'telegram' => [
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ final class AppDTO
|
|||||||
private bool $appDebug;
|
private bool $appDebug;
|
||||||
private int $languageId;
|
private int $languageId;
|
||||||
private string $shopBaseUrl;
|
private string $shopBaseUrl;
|
||||||
|
private bool $hapticEnabled;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
bool $appEnabled,
|
bool $appEnabled,
|
||||||
@@ -21,7 +22,8 @@ final class AppDTO
|
|||||||
string $themeDark,
|
string $themeDark,
|
||||||
bool $appDebug,
|
bool $appDebug,
|
||||||
int $languageId,
|
int $languageId,
|
||||||
string $shopBaseUrl
|
string $shopBaseUrl,
|
||||||
|
bool $hapticEnabled = true
|
||||||
) {
|
) {
|
||||||
$this->appEnabled = $appEnabled;
|
$this->appEnabled = $appEnabled;
|
||||||
$this->appName = $appName;
|
$this->appName = $appName;
|
||||||
@@ -31,6 +33,7 @@ final class AppDTO
|
|||||||
$this->appDebug = $appDebug;
|
$this->appDebug = $appDebug;
|
||||||
$this->languageId = $languageId;
|
$this->languageId = $languageId;
|
||||||
$this->shopBaseUrl = $shopBaseUrl;
|
$this->shopBaseUrl = $shopBaseUrl;
|
||||||
|
$this->hapticEnabled = $hapticEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAppEnabled(): bool
|
public function isAppEnabled(): bool
|
||||||
@@ -73,6 +76,11 @@ final class AppDTO
|
|||||||
return $this->shopBaseUrl;
|
return $this->shopBaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isHapticEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->hapticEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public function toArray(): array
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -84,6 +92,7 @@ final class AppDTO
|
|||||||
'app_debug' => $this->isAppDebug(),
|
'app_debug' => $this->isAppDebug(),
|
||||||
'language_id' => $this->getLanguageId(),
|
'language_id' => $this->getLanguageId(),
|
||||||
'shop_base_url' => $this->getShopBaseUrl(),
|
'shop_base_url' => $this->getShopBaseUrl(),
|
||||||
|
'haptic_enabled' => $this->isHapticEnabled(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class SettingsHandler
|
|||||||
'mainpage_blocks' => $this->settings->get('mainpage_blocks', []),
|
'mainpage_blocks' => $this->settings->get('mainpage_blocks', []),
|
||||||
'privacy_policy_link' => $this->settings->get('app.privacy_policy_link'),
|
'privacy_policy_link' => $this->settings->get('app.privacy_policy_link'),
|
||||||
'image_aspect_ratio' => $this->settings->get('app.image_aspect_ratio', '1:1'),
|
'image_aspect_ratio' => $this->settings->get('app.image_aspect_ratio', '1:1'),
|
||||||
|
'haptic_enabled' => $appConfig->isHapticEnabled(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ class SettingsSerializerService
|
|||||||
$data['theme_dark'] ?? 'dark',
|
$data['theme_dark'] ?? 'dark',
|
||||||
$data['app_debug'] ?? false,
|
$data['app_debug'] ?? false,
|
||||||
$data['language_id'],
|
$data['language_id'],
|
||||||
$data['shop_base_url']
|
$data['shop_base_url'],
|
||||||
|
$data['haptic_enabled'] ?? true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user