- Создано ядро MVC: App, Router, Controller, Model, View, Database - Созданы модели: User, Product, Category, Cart, Order - Созданы контроллеры: Home, Auth, Product, Cart, Order, Page, Admin - Созданы layouts и partials для представлений - Добавлены все views для страниц - Настроена маршрутизация с чистыми URL - Обновлена конфигурация Docker и Apache для mod_rewrite - Добавлена единая точка входа public/index.php
305 lines
14 KiB
PHP
305 lines
14 KiB
PHP
<?php
|
||
$title = 'Корзина';
|
||
use App\Core\View;
|
||
?>
|
||
|
||
<style>
|
||
.main__content { display: flex; gap: 30px; margin: 30px 0; }
|
||
.products { flex: 1; }
|
||
.order { width: 400px; background: #f8f9fa; padding: 25px; border-radius: 8px; height: fit-content; }
|
||
.products__list { display: flex; flex-direction: column; gap: 15px; }
|
||
.products__item { display: flex; gap: 15px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
|
||
.products__image { width: 100px; height: 100px; flex-shrink: 0; }
|
||
.products__image img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; }
|
||
.products__details { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }
|
||
.products__name { font-weight: bold; color: #453227; }
|
||
.products__price { color: #617365; font-size: 18px; font-weight: bold; }
|
||
.products__controls { display: flex; align-items: center; gap: 15px; }
|
||
.products__quantity { display: flex; align-items: center; gap: 8px; }
|
||
.products__qty-btn { width: 30px; height: 30px; border: 1px solid #ddd; background: white; cursor: pointer; border-radius: 4px; }
|
||
.products__qty-value { padding: 5px 10px; }
|
||
.products__cart-icon { background: none; border: none; color: #dc3545; cursor: pointer; font-size: 18px; }
|
||
.empty-cart { text-align: center; padding: 40px; color: #666; }
|
||
.order__title { color: #453227; margin-bottom: 20px; }
|
||
.order__section { margin-bottom: 20px; }
|
||
.order__section-title { color: #453227; font-size: 14px; margin-bottom: 10px; }
|
||
.form__radio-group { display: flex; flex-direction: column; gap: 10px; margin-bottom: 15px; }
|
||
.form__radio-label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
|
||
.form__input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 10px; }
|
||
.form__row { display: flex; gap: 10px; }
|
||
.summary { margin: 20px 0; }
|
||
.summary__item { display: flex; justify-content: space-between; padding: 8px 0; }
|
||
.summary__item.total { font-weight: bold; font-size: 18px; border-top: 2px solid #453227; margin-top: 10px; padding-top: 15px; }
|
||
.order-btn { width: 100%; background: #453227; color: white; padding: 15px; border: none; border-radius: 4px; font-size: 16px; font-weight: bold; cursor: pointer; }
|
||
.order-btn:hover { background: #617365; }
|
||
.promo { display: flex; gap: 10px; margin-bottom: 20px; }
|
||
.promo__input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
|
||
.promo__btn { background: #617365; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; }
|
||
.privacy { display: flex; align-items: center; gap: 8px; margin-top: 15px; font-size: 14px; color: #666; }
|
||
</style>
|
||
|
||
<main class="container">
|
||
<div class="breadcrumbs">
|
||
<a href="/cite_practica/">Главная</a> • <span class="current-page">Корзина</span>
|
||
</div>
|
||
|
||
<h2 style="color: #453227; margin: 20px 0;">Товары в корзине</h2>
|
||
|
||
<?php if (empty($cartItems)): ?>
|
||
<div class="empty-cart">
|
||
<i class="fas fa-shopping-cart" style="font-size: 48px; color: #ccc; margin-bottom: 20px;"></i>
|
||
<p>Ваша корзина пуста</p>
|
||
<a href="/cite_practica/catalog" class="btn primary-btn" style="margin-top: 20px; display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
|
||
Продолжить покупки
|
||
</a>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="main__content">
|
||
<section class="products">
|
||
<div class="products__list" id="cartItems">
|
||
<?php foreach ($cartItems as $item): ?>
|
||
<div class="products__item" data-product-id="<?= $item['product_id'] ?>" data-price="<?= $item['price'] ?>">
|
||
<div class="products__image">
|
||
<img src="/cite_practica/<?= htmlspecialchars($item['image_url'] ?? 'img/1.jpg') ?>"
|
||
alt="<?= htmlspecialchars($item['name']) ?>">
|
||
</div>
|
||
<div class="products__details">
|
||
<div class="products__name"><?= htmlspecialchars($item['name']) ?></div>
|
||
<div class="products__price"><?= View::formatPrice($item['price']) ?></div>
|
||
<div class="products__controls">
|
||
<div class="products__quantity">
|
||
<button class="products__qty-btn minus" data-id="<?= $item['product_id'] ?>">-</button>
|
||
<span class="products__qty-value"><?= $item['quantity'] ?></span>
|
||
<button class="products__qty-btn plus" data-id="<?= $item['product_id'] ?>">+</button>
|
||
</div>
|
||
<button class="products__cart-icon remove-from-cart" data-id="<?= $item['product_id'] ?>">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="order">
|
||
<form id="orderForm">
|
||
<h2 class="order__title">Оформление заказа</h2>
|
||
|
||
<div class="order__section">
|
||
<h3 class="order__section-title">СПОСОБ ДОСТАВКИ</h3>
|
||
<div class="form__radio-group">
|
||
<label class="form__radio-label">
|
||
<input type="radio" name="delivery" value="courier" checked>
|
||
Курьерская доставка
|
||
</label>
|
||
<label class="form__radio-label">
|
||
<input type="radio" name="delivery" value="pickup">
|
||
Самовывоз
|
||
</label>
|
||
</div>
|
||
|
||
<input type="text" name="full_name" class="form__input" placeholder="ФИО..."
|
||
value="<?= htmlspecialchars($user['full_name'] ?? '') ?>" required>
|
||
<div class="form__row">
|
||
<input type="tel" name="phone" class="form__input" placeholder="Телефон..."
|
||
value="<?= htmlspecialchars($user['phone'] ?? '') ?>" required style="flex: 1;">
|
||
<input type="email" name="email" class="form__input" placeholder="E-mail..."
|
||
value="<?= htmlspecialchars($user['email'] ?? '') ?>" required style="flex: 1;">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="order__section">
|
||
<h3 class="order__section-title">СПОСОБ ОПЛАТЫ</h3>
|
||
<div class="form__radio-group">
|
||
<label class="form__radio-label">
|
||
<input type="radio" name="payment" value="card" checked>
|
||
Банковская карта
|
||
</label>
|
||
<label class="form__radio-label">
|
||
<input type="radio" name="payment" value="cash">
|
||
Наличные
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form__row">
|
||
<input type="text" name="region" class="form__input" placeholder="Регион..." required style="flex: 1;">
|
||
<input type="text" name="postal_code" class="form__input" placeholder="Индекс..." style="flex: 1;">
|
||
</div>
|
||
<input type="text" name="address" class="form__input" placeholder="Улица, дом, квартира..." required>
|
||
</div>
|
||
|
||
<div class="promo">
|
||
<input type="text" id="promo_code" name="promo_code" class="promo__input" placeholder="Промокод">
|
||
<button type="button" class="promo__btn" id="applyPromo">ПРИМЕНИТЬ</button>
|
||
</div>
|
||
|
||
<input type="hidden" name="discount" value="0">
|
||
<input type="hidden" name="delivery_price" value="2000">
|
||
|
||
<div class="summary">
|
||
<div class="summary__item">
|
||
<span>Товары, <span class="summary-count"><?= $totalQuantity ?></span> шт.</span>
|
||
<span class="products-total"><?= View::formatPrice($totalAmount) ?></span>
|
||
</div>
|
||
<div class="summary__item">
|
||
<span>Скидка</span>
|
||
<span class="discount-total">0 ₽</span>
|
||
</div>
|
||
<div class="summary__item">
|
||
<span>Доставка</span>
|
||
<span class="delivery-price">2 000 ₽</span>
|
||
</div>
|
||
<div class="summary__item total">
|
||
<span>ИТОГО:</span>
|
||
<span class="final-total"><?= View::formatPrice($totalAmount + 2000) ?></span>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="submit" class="order-btn" id="submit-order">ОФОРМИТЬ ЗАКАЗ</button>
|
||
|
||
<label class="privacy">
|
||
<input type="checkbox" id="privacy-checkbox" required>
|
||
Даю согласие на обработку персональных данных
|
||
</label>
|
||
</form>
|
||
</section>
|
||
</div>
|
||
<?php endif; ?>
|
||
</main>
|
||
|
||
<script>
|
||
$(document).ready(function() {
|
||
// Обновление количества
|
||
$('.products__qty-btn').on('click', function(e) {
|
||
e.preventDefault();
|
||
const productId = $(this).data('id');
|
||
const isPlus = $(this).hasClass('plus');
|
||
const $qtyValue = $(this).siblings('.products__qty-value');
|
||
let quantity = parseInt($qtyValue.text());
|
||
|
||
if (isPlus) { quantity++; }
|
||
else if (quantity > 1) { quantity--; }
|
||
else { return; }
|
||
|
||
$qtyValue.text(quantity);
|
||
|
||
$.ajax({
|
||
url: '/cite_practica/cart/update',
|
||
method: 'POST',
|
||
data: { product_id: productId, quantity: quantity },
|
||
dataType: 'json',
|
||
success: function(result) {
|
||
if (result.success) {
|
||
updateTotals();
|
||
$('.cart-count').text(result.cart_count);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// Удаление товара
|
||
$('.remove-from-cart').on('click', function(e) {
|
||
e.preventDefault();
|
||
const productId = $(this).data('id');
|
||
const $item = $(this).closest('.products__item');
|
||
|
||
if (!confirm('Удалить товар из корзины?')) return;
|
||
|
||
$.ajax({
|
||
url: '/cite_practica/cart/remove',
|
||
method: 'POST',
|
||
data: { product_id: productId },
|
||
dataType: 'json',
|
||
success: function(result) {
|
||
if (result.success) {
|
||
$item.fadeOut(300, function() {
|
||
$(this).remove();
|
||
updateTotals();
|
||
$('.cart-count').text(result.cart_count);
|
||
if ($('#cartItems .products__item').length === 0) {
|
||
location.reload();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
function updateTotals() {
|
||
let productsTotal = 0;
|
||
let totalCount = 0;
|
||
|
||
$('.products__item').each(function() {
|
||
const price = parseInt($(this).data('price'));
|
||
const quantity = parseInt($(this).find('.products__qty-value').text());
|
||
productsTotal += price * quantity;
|
||
totalCount += quantity;
|
||
});
|
||
|
||
const delivery = parseFloat($('input[name="delivery_price"]').val());
|
||
const discount = parseFloat($('input[name="discount"]').val());
|
||
const finalTotal = productsTotal + delivery - discount;
|
||
|
||
$('.products-total').text(productsTotal.toLocaleString('ru-RU') + ' ₽');
|
||
$('.summary-count').text(totalCount);
|
||
$('.final-total').text(finalTotal.toLocaleString('ru-RU') + ' ₽');
|
||
}
|
||
|
||
// Промокод
|
||
$('#applyPromo').click(function() {
|
||
const promoCode = $('#promo_code').val().toUpperCase();
|
||
if (promoCode === 'SALE10') {
|
||
const productsTotal = parseFloat($('.products-total').text().replace(/[^0-9]/g, ''));
|
||
const discount = Math.round(productsTotal * 0.1);
|
||
$('input[name="discount"]').val(discount);
|
||
$('.discount-total').text(discount.toLocaleString('ru-RU') + ' ₽');
|
||
showNotification('Промокод применен! Скидка 10%');
|
||
updateTotals();
|
||
} else if (promoCode === 'FREE') {
|
||
$('input[name="delivery_price"]').val(0);
|
||
$('.delivery-price').text('0 ₽');
|
||
showNotification('Промокод применен! Бесплатная доставка');
|
||
updateTotals();
|
||
} else if (promoCode) {
|
||
showNotification('Промокод недействителен', 'error');
|
||
}
|
||
});
|
||
|
||
// Оформление заказа
|
||
$('#orderForm').submit(function(e) {
|
||
e.preventDefault();
|
||
|
||
if (!$('#privacy-checkbox').is(':checked')) {
|
||
showNotification('Необходимо согласие на обработку данных', 'error');
|
||
return;
|
||
}
|
||
|
||
$('#submit-order').prop('disabled', true).text('ОБРАБОТКА...');
|
||
|
||
$.ajax({
|
||
url: '/cite_practica/order',
|
||
method: 'POST',
|
||
data: $(this).serialize(),
|
||
dataType: 'json',
|
||
success: function(result) {
|
||
if (result.success) {
|
||
showNotification('Заказ успешно оформлен!');
|
||
setTimeout(function() {
|
||
window.location.href = '/cite_practica/';
|
||
}, 1500);
|
||
} else {
|
||
showNotification('Ошибка: ' + result.message, 'error');
|
||
$('#submit-order').prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ');
|
||
}
|
||
},
|
||
error: function() {
|
||
showNotification('Ошибка сервера', 'error');
|
||
$('#submit-order').prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
|