Igor150195

калькулятор

Dec 3rd, 2025
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <script>
  2. // Константы для расчета стоимости
  3. const PRICING = {
  4.     additionalUser: 2880, // руб за дополнительного пользователя свыше трёх
  5.     yearlyDiscount: 0.1, // скидка 10% при годовой оплате
  6.     basePrice: 10400 // минимальная базовая стоимость в месяц
  7. };
  8.  
  9. // Тарифы для клиентов в базе
  10. // Цены уже включают скидку 10% от базовых тарифов
  11. const CLIENT_PRICING_TIERS = [
  12.     { min: 0, max: 24999, price: 0 },
  13.     { min: 25000, max: 49999, price: 16740 },
  14.     { min: 50000, max: 99999, price: 28350 },
  15.     { min: 100000, max: 149999, price: 47430 },
  16.     { min: 150000, max: 199999, price: 62730 },
  17.     { min: 200000, max: 249999, price: 70020 },
  18.     { min: 250000, max: 499999, price: 78480 },
  19.     { min: 500000, max: 999999, price: 119070 },
  20.     { min: 1000000, max: 1499999, price: 159750 },
  21.     { min: 1500000, max: 1999999, price: 200340 },
  22.     { min: 2000000, max: 3999999, price: 277200 },
  23.     { min: 4000000, max: 4999999, price: 324000 },
  24.     { min: 5000000, max: 5999999, price: 378000 },
  25.     { min: 6000000, max: 6999999, price: 432000 },
  26.     { min: 7000000, max: 7999999, price: 513000 },
  27.     { min: 8000000, max: Infinity, price: 585000 }
  28. ];
  29.  
  30. // Тарифы для писем email-рассылки
  31. const EMAIL_PRICING_TIERS = [
  32.     { min: 0, max: 24999, price: 0 },
  33.     { min: 25000, max: 49999, price: 1700 },
  34.     { min: 50000, max: 99999, price: 3500 },
  35.     { min: 100000, max: 149999, price: 5150 },
  36.     { min: 150000, max: 199999, price: 7550 },
  37.     { min: 200000, max: 249999, price: 10000 },
  38.     { min: 250000, max: 499999, price: 12600 },
  39.     { min: 500000, max: 999999, price: 25000 },
  40.     { min: 1000000, max: 1499999, price: 37800 },
  41.     { min: 1500000, max: 1999999, price: 48700 },
  42.     { min: 2000000, max: 3999999, price: 60000 },
  43.     { min: 4000000, max: 4999999, price: 120000 },
  44.     { min: 5000000, max: 5999999, price: 148000 },
  45.     { min: 6000000, max: 6999999, price: 175000 },
  46.     { min: 7000000, max: 7999999, price: 200000 },
  47.     { min: 8000000, max: Infinity, price: 225000 }
  48. ];
  49.  
  50. // Тарифы для участников программы лояльности
  51. // Цены уже включают скидку 10% от базовых тарифов
  52. const LOYALTY_PRICING_TIERS = [
  53.     { min: 0, max: 9999, price: 0 },
  54.     { min: 10000, max: 24999, price: 3420 },
  55.     { min: 25000, max: 49999, price: 7380 },
  56.     { min: 50000, max: 99999, price: 12240 },
  57.     { min: 100000, max: 149999, price: 23400 },
  58.     { min: 150000, max: 199999, price: 33210 },
  59.     { min: 200000, max: 249999, price: 41040 },
  60.     { min: 250000, max: 499999, price: 47880 },
  61.     { min: 500000, max: 999999, price: 76230 },
  62.     { min: 1000000, max: 1499999, price: 138780 },
  63.     { min: 1500000, max: 1999999, price: 187650 },
  64.     { min: 2000000, max: 3999999, price: 223740 },
  65.     { min: 4000000, max: 4999999, price: 341010 },
  66.     { min: 5000000, max: 5999999, price: 351810 },
  67.     { min: 6000000, max: 6999999, price: 381060 },
  68.     { min: 7000000, max: 7999999, price: 410400 },
  69.     { min: 8000000, max: Infinity, price: 469080 }
  70. ];
  71.  
  72. // Универсальная функция для получения цены по тарифным диапазонам
  73. function getPriceByTier(value, tiers) {
  74.     const tier = tiers.find(t => value >= t.min && value <= t.max);
  75.     return tier ? tier.price : 0;
  76. }
  77.  
  78. // Получение стоимости за клиентов в базе
  79. const getClientPrice = (clients) => getPriceByTier(clients, CLIENT_PRICING_TIERS);
  80.  
  81. // Получение стоимости за письма email-рассылки
  82. const getEmailPrice = (emails) => getPriceByTier(emails, EMAIL_PRICING_TIERS);
  83.  
  84. // Получение стоимости за участников программы лояльности
  85. const getLoyaltyPrice = (loyalty) => getPriceByTier(loyalty, LOYALTY_PRICING_TIERS);
  86.  
  87. // Получение элементов по атрибутам calc-price
  88. const getElement = (attr) => {
  89.     const el = document.querySelector(`[calc-price="${attr}"]`);
  90.     if (!el) return null;
  91.  
  92.     // Если это wrapper, ищем внутри input
  93.     if (el.tagName !== 'INPUT') {
  94.         const inner = el.querySelector('input');
  95.         return inner || el;
  96.     }
  97.  
  98.     return el;
  99. };
  100.  
  101.  
  102. // Форматирование числа с пробелами
  103. const formatNumber = (num) => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
  104.  
  105. // Хранение последних валидных значений
  106. let lastValidValues = {
  107.     users: 3,
  108.     clients: 10000,
  109.     emails: 10000,
  110.     loyalty: 5000
  111. };
  112.  
  113. // Конфигурация полей ввода
  114. const INPUT_FIELDS = [
  115.     { name: 'users', inputAttr: 'users-input', rangeAttr: 'users-range', summaryAttr: 'summary-users' },
  116.     { name: 'clients', inputAttr: 'clients-input', rangeAttr: 'clients-range', summaryAttr: 'summary-clients' },
  117.     { name: 'emails', inputAttr: 'emails-input', rangeAttr: 'emails-range', summaryAttr: 'summary-emails' },
  118.     { name: 'loyalty', inputAttr: 'loyalty-input', rangeAttr: 'loyalty-range', summaryAttr: 'summary-loyalty' }
  119. ];
  120.  
  121. // Получение min/max значений из элемента
  122. const getInputLimits = (input) => {
  123.     const wrapper = input.closest('[calc-price]');
  124.     return {
  125.         min: parseInt(wrapper.dataset.min || input.min),
  126.         max: parseInt(wrapper.dataset.max || input.max)
  127.     };
  128. };
  129.  
  130.  
  131. // Парсинг значения из строки
  132. const parseInputValue = (str) => {
  133.     const cleaned = str.replace(/\s/g, '');
  134.     return cleaned === '' ? 0 : parseInt(cleaned) || 0;
  135. };
  136.  
  137. // Проверка валидности значения
  138. const isValidValue = (value, min, max) => {
  139.     if (value === 0) return false;
  140.     return value >= min && value <= max;
  141. };
  142.  
  143. // Расчет базовой стоимости
  144. function calculateBasePrice(users, clients, emails, loyalty) {
  145.     const baseCost = PRICING.basePrice;
  146.     const userCost = users > 3 ? (users - 3) * PRICING.additionalUser : 0;
  147.     const clientCost = getClientPrice(clients);
  148.     const emailCost = getEmailPrice(emails);
  149.     const loyaltyCost = getLoyaltyPrice(loyalty);
  150.  
  151.     return {
  152.         total: baseCost + userCost + clientCost + emailCost + loyaltyCost,
  153.         breakdown: {
  154.             base: baseCost,
  155.             users: userCost,
  156.             clients: clientCost,
  157.             emails: emailCost,
  158.             loyalty: loyaltyCost
  159.         }
  160.     };
  161. }
  162.  
  163. // Обновление итоговой стоимости
  164. function updatePrice() {
  165.     // Получаем все поля ввода
  166.     const inputs = INPUT_FIELDS.map(field => ({
  167.         ...field,
  168.         input: getElement(field.inputAttr),
  169.         range: getElement(field.rangeAttr)
  170.     }));
  171.  
  172.     // Парсим значения и проверяем валидность
  173.     const values = inputs.map(({ input, name }) => {
  174.         const rawValue = parseInputValue(input.value);
  175.         const { min, max } = getInputLimits(input);
  176.         const isValid = isValidValue(rawValue, min, max);
  177.  
  178.         input.classList.toggle('invalid', !isValid);
  179.  
  180.         return { name, value: rawValue, isValid };
  181.     });
  182.  
  183.     // Если хотя бы одно значение невалидно, не пересчитываем цену
  184.     if (values.some(v => !v.isValid)) {
  185.         return;
  186.     }
  187.  
  188.     // Сохраняем валидные значения
  189.     values.forEach(({ name, value }) => {
  190.         lastValidValues[name] = value;
  191.     });
  192.  
  193.     // Извлекаем значения для расчета
  194.     const { users, clients, emails, loyalty } = lastValidValues;
  195.  
  196.     const isYearly = getElement('billing-year').classList.contains('active');
  197.     const priceCalculation = calculateBasePrice(users, clients, emails, loyalty);
  198.     const basePrice = priceCalculation.total;
  199.  
  200.     let monthlyPrice = basePrice;
  201.     let savings = 0;
  202.  
  203.     if (isYearly) {
  204.         monthlyPrice = basePrice * (1 - PRICING.yearlyDiscount);
  205.         savings = basePrice - monthlyPrice;
  206.     }
  207.  
  208.     // Обновление итоговой суммы
  209.     getElement('total').textContent = `${formatNumber(Math.round(monthlyPrice))} ₽`;
  210.  
  211.     // Обновление сводки
  212.     values.forEach(({ name, value }) => {
  213.         const summaryElement = getElement(INPUT_FIELDS.find(f => f.name === name).summaryAttr);
  214.         summaryElement.textContent = name === 'users' ? value : formatNumber(value);
  215.     });
  216.  
  217.     // Обновление экономии
  218.     const savingWrapper = getElement('saving-wrapper');
  219.     if (isYearly && savings > 0) {
  220.         getElement('saving-month').textContent = `${formatNumber(Math.round(savings))} ₽`;
  221.         getElement('saving-year').textContent = `${formatNumber(Math.round(savings * 12))} ₽`;
  222.         savingWrapper.classList.remove('hidden');
  223.     } else {
  224.         savingWrapper.classList.add('hidden');
  225.     }
  226. }
  227.  
  228. // Применение корректного значения к полю ввода
  229. const applyCorrectValue = (input, range, value) => {
  230.     input.value = formatNumber(value);
  231.     range.value = value;
  232.     input.classList.add('reset-success');
  233.     setTimeout(() => input.classList.remove('reset-success'), 400);
  234. };
  235.  
  236. // Синхронизация слайдера и поля ввода
  237. function syncInputAndRange(inputAttr, rangeAttr) {
  238.     const input = getElement(inputAttr);
  239.     const range = getElement(rangeAttr);
  240.  
  241.     if (!input || !range) return;
  242.  
  243.     // Получаем реальные лимиты из wrapper (если они там)
  244.     const limits = getInputLimits(range);
  245.     const min = Number.isNaN(limits.min) ? 0 : limits.min;
  246.     const max = Number.isNaN(limits.max) ? 100 : limits.max;
  247.  
  248.     // Устанавливаем min/max прямо на <input type="range"> чтобы браузер работал корректно
  249.     range.min = min;
  250.     range.max = max;
  251.  
  252.     // Приводим значение range к корректному числу (в пределах)
  253.     const initialRaw = parseInt(input.value.toString().replace(/\s/g, '')) || min;
  254.     range.value = Math.min(Math.max(initialRaw, min), max);
  255.  
  256.     // Синхронизация слайдера -> поле ввода
  257.     range.addEventListener('input', () => {
  258.         const numValue = parseInt(range.value);
  259.         input.value = formatNumber(numValue);
  260.         input.classList.remove('invalid');
  261.         updatePrice();
  262.     });
  263.  
  264.     // Синхронизация поля ввода -> слайдер
  265.     input.addEventListener('input', function () {
  266.         let rawValue = this.value.replace(/[^\d]/g, '');
  267.  
  268.         // Ограничиваем максимальное количество цифр до 8
  269.         if (rawValue.length > 8) {
  270.             rawValue = rawValue.substring(0, 8);
  271.         }
  272.  
  273.         const value = parseInt(rawValue) || 0;
  274.  
  275.         this.value = rawValue === '' ? '' : formatNumber(value);
  276.  
  277.         // Обновляем слайдер только если значение в пределах и не равно 0
  278.         if (value >= min && value <= max && value !== 0) {
  279.             range.value = value;
  280.         }
  281.  
  282.         this.classList.toggle('invalid', !(value >= min && value <= max && value !== 0));
  283.         updatePrice();
  284.     });
  285.  
  286.     // При потере фокуса проверяем валидность и исправляем некорректные значения
  287.     input.addEventListener('blur', function () {
  288.         const rawValue = this.value.replace(/[^\d]/g, '');
  289.         // Если поле пустое или значение некорректно, устанавливаем минимальное значение
  290.         if (rawValue === '' || rawValue === '0') {
  291.             applyCorrectValue(this, range, min);
  292.             updatePrice();
  293.             return;
  294.         }
  295.  
  296.         const value = parseInt(rawValue);
  297.  
  298.         // Если значение меньше минимума или больше максимума, устанавливаем минимальное
  299.         if (value < min || value > max) {
  300.             applyCorrectValue(this, range, min);
  301.         } else {
  302.             // всё в порядке — синхронизируем range
  303.             range.value = value;
  304.         }
  305.  
  306.         updatePrice();
  307.     });
  308. }
  309.  
  310. // Инициализация переключателя тарифов
  311. function initBillingTabs() {
  312.     const yearTab = getElement('billing-year');
  313.     const monthTab = getElement('billing-month');
  314.  
  315.     const switchTab = (activeTab, inactiveTab) => {
  316.         activeTab.classList.add('active');
  317.         inactiveTab.classList.remove('active');
  318.         updatePrice();
  319.     };
  320.  
  321.     yearTab.addEventListener('click', () => switchTab(yearTab, monthTab));
  322.     monthTab.addEventListener('click', () => switchTab(monthTab, yearTab));
  323. }
  324.  
  325. // Инициализация всех слайдеров и полей ввода
  326. function initInputs() {
  327.     INPUT_FIELDS.forEach(({ inputAttr, rangeAttr }) => {
  328.         syncInputAndRange(inputAttr, rangeAttr);
  329.     });
  330. }
  331.  
  332. // Инициализация при загрузке страницы
  333. document.addEventListener('DOMContentLoaded', () => {
  334.     initInputs();
  335.     initBillingTabs();
  336.  
  337.     // Форматируем начальные значения в полях ввода
  338.     INPUT_FIELDS.forEach(({ inputAttr }) => {
  339.         const input = getElement(inputAttr);
  340.         if (input) {
  341.             const rawValue = input.value.replace(/[^\d]/g, '');
  342.             const { min } = getInputLimits(input);
  343.             const value = parseInt(rawValue) || min;
  344.             input.value = formatNumber(value);
  345.         }
  346.     });
  347.  
  348.     updatePrice();
  349. });
  350. </script>
Advertisement
Add Comment
Please, Sign In to add comment