UNPKG

carget

Version:

optimization js code for carget website

1,587 lines (1,410 loc) 96.1 kB
const isExelLoadingFinish = false let Google_DB = {} let carData = {} const customConsoleLog = async ( description, value, descriptionStyle, valueStyle ) => { await console.log(`%c${description}`, descriptionStyle, value) } let TEST3, TEST4 let modelsArray = [] const data4 = [ 'Работаем за свои средства', 'Белая и прозрачная структура договоров', 'Официальные оплаты по счетам', 'Быстрый возврат средств клиенту в случае нарушения условий договора', 'Исключены таможенные риски', 'Большой парк электромобилей в наличии в Москве', 'Русификация авто и техническая поддержка', ] const data5 = [ { img: 'https://carget.su/wp-content/uploads/2023/11/phone-icon.png', contact: '+7 996 410-01-87', }, { img: 'https://carget.su/wp-content/uploads/2023/11/whatsapp-icon.png', contact: '+7 996 410-01-87', }, { img: 'https://carget.su/wp-content/uploads/2023/11/telegram-icon.png', contact: '+7 996 410-01-87', }, { img: 'https://carget.su/wp-content/uploads/2023/11/email-icon.png', contact: 'carget@bk.ru', }, ] let carState = { model: [], models: [], options: { color: [0, '', []], wheels: [0, '', [], true], interiorColor: [0, '', []], runningBoards: [[], true], }, delivery: {}, labFees: 0, recyclingFee: 0, carLocalization: 0, marga: [], } let updatedCarState let myPriceModels const priceInRub = {} const pricecustomInRub = {} let savedSlides = {} let originalColorOptions = {} let originalWheelsOptions = {} let originalInteriorColorOptions = {} // Создаем динамически divPriceModel для каждой модели const divPriceModels = {} let previousModel, previousColor = null // ..... импорты ..... // let saleAll = 0 let pdfDoc let totalPrice = 0 let indexDisk = 0 let myArrowDiskImages let myDiskImage, mySalonImage let isUpdatingCarousel = false let modelNames, modelElements let arrayImagesForPDF = [ 'https://carget.su/wp-content/uploads/2023/11/carget-logo.jpg', 'https://carget.su/wp-content/uploads/2023/11/carget-logo.jpg', 'https://carget.su/wp-content/uploads/2023/11/carget-logo.jpg', ] const arrayTitlePrice = { color: '#carget-acordion-color-price span', wheels: '#carget-acordion-wheels-price span', interiorColor: '#carget-acordion-interiorColor-price span', } const arrayWrappers = [ '.carget-color', '.carget-wheels', '.carget-interiorColor', '.carget-runningBoards', ] const cargetModels = document.querySelector( '#carget-models .elementor-container' ) // ЦЕНА В ШАПКЕ const totalpriceElements = document.querySelectorAll('.header-totalprice') // ЧЕКБОКСЫ ---------------------------------------- // const checkbox = document.getElementById('myCheckbox') const checkboxLabel = document.querySelector('.custom-checkbox-label') const checkboxSpan = document.getElementById('custom-checkbox-span') // ЧЕК --------------------------------------- // const checkImages = document.querySelectorAll('.check-list-image') const discountCheck = document.getElementById('check-list-items-discount') const priceItemDiscount = document.getElementById('price-items-discount') // Выбираем все элементы с классами для цвета, салона, дисков и дополнительных опций const colorKuzovaElements = document.querySelectorAll('.check-color') const colorSalonaElements = document.querySelectorAll('.check-color-salon') const diskElements = document.querySelectorAll('.check-disk') const dopOptionsElements = document.querySelector('.check-dop-option') const totalPriceCarElements = document.querySelectorAll('.total-price-car') const totalSaleCarElements = document.querySelectorAll('.total-discount') // Вставляем стоимости const priceColorElements = document.querySelectorAll('.check-color-price') const priceColorSalonElements = document.querySelectorAll( '.check-color-salon-price' ) const priceDiskElements = document.querySelectorAll('.check-disk-price') const priceDopOptionsElements = document.querySelector( '.check-dop-option-price' ) // Получаем элементы ВСЕХ каруселей const colorCarousel = document .getElementById('carget-acordion-color') .querySelector('.swiper') const colorImageCarousel = document .getElementById('carget-acordion-color-image') .querySelector('.swiper') const diskDiametr = document.getElementById('carget-acordion-disk') ? document.getElementById('carget-acordion-disk').querySelector('.swiper') : '' const diskImage = document.getElementById('carget-acordion-disk-image') ? document .getElementById('carget-acordion-disk-image') .querySelector('.swiper') : '' const colorSalon = document .getElementById('carget-acordion-salon') .querySelector('.swiper') const salonImage = document .getElementById('carget-acordion-salon-color') .querySelector('.swiper') // Определим функцию для добавления нового элемента function addNewElementInCheck(customs) { try { // Найдем элемент по id var wrapperBlock = document.getElementById('check-list-wrapper-block') if (!wrapperBlock) throw new Error("Элемент с id 'check-list-wrapper-block' не найден.") // Проверим и удалим существующий элемент, если он уже добавлен var existingElement = document.querySelector( '.check-list-items.new-element-custom' ) if (existingElement) { wrapperBlock.removeChild(existingElement) } // Создадим новый div элемент с классом 'check-list-items' и 'new-element' var newCheckListItem = document.createElement('div') newCheckListItem.className = 'check-list-items new-element-custom' // Используем данные из объекта customs для создания внутренней структуры HTML newCheckListItem.innerHTML = ` <div class="check-list-row"> <span class="check-list-item">${customs.title}</span> <span class="check-list-item check-new-item"></span> <span class="check-list-item check-new-item-price">${customs.price}</span> </div> ` // Найдем элемент "Суммарная скидка" var discountElement = document.getElementById('check-list-items-discount') if (!discountElement) throw new Error("Элемент с id 'check-list-items-discount' не найден.") // Вставим новый элемент перед элементом "Суммарная скидка" wrapperBlock.insertBefore(newCheckListItem, discountElement) document .querySelectorAll('.new-element-custom .check-list-item') .forEach((e) => { e.style.borderBottom = '0' }) } catch (error) { console.error( 'Произошла ошибка при добавлении нового элемента:', error.message ) } } const loader = document.getElementById('header-loader') const cargetLoader = document.getElementById('carget-loader') function showLoader() { loader.style.display = 'block' cargetLoader.style.display = 'block' } function hideLoader() { loader.style.display = 'none' cargetLoader.style.display = 'none' console.log('loader hide') } // Создаем элемент иконки автомобиля от Google Fonts const loaderCar = document.createElement('div') loaderCar.id = 'loader-car' loaderCar.className = 'loaderCar-text' loaderCar.textContent = 'CARGET' // Создаем элемент текста загрузки const loaderText = document.createElement('div') loaderText.id = 'loader-text' loaderText.className = 'loader-text' loaderText.textContent = 'Подгружаем данные' // Добавляем элемент текста в загрузчик loader.appendChild(loaderCar) loader.appendChild(loaderText) // Тексты для смены const texts = [ 'Настраиваем конфигуратор', 'Почти готово', 'Загружаем настройки', 'Финальные штрихи', ] let textIndex = 0 function changeLoaderText() { loaderText.textContent = texts[textIndex] textIndex = (textIndex + 1) % texts.length } // Меняем текст каждые 2 секунды const textChangeInterval = setInterval(changeLoaderText, 2000) let selectedColor const processSlidesColor = ( carousel, savedSlides, addedSlides, option, attribute ) => { // Проверяем, был ли слайд уже добавлен const isSlideAlreadyAdded = addedSlides.some((slide) => slide.innerHTML.includes(`alt="${option.color}"`) ) // Если слайд не был добавлен, фильтруем и добавляем уникальные слайды if (!isSlideAlreadyAdded) { const filteredSlides = savedSlides.filter((slide) => slide.innerHTML.includes(`alt="${option.color}"`) ) // Для carousels, которые содержат изображения, удаляем дублирующие слайды по alt if (attribute) { const uniqueAlts = {} filteredSlides.forEach((slide) => { const alt = slide.querySelector(attribute).getAttribute('alt') if (!(alt in uniqueAlts)) { uniqueAlts[alt] = true } else { slide.remove() } }) } filteredSlides.forEach((slide) => { carousel.swiper.appendSlide(slide) addedSlides.push(slide) }) } } const processSlidesWheelsSalon = ( swiper, savedSlides, addedSlides, option, attribute, selectedModel, selectedColor ) => { // Проверяем, соответствует ли опция выбранной модели и цвету if ( (option.models.includes(selectedModel) || option.models.includes('All')) && (option.colors.includes(selectedColor) || option.colors.includes('All')) ) { // Проверяем, был ли слайд уже добавлен const isSlideAlreadyAdded = Array.from(addedSlides).some( (slide) => slide.childNodes[0].childNodes[0].alt === option.color ) // Если слайд не был добавлен, фильтруем и добавляем уникальные слайды if (!isSlideAlreadyAdded) { /* const filteredSlides = savedSlides.filter((slide) => slide.innerHTML.includes(`alt="${option.color}"`) ) */ const filteredSlides = savedSlides.filter( (slide) => slide.childNodes[0].childNodes[0].alt === option.color ) // Для carousels, которые содержат изображения, удаляем дублирующие слайды по alt if (attribute) { const uniqueAlts = {} filteredSlides.forEach((slide) => { const alt = slide.childNodes[0].childNodes[0].alt if (!(alt in uniqueAlts)) { uniqueAlts[alt] = true } else { slide.remove() } }) } filteredSlides.forEach((slide) => { swiper.appendSlide(slide) addedSlides.add(slide) }) } } else { } } const checkColor = (selectedModel) => { if (carState.options.color[2][0].models) { carState.options.color[2] = [...originalColorOptions] carState.options.color[2] = carState.options.color[2].filter( (option) => option.models.includes(selectedModel) || option.models.includes('All') ) colorCarousel.swiper.removeAllSlides() colorImageCarousel.swiper.removeAllSlides() let addedColorSlides = [] let addedColorImageSlides = [] carState.options.color[2].forEach((option) => { // Обрабатываем слайды для карусели цвета processSlidesColor( colorCarousel, savedSlides.colorCarousel, addedColorSlides, option, '.swiper-slide-image' ) // Обрабатываем слайды для карусели изображений цвета processSlidesColor( colorImageCarousel, savedSlides.colorImageCarousel, addedColorImageSlides, option, '.swiper-slide-image' ) }) areAllSlidesWithSameAlt(colorCarousel) ? removeDuplicateSlides(colorCarousel) : '' areAllSlidesWithSameAlt(colorImageCarousel) ? removeDuplicateSlides(colorImageCarousel) : '' colorCarousel.swiper.update() colorImageCarousel.swiper.update() } if (colorCarousel.swiper.slides.length > 1) { customConsoleLog( 'colorCarousel.swiper.slides.length > 1', colorCarousel.swiper.slides.length, 'background: brown; color: white;', 'font-weight: bold;' ) setTimeout(() => { colorCarousel.swiper.slideTo(colorCarousel.swiper.params.slidesPerView) }, 400) } else if (colorCarousel.swiper.slides.length <= 1) { customConsoleLog( 'colorCarousel.swiper.slides.length <= 1', colorCarousel.swiper.slides.length, 'background: brown; color: white;', 'font-weight: bold;' ) setTimeout(() => { carState.options.color[1] = colorCarousel.querySelector( '.swiper-slide-active img' ).alt }, 1500) } else { console.log('сработал не известный сценарий checkColor') } return true } const checkWheels = (selectedModel) => { if (diskDiametr.swiper) { // Показать блок диски, если есть хотя бы один слайд и активирована опция колес document.querySelectorAll(arrayWrappers[1]).forEach((e) => { if (diskDiametr.swiper.slides.length >= 1 || carState.options.wheels[3]) { e.style.display = 'block' } }) // Работа с базой - Копируем исходные опции колес carState.options.wheels[2] = [...originalWheelsOptions] // Фильтруем опции колес по выбранной модели и цвету carState.options.wheels[2] = carState.options.wheels[2].filter( (option) => (option.models.includes(selectedModel) || option.models.includes('All')) && (option.colors.includes( previousColor ? previousColor : selectedColor ) || option.colors.includes('All')) ) // Работа с каруселью - Удаляем все слайды из каруселей диаметра и изображений дисков diskDiametr.swiper.removeAllSlides() diskImage.swiper.removeAllSlides() let addedDiskDiametrSlides = new Set() // Обрабатываем слайды для диаметра и изображений дисков carState.options.wheels[2].forEach((option) => { processSlidesWheelsSalon( diskDiametr.swiper, savedSlides.diskDiametr, addedDiskDiametrSlides, option, '.swiper-slide-image', selectedModel, selectedColor ) }) // Проверяем и удаляем дубликаты слайдов areAllSlidesWithSameAlt(diskDiametr) ? removeDuplicateSlides(diskDiametr) : '' // Обновляем карусели diskDiametr.swiper.update() diskImage.swiper.update() // Сценарии для карусели if (diskDiametr.swiper.slides.length > 1) { customConsoleLog( 'diskDiametr > 1', diskDiametr.swiper.slides.length, 'background: yellow; color: black;', 'font-weight: bold;' ) setTimeout(() => { // diskDiametr.swiper.params.centeredSlides = true // diskDiametr.swiper.params.slideToClickedSlide = true diskDiametr .querySelectorAll('.elementor-swiper-button') .forEach((e) => { e.style.display = 'inline-flex' }) }, 1500) } else if (diskDiametr.swiper.slides.length <= 1) { customConsoleLog( 'diskDiametr <= 1', diskDiametr.swiper.slides.length, 'background: yellow; color: black;', 'font-weight: bold;' ) // Если один или меньше слайдов, скрываем кнопки переключения setTimeout(() => { carState.options.wheels[1] = diskDiametr.querySelector( '.swiper-slide-active img' ) ? diskDiametr.querySelector('.swiper-slide-active img').alt : '' diskDiametr .querySelectorAll('.elementor-swiper-button') .forEach((e) => { e.style.display = 'none' }) }, 1500) } else { console.log('нет такого СЦЕНАРИЯ для дисков') customConsoleLog( 'нет такого СЦЕНАРИЯ для дисков', diskDiametr.swiper.slides.length, 'background: yellow; color: black;', 'font-weight: bold;' ) } } diskImage.swiper ? updateCarouselDisk(`${carState.options.color[1]}-0`, 'checkWheels') : '' return true } const checkSalon = (selectedModel) => { carState.options.interiorColor[2] = [...originalInteriorColorOptions] carState.options.interiorColor[2] = carState.options.interiorColor[2].filter( (option) => (option.models.includes(selectedModel) || option.models.includes('All')) && (option.colors.includes(previousColor ? previousColor : selectedColor) || option.colors.includes('All')) ) colorSalon.swiper.removeAllSlides() salonImage.swiper.removeAllSlides() let addedColorSalonSlides = [] let addedSalonImageSlides = [] carState.options.interiorColor[2].forEach((option) => { processSlidesColor( colorSalon, savedSlides.colorSalon, addedColorSalonSlides, option, '.swiper-slide-image' ) processSlidesColor( salonImage, savedSlides.salonImage, addedSalonImageSlides, option, '.swiper-slide-image' ) }) areAllSlidesWithSameAlt(colorSalon) ? removeDuplicateSlides(colorSalon) : '' colorSalon.swiper.update() salonImage.swiper.update() //обновляю цвет салона, заголовок и цену if (colorSalon.swiper.slides.length > 1) { setTimeout(() => { colorSalon.swiper.slideTo(colorSalon.swiper.params.slidesPerView) ? colorSalon.swiper.slideTo(colorSalon.swiper.params.slidesPerView) : colorSalon.swiper.slideNext() colorSalon.swiper.params.centeredSlides = true colorSalon.swiper.params.slideToClickedSlide = true colorSalon.querySelectorAll('.elementor-swiper-button').forEach((e) => { e.style.display = 'inline-flex' }) }, 1500) } else if (colorSalon.swiper.slides.length <= 1) { setTimeout(() => { carState.options.interiorColor[1] = colorSalon.querySelector( '.swiper-slide-active img' ).alt colorSalon.querySelectorAll('.elementor-swiper-button').forEach((e) => { e.style.display = 'none' }) }, 1500) } return true } async function updateOptions(selectedModel, where) { customConsoleLog( 'updateOptions', where, 'background: blue; color: white;', 'font-weight: bold;' ) const { options } = carState const { wheels, color } = options const wheelsCheckNeeded = wheels[3] === true const colorCheckNeeded = selectedModel && selectedModel !== previousModel const salonCheckNeeded = colorSalon.swiper ? true : false const runChecks = async ({ isCheckColor = false, isCheckWheels = false, isCheckSalon = false, }) => { try { if (isCheckColor && colorCarousel.swiper) { await checkColor(selectedModel) } if (isCheckWheels) { await checkWheels(selectedModel) } if (isCheckSalon && colorSalon.swiper) { await checkSalon(selectedModel) } await updateTitlePrice('updateOptions') } catch (error) { console.error('Ошибка при выполнении проверок:', error) } } try { // -------------------- МОДЕЛЬ ИЗМЕНИЛАСЬ if (colorCheckNeeded) { previousModel = selectedModel customConsoleLog( 'МОДЕЛЬ ИЗМЕНИЛАСЬ', selectedModel, 'background: seagreen; color: white;', 'font-weight: bold;' ) await runChecks({ isCheckColor: colorCheckNeeded, isCheckWheels: wheelsCheckNeeded, isCheckSalon: salonCheckNeeded, }) const customs = { title: '*Стоимость таможни РФ', description: '—', price: `${numberWithSpaces( roundNumberToNChars( pricecustomInRub[selectedModel], 4, 'updateOptions' ) )} руб.`, } addNewElementInCheck(customs) } // -------------------- ЦВЕТ КУЗОВА ИЗМЕНИЛСЯ else if (color[1] && color[1] !== previousColor) { previousColor = color[1] customConsoleLog( 'ЦВЕТ КУЗОВА ИЗМЕНИЛСЯ', color[1], 'background: seagreen; color: white;', 'font-weight: bold;' ) await runChecks({ isCheckColor: colorCheckNeeded, isCheckWheels: wheelsCheckNeeded, isCheckSalon: salonCheckNeeded, }) } } catch (error) { console.error('Ошибка при обновлении опций:', error) } } //удаляем дубликаты, если один элемент function removeDuplicateSlides(carouselElement) { try { // Создаем Set для хранения уникальных значений alt const uniqueAlts = new Set() // Получаем все слайды карусели const slides = carouselElement.querySelectorAll('.swiper-slide') // Проходимся по каждому слайду slides.forEach((slide, index) => { const imgElement = slide.querySelector('.swiper-slide-image') if (imgElement) { // Получаем значение атрибута alt изображения const alt = imgElement.getAttribute('alt') // Если такого alt еще нет в Set, добавляем его if (!uniqueAlts.has(alt)) { uniqueAlts.add(alt) } else { // Если такой alt уже есть, удаляем текущий слайд из DOM slide.remove() } } else { console.warn( `Слайд ${ index + 1 } не содержит изображение с классом '.swiper-slide-image'` ) } }) } catch (error) { console.error('Ошибка в функции removeDuplicateSlides:', error) } } //проверяем на одинаковый alt у всех в карусели function areAllSlidesWithSameAlt(carousel) { const slides = carousel.querySelectorAll('.swiper-slide') if (slides.length === 0) { // Если карусель пустая, считаем, что все слайды с одинаковым alt return true } // Получаем значение атрибута alt первого слайда const firstSlideAlt = slides[0] .querySelector('.swiper-slide-image') .getAttribute('alt') // Проверяем, совпадает ли значение атрибута alt у всех слайдов for (let i = 1; i < slides.length; i++) { const slideAlt = slides[i] .querySelector('.swiper-slide-image') .getAttribute('alt') if (slideAlt !== firstSlideAlt) { // Если хотя бы одно значение отличается, возвращаем false return false } } // Если все значения атрибута alt одинаковы, возвращаем true return true } // плюс процент с округлением function calculateWithPercentage(sum, percentage) { return roundNumberToNChars( Math.ceil(sum * (1 + percentage / 100)), 4, 'calculateWithPercentage' ) } // пробелы function numberWithSpaces(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') } // округление function roundNumberToNChars(number, n, where) { number = Math.ceil(number) if (typeof number !== 'number' || !Number.isInteger(number) || number < 0) { throw new Error('number должно быть положительным целым числом', where) } if (typeof n !== 'number' || !Number.isInteger(n) || n < 1) { throw new Error( 'n должно быть положительным целым числом больше или равным 1', where ) } const multiplier = Math.pow(10, n - 1) const roundedNumber = Math.ceil(number / multiplier) * multiplier return roundedNumber } // API курсы валют async function fetchCurrencyRates() { try { const storedData = localStorage.getItem('currencyRates') if (!storedData) { const responseCNY = await fetch('https://open.er-api.com/v6/latest/CNY') const dataCNY = await responseCNY.json() const responseUSD = await fetch('https://open.er-api.com/v6/latest/USD') const dataUSD = await responseUSD.json() const rates = { CNY: dataCNY.rates.RUB, USD: dataUSD.rates.RUB } await saveCurrencyRatesToLocalStorage(rates) } else { const currencyRates = JSON.parse(storedData) const lastUpdated = new Date(currencyRates.lastUpdated) const currentDate = new Date() const currentDateString = `${currentDate.getDate()}-${ currentDate.getMonth() + 1 }-${currentDate.getFullYear()}` const lastUpdatedString = `${lastUpdated.getDate()}-${ lastUpdated.getMonth() + 1 }-${lastUpdated.getFullYear()}` if (currentDateString !== lastUpdatedString) { const responseCNY = await fetch('https://open.er-api.com/v6/latest/CNY') const dataCNY = await responseCNY.json() const responseUSD = await fetch('https://open.er-api.com/v6/latest/USD') const dataUSD = await responseUSD.json() const rates = { CNY: dataCNY.rates.RUB, USD: dataUSD.rates.RUB } await saveCurrencyRatesToLocalStorage(rates) } } } catch (error) { console.error('Ошибка при загрузке курсов валют:', error) throw error // Обработка ошибки } } async function saveCurrencyRatesToLocalStorage(data) { const currentDate = new Date().toISOString() // Получить текущую дату и время const currencyRates = { rates: data, lastUpdated: currentDate } await localStorage.setItem('currencyRates', JSON.stringify(currencyRates)) return data } /* // КАРУСЕЛИ НА НУЖНЫЙ СЛАЙД ОБНОВЛЕНИЯ async function updateCarouselDisk(dc, where) { // Включаем карусель diskImage и очищаем все слайды diskImage.swiper.removeAllSlides() const consoleValue = { dc, where } customConsoleLog( 'updateCarouselDisk', consoleValue, 'background: blue; color: white;', 'font-weight: bold;' ) const color = dc.split('-')[0] const matchingIndexes = [] carState.options.wheels[2].forEach((option) => { // Проверяем, что у опции есть нужный цвет или 'All' if (option.colors.includes(color) || option.colors.includes('All')) { matchingIndexes.push(option.index) // Добавляем индекс в массив } }) console.log('Matching indexes:', matchingIndexes) const exactMatchSlides = myArrowDiskImages.filter((slide) => { // Проверяем условия фильтрации для каждого слайда return matchingIndexes.some( (index) => slide.querySelector('img').alt === `${color}-${index}` ) }) console.log('Filtered slides:', exactMatchSlides) exactMatchSlides.forEach((slide) => { diskImage.swiper.appendSlide(slide) }) diskImage.swiper.update() // Обновляем состояние carState carState.options.wheels[0] = indexOption('wheels', 0, 'index').price carState.options.wheels[1] = indexOption('wheels', 0, 'index').color if (diskDiametr.swiper) { diskDiametr.swiper.slideTo(diskDiametr.swiper.params.slidesPerView, 400) diskDiametr.swiper.update() } diskDiametr.querySelectorAll('.elementor-swiper-button').forEach((button) => { button.style.display = diskDiametr.swiper.slides.length <= 1 ? 'none' : 'inline-flex' }) updateTitlePrice('updateCarouselDisk') }*/ // КАРУСЕЛИ НА НУЖНЫЙ СЛАЙД ОБНОВЛЕНИЯ async function updateCarouselDisk(dc, where) { // Включаем карусель diskImage и очищаем все слайды diskImage.swiper.removeAllSlides() const consoleValue = { dc, where } customConsoleLog( 'updateCarouselDisk', consoleValue, 'background: blue; color: white;', 'font-weight: bold;' ) // const color = dc.split('-')[0] const color = dc.substring(0, dc.lastIndexOf('-')) const matchingIndexes = [] if (carState.options.wheels[2] && diskDiametr.swiper) { carState.options.wheels[2].forEach((option) => { // Проверяем, что у опции есть нужный цвет или 'All' if (option.colors.includes(color) || option.colors.includes('All')) { matchingIndexes.push(option.index) // Добавляем индекс в массив } }) } else if (carState.options.wheels[2] && !diskDiametr.swiper) { matchingIndexes.push(carState.options.wheels[2][0].index) } else { console.error('matchingIndexes underfind') return } let exactMatchSlides = [] if (Array.isArray(myArrowDiskImages)) { exactMatchSlides = myArrowDiskImages.filter((slide) => { // Проверяем условия фильтрации для каждого слайда return matchingIndexes.some( (index) => slide.querySelector('img').alt === `${color}-${index}` ) }) } else if (myArrowDiskImages) { exactMatchSlides = myArrowDiskImages } else { console.error('myArrowDiskImages is undefined') return } exactMatchSlides.forEach((slide) => { diskImage.swiper.appendSlide(slide) }) diskImage.swiper.update() // Обновляем состояние carState const wheelIndexOption = indexOption('wheels', 0, 'index') if (wheelIndexOption) { carState.options.wheels[0] = wheelIndexOption.price carState.options.wheels[1] = wheelIndexOption.color } else { console.error('indexOption("wheels", 0, "index") returned undefined') return } if (diskDiametr && diskDiametr.swiper) { diskDiametr.swiper.slideTo(diskDiametr.swiper.params.slidesPerView, 400) diskDiametr.swiper.update() diskDiametr .querySelectorAll('.elementor-swiper-button') .forEach((button) => { button.style.display = diskDiametr.swiper.slides.length <= 1 ? 'none' : 'inline-flex' }) } else if (diskDiametr) { // Если нет swiper, обработка для случая без swiper // Например, можно просто показать первый элемент и скрыть кнопки навигации const slides = Array.from(diskDiametr.children) slides.forEach((slide, index) => { slide.style.display = index === 0 ? 'block' : 'none' }) diskDiametr .querySelectorAll('.elementor-swiper-button') .forEach((button) => { button.style.display = slides.length <= 1 ? 'none' : 'inline-flex' }) } else { console.error('diskDiametr is undefined') return } updateTitlePrice('updateCarouselDisk') } function findSlideIndexByAlt(slider, altAttribute) { const slides = slider.querySelectorAll('.swiper-slide') for (let i = 0; i < slides.length; i++) { const slide = slides[i] const img = slide.querySelector('img') if (img && img.alt === altAttribute) { const dataIndex = slide.getAttribute('data-swiper-slide-index') return parseInt(dataIndex) } } return -1 // Возвращаем -1, если слайд с указанным alt не найден } // ПРОВЕРКА НА ДИСКИ ДЛЯ ВЫГРУЗКИ ФОТО АВТО, ЕСЛИ FALSE ТО ВЫГРУЖАЮ ИЗ ПЕРВОЙ КАРУСЕЛИ // Функция для установки содержимого изображения в чек и обновления массива изображений для PDF async function updateImages(imageElement) { if (!imageElement) return const imgElement = imageElement.querySelector('img') if (!imgElement) return const src = imgElement.getAttribute('src') if (!src) return arrayImagesForPDF[1] = src checkImages[0].innerHTML = myDiskImage.outerHTML } async function loadImageAsDataURLWithLogging(imagePath) { try { const imageDataURL = await loadImageAsDataURL(imagePath) return imageDataURL } catch (error) { console.error('Error loading image:', error) throw error } } // Функция для преобразования изображения в Data URL async function loadImageAsDataURL(imagePath) { // Проверяем, является ли переданный путь уже Data URL if (!imagePath || imagePath.startsWith('data:image')) { return imagePath // Если это пустое значение или Data URL, возвращаем его без изменений } try { const response = await fetch(imagePath) if (!response.ok) { throw new Error('Failed to fetch image') } let blob = await response.blob() // Изменили const на let const reader = new FileReader() // Проверяем, является ли тип изображения WebP или PNG const isWebP = blob.type === 'image/webp' if (isWebP) { // Если изображение в формате WebP или PNG, конвертируем его в другой формат const image = new Image() image.src = URL.createObjectURL(blob) await image.decode() // Дожидаемся загрузки изображения const canvas = document.createElement('canvas') canvas.width = image.width canvas.height = image.height const context = canvas.getContext('2d') context.drawImage(image, 0, 0) const convertedBlob = await new Promise((resolve, reject) => { canvas.toBlob((blob) => { if (!blob) { reject(new Error('Failed to convert image')) } resolve(blob) }) // Если изображение в формате PNG, конвертируем его в JPEG }) blob = convertedBlob // Изменили присвоение значения константе } return new Promise((resolve, reject) => { reader.onload = () => resolve(reader.result) reader.onerror = reject reader.readAsDataURL(blob) }) } catch (error) { throw new Error('Error loading image as Data URL: ' + error.message) } } // СКИДКА заголовки параметров const updateTitlePrice = async (where) => { customConsoleLog( 'updateTitlePrice', where, 'background: blue; color: white;', 'font-weight: bold;' ) if (typeof arrayTitlePrice !== 'object' || arrayTitlePrice === null) { console.error('arrayTitlePrice не является объектом') return } Object.entries(arrayTitlePrice).forEach(([option, selector]) => { try { const optionData = carState.options[option] if (!optionData) return const [price, selectedColor, colorOptions] = optionData let selectedOption = colorOptions.find( ({ color }) => color === selectedColor ) if (!selectedOption && colorOptions.length > 0) { selectedOption = colorOptions[0] } const element = document.querySelector(selector) if (element && selectedOption) { const saleHtml = selectedOption.sale !== undefined ? `<span style="font-weight:700; display: flex; justify-content: center; gap: 8px;"> ${price} руб. <del style="text-decoration-color:#D10000; font-weight:300;">${selectedOption.sale} руб.</del> </span>` : `<span>${price} руб.</span>` element.innerHTML = ` <div style="display:flex;flex-direction:column;"> <span style="font-weight:700;">${ selectedColor === selectedOption.color ? selectedColor : selectedOption.color }</span> ${saleHtml} </div>` } } catch (error) { console.error(`Error updating option ${option}:`, error) } }) // Проверка состояния колес и наличия diskImage if (carState.options.wheels[3] === true && diskImage) { // ФОТО АВТО В ЧЕК myDiskImage = diskImage.querySelector('.swiper-slide-active') ? diskImage.querySelector('.swiper-slide-active') : diskImage.querySelector('.swiper-slide') if (myDiskImage) { // Копируем src изображения перед обновлением массива await updateImages(myDiskImage) } } else if (carState.options.wheels[3] === false && colorImageCarousel) { myDiskImage = colorImageCarousel.querySelector('.swiper-slide-active') if (myDiskImage) { // Копируем src изображения перед обновлением массива await updateImages(myDiskImage) } customConsoleLog( 'wheels[3] === false', '', 'background: brown; color: white;', 'font-weight: bold;' ) } else { myDiskImage = colorImageCarousel.querySelector('.swiper-slide') // Копируем src изображения перед обновлением массива await updateImages(myDiskImage) customConsoleLog( 'wheels[3] ни один сценарий не сработал', '', 'background: brown; color: white;', 'font-weight: bold;' ) } if (salonImage && salonImage.querySelector('.swiper-slide-active img')) { arrayImagesForPDF[2] = salonImage.querySelector( '.swiper-slide-active img' )?.src } else if (salonImage && salonImage.querySelector('.swiper-slide img')) { arrayImagesForPDF[2] = salonImage.querySelector('.swiper-slide img')?.src } updateCheck() } // Показать или скрыть ДОП ОПЦИИ const checkDopOtionShow = () => { const displayValue = carState.options.runningBoards[1] ? 'flex' : 'none' document.querySelectorAll(arrayWrappers[3]).forEach((e) => { e.style.display = displayValue }) } // Обнуление после выбора другой модели const removeShowDopOptions = () => { const dopOptionsWrapper = document.querySelector( '.dop-options-wrapper .elementor-widget-wrap' ) const selectedModel = carState.model[1] // Получаем текущую модель const children = dopOptionsWrapper.children const childrenArray = Array.from(children) carState.options.runningBoards[0].forEach((option, index) => { const child = children[index] const checkbox = document.getElementById(child.id) const checkboxInput = checkbox.querySelector(`#${child.id}`) // Установка состояния чекбокса в зависимости от условий if (option.check) { option.show = false checkboxInput.checked = false } const modelsToShow = option.models // Список моделей, для которых опция должна быть показана const showOption = modelsToShow.includes(selectedModel) || modelsToShow.includes('All') const displayValue = showOption ? 'flex' : 'none' childrenArray.forEach((el) => { if (el.id === child.id) { el.style.display = displayValue } }) }) } const updateDopOptions = () => { const selectedModel = carState.model[1] // Получаем текущую модель //ДОП ОПЦИИ const dopOptionsWrapper = document.querySelector( '.dop-options-wrapper .elementor-widget-wrap' ) const children = dopOptionsWrapper.children const childrenArray = Array.from(children) carState.options.runningBoards[0].forEach((option, index) => { const child = children[index] const modelsToShow = option.models // Список моделей, для которых опция должна быть показана const showOption = modelsToShow.includes(selectedModel) || modelsToShow.includes('All') const displayValue = showOption ? 'flex' : 'none' childrenArray.forEach((el) => { if (el.id === child.id) { el.style.display = displayValue } }) const checkbox = document.getElementById(child.id) const checkboxInput = checkbox.querySelector(`#${child.id}`) const checkboxLabel2 = checkbox.querySelector('.custom-checkbox-label') const checkboxLabel = checkbox.querySelector(`.dop-option-title`) const checkboxDescription = checkbox.querySelector( '.dop-option-description' ) const checkboxSpan = checkbox.querySelector(`#span-${child.id}`) // Устанавливаем заголовок и описание из option.title и option.description // СКИДКА доп опции checkboxLabel.textContent = option.title checkboxDescription.textContent = option.description checkboxSpan.innerHTML = `<div style="display:flex; gap:8px;"> <span style="font-weight:700;">${numberWithSpaces( option.price )} руб.</span> ${ option.sale !== 0 && option.sale ? `<del style="text-decoration-color:#D10000; font-weight:300; color: #000">${option.sale} руб.</del> ` : '' } </div>` // Добавление слушателей событий для чекбоксов const handleClick = () => { if (option.check) { checkboxInput.checked = !checkboxInput.checked option.show = checkboxInput.checked updateCheck() } } checkboxLabel2.addEventListener('click', handleClick) checkboxSpan.addEventListener('click', handleClick) }) } const checkDopOptions = () => { // Получаем данные о дополнительных опциях, которые нужно отобразить const dopOptionsToShow = carState.options.runningBoards[0].filter( (option) => option.show ) // Очищаем текстовое содержимое элементов dopOptionsElements и priceDopOptionsElements dopOptionsElements.textContent = '' priceDopOptionsElements.textContent = '' // Если есть отображаемые опции, выводим информацию о них if (dopOptionsToShow.length > 0 && carState.options.runningBoards[1]) { dopOptionsToShow.forEach((option) => { const title = option.title const price = option.price const titleElement = document.createElement('div') titleElement.textContent = title const priceElement = document.createElement('div') priceElement.textContent = `${numberWithSpaces(price)} руб.` dopOptionsElements.appendChild(titleElement) priceDopOptionsElements.appendChild(priceElement) }) } else { // Если опций нет или показывать их не нужно, выводим информацию "Нет" const noOptionsElement = document.createElement('div') noOptionsElement.textContent = 'Нет' dopOptionsElements.appendChild(noOptionsElement) priceDopOptionsElements.appendChild(noOptionsElement) } } // РАБОТА С МОДЕЛЯМИ // ---- НОВЫЙ МЕТОД -------- // Определение функции select function select(selector) { return document.querySelectorAll(selector) } /** * Получение ID элемента на основе имени модели. * @param {string} name - Имя модели. * @param {string} prefix - Префикс ID (по умолчанию 'accordion-car-model-'). * @returns {string} - ID элемента. */ function getElementId(name, prefix = 'accordion-car-model-') { const id = `${prefix}${name.toLowerCase()}` return id } /** * Создание объекта с элементами для модели. * @param {string} modelName - Название модели. * @param {object} modelInfo - Информация о модели. * @returns {object} - Объект с элементами для модели. */ function createModelElement(modelName, modelInfo) { const lowercaseName = modelName.toLowerCase() const elements = { model: select(`.${getElementId(lowercaseName)}`), acc: select(`.${getElementId(lowercaseName)} h4`), mobileButton: select(`.car-${lowercaseName}-mobile`), accordionItem: select( `.${getElementId(lowercaseName)} .elementor-tab-title` ), } return elements } /** * Определение элементов для каждой модели. * @param {array} models - Массив объектов с информацией о моделях. * @returns {object} - Объект с элементами для каждой модели. */ function defineModelElements(models) { const elements = {} models.forEach((model) => { const modelName = Object.keys(model)[0] elements[modelName] = createModelElement(modelName, model[modelName]) }) return elements } // СОЗДАНИЕ ЭЛЕМЕНТОВ - ПРАЙС В МОДЕЛИ const createDivPriceModel = (modelName) => { const divPriceModel = document.createElement('div') divPriceModel.className = `car${modelName}Model` // Используем динамический класс divPriceModel.id = `carmodel-price-${modelName.toLowerCase()}` return divPriceModel } // ЗАКРЫТОЕ ПОЛОЖЕНИЕ АККОРДИОНОВ async function closeAccordions() { modelNames.forEach((e) => { modelElements[e].accordionItem[0].click() }) } // СОЗДАЕМ ЦЕНУ АККОРДИОНАМ const innerPriceTitleModels = () => { // ЗАПИСЬ СТОИМОСТИ В ЗАГОЛОВКИ АККАРДИОНА // СКИДКА модели комплектации modelNames.forEach((modelName, index) => { divPriceModels[modelName].innerHTML = ` <div style="display:flex;flex-direction:column; gap: 8px;"> ${ carState.models[index][modelName].sale !== 0 && carState.models[index][modelName].sale ? `<span style="font-size:16; font-weight:300"><del style="color: white;">${carState.models[index][modelName].sale} руб.</del></span>` : '' } <span style="font-weight:700;">${numberWithSpaces( myPriceModels[index] )} руб.</span> </div> ` }) modelNames.forEach((modelName, index) => { const price = numberWithSpaces(myPriceModels[index]) modelElements[modelName].acc.forEach((element) => { element.innerHTML = ` <div style="display:flex;flex-direction:column; gap: 8px;"> ${ carState.models[index][modelName].sale !== 0 && carState.models[index][modelName].sale ? `<span style="font-size:20px; font-weight:300"><del style="color: white;">${carState.models[index][modelName].sale} руб.</del></span>` : '' } <span style="font-weight:700;">${price} руб.</span> </div> ` }) modelElements[modelName].accordionItem[0].appendChild( divPriceModels[modelName] ) }) } // Обновление кнопок function createAndAttachButtonClickHandler(modelName) { const buttons = document.querySelectorAll( `.car-${modelName.toLowerCase()}-mobile` ) buttons.forEach((button) => { button.addEventListener('click', () => { carState.model[1] = `${modelName}` updateButtonState(carState) removeShowDopOptions() updateCheck() }) }) } // Функция обновления состояния кнопок моделей function updateButtonState(carState) { modelNames.forEach((modelName) => { const isSelected = carState.model[1] === `${modelName}` const buttons = document.querySelectorAll( `.car-${modelName.toLowerCase()}-mobile` ) buttons.forEach((button) => { const buttonTextElement = button.querySelector('.elementor-button-text') const elementorButton = button.querySelector('.elementor-button') button.style.cursor = 'pointer' button.style.borderRadius = '3px' if (isSelected) { // Если выбрано, измените фон и цвет текста button.style.backgroundColor = '#fff' elementorButton ? (elementorButton.style.backgroundColor = '#fff') : '' buttonTextElement.style.color = '#000' updateOptions(modelName, 'updateButtonState') } else { // Если не выбрано, восстановите стандартные стили button.style.backgroundColor = '#DB2424' elementorButton ? (elementorButton.style.backgroundColor = '#DB2424') : '' buttonTextElement.style.color = '#fff' } buttonTextElement.textContent = isSelected ? 'ВЫБРАНО' : 'ВЫБРАТЬ' }) }) } function convertCarState(carState) { const storedData = localStorage.getItem('currencyRates') const currencyRates = JSON.parse(storedData) const usdToRubExchangeRate = currencyRates.rates.USD const cnyToRubExchangeRate = currencyRates.rates.CNY // Конвертация МОДЕЛЕЙ в рубли modelNames.forEach((modelName) => { const modelPrice = carState.models.find((model) => model[modelName]) const customPrice = modelPrice ? modelPrice[modelName].custom : 0 // Получаем таможенную стоимость модели const modelPriceCNY = modelPrice ? modelPrice[modelName].price : 0 pricecustomInRub[modelName] = customPrice * cnyToRubExchangeRate priceInRub[modelName] = modelPriceCNY * cnyToRubExchangeRate }) // Обновление объекта состояния с новыми значениями в рублях const updatedCarState = { ...carState, models: carState.models.map((model) => { const modelName = Object.keys(model)[0] const priceInRub = model[modelName].price * cnyToRubExchangeRate const customInRub = model[modelName].custom * cnyToRubExchangeRate return { [modelName]: { price: priceInRub, custom: customInRub } } }), delivery: { ...carState.delivery, fromTurgartToBishkek: carState.delivery.fromTurgartToBishkek * usdToRubExchangeRate, customs: carState.delivery.customs * usdToRubExchangeRate, otherExpenses: carState.delivery.otherExpenses * usdToRubExchangeRate, fromBishkekToRussia: carState.delivery.fromBishkekToRussia * usdToRubExchangeRate, }, labFees: carState.labFees, recyclingFee: carState.recyclingFee, carLocalization: carState.carLocalization, } return updatedCarState } function sumCarModelsPrices(carState) { // Получение всех цен из объекта состояния БЕЗ ОПЦИЙ const { models, delivery: { fromTurgartToBishkek, customs, otherExpenses, fromBishkekToRussia, }, labFees, recyclingFee, carLocalization, marga, } = carState // Суммирование всех цен const totalPrices = modelNames.map((modelName) => { const modelPriceCNY = models.find((model) => model[modelName])?.[modelName].price || 0 const customCNY = models.find((model) => model[modelName])?.[modelName].custom || 0 const total = modelPriceCNY + customCNY + fromTurgartToBishkek + customs + otherExpenses + fromBishkekToRussia + labFees + recyclingFee + carLocalization const totalWithMarga = calculateWithPercentage(total, carState.marga) // return roundNumberToNChars(totalWithMarga, 4, 'sumCarModelsPrices') return roundNumberToNChars(totalWithMarga, 4, 'sumCarModelsPrices') }) return totalPrices } function sumCarPrices(carState, myPriceModels) { // Получение всех цен из объекта состояния ОПЦИЙ const { options: { color, wheels, interiorColor, runningBoards }, } = carState const selectedModel = carState.model[1] const modelIndex = modelNames.indexOf(selectedModel) // Добавим консольные логи для отслеживания // Проверим, что modelIndex не равен -1 if (modelIndex === -1) { console.error('Выбранная модель не найдена в modelNames.') return // Возвращаем undefined в случае ошибки } const modelPrice = myPriceModels[modelIndex] let total = modelPrice + color[0] + wheels[0] + interiorColor[0] /* - saleAll если нужно делать вычет */ if (runningBoards[1]) { const options = runningBoards[0] options.forEach((option) => { if (option.show) { total += option.price } }) } totalPrice = total return numberWithSpaces(total) } const innerPriceHeader = () => { // ЗАПИСЬ В ШАПКУ ТОТАЛ ПРАЙС totalpriceElements.forEach((e) => { e.innerHTML = numberWithSpaces(totalPrice) }) } const totalSaleCheck = () => { // Сумма скидок для выбранной модели const model = carState.models.find((model) => model[carState.model[1]])?.[ carState.model[1] ] const modelDiscount = model && model.hasOwnProperty('sale') ? model.sale : 0 // Сумма скидок для выбранного цвета const colorDiscount = carState.options.color[2].find( (option) => option.color === carState.options.color[1] && option.hasOwnProperty('sale') )?.sale || 0 // Сумма скидок для выбранных колес const wheelDiscount = carState.options.wheels[2].find( (wheel) => wheel.color === carState.options.wheels[1] )?.sale || 0 // Сумма скидок для выбранного цвета салона const interiorColorDiscount = carState.options.interiorColor[2].find( (option) => option.color === carState.options.interiorColor[1] )?.sale || 0 const runningBoardsDiscount = carState.options.runningBoards[0] .filter((option) => option.show && option.hasOwnProperty('sale')) .reduce((totalDiscount, option) => totalDiscount + (option.sale || 0), 0) // Общая сумма всех скидок const to