В одном из личных проектов, на сайте МебельДагестана пришлось делать калькулятор доставки. Так как почти всех клиентов которых интересует корпусная мебель на заказ, впервую очередь интересуют вопросы доставки и сборки. Готовых решений я не нашел. Были конечно модули от самых транспортных компаний но они мне показались сложными в настройке да еще и требовали согласования с самой транспортной компанией. Потому было решено делать свой калькулятор доствка на сайте.
Добавляем tabs с доставкой на странице товара
Дизайн предполагался простым, 2 колонки. 1 колонка с полями ввода значений, вторая колонка с результирующей стоимостью и кнопкой расчета. Логика не предполагала сложных расчетов. Расчет все таки должен давать ориентировочную цену доставки. Точную цену в любом случае дать не получится, так как логисты транспортной все будут рассчитывать по своим коэффициентам.
Вставить на сайт форму я решил в дополнительный вкладке доп информации woocommerce./
Так было до. А дальше скрин после добавления вкладки с рассчетом доставки.
Код добавления вкладки следующий
add_filter( 'woocommerce_product_tabs', 'woo_delivery_tab' );
function woo_delivery_tab( $tabs ) {
// Только для категорий кухни и шкафы
global $product;
if ( has_term( array( 'Кухни', 'Шкафы-купе', 'Прихожие', 'Диваны' ), 'product_cat', $product->get_id() )) {
$tabs['woo_delivery_tab'] = array(
'title' => __( 'Доставка', 'woocommerce' ),
'priority' => 20,
'callback' => 'woo_delivery_tab_content'
);
return $tabs;
} else {
return $tabs;
}
}
function woo_delivery_tab_content() {
global $product;
// проверяем, принадлежит ли товар категории "Кухни"
if ( has_term( 'Кухни', 'product_cat', $product->get_id()) ) {
// Подключаем файл с нужным HTML-кодом
get_template_part( 'template-parts/tabs/delivery-kitchen', 25 );
}
}
При этом тут есть функция get_template_part( ‘template-parts/tabs/delivery-kitchen’, 25 ); которая подразумевает что у нас в папке дочерней темы по пути template-parts/tabs/ находится файл delivery-kitchen.php в котором мы и будем писать логику.
Пишем код калькулятора на html + js
Никаких данных из самого товара я решил не подтягивать, а потому на php ничего писать не пришлось. Код файла delivery-kitchen.php это html код и js скрипт. Все очень просто. Кстати почему файл так называется, потому что я для каждой категории товаров скорее всего буду делать свой калькулятор, так как вариант просчета доставка для кухни и например дивана будет немного отличаться.
Итак код верстки самой формы такой.
<div class="calculator">
<div class="delivery-form">
<div>
<h2 class="has-text-align-center wp-block-heading" access="false" id="control-1417866">Рассчитать стоимость доставки</h2>
</div>
<form>
<div>
<label for="size">Размер (пог. метр):</label>
<input type="number" id="size" name="size" value="3" placeholder="Длина кухни/шкафа в погонных метрах" required>
<div id="size-error" class="invalid-feedback"></div>
</div>
<div>
<label for="delivery_city">Куда:</label>
<select name="delivery_city" id="delivery_city" required>
<option value="Москва">Москва</option>
<option value="Санкт-Петербург">Санкт-Петербург</option>
<option value="Новосибирск">Новосибирск</option>
<option value="Екатеринбург">Екатеринбург</option>
<option value="Нижний Новгород">Нижний Новгород</option>
<option value="Казань">Казань</option>
<option value="Челябинск">Челябинск</option>
<option value="Омск">Омск</option>
<option value="Самара">Самара</option>
<option value="Ростов-на-Дону">Ростов-на-Дону</option>
<option value="Уфа">Уфа</option>
<option value="Красноярск">Красноярск</option>
<option value="Пермь">Пермь</option>
<option value="Воронеж">Воронеж</option>
<option value="Волгоград">Волгоград</option>
</select>
</div>
<div>
<label for="weight">Вес груза (кг):</label>
<input type="number" id="weight" name="weight" min="1" max="10000" placeholder="Укажите вес" value="150" required>
<div id="weight-error" class="invalid-feedback"></div>
</div>
<div>
<label for="volume">Объем груза (м³):</label>
<input type="number" id="volume" name="volume" min="0.7" step="0.1" placeholder="Укажите объем" required value="2.1">
<div id="volume-error" class="invalid-feedback"></div>
</div>
</form>
</div>
<div class="result-column">
<div class="result-wrapper">
<h2 class="has-text-align-center wp-block-heading">Стоимость доставки</h2>
<p id="total_cost" class="result-value">0 руб.</p>
<div>
<button class="calculate-btn" type="button" onclick="calculateDelivery()">Рассчитать</button>
</div>
</div>
</div>
</div>
Тут ничего особенного 2 колонки flex блоков. В первой элементы ввода во второй расчет стоимости. Стоит отметить что код самой формы почти на 80% написан с использованием chatGPT.
Далее привожу часть файла в которой располагается js код
<script>
const citySelect = document.getElementById("delivery_city");
const weightInput = document.getElementById("weight");
const volumeInput = document.getElementById("volume");
const sizeInput = document.getElementById("size");
const totalCostDiv = document.getElementById("total_cost");
const sizeError = document.getElementById("size-error");
const weightError = document.getElementById("weight-error");
const volumeError = document.getElementById("volume-error");
function calculateDelivery() {
document.getElementById("total_cost").innerHTML = "";
const city = citySelect.value;
const weight = weightInput.value;
const volume = volumeInput.value;
const size = parseFloat(sizeInput.value);;
let cost = 0;
switch (city) {
case "Москва":
cost = 500;
break;
case "Санкт-Петербург":
cost = 600;
break;
case "Новосибирск":
cost = 1000;
break;
case "Екатеринбург":
cost = 900;
break;
case "Нижний Новгород":
cost = 700;
break;
case "Казань":
cost = 800;
break;
case "Челябинск":
cost = 1000;
break;
case "Омск":
cost = 1200;
break;
case "Самара":
cost = 900;
break;
case "Ростов-на-Дону":
cost = 1100;
break;
case "Уфа":
cost = 1100;
break;
case "Красноярск":
cost = 1300;
break;
case "Пермь":
cost = 1000;
break;
case "Воронеж":
cost = 800;
break;
case "Волгоград":
cost = 1200;
break;
default:
break;
}
if (isNaN(weight) || weight < 50) {
weightError.textContent = 'Введите корректное значение веса';
weightInput.classList.add('is-invalid');
} else {
weightError.textContent = '';
weightInput.classList.remove('is-invalid');
// Округляем вес до ближайшего числа, оканчивающегося на 50
let roundedWeight = Math.ceil(weight/50) * 50;
// Вычисляем коэффициент увеличения стоимости
let coefficient = roundedWeight/50 + 1;
// Умножаем стоимость доставки на коэффициент
cost *= coefficient;
}
if (isNaN(size) || size < 1) {
sizeError.textContent = 'Введите корректное значение размера';
sizeInput.classList.add('is-invalid');
} else {
sizeError.textContent = '';
sizeInput.classList.remove('is-invalid');
cost = Math.ceil(cost *size);
}
if (isNaN(volume) || volume < 0.7) {
volumeError.textContent = 'Введите корректное значение объема';
volumeInput.classList.add('is-invalid');
} else {
volumeError.textContent = '';
volumeInput.classList.remove('is-invalid');
cost += (volume/0.7)*1036;
}
totalCostDiv.innerHTML = `${cost} руб.`;
}
sizeInput.addEventListener('input', function() {
totalCostDiv.innerHTML = '';
});
sizeInput.addEventListener('change', () => {
const sizeValue = parseFloat(sizeInput.value);
if (!isNaN(sizeValue)) {
//Изменяем вес с шагом 50
const weightValue = sizeValue * 50;
weightInput.value = weightValue;
//Изменяем объем с шагом 0.7
const volumeValue = (sizeValue * 0.7).toFixed(1);
volumeInput.value = volumeValue;
}
});
</script>
В целом по коду думаю итак все понятно. По id элементов формы мы получаем значения полей и в функции calculateDelivery() делаем свой рассчет. Также есть простая валидация на заполнение элементов ввода. Размера, объема и веса. Отмечу что я свою форму заполняю начальными данным, например размер 3 метра, вест 150 кг. объем 2.1 куба.
И при изменении размера также изменяю параметры груза с определнным шагом, для веса 50 кг, для объема 0.7 кубов. Цифры для себя я взял эмпирические у вас они могут быть другими.
Ну и конечно же добавим стили css для адекватного отображения формы.
<style>
.is-invalid {
border-color: #dc3545; /* устанавливаем цвет рамки элемента */
padding-right: calc(1.5em + .75rem); /* увеличиваем правый отступ, чтобы было место для иконки ошибки */
background-image: url('path/to/error-icon.png'); /* устанавливаем изображение иконки ошибки */
background-repeat: no-repeat; /* запрещаем повторение изображения */
background-position: right calc(.375em + .1875rem) center; /* устанавливаем позицию иконки ошибки */
background-size: calc(.75em + .375rem) calc(.75em + .375rem); /* устанавливаем размер иконки ошибки */
}
.is-invalid:focus {
border-color: #dc3545; /* устанавливаем цвет рамки при фокусе */
box-shadow: 0 0 0 .2rem rgba(220,53,69,.25); /* добавляем тень при фокусе */
}
.is-invalid ~ .invalid-feedback {
display: block; /* показываем блок с сообщением об ошибке */
}
.invalid-feedback {
color: red;
font-size: 14px;
margin-top: 5px;
}
.calculator {
display: flex;
justify-content: space-between;
align-items: center;
}
.result-column {
flex-basis: 33.33%; /* одна треть ширины */
flex-grow: 1;
}
.result-wrapper {
background-color: #f5f5f5;
padding: 50px 10px;
border-radius: 5px;
text-align: center;
}
.result-text {
margin-bottom: 10px;
font-size: 16px;
font-weight: bold;
}
.result-value {
font-size: 30px;
font-weight: bold;
}
.delivery-form {
display: flex;
flex-direction: column;
flex-basis: 66.67%; /* две трети ширины */
flex-grow: 1;
padding-right: 20px;
gap: 10px;
font-family: Arial, sans-serif;
}
.delivery-form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.delivery-form select,
.delivery-form input[type="number"],
.delivery-form input[type="checkbox"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-bottom: 10px;
}
.delivery-form input[type="checkbox"] {
display: inline-block;
margin-right: 10px;
}
.calculate-btn {
padding: 10px 20px;
border-radius: 5px;
border: none;
background-color: rgb(26, 137, 23); /* зеленый цвет */
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
align-self: flex-start;
}
.calculate-btn:hover {
background-color: #136f13;
}
.delivery-form #total_cost {
margin-top: 10px;
font-weight: bold;
}
</style>
Итог
Вот так просто, припомощи chatGpt за 1 вечер можно создать красивую функциональную и адаптивную форму рассчета примерной стоимости доставки. Несомненно такая форма должна увеличить конверсию на сайте и благотворно повлиять на поведенческие факторы.

