carget
Version:
optimization js code for carget website
1,556 lines (1,373 loc) • 71.5 kB
JavaScript
// ..... импорты ..... //
let savedSlides = {}
let originalColorOptions = {}
let originalWheelsOptions = {}
let originalInteriorColorOptions = {}
let previousModel = null
let saleAll = 0
let pdfDoc
let totalPrice = 0
let indexDisk = 0
let myArrowDiskImages
let myDiskImage, mySalonImage
let isUpdatingCarousel = false
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 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')
async function updateOptions(selectedModel) {
if (selectedModel !== previousModel) {
// Модель изменилась, выполняем обновление опций
previousModel = selectedModel
carState.options.color[2] = originalColorOptions
carState.options.wheels[2] = [...originalWheelsOptions]
carState.options.interiorColor[2] = [...originalInteriorColorOptions]
carState.options.color[2] = carState.options.color[2].filter((option) =>
option.models.includes(selectedModel)
)
carState.options.wheels[2] = carState.options.wheels[2].filter((option) =>
option.models.includes(selectedModel)
)
carState.options.interiorColor[2] = originalInteriorColorOptions.filter(
(option) => option.models.includes(selectedModel)
)
colorCarousel.swiper.removeAllSlides()
colorImageCarousel.swiper.removeAllSlides()
diskDiametr.swiper.removeAllSlides()
colorSalon.swiper.removeAllSlides()
salonImage.swiper.removeAllSlides()
// Создаем пустые массивы для отслеживания уже добавленных слайдов для всех каруселей
let addedColorSlides = []
let addedColorImageSlides = []
let addedDiskDiametrSlides = []
let addedColorSalonSlides = []
let addedSalonImageSlides = []
// Проходим по каждой опции цвета кузова
carState.options.color[2].forEach((option) => {
if (option.models.includes(selectedModel)) {
const isSlideAlreadyAdded = addedColorSlides.some((slide) =>
slide.innerHTML.includes(`alt="${option.color}"`)
)
if (!isSlideAlreadyAdded) {
const filteredColorSlides = savedSlides.colorCarousel.filter(
(slide) => slide.innerHTML.includes(`alt="${option.color}"`)
)
filteredColorSlides.forEach((slide) => {
colorCarousel.swiper.appendSlide(slide)
addedColorSlides.push(slide)
})
}
}
})
// Проходим по каждой опции цвета кузова
carState.options.color[2].forEach((option) => {
if (option.models.includes(selectedModel)) {
// Фильтруем сохраненные слайды по цвету для colorImageCarousel
const filteredColorImageSlides = savedSlides.colorImageCarousel.filter(
(slide) => slide.innerHTML.includes(`alt="${option.color}"`)
)
// Создаем объект для хранения уникальных значений alt
const uniqueAlts = {}
// Проходимся по каждому отфильтрованному слайду
filteredColorImageSlides.forEach((slide) => {
// Получаем значение атрибута alt изображения
const alt = slide
.querySelector('.swiper-slide-image')
.getAttribute('alt')
// Если такого alt еще нет в объекте uniqueAlts, добавляем его в объект
if (!(alt in uniqueAlts)) {
uniqueAlts[alt] = true // Используем значение true для указания на уникальность
} else {
// Если такой alt уже есть, удаляем текущий слайд из DOM
slide.remove()
}
})
// Перебираем отфильтрованные и отфильтрованные от дубликатов слайды и добавляем их в карусель
filteredColorImageSlides.forEach((slide) => {
// Проверяем, добавлен ли уже этот слайд
if (!addedColorImageSlides.includes(slide)) {
colorImageCarousel.swiper.appendSlide(slide)
addedColorImageSlides.push(slide)
}
})
}
})
// Проходим по каждой опции диаметра диска
carState.options.wheels[2].forEach((option) => {
if (option.models.includes(selectedModel)) {
const isSlideAlreadyAdded = addedDiskDiametrSlides.some((slide) =>
slide.innerHTML.includes(`alt="${option.color}"`)
)
if (!isSlideAlreadyAdded) {
const filteredDiskDiametrSlides = savedSlides.diskDiametr.filter(
(slide) => slide.innerHTML.includes(`alt="${option.color}"`)
)
filteredDiskDiametrSlides.forEach((slide) => {
diskDiametr.swiper.appendSlide(slide)
addedDiskDiametrSlides.push(slide)
})
}
}
})
// Проходим по каждой опции цвета салона
carState.options.interiorColor[2].forEach((option) => {
if (option.models.includes(selectedModel)) {
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)
})
}
}
})
// Проходим по каждой опции салона
carState.options.interiorColor[2].forEach((option) => {
if (option.models.includes(selectedModel)) {
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)
})
}
}
})
if (colorCarousel.swiper.slides.length > 1) {
setTimeout(() => {
// Обновляем все карусели
colorCarousel.swiper.params.centeredSlides = true
colorCarousel.swiper.params.slideToClickedSlide = true
colorCarousel.swiper.update()
colorImageCarousel.swiper.update()
diskDiametr.swiper.update()
diskImage.swiper.update()
colorSalon.swiper.update()
salonImage.swiper.update()
colorCarousel.swiper.slideTo(colorCarousel.swiper.params.slidesPerView)
}, 1000)
}
if (colorSalon.swiper.slides.length > 1) {
colorSalon.swiper.slideTo(colorSalon.swiper.params.slidesPerView)
colorSalon.swiper.params.centeredSlides = true
colorSalon.swiper.params.slideToClickedSlide = true
colorSalon.swiper.update()
salonImage.swiper.update()
}
areAllSlidesWithSameAlt(colorCarousel)
? removeDuplicateSlides(colorCarousel)
: ''
areAllSlidesWithSameAlt(colorImageCarousel)
? removeDuplicateSlides(colorImageCarousel)
: ''
areAllSlidesWithSameAlt(diskDiametr)
? removeDuplicateSlides(diskDiametr)
: ''
areAllSlidesWithSameAlt(colorSalon) ? removeDuplicateSlides(colorSalon) : ''
areAllSlidesWithSameAlt(salonImage) ? removeDuplicateSlides(salonImage) : ''
if (colorCarousel.swiper.slides.length <= 1) {
colorCarousel
.querySelectorAll('.elementor-swiper-button')
.forEach((e) => {
e.style.display = 'none'
})
} else {
colorCarousel
.querySelectorAll('.elementor-swiper-button')
.forEach((e) => {
e.style.display = 'inline-flex'
})
}
if (diskDiametr.swiper.slides.length <= 1) {
diskDiametr.querySelectorAll('.elementor-swiper-button').forEach((e) => {
e.style.display = 'none'
})
} else {
diskDiametr.querySelectorAll('.elementor-swiper-button').forEach((e) => {
e.style.display = 'inline-flex'
})
}
if (colorSalon.swiper.slides.length <= 1) {
colorSalon.querySelectorAll('.elementor-swiper-button').forEach((e) => {
e.style.display = 'none'
})
} else {
colorSalon.querySelectorAll('.elementor-swiper-button').forEach((e) => {
e.style.display = 'inline-flex'
})
}
await updateTitlePrice()
}
}
function removeDuplicateSlides(carouselElement) {
// Создаем объект для хранения уникальных значений alt
const uniqueAlts = {}
// Получаем все слайды карусели
const slides = carouselElement.querySelectorAll('.swiper-slide')
// Проходимся по каждому слайду
slides.forEach((slide) => {
// Получаем значение атрибута alt изображения
const alt = slide.querySelector('.swiper-slide-image').getAttribute('alt')
// Если такого alt еще нет в объекте uniqueAlts, добавляем его в объект
if (!(alt in uniqueAlts)) {
uniqueAlts[alt] = true // Используем значение true для указания на уникальность
} else {
// Если такой alt уже есть, удаляем текущий слайд из DOM
slide.remove()
}
})
}
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) {
console.log('dc', dc)
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, 0)
diskDiametr.swiper.update()
}
// Проверяем существование объекта diskImage и его свойства swiper
if (diskImage) {
// Вызываем метод slideTo и update для объекта diskImage.swiper
diskImage.swiper.slideTo(1, 0)
diskImage.swiper.update()
}
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) {
arrayImagesForPDF[2] = salonImage
.querySelector('.swiper-slide-active img')
.getAttribute('src')
} else {
arrayImagesForPDF[2] = salonImage
.querySelector('.swiper-slide img')
.getAttribute('src')
}
updateCheck()
}
//Диски
document.addEventListener('DOMContentLoaded', async (event) => {
// Ожидаем, пока слайдер будет инициализирован
const waitForSlider = () => {
return new Promise((resolve) => {
const checkSlider = () => {
if (colorCarousel && colorCarousel.swiper) {
resolve()
} else {
// Если слайдер ещё не готов, проверяем снова через небольшой интервал
setTimeout(checkSlider, 100)
}
}
checkSlider()
})
}
// Дожидаемся инициализации САЙТА
await waitForSlider()
})
// Показать или скрыть ДОП ОПЦИИ
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}`)
// Установка состояния чекбокса в зависимости от условий
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 = () => {
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)
}
}
document.addEventListener('DOMContentLoaded', async (event) => {
updateDopOptions()
})
// РАБОТА С МОДЕЛЯМИ
// ---- НОВЫЙ МЕТОД --------
// Определение функции 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
}
// Получаем все модели из carState.models
const modelNames = carState.models.map((model) => Object.keys(model)[0])
// Определяем элементы для каждой модели
const modelElements = defineModelElements(carState.models)
// Создаем динамически divPriceModel для каждой модели
const divPriceModels = {}
modelNames.forEach((modelName) => {
divPriceModels[modelName] = createDivPriceModel(modelName)
})
// ЗАКРЫТОЕ ПОЛОЖЕНИЕ АККОРДИОНОВ
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
console.log('carrensy', usdToRubExchangeRate, cnyToRubExchangeRate)
// Конвертация МОДЕЛЕЙ в рубли
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[0] / 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 updateWebsite = () => {
// Сохраняем слайды
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]]
//Вычисляем количество моделей для выравнивания
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`)
// Другие операции с дисками или что угодно, что нужно выполнить, если wheels[3] === true
} else if (
carState.options.wheels[3] === false &&
colorImageCarousel.swiper
) {
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, myPriceModels),
4
)
const data2 = [
saleAll && saleAll !== 0
? {
label: 'СУММАРНАЯ СКИДКА:',
value: `${saleAll} руб.`,
}
: '',
{
label:
saleAll && saleAll !== 0
? 'ИТОГОВАЯ СТОИМОСТЬ\nс учетом скидки:'
: 'ИТОГОВАЯ СТОИМОСТЬ',
value: `${numberWithSpaces(totalCarPrice)} руб.`,
},
{
label: 'СТОИМОСТЬ Б/Н БЕЗ НДС:',
value: `${numberWithSpaces(
calculateWithPercentage(totalCarPrice, carState.marga[1]) + 40000
)} руб.`,
},
{
label: 'СТОИМОСТЬ Б/Н С НДС 20%:',
value: `${numberWithSpaces(
calculateWithPercentage(totalCarPrice, carState.marga[2]) + 40000
)} руб.`,
},
{ label: 'СРОК ПОСТАВКИ:', value: '45-60 ДНЕЙ' },
]
// Ваш массив данных
const runningBoards = carState.options.runningBoards[0]
// Вывод доп опций
const filteredLabels = runningBoards.filter((label) => label.show)
const optionsText = filteredLabels.map((label) => label.title).join(', ')
// Получаем списки с классами accordion-ul-items-complect и accordion-ul-items-description
const complectList = modelElements[
carState.model[1]
].model[0].querySelectorAll('.accordion-ul-items-complect li')
const descriptionList = modelElements[
carState.model[1]
].model[0].querySelectorAll('.accordion-ul-items-description li')
// Функция для извлечения текста из элементов списка и создания массива данных
function extractTextFromList(list) {
return Array.from(list).map((item) => {
const text = item.innerHTML.trim()
const cleanedText = text
.replace(/<[^>]*>/g, '') // Удаляем все HTML-теги и их содержимое
.replace(/\n+/g, ' ') // Заменяем все переносы строк на пробелы
.replace(/\s+/g, ' ') // Заменяем последовательности пробелов на одиночные пробелы
return cleanedText.trim() // Удаляем пробелы в начале и конце строки
})
}
// Извлекаем текст из списков
const complectData = extractTextFromList(complectList)
const descriptionData = extractTextFromList(descriptionList)
const tableBodycomplectData = complectData.map((item) => [
{
text: item,
alignment: 'left',
fontSize: 10,
margin: [0, 0, 0, 8],
},
])
const tableBodydescriptionData = descriptionData.map((item) => [
{
text: item,
alignment: 'left',
fontSize: 10,
margin: [0, 0, 0, 8],
},
])
const tableBody = data.map(({ label, value }) => [
{
text: label,
alignment: 'left',
border: [0, 0, 0, 0],
fontSize: 10,
margin: [2, 4, 2, 4],
},
{
text: value,
alignment: 'left',
border: [0, 0, 0, 0],
margin: [-16, 4, 2, 4],
fontSize: 10,
bold: true,
},
])
const tableBody2 = data2.map(({ label, value }) => [
{
text: label,
alignment: 'left',
border: [0, 0, 0, 0],
fontSize: 10,
margin: [0, 0, -64, 0],
},
{
text: value,
alignment: 'left',
border: [0, 0, 0, 0],
fontSize: 10,
bold: true,
margin: [40, 0, 0, 0],
},
])
var pdfContent = {
defaultFileName: `${carState.model[0]} ${carState.model[1]}.pdf`,
pageOrientation: 'portrait',
pageSize: {
width: 600,
height: 1000,
},
pageMargins: [32, 64, 32, 64],
info: {
title: `${carState.model[0]} ${carState.model[1]}`,
author: 'CARGET',
keywords: ['CARGET', `${carState.model[0]} ${carState.model[1]}`],
},
header: {
columns: [
{
image: await loadImageAsDataURL(arrayImagesForPDF[0]),
width: 196,
height: 40,
margin: [16, 16, 0, 0],
},
{
text: 'CARGET.SU\nГ. МОСКВА, УЛ. КУЛАКОВА Д.20 К1, ОФ.213\nООО «ВОСТОКИМПОРТ»',
alignment: 'right',
margin: [0, 16, 16, 0],
height: 40,
fontSize: 10,
},
],
},
footer: {
columns: [
{ text: 'CARGET.SU', alignment: 'left' },
{ text: 'ООО «ВОСТОКИМПОРТ»', alignment: 'center' },
{ text: '+7 996 410-01-87', alignment: 'right' },
],
margin: [16, 16, 16, 16],
},
content: [
{
canvas: [
{
type: 'rect',
x: -16,
y: 0,
w: 563, // Ширина прямоугольника (можете настроить под ваш макет)
h: 0.5, // Высота прямоугольника (толщина линии)
color: '#000000', // Цвет черной полосы
},
],
},
{
text: 'КОММЕРЧЕСКОЕ ПРЕДЛОЖЕНИЕ',
fontSize: 22,
alignment: 'center',
margin: [32, 32, 32, 8],
bold: true,
},
{
text: `${carState.model[0]} ${carState.model[1]}`,
fontSize: 18,
margin: [0, 0, 0, 8],
alignment: 'center',
bold: true,
},
{
text: 'Предлагаем Вам рассмотреть возможность приобретения данного автомобиля.\nС комплектацией, стоимостью и условиями Вы можете ознакомиться ниже',
fontSize: 10,
margin: [0, 8, 0, 32],
alignment: 'left',
},
// КАРТОЧКИ - ЧЕК
{
columns: [
{
margin: [0, 0, 0, 0],
table: {
widths: [120, 120],
body: tableBody,
},
},
{
margin: [0, 0, 0, 0],
table: {
widths: ['*', '*'],
marginTop: -100,
body: [
[
{
text: 'ДОП. ОПЦИИ:',
alignment: 'left',
border: [0, 0, 0, 0],
fontSize: 9,
margin: [4, 4, 0, 4],
},
{
border: [0, 0, 0, 0],
text: '',
},
],
[
{
text: '',
alignment: 'left',
border: [0, 0, 0, 0],
fontSize: 9,
margin: [4, 4, 4, 4],
},
{
fontSize: 10,
border: [0, 0, 0, 0],
stack:
optionsText !== ''
? [{ text: optionsText, border: [0, 0, 0, 0] }]
: [{ text: 'Нет', border: [0, 0, 0, 0] }],
margin: [-130, 0, 4, 4],
},
],
...tableBody2,
[
{
text: '',
border: [0, 0, 0, 0],
margin: [0, 26, 0, 0],