UNPKG

carget

Version:

optimization js code for carget website

1,577 lines (1,391 loc) 92.6 kB
const isExelLoadingFinish = false let Google_DB = {} let carData = {} 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 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') .querySelector('.swiper') const diskImage = 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') 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' } // Создаем элемент иконки автомобиля от 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.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) => { swiper.appendSlide(slide) addedSlides.add(slide) }) } } } // Функция для фильтрации слайдов изображений дисков const filterDiskImages = () => { let selectedWheelIndex = carState.options.wheels[2][0].index diskImage.querySelectorAll('.swiper-slide img').forEach((img) => { const altValue = img.alt const expectedAltValue = `${carState.options.color[1]}-${selectedWheelIndex}` if (altValue !== expectedAltValue) { console.log('Надо удалить', altValue) } else { console.log('Надо оставить:', altValue) } }) } 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') ) console.log('Отфильтрованные опции цвета:', carState.options.color[2]) 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) { setTimeout(() => { colorCarousel.swiper.slideTo(colorCarousel.swiper.params.slidesPerView) colorCarousel.swiper.params.centeredSlides = true colorCarousel.swiper.params.slideToClickedSlide = true }, 1000) } else if (colorCarousel.swiper.slides.length <= 1) { setTimeout(() => { carState.options.color[1] = colorCarousel.querySelector( '.swiper-slide-active img' ).alt }, 1500) } else { console.log('сработал не известный сценарий checkColor') } return true } const checkWheels = (selectedModel) => { // Показать блок диски, если есть хотя бы один слайд и активирована опция колес document.querySelectorAll(arrayWrappers[1]).forEach((e) => { if (diskDiametr.swiper.slides.length >= 1 || carState.options.wheels[3]) { e.style.display = 'block' } }) // Проверяем, есть ли опции для колес if (carState.options.wheels[2][0].models) { // Копируем исходные опции колес 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(selectedColor) || option.colors.includes('All')) ) console.log('Отфильтрованные опции дисков', carState.options.wheels[2]) // Удаляем все слайды из каруселей диаметра и изображений дисков diskDiametr.swiper.removeAllSlides() diskImage.swiper.removeAllSlides() let addedDiskDiametrSlides = new Set() let addedDiskImageSlides = new Set() // Обрабатываем слайды для диаметра и изображений дисков carState.options.wheels[2].forEach((option) => { processSlidesWheelsSalon( diskDiametr.swiper, savedSlides.diskDiametr, addedDiskDiametrSlides, option, '.swiper-slide-image', selectedModel, selectedColor ) processSlidesWheelsSalon( diskImage.swiper, savedSlides.diskImage, addedDiskImageSlides, option, '.swiper-slide-image', selectedModel, selectedColor ) }) // Проверяем и удаляем дубликаты слайдов areAllSlidesWithSameAlt(diskDiametr) ? removeDuplicateSlides(diskDiametr) : '' areAllSlidesWithSameAlt(diskImage) ? removeDuplicateSlides(diskImage) : '' // Обновляем карусели diskDiametr.swiper.update() diskImage.swiper.update() } updateCaruselDisk(`${carState.options.color[1]}-0`, 'checkWheels') // Сценарии для карусели if (diskDiametr.swiper.slides.length > 1) { console.log('СЦЕНАРИЙ diskDiametr > 1') setTimeout(() => { diskDiametr.swiper.slideNext() 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) { console.log('СЦЕНАРИЙ diskDiametr <= 1') // Если один или меньше слайдов, скрываем кнопки переключения setTimeout(() => { // filterDiskImages() carState.options.wheels[1] = diskDiametr.querySelector( '.swiper-slide-active img' ).alt diskDiametr.querySelectorAll('.elementor-swiper-button').forEach((e) => { e.style.display = 'none' }) }, 1500) } else { console.log('СЦЕНАРИЙ новый') } updateCaruselDisk(`${carState.options.color[1]}-0`, 'checkWheels') return true } const checkSalon = (selectedModel) => { if (carState.options.interiorColor[2][0].models) { 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(selectedColor) || option.colors.includes('All')) ) colorSalon.swiper.removeAllSlides() salonImage.swiper.removeAllSlides() let addedColorSalonSlides = [] let addedSalonImageSlides = [] carState.options.interiorColor[2].forEach((option) => { if ( (option.models.includes(selectedModel) || option.models.includes('All')) && (option.colors.includes(selectedColor) || option.colors.includes('All')) ) { const isSlideAlreadyAdded = addedColorSalonSlides.some((slide) => slide.innerHTML.includes(`alt="${option.color}"`) ) if (!isSlideAlreadyAdded) { const filteredColorSalonSlides = savedSlides.colorSalon.filter( (slide) => slide.innerHTML.includes(`alt="${option.color}"`) ) filteredColorSalonSlides.forEach((slide) => { colorSalon.swiper.appendSlide(slide) addedColorSalonSlides.push(slide) }) } } else { option.remove() } }) carState.options.interiorColor[2].forEach((option) => { if ( (option.models.includes(selectedModel) || option.models.includes('All')) && (option.colors.includes(selectedColor) || option.colors.includes('All')) ) { const isSlideAlreadyAdded = addedSalonImageSlides.some((slide) => slide.innerHTML.includes(`alt="${option.color}"`) ) if (!isSlideAlreadyAdded) { const filteredSalonImageSlides = savedSlides.salonImage.filter( (slide) => slide.innerHTML.includes(`alt="${option.color}"`) ) filteredSalonImageSlides.forEach((slide) => { salonImage.swiper.appendSlide(slide) addedSalonImageSlides.push(slide) }) } } else { option.remove() } }) 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) { //есть изменение комплектации if (selectedModel && selectedModel !== previousModel) { previousModel = selectedModel checkColor(selectedModel) carState.options.wheels[3] === true ? checkWheels(selectedModel) : '' checkSalon(selectedModel) } //есть изменение кузова else if ( carState.options.color[1] && carState.options.color[1] !== previousColor ) { previousColor = carState.options.color[1] carState.options.wheels[3] === true ? checkWheels(selectedModel) : '' checkSalon(selectedModel) } updateTitlePrice() } //удаляем дубликаты, если один элемент 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 } // 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 updateCaruselDisk(dc, where) { console.log('updateCaruselDisk', dc, where) diskImage.swiper.enable() diskImage.swiper.removeAllSlides() // Функция, которая находит видимый слайд с определенным цветом (игнорируя индекс) function findVisibleSlides(color) { const visibleSlides = myArrowDiskImages.filter((slide) => { // Проверяем, что слайд не имеет классов "swiper-slide swiper-slide-duplicate swiper-slide-prev" return ( !slide.classList.contains('swiper-slide-duplicate') && !slide.classList.contains('swiper-slide-prev') ) }) return visibleSlides.filter((slide) => { const slideAlt = slide.querySelector('.swiper-slide-image').alt // Проверяем соответствие цвета без учета индекса в альте слайда return slideAlt.startsWith(color + '-') }) } // Получаем цвет из аргумента dc (игнорируем индекс) const color = dc.split('-')[0] // Поиск видимых слайдов с цветом исходного и цветом противоположного const exactMatchSlides = findVisibleSlides(color) exactMatchSlides.forEach((slide) => { diskImage.swiper.appendSlide(slide) }) // Обновляем карусель diskImage.swiper.update() // дополнительные операции... carState.options.wheels[0] = indexOption('wheels', 0, 'index').price carState.options.wheels[1] = indexOption('wheels', 0, 'index').color // Проверяем существование объекта diskDiametr и его свойства swiper if (diskDiametr) { // Вызываем метод slideTo для объекта diskDiametr.swiper diskDiametr.swiper.slideTo(diskDiametr.swiper.params.slidesPerView, 400) diskDiametr.swiper.update() } // Проверяем существование объекта diskImage и его свойства swiper if (diskImage) { // Вызываем метод slideTo и update для объекта diskImage.swiper diskImage.swiper.slideTo(0, 0) diskImage.swiper.update() } diskDiametr.querySelectorAll('.elementor-swiper-button').forEach((e) => { if (diskDiametr.swiper.slides.length <= 1) { e.style.display = 'none' } else { e.style.display = 'inline-flex' } }) diskImage.swiper.disable() updateTitlePrice() } 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 не найден } async function updateCarousel( carouselElement, priceElementSelector, carStateOption, dc ) { carouselElement.swiper.slideTo(dc, 400) updateTitlePrice() } // ПРОВЕРКА НА ДИСКИ ДЛЯ ВЫГРУЗКИ ФОТО АВТО, ЕСЛИ 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 () => { for (const option in arrayTitlePrice) { const selector = arrayTitlePrice[option] const optionSelect = carState.options[option][2].find( (obj) => obj.color === carState.options[option][1] ) document.querySelector(selector).innerHTML = ` <div style="display:flex;flex-direction:column;"> <span style="font-weight:700;">${carState.options[option][1]}</span> ${ optionSelect && optionSelect.sale !== undefined ? `<span style="font-weight:700; display: flex; justify-content: center; gap: 8px;">${carState.options[option][0]} руб. <del style="text-decoration-color:#D10000; font-weight:300;">${optionSelect.sale} руб.</del></span>` : `<span>${carState.options[option][0]} руб.</span>` } </div>` } // Проверка состояния колес и наличия diskImage if (carState.options.wheels[3] === true && diskImage) { // ФОТО АВТО В ЧЕК myDiskImage = diskImage.querySelector('.swiper-slide-active') 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) } } else { myDiskImage = colorImageCarousel.querySelector('.swiper-slide') // Копируем src изображения перед обновлением массива await updateImages(myDiskImage) } if ( salonImage && salonImage.swiper && salonImage.querySelector('.swiper-slide-active img') ) { arrayImagesForPDF[2] = salonImage.querySelector( '.swiper-slide-active img' )?.src } else if ( salonImage && salonImage.swiper && 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) } else { // Если не выбрано, восстановите стандартные стили button.style.backgroundColor = '#DB2424' elementorButton ? (elementorButton.style.backgroundColor = '#DB2424') : '' buttonTextElement.style.color = '#fff' } buttonTextElement.textContent = isSelected ? 'ВЫБРАНО' : 'ВЫБРАТЬ' }) }) } let updatedCarState let myPriceModels function convertCarState(carState) { const storedData = localStorage.getItem('currencyRates') const currencyRates = JSON.parse(storedData) const usdToRubExchangeRate = currencyRates.rates.USD const cnyToRubExchangeRate = currencyRates.rates.CNY // Конвертация МОДЕЛЕЙ в рубли const priceInRub = {} modelNames.forEach((modelName) => { const modelPrice = carState.models.find((model) => model[modelName]) const modelPriceCNY = modelPrice ? modelPrice[modelName].price : 0 priceInRub[modelName] = modelPriceCNY * cnyToRubExchangeRate }) // Обновление объекта состояния с новыми значениями в рублях const updatedCarState = { ...carState, models: carState.models.map((model) => { const modelName = Object.keys(model)[0] const priceInRub = model[modelName].price * cnyToRubExchangeRate return { [modelName]: priceInRub } }), 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] || 0 const total = modelPriceCNY + fromTurgartToBishkek + customs + otherExpenses + fromBishkekToRussia + labFees + recyclingFee + carLocalization const totalWithMarga = total + total * (marga / 100) return roundNumberToNChars(totalWithMarga, 4) }) 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 (carState.options.runningBoards[1]) { const options = carState.options.runningBoards[0] options.forEach((option) => { if (option.show) { total += option.price } }) } totalPrice = total return total } function roundNumberToNChars(number, n) { number = Math.ceil(number) if (typeof number !== 'number' || !Number.isInteger(number) || number < 0) { throw new Error('number должно быть положительным целым числом') } if (typeof n !== 'number' || !Number.isInteger(n) || n < 1) { throw new Error( 'n должно быть положительным целым числом больше или равным 1' ) } const multiplier = Math.pow(10, n - 1) const roundedNumber = Math.ceil(number / multiplier) * multiplier return roundedNumber } 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 totalDiscount = modelDiscount + colorDiscount + wheelDiscount + interiorColorDiscount + runningBoardsDiscount saleAll = totalDiscount return totalDiscount } // ОБНОВЛЕНИЕ ЧЕКА async function updateCheck() { totalSaleCheck() // Предполагается, что totalSaleCheck() возвращает числовое значение скидки checkDopOtionShow() checkDopOptions() if (saleAll && saleAll !== 0) { ;(discountCheck.style.display = 'flex'), (priceItemDiscount.style.display = 'flex') } else { ;(discountCheck.style.display = 'none'), (priceItemDiscount.style.display = 'none') } await sumCarPrices(updatedCarState, myPriceModels) totalpriceElements.forEach((e) => { e.innerHTML = numberWithSpaces(sumCarPrices(carState, myPriceModels)) }) // Вставляем выбранные цвета, диски и салон В ЧЕК colorKuzovaElements.forEach((element) => { element.textContent = carState.options.color[1] }) colorSalonaElements.forEach((element) => { element.textContent = carState.options.interiorColor[1] }) diskElements.forEach((element) => { element.textContent = `${carState.options.wheels[1]}` }) priceColorElements.forEach((element) => { element.textContent = ` ${numberWithSpaces(carState.options.color[0])} руб.` }) priceColorSalonElements.forEach((element) => { element.textContent = `${numberWithSpaces( carState.options.interiorColor[0] )} руб.` }) priceDiskElements.forEach((element) => { element.textContent = ` ${numberWithSpaces( carState.options.wheels[0] )} руб.` }) // Вычисляем и вставляем общую скидку totalSaleCarElements.forEach((element) => { element.innerHTML = saleAll === 0 || saleAll === null || saleAll === undefined ? 'Нет' : `${saleAll} руб.` }) // Вычисляем и вставляем общую стоимость totalPriceCarElements.forEach((element) => { element.textContent = `${numberWithSpaces( sumCarPrices(carState, myPriceModels) )} руб.` }) } function numberWithSpaces(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') } // СОХРАНЯЕМ СЛАЙДЫ АВТО ДИСКИ function saveAllSlides(carousel) { const allSlides = [] // Получите все слайды в карусели const slides = carousel.slides slides.forEach((slide) => { // Добавьте слайд в массив allSlides.push(slide) }) return allSlides } const indexColor = (value, searchBy = 'color') => { if (searchBy === 'color') { const index = carState.options.color[2].findIndex((e) => e.color === value) return index } else if (searchBy === 'index') { if (value >= 0 && value < carState.options.color[2].length) { return carState.options.color[2][value].color } } return null } const indexOption = (property, value, searchBy = 'color') => { const option = carState.options[property] if (Array.isArray(option) && option.length > 2 && Array.isArray(option[2])) { if (searchBy === 'color') { const index = option[2].findIndex((e) => e.color === value) return index } else if (searchBy === 'index') { if (value >= 0 && value < option[2].length) { return option[2][value] } } } return null } // Первое ОБНОВЛЕНИЕ САЙТА (КАРУСЕЛИ, АККОРДИОН, ШАПКА) const dopOptionsWrapper = document.querySelector( '.dop-options-wrapper .elementor-widget-wrap' ) const children = dopOptionsWrapper.children const updateWebsite = () => { cargetModels.style.display = 'flex' if (cargetModels.children.length <= 4) { cargetModels.style.flexWrap = 'nowrap' } if (cargetModels.children.length >= 5) { cargetModels.style.flexWrap = 'wrap' } // Сохраняем слайды savedSlides.colorCarousel = colorCarousel.swiper.slides savedSlides.colorImageCarousel = colorImageCarousel.swiper.slides savedSlides.diskDiametr = diskDiametr.swiper.slides savedSlides.diskImage = diskImage.swiper.slides savedSlides.colorSalon = colorSalon.swiper.slides savedSlides.salonImage = salonImage.swiper.slides //сохраняем объект стейта originalColorOptions = [...carState.options.color[2]] originalWheelsOptions = [...carState.options.wheels[2]] originalInteriorColorOptions = [...carState.options.interiorColor[2]] 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) { checkboxInput.checked = true } }) //Вычисляем количество моделей для выравнивания const m = document.querySelectorAll('#carget-models .elementor-column').length let divisor if (m === 1) { divisor = 1 } else if (m % 2 === 0) { divisor = 2 } else { divisor = 3 } document.querySelectorAll('#carget-models .elementor-column').forEach((e) => { e.style.width = `${96 / divisor}%` }) // ПРОВЕРКА НА ДИСКИ TRUE FALSE if (carState.options.wheels[3] === true) { myArrowDiskImages = saveAllSlides(diskImage.swiper) updateCaruselDisk(`${carState.options.color[1]}-0`, 'updateWebsite') // Другие операции с дисками или что угодно, что нужно выполнить, если wheels[3] === true } else if (carState.options.wheels[3] === false) { myArrowDiskImages = saveAllSlides(colorImageCarousel.swiper) document.querySelectorAll(arrayWrappers[1]).forEach((e) => { e.style.display = 'none' }) } checkDopOtionShow() closeAccordions() updateButtonState(carState) updateCheck(totalPrice) innerPriceHeader() innerPriceTitleModels() updateTitlePrice() myPDF() } // Функция для вычисления суммы с процентом без десятых function calculateWithPercentage(sum, percentage) { return roundNumberToNChars(Math.ceil(sum * (1 + percentage / 100)), 4) } const tableBody4 = data4.map((item) => [ { text: item, alignment: 'left', fontSize: 10, margin: [0, 0, 0, 8], }, ]) const myPDF = async () => { const image1 = await loadImageAsDataURL(data5[0].img) const image2 = await loadImageAsDataURL(data5[1].img) const image3 = await loadImageAsDataURL(data5[2].img) const image4 = await loadImageAsDataURL(data5[3].img) const data = [ { label: 'МОДЕЛЬ:', value: carState.model[0] }, { label: 'КОМПЛЕКТАЦИЯ:', value: carState.model[1] }, { label: 'ГОД ВЫПУСКА:', value: new Date().getFullYear() }, { label: 'ЦВЕТ КУЗОВА:', value: carState.options.color[1] }, { label: 'ДИСКИ:', value: `${carState.options.wheels[2][indexDisk].color}`, }, { label: 'ЦВЕТ САЛОНА:', value: carState.options.interiorColor[1] }, ] const totalCarPrice = roundNumberToNChars( sumCarPrices(carState, myPric