[MVC] Полная миграция на MVC архитектуру
- Создано ядро 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
This commit is contained in:
304
app/Views/cart/checkout.php
Normal file
304
app/Views/cart/checkout.php
Normal file
@@ -0,0 +1,304 @@
|
||||
<?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>
|
||||
|
||||
Reference in New Issue
Block a user