carget
Version:
optimization js code for carget website
1,610 lines (1,415 loc) • 88.1 kB
JavaScript
const isExelLoadingFinish = false
let Google_DB = {}
let carData = {}
const customConsoleLog = async (
description,
value,
descriptionStyle,
valueStyle
) => {
await console.log(`%c${description}`, descriptionStyle, value)
}
/*
customConsoleLog(
'ДО фильтра опции колес',
carState.options.wheels[2],
'background: brown; color: white;',
'font-weight: bold;'
)
*/
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 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')
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 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) {
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'
}
})
customConsoleLog(
'ДО фильтра опции колес',
diskImage.swiper.slides.length,
'background: brown; color: white;',
'font-weight: bold;'
)
// Работа с базой - Копируем исходные опции колес
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
)
})
customConsoleLog(
'ПОСЛЕ фильтра опции колес',
diskImage.swiper.slides.length,
'background: brown; color: white;',
'font-weight: bold;'
)
// Проверяем и удаляем дубликаты слайдов
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'
).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;'
)
}
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) {
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 && diskDiametr.swiper) {
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,
})
}
// -------------------- ЦВЕТ КУЗОВА ИЗМЕНИЛСЯ
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
}
// 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')
}
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)
}
} else {
myDiskImage = colorImageCarousel.querySelector('.swiper-slide')
// Копируем src изображения перед обновлением массива
await updateImages(myDiskImage)
}
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)
} 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
? colorCarousel.swiper.slides
: ''
savedSlides.colorImageCarousel = colorImageCarousel.swiper
? colorImageCarousel.swiper.slides
: ''
savedSlides.diskDiametr = diskDiametr.swiper ? diskDiametr.swiper.slides : ''
savedSlides.diskImage = diskImage.swiper ? diskImage.swiper.slides : ''
savedSlides.colorSalon = colorSalon.swiper ? colorSalon.swiper.slides : ''
savedSlides.salonImage = salonImage.swiper ? 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 && diskDiametr.swiper) {
myArrowDiskImages = saveAllSlides(diskImage.swiper)
// updateCarouselDisk(`${carState.options.color[1]}-0`, 'updateWebsite')
// Другие операции с дисками или что угодно, что нужно выполнить, если wheels[3] === true
} else if (carState.options.wheels[3] === false || !diskDiametr.swiper) {
myArrowDiskImages = colorImageCarousel.querySelector('.swiper-slide-active')
document.querySelectorAll(arrayWrappers[1]).forEach((e) => {
e.style.display = 'none'
})
}
checkDopOtionShow()
closeAccordions()
updateButtonState(carState)
updateCheck(totalPrice)
innerPriceHeader()
innerPriceTitleModels()
updateTitlePrice('updateWebsite')
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: 'ЦВЕТ КУЗОВА:', val