Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <script>
- // Константы для расчета стоимости
- const PRICING = {
- additionalUser: 2880, // руб за дополнительного пользователя свыше трёх
- yearlyDiscount: 0.1, // скидка 10% при годовой оплате
- basePrice: 10400 // минимальная базовая стоимость в месяц
- };
- // Тарифы для клиентов в базе
- // Цены уже включают скидку 10% от базовых тарифов
- const CLIENT_PRICING_TIERS = [
- { min: 0, max: 24999, price: 0 },
- { min: 25000, max: 49999, price: 16740 },
- { min: 50000, max: 99999, price: 28350 },
- { min: 100000, max: 149999, price: 47430 },
- { min: 150000, max: 199999, price: 62730 },
- { min: 200000, max: 249999, price: 70020 },
- { min: 250000, max: 499999, price: 78480 },
- { min: 500000, max: 999999, price: 119070 },
- { min: 1000000, max: 1499999, price: 159750 },
- { min: 1500000, max: 1999999, price: 200340 },
- { min: 2000000, max: 3999999, price: 277200 },
- { min: 4000000, max: 4999999, price: 324000 },
- { min: 5000000, max: 5999999, price: 378000 },
- { min: 6000000, max: 6999999, price: 432000 },
- { min: 7000000, max: 7999999, price: 513000 },
- { min: 8000000, max: Infinity, price: 585000 }
- ];
- // Тарифы для писем email-рассылки
- const EMAIL_PRICING_TIERS = [
- { min: 0, max: 24999, price: 0 },
- { min: 25000, max: 49999, price: 1700 },
- { min: 50000, max: 99999, price: 3500 },
- { min: 100000, max: 149999, price: 5150 },
- { min: 150000, max: 199999, price: 7550 },
- { min: 200000, max: 249999, price: 10000 },
- { min: 250000, max: 499999, price: 12600 },
- { min: 500000, max: 999999, price: 25000 },
- { min: 1000000, max: 1499999, price: 37800 },
- { min: 1500000, max: 1999999, price: 48700 },
- { min: 2000000, max: 3999999, price: 60000 },
- { min: 4000000, max: 4999999, price: 120000 },
- { min: 5000000, max: 5999999, price: 148000 },
- { min: 6000000, max: 6999999, price: 175000 },
- { min: 7000000, max: 7999999, price: 200000 },
- { min: 8000000, max: Infinity, price: 225000 }
- ];
- // Тарифы для участников программы лояльности
- // Цены уже включают скидку 10% от базовых тарифов
- const LOYALTY_PRICING_TIERS = [
- { min: 0, max: 9999, price: 0 },
- { min: 10000, max: 24999, price: 3420 },
- { min: 25000, max: 49999, price: 7380 },
- { min: 50000, max: 99999, price: 12240 },
- { min: 100000, max: 149999, price: 23400 },
- { min: 150000, max: 199999, price: 33210 },
- { min: 200000, max: 249999, price: 41040 },
- { min: 250000, max: 499999, price: 47880 },
- { min: 500000, max: 999999, price: 76230 },
- { min: 1000000, max: 1499999, price: 138780 },
- { min: 1500000, max: 1999999, price: 187650 },
- { min: 2000000, max: 3999999, price: 223740 },
- { min: 4000000, max: 4999999, price: 341010 },
- { min: 5000000, max: 5999999, price: 351810 },
- { min: 6000000, max: 6999999, price: 381060 },
- { min: 7000000, max: 7999999, price: 410400 },
- { min: 8000000, max: Infinity, price: 469080 }
- ];
- // Универсальная функция для получения цены по тарифным диапазонам
- function getPriceByTier(value, tiers) {
- const tier = tiers.find(t => value >= t.min && value <= t.max);
- return tier ? tier.price : 0;
- }
- // Получение стоимости за клиентов в базе
- const getClientPrice = (clients) => getPriceByTier(clients, CLIENT_PRICING_TIERS);
- // Получение стоимости за письма email-рассылки
- const getEmailPrice = (emails) => getPriceByTier(emails, EMAIL_PRICING_TIERS);
- // Получение стоимости за участников программы лояльности
- const getLoyaltyPrice = (loyalty) => getPriceByTier(loyalty, LOYALTY_PRICING_TIERS);
- // Получение элементов по атрибутам calc-price
- const getElement = (attr) => {
- const el = document.querySelector(`[calc-price="${attr}"]`);
- if (!el) return null;
- // Если это wrapper, ищем внутри input
- if (el.tagName !== 'INPUT') {
- const inner = el.querySelector('input');
- return inner || el;
- }
- return el;
- };
- // Форматирование числа с пробелами
- const formatNumber = (num) => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
- // Хранение последних валидных значений
- let lastValidValues = {
- users: 3,
- clients: 10000,
- emails: 10000,
- loyalty: 5000
- };
- // Конфигурация полей ввода
- const INPUT_FIELDS = [
- { name: 'users', inputAttr: 'users-input', rangeAttr: 'users-range', summaryAttr: 'summary-users' },
- { name: 'clients', inputAttr: 'clients-input', rangeAttr: 'clients-range', summaryAttr: 'summary-clients' },
- { name: 'emails', inputAttr: 'emails-input', rangeAttr: 'emails-range', summaryAttr: 'summary-emails' },
- { name: 'loyalty', inputAttr: 'loyalty-input', rangeAttr: 'loyalty-range', summaryAttr: 'summary-loyalty' }
- ];
- // Получение min/max значений из элемента
- const getInputLimits = (input) => {
- const wrapper = input.closest('[calc-price]');
- return {
- min: parseInt(wrapper.dataset.min || input.min),
- max: parseInt(wrapper.dataset.max || input.max)
- };
- };
- // Парсинг значения из строки
- const parseInputValue = (str) => {
- const cleaned = str.replace(/\s/g, '');
- return cleaned === '' ? 0 : parseInt(cleaned) || 0;
- };
- // Проверка валидности значения
- const isValidValue = (value, min, max) => {
- if (value === 0) return false;
- return value >= min && value <= max;
- };
- // Расчет базовой стоимости
- function calculateBasePrice(users, clients, emails, loyalty) {
- const baseCost = PRICING.basePrice;
- const userCost = users > 3 ? (users - 3) * PRICING.additionalUser : 0;
- const clientCost = getClientPrice(clients);
- const emailCost = getEmailPrice(emails);
- const loyaltyCost = getLoyaltyPrice(loyalty);
- return {
- total: baseCost + userCost + clientCost + emailCost + loyaltyCost,
- breakdown: {
- base: baseCost,
- users: userCost,
- clients: clientCost,
- emails: emailCost,
- loyalty: loyaltyCost
- }
- };
- }
- // Обновление итоговой стоимости
- function updatePrice() {
- // Получаем все поля ввода
- const inputs = INPUT_FIELDS.map(field => ({
- ...field,
- input: getElement(field.inputAttr),
- range: getElement(field.rangeAttr)
- }));
- // Парсим значения и проверяем валидность
- const values = inputs.map(({ input, name }) => {
- const rawValue = parseInputValue(input.value);
- const { min, max } = getInputLimits(input);
- const isValid = isValidValue(rawValue, min, max);
- input.classList.toggle('invalid', !isValid);
- return { name, value: rawValue, isValid };
- });
- // Если хотя бы одно значение невалидно, не пересчитываем цену
- if (values.some(v => !v.isValid)) {
- return;
- }
- // Сохраняем валидные значения
- values.forEach(({ name, value }) => {
- lastValidValues[name] = value;
- });
- // Извлекаем значения для расчета
- const { users, clients, emails, loyalty } = lastValidValues;
- const isYearly = getElement('billing-year').classList.contains('active');
- const priceCalculation = calculateBasePrice(users, clients, emails, loyalty);
- const basePrice = priceCalculation.total;
- let monthlyPrice = basePrice;
- let savings = 0;
- if (isYearly) {
- monthlyPrice = basePrice * (1 - PRICING.yearlyDiscount);
- savings = basePrice - monthlyPrice;
- }
- // Обновление итоговой суммы
- getElement('total').textContent = `${formatNumber(Math.round(monthlyPrice))} ₽`;
- // Обновление сводки
- values.forEach(({ name, value }) => {
- const summaryElement = getElement(INPUT_FIELDS.find(f => f.name === name).summaryAttr);
- summaryElement.textContent = name === 'users' ? value : formatNumber(value);
- });
- // Обновление экономии
- const savingWrapper = getElement('saving-wrapper');
- if (isYearly && savings > 0) {
- getElement('saving-month').textContent = `${formatNumber(Math.round(savings))} ₽`;
- getElement('saving-year').textContent = `${formatNumber(Math.round(savings * 12))} ₽`;
- savingWrapper.classList.remove('hidden');
- } else {
- savingWrapper.classList.add('hidden');
- }
- }
- // Применение корректного значения к полю ввода
- const applyCorrectValue = (input, range, value) => {
- input.value = formatNumber(value);
- range.value = value;
- input.classList.add('reset-success');
- setTimeout(() => input.classList.remove('reset-success'), 400);
- };
- // Синхронизация слайдера и поля ввода
- function syncInputAndRange(inputAttr, rangeAttr) {
- const input = getElement(inputAttr);
- const range = getElement(rangeAttr);
- if (!input || !range) return;
- // Получаем реальные лимиты из wrapper (если они там)
- const limits = getInputLimits(range);
- const min = Number.isNaN(limits.min) ? 0 : limits.min;
- const max = Number.isNaN(limits.max) ? 100 : limits.max;
- // Устанавливаем min/max прямо на <input type="range"> чтобы браузер работал корректно
- range.min = min;
- range.max = max;
- // Приводим значение range к корректному числу (в пределах)
- const initialRaw = parseInt(input.value.toString().replace(/\s/g, '')) || min;
- range.value = Math.min(Math.max(initialRaw, min), max);
- // Синхронизация слайдера -> поле ввода
- range.addEventListener('input', () => {
- const numValue = parseInt(range.value);
- input.value = formatNumber(numValue);
- input.classList.remove('invalid');
- updatePrice();
- });
- // Синхронизация поля ввода -> слайдер
- input.addEventListener('input', function () {
- let rawValue = this.value.replace(/[^\d]/g, '');
- // Ограничиваем максимальное количество цифр до 8
- if (rawValue.length > 8) {
- rawValue = rawValue.substring(0, 8);
- }
- const value = parseInt(rawValue) || 0;
- this.value = rawValue === '' ? '' : formatNumber(value);
- // Обновляем слайдер только если значение в пределах и не равно 0
- if (value >= min && value <= max && value !== 0) {
- range.value = value;
- }
- this.classList.toggle('invalid', !(value >= min && value <= max && value !== 0));
- updatePrice();
- });
- // При потере фокуса проверяем валидность и исправляем некорректные значения
- input.addEventListener('blur', function () {
- const rawValue = this.value.replace(/[^\d]/g, '');
- // Если поле пустое или значение некорректно, устанавливаем минимальное значение
- if (rawValue === '' || rawValue === '0') {
- applyCorrectValue(this, range, min);
- updatePrice();
- return;
- }
- const value = parseInt(rawValue);
- // Если значение меньше минимума или больше максимума, устанавливаем минимальное
- if (value < min || value > max) {
- applyCorrectValue(this, range, min);
- } else {
- // всё в порядке — синхронизируем range
- range.value = value;
- }
- updatePrice();
- });
- }
- // Инициализация переключателя тарифов
- function initBillingTabs() {
- const yearTab = getElement('billing-year');
- const monthTab = getElement('billing-month');
- const switchTab = (activeTab, inactiveTab) => {
- activeTab.classList.add('active');
- inactiveTab.classList.remove('active');
- updatePrice();
- };
- yearTab.addEventListener('click', () => switchTab(yearTab, monthTab));
- monthTab.addEventListener('click', () => switchTab(monthTab, yearTab));
- }
- // Инициализация всех слайдеров и полей ввода
- function initInputs() {
- INPUT_FIELDS.forEach(({ inputAttr, rangeAttr }) => {
- syncInputAndRange(inputAttr, rangeAttr);
- });
- }
- // Инициализация при загрузке страницы
- document.addEventListener('DOMContentLoaded', () => {
- initInputs();
- initBillingTabs();
- // Форматируем начальные значения в полях ввода
- INPUT_FIELDS.forEach(({ inputAttr }) => {
- const input = getElement(inputAttr);
- if (input) {
- const rawValue = input.value.replace(/[^\d]/g, '');
- const { min } = getInputLimits(input);
- const value = parseInt(rawValue) || min;
- input.value = formatNumber(value);
- }
- });
- updatePrice();
- });
- </script>
Advertisement
Add Comment
Please, Sign In to add comment